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

Support removal of environment variables and system properties #75

Merged
merged 1 commit into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");
Comment on lines +394 to +396
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ashleyfrieze

this does not compile due to the introduction of SystemPropertiesImpl.

Also this PR breaks backward compatibility. This was working before but no longer does:

class SomeTest {
    @SystemStub
    private static SystemProperties systemProperties = new MySystemProperties().set("property", "value");
}

class MySystemProperties extends SystemProperties {

    public MySystemProperties() {
        super("key1", "value1");
        this.set("key2", "value2");
    }
}

Created #80 for this.

```

### 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