Skip to content

Commit

Permalink
Add proof-of-concept Java junit test rule. (#12177)
Browse files Browse the repository at this point in the history
This implementation is just good enough to demonstrate how to use the existing Pants Java infrastructure to compile and consume Java source for the purpose of executing junit tests. This initial iteration has several limitations:

* jUnit5 (org.junit.platform:junit-platform-console:1.7.2) is hard-coded as the JUnit runner in the rule source.  As needed, this can be hoisted into a subsystem for configurability.  By design, junit4 is not supported as a **runner**, because its classpath scanning isn't powerful enough.  However, junit4 tests can still be run with the junit5 runner.

* junit_tests targets have the same requirement of java_library targets that there must be exactly 1 coursier_lockfile dependency in the transitive closure of the junit_tests target. In practice this means that any third party dependencies required by the test source must also be shared by the library targets upon which the test transitively depends. Lockfile subsetting will mostly make this a non-issue, but it's still unfortunate that all test targets are indirectly locked to all other test targets that transitively depend on the same Java library code.

* Due to #12293, the test runner currently hard-codes the Coursier `--system-jvm` argument.  Future revisions will expose this as an option via `junit_test` parameters and/or a junit subsystem.

[ci skip-rust]
[ci skip-build-wheels]
  • Loading branch information
patricklaw authored Sep 1, 2021
1 parent 8c319ae commit 1552119
Show file tree
Hide file tree
Showing 4 changed files with 737 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/python/pants/backend/java/test/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

python_library()
python_tests(name="tests", timeout=240)
Empty file.
125 changes: 125 additions & 0 deletions src/python/pants/backend/java/test/junit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import logging
from dataclasses import dataclass

from pants.backend.java.compile.javac import CompiledClassfiles, CompileJavaSourceRequest
from pants.backend.java.target_types import JavaTestsSources
from pants.core.goals.test import TestFieldSet, TestResult
from pants.engine.addresses import Addresses
from pants.engine.fs import AddPrefix, Digest, MergeDigests
from pants.engine.process import FallibleProcessResult, Process
from pants.engine.rules import Get, MultiGet, collect_rules, rule
from pants.engine.target import (
CoarsenedTargets,
Targets,
TransitiveTargets,
TransitiveTargetsRequest,
)
from pants.engine.unions import UnionRule
from pants.jvm.resolve.coursier_fetch import (
CoursierLockfileForTargetRequest,
CoursierResolvedLockfile,
MaterializedClasspath,
MaterializedClasspathRequest,
MavenRequirements,
)
from pants.jvm.resolve.coursier_setup import Coursier
from pants.util.logging import LogLevel

logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class JavaTestFieldSet(TestFieldSet):
required_fields = (JavaTestsSources,)

sources: JavaTestsSources


@rule(desc="Run JUnit", level=LogLevel.DEBUG)
async def run_junit_test(
coursier: Coursier,
field_set: JavaTestFieldSet,
) -> TestResult:
transitive_targets = await Get(TransitiveTargets, TransitiveTargetsRequest([field_set.address]))
coarsened_targets = await Get(
CoarsenedTargets, Addresses(t.address for t in transitive_targets.closure)
)
lockfile = await Get(
CoursierResolvedLockfile,
CoursierLockfileForTargetRequest(Targets(transitive_targets.closure)),
)
materialized_classpath = await Get(
MaterializedClasspath,
MaterializedClasspathRequest(
prefix="__thirdpartycp",
lockfiles=(lockfile,),
maven_requirements=(
MavenRequirements.create_from_maven_coordinates_fields(
fields=(),
additional_requirements=[
"org.junit.platform:junit-platform-console:1.7.2",
"org.junit.jupiter:junit-jupiter-engine:5.7.2",
"org.junit.vintage:junit-vintage-engine:5.7.2",
],
),
),
),
)
transitive_user_classfiles = await MultiGet(
Get(CompiledClassfiles, CompileJavaSourceRequest(component=t)) for t in coarsened_targets
)
merged_transitive_user_classfiles_digest = await Get(
Digest, MergeDigests(classfiles.digest for classfiles in transitive_user_classfiles)
)
usercp_relpath = "__usercp"
prefixed_transitive_user_classfiles_digest = await Get(
Digest, AddPrefix(merged_transitive_user_classfiles_digest, usercp_relpath)
)
merged_digest = await Get(
Digest,
MergeDigests(
(
prefixed_transitive_user_classfiles_digest,
materialized_classpath.digest,
coursier.digest,
)
),
)
proc = Process(
argv=[
coursier.coursier.exe,
"java",
"--system-jvm", # TODO(#12293): use a fixed JDK version from a subsystem.
"-cp",
materialized_classpath.classpath_arg(),
"org.junit.platform.console.ConsoleLauncher",
"--classpath",
usercp_relpath,
"--scan-class-path",
usercp_relpath,
],
input_digest=merged_digest,
description=f"Run JUnit 5 ConsoleLauncher against {field_set.address}",
level=LogLevel.DEBUG,
)

process_result = await Get(
FallibleProcessResult,
Process,
proc,
)

return TestResult.from_fallible_process_result(
process_result,
address=field_set.address,
)


def rules():
return [
*collect_rules(),
UnionRule(TestFieldSet, JavaTestFieldSet),
]
Loading

0 comments on commit 1552119

Please sign in to comment.