From 6d472be847518dbf96319893db387a097d7b0d51 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 2 Jun 2026 10:27:30 +0100 Subject: [PATCH 1/3] Remove skip for GridFS download timeout test - Remove JAVA-5839 skip entry for "timeoutMS applied to entire download, not individual parts" test so it runs in CI JAVA-5839 --- .../com/mongodb/client/unified/UnifiedTestModifications.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java index 286e6f525a..a6a375382b 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java @@ -104,10 +104,6 @@ public static void applyCustomizations(final TestDef def) { .test("client-side-operations-timeout", "timeoutMS behaves correctly for tailable awaitData cursors", "apply maxAwaitTimeMS if less than remaining timeout"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5839") - .test("client-side-operations-timeout", "timeoutMS behaves correctly for GridFS download operations", - "timeoutMS applied to entire download, not individual parts"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5491") .when(() -> !serverVersionLessThan(8, 3)) .test("client-side-operations-timeout", "operations ignore deprecated timeout options if timeoutMS is set", From df6993891f715c0908a7027b692d38f90f56b711 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 2 Jun 2026 11:27:08 +0100 Subject: [PATCH 2/3] Rename misleading variable names in UnifiedTestModifications Rename dir/file/test to directory/fileDescription/testDescription to clarify that 'file' refers to the spec file's description field, not the filename, and 'test' refers to the individual test description. JAVA-5839 --- .../unified/UnifiedTestModifications.java | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java index a6a375382b..1a3cb5c612 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java @@ -504,27 +504,27 @@ public static void applyCustomizations(final TestDef def) { private UnifiedTestModifications() { } - public static TestDef testDef(final String dir, final String file, final String test, final boolean reactive, - final UnifiedTest.Language language) { - return new TestDef(dir, file, test, reactive, language); + public static TestDef testDef(final String directory, final String fileDescription, final String testDescription, + final boolean reactive, final UnifiedTest.Language language) { + return new TestDef(directory, fileDescription, testDescription, reactive, language); } public static final class TestDef { - private final String dir; - private final String file; - private final String test; + private final String directory; + private final String fileDescription; + private final String testDescription; private final boolean reactive; private final UnifiedTest.Language language; private final List modifiers = new ArrayList<>(); private Function matchesThrowable; - private TestDef(final String dir, final String file, final String test, final boolean reactive, - final UnifiedTest.Language language) { - this.dir = assertNotNull(dir); - this.file = assertNotNull(file); - this.test = assertNotNull(test); + private TestDef(final String directory, final String fileDescription, final String testDescription, + final boolean reactive, final UnifiedTest.Language language) { + this.directory = assertNotNull(directory); + this.fileDescription = assertNotNull(fileDescription); + this.testDescription = assertNotNull(testDescription); this.reactive = reactive; this.language = assertNotNull(language); } @@ -534,9 +534,9 @@ public String toString() { return "TestDef{" + "modifiers=" + modifiers + ", reactive=" + reactive - + ", test='" + test + '\'' - + ", file='" + file + '\'' - + ", dir='" + dir + '\'' + + ", testDescription='" + testDescription + '\'' + + ", fileDescription='" + fileDescription + '\'' + + ", directory='" + directory + '\'' + '}'; } @@ -671,59 +671,59 @@ private TestApplicator onMatch(final boolean match) { /** * Applies to all tests in directory. * - * @param dir the directory name + * @param directory the directory name * @return this */ - public TestApplicator directory(final String dir) { - boolean match = (dir).equals(testDef.dir); + public TestApplicator directory(final String directory) { + boolean match = (directory).equals(testDef.directory); return onMatch(match); } /** * Applies to all tests in file under the directory. * - * @param dir the directory name - * @param file the test file's "description" field + * @param directory the directory name + * @param fileDescription the test file's "description" field * @return this */ - public TestApplicator file(final String dir, final String file) { - boolean match = (dir).equals(testDef.dir) - && file.equals(testDef.file); + public TestApplicator file(final String directory, final String fileDescription) { + boolean match = (directory).equals(testDef.directory) + && fileDescription.equals(testDef.fileDescription); return onMatch(match); } /** - * Applies to the test where dir, file, and test match. + * Applies to the test where directory, fileDescription, and testDescription match. * - * @param dir the directory name - * @param file the test file's "description" field - * @param test the individual test's "description" field + * @param directory the directory name + * @param fileDescription the test file's "description" field + * @param testDescription the individual test's "description" field * @return this */ - public TestApplicator test(final String dir, final String file, final String test) { - boolean match = testDef.dir.equals(dir) - && testDef.file.equals(file) - && testDef.test.equals(test); + public TestApplicator test(final String directory, final String fileDescription, final String testDescription) { + boolean match = testDef.directory.equals(directory) + && testDef.fileDescription.equals(fileDescription) + && testDef.testDescription.equals(testDescription); return onMatch(match); } /** * Utility method: emit replacement to standard out. * - * @param dir the directory name - * @param fragment the substring to check in the test "description" field + * @param directory the directory name + * @param fragment the substring to check in the test "description" field * @return this */ - public TestApplicator testContains(final String dir, final String fragment) { - boolean match = (dir).equals(testDef.dir) - && testDef.test.contains(fragment); + public TestApplicator testContains(final String directory, final String fragment) { + boolean match = (directory).equals(testDef.directory) + && testDef.testDescription.contains(fragment); if (match) { System.out.printf( "!!! REPLACE %s WITH: .test(\"%s\", \"%s\", \"%s\")%n", fragment, - testDef.dir, - testDef.file, - testDef.test); + testDef.directory, + testDef.fileDescription, + testDef.testDescription); } return this; } @@ -731,16 +731,16 @@ public TestApplicator testContains(final String dir, final String fragment) { /** * Utility method: emit file info to standard out * - * @param dir the directory name - * @param test the individual test's "description" field + * @param directory the directory name + * @param testDescription the individual test's "description" field * @return this */ - public TestApplicator debug(final String dir, final String test) { - boolean match = testDef.test.equals(test); + public TestApplicator debug(final String directory, final String testDescription) { + boolean match = testDef.testDescription.equals(testDescription); if (match) { System.out.printf( "!!! ADD: \"%s\", \"%s\", \"%s\"%n", - testDef.dir, testDef.file, test); + testDef.directory, testDef.fileDescription, testDescription); } return this; } From e66c06e1add9c571ff1f48b3e5e9f30e32a33821 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 2 Jun 2026 11:30:12 +0100 Subject: [PATCH 3/3] Add transform mechanism to UnifiedTestModifications for test data mutation Add TestTransformer functional interface and transform() method to TestDef, allowing spec test data (entities and definition) to be mutated before execution. Transformations are logged with a mandatory reason string. Apply transform for JAVA-5839: bump timeoutMS from 75 to 250 and blockTimeMS from 50 to 200 for the GridFS download timeout test to avoid CI latency failures. The 150ms margin (6x the original 25ms) should be reliable across CI environments. JAVA-5839 --- .../mongodb/client/unified/UnifiedTest.java | 7 +- .../unified/UnifiedTestModifications.java | 119 +++++++++++++++++- 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java index 35189aef45..beea019b86 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java @@ -267,6 +267,11 @@ public void setUp( boolean skip = testDef.wasAssignedModifier(Modifier.SKIP); assumeFalse(skip, "Skipping test"); + + if (testDef.hasTransformations()) { + this.entitiesArray = entitiesArray.clone(); + testDef.applyTransformations(this.entitiesArray, definition); + } } skips(fileDescription, testDescription); @@ -289,7 +294,7 @@ public void setUp( startingClusterTime = addInitialDataAndGetClusterTime(); - entities.init(entitiesArray, startingClusterTime, + entities.init(this.entitiesArray, startingClusterTime, fileDescription != null && PRESTART_POOL_ASYNC_WORK_MANAGER_FILE_DESCRIPTIONS.contains(fileDescription), this::createMongoClient, this::createGridFSBucket, diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java index 1a3cb5c612..d1f1a194f8 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java @@ -17,10 +17,18 @@ package com.mongodb.client.unified; import com.mongodb.ClusterFixture; +import com.mongodb.lang.Nullable; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonInt32; +import org.bson.BsonValue; +import org.bson.diagnostics.Logger; +import org.bson.diagnostics.Loggers; import org.opentest4j.AssertionFailedError; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; @@ -39,6 +47,8 @@ import static java.lang.String.format; public final class UnifiedTestModifications { + private static final Logger LOGGER = Loggers.getLogger("UnifiedTestModifications"); + public static void applyCustomizations(final TestDef def) { // change-streams @@ -316,6 +326,15 @@ public static void applyCustomizations(final TestDef def) { def.skipJira("https://jira.mongodb.org/browse/JAVA-5689") .file("gridfs", "gridfs-deleteByName") .file("gridfs", "gridfs-renameByName"); + def.transform("JAVA-5839: Bump blocking/timeout to avoid CI latency failures", + (entitiesArray, definition) -> { + findAndSetInt(entitiesArray, "client.uriOptions.timeoutMS", 250); + findAndSetInt(definition.getArray("operations"), + "arguments.failPoint.data.blockTimeMS", 200); + }) + .test("client-side-operations-timeout", + "timeoutMS behaves correctly for GridFS download operations", + "timeoutMS applied to entire download, not individual parts"); // Skip all rawData based tests def.skipJira("https://jira.mongodb.org/browse/JAVA-5830 rawData support only added to Go and Node") @@ -501,6 +520,44 @@ public static void applyCustomizations(final TestDef def) { .file("unified-test-format/tests/valid-fail", "operator-matchAsDocument"); } + /** + * Searches each document in {@code array} for a nested int field specified + * by a dot-separated {@code path}, and replaces it with {@code newValue}. + * Logs each replacement. Silently skips documents where the path does not + * exist or the intermediate keys are absent. + * + *

Example: {@code findAndSetInt(entitiesArray, "client.uriOptions.timeoutMS", 250)} + * walks each element looking for {@code element.client.uriOptions.timeoutMS}.

+ * + * @param array the array to search + * @param path dot-separated path to an int field + * @param newValue the replacement value + */ + static void findAndSetInt(final BsonArray array, final String path, final int newValue) { + String[] segments = path.split("\\."); + for (BsonValue element : array) { + if (!element.isDocument()) { + continue; + } + BsonDocument current = element.asDocument(); + boolean found = true; + for (int i = 0; i < segments.length - 1; i++) { + if (current.containsKey(segments[i]) && current.get(segments[i]).isDocument()) { + current = current.getDocument(segments[i]); + } else { + found = false; + break; + } + } + String leafKey = segments[segments.length - 1]; + if (found && current.containsKey(leafKey) && current.get(leafKey).isInt32()) { + int oldValue = current.getInt32(leafKey).getValue(); + LOGGER.info(format(" %s: %d -> %d", leafKey, oldValue, newValue)); + current.put(leafKey, new BsonInt32(newValue)); + } + } + } + private UnifiedTestModifications() { } @@ -518,6 +575,7 @@ public static final class TestDef { private final UnifiedTest.Language language; private final List modifiers = new ArrayList<>(); + private final List transformers = new ArrayList<>(); private Function matchesThrowable; private TestDef(final String directory, final String fileDescription, final String testDescription, @@ -610,6 +668,34 @@ public TestApplicator retryReactive(final String reason) { .when(this::isReactive); } + /** + * Registers a transformation that mutates the test's entity and + * definition data before execution. The reason is logged when the + * transformation is registered for a matching test. + * + * @param reason why the transformation is needed + * @param transformer the transformation to apply + */ + public TestApplicator transform(final String reason, final TestTransformer transformer) { + return new TestApplicator(this, reason, transformer); + } + + /** + * Applies all registered transformations to the test data. + */ + public void applyTransformations(final BsonArray entitiesArray, final BsonDocument definition) { + for (TestTransformer transformer : transformers) { + transformer.transform(entitiesArray, definition); + } + } + + /** + * Returns true if any transformations have been registered. + */ + public boolean hasTransformations() { + return !transformers.isEmpty(); + } + public TestApplicator modify(final Modifier... modifiers) { return new TestApplicator(this, null, modifiers); } @@ -644,18 +730,34 @@ public static final class TestApplicator { private final List modifiersToApply; private Function matchesThrowable; + @Nullable + private final TestTransformer transformer; + @Nullable + private final String reason; private TestApplicator( final TestDef testDef, - final String reason, + @Nullable final String reason, final Modifier... modifiersToApply) { this.testDef = testDef; + this.reason = reason; this.modifiersToApply = Arrays.asList(modifiersToApply); + this.transformer = null; if (this.modifiersToApply.contains(SKIP) || this.modifiersToApply.contains(RETRY)) { assertNotNull(reason); } } + private TestApplicator( + final TestDef testDef, + final String reason, + final TestTransformer transformer) { + this.testDef = testDef; + this.reason = assertNotNull(reason); + this.modifiersToApply = Collections.emptyList(); + this.transformer = assertNotNull(transformer); + } + private TestApplicator onMatch(final boolean match) { matchWasPerformed = true; if (precondition != null && !precondition.get()) { @@ -664,6 +766,11 @@ private TestApplicator onMatch(final boolean match) { if (match) { this.testDef.modifiers.addAll(this.modifiersToApply); this.testDef.matchesThrowable = this.matchesThrowable; + if (this.transformer != null) { + LOGGER.info("Registered transformation for test [" + + testDef.testDescription + "]: " + reason); + this.testDef.transformers.add(this.transformer); + } } return this; } @@ -784,6 +891,16 @@ public TestApplicator whenFailureContains(final String messageFragment) { } + /** + * A transformation that mutates the test's entity array and/or definition + * before execution. Used to adjust spec test values (e.g., timeouts) that + * are too tight for CI environments. + */ + @FunctionalInterface + public interface TestTransformer { + void transform(BsonArray entitiesArray, BsonDocument definition); + } + public enum Modifier { /** * Reactive only.