Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f56e3ca
Adjust the `EnvironmentTests` to no longer assume environments to be
serhii-lekariev May 26, 2020
6e42cc5
Introduce the environment-related API. Strip the `Environment` docs.
serhii-lekariev May 26, 2020
fc5a8d8
Update the `Environment` Javadocs and the license report.
serhii-lekariev May 26, 2020
e1ab97e
Add a method Javadoc sentence.
serhii-lekariev May 26, 2020
55daf08
Advance the version.
serhii-lekariev May 26, 2020
478b10e
Fix documentation errors.
serhii-lekariev May 26, 2020
53370c6
Remove unnecessary parens.
serhii-lekariev May 26, 2020
a32ff87
`@Internal`ize a method.
serhii-lekariev May 27, 2020
1cf44c2
Cut the `EnvironmentType` - it no longer can set the system to itself.
serhii-lekariev May 27, 2020
a7131d8
Register environment types even if they are not enums.
serhii-lekariev May 27, 2020
927978e
Bump the version, regenerate the license report.
serhii-lekariev May 27, 2020
6897c5e
Fix method naming and documentation wording errors.
serhii-lekariev May 27, 2020
f17fe9c
Fix formatting.
serhii-lekariev May 27, 2020
7d71ccb
Merge remote-tracking branch 'origin/master' into env-types
serhii-lekariev May 27, 2020
0333a69
`license-report.md` and `pom.xml`.
serhii-lekariev May 27, 2020
27de43d
Fix documentation and wording errors.
serhii-lekariev May 29, 2020
5976b4d
Clear the env variable, specify env type checking order.
serhii-lekariev May 29, 2020
8c18656
Reword a doc paragraph.
serhii-lekariev May 30, 2020
7e07c10
Fix formatting and a test `@DisplayName`.
serhii-lekariev May 30, 2020
340fd04
Fix formatting and documentation wording.
serhii-lekariev May 30, 2020
35f1262
Fix test methods naming.
serhii-lekariev May 30, 2020
a838155
Decrease test verbosity.
serhii-lekariev May 30, 2020
aaddbd4
Rephrase the `Environment` class-level doc.
serhii-lekariev May 31, 2020
8a042f6
Document the new functionality of `Environment`.
armiol Jun 1, 2020
d97c603
Remove the redundant empty lines.
armiol Jun 1, 2020
dc6705e
Tweak the API of `Environment`: `is(EnvironmentType)` instead of `cur…
serhii-lekariev Jun 3, 2020
2ac7fcb
Fix documentation errors.
serhii-lekariev Jun 3, 2020
bf9bc3f
`instance()` -> `type()` for default `EnvironmentType`s. Also license…
serhii-lekariev Jun 3, 2020
16f571a
Deprecate the old API.
serhii-lekariev Jun 3, 2020
a45855e
Correct documentation errors, test the `@Deprecated` methods.
serhii-lekariev Jun 3, 2020
b9e9bb4
Add a doc sentence.
serhii-lekariev Jun 4, 2020
62926e1
Deprecate the `setToTests` and `setToProduction`.
serhii-lekariev Jun 4, 2020
e4cefb6
Introduce a `type` method to check the current env type without having
serhii-lekariev Jun 4, 2020
2de3048
Ensure correct access level.
serhii-lekariev Jun 4, 2020
117a4e9
Add a test.
serhii-lekariev Jun 4, 2020
1d8891e
Fix doc errors.
serhii-lekariev Jun 4, 2020
9858b0d
Allow to register the same environment twice.
serhii-lekariev Jun 4, 2020
5cf0c03
Correct a documentation error.
serhii-lekariev Jun 4, 2020
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
256 changes: 194 additions & 62 deletions base/src/main/java/io/spine/base/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,37 +21,141 @@
package io.spine.base;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.spine.annotation.SPI;
import org.checkerframework.checker.nullness.qual.Nullable;

import static com.google.common.base.Preconditions.checkNotNull;

/**
* Provides information about the environment (current platform used, etc.).
*
* <h1>Environment Type Detection</h1>
*
* <p>Current implementation allows to {@linkplain #is(EnvironmentType) check} whether a given
* environment is currently the active one and {@linkplain #type() get an instance of the current
* environment type}. Two environment types exist out of the box:
*
* <ul>
* <li><em>{@link Tests}</em> is detected if the current call stack has a reference to the unit
* testing framework.
*
* <li><em>{@link Production}</em> is set in all other cases.
* </ul>
*
* <p>The framework users may define their custom settings depending on the current environment
* type:
*
* <pre>
*
* public final class Application {
*
* private final EmailSender sender;
*
* private Application() {
* Environment environment = Environment.instance();
* if(environment.is(Tests.type())) {
* // Do not send out emails if in tests.
* this.sender = new MockEmailSender();
* } else {
* this.sender = EmailSender.withConfig("email_gateway.yml");
* }
* //...
* }
* }
* </pre>
*
* <h1>Custom environment types</h1>
*
* {@code Environment} allows to {@link #register(EnvironmentType) reguster custom types}.
* In this case the environment detection functionality iterates over all known types, starting
* with those registered by the framework user:
*
* <pre>
*
* public final class Application {
*
* static {
* Environment.instance()
* .register(StagingEnvironmentType.type())
* .register(LoadTestingType.type());
* }
*
* private final ConnectionPool pool;
*
* private Application() {
* Environment environment = Environment.instance();
* if (environment.is(Tests.type()) {
* // Single connection is enough for tests.
* this.pool = new ConnectionPoolImpl(PoolCapacity.of(1));
* } else {
* if(environment.is(LoadTesting.type()) {
* this.pool =
* new ConnectionPoolImpl(PoolCapacity.fromConfig("load_tests.yml"));
* } else {
* this.pool =
* new ConnectionPoolImpl(PoolCapacity.fromConfig("cloud_deployment.yml"));
* }
* }
* //...
* }
* }
* </pre>
*
* <p><b>When registering custom types, please ensure</b> their mutual exclusivity.
* If two or more environment types {@linkplain EnvironmentType#enabled() consider themselves
* enabled} at the same time, the behaviour of {@link #is(EnvironmentType)}} is undefined.
*
* @see EnvironmentType
* @see Tests
* @see Production
*/
@SPI
@SuppressWarnings("AccessOfSystemProperties") // OK as we need system properties for this class.
public final class Environment {

private static final Environment INSTANCE = new Environment();
private static final ImmutableList<EnvironmentType> BASE_TYPES =
ImmutableList.of(Tests.type(), Production.type());

/**
* The key name of the system property which tells if a code runs under a testing framework.
*
* <p>If your testing framework is not among the supported by {@link #isTests()},
* set this property to {@code true} before running tests.
*/
public static final String ENV_KEY_TESTS = "io.spine.tests";
private static final Environment INSTANCE = new Environment();

/** If set, tells if the code runs from a testing framework. */
private @Nullable Boolean tests;
private ImmutableList<EnvironmentType> knownEnvTypes;
private @Nullable EnvironmentType currentEnvType;

/** Prevents instantiation of this singleton class from outside. */
private Environment() {
this.knownEnvTypes = BASE_TYPES;
}

/** Creates a new instance with the copy of the state of the passed environment. */
private Environment(Environment copy) {
this.tests = copy.tests;
this.knownEnvTypes = copy.knownEnvTypes;
this.currentEnvType = copy.currentEnvType;
}

/**
* Remembers the specified environment type, allowing {@linkplain #is(EnvironmentType) to
* determine whether it's enabled} later.
*
* <p>Note that the default types are still present.
* When trying to determine which environment type is enabled, the user-defined types are
* checked first, in the first-registered to last-registered order.
*
* @param environmentType
* a user-defined environment type
* @return this instance of {@code Environment}
* @see Tests
* @see Production
*/
@CanIgnoreReturnValue
public Environment register(EnvironmentType environmentType) {
if (!knownEnvTypes.contains(environmentType)) {
knownEnvTypes = ImmutableList
.<EnvironmentType>builder()
.add(environmentType)
.addAll(INSTANCE.knownEnvTypes)
.build();
}
return this;
}

/** Returns the singleton instance. */
Expand All @@ -69,60 +173,58 @@ public Environment createCopy() {
}

/**
* Restores the state from the instance created by {@link #createCopy()}.
* Determines whether the current environment is the same as the specified one.
*
* <p>Call this method when cleaning up tests that modify {@code Environment}.
* <p>If {@linkplain #register(EnvironmentType) custom env types have been defined},
* goes through them in the latest-registered to earliest-registered order.
* Then, checks {@link Tests} and {@link Production}.
*
* @return the current environment type.
*/
@VisibleForTesting
public void restoreFrom(Environment copy) {
// Make sure this matches the set of fields copied in the copy constructor.
this.tests = copy.tests;
@SuppressWarnings("ConstantConditions"/* no NPE is ensured by the `ensureTypeIsSet` call. */)
public boolean is(EnvironmentType type) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's bring back isTests() and isProduction() and mark them @Deprecated in favor of ... .enabled().
It's a proper way of propagating the public API changes.

ensureTypeIsSet();
return currentEnvType.equals(type);
}

/**
* Verifies if the code currently runs under a unit testing framework.
* Returns the current environment type.
*
* <p>The method returns {@code true} if the following packages are discovered
* in the stacktrace:
* <ul>
* <li>{@code org.junit}
* <li>{@code org.testng}
* </ul>
* <p>If {@linkplain #register(EnvironmentType) custom env types have been defined},
* goes through them in the latest-registered to earliest-registered order.
* Then, checks {@link Tests} and {@link Production}.
*
* @return {@code true} if the code runs under a testing framework, {@code false} otherwise
* @return the current environment type
*/
@SuppressWarnings({
"DynamicRegexReplaceableByCompiledPattern", // OK as we cache the result
"DuplicateStringLiteralInspection" // used in another context
})
public boolean isTests() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's bring this public API method back. Its implementation will be simpler, that's for sure.

// If we cached the value before, return it.
if (tests != null) {
return tests;
}
public EnvironmentType type() {
ensureTypeIsSet();
return currentEnvType;
}

// Check the environment variable. We may run under unknown testing framework or
// tests may require production-like mode, which they simulate by setting
// the property to `false`.
String testProp = System.getProperty(ENV_KEY_TESTS);
if (testProp != null) {
testProp = testProp.replaceAll("\"' ", "");
this.tests = (String.valueOf(true)
.equalsIgnoreCase(testProp)
|| "1".equals(testProp));
return this.tests;
private void ensureTypeIsSet() {
if (currentEnvType == null) {
determineCurrentType();
}
}

// Check stacktrace for known frameworks.
String stacktrace = Throwables.getStackTraceAsString(new RuntimeException(""));
if (stacktrace.contains("org.junit")
|| stacktrace.contains("org.testng")) {
this.tests = true;
return true;
private void determineCurrentType() {
for (EnvironmentType type : knownEnvTypes) {
if (type.enabled()) {
this.currentEnvType = type;
return;
}
}
}

this.tests = false;
return false;
/**
* Verifies if the code currently runs under a unit testing framework.
*
* @see Tests
* @deprecated use {@code Environment.instance().is(Tests.type)}
*/
@Deprecated
public boolean isTests() {
return is(Tests.type());
}

/**
Expand All @@ -131,39 +233,69 @@ public boolean isTests() {
* <p>This method is opposite to {@link #isTests()}
*
* @return {@code true} if the code runs in the production mode, {@code false} otherwise
* @see Production
* @deprecated use {@code Environment.instance().is(Production.type())}
*/
@Deprecated
public boolean isProduction() {
return !isTests();
}

/**
* Restores the state from the instance created by {@link #createCopy()}.
*
* <p>Call this method when cleaning up tests that modify {@code Environment}.
*/
@VisibleForTesting
public void restoreFrom(Environment copy) {
// Make sure this matches the set of fields copied in the copy constructor.
this.knownEnvTypes = copy.knownEnvTypes;
this.currentEnvType = copy.currentEnvType;
}

/**
* Forces the specified environment type to be the current one.
*/
@VisibleForTesting
public void setTo(EnvironmentType type) {
this.currentEnvType = checkNotNull(type);
}

/**
* Turns the test mode on.
*
* <p>This method is opposite to {@link #setToProduction()}.
*
* @deprecated use {@link #setTo(EnvironmentType)}
*/
@Deprecated
@VisibleForTesting
public void setToTests() {
this.tests = true;
System.setProperty(ENV_KEY_TESTS, String.valueOf(true));
this.currentEnvType = Tests.type();
Tests.enable();
}

/**
* Turns the production mode on.
*
* <p>This method is opposite to {@link #setToTests()}.
*
* @deprecated use {@link #setTo(EnvironmentType)}
*/
@Deprecated
@VisibleForTesting
public void setToProduction() {
this.tests = false;
System.setProperty(ENV_KEY_TESTS, String.valueOf(false));
this.currentEnvType = Production.type();
Tests.clearTestingEnvVariable();
}

/**
* Resets the instance and clears the {@link #ENV_KEY_TESTS} variable.
* Resets the instance and clears the {@link Tests#ENV_KEY_TESTS} variable.
*/
@VisibleForTesting
public void reset() {
this.tests = null;
System.clearProperty(ENV_KEY_TESTS);
this.currentEnvType = null;
this.knownEnvTypes = BASE_TYPES;
Tests.clearTestingEnvVariable();
}
}
Loading