diff --git a/.evergreen/setup-env.bash b/.evergreen/setup-env.bash index a8a7840292c..c8a96b0300c 100644 --- a/.evergreen/setup-env.bash +++ b/.evergreen/setup-env.bash @@ -1,5 +1,15 @@ # Java configurations for evergreen +# On Windows Evergreen hosts `OS` is a native environment variable set to +# "Windows_NT". It is not set on other platforms, so default it from `uname` +# to avoid an unbound variable error under `set -u`. +if [ -z "${OS:-}" ]; then + case "$(uname -s)" in + CYGWIN*|MINGW*|MSYS*|Windows_NT) OS="Windows_NT" ;; + *) OS="$(uname -s)" ;; + esac +fi + if [ "Windows_NT" == "$OS" ]; then export JDK8="/cygdrive/c/java/jdk8" export JDK11="/cygdrive/c/java/jdk11" diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java index 228dc3ede76..16c0c2f856b 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java @@ -190,8 +190,8 @@ public void asStringTest() { } @Test - public void asStringTestNestedPre82() { - assumeTrue(serverVersionLessThan(8, 2)); + public void asStringTestNestedPre83() { + assumeTrue(serverVersionLessThan(8, 3)); // Arrays and documents are not (yet) supported: assertThrows(MongoCommandException.class, () -> @@ -202,7 +202,7 @@ public void asStringTestNestedPre82() { @Test public void asStringTestNested() { - assumeTrue(serverVersionAtLeast(8, 2)); + assumeTrue(serverVersionAtLeast(8, 3)); assertExpression("[1,2]", ofIntegerArray(1, 2).asString()); assertExpression("{\"a\":1}", of(Document.parse("{a: 1}")).asString()); diff --git a/driver-sync/src/test/functional/com/mongodb/client/Fixture.java b/driver-sync/src/test/functional/com/mongodb/client/Fixture.java index 8114d62e41a..8e1227803ba 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/Fixture.java +++ b/driver-sync/src/test/functional/com/mongodb/client/Fixture.java @@ -54,7 +54,11 @@ public static synchronized MongoClient getMongoClient() { return; } if (defaultDatabase != null) { - defaultDatabase.drop(); + try { + defaultDatabase.drop(); + } catch (Exception e) { + // ignore + } } mongoClient.close(); mongoClient = null; diff --git a/mongodb-crypt/build.gradle.kts b/mongodb-crypt/build.gradle.kts index 208034beaad..9858e283af6 100644 --- a/mongodb-crypt/build.gradle.kts +++ b/mongodb-crypt/build.gradle.kts @@ -168,10 +168,45 @@ abstract class VerifyLibmongocryptTask : DefaultTask() { return } + // The gpg shipped on the Evergreen Windows hosts is a Cygwin build that only understands POSIX paths. + // Handed a native Windows path ("C:\dir") it treats the backslash path as relative, prepends its working + // directory, and fails with "no writable keyring found". Translate drive-letter paths to the Cygwin form + // ("C:\dir" -> "/cygdrive/c/dir") on Windows so every path argument (homedir, key, signatures, tarballs) + // is parsed correctly; other platforms pass paths through unchanged. + val isWindows = System.getProperty("os.name").startsWith("Windows", ignoreCase = true) + fun toGpgPath(path: String): String { + if (!isWindows) { + return path + } + val driveLetter = Regex("^([A-Za-z]):[\\\\/](.*)$").matchEntire(path) ?: return path.replace('\\', '/') + val (drive, rest) = driveLetter.destructured + return "/cygdrive/${drive.lowercase(java.util.Locale.ROOT)}/${rest.replace('\\', '/')}" + } + + // Run gpg capturing both streams; on non-zero exit throw with the captured output appended so the + // underlying gpg diagnostic is visible instead of Gradle's opaque "finished with non-zero exit value N". + fun runGpg(vararg args: String, onFailure: (String) -> String) { + val out = ByteArrayOutputStream() + val err = ByteArrayOutputStream() + val result = + execOps.exec { + commandLine(listOf("gpg") + args) + standardOutput = out + errorOutput = err + isIgnoreExitValue = true + } + val combined = (out.toString().trim() + "\n" + err.toString().trim()).trim() + logger.info("gpg ${args.joinToString(" ")} -> exit ${result.exitValue}\n$combined") + if (result.exitValue != 0) { + throw GradleException("${onFailure(combined)}\ngpg command: gpg ${args.joinToString(" ")}") + } + } + + val versionOut = ByteArrayOutputStream() try { execOps.exec { commandLine("gpg", "--version") - standardOutput = ByteArrayOutputStream() + standardOutput = versionOut } } catch (e: Exception) { throw GradleException( @@ -180,6 +215,7 @@ abstract class VerifyLibmongocryptTask : DefaultTask() { "or pass -PskipCryptVerify=true for offline development builds.", e) } + logger.lifecycle("Using gpg:\n${versionOut.toString().trim().lineSequence().firstOrNull() ?: ""}") val home = gnupgHome.get().asFile.apply { @@ -193,41 +229,28 @@ abstract class VerifyLibmongocryptTask : DefaultTask() { setExecutable(false, false) setExecutable(true, true) } + val homedir = toGpgPath(home.path) + logger.lifecycle( + "libmongocrypt verify: gnupgHome=${home.path} -> $homedir (exists=${home.exists()}, " + + "writable=${home.canWrite()}), publicKey=${publicKey.get().asFile.path} " + + "(exists=${publicKey.get().asFile.exists()})") - execOps.exec { - commandLine( - "gpg", - "--homedir", - home.path, - "--batch", - "--quiet", - "--no-autostart", - "--import", - publicKey.get().asFile.path) - standardOutput = ByteArrayOutputStream() - errorOutput = ByteArrayOutputStream() + runGpg("--homedir", homedir, "--batch", "--no-autostart", "--import", toGpgPath(publicKey.get().asFile.path)) { + output -> + "Failed to import libmongocrypt signing key into scratch keyring at ${home.path}.\n$output" } - try { - execOps.exec { - commandLine( - "gpg", - "--homedir", - home.path, - "--batch", - "--no-autostart", - "--with-colons", - "--fingerprint", - expectedFingerprint.get()) - standardOutput = ByteArrayOutputStream() - errorOutput = ByteArrayOutputStream() - } - } catch (e: Exception) { - throw GradleException( + runGpg( + "--homedir", + homedir, + "--batch", + "--no-autostart", + "--with-colons", + "--fingerprint", + expectedFingerprint.get()) { output -> "Imported libmongocrypt signing key fingerprint does not match expected value " + - "${expectedFingerprint.get()}. The downloaded public key may have been rotated.", - e) - } + "${expectedFingerprint.get()}. The downloaded public key may have been rotated.\n$output" + } // Pair tarballs with signatures by basename; ConfigurableFileCollection.files is an // unordered Set, so zipping the two collections could mismatch pairs. @@ -238,28 +261,18 @@ abstract class VerifyLibmongocryptTask : DefaultTask() { signaturesByName[signatureName] ?: throw GradleException( "Missing signature $signatureName for ${tarball.name}; expected it next to the tarball.") - val verifyErr = ByteArrayOutputStream() - try { - execOps.exec { - commandLine( - "gpg", - "--homedir", - home.path, - "--batch", - "--quiet", - "--no-autostart", - "--trust-model", - "always", - "--verify", - signature.path, - tarball.path) - standardOutput = ByteArrayOutputStream() - errorOutput = verifyErr + runGpg( + "--homedir", + homedir, + "--batch", + "--no-autostart", + "--trust-model", + "always", + "--verify", + toGpgPath(signature.path), + toGpgPath(tarball.path)) { output -> + "GPG signature verification failed for ${tarball.name}:\n$output" } - } catch (e: Exception) { - throw GradleException( - "GPG signature verification failed for ${tarball.name}:\n${verifyErr.toString().trim()}", e) - } } verificationStamp