-
Notifications
You must be signed in to change notification settings - Fork 4
Custom environment types #539
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
Merged
Merged
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 6e42cc5
Introduce the environment-related API. Strip the `Environment` docs.
serhii-lekariev fc5a8d8
Update the `Environment` Javadocs and the license report.
serhii-lekariev e1ab97e
Add a method Javadoc sentence.
serhii-lekariev 55daf08
Advance the version.
serhii-lekariev 478b10e
Fix documentation errors.
serhii-lekariev 53370c6
Remove unnecessary parens.
serhii-lekariev a32ff87
`@Internal`ize a method.
serhii-lekariev 1cf44c2
Cut the `EnvironmentType` - it no longer can set the system to itself.
serhii-lekariev a7131d8
Register environment types even if they are not enums.
serhii-lekariev 927978e
Bump the version, regenerate the license report.
serhii-lekariev 6897c5e
Fix method naming and documentation wording errors.
serhii-lekariev f17fe9c
Fix formatting.
serhii-lekariev 7d71ccb
Merge remote-tracking branch 'origin/master' into env-types
serhii-lekariev 0333a69
`license-report.md` and `pom.xml`.
serhii-lekariev 27de43d
Fix documentation and wording errors.
serhii-lekariev 5976b4d
Clear the env variable, specify env type checking order.
serhii-lekariev 8c18656
Reword a doc paragraph.
serhii-lekariev 7e07c10
Fix formatting and a test `@DisplayName`.
serhii-lekariev 340fd04
Fix formatting and documentation wording.
serhii-lekariev 35f1262
Fix test methods naming.
serhii-lekariev a838155
Decrease test verbosity.
serhii-lekariev aaddbd4
Rephrase the `Environment` class-level doc.
serhii-lekariev 8a042f6
Document the new functionality of `Environment`.
armiol d97c603
Remove the redundant empty lines.
armiol dc6705e
Tweak the API of `Environment`: `is(EnvironmentType)` instead of `cur…
serhii-lekariev 2ac7fcb
Fix documentation errors.
serhii-lekariev bf9bc3f
`instance()` -> `type()` for default `EnvironmentType`s. Also license…
serhii-lekariev 16f571a
Deprecate the old API.
serhii-lekariev a45855e
Correct documentation errors, test the `@Deprecated` methods.
serhii-lekariev b9e9bb4
Add a doc sentence.
serhii-lekariev 62926e1
Deprecate the `setToTests` and `setToProduction`.
serhii-lekariev e4cefb6
Introduce a `type` method to check the current env type without having
serhii-lekariev 2de3048
Ensure correct access level.
serhii-lekariev 117a4e9
Add a test.
serhii-lekariev 1d8891e
Fix doc errors.
serhii-lekariev 9858b0d
Allow to register the same environment twice.
serhii-lekariev 5cf0c03
Correct a documentation error.
serhii-lekariev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. */ | ||
|
|
@@ -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) { | ||
| 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() { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -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() { | ||
armiol marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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(); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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()andisProduction()and mark them@Deprecatedin favor of... .enabled().It's a proper way of propagating the public API changes.