diff --git a/.evergreen/.evg.yml b/.evergreen/.evg.yml index 7de253baac6..8755625cdad 100644 --- a/.evergreen/.evg.yml +++ b/.evergreen/.evg.yml @@ -1740,6 +1740,12 @@ axes: display_name: "8.0" variables: VERSION: "8.0" + # 8.2 is used solely for Windows testing. MongoDB 8.0 binaries are affected by SERVER-116018 on Windows, + # and the fix is only available starting from 8.2. + - id: "8.2" + display_name: "8.2" + variables: + VERSION: "8.2" - id: "7.0" display_name: "7.0" variables: @@ -1770,6 +1776,9 @@ axes: - id: "ubuntu" display_name: "Ubuntu" run_on: "ubuntu2004-small" + - id: "windows" + display_name: "Windows" + run_on: "windows-2022-latest-small" - id: "topology" display_name: "Topology" @@ -2403,6 +2412,22 @@ buildvariants: - name: "test-core-task" - name: "test-legacy-task" + - matrix_name: "tests-jdk-secure-windows" + matrix_spec: { auth: "auth", ssl: "ssl", jdk: "jdk17", + version: [ "8.2" ], + topology: "*", os: "windows" } + display_name: "${os}: ${version} ${topology} ${auth} ${ssl} ${jdk}" + tags: [ "tests-variant" ] + tasks: + - name: "test-sync-task" + exec_timeout_secs: 7200 + - name: "test-reactive-task" + exec_timeout_secs: 7200 + - name: "test-core-task" + exec_timeout_secs: 7200 + - name: "test-legacy-task" + exec_timeout_secs: 7200 + - matrix_name: "tests-require-api-version" matrix_spec: { api-version: "required", auth: "auth", ssl: "nossl", jdk: [ "jdk21" ], version: [ "5.0", "6.0", "7.0", "8.0", "latest" ], topology: "standalone", os: "linux" } @@ -2540,6 +2565,15 @@ buildvariants: tasks: - name: "csfle-tests-with-mongocryptd-task" + - matrix_name: "csfle-tests-with-mongocryptd-windows" + matrix_spec: { os: "windows", + version: [ "8.0", "latest" ], + topology: [ "replicaset" ] } + display_name: "${os} CSFLE with mongocryptd: ${version}" + tasks: + - name: "csfle-tests-with-mongocryptd-task" + exec_timeout_secs: 7200 + - matrix_name: "socks5-tests" matrix_spec: { os: "linux", ssl: [ "nossl", "ssl" ], version: [ "latest" ], topology: [ "replicaset" ], socks-auth: [ "auth", "noauth" ] } display_name: "SOCKS5 proxy ${socks-auth} : ${version} ${topology} ${ssl} ${jdk} ${os}" diff --git a/.evergreen/run-csfle-tests-with-mongocryptd.sh b/.evergreen/run-csfle-tests-with-mongocryptd.sh index 4e320c32178..a5322955ff7 100755 --- a/.evergreen/run-csfle-tests-with-mongocryptd.sh +++ b/.evergreen/run-csfle-tests-with-mongocryptd.sh @@ -39,8 +39,16 @@ provision_ssl () { cp ${JAVA_HOME}/lib/security/cacerts mongo-truststore ${JAVA_HOME}/bin/keytool -importcert -trustcacerts -file ${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem -keystore mongo-truststore -storepass changeit -storetype JKS -noprompt + # Use native paths on Windows (cygwin paths like /cygdrive/c/... are not understood by the JDK) + local CURRENT_DIR + if [ "Windows_NT" == "$OS" ]; then + CURRENT_DIR=$(cygpath -m "$(pwd)") + else + CURRENT_DIR=$(pwd) + fi + # We add extra gradle arguments for SSL - export GRADLE_EXTRA_VARS="-Pssl.enabled=true -Pssl.keyStoreType=pkcs12 -Pssl.keyStore=`pwd`/client.pkc -Pssl.keyStorePassword=bithere -Pssl.trustStoreType=jks -Pssl.trustStore=`pwd`/mongo-truststore -Pssl.trustStorePassword=changeit" + export GRADLE_EXTRA_VARS="-Pssl.enabled=true -Pssl.keyStoreType=pkcs12 -Pssl.keyStore=${CURRENT_DIR}/client.pkc -Pssl.keyStorePassword=bithere -Pssl.trustStoreType=jks -Pssl.trustStore=${CURRENT_DIR}/mongo-truststore -Pssl.trustStorePassword=changeit" } ############################################ diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 10bd5bc107d..23daa6ddfec 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -62,8 +62,16 @@ provision_ssl () { cp ${JAVA_HOME}/lib/security/cacerts mongo-truststore ${JAVA_HOME}/bin/keytool -importcert -trustcacerts -file ${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem -keystore mongo-truststore -storepass changeit -storetype JKS -noprompt + # Use native paths on Windows (cygwin paths like /cygdrive/c/... are not understood by the JDK) + local CURRENT_DIR + if [ "Windows_NT" == "$OS" ]; then + CURRENT_DIR=$(cygpath -m "$(pwd)") + else + CURRENT_DIR=$(pwd) + fi + # We add extra gradle arguments for SSL - export GRADLE_EXTRA_VARS="-Pssl.enabled=true -Pssl.keyStoreType=pkcs12 -Pssl.keyStore=`pwd`/client.pkc -Pssl.keyStorePassword=bithere -Pssl.trustStoreType=jks -Pssl.trustStore=`pwd`/mongo-truststore -Pssl.trustStorePassword=changeit" + export GRADLE_EXTRA_VARS="-Pssl.enabled=true -Pssl.keyStoreType=pkcs12 -Pssl.keyStore=${CURRENT_DIR}/client.pkc -Pssl.keyStorePassword=bithere -Pssl.trustStoreType=jks -Pssl.trustStore=${CURRENT_DIR}/mongo-truststore -Pssl.trustStorePassword=changeit" } provision_multi_mongos_uri_for_ssl () { @@ -124,6 +132,12 @@ if [ ! -z "$REQUIRE_API_VERSION" ]; then export API_VERSION="-Dorg.mongodb.test.api.version=1" fi +# Log Windows version info for diagnostics +if [ "Windows_NT" == "$OS" ]; then + echo "Windows build info:" + cmd /c ver +fi + echo "Running $AUTH tests over $SSL for $TOPOLOGY and connecting to $MONGODB_URI" echo "Running tests with Java ${JAVA_VERSION}" diff --git a/.evergreen/setup-env.bash b/.evergreen/setup-env.bash index cae67cd65eb..a8a7840292c 100644 --- a/.evergreen/setup-env.bash +++ b/.evergreen/setup-env.bash @@ -1,9 +1,16 @@ # Java configurations for evergreen -export JDK8="/opt/java/jdk8" -export JDK11="/opt/java/jdk11" -export JDK17="/opt/java/jdk17" -export JDK21="/opt/java/jdk21" +if [ "Windows_NT" == "$OS" ]; then + export JDK8="/cygdrive/c/java/jdk8" + export JDK11="/cygdrive/c/java/jdk11" + export JDK17="/cygdrive/c/java/jdk17" + export JDK21="/cygdrive/c/java/jdk21" +else + export JDK8="/opt/java/jdk8" + export JDK11="/opt/java/jdk11" + export JDK17="/opt/java/jdk17" + export JDK21="/opt/java/jdk21" +fi # note that `JDK21_GRAALVM` is used in `run-graalvm-native-image-app.sh` # by dynamically constructing the variable name export JDK21_GRAALVM="/opt/java/jdk21-graalce" diff --git a/bson/src/test/unit/util/JsonPoweredTestHelper.java b/bson/src/test/unit/util/JsonPoweredTestHelper.java index e261e132ab4..36fd4e66d8c 100644 --- a/bson/src/test/unit/util/JsonPoweredTestHelper.java +++ b/bson/src/test/unit/util/JsonPoweredTestHelper.java @@ -86,7 +86,8 @@ public static List getTestDocuments(final String resourcePath) { public FileVisitResult visitFile(final Path filePath, final BasicFileAttributes attrs) throws IOException { if (filePath.toString().endsWith(".json")) { if (fileSystem == null) { - files.add(getTestDocumentWithMetaData(filePath.toString().substring(filePath.toString().lastIndexOf(resourcePath)))); + String filePathStr = filePath.toString().replace('\\', '/'); + files.add(getTestDocumentWithMetaData(filePathStr.substring(filePathStr.lastIndexOf(resourcePath)))); } else { files.add(getTestDocumentWithMetaData(filePath.toString())); } diff --git a/driver-core/src/main/com/mongodb/internal/EnvironmentProvider.java b/driver-core/src/main/com/mongodb/internal/EnvironmentProvider.java new file mode 100644 index 00000000000..e19619e900f --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/EnvironmentProvider.java @@ -0,0 +1,68 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.internal; + +import com.mongodb.lang.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.UnaryOperator; + +/** + * Centralized access to environment variables. All production code should use + * this class instead of calling {@link System#getenv(String)} directly. + * + *

Tests can override values via {@link #envOverride()}.

+ */ +public final class EnvironmentProvider { + private static UnaryOperator envLookup = System::getenv; + + private EnvironmentProvider() { + } + + @Nullable + public static String getEnv(final String key) { + return envLookup.apply(key); + } + + /** Exists only for testing **/ + public static EnvironmentOverride envOverride() { + return new EnvironmentOverride(); + } + + public static final class EnvironmentOverride implements AutoCloseable { + private final Map overrides = new HashMap<>(); + private final UnaryOperator original; + + private EnvironmentOverride() { + original = envLookup; + envLookup = key -> overrides.containsKey(key) + ? overrides.get(key) + : original.apply(key); + } + + public EnvironmentOverride set(final String key, @Nullable final String value) { + overrides.put(key, value); + return this; + } + + @Override + public void close() { + envLookup = original; + } + } +} \ No newline at end of file diff --git a/driver-core/src/main/com/mongodb/internal/authentication/BuiltInAwsCredentialSupplier.java b/driver-core/src/main/com/mongodb/internal/authentication/BuiltInAwsCredentialSupplier.java index 87a5cfc37b1..b4f4487eda5 100644 --- a/driver-core/src/main/com/mongodb/internal/authentication/BuiltInAwsCredentialSupplier.java +++ b/driver-core/src/main/com/mongodb/internal/authentication/BuiltInAwsCredentialSupplier.java @@ -17,6 +17,7 @@ package com.mongodb.internal.authentication; import com.mongodb.AwsCredential; +import com.mongodb.internal.EnvironmentProvider; import org.bson.BsonDocument; import java.util.HashMap; @@ -29,7 +30,7 @@ class BuiltInAwsCredentialSupplier implements Supplier { @Override public AwsCredential get() { - if (System.getenv("AWS_ACCESS_KEY_ID") != null) { + if (EnvironmentProvider.getEnv("AWS_ACCESS_KEY_ID") != null) { return obtainFromEnvironmentVariables(); } else { return obtainFromEc2OrEcsResponse(); @@ -38,13 +39,13 @@ public AwsCredential get() { private static AwsCredential obtainFromEnvironmentVariables() { return new AwsCredential( - System.getenv("AWS_ACCESS_KEY_ID"), - System.getenv("AWS_SECRET_ACCESS_KEY"), - System.getenv("AWS_SESSION_TOKEN")); + EnvironmentProvider.getEnv("AWS_ACCESS_KEY_ID"), + EnvironmentProvider.getEnv("AWS_SECRET_ACCESS_KEY"), + EnvironmentProvider.getEnv("AWS_SESSION_TOKEN")); } private static AwsCredential obtainFromEc2OrEcsResponse() { - String path = System.getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); + String path = EnvironmentProvider.getEnv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); BsonDocument ec2OrEcsResponse = path == null ? BsonDocument.parse(getEc2Response()) : BsonDocument.parse(getEcsResponse(path)); return new AwsCredential( diff --git a/driver-core/src/main/com/mongodb/internal/connection/FaasEnvironment.java b/driver-core/src/main/com/mongodb/internal/connection/FaasEnvironment.java index a54c1efb066..61e1971d281 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/FaasEnvironment.java +++ b/driver-core/src/main/com/mongodb/internal/connection/FaasEnvironment.java @@ -16,13 +16,12 @@ package com.mongodb.internal.connection; +import com.mongodb.internal.EnvironmentProvider; import com.mongodb.lang.Nullable; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; enum FaasEnvironment { AWS_LAMBDA("aws.lambda"), @@ -31,8 +30,6 @@ enum FaasEnvironment { VERCEL("vercel"), UNKNOWN(null); - static final Map ENV_OVERRIDES_FOR_TESTING = new HashMap<>(); - static FaasEnvironment getFaasEnvironment() { List result = new ArrayList<>(); String awsExecutionEnv = getEnv("AWS_EXECUTION_ENV"); @@ -62,10 +59,7 @@ static FaasEnvironment getFaasEnvironment() { @Nullable public static String getEnv(final String key) { - if (ENV_OVERRIDES_FOR_TESTING.containsKey(key)) { - return ENV_OVERRIDES_FOR_TESTING.get(key); - } - return System.getenv(key); + return EnvironmentProvider.getEnv(key); } @Nullable diff --git a/driver-core/src/main/com/mongodb/internal/connection/OidcAuthenticator.java b/driver-core/src/main/com/mongodb/internal/connection/OidcAuthenticator.java index f8c7f3ea893..3283bd82b85 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/OidcAuthenticator.java +++ b/driver-core/src/main/com/mongodb/internal/connection/OidcAuthenticator.java @@ -17,6 +17,7 @@ package com.mongodb.internal.connection; import com.mongodb.AuthenticationMechanism; +import com.mongodb.internal.EnvironmentProvider; import com.mongodb.MongoClientException; import com.mongodb.MongoCommandException; import com.mongodb.MongoConfigurationException; @@ -235,8 +236,8 @@ private static OidcCallback getTestCallback() { @VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE) static OidcCallback getK8sCallback() { return (context) -> { - String azure = System.getenv(K8S_AZURE_FILE); - String aws = System.getenv(K8S_AWS_FILE); + String azure = EnvironmentProvider.getEnv(K8S_AZURE_FILE); + String aws = EnvironmentProvider.getEnv(K8S_AWS_FILE); String path; if (azure != null) { path = azure; @@ -542,7 +543,7 @@ public boolean isComplete() { } private static String readTokenFromFile() { - String path = System.getenv(OIDC_TOKEN_FILE); + String path = EnvironmentProvider.getEnv(OIDC_TOKEN_FILE); if (path == null) { throw new MongoClientException( format("Environment variable must be specified: %s", OIDC_TOKEN_FILE)); diff --git a/driver-core/src/main/com/mongodb/internal/observability/micrometer/MicrometerTracer.java b/driver-core/src/main/com/mongodb/internal/observability/micrometer/MicrometerTracer.java index a7204a01a71..92c124255a4 100644 --- a/driver-core/src/main/com/mongodb/internal/observability/micrometer/MicrometerTracer.java +++ b/driver-core/src/main/com/mongodb/internal/observability/micrometer/MicrometerTracer.java @@ -38,7 +38,7 @@ import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_TYPE; import static com.mongodb.internal.observability.micrometer.MongodbObservation.MONGODB_OBSERVATION; import static com.mongodb.internal.observability.micrometer.TracingManager.ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH; -import static java.lang.System.getenv; +import static com.mongodb.internal.EnvironmentProvider.getEnv; import static java.util.Optional.ofNullable; @@ -75,7 +75,7 @@ public MicrometerTracer(final ObservationRegistry observationRegistry) { public MicrometerTracer(final ObservationRegistry observationRegistry, final boolean allowCommandPayload, final int textMaxLength) { this.allowCommandPayload = allowCommandPayload; this.observationRegistry = observationRegistry; - this.textMaxLength = ofNullable(getenv(ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH)) + this.textMaxLength = ofNullable(getEnv(ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH)) .map(Integer::parseInt) .orElse(textMaxLength); } diff --git a/driver-core/src/main/com/mongodb/internal/observability/micrometer/TracingManager.java b/driver-core/src/main/com/mongodb/internal/observability/micrometer/TracingManager.java index 4247ed1c3dd..4e4a5daa993 100644 --- a/driver-core/src/main/com/mongodb/internal/observability/micrometer/TracingManager.java +++ b/driver-core/src/main/com/mongodb/internal/observability/micrometer/TracingManager.java @@ -49,7 +49,7 @@ import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SYSTEM; import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.TRANSACTION_NUMBER; import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.CURSOR_ID; -import static java.lang.System.getenv; +import static com.mongodb.internal.EnvironmentProvider.getEnv; /** * Manages tracing spans for MongoDB driver activities. @@ -93,7 +93,7 @@ public TracingManager(@Nullable final ObservabilitySettings observabilitySetting throw new IllegalArgumentException("Only Micrometer based observability is currently supported"); } - String envOtelInstrumentationEnabled = getenv(ENV_OBSERVABILITY_ENABLED); + String envOtelInstrumentationEnabled = getEnv(ENV_OBSERVABILITY_ENABLED); boolean enableTracing = true; if (envOtelInstrumentationEnabled != null) { enableTracing = Boolean.parseBoolean(envOtelInstrumentationEnabled); diff --git a/driver-core/src/test/functional/com/mongodb/client/WithWrapper.java b/driver-core/src/test/functional/com/mongodb/client/WithWrapper.java index e610f578112..d7ad3baf1b8 100644 --- a/driver-core/src/test/functional/com/mongodb/client/WithWrapper.java +++ b/driver-core/src/test/functional/com/mongodb/client/WithWrapper.java @@ -16,11 +16,9 @@ package com.mongodb.client; -import com.mongodb.internal.connection.FaasEnvironmentAccessor; +import com.mongodb.internal.EnvironmentProvider; import com.mongodb.lang.Nullable; -import java.util.Map; - @FunctionalInterface public interface WithWrapper { @@ -32,22 +30,12 @@ static WithWrapper withWrapper() { default WithWrapper withEnvironmentVariable(final String name, @Nullable final String value) { return runnable -> { - Map innerMap = FaasEnvironmentAccessor.getFaasEnvMap(); - String original = innerMap.get(name); - if (value == null) { - innerMap.remove(name); - } else { - innerMap.put(name, value); - } - try { - this.run(runnable); - } finally { - if (original == null) { - innerMap.remove(name); - } else { - innerMap.put(name, original); + this.run(() -> { + try (EnvironmentProvider.EnvironmentOverride env = EnvironmentProvider.envOverride()) { + env.set(name, value); + runnable.run(); } - } + }); }; } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataTest.java index 6ea8c506ff4..098d436d447 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataTest.java @@ -234,7 +234,7 @@ void testDockerMetadataIncluded() { @Test void testDockerAndKubernetesMetadataIncluded() { try (MockedStatic pathsMockedStatic = Mockito.mockStatic(Files.class)) { - Path path = Paths.get(File.separator + "/.dockerenv"); + Path path = Paths.get(File.separator + ".dockerenv"); pathsMockedStatic.when(() -> Files.exists(path)).thenReturn(true); withWrapper() diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/FaasEnvironmentAccessor.java b/driver-core/src/test/functional/com/mongodb/internal/connection/FaasEnvironmentAccessor.java deleted file mode 100644 index ccc71f718ba..00000000000 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/FaasEnvironmentAccessor.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2008-present MongoDB, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.mongodb.internal.connection; - -import java.util.Map; - -/** - * In the same package as FaasEnvironment, to access package-private - */ -public final class FaasEnvironmentAccessor { - private FaasEnvironmentAccessor() { - } - - public static Map getFaasEnvMap() { - return FaasEnvironment.ENV_OVERRIDES_FOR_TESTING; - } -} diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy index 68a82fcbf74..53a19270fef 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy @@ -43,6 +43,7 @@ import static java.util.concurrent.TimeUnit.SECONDS class SocketStreamHelperSpecification extends Specification { + @IgnoreIf({ System.getProperty('os.name').toLowerCase().contains('windows') }) def 'should configure socket with settings()'() { given: Socket socket = SocketFactory.default.createSocket() diff --git a/driver-core/src/test/unit/com/mongodb/internal/EnvironmentProviderTest.java b/driver-core/src/test/unit/com/mongodb/internal/EnvironmentProviderTest.java new file mode 100644 index 00000000000..f683e4ef56b --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/EnvironmentProviderTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.internal; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class EnvironmentProviderTest { + + @Test + void shouldDelegateGetEnvToSystemByDefault() { + String path = System.getenv("PATH"); + assertEquals(path, EnvironmentProvider.getEnv("PATH")); + } + + @Test + void shouldInterceptGetEnvWhenOverridden() { + try (EnvironmentProvider.EnvironmentOverride env = EnvironmentProvider.envOverride()) { + env.set("MY_TEST_VAR", "hello"); + assertEquals("hello", EnvironmentProvider.getEnv("MY_TEST_VAR")); + } + + assertNull(EnvironmentProvider.getEnv("MY_TEST_VAR")); + } + + @Test + void shouldAllowSettingNullOverride() { + try (EnvironmentProvider.EnvironmentOverride env = EnvironmentProvider.envOverride()) { + env.set("PATH", null); + assertNull(EnvironmentProvider.getEnv("PATH")); + } + + assertEquals(System.getenv("PATH"), EnvironmentProvider.getEnv("PATH")); + } + + @Test + void shouldChainOverridesCorrectly() { + try (EnvironmentProvider.EnvironmentOverride outer = EnvironmentProvider.envOverride()) { + outer.set("VAR_A", "outer"); + assertEquals("outer", EnvironmentProvider.getEnv("VAR_A")); + + try (EnvironmentProvider.EnvironmentOverride inner = EnvironmentProvider.envOverride()) { + inner.set("VAR_B", "inner"); + assertEquals("outer", EnvironmentProvider.getEnv("VAR_A")); + assertEquals("inner", EnvironmentProvider.getEnv("VAR_B")); + } + + assertEquals("outer", EnvironmentProvider.getEnv("VAR_A")); + assertNull(EnvironmentProvider.getEnv("VAR_B")); + } + } + + @Test + void shouldReturnSelfForChaining() { + try (EnvironmentProvider.EnvironmentOverride env = EnvironmentProvider.envOverride()) { + env.set("A", "1").set("B", "2").set("C", "3"); + assertEquals("1", EnvironmentProvider.getEnv("A")); + assertEquals("2", EnvironmentProvider.getEnv("B")); + assertEquals("3", EnvironmentProvider.getEnv("C")); + } + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractMicrometerProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractMicrometerProseTest.java index 746b0ffd8d9..cba6e43b4d4 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractMicrometerProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractMicrometerProseTest.java @@ -17,7 +17,7 @@ package com.mongodb.client; import com.mongodb.MongoClientSettings; -import com.mongodb.lang.Nullable; +import com.mongodb.internal.EnvironmentProvider; import com.mongodb.observability.ObservabilitySettings; import com.mongodb.client.observability.SpanTree; import com.mongodb.client.observability.SpanTree.SpanNode; @@ -33,7 +33,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -62,23 +61,18 @@ public abstract class AbstractMicrometerProseTest { private final ObservationRegistry observationRegistry = ObservationRegistry.create(); private InMemoryOtelSetup memoryOtelSetup; private InMemoryOtelSetup.Builder.OtelBuildingBlocks inMemoryOtel; - private static String previousEnvVarMdbTracingEnabled; - private static String previousEnvVarMdbQueryTextLength; + private static EnvironmentProvider.EnvironmentOverride environmentOverride; protected abstract MongoClient createMongoClient(MongoClientSettings settings); @BeforeAll static void beforeAll() { - // preserve original env var values - previousEnvVarMdbTracingEnabled = System.getenv(ENV_OBSERVABILITY_ENABLED); - previousEnvVarMdbQueryTextLength = System.getenv(ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH); + environmentOverride = EnvironmentProvider.envOverride(); } @AfterAll - static void afterAll() throws Exception { - // restore original env var values - setEnv(ENV_OBSERVABILITY_ENABLED, previousEnvVarMdbTracingEnabled); - setEnv(ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH, previousEnvVarMdbQueryTextLength); + static void afterAll() { + environmentOverride.close(); } @BeforeEach @@ -95,7 +89,7 @@ void tearDown() { @DisplayName("Test 1: Tracing Enable/Disable via Environment Variable") @Test void testControlOtelInstrumentationViaEnvironmentVariable() throws Exception { - setEnv(ENV_OBSERVABILITY_ENABLED, "false"); + environmentOverride.set(ENV_OBSERVABILITY_ENABLED, "false"); // don't enable command payload by default MongoClientSettings clientSettings = getMongoClientSettingsBuilder() .observabilitySettings(ObservabilitySettings.micrometerBuilder() @@ -113,7 +107,7 @@ void testControlOtelInstrumentationViaEnvironmentVariable() throws Exception { assertTrue(inMemoryOtel.getFinishedSpans().isEmpty(), "Spans should not be emitted when instrumentation is disabled."); } - setEnv(ENV_OBSERVABILITY_ENABLED, "true"); + environmentOverride.set(ENV_OBSERVABILITY_ENABLED, "true"); try (MongoClient client = createMongoClient(clientSettings)) { MongoDatabase database = client.getDatabase(getDefaultDatabaseName()); MongoCollection collection = database.getCollection("test"); @@ -133,7 +127,7 @@ void testControlOtelInstrumentationViaEnvironmentVariable() throws Exception { @DisplayName("Test 2: Command Payload Emission via Environment Variable") @Test void testControlCommandPayloadViaEnvironmentVariable() throws Exception { - setEnv(ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH, "42"); + environmentOverride.set(ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH, "42"); MicrometerObservabilitySettings settings = MicrometerObservabilitySettings.builder() .observationRegistry(observationRegistry) .enableCommandPayloadTracing(true) @@ -167,7 +161,7 @@ void testControlCommandPayloadViaEnvironmentVariable() throws Exception { memoryOtelSetup = InMemoryOtelSetup.builder().register(observationRegistry); inMemoryOtel = memoryOtelSetup.getBuildingBlocks(); - setEnv(ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH, null); // Unset the environment variable + environmentOverride.set(ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH, null); // Unset the environment variable clientSettings = getMongoClientSettingsBuilder() @@ -226,7 +220,7 @@ void testControlCommandPayloadViaEnvironmentVariable() throws Exception { */ @Test void testConcurrentOperationsHaveSeparateSpans() throws Exception { - setEnv(ENV_OBSERVABILITY_ENABLED, "true"); + environmentOverride.set(ENV_OBSERVABILITY_ENABLED, "true"); int nbrConcurrentOps = 10; MongoClientSettings clientSettings = getMongoClientSettingsBuilder() .applyToConnectionPoolSettings(pool -> pool.maxSize(nbrConcurrentOps)) @@ -317,26 +311,4 @@ void testConcurrentOperationsHaveSeparateSpans() throws Exception { } } - @SuppressWarnings("unchecked") - private static void setEnv(final String key, @Nullable final String value) throws Exception { - // Get the unmodifiable Map from System.getenv() - Map env = System.getenv(); - - // Use reflection to get the class of the unmodifiable map - Class unmodifiableMapClass = env.getClass(); - - // Get the 'm' field which holds the actual modifiable map - Field mField = unmodifiableMapClass.getDeclaredField("m"); - mField.setAccessible(true); - - // Get the modifiable map from the 'm' field - Map modifiableEnv = (Map) mField.get(env); - - // Modify the map - if (value == null) { - modifiableEnv.remove(key); - } else { - modifiableEnv.put(key, value); - } - } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractSessionsProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractSessionsProseTest.java index 910cf57edfd..6a1216687bb 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractSessionsProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractSessionsProseTest.java @@ -271,7 +271,7 @@ private static Process startMongocryptdProcess() throws IOException { "--port", port, "--pidfilepath", "mongocryptd-" + port + ".pid")); processBuilder.redirectErrorStream(true); - processBuilder.redirectOutput(new File("/tmp/mongocryptd.log")); + processBuilder.redirectOutput(new File(System.getProperty("java.io.tmpdir"), "mongocryptd.log")); return processBuilder.start(); }