From f067affabf8c7d457dc1fb8ec5e3b13ae04bf9e2 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 14 Mar 2024 14:00:53 +0100 Subject: [PATCH 1/9] escape spaces in paths --- .../java/io/sentry/cli/SentryCliRunner.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/sentry/cli/SentryCliRunner.java b/src/main/java/io/sentry/cli/SentryCliRunner.java index 9a9902a..6fa6169 100644 --- a/src/main/java/io/sentry/cli/SentryCliRunner.java +++ b/src/main/java/io/sentry/cli/SentryCliRunner.java @@ -65,16 +65,18 @@ public SentryCliRunner( attributes( attribute("executable", executable), attribute("failOnError", String.valueOf(failOnError)), - attribute("output", logFile.getAbsolutePath())), + attribute("output", escape(logFile.getAbsolutePath(), isWindows))), element(name("arg"), attributes(attribute("value", cArg))), element( name("arg"), attributes( attribute( "value", - getCliPath(mavenProject, sentryCliExecutablePath) - + " " - + sentryCliCommand)))))), + escape( + getCliPath(mavenProject, sentryCliExecutablePath) + + " " + + sentryCliCommand, + isWindows))))))), executionEnvironment(mavenProject, mavenSession, pluginManager)); return collectAndMaybePrintOutput(logFile, debugSentryCli); @@ -92,6 +94,17 @@ public SentryCliRunner( } } + private @Nullable String escape(final @Nullable String toEscape, final boolean isWindows) { + if (toEscape == null) { + return null; + } + if (isWindows) { + return toEscape.replaceAll(" ", "^ "); + } else { + return toEscape.replaceAll(" ", "\\ "); + } + } + private @Nullable String collectAndMaybePrintOutput( final @NotNull File logFile, final boolean shouldPrint) { try { From 6ebffff22dea2e5b33bb39165678e7ab3d67321a Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 25 Mar 2024 10:43:50 +0100 Subject: [PATCH 2/9] CR changes --- .../io/sentry/UploadSourceBundleMojo.java | 7 +++--- .../java/io/sentry/cli/SentryCliRunner.java | 25 +++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/sentry/UploadSourceBundleMojo.java b/src/main/java/io/sentry/UploadSourceBundleMojo.java index 5701a9f..c855868 100644 --- a/src/main/java/io/sentry/UploadSourceBundleMojo.java +++ b/src/main/java/io/sentry/UploadSourceBundleMojo.java @@ -135,7 +135,8 @@ private void bundleSources( bundleSourcesCommand.add("debug-files"); bundleSourcesCommand.add("bundle-jvm"); - bundleSourcesCommand.add("--output=" + sourceBundleTargetDir.getAbsolutePath()); + bundleSourcesCommand.add( + "--output=" + cliRunner.escape(sourceBundleTargetDir.getAbsolutePath())); bundleSourcesCommand.add("--debug-id=" + bundleId); if (org != null) { bundleSourcesCommand.add("--org=" + org); @@ -143,7 +144,7 @@ private void bundleSources( if (project != null) { bundleSourcesCommand.add("--project=" + project); } - bundleSourcesCommand.add(sourceRoot); + bundleSourcesCommand.add(cliRunner.escape(sourceRoot)); cliRunner.runSentryCli(String.join(" ", bundleSourcesCommand), true); } else { @@ -190,7 +191,7 @@ private void uploadSourceBundle( if (project != null) { command.add("--project=" + project); } - command.add(sourceBundleTargetDir.getAbsolutePath()); + command.add(cliRunner.escape(sourceBundleTargetDir.getAbsolutePath())); cliRunner.runSentryCli(String.join(" ", command), true); } catch (Throwable t) { diff --git a/src/main/java/io/sentry/cli/SentryCliRunner.java b/src/main/java/io/sentry/cli/SentryCliRunner.java index 6fa6169..65cdbbd 100644 --- a/src/main/java/io/sentry/cli/SentryCliRunner.java +++ b/src/main/java/io/sentry/cli/SentryCliRunner.java @@ -42,8 +42,7 @@ public SentryCliRunner( public @Nullable String runSentryCli( final @NotNull String sentryCliCommand, final boolean failOnError) throws MojoExecutionException { - final boolean isWindows = - System.getProperty("os.name").toLowerCase(Locale.ROOT).startsWith("windows"); + final boolean isWindows = isWindows(); final @NotNull String executable = isWindows ? "cmd.exe" : "/bin/sh"; final @NotNull String cArg = isWindows ? "/c" : "-c"; @@ -65,18 +64,16 @@ public SentryCliRunner( attributes( attribute("executable", executable), attribute("failOnError", String.valueOf(failOnError)), - attribute("output", escape(logFile.getAbsolutePath(), isWindows))), + attribute("output", escape(logFile.getAbsolutePath()))), element(name("arg"), attributes(attribute("value", cArg))), element( name("arg"), attributes( attribute( "value", - escape( - getCliPath(mavenProject, sentryCliExecutablePath) - + " " - + sentryCliCommand, - isWindows))))))), + escape(getCliPath(mavenProject, sentryCliExecutablePath)) + + " " + + sentryCliCommand)))))), executionEnvironment(mavenProject, mavenSession, pluginManager)); return collectAndMaybePrintOutput(logFile, debugSentryCli); @@ -94,14 +91,20 @@ public SentryCliRunner( } } - private @Nullable String escape(final @Nullable String toEscape, final boolean isWindows) { + private static boolean isWindows() { + final boolean isWindows = + System.getProperty("os.name").toLowerCase(Locale.ROOT).startsWith("windows"); + return isWindows; + } + + public @Nullable String escape(final @Nullable String toEscape) { if (toEscape == null) { return null; } - if (isWindows) { + if (isWindows()) { return toEscape.replaceAll(" ", "^ "); } else { - return toEscape.replaceAll(" ", "\\ "); + return toEscape.replaceAll(" ", "\\\\ "); } } From 34a3d6aa87bdfcf58e84e18e24090f90c5e4a63e Mon Sep 17 00:00:00 2001 From: Lukas Bloder Date: Mon, 25 Mar 2024 16:57:10 +0100 Subject: [PATCH 3/9] fix command escaping for windows --- .../java/io/sentry/cli/SentryCliRunner.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/sentry/cli/SentryCliRunner.java b/src/main/java/io/sentry/cli/SentryCliRunner.java index 65cdbbd..16d1899 100644 --- a/src/main/java/io/sentry/cli/SentryCliRunner.java +++ b/src/main/java/io/sentry/cli/SentryCliRunner.java @@ -71,9 +71,10 @@ public SentryCliRunner( attributes( attribute( "value", - escape(getCliPath(mavenProject, sentryCliExecutablePath)) - + " " - + sentryCliCommand)))))), + wrapForWindows( + escape(getCliPath(mavenProject, sentryCliExecutablePath)) + + " " + + sentryCliCommand))))))), executionEnvironment(mavenProject, mavenSession, pluginManager)); return collectAndMaybePrintOutput(logFile, debugSentryCli); @@ -97,12 +98,26 @@ private static boolean isWindows() { return isWindows; } + private @Nullable String wrapForWindows(final @Nullable String toWrap) { + // Wrap whole command in double quotes as Windows cmd will remove the first and last double quote + if (toWrap != null && isWindows()) { + return "\"" + toWrap + "\""; + } else { + return toWrap; + } + } + public @Nullable String escape(final @Nullable String toEscape) { if (toEscape == null) { return null; } if (isWindows()) { - return toEscape.replaceAll(" ", "^ "); + // Wrap paths that contain a whitespace in double quotes + // For some reason wrapping paths that do not contain a whitespace lead to an error + if (toEscape.contains(" ")) { + return "\"" + toEscape + "\""; + } + return toEscape; } else { return toEscape.replaceAll(" ", "\\\\ "); } From f1c30d611f8099fff6c882aaec42eace585721ba Mon Sep 17 00:00:00 2001 From: Lukas Bloder Date: Mon, 25 Mar 2024 17:02:01 +0100 Subject: [PATCH 4/9] format, add changelog --- CHANGELOG.md | 1 + src/main/java/io/sentry/cli/SentryCliRunner.java | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2c533f..d191ed5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - Fix `isSaas` check for telemetry ([#66](https://github.com/getsentry/sentry-maven-plugin/pull/66)) +- Escape spaces in paths ([#64](https://github.com/getsentry/sentry-maven-plugin/pull/64)) ### Features diff --git a/src/main/java/io/sentry/cli/SentryCliRunner.java b/src/main/java/io/sentry/cli/SentryCliRunner.java index 16d1899..a481016 100644 --- a/src/main/java/io/sentry/cli/SentryCliRunner.java +++ b/src/main/java/io/sentry/cli/SentryCliRunner.java @@ -99,7 +99,8 @@ private static boolean isWindows() { } private @Nullable String wrapForWindows(final @Nullable String toWrap) { - // Wrap whole command in double quotes as Windows cmd will remove the first and last double quote + // Wrap whole command in double quotes as Windows cmd will remove the first and last double + // quote if (toWrap != null && isWindows()) { return "\"" + toWrap + "\""; } else { @@ -112,8 +113,8 @@ private static boolean isWindows() { return null; } if (isWindows()) { - // Wrap paths that contain a whitespace in double quotes - // For some reason wrapping paths that do not contain a whitespace lead to an error + // Wrap paths that contain a whitespace in double quotes + // For some reason wrapping paths that do not contain a whitespace leads to an error if (toEscape.contains(" ")) { return "\"" + toEscape + "\""; } From 7f72e30ded9d0f84321adc113c5f69ea16dc465b Mon Sep 17 00:00:00 2001 From: Lukas Bloder Date: Tue, 26 Mar 2024 09:05:56 +0100 Subject: [PATCH 5/9] integration test for sourcebundle upload with spaces in paths --- .../io/sentry/integration/cli/PomUtils.kt | 64 ++++++++++++++ .../cli/SentryCliWhitespacesTest.kt | 85 +++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 src/test/java/io/sentry/integration/cli/PomUtils.kt create mode 100644 src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTest.kt diff --git a/src/test/java/io/sentry/integration/cli/PomUtils.kt b/src/test/java/io/sentry/integration/cli/PomUtils.kt new file mode 100644 index 0000000..2cfd682 --- /dev/null +++ b/src/test/java/io/sentry/integration/cli/PomUtils.kt @@ -0,0 +1,64 @@ +package io.sentry.integration.cli + +fun basePom( + skipPlugin: Boolean = false, + skipSourceBundle: Boolean = false, + sentryCliPath: String? = null, +): String { + return """ + + 4.0.0 + + io.sentry.autoinstall + installsentry + 1.0-SNAPSHOT + + jar + + + 11 + 11 + + + + + com.graphql-java + graphql-java + 2.0.0 + + + io.sentry + sentry-graphql + 6.32.0 + + + + + + + io.sentry + sentry-maven-plugin + 1.0-SNAPSHOT + true + + true + $skipPlugin + ${skipSourceBundle} + sentry-sdks + android-sagp-testing + ${if (sentryCliPath.isNullOrBlank()) "" else "${sentryCliPath}"} + + + + + uploadSourceBundle + + + + + + + + """.trimIndent() +} diff --git a/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTest.kt b/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTest.kt new file mode 100644 index 0000000..ebc2c85 --- /dev/null +++ b/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTest.kt @@ -0,0 +1,85 @@ +package io.sentry.integration.cli + +import io.sentry.SentryCliProvider +import io.sentry.autoinstall.util.SdkVersionInfo +import io.sentry.integration.installMavenWrapper +import org.apache.maven.it.VerificationException +import org.apache.maven.it.Verifier +import org.apache.maven.project.MavenProject +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.StandardOpenOption +import kotlin.io.path.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.createTempDirectory +import kotlin.test.assertTrue + +class SentryCliWhitespacesTest { + + @TempDir() + lateinit var file: File + + fun getPOM( + baseDir: File, + skipPlugin: Boolean = false, + skipSourceBundle: Boolean = false, + sentryCliPath: String? = null, + ): String { + val pomContent = basePom(skipPlugin, skipSourceBundle, sentryCliPath) + + Files.write(Path("${baseDir.absolutePath}/pom.xml"), pomContent.toByteArray(), StandardOpenOption.CREATE) + + return baseDir.absolutePath + } + + @Test + @Throws(VerificationException::class, IOException::class) + fun sentryCliExecutionInProjectPathWithSpaces() { + val baseDir = setupProject() + val path = getPOM(baseDir) + val verifier = Verifier(path) + verifier.isAutoclean = false + verifier.executeGoal("install") + verifier.verifyErrorFreeLog() + verifier.verifyTextInLog("Bundled 1 file for upload") + verifier.verifyTextInLog("Uploaded 1 missing debug information file"); + verifier.resetStreams() + } + + @Test + @Throws(VerificationException::class, IOException::class) + fun sentryCliExecutionInProjectAndCliPathWithSpaces() { + val cliPath = SentryCliProvider.getCliPath(MavenProject(), null); + val baseDir = setupProject() + val cliPathWithSpaces = Files.copy(Path(cliPath), Path(baseDir.absolutePath, "sentry-cli")) + val path = getPOM(baseDir, sentryCliPath = cliPathWithSpaces.absolutePathString()) + val verifier = Verifier(path) + + verifier.isAutoclean = false + verifier.executeGoal("install") + verifier.verifyErrorFreeLog() + verifier.verifyTextInLog("Bundled 1 file for upload") + verifier.verifyTextInLog("Uploaded 1 missing debug information file"); + verifier.resetStreams() + } + + private fun setupProject(): File { + val baseDir = File(file, "base with spaces") + val srcDir = File(baseDir, "/src/main/java") + val srcFile = File(srcDir, "Main.java") + val baseDirResult = baseDir.mkdir() + val srcDirResult = srcDir.mkdirs() + val srcFileResult = srcFile.createNewFile() + + assertTrue(baseDirResult, "Error creating base directory") + assertTrue(srcDirResult, "Error creating source directory") + assertTrue(srcFileResult, "Error creating source file") + installMavenWrapper(baseDir, "3.8.6") + + return baseDir + } +} From 3d05744ebe2da056b2b188af1180b0dcd5f419b4 Mon Sep 17 00:00:00 2001 From: Lukas Bloder Date: Tue, 26 Mar 2024 09:51:11 +0100 Subject: [PATCH 6/9] fix test naming --- src/test/java/io/sentry/integration/cli/PomUtils.kt | 8 ++++---- ...tespacesTest.kt => SentryCliWhitespacesTestIT.kt} | 12 ++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) rename src/test/java/io/sentry/integration/cli/{SentryCliWhitespacesTest.kt => SentryCliWhitespacesTestIT.kt} (93%) diff --git a/src/test/java/io/sentry/integration/cli/PomUtils.kt b/src/test/java/io/sentry/integration/cli/PomUtils.kt index 2cfd682..cbc9dd2 100644 --- a/src/test/java/io/sentry/integration/cli/PomUtils.kt +++ b/src/test/java/io/sentry/integration/cli/PomUtils.kt @@ -10,8 +10,8 @@ fun basePom( xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - io.sentry.autoinstall - installsentry + io.sentry.maven + cli-tests 1.0-SNAPSHOT jar @@ -44,10 +44,10 @@ fun basePom( true $skipPlugin - ${skipSourceBundle} + $skipSourceBundle sentry-sdks android-sagp-testing - ${if (sentryCliPath.isNullOrBlank()) "" else "${sentryCliPath}"} + ${if (sentryCliPath.isNullOrBlank()) "" else "$sentryCliPath"} diff --git a/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTest.kt b/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTestIT.kt similarity index 93% rename from src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTest.kt rename to src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTestIT.kt index ebc2c85..3edda46 100644 --- a/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTest.kt +++ b/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTestIT.kt @@ -1,12 +1,10 @@ package io.sentry.integration.cli import io.sentry.SentryCliProvider -import io.sentry.autoinstall.util.SdkVersionInfo import io.sentry.integration.installMavenWrapper import org.apache.maven.it.VerificationException import org.apache.maven.it.Verifier import org.apache.maven.project.MavenProject -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.io.File @@ -15,11 +13,9 @@ import java.nio.file.Files import java.nio.file.StandardOpenOption import kotlin.io.path.Path import kotlin.io.path.absolutePathString -import kotlin.io.path.createTempDirectory import kotlin.test.assertTrue -class SentryCliWhitespacesTest { - +class SentryCliWhitespacesTestIT { @TempDir() lateinit var file: File @@ -46,14 +42,14 @@ class SentryCliWhitespacesTest { verifier.executeGoal("install") verifier.verifyErrorFreeLog() verifier.verifyTextInLog("Bundled 1 file for upload") - verifier.verifyTextInLog("Uploaded 1 missing debug information file"); + verifier.verifyTextInLog("Uploaded 1 missing debug information file") verifier.resetStreams() } @Test @Throws(VerificationException::class, IOException::class) fun sentryCliExecutionInProjectAndCliPathWithSpaces() { - val cliPath = SentryCliProvider.getCliPath(MavenProject(), null); + val cliPath = SentryCliProvider.getCliPath(MavenProject(), null) val baseDir = setupProject() val cliPathWithSpaces = Files.copy(Path(cliPath), Path(baseDir.absolutePath, "sentry-cli")) val path = getPOM(baseDir, sentryCliPath = cliPathWithSpaces.absolutePathString()) @@ -63,7 +59,7 @@ class SentryCliWhitespacesTest { verifier.executeGoal("install") verifier.verifyErrorFreeLog() verifier.verifyTextInLog("Bundled 1 file for upload") - verifier.verifyTextInLog("Uploaded 1 missing debug information file"); + verifier.verifyTextInLog("Uploaded 1 missing debug information file") verifier.resetStreams() } From 1012edc47f95ec7075c749bd23bc924b4048a689 Mon Sep 17 00:00:00 2001 From: Lukas Bloder Date: Tue, 26 Mar 2024 13:55:45 +0100 Subject: [PATCH 7/9] add local server for integration tests --- .github/workflows/pre-merge.yaml | 8 +- .../io/sentry/integration/cli/PomUtils.kt | 4 +- .../cli/SentryCliWhitespacesTestIT.kt | 36 ++++- test/assets/artifact.json | 10 ++ test/assets/artifacts.json | 22 +++ test/assets/assemble-artifacts-response.json | 5 + test/assets/associate-dsyms-response.json | 15 ++ test/assets/debug-info-files.json | 13 ++ test/assets/deploy.json | 8 + test/assets/release.json | 44 ++++++ test/assets/repos.json | 13 ++ test/integration-test-server-start.sh | 3 + test/integration-test-server-stop.sh | 3 + test/integration-test-server.py | 146 ++++++++++++++++++ 14 files changed, 321 insertions(+), 9 deletions(-) create mode 100644 test/assets/artifact.json create mode 100644 test/assets/artifacts.json create mode 100644 test/assets/assemble-artifacts-response.json create mode 100644 test/assets/associate-dsyms-response.json create mode 100644 test/assets/debug-info-files.json create mode 100644 test/assets/deploy.json create mode 100644 test/assets/release.json create mode 100644 test/assets/repos.json create mode 100755 test/integration-test-server-start.sh create mode 100755 test/integration-test-server-stop.sh create mode 100644 test/integration-test-server.py diff --git a/.github/workflows/pre-merge.yaml b/.github/workflows/pre-merge.yaml index 54bc17b..ffd0249 100644 --- a/.github/workflows/pre-merge.yaml +++ b/.github/workflows/pre-merge.yaml @@ -14,6 +14,8 @@ jobs: os: [ ubuntu-latest, macos-latest ] fail-fast: false runs-on: ${{ matrix.os }} + env: + SENTRY_URL: http://127.0.0.1:8000 steps: - name: Checkout Repo @@ -25,5 +27,7 @@ jobs: distribution: 'temurin' java-version: '17' - - name: Build the Release variant - run: ./mvnw clean verify -PintegrationTests + - name: Build the Release variant, run integration tests + run: | + test/integration-test-server-start.sh & + ./mvnw clean verify -PintegrationTests diff --git a/src/test/java/io/sentry/integration/cli/PomUtils.kt b/src/test/java/io/sentry/integration/cli/PomUtils.kt index cbc9dd2..755ac3f 100644 --- a/src/test/java/io/sentry/integration/cli/PomUtils.kt +++ b/src/test/java/io/sentry/integration/cli/PomUtils.kt @@ -45,8 +45,10 @@ fun basePom( true $skipPlugin $skipSourceBundle + true sentry-sdks - android-sagp-testing + sentry-maven + \<token\> ${if (sentryCliPath.isNullOrBlank()) "" else "$sentryCliPath"} diff --git a/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTestIT.kt b/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTestIT.kt index 3edda46..9d71341 100644 --- a/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTestIT.kt +++ b/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTestIT.kt @@ -8,11 +8,14 @@ import org.apache.maven.project.MavenProject import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.io.File +import java.io.FileInputStream import java.io.IOException import java.nio.file.Files import java.nio.file.StandardOpenOption +import java.util.* import kotlin.io.path.Path import kotlin.io.path.absolutePathString +import kotlin.test.assertEquals import kotlin.test.assertTrue class SentryCliWhitespacesTestIT { @@ -41,8 +44,14 @@ class SentryCliWhitespacesTestIT { verifier.isAutoclean = false verifier.executeGoal("install") verifier.verifyErrorFreeLog() - verifier.verifyTextInLog("Bundled 1 file for upload") - verifier.verifyTextInLog("Uploaded 1 missing debug information file") + + val output = verifier.loadLines(verifier.logFileName, "UTF-8").joinToString("\n") + + val uploadedId = getUploadedBundleIdFromLog(output) + val bundleId = getBundleIdFromProperties(baseDir.absolutePath) + + assertEquals(bundleId, uploadedId, "Bundle ID from properties file should match the one from the log") + verifier.resetStreams() } @@ -56,10 +65,14 @@ class SentryCliWhitespacesTestIT { val verifier = Verifier(path) verifier.isAutoclean = false - verifier.executeGoal("install") - verifier.verifyErrorFreeLog() - verifier.verifyTextInLog("Bundled 1 file for upload") - verifier.verifyTextInLog("Uploaded 1 missing debug information file") + mac + val output = verifier.loadLines(verifier.logFileName, "UTF-8").joinToString("\n") + + val uploadedId = getUploadedBundleIdFromLog(output) + val bundleId = getBundleIdFromProperties(baseDir.absolutePath) + + assertEquals(bundleId, uploadedId, "Bundle ID from properties file should match the one from the log") + verifier.resetStreams() } @@ -78,4 +91,15 @@ class SentryCliWhitespacesTestIT { return baseDir } + + private fun getUploadedBundleIdFromLog(output: String): String? { + val uploadedIdRegex = """\w+":\{"state":"ok","missingChunks":\[],"uploaded_id":"(\w+-\w+-\w+-\w+-\w+)""".toRegex() + return uploadedIdRegex.find(output)?.groupValues?.get(1) + } + + private fun getBundleIdFromProperties(baseDir: String): String { + val myProps = Properties() + myProps.load(FileInputStream("${baseDir}/target/sentry/properties/sentry-debug-meta.properties")) + return myProps.getProperty("io.sentry.bundle-ids") + } } diff --git a/test/assets/artifact.json b/test/assets/artifact.json new file mode 100644 index 0000000..43bce35 --- /dev/null +++ b/test/assets/artifact.json @@ -0,0 +1,10 @@ +{ + "id": "fixture-id", + "sha1": "fixture-sha1", + "name": "fixture-name", + "size": 1, + "dist": null, + "headers": { + "fixture-header-key": "fixture-header-value" + } +} diff --git a/test/assets/artifacts.json b/test/assets/artifacts.json new file mode 100644 index 0000000..a7d5237 --- /dev/null +++ b/test/assets/artifacts.json @@ -0,0 +1,22 @@ +[ + { + "id": "6796495645", + "name": "~/dist/bundle.min.js", + "dist": "foo", + "headers": { + "Sourcemap": "dist/bundle.min.js.map" + }, + "size": 497, + "sha1": "2fb719956748ab7ec5ae9bcb47606733f5589b72", + "dateCreated": "2022-05-12T11:08:01.520199Z" + }, + { + "id": "6796495646", + "name": "~/dist/bundle.min.js.map", + "dist": "foo", + "headers": {}, + "size": 1522, + "sha1": "f818059cbf617a8fae9b4e46d08f6c0246bb1624", + "dateCreated": "2022-05-12T11:08:01.496220Z" + } +] \ No newline at end of file diff --git a/test/assets/assemble-artifacts-response.json b/test/assets/assemble-artifacts-response.json new file mode 100644 index 0000000..0c71ed6 --- /dev/null +++ b/test/assets/assemble-artifacts-response.json @@ -0,0 +1,5 @@ +{ + "state": "ok", + "missingChunks": [], + "detail": null +} diff --git a/test/assets/associate-dsyms-response.json b/test/assets/associate-dsyms-response.json new file mode 100644 index 0000000..6b7f7f4 --- /dev/null +++ b/test/assets/associate-dsyms-response.json @@ -0,0 +1,15 @@ +{ + "associatedDsymFiles": [ + { + "uuid": null, + "debugId": null, + "objectName": "fixture-objectName", + "cpuName": "fixture-cpuName", + "sha1": "fixture-sha1", + "data": { + "type": null, + "features": ["fixture-feature"] + } + } + ] +} diff --git a/test/assets/debug-info-files.json b/test/assets/debug-info-files.json new file mode 100644 index 0000000..7ef5e52 --- /dev/null +++ b/test/assets/debug-info-files.json @@ -0,0 +1,13 @@ +[ + { + "uuid": null, + "debugId": null, + "objectName": "fixture-objectName", + "cpuName": "fixture-cpuName", + "sha1": "fixture-sha1", + "data": { + "type": null, + "features": ["fixture-feature"] + } + } +] \ No newline at end of file diff --git a/test/assets/deploy.json b/test/assets/deploy.json new file mode 100644 index 0000000..39d8169 --- /dev/null +++ b/test/assets/deploy.json @@ -0,0 +1,8 @@ +{ + "id": "1", + "environment": "production", + "dateStarted": null, + "dateFinished": "2022-01-01T12:00:00.000000Z", + "name": "fixture-deploy", + "url": null +} diff --git a/test/assets/release.json b/test/assets/release.json new file mode 100644 index 0000000..27af305 --- /dev/null +++ b/test/assets/release.json @@ -0,0 +1,44 @@ +{ + "dateReleased": "2022-01-01T12:00:00.000000Z", + "newGroups": 0, + "commitCount": 0, + "url": null, + "data": + {}, + "lastDeploy": null, + "deployCount": 0, + "dateCreated": "2022-01-01T12:00:00.000000Z", + "lastEvent": null, + "version": "1.1.0", + "firstEvent": null, + "lastCommit": null, + "shortVersion": "1.1.0", + "authors": + [], + "owner": null, + "versionInfo": + { + "buildHash": null, + "version": + { + "raw": "1.1.0" + }, + "description": "1.1.0", + "package": null + }, + "ref": null, + "projects": + [ + { + "name": "Sentry Fastlane App", + "platform": "ruby", + "slug": "sentry-fastlane-plugin", + "platforms": + [ + "ruby" + ], + "newGroups": 0, + "id": 1234567 + } + ] +} diff --git a/test/assets/repos.json b/test/assets/repos.json new file mode 100644 index 0000000..998b89b --- /dev/null +++ b/test/assets/repos.json @@ -0,0 +1,13 @@ +[ + { + "id": "1", + "name": "sentry/sentry-fastlane-plugin", + "url": null, + "provider": { + "id": "1", + "name": "fixture-name" + }, + "status": "fixture-status", + "dateCreated": "2022-01-01T12:00:00.000000Z" + } +] diff --git a/test/integration-test-server-start.sh b/test/integration-test-server-start.sh new file mode 100755 index 0000000..9fc54bf --- /dev/null +++ b/test/integration-test-server-start.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +python3 test/integration-test-server.py diff --git a/test/integration-test-server-stop.sh b/test/integration-test-server-stop.sh new file mode 100755 index 0000000..a9168ed --- /dev/null +++ b/test/integration-test-server-stop.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +curl http://127.0.0.1:8000/STOP diff --git a/test/integration-test-server.py b/test/integration-test-server.py new file mode 100644 index 0000000..1ee7c94 --- /dev/null +++ b/test/integration-test-server.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 + +from http import HTTPStatus +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from urllib.parse import urlparse +import sys +import threading +import binascii +import json + +apiOrg = 'sentry-sdks' +apiProject = 'sentry-maven' +uri = urlparse(sys.argv[1] if len(sys.argv) > 1 else 'http://127.0.0.1:8000') +version='1.1.0' +appIdentifier='com.sentry.fastlane.app' + +class Handler(BaseHTTPRequestHandler): + body = None + + def do_GET(self): + self.start_response(HTTPStatus.OK) + + if self.path == "/STOP": + print("HTTP server stopping!") + threading.Thread(target=self.server.shutdown).start() + return + + if self.isApi('api/0/organizations/{}/chunk-upload/'.format(apiOrg)): + self.writeJSON('{"url":"' + uri.geturl() + self.path + '",' + '"chunkSize":8388608,"chunksPerRequest":64,"maxFileSize":2147483648,' + '"maxRequestSize":33554432,"concurrency":1,"hashAlgorithm":"sha1","compression":["gzip"],' + '"accept":["debug_files","release_files","pdbs","sources","bcsymbolmaps"]}') + elif self.isApi('/api/0/organizations/{}/repos/?cursor='.format(apiOrg)): + self.writeJSONFile("test/assets/repos.json") + elif self.isApi('/api/0/organizations/{}/releases/{}/previous-with-commits/'.format(apiOrg, version)): + self.writeJSONFile("test/assets/release.json") + elif self.isApi('/api/0/projects/{}/{}/releases/{}/files/?cursor='.format(apiOrg, apiProject, version)): + self.writeJSONFile("test/assets/artifacts.json") + else: + self.end_headers() + + self.flushLogs() + + def do_POST(self): + if self.isApi('/api/0/projects/{}/{}/files/proguard-artifact-releases/'.format(apiOrg, apiProject)): + self.start_response(HTTPStatus.NOT_FOUND) + else: + self.start_response(HTTPStatus.OK) + + if self.isApi('api/0/projects/{}/{}/files/difs/assemble/'.format(apiOrg, apiProject)): + # Request body example: + # { + # "9a01653a...":{"name":"UnityPlayer.dylib","debug_id":"eb4a7644-...","chunks":["f84d3907945cdf41b33da8245747f4d05e6ffcb4", ...]}, + # "4185e454...":{"name":"UnityPlayer.dylib","debug_id":"86d95b40-...","chunks":[...]} + # } + # Response body to let the CLI know we have the symbols already (we don't need to test the actual upload): + # { + # "9a01653a...":{"state":"ok","missingChunks":[]}, + # "4185e454...":{"state":"ok","missingChunks":[]} + # } + jsonRequest = json.loads(self.body) + jsonResponse = '{' + for key, value in jsonRequest.items(): + jsonResponse += '"{}":{{"state":"ok","missingChunks":[],"uploaded_id":"{}"}},'.format( + key, value['debug_id']) + self.log_message('Received: %40s %40s %s', key, + value['debug_id'], value['name']) + jsonResponse = jsonResponse.rstrip(',') + '}' + self.writeJSON(jsonResponse) + elif self.isApi('api/0/projects/{}/{}/releases/'.format(apiOrg, apiProject)): + self.writeJSONFile("test/assets/release.json") + elif self.isApi('/api/0/organizations/{}/releases/{}@{}/deploys/'.format(apiOrg, appIdentifier, version)): + self.writeJSONFile("test/assets/deploy.json") + elif self.isApi('/api/0/projects/{}/{}/releases/{}@{}/files/'.format(apiOrg, apiProject, appIdentifier, version)): + self.writeJSONFile("test/assets/artifact.json") + elif self.isApi('/api/0/organizations/{}/releases/{}/assemble/'.format(apiOrg, version)): + self.writeJSONFile("test/assets/assemble-artifacts-response.json") + elif self.isApi('/api/0/projects/{}/{}/files/dsyms/'.format(apiOrg, apiProject)): + self.writeJSONFile("test/assets/debug-info-files.json") + elif self.isApi('/api/0/projects/{}/{}/files/dsyms/associate/'.format(apiOrg, apiProject)): + self.writeJSONFile("test/assets/associate-dsyms-response.json") + else: + self.end_headers() + + self.flushLogs() + + def do_PUT(self): + self.start_response(HTTPStatus.OK) + + if self.isApi('/api/0/organizations/{}/releases/{}/'.format(apiOrg, version)): + self.writeJSONFile("test/assets/release.json") + else: + self.end_headers() + + self.flushLogs() + + def start_response(self, code): + self.body = None + self.log_request(code) + self.send_response_only(code) + + def log_request(self, code=None, size=None): + if isinstance(code, HTTPStatus): + code = code.value + body = self.body = self.requestBody() + if body: + body = self.body[0:min(1000, len(body))] + self.log_message('"%s" %s %s%s', + self.requestline, str(code), "({} bytes)".format(size) if size else '', body) + + # Note: this may only be called once during a single request - can't `.read()` the same stream again. + def requestBody(self): + if self.command == "POST" and 'Content-Length' in self.headers: + length = int(self.headers['Content-Length']) + content = self.rfile.read(length) + try: + return content.decode("utf-8") + except: + return binascii.hexlify(bytearray(content)) + return None + + def isApi(self, api: str): + if self.path.strip('/') == api.strip('/'): + self.log_message("Matched API endpoint {}".format(api)) + return True + return False + + def writeJSONFile(self, file_name: str): + json_file = open(file_name, "r") + self.writeJSON(json_file.read()) + json_file.close() + + def writeJSON(self, string: str): + self.send_header("Content-type", "application/json") + self.end_headers() + self.wfile.write(str.encode(string)) + + def flushLogs(self): + sys.stdout.flush() + sys.stderr.flush() + + +print("HTTP server listening on {}".format(uri.geturl())) +print("To stop the server, execute a GET request to {}/STOP".format(uri.geturl())) +httpd = ThreadingHTTPServer((uri.hostname, uri.port), Handler) +target = httpd.serve_forever() From f0ee41c7c8c2a173520a6a14f812cb58d8fea9a0 Mon Sep 17 00:00:00 2001 From: Lukas Bloder Date: Tue, 26 Mar 2024 14:10:50 +0100 Subject: [PATCH 8/9] fix lint error, fix executable with with whitespace test --- .../cli/SentryCliWhitespacesTestIT.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTestIT.kt b/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTestIT.kt index 9d71341..bccfaac 100644 --- a/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTestIT.kt +++ b/src/test/java/io/sentry/integration/cli/SentryCliWhitespacesTestIT.kt @@ -10,9 +10,10 @@ import org.junit.jupiter.api.io.TempDir import java.io.File import java.io.FileInputStream import java.io.IOException +import java.nio.charset.Charset import java.nio.file.Files import java.nio.file.StandardOpenOption -import java.util.* +import java.util.Properties import kotlin.io.path.Path import kotlin.io.path.absolutePathString import kotlin.test.assertEquals @@ -45,7 +46,7 @@ class SentryCliWhitespacesTestIT { verifier.executeGoal("install") verifier.verifyErrorFreeLog() - val output = verifier.loadLines(verifier.logFileName, "UTF-8").joinToString("\n") + val output = verifier.loadLines(verifier.logFileName, Charset.defaultCharset().name()).joinToString("\n") val uploadedId = getUploadedBundleIdFromLog(output) val bundleId = getBundleIdFromProperties(baseDir.absolutePath) @@ -61,12 +62,16 @@ class SentryCliWhitespacesTestIT { val cliPath = SentryCliProvider.getCliPath(MavenProject(), null) val baseDir = setupProject() val cliPathWithSpaces = Files.copy(Path(cliPath), Path(baseDir.absolutePath, "sentry-cli")) + cliPathWithSpaces.toFile().setExecutable(true) + val path = getPOM(baseDir, sentryCliPath = cliPathWithSpaces.absolutePathString()) - val verifier = Verifier(path) + val verifier = Verifier(path) verifier.isAutoclean = false - mac - val output = verifier.loadLines(verifier.logFileName, "UTF-8").joinToString("\n") + verifier.executeGoal("install") + verifier.verifyErrorFreeLog() + + val output = verifier.loadLines(verifier.logFileName, Charset.defaultCharset().name()).joinToString("\n") val uploadedId = getUploadedBundleIdFromLog(output) val bundleId = getBundleIdFromProperties(baseDir.absolutePath) @@ -99,7 +104,7 @@ class SentryCliWhitespacesTestIT { private fun getBundleIdFromProperties(baseDir: String): String { val myProps = Properties() - myProps.load(FileInputStream("${baseDir}/target/sentry/properties/sentry-debug-meta.properties")) + myProps.load(FileInputStream("$baseDir/target/sentry/properties/sentry-debug-meta.properties")) return myProps.getProperty("io.sentry.bundle-ids") } } From 55cacd58eb06ef8252572d5a2112994e8cfe24eb Mon Sep 17 00:00:00 2001 From: Lukas Bloder Date: Tue, 26 Mar 2024 14:15:59 +0100 Subject: [PATCH 9/9] apply suggested rename --- src/main/java/io/sentry/cli/SentryCliRunner.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/sentry/cli/SentryCliRunner.java b/src/main/java/io/sentry/cli/SentryCliRunner.java index a481016..b0ffbce 100644 --- a/src/main/java/io/sentry/cli/SentryCliRunner.java +++ b/src/main/java/io/sentry/cli/SentryCliRunner.java @@ -108,19 +108,19 @@ private static boolean isWindows() { } } - public @Nullable String escape(final @Nullable String toEscape) { - if (toEscape == null) { + public @Nullable String escape(final @Nullable String escapePath) { + if (escapePath == null) { return null; } if (isWindows()) { // Wrap paths that contain a whitespace in double quotes // For some reason wrapping paths that do not contain a whitespace leads to an error - if (toEscape.contains(" ")) { - return "\"" + toEscape + "\""; + if (escapePath.contains(" ")) { + return "\"" + escapePath + "\""; } - return toEscape; + return escapePath; } else { - return toEscape.replaceAll(" ", "\\\\ "); + return escapePath.replaceAll(" ", "\\\\ "); } }