Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue/574 sys props restore v2 #700

Merged
merged 67 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
2c87077
Partial work
ee-usgs Dec 5, 2022
2828e1c
partial work
ee-usgs Dec 5, 2022
13af73f
Initial code in place - all existing tests pass
ee-usgs Dec 6, 2022
abd7aa0
Fixed one bug and added the first tests.
ee-usgs Dec 6, 2022
31c1d64
Improved typing and basic tests. Need more.
ee-usgs Dec 7, 2022
833ab00
Fixed javadoc error
ee-usgs Dec 8, 2022
6fb6626
Fixed tests, fixed restore bug when Props has defaults
ee-usgs Dec 10, 2022
f477c9c
Several logic and test fixes
ee-usgs Dec 11, 2022
8a00c4c
New PropertiesAssert for use in tests
ee-usgs Dec 16, 2022
c15138b
Much improved PropertiesAssert and related tests
ee-usgs Dec 17, 2022
44ea0d5
SystemPropertyExtension is complete w/ tests
ee-usgs Dec 19, 2022
7f36717
Merge branch 'main' of github.com:junit-pioneer/junit-pioneer into is…
ee-usgs Dec 21, 2022
b48f941
rm unused import
ee-usgs Dec 22, 2022
27b401d
Reformat imports
ee-usgs Dec 23, 2022
ae2ba8d
Improve javadocs & formatting
ee-usgs Dec 23, 2022
2018386
Documentation and example for sys props
ee-usgs Jan 2, 2023
099f6a6
Finished Sys Prop docs
ee-usgs Jan 2, 2023
5bc0133
A few added sys prop tests
ee-usgs Jan 2, 2023
10b9439
Fixed logic error in EnvVars restore
ee-usgs Jan 3, 2023
517afb7
Much improved testing for RestoreEnvironmentVariables
ee-usgs Jan 4, 2023
d20bb3a
Merge branch 'main' of github.com:junit-pioneer/junit-pioneer into is…
ee-usgs Jan 4, 2023
e0b7d6c
More tests, javadocs and Env Var user docs
ee-usgs Jan 4, 2023
90aee99
Add my listing in the ReadMe file
ee-usgs Jan 4, 2023
c0c99a4
Spotless java formatting
ee-usgs Jan 5, 2023
961c51a
Updated docs-nav.yml and package-info.java
ee-usgs Jan 5, 2023
b9e0c8d
Fix review issues
ee-usgs Jan 5, 2023
b52b272
* rm demo reference to Env Var Utility method (not supported)
ee-usgs Jan 8, 2023
f284521
Grammer fix
ee-usgs Jan 8, 2023
d5d62fa
Added a demo and section for combining annotations
ee-usgs Jan 15, 2023
fa96c2a
Fixed older typo
ee-usgs Jan 17, 2023
45d6523
Merge branch 'main' of github.com:junit-pioneer/junit-pioneer into is…
ee-usgs Feb 14, 2023
789d8b6
Fix grammer / name ref
eeverman Feb 14, 2023
86abac2
Grammer fix
eeverman Feb 14, 2023
5c7397a
Clarify docs
eeverman Feb 14, 2023
b1babd5
Fix grammer
eeverman Feb 14, 2023
5507bfa
Make doc clearer
eeverman Feb 14, 2023
79f4273
rm empty comment
eeverman Feb 14, 2023
3880e6e
Update src/main/java/org/junitpioneer/jupiter/AbstractEntryBasedExten…
eeverman Feb 16, 2023
3b0ab61
Update src/main/java/org/junitpioneer/jupiter/AbstractEntryBasedExten…
eeverman Feb 16, 2023
05e1233
Update src/test/java/org/junitpioneer/testkit/assertion/PropertiesAss…
eeverman Feb 16, 2023
55c3c28
Lots of small changes and renames per code review
ee-usgs Feb 16, 2023
9cbec30
Additional review changes
ee-usgs Feb 16, 2023
e97fd9c
Update src/main/java/org/junitpioneer/jupiter/AbstractEntryBasedExten…
eeverman Feb 16, 2023
02576a2
Update src/main/java/org/junitpioneer/jupiter/AbstractEntryBasedExten…
eeverman Feb 16, 2023
0be21bf
Update src/main/java/org/junitpioneer/jupiter/AbstractEntryBasedExten…
eeverman Feb 16, 2023
0e41c7c
Bunch more small changes
ee-usgs Feb 16, 2023
dacaf95
Merge branch 'issue/574-sys-props-restore-v2' of github.com:eeverman/…
ee-usgs Feb 16, 2023
35f83f3
Update src/main/java/org/junitpioneer/jupiter/RestoreEnvironmentVaria…
eeverman Feb 16, 2023
828a235
Update src/main/java/org/junitpioneer/jupiter/RestoreEnvironmentVaria…
eeverman Feb 16, 2023
a0e78fd
Addressed demo / doc comments
ee-usgs Mar 7, 2023
371efe0
apply spotless
ee-usgs Mar 7, 2023
030e021
remove all final variables in method sigs and in methods
ee-usgs Mar 7, 2023
f729c82
Add better 'Env Vars are immutable' warning
ee-usgs Mar 7, 2023
af0c193
Switch to single space instead of two after a period
eeverman Mar 7, 2023
16cf5ab
apply spotless
ee-usgs Mar 7, 2023
098e110
Merge branch 'issue/574-sys-props-restore-v2' of github.com:eeverman/…
ee-usgs Mar 7, 2023
cebdc97
Fix html whitespace.
eeverman Mar 7, 2023
c813fc4
Fix html whitespace.
eeverman Mar 7, 2023
2fb9dca
Simplified javadoc reference
eeverman Mar 7, 2023
481c410
Several javadoc formatting fixes in SystemPropertyExtension
ee-usgs Mar 7, 2023
adab6ae
Lots of changes, mostly javadoc formatting, removing method qualifier…
ee-usgs Mar 7, 2023
e33eec8
Moved PropertiesAssert static assertThat method to PioneerAssertions …
ee-usgs Mar 8, 2023
c02ef2f
Format
beatngu13 Jun 14, 2023
5527f9f
Remove unnecessary stream
beatngu13 Jun 14, 2023
702b231
Make final
beatngu13 Jun 14, 2023
325a0ca
Remove unnecessary access modifier
beatngu13 Jun 14, 2023
c16e01f
Use expression lambdas
beatngu13 Jun 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ Thank you for your efforts! 🙏

The least we can do is to thank them and list some of their accomplishments here (in lexicographic order).

#### 2023
* [Eric Everman](https://github.com/eeverman) added `@RestoreSystemProperties` and `@RestoreEnvironmentVariables` annotations to the [System Properties](https://junit-pioneer.org/docs/system-properties/) and [Environment Variables](https://junit-pioneer.org/docs/environment-variables/) extensions (#574 / #700)

#### 2022

* [Filip Hrisafov](https://github.com/filiphr) contributed the [JSON Argument Source](https://junit-pioneer.org/docs/json-argument-source/) support (#101 / #492)
Expand Down
4 changes: 2 additions & 2 deletions docs/docs-nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
children:
- title: "Cartesian Product of Parameters"
url: /docs/cartesian-product/
- title: "Clearing or Setting System Properties"
- title: "Clear, Set, and Restore System Properties"
url: /docs/system-properties/
- title: "Clearing or Setting Environment Variables"
- title: "Clear, Set, and Restore Environment Variables"
url: /docs/environment-variables/
- title: "Default Locale and TimeZone"
url: /docs/default-locale-timezone/
Expand Down
66 changes: 60 additions & 6 deletions docs/environment-variables.adoc
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
:page-title: Clearing or Setting Environment Variables
:page-description: The JUnit 5 (Jupiter) extensions `@ClearEnvironmentVariable`/`@SetEnvironmentVariable` clear/set the values of environment variables for the duration of a test
:page-title: Clear, Set, and Restore Environment Variables
:page-description: The JUnit 5 (Jupiter) extensions `@ClearEnvironmentVariable`, `@SetEnvironmentVariable` and `@RestoreEnvironmentVariables` clear/set/restore the values of environment variables for the duration of a test, and/or restore them after
:xp-demo-dir: ../src/demo/java
:demo: {xp-demo-dir}/org/junitpioneer/jupiter/EnvironmentVariablesExtensionDemo.java

== `@ClearEnvironmentVariable` and `@SetEnvironmentVariable`
The `@ClearEnvironmentVariable` and `@SetEnvironmentVariable` annotations can be used to clear and set, respectively, the values of environment variables for a test execution.
Both annotations work on the test method and class level, are repeatable, combinable, and inherited from higher-level containers.
After the annotated method has been executed, the variables mentioned in the annotation will be restored to their original value or the value of the higher-level container, or will be cleared if they didn't have one before.
Other environment variables that are changed during the test, are *not* restored.
Other environment variables that are changed during the test, are *not* restored (unless the `@RestoreEnvironmentVariables` is used).

[WARNING]
====
Expand Down Expand Up @@ -51,6 +52,59 @@ Method-level configurations are visible in both `@BeforeEach` setup methods and
Since v1.7.0, a class-level configuration means that the specified environment variables are cleared/set before and reset after each individual test in the annotated class.
====

== `@RestoreEnvironmentVariables`
`@RestoreEnvironmentVariables` can be used to restore changes to environment variables made directly in code.
While `@ClearEnvironmentVariable` and `@SetEnvironmentVariable` set or clear specific variables and values, they don't allow values to be calculated or parameterized, thus there are times you may want to directly set them in your test code.
`@RestoreEnvironmentVariables` can be placed on test methods or test classes and will completely restore all environment variables to their original state after a test or test class is complete.

In this example, `@RestoreEnvironmentVariables` is used on a test method, ensuring any changes made in that method are restored:

[source,java,indent=0]
----
include::{demo}[tag=environment_method_restore_test]
----

[NOTE]
====
Modifying environment variables of a running JVM requires several lines of code and the Java reflection API.
The two `@RestoreEnvironmentVariables` examples leave out that detail and use a hypothetical `setEnvVar()` method.
====

When `@RestoreEnvironmentVariables` is used on a test class, any environment variable changes made during the entire lifecycle of the test class, including test methods, `@BeforeAll`, `@BeforeEach` and 'after' methods, are restored after the test class' lifecycle is complete.
In addition, the annotation is inherited by each test method just as if each one was annotated with `@RestoreEnvironmentVariables`.

In the following example, both test methods see the environment variable changes made in `@BeforeAll` and `@BeforeEach`, however, the test methods are isolated from each other (`isolatedTest2` does not 'see' changes made in `isolatedTest1`).
As shown in the second example below, the class-level `@RestoreEnvironmentVariables` ensures that environment variable changes made within the annotated class are completely restored after the class's lifecycle, ensuring that changes are not visible to `SomeOtherTestClass`.
Note that `SomeOtherTestClass` uses the `@ReadsEnvironmentVariable` annotation: This ensures that JUnit does not schedule the class to run during any test known to modify environment variables (see <<Thread-Safety>>).

[source,java,indent=0]
----
include::{demo}[tag=environment_class_restore_setup]
----

Some other test class, running later:

[source,java,indent=0]
----
include::{demo}[tag=environment_class_restore_isolated_class]
----

== Using `@ClearEnvironmentVariable`, `@SetEnvironmentVariable`, and `@RestoreEnvironmentVariables` together
All three annotations can be combined, which could be used when some environment values are parameterized (i.e. need to be set in code) and others are not.
For instance, imagine testing an image generation utility that takes configuration from environment variables.
Basic configuration can be specified using `Set` and `Clear` and the image size parameterized:

[source,java,indent=0]
----
include::{demo}[tag=environment_method_combine_all_test]
----

[NOTE]
====
Using `@RestoreEnvironmentVariables` is not necessary to restore environment variables modified via `@ClearEnvironmentVariable` or `@SetEnvironmentVariable` - they each automatically restore the referenced variables.
'Restore', is only needed if environment variables are modified in some way _other than_ Clear and Set during a test.
====

== Warnings for Reflective Access

As explained above, this extension uses reflective access to change the otherwise immutable environment variables.
Expand Down Expand Up @@ -91,13 +145,13 @@ These command line options need to be added to the JVM that executes the tests:
== Thread-Safety

Since environment variables are global state, reading and writing them during https://junit.org/junit5/docs/current/user-guide/#writing-tests-parallel-execution[parallel test execution] can lead to unpredictable results and flaky tests.
The environment variable extension is prepared for that and tests annotated with `@ClearEnvironmentVariable` or `@SetEnvironmentVariable` will never execute in parallel (thanks to https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLock.html[resource locks]) to guarantee correct test results.
The environment variable extension is prepared for that and tests annotated with `@ClearEnvironmentVariable`, `@SetEnvironmentVariable`, or `@RestoreEnvironmentVariables` will never execute in parallel (thanks to https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLock.html[resource locks]) to guarantee correct test results.

However, this does not cover all possible cases.
Tested code that reads or writes environment variables _independently_ of the extension can still run in parallel to it and may thus behave erratically when, for example, it unexpectedly reads a variable set by the extension in another thread.
Tests that cover code that reads or writes environment variables need to be annotated with the respective annotation:

* `@ReadsEnvironmentVariable`
* `@WritesEnvironmentVariable`
* `@WritesEnvironmentVariable` (though consider using `@RestoreEnvironmentVariables` instead)

Tests annotated in this way will never execute in parallel with tests annotated with `@ClearEnvironmentVariable` or `@SetEnvironmentVariable`.
Tests annotated in this way will never execute in parallel with tests annotated with `@ClearEnvironmentVariable`, `@SetEnvironmentVariable`, or `@RestoreEnvironmentVariables`.
66 changes: 57 additions & 9 deletions docs/system-properties.adoc
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
:page-title: Clearing or Setting System Properties
:page-description: The JUnit 5 (Jupiter) extensions `@ClearSystemProperty`/`@SetSystemProperty` clear/set the values of system properties for the duration of a test
:page-title: Clear, Set, and Restore System Properties
:page-description: The JUnit 5 (Jupiter) extensions `@ClearSystemProperty`, `@SetSystemProperty` and `@RestoreSystemProperties` clear/set/restore the values of system properties for the duration of a test, and/or restore them after
:xp-demo-dir: ../src/demo/java
:demo: {xp-demo-dir}/org/junitpioneer/jupiter/SystemPropertyExtensionDemo.java

== `@ClearSystemProperty` and `@SetSystemProperty`
The `@ClearSystemProperty` and `@SetSystemProperty` annotations can be used to clear and set, respectively, the values of system properties for a test execution.
Both annotations work on the test method and class level, are repeatable, combinable, and inherited from higher-level containers.
After the annotated method has been executed, the properties mentioned in the annotation will be restored to their original value or the value of the higher-level container, or will be cleared if they didn't have one before.
Other system properties that are changed during the test, are *not* restored.
Other system properties that are changed during the test, are *not* restored (unless the `@RestoreSystemProperties` is used).

For example, clearing a system property for a test execution can be done as follows:

Expand Down Expand Up @@ -44,24 +45,71 @@ Method-level configurations are visible in both `@BeforeEach` setup methods and
Since v1.7.0, a class-level configuration means that the specified system properties are cleared/set before and reset after each individual test in the annotated class.
====

Sometimes, you might also need to set a system property to a value that is not a constant expression, which is required for annotation values.
In this case, you can still leverage the restore mechanism:
== `@RestoreSystemProperties`
`@RestoreSystemProperties` can be used to restore changes to system properties made directly in code.
While `@ClearSystemProperty` and `@SetSystemProperty` set or clear specific properties and values, they don't allow property values to be calculated or parameterized, thus there are times you may want to directly set properties in your test code.
`@RestoreSystemProperties` can be placed on test methods or test classes and will completely restore all system properties to their original state after a test or test class is complete.

In this example, `@RestoreSystemProperties` is used on a test method, ensuring any changes made in that method are restored:

[source,java,indent=0]
----
include::{demo}[tag=systemproperty_restore_test]
----

When `@RestoreSystemProperties` is used on a test class, any system properties changes made during the entire lifecycle of the test class, including test methods, `@BeforeAll`, `@BeforeEach` and 'after' methods, are restored after the test class' lifecycle is complete.
In addition, the annotation is inherited by each test method just as if each one was annotated with `@RestoreSystemProperties`.

In the following example, both test methods see the system property changes made in `@BeforeAll` and `@BeforeEach`, however, the test methods are isolated from each other (`isolatedTest2` does not 'see' changes made in `isolatedTest1`).
As shown in the second example below, the class-level `@RestoreSystemProperties` ensures that system property changes made within the annotated class are completely restored after the class's lifecycle, ensuring that changes are not visible to `SomeOtherTestClass`.
Note that `SomeOtherTestClass` uses the `@ReadsSystemProperty` annotation: This ensures that JUnit does not schedule the class to run during any test known to modify system properties (see <<Thread-Safety>>).

[source,java,indent=0]
----
include::{demo}[tag=systemproperty_class_restore_setup]
----

Some other test class, running later:

[source,java,indent=0]
----
include::{demo}[tag=systemproperty_parameter]
include::{demo}[tag=systemproperty_class_restore_isolated_class]
----

== Using `@ClearSystemProperty`, `@SetSystemProperty`, and `@RestoreSystemProperties` together
All three annotations can be combined, which could be used when some system properties are parameterized (i.e. need to be set in code) and others are not.
For instance, imagine testing an image generation utility that takes configuration from system properties.
Basic configuration can be specified using `Set` and `Clear` and the image size parameterized:

[source,java,indent=0]
----
include::{demo}[tag=systemproperty_method_combine_all_test]
----

[NOTE]
====
Using `@RestoreSystemProperties` is not necessary to restore system properties modified via `@ClearSystemProperty` or `@SetSystemProperty` - they each automatically restore the referenced properties.
'Restore', is only needed if system properties are modified in some way _other than_ Clear and Set during a test.
====

=== `@RestoreSystemProperties` Limitations
The system `Properties` object is normally just a hashmap of strings, however, it is technically possible to store non-string values and create https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Properties.html#%3Cinit%3E(java.util.Properties)[nested `Properties` with inherited default values].
`@RestoreSystemProperties` restores the original `Properties` object with all of its potential richness _after_ the annotated scope is complete.
However, for use during the test _within_ the test scope it provides a cloned `Properties` object with these limitations:

- Properties with non-string values are removed
- Nested `Properties` are flattened into a non-nested instance that has the same effective values, but not necessarily the same structure

== Thread-Safety

Since system properties are global state, reading and writing them during https://junit.org/junit5/docs/current/user-guide/#writing-tests-parallel-execution[parallel test execution] can lead to unpredictable results and flaky tests.
The system property extension is prepared for that and tests annotated with `@ClearSystemProperty` or `@SetSystemProperty` will never execute in parallel (thanks to https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLock.html[resource locks]) to guarantee correct test results.
The system property extension is prepared for that and tests annotated with `@ClearSystemProperty`, `@SetSystemProperty`, or `@RestoreSystemProperties` will never execute in parallel (thanks to https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLock.html[resource locks]) to guarantee correct test results.

However, this does not cover all possible cases.
Tested code that reads or writes system properties _independently_ of the extension can still run in parallel to it and may thus behave erratically when, for example, it unexpectedly reads a property set by the extension in another thread.
Tests that cover code that reads or writes system properties need to be annotated with the respective annotation:

* `@ReadsSystemProperty`
* `@WritesSystemProperty`
* `@WritesSystemProperty` (though consider using `@RestoreSystemProperties` instead)

Tests annotated in this way will never execute in parallel with tests annotated with `@ClearSystemProperty` or `@SetSystemProperty`.
Tests annotated in this way will never execute in parallel with tests annotated with `@ClearSystemProperty`, `@SetSystemProperty`, or `@RestoreSystemProperties`.
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,19 @@

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestClassOrder;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junitpioneer.jupiter.params.IntRangeSource;

@EnabledForJreRange(max = JRE.JAVA_16, disabledReason = "See: https://github.com/junit-pioneer/junit-pioneer/issues/509")
public class EnvironmentVariablesExtensionDemo {
Expand Down Expand Up @@ -60,4 +70,90 @@ void clearedAtClasslevel() {
}
// end::environment_using_at_class_level[]

// tag::environment_method_restore_test[]
@ParameterizedTest
@ValueSource(strings = { "foo", "bar" })
@RestoreEnvironmentVariables
void parameterizedTest(String value) {
setEnvVar("some parameterized property", value);
setEnvVar("some other dynamic property", "my code calculates somehow");
}
// end::environment_method_restore_test[]

@Nested
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
class EnvironmentVariableRestoreExample {

@Nested
@Order(1)
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // Allow non-static @BeforeAll
// tag::environment_class_restore_setup[]
@RestoreEnvironmentVariables
class EnvironmentVarRestoreTest {

@BeforeAll
void beforeAll() {
setEnvVar("A", "A value");
}

@BeforeEach
void beforeEach() {
setEnvVar("B", "B value");
}

@Test
void isolatedTest1() {
setEnvVar("C", "C value");
}

@Test
void isolatedTest2() {
assertThat(System.getenv("A")).isEqualTo("A value");
assertThat(System.getenv("B")).isEqualTo("B value");

// Class-level @RestoreEnvironmentVariables restores "C" to original state
assertThat(System.getenv("C")).isNull();
}

}
// end::environment_class_restore_setup[]

@Nested
@Order(2)
// tag::environment_class_restore_isolated_class[]
// A test class that runs later
@ReadsEnvironmentVariable
class SomeOtherTestClass {

@Test
void isolatedTest() {
// Class-level @RestoreEnvironmentVariables restores all changes made in EnvironmentVarRestoreTest
assertThat(System.getenv("A")).isNull();
assertThat(System.getenv("B")).isNull();
assertThat(System.getenv("C")).isNull();
}

// Changes to A, B, C have been restored to their values prior to the above test
}

// end::environment_class_restore_isolated_class[]
}

// tag::environment_method_combine_all_test[]
@ParameterizedTest
@IntRangeSource(from = 0, to = 10000, step = 500)
@RestoreEnvironmentVariables
@SetEnvironmentVariable(key = "DISABLE_CACHE", value = "TRUE")
@ClearEnvironmentVariable(key = "COPYWRITE_OVERLAY_TEXT")
void imageGenerationTest(int imageSize) {
setEnvVar("IMAGE_SIZE", String.valueOf(imageSize)); // Requires restore

// Test your image generation utility with the current environment variables
}
// end::environment_method_combine_all_test[]

public static void setEnvVar(String name, String value) {
EnvironmentVariableUtils.set(name, value);
eeverman marked this conversation as resolved.
Show resolved Hide resolved
}

}