Skip to content

Commit

Permalink
Add support for using environment variables in JVM tests (pantsbuild#…
Browse files Browse the repository at this point in the history
…16455)

The implementation approach is based on the recommendations found in pantsbuild#15963 and, since this affects to several targets, similar as in pantsbuild#16387, where a base class for the field is created in core and then extended for each of the different target types.
  • Loading branch information
alonsodomin authored and cczona committed Sep 1, 2022
1 parent ba7e68a commit 3434afd
Show file tree
Hide file tree
Showing 16 changed files with 1,546 additions and 73 deletions.
26 changes: 26 additions & 0 deletions docs/markdown/Java and Scala/jvm-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,32 @@ If a target sets its `timeout` higher than `[test].timeout_maximum`, Pants will

Use the option `./pants test --no-timeouts` to temporarily disable timeouts, e.g. when debugging.

### Setting environment variables

Test runs are _hermetic_, meaning that they are stripped of the parent `./pants` process's environment variables. This is important for reproducibility, and it also increases cache hits.

To add any arbitrary environment variable back to the process, you can either add the environment variable to the specific tests with the `extra_env_vars` field on `junit_test` / `junit_tests` / `scala_junit_test` / `scala_junit_tests` / `scalatest_test` / `scalatest_tests` targets or to all your tests with the `[test].extra_env_vars` option. Generally, prefer the field `extra_env_vars` field so that more of your tests are hermetic.

With both `[test].extra_env_vars` and the `extra_env_vars` field, you can either hardcode a value or leave off a value to "allowlist" it and read from the parent `./pants` process's environment.

```toml pants.toml
[test]
extra_env_vars = ["VAR1", "VAR2=hardcoded_value"]
```
```python project/BUILD
junit_tests(
name="tests",
# Adds to all generated `junit_test` targets,
# i.e. each file in the `sources` field.
extra_env_vars=["VAR3", "VAR4=hardcoded"],
# Even better, use `overrides` to be more granular.
overrides={
"StrUtilTest.java": {"extra_env_vars": ["VAR"]},
("DirUtilTest.java", "OSUtilTest.java"): {"extra_env_vars": ["VAR5"]},
},
)
```

Lint and Format
---------------

Expand Down
26 changes: 26 additions & 0 deletions docs/markdown/Java and Scala/kotlin.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,32 @@ To run tests, use `./pants test`:

The Kotlin backend currently supports JUnit tests specified using the `kotlin_junit_tests` target type.

### Setting environment variables

Test runs are _hermetic_, meaning that they are stripped of the parent `./pants` process's environment variables. This is important for reproducibility, and it also increases cache hits.

To add any arbitrary environment variable back to the process, you can either add the environment variable to the specific tests with the `extra_env_vars` field on `kotlin_junit_test` / `kotlin_junit_tests` targets or to all your tests with the `[test].extra_env_vars` option. Generally, prefer the field `extra_env_vars` field so that more of your tests are hermetic.

With both `[test].extra_env_vars` and the `extra_env_vars` field, you can either hardcode a value or leave off a value to "allowlist" it and read from the parent `./pants` process's environment.

```toml pants.toml
[test]
extra_env_vars = ["VAR1", "VAR2=hardcoded_value"]
```
```python project/BUILD
kotlin_junit_tests(
name="tests",
# Adds to all generated `kotlin_junit_test` targets,
# i.e. each file in the `sources` field.
extra_env_vars=["VAR3", "VAR4=hardcoded"],
# Even better, use `overrides` to be more granular.
overrides={
"StrUtilTest.kt": {"extra_env_vars": ["VAR"]},
("DirUtilTest.kt", "OSUtilTest.kt"): {"extra_env_vars": ["VAR5"]},
},
)
```

## Lint and Format

[`ktlint`](https://ktlint.github.io/) can be enabled by adding the `pants.backend.experimental.kotlin.lint.ktlint`
Expand Down
13 changes: 2 additions & 11 deletions src/python/pants/backend/go/target_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from pants.core.goals.package import OutputPathField
from pants.core.goals.run import RestartableField
from pants.core.goals.test import TestTimeoutField
from pants.core.goals.test import TestExtraEnvVarsField, TestTimeoutField
from pants.engine.addresses import Address
from pants.engine.engine_aware import EngineAwareParameter
from pants.engine.target import (
Expand All @@ -21,7 +21,6 @@
InvalidTargetException,
MultipleSourcesField,
StringField,
StringSequenceField,
Target,
TargetGenerator,
ValidNumbers,
Expand Down Expand Up @@ -180,16 +179,8 @@ class SkipGoTestsField(BoolField):
help = "If true, don't run this package's tests."


class GoTestExtraEnvVarsField(StringSequenceField):
class GoTestExtraEnvVarsField(TestExtraEnvVarsField):
alias = "test_extra_env_vars"
help = softwrap(
"""
Additional environment variables to include in test processes.
Entries are strings in the form `ENV_VAR=value` to use explicitly; or just
`ENV_VAR` to copy the value of a variable in Pants's own environment.
This will be merged with and override values from [test].extra_env_vars.
"""
)


class GoTestTimeoutField(TestTimeoutField):
Expand Down
8 changes: 7 additions & 1 deletion src/python/pants/backend/java/target_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)
from pants.jvm import target_types as jvm_target_types
from pants.jvm.target_types import (
JunitTestExtraEnvVarsField,
JunitTestSourceField,
JunitTestTimeoutField,
JvmDependenciesField,
Expand Down Expand Up @@ -63,6 +64,7 @@ class JunitTestTarget(Target):
*COMMON_TARGET_FIELDS,
JavaJunitTestSourceField,
JunitTestTimeoutField,
JunitTestExtraEnvVarsField,
JvmDependenciesField,
JvmResolveField,
JvmProvidesTypesField,
Expand All @@ -80,11 +82,15 @@ class JavaTestsGeneratorSourcesField(JavaGeneratorSourcesField):

class JunitTestsGeneratorTarget(TargetFilesGenerator):
alias = "junit_tests"
core_fields = (*COMMON_TARGET_FIELDS, JavaTestsGeneratorSourcesField, JunitTestTimeoutField)
core_fields = (
*COMMON_TARGET_FIELDS,
JavaTestsGeneratorSourcesField,
)
generated_target_cls = JunitTestTarget
copied_fields = COMMON_TARGET_FIELDS
moved_fields = (
JunitTestTimeoutField,
JunitTestExtraEnvVarsField,
JvmDependenciesField,
JvmJdkField,
JvmProvidesTypesField,
Expand Down
4 changes: 3 additions & 1 deletion src/python/pants/backend/kotlin/target_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
generate_multiple_sources_field_help_message,
)
from pants.jvm.target_types import (
JunitTestExtraEnvVarsField,
JunitTestSourceField,
JunitTestTimeoutField,
JvmJdkField,
Expand Down Expand Up @@ -137,6 +138,7 @@ class KotlinJunitTestTarget(Target):
KotlinJunitTestSourceField,
KotlincConsumedPluginIdsField,
JunitTestTimeoutField,
JunitTestExtraEnvVarsField,
JvmResolveField,
JvmJdkField,
JvmProvidesTypesField,
Expand All @@ -156,14 +158,14 @@ class KotlinJunitTestsGeneratorTarget(TargetFilesGenerator):
core_fields = (
*COMMON_TARGET_FIELDS,
KotlinJunitTestsGeneratorSourcesField,
JunitTestTimeoutField,
)
generated_target_cls = KotlinJunitTestTarget
copied_fields = COMMON_TARGET_FIELDS
moved_fields = (
KotlinJunitTestDependenciesField,
KotlincConsumedPluginIdsField,
JunitTestTimeoutField,
JunitTestExtraEnvVarsField,
JvmResolveField,
JvmJdkField,
JvmProvidesTypesField,
Expand Down
61 changes: 60 additions & 1 deletion src/python/pants/backend/kotlin/test/junit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from pants.backend.kotlin.subsystems.kotlin import DEFAULT_KOTLIN_VERSION
from pants.backend.kotlin.target_types import KotlinJunitTestsGeneratorTarget
from pants.backend.kotlin.test.junit import rules as kotlin_junit_rules
from pants.core.goals.test import TestResult
from pants.core.goals.test import TestResult, get_filtered_environment
from pants.core.target_types import FilesGeneratorTarget, FileTarget, RelocatedFiles
from pants.core.util_rules import config_files, source_files
from pants.core.util_rules.external_tool import rules as external_tool_rules
Expand All @@ -35,6 +35,7 @@
from pants.jvm.test.junit import JunitTestFieldSet
from pants.jvm.test.junit import rules as jvm_junit_rules
from pants.jvm.test.junit_test import run_junit_test
from pants.jvm.testutil import maybe_skip_jdk_test
from pants.jvm.util_rules import rules as util_rules
from pants.testutil.rule_runner import PYTHON_BOOTSTRAP_ENV, QueryRule, RuleRunner

Expand Down Expand Up @@ -78,6 +79,7 @@ def rule_runner() -> RuleRunner:
*kotlinc.rules(),
*kotlinc_plugins.rules(),
*kotlin_dep_inf_rules(),
get_filtered_environment,
QueryRule(CoarsenedTargets, (Addresses,)),
QueryRule(TestResult, (JunitTestFieldSet,)),
],
Expand All @@ -97,6 +99,7 @@ def rule_runner() -> RuleRunner:
return rule_runner


@maybe_skip_jdk_test
def test_vintage_kotlin_simple_success(
rule_runner: RuleRunner, jvm_lockfile: JVMLockfileFixture
) -> None:
Expand Down Expand Up @@ -135,3 +138,59 @@ def test_vintage_kotlin_simple_success(
assert re.search(r"Finished:\s+testHello", test_result.stdout) is not None
assert re.search(r"1 tests successful", test_result.stdout) is not None
assert re.search(r"1 tests found", test_result.stdout) is not None


@maybe_skip_jdk_test
def test_vintage_extra_env_vars(rule_runner: RuleRunner, jvm_lockfile: JVMLockfileFixture) -> None:
rule_runner.write_files(
{
"3rdparty/jvm/default.lock": jvm_lockfile.serialized_lockfile,
"3rdparty/jvm/BUILD": jvm_lockfile.requirements_as_jvm_artifact_targets(),
"BUILD": dedent(
"""\
kotlin_junit_tests(
name='example-test',
extra_env_vars=[
"JUNIT_TESTS_VAR_WITHOUT_VALUE",
"JUNIT_TESTS_VAR_WITH_VALUE=junit_tests_var_with_value",
"JUNIT_TESTS_OVERRIDE_WITH_VALUE_VAR=junit_tests_override_with_value_var_override",
],
)
"""
),
"ExtraEnvVarsTest.kt": dedent(
"""
package org.pantsbuild.example
import kotlin.test.Test
import kotlin.test.assertEquals
internal class ExtraEnvVarsTest {
@Test
fun testArgs() {
assertEquals(System.getenv("ARG_WITH_VALUE_VAR"), "arg_with_value_var");
assertEquals(System.getenv("ARG_WITHOUT_VALUE_VAR"), "arg_without_value_var");
assertEquals(System.getenv("JUNIT_TESTS_VAR_WITH_VALUE"), "junit_tests_var_with_value");
assertEquals(System.getenv("JUNIT_TESTS_VAR_WITHOUT_VALUE"), "junit_tests_var_without_value");
assertEquals(System.getenv("JUNIT_TESTS_OVERRIDE_WITH_VALUE_VAR"), "junit_tests_override_with_value_var_override");
}
}
"""
),
}
)

test_result = run_junit_test(
rule_runner,
"example-test",
"ExtraEnvVarsTest.kt",
extra_args=[
'--test-extra-env-vars=["ARG_WITH_VALUE_VAR=arg_with_value_var", "ARG_WITHOUT_VALUE_VAR", "JUNIT_TESTS_OVERRIDE_WITH_VALUE_VAR"]'
],
env={
"ARG_WITHOUT_VALUE_VAR": "arg_without_value_var",
"JUNIT_TESTS_VAR_WITHOUT_VALUE": "junit_tests_var_without_value",
"JUNIT_TESTS_OVERRIDE_WITH_VALUE_VAR": "junit_tests_override_with_value_var",
},
)
assert test_result.exit_code == 0
18 changes: 7 additions & 11 deletions src/python/pants/backend/python/target_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@
from pants.core.goals.generate_lockfiles import UnrecognizedResolveNamesError
from pants.core.goals.package import OutputPathField
from pants.core.goals.run import RestartableField
from pants.core.goals.test import RuntimePackageDependenciesField, TestSubsystem
from pants.core.goals.test import (
RuntimePackageDependenciesField,
TestExtraEnvVarsField,
TestSubsystem,
)
from pants.engine.addresses import Address, Addresses
from pants.engine.target import (
COMMON_TARGET_FIELDS,
Expand Down Expand Up @@ -850,16 +854,8 @@ def resolve_opt(*, option: str) -> Any:
return result


class PythonTestsExtraEnvVarsField(StringSequenceField):
alias = "extra_env_vars"
help = softwrap(
"""
Additional environment variables to include in test processes.
Entries are strings in the form `ENV_VAR=value` to use explicitly; or just
`ENV_VAR` to copy the value of a variable in Pants's own environment.
This will be merged with and override values from [test].extra_env_vars.
"""
)
class PythonTestsExtraEnvVarsField(TestExtraEnvVarsField):
pass


class SkipPythonTestsField(BoolField):
Expand Down
12 changes: 10 additions & 2 deletions src/python/pants/backend/scala/target_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from dataclasses import dataclass

from pants.backend.scala.subsystems.scala_infer import ScalaInferSubsystem
from pants.core.goals.test import TestTimeoutField
from pants.core.goals.test import TestExtraEnvVarsField, TestTimeoutField
from pants.engine.rules import collect_rules, rule
from pants.engine.target import (
COMMON_TARGET_FIELDS,
Expand All @@ -28,6 +28,7 @@
from pants.engine.unions import UnionRule
from pants.jvm import target_types as jvm_target_types
from pants.jvm.target_types import (
JunitTestExtraEnvVarsField,
JunitTestSourceField,
JunitTestTimeoutField,
JvmJdkField,
Expand Down Expand Up @@ -107,6 +108,10 @@ class ScalatestTestTimeoutField(TestTimeoutField):
pass


class ScalatestTestExtraEnvVarsField(TestExtraEnvVarsField):
pass


class ScalatestTestTarget(Target):
alias = "scalatest_test"
core_fields = (
Expand All @@ -115,6 +120,7 @@ class ScalatestTestTarget(Target):
ScalatestTestSourceField,
ScalaConsumedPluginNamesField,
ScalatestTestTimeoutField,
ScalatestTestExtraEnvVarsField,
JvmResolveField,
JvmProvidesTypesField,
JvmJdkField,
Expand Down Expand Up @@ -148,14 +154,14 @@ class ScalatestTestsGeneratorTarget(TargetFilesGenerator):
*COMMON_TARGET_FIELDS,
ScalatestTestsGeneratorSourcesField,
ScalatestTestsSourcesOverridesField,
ScalatestTestTimeoutField,
)
generated_target_cls = ScalatestTestTarget
copied_fields = COMMON_TARGET_FIELDS
moved_fields = (
ScalaDependenciesField,
ScalaConsumedPluginNamesField,
ScalatestTestTimeoutField,
ScalatestTestExtraEnvVarsField,
JvmJdkField,
JvmProvidesTypesField,
JvmResolveField,
Expand Down Expand Up @@ -186,6 +192,7 @@ class ScalaJunitTestTarget(Target):
ScalaJunitTestSourceField,
ScalaConsumedPluginNamesField,
JunitTestTimeoutField,
JunitTestExtraEnvVarsField,
JvmResolveField,
JvmProvidesTypesField,
JvmJdkField,
Expand Down Expand Up @@ -227,6 +234,7 @@ class ScalaJunitTestsGeneratorTarget(TargetFilesGenerator):
ScalaDependenciesField,
ScalaConsumedPluginNamesField,
JunitTestTimeoutField,
JunitTestExtraEnvVarsField,
JvmJdkField,
JvmProvidesTypesField,
JvmResolveField,
Expand Down
5 changes: 2 additions & 3 deletions src/python/pants/backend/scala/test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@

python_sources()

python_tests(
name="tests",
)
python_tests(name="tests", dependencies=[":test_resources"])
resources(name="test_resources", sources=["*.test.lock"])

0 comments on commit 3434afd

Please sign in to comment.