Skip to content

Commit

Permalink
Support removal of environment variables and system properties
Browse files Browse the repository at this point in the history
  • Loading branch information
ashleyfrieze committed Nov 12, 2023
1 parent 18ca9f0 commit a71d73c
Show file tree
Hide file tree
Showing 12 changed files with 401 additions and 93 deletions.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ See the full guide to [JUnit 5](system-stubs-jupiter/README.md), or use it with

## Using System Stubs Individually

The plugins for JUnit etc will allow the stub objects to be used during a test, where they will
be set up and torn down around the test method. However, they can also be used without the framework.

You can declare a system stub object:

```java
Expand Down Expand Up @@ -310,6 +313,22 @@ mutable version of the `and` method used in the first example.
affect the runtime environment. Calling it outside of execution will store the
value for writing into the environment within `execute`.

You can remove an environment variable with `remove`:

```java
// assuming that there's an environment variable "STAGE" set here
new EnvironmentVariables()
.remove("STAGE")
.execute(() -> {
// the variable has been removed
assertThat(System.getenv("STAGE")).isNull();
});
```

The `remove` method deletes environment variables requested in the `EnvironmentVariables` object previously
and also takes those environment variables out of the system environment while the `EnvironmentVariables`
object is active.

### System Properties

#### With `SystemStubs`
Expand All @@ -333,6 +352,15 @@ void execute_code_that_manipulates_system_properties() throws Exception {
}
```

This also supports removing properties from the system properties:

```java
// will be restored
restoreSystemProperties(() ->{
System.getProperties().remove("someProp");
});
```

#### With `SystemProperties`

A `SystemProperties` object allows you to set the system properties that will be provided
Expand All @@ -358,6 +386,16 @@ someProperties.execute(() -> {
// here the system properties are reverted
```

We can also specify properties to delete from the default system properties:

```java
// when this object is active, some properties will be removed
// from system properties
SystemProperties someProperties = new SystemProperties()
.remove("property1")
.remove("property2");
```

### Sources of `Properties` for `EnvironmentVariables` and `SystemProperties`

Once you have constructed an `EnvironmentVariables` or `SystemProperties` object, you can use the `set` method to apply properties. If these objects are presently _active_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,25 @@ private static void installInterceptorIntoBootLoader(Instrumentation instrumenta
instrumentation.appendToBootstrapClassLoaderSearch(new JarFile(tempFile));
}

@Deprecated(since = "2.1.5")
public static void connect(Map<String, String> newEnvironmentVariables) {
connect(newEnvironmentVariables, Collections.emptySet());
}

/**
* Attach a map as the mutable replacement environment variables for now. This can be done
* multiple times and each time the replacement will supersede the maps before. Then when {@link #pop()}
* is called, we'll rollback to the previous.
* @param newEnvironmentVariables the mutable map - note: this will be populated by the current
* environment
* @param variablesToRemove a list of variables to take out of the resulting environment variables
*/
public static void connect(Map<String, String> newEnvironmentVariables) {
public static void connect(Map<String, String> newEnvironmentVariables, Set<String> variablesToRemove) {
// add all entries not already present in the new environment variables
System.getenv().entrySet().stream()
.filter(entry -> !newEnvironmentVariables.containsKey(entry.getKey()))
.forEach(entry -> newEnvironmentVariables.put(entry.getKey(), entry.getValue()));
variablesToRemove.forEach(newEnvironmentVariables::remove);
REPLACEMENT_ENV.push(newEnvironmentVariables);
ProcessEnvironmentInterceptor.setEnv(newEnvironmentVariables);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
import uk.org.webcompere.systemstubs.resource.SingularTestResource;

import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.*;

import static java.util.Collections.emptyMap;
import static uk.org.webcompere.systemstubs.properties.PropertiesUtils.toStringMap;
Expand All @@ -35,7 +33,8 @@
* @since 1.0.0
*/
public class EnvironmentVariables extends SingularTestResource implements NameValuePairSetter<EnvironmentVariables> {
protected final Map<String, String> variables;
private final Map<String, String> variables;
private final Set<String> toRemove = new HashSet<>();

/**
* Default constructor with an empty set of environment variables. Use {@link #set(String, String)} to
Expand Down Expand Up @@ -113,6 +112,14 @@ public EnvironmentVariables set(String name, String value) {
return this;
}

@Override
public EnvironmentVariables remove(String name) {
toRemove.add(name);
variables.remove(name);

return this;
}

/**
* Return a copy of all the variables set for testing
* @return a copy of the map
Expand Down Expand Up @@ -141,7 +148,7 @@ private String format(String text) {

@Override
protected void doSetup() {
EnvironmentVariableMocker.connect(variables);
EnvironmentVariableMocker.connect(variables, toRemove);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,88 +1,23 @@
package uk.org.webcompere.systemstubs.properties;

import uk.org.webcompere.systemstubs.resource.NameValuePairSetter;
import uk.org.webcompere.systemstubs.resource.SingularTestResource;

import java.util.Properties;

import static java.lang.System.getProperties;
import static java.lang.System.setProperties;

/**
* Maintain system properties after a test from the ones before the test. Stores the
* existing properties when started, and restores them when complete. Allows for a list of properties
* that will be applied to the system to be set before the stubbing is triggered.
*/
public class SystemProperties extends SingularTestResource implements NameValuePairSetter<SystemProperties> {
private Properties originalProperties;
private Properties properties;
public class SystemProperties extends SystemPropertiesImpl<SystemProperties> {

/**
* Default constructor with no properties. Use {@link #set} to set properties
* either while active or before activation.
* @since 1.0.0
*/
public SystemProperties() {
this.properties = new Properties();
super();
}

/**
* Construct with a specific set of properties.
* @param properties properties to use
* @since 1.0.0
*/
public SystemProperties(Properties properties) {
this.properties = PropertiesUtils.copyOf(properties);
super(properties);
}

/**
* Construct with a set of properties to apply when the object is active
* @param name name of the first property
* @param value value of the first property
* @param nameValues pairs of names and values for further properties
* @since 1.0.0
*/
public SystemProperties(String name, String value, String... nameValues) {
this();
if (nameValues.length % 2 != 0) {
throw new IllegalArgumentException("Must have pairs of values");
}
properties.setProperty(name, value);
for (int i = 0; i < nameValues.length; i += 2) {
properties.setProperty(nameValues[i], nameValues[i + 1]);
}
}

/**
* Set a system property. If active, this will set it with {@link System#setProperty(String, String)}.
* If not active, then this will store the property to apply when this object is part of an execution.
* It is also possible to use {@link System#setProperty(String, String)} while this object is active,
* but when the execution finishes, this object will be unaware of the property set, so will not set
* it next time.
* @param name name of the property
* @param value value to set
* @return this object for fluent use
* @since 1.0.0
*/
@Override
public SystemProperties set(String name, String value) {
properties.setProperty(name, value);
if (isActive()) {
System.setProperty(name, value);
}
return this;
}

@Override
protected void doSetup() throws Exception {
originalProperties = getProperties();
Properties copyProperties = PropertiesUtils.copyOf(originalProperties);
copyProperties.putAll(properties);
setProperties(copyProperties);
}

@Override
protected void doTeardown() throws Exception {
setProperties(originalProperties);
super(name, value, nameValues);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package uk.org.webcompere.systemstubs.properties;

import uk.org.webcompere.systemstubs.resource.NameValuePairSetter;
import uk.org.webcompere.systemstubs.resource.SingularTestResource;

import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import static java.lang.System.getProperties;
import static java.lang.System.setProperties;

/**
* Maintain system properties after a test from the ones before the test. Stores the
* existing properties when started, and restores them when complete. Allows for a list of properties
* that will be applied to the system to be set before the stubbing is triggered.
*/
public class SystemPropertiesImpl<T extends SystemPropertiesImpl<T>> extends SingularTestResource
implements NameValuePairSetter<SystemPropertiesImpl<T>> {
private Properties originalProperties;
private Properties properties;

private Set<String> propertiesToRemove = new HashSet<>();

/**
* Default constructor with no properties. Use {@link #set} to set properties
* either while active or before activation.
* @since 1.0.0
*/
public SystemPropertiesImpl() {
this.properties = new Properties();
}

/**
* Construct with a specific set of properties.
* @param properties properties to use
* @since 1.0.0
*/
public SystemPropertiesImpl(Properties properties) {
this.properties = PropertiesUtils.copyOf(properties);
}

/**
* Construct with a set of properties to apply when the object is active
* @param name name of the first property
* @param value value of the first property
* @param nameValues pairs of names and values for further properties
* @since 1.0.0
*/
public SystemPropertiesImpl(String name, String value, String... nameValues) {
this();
if (nameValues.length % 2 != 0) {
throw new IllegalArgumentException("Must have pairs of values");
}
properties.setProperty(name, value);
for (int i = 0; i < nameValues.length; i += 2) {
properties.setProperty(nameValues[i], nameValues[i + 1]);
}
}

/**
* Set a system property. If active, this will set it with {@link System#setProperty(String, String)}.
* If not active, then this will store the property to apply when this object is part of an execution.
* It is also possible to use {@link System#setProperty(String, String)} while this object is active,
* but when the execution finishes, this object will be unaware of the property set, so will not set
* it next time.
* @param name name of the property
* @param value value to set
* @return this object for fluent use
* @since 1.0.0
*/
@Override
public SystemPropertiesImpl<T> set(String name, String value) {
properties.setProperty(name, value);
if (isActive()) {
System.setProperty(name, value);
}
return this;
}

/**
* Remove a property - this removes it from system properties if active, and remembers to remove it
* while the object is active
* @param name the name of the property to remove
* @return <code>this</code> for fluent use
* @since 2.1.5
*/
@Override
public SystemPropertiesImpl<T> remove(String name) {
propertiesToRemove.add(name);
if (isActive()) {
System.getProperties().remove(name);
}
return this;
}

@Override
protected void doSetup() throws Exception {
originalProperties = getProperties();
Properties copyProperties = PropertiesUtils.copyOf(originalProperties);
propertiesToRemove.forEach(copyProperties::remove);
copyProperties.putAll(properties);
setProperties(copyProperties);
}

@Override
protected void doTeardown() throws Exception {
setProperties(originalProperties);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
* The general interface of something that can set name value pairs on itself
* @param <T> the final type of the class which provides this
*/
@FunctionalInterface
public interface NameValuePairSetter<T extends NameValuePairSetter> {
public interface NameValuePairSetter<T extends NameValuePairSetter<T>> {
/**
* Set a name value pair
* @param name the name
Expand Down Expand Up @@ -43,4 +42,11 @@ default T set(Map<Object, Object> properties) {
properties.forEach((key, value) -> set(String.valueOf(key), String.valueOf(value)));
return (T)this;
}

/**
* Remove one of the name value pairs
* @param name the name
* @return <code>this</code> for fluent calling
*/
T remove(String name);
}
Loading

0 comments on commit a71d73c

Please sign in to comment.