From 497d9141ef1748ab4fb2e18524d2ca2c86aeda2a Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 11 Oct 2022 13:42:58 +0200 Subject: [PATCH 01/11] refactor: speed up plugin tests by using mock CLI --- lib/src/configuration.dart | 3 +-- lib/src/utils/injector.dart | 3 +++ test/plugin_test.dart | 54 ++++++++++++++++++++++++++++--------- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 8bba2fc..0f95f7d 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -147,7 +147,6 @@ class Configuration { } Future _findAndSetCliPath() async { - final cliSetup = CLISetup(currentCLISources); HostPlatform? platform; if (Platform.isMacOS) { platform = HostPlatform.darwinUniversal; @@ -179,7 +178,7 @@ class Configuration { } try { - cliPath = await cliSetup.download(platform); + cliPath = await injector.get().download(platform); } on Exception catch (e) { Log.error("Failed to download Sentry CLI: $e"); return _setPreInstalledCli(); diff --git a/lib/src/utils/injector.dart b/lib/src/utils/injector.dart index 2050eaa..bb62511 100644 --- a/lib/src/utils/injector.dart +++ b/lib/src/utils/injector.dart @@ -3,6 +3,8 @@ import 'package:file/local.dart'; import 'package:injector/injector.dart'; import 'package:process/process.dart'; +import '../cli/_sources.dart'; +import '../cli/setup.dart'; import '../configuration.dart'; /// Injector singleton instance @@ -13,4 +15,5 @@ void initInjector() { injector.registerSingleton(() => Configuration()); injector.registerSingleton(() => LocalProcessManager()); injector.registerSingleton(() => LocalFileSystem()); + injector.registerSingleton(() => CLISetup(currentCLISources)); } diff --git a/test/plugin_test.dart b/test/plugin_test.dart index fdb88ac..846193c 100644 --- a/test/plugin_test.dart +++ b/test/plugin_test.dart @@ -4,6 +4,8 @@ import 'dart:convert'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:process/process.dart'; +import 'package:sentry_dart_plugin/src/cli/host_platform.dart'; +import 'package:sentry_dart_plugin/src/cli/setup.dart'; import 'package:test/test.dart'; import 'package:sentry_dart_plugin/sentry_dart_plugin.dart'; @@ -20,15 +22,16 @@ void main() { injector.registerSingleton(() => pm, override: true); fs = MemoryFileSystem.test(); injector.registerSingleton(() => fs, override: true); + injector.registerSingleton(() => MockCLI(), override: true); }); + const cli = MockCLI.name; + const orgAndProject = '--org o --project p'; + test('fails without args and pubspec', () async { final exitCode = await plugin.run([]); expect(exitCode, 1); - expect(pm.commandLog, const [ - 'chmod +x .dart_tool/pub/bin/sentry_dart_plugin/sentry-cli', - '.dart_tool/pub/bin/sentry_dart_plugin/sentry-cli help' - ]); + expect(pm.commandLog, const ['chmod +x $cli', '$cli help']); }); test('works with pubspec', () async { @@ -43,19 +46,39 @@ sentry: auth_token: t project: p org: o - url: http://127.0.0.1 + url: http://127.0.0.1 # TODO: because this param affects all commands, make it a test-group argument and run all test cases with/without it. log_level: debug '''); final exitCode = await plugin.run([]); expect(exitCode, 0); + const args = '--url http://127.0.0.1 --auth-token t --log-level debug'; expect(pm.commandLog, const [ - 'chmod +x .dart_tool/pub/bin/sentry_dart_plugin/sentry-cli', - '.dart_tool/pub/bin/sentry_dart_plugin/sentry-cli help', - '.dart_tool/pub/bin/sentry_dart_plugin/sentry-cli --url http://127.0.0.1 --auth-token t --log-level debug upload-dif --include-sources --org o --project p /', - '.dart_tool/pub/bin/sentry_dart_plugin/sentry-cli --url http://127.0.0.1 --auth-token t --log-level debug releases --org o --project p new project@1.1.0', - '.dart_tool/pub/bin/sentry_dart_plugin/sentry-cli --url http://127.0.0.1 --auth-token t --log-level debug releases --org o --project p files project@1.1.0 upload-sourcemaps /build/web --ext map --ext js', - '.dart_tool/pub/bin/sentry_dart_plugin/sentry-cli --url http://127.0.0.1 --auth-token t --log-level debug releases --org o --project p files project@1.1.0 upload-sourcemaps / --ext dart', - '.dart_tool/pub/bin/sentry_dart_plugin/sentry-cli --url http://127.0.0.1 --auth-token t --log-level debug releases --org o --project p finalize project@1.1.0' + 'chmod +x $cli', + '$cli help', + '$cli $args upload-dif --include-sources $orgAndProject /', + '$cli $args releases $orgAndProject new project@1.1.0', + '$cli $args releases $orgAndProject files project@1.1.0 upload-sourcemaps /build/web --ext map --ext js', + '$cli $args releases $orgAndProject files project@1.1.0 upload-sourcemaps / --ext dart', + '$cli $args releases $orgAndProject finalize project@1.1.0' + ]); + }); + + test('defaults', () async { + fs.file('pubspec.yaml').writeAsStringSync(''' +name: project +version: 1.1.0 + +sentry: + auth_token: t # TODO: support not specifying this, let sentry-cli use the value it can find in its configs + project: p + org: o +'''); + final exitCode = await plugin.run([]); + expect(exitCode, 0); + expect(pm.commandLog, const [ + 'chmod +x $cli', + '$cli help', + '$cli --auth-token t upload-dif $orgAndProject /', ]); }); } @@ -102,3 +125,10 @@ class MockProcessManager implements ProcessManager { throw UnimplementedError(); } } + +class MockCLI implements CLISetup { + static const name = 'mock-cli'; + + @override + Future download(HostPlatform platform) => Future.value(name); +} From a2444399b0ef3612b6941fc1db0c6a92313c8c07 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 11 Oct 2022 14:22:48 +0200 Subject: [PATCH 02/11] test: refactor plugin_test --- lib/src/configuration.dart | 1 - test/plugin_test.dart | 68 +++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 0f95f7d..2885b38 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:file/file.dart'; import 'package:process/process.dart'; -import 'package:sentry_dart_plugin/src/cli/_sources.dart'; import 'package:system_info2/system_info2.dart'; import 'package:yaml/yaml.dart'; diff --git a/test/plugin_test.dart b/test/plugin_test.dart index 846193c..1dacd86 100644 --- a/test/plugin_test.dart +++ b/test/plugin_test.dart @@ -27,55 +27,61 @@ void main() { const cli = MockCLI.name; const orgAndProject = '--org o --project p'; + const project = 'project'; + const version = '1.1.0'; + const release = '$project@$version'; - test('fails without args and pubspec', () async { - final exitCode = await plugin.run([]); - expect(exitCode, 1); - expect(pm.commandLog, const ['chmod +x $cli', '$cli help']); - }); + Future> runWith(String config) async { + // properly indent the configuration for the `sentry` section in the yaml + final configIndented = + config.trim().split('\n').map((l) => ' ${l.trim()}').join('\n'); - test('works with pubspec', () async { fs.file('pubspec.yaml').writeAsStringSync(''' -name: project -version: 1.1.0 +name: $project +version: $version sentry: - upload_native_symbols: true - include_native_sources: true - upload_source_maps: true - auth_token: t + auth_token: t # TODO: support not specifying this, let sentry-cli use the value it can find in its configs project: p org: o - url: http://127.0.0.1 # TODO: because this param affects all commands, make it a test-group argument and run all test cases with/without it. - log_level: debug +$configIndented '''); + final exitCode = await plugin.run([]); expect(exitCode, 0); + return pm.commandLog; + } + + test('fails without args and pubspec', () async { + final exitCode = await plugin.run([]); + expect(exitCode, 1); + expect(pm.commandLog, const ['chmod +x $cli', '$cli help']); + }); + + test('works with pubspec', () async { + // TODO: because `url` param affects all commands, make it a test-group argument and run all test cases with/without it. + final commandLog = await runWith(''' + upload_native_symbols: true + include_native_sources: true + upload_source_maps: true + url: http://127.0.0.1 + log_level: debug + '''); const args = '--url http://127.0.0.1 --auth-token t --log-level debug'; - expect(pm.commandLog, const [ + expect(commandLog, const [ 'chmod +x $cli', '$cli help', '$cli $args upload-dif --include-sources $orgAndProject /', - '$cli $args releases $orgAndProject new project@1.1.0', - '$cli $args releases $orgAndProject files project@1.1.0 upload-sourcemaps /build/web --ext map --ext js', - '$cli $args releases $orgAndProject files project@1.1.0 upload-sourcemaps / --ext dart', - '$cli $args releases $orgAndProject finalize project@1.1.0' + '$cli $args releases $orgAndProject new $release', + '$cli $args releases $orgAndProject files $release upload-sourcemaps /build/web --ext map --ext js', + '$cli $args releases $orgAndProject files $release upload-sourcemaps / --ext dart', + '$cli $args releases $orgAndProject finalize $release' ]); }); test('defaults', () async { - fs.file('pubspec.yaml').writeAsStringSync(''' -name: project -version: 1.1.0 - -sentry: - auth_token: t # TODO: support not specifying this, let sentry-cli use the value it can find in its configs - project: p - org: o -'''); - final exitCode = await plugin.run([]); - expect(exitCode, 0); - expect(pm.commandLog, const [ + final commandLog = await runWith(''); + expect(commandLog, const [ 'chmod +x $cli', '$cli help', '$cli --auth-token t upload-dif $orgAndProject /', From 801e3f899eb46170f3c625061a4eccac8ed5a9e9 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 11 Oct 2022 15:55:57 +0200 Subject: [PATCH 03/11] test: futher improve plugin tests --- test/plugin_test.dart | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/test/plugin_test.dart b/test/plugin_test.dart index 1dacd86..87dee28 100644 --- a/test/plugin_test.dart +++ b/test/plugin_test.dart @@ -16,22 +16,24 @@ void main() { late MockProcessManager pm; late FileSystem fs; + const cli = MockCLI.name; + const orgAndProject = '--org o --project p'; + const project = 'project'; + const version = '1.1.0'; + const release = '$project@$version'; + const buildDir = '/subdir'; + setUp(() { // override dependencies for testing pm = MockProcessManager(); injector.registerSingleton(() => pm, override: true); fs = MemoryFileSystem.test(); + fs.currentDirectory = fs.directory(buildDir)..createSync(); injector.registerSingleton(() => fs, override: true); injector.registerSingleton(() => MockCLI(), override: true); }); - const cli = MockCLI.name; - const orgAndProject = '--org o --project p'; - const project = 'project'; - const version = '1.1.0'; - const release = '$project@$version'; - - Future> runWith(String config) async { + Future> runWith(String config) async { // properly indent the configuration for the `sentry` section in the yaml final configIndented = config.trim().split('\n').map((l) => ' ${l.trim()}').join('\n'); @@ -49,7 +51,8 @@ $configIndented final exitCode = await plugin.run([]); expect(exitCode, 0); - return pm.commandLog; + expect(pm.commandLog.take(2), const ['chmod +x $cli', '$cli help']); + return pm.commandLog.skip(2); } test('fails without args and pubspec', () async { @@ -69,12 +72,10 @@ $configIndented '''); const args = '--url http://127.0.0.1 --auth-token t --log-level debug'; expect(commandLog, const [ - 'chmod +x $cli', - '$cli help', - '$cli $args upload-dif --include-sources $orgAndProject /', + '$cli $args upload-dif --include-sources $orgAndProject $buildDir', '$cli $args releases $orgAndProject new $release', - '$cli $args releases $orgAndProject files $release upload-sourcemaps /build/web --ext map --ext js', - '$cli $args releases $orgAndProject files $release upload-sourcemaps / --ext dart', + '$cli $args releases $orgAndProject files $release upload-sourcemaps $buildDir/build/web --ext map --ext js', + '$cli $args releases $orgAndProject files $release upload-sourcemaps $buildDir --ext dart', '$cli $args releases $orgAndProject finalize $release' ]); }); @@ -82,9 +83,7 @@ $configIndented test('defaults', () async { final commandLog = await runWith(''); expect(commandLog, const [ - 'chmod +x $cli', - '$cli help', - '$cli --auth-token t upload-dif $orgAndProject /', + '$cli --auth-token t upload-dif $orgAndProject $buildDir', ]); }); } From d91527dda4f0f6bb06648f1a8dbe8bd4650f2d9e Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 11 Oct 2022 17:38:55 +0200 Subject: [PATCH 04/11] feat: support setting commits --- lib/sentry_dart_plugin.dart | 77 ++++++++++++++++++++++--------------- lib/src/configuration.dart | 3 ++ test/plugin_test.dart | 58 ++++++++++++++++++++++++---- 3 files changed, 101 insertions(+), 37 deletions(-) diff --git a/lib/sentry_dart_plugin.dart b/lib/sentry_dart_plugin.dart index 8177ee6..cf62715 100644 --- a/lib/sentry_dart_plugin.dart +++ b/lib/sentry_dart_plugin.dart @@ -33,11 +33,21 @@ class SentryDartPlugin { Log.info('uploadNativeSymbols is disabled.'); } + _executeNewRelease(); + if (_configuration.uploadSourceMaps) { _executeCliForSourceMaps(); } else { Log.info('uploadSourceMaps is disabled.'); } + + if (_configuration.commits.toLowerCase() != 'false') { + _executeSetCommits(); + } else { + Log.info('Commit integration is disabled.'); + } + + _executeFinalizeRelease(); } on ExitError catch (e) { return e.code; } @@ -51,6 +61,7 @@ class SentryDartPlugin { List params = []; _setUrlAndTokenAndLog(params); + _addOrgAndProject(params); params.add('upload-dif'); @@ -60,8 +71,6 @@ class SentryDartPlugin { Log.info('includeNativeSources is disabled, not uploading sources.'); } - _addOrgAndProject(params); - params.add(_configuration.buildFilesFolder); _addWait(params); @@ -71,36 +80,52 @@ class SentryDartPlugin { Log.taskCompleted(taskName); } - void _executeCliForSourceMaps() { - const taskName = 'uploading source maps'; - Log.startingTask(taskName); - - List params = []; - + List _releasesCliParams() { + final params = []; _setUrlAndTokenAndLog(params); - + _addOrgAndProject(params); params.add('releases'); + return params; + } - _addOrgAndProject(params); + void _executeNewRelease() { + _executeAndLog('Failed to create a new release', + [..._releasesCliParams(), 'new', _release]); + } - List releaseFinalizeParams = []; - releaseFinalizeParams.addAll(params); + void _executeFinalizeRelease() { + _executeAndLog('Failed to finalize the new release', + [..._releasesCliParams(), 'finalize', _release]); + } - // create new release - List releaseNewParams = []; - releaseNewParams.addAll(params); - releaseNewParams.add('new'); + void _executeSetCommits() { + final params = [ + ..._releasesCliParams(), + 'set-commits', + _release, + ]; - final release = _getRelease(); - releaseNewParams.add(release); + if (['auto', 'true', ''].contains(_configuration.commits.toLowerCase())) { + params.add('--auto'); + } else { + params.add('--commit'); + params.add(_configuration.commits); + } - _executeAndLog('Failed to create new release', releaseNewParams); + _executeAndLog('Failed to set commits', params); + } + + void _executeCliForSourceMaps() { + const taskName = 'uploading source maps'; + Log.startingTask(taskName); + + List params = _releasesCliParams(); // upload source maps (js and map) List releaseJsFilesParams = []; releaseJsFilesParams.addAll(params); - _addExtensionToParams(['map', 'js'], releaseJsFilesParams, release, + _addExtensionToParams(['map', 'js'], releaseJsFilesParams, _release, _configuration.webBuildFilesFolder); _addWait(releaseJsFilesParams); @@ -111,19 +136,13 @@ class SentryDartPlugin { List releaseDartFilesParams = []; releaseDartFilesParams.addAll(params); - _addExtensionToParams(['dart'], releaseDartFilesParams, release, + _addExtensionToParams(['dart'], releaseDartFilesParams, _release, _configuration.buildFilesFolder); _addWait(releaseDartFilesParams); _executeAndLog('Failed to upload source maps', releaseDartFilesParams); - // finalize new release - releaseFinalizeParams.add('finalize'); - releaseFinalizeParams.add(release); - - _executeAndLog('Failed to create new release', releaseFinalizeParams); - Log.taskCompleted(taskName); } @@ -178,9 +197,7 @@ class SentryDartPlugin { } } - String _getRelease() { - return '${_configuration.name}@${_configuration.version}'; - } + String get _release => '${_configuration.name}@${_configuration.version}'; void _addWait(List params) { if (_configuration.waitForProcessing) { diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 2885b38..11b70f2 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -57,6 +57,8 @@ class Configuration { /// the Web Build folder, defaults to build/web late String webBuildFilesFolder; + late String commits; + dynamic _getPubspec() { final file = injector.get().file("pubspec.yaml"); if (!file.existsSync()) { @@ -86,6 +88,7 @@ class Configuration { uploadNativeSymbols = config?['upload_native_symbols'] ?? true; uploadSourceMaps = config?['upload_source_maps'] ?? false; includeNativeSources = config?['include_native_sources'] ?? false; + commits = (config?['commits'] ?? 'auto').toString(); // uploading JS and Map files need to have the correct folder structure // otherwise symbolication fails, the default path for the web build folder is build/web diff --git a/test/plugin_test.dart b/test/plugin_test.dart index 87dee28..dceef45 100644 --- a/test/plugin_test.dart +++ b/test/plugin_test.dart @@ -70,22 +70,66 @@ $configIndented url: http://127.0.0.1 log_level: debug '''); - const args = '--url http://127.0.0.1 --auth-token t --log-level debug'; + const args = + '--url http://127.0.0.1 --auth-token t --log-level debug $orgAndProject'; expect(commandLog, const [ - '$cli $args upload-dif --include-sources $orgAndProject $buildDir', - '$cli $args releases $orgAndProject new $release', - '$cli $args releases $orgAndProject files $release upload-sourcemaps $buildDir/build/web --ext map --ext js', - '$cli $args releases $orgAndProject files $release upload-sourcemaps $buildDir --ext dart', - '$cli $args releases $orgAndProject finalize $release' + '$cli $args upload-dif --include-sources $buildDir', + '$cli $args releases new $release', + '$cli $args releases files $release upload-sourcemaps $buildDir/build/web --ext map --ext js', + '$cli $args releases files $release upload-sourcemaps $buildDir --ext dart', + '$cli $args releases set-commits $release --auto', + '$cli $args releases finalize $release' ]); }); test('defaults', () async { final commandLog = await runWith(''); + const args = '--auth-token t $orgAndProject'; expect(commandLog, const [ - '$cli --auth-token t upload-dif $orgAndProject $buildDir', + '$cli $args upload-dif $buildDir', + '$cli $args releases new $release', + '$cli $args releases set-commits $release --auto', + '$cli $args releases finalize $release' ]); }); + + group('commits', () { + const args = '--auth-token t $orgAndProject'; + + // https://docs.sentry.io/product/cli/releases/#sentry-cli-commit-integration + for (final value in const [ + null, // test the implicit default + 'true', + 'auto', + 'repo_name@293ea41d67225d27a8c212f901637e771d73c0f7', + 'repo_name@293ea41d67225d27a8c212f901637e771d73c0f7..1e248e5e6c24b79a5c46a2e8be12cef0e41bd58d', + ]) { + test(value, () async { + final commandLog = + await runWith(value == null ? '' : 'commits: $value'); + final expectedArgs = + (value == null || value == 'auto' || value == 'true') + ? '--auto' + : '--commit $value'; + expect(commandLog, [ + '$cli $args upload-dif $buildDir', + '$cli $args releases new $release', + '$cli $args releases set-commits $release $expectedArgs', + '$cli $args releases finalize $release' + ]); + }); + } + + // if explicitly disabled + test('false', () async { + final commandLog = await runWith('commits: false'); + expect(commandLog, [ + '$cli $args upload-dif $buildDir', + '$cli $args releases new $release', + '$cli $args releases finalize $release' + ]); + }); + }); } class MockProcessManager implements ProcessManager { From 4bcb2988e08fa49d25c8a047246ff4b212e5f083 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 11 Oct 2022 20:46:55 +0200 Subject: [PATCH 05/11] fix cli calls & integration test server (wip) --- integration-test/integration-test-server.py | 48 +++++++++++++-------- lib/sentry_dart_plugin.dart | 5 ++- test/plugin_test.dart | 41 +++++++++--------- 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/integration-test/integration-test-server.py b/integration-test/integration-test-server.py index d59377f..8fc8178 100644 --- a/integration-test/integration-test-server.py +++ b/integration-test/integration-test-server.py @@ -18,8 +18,8 @@ class Handler(BaseHTTPRequestHandler): body = None def do_GET(self): - self.start_response(HTTPStatus.OK) - + self.start_response() + if self.path == "/STOP": print("HTTP server stopping!") threading.Thread(target=self.server.shutdown).start() @@ -32,17 +32,17 @@ def do_GET(self): '"accept":["debug_files","release_files","pdbs","sources","bcsymbolmaps"]}') elif self.isApi('/api/0/organizations/{}/repos/?cursor='.format(apiOrg)): self.writeJSONFile("assets/repos.json") - elif self.isApi('/api/0/organizations/{}/releases/{}/previous-with-commits/'.format(apiOrg, version)): - self.writeJSONFile("assets/release.json") + elif self.isApi('/api/0/organizations/{}/releases/{}@{}/previous-with-commits/'.format(apiOrg, appIdentifier, version)): + self.writeJSON('{ }') elif self.isApi('/api/0/projects/{}/{}/releases/{}/files/?cursor='.format(apiOrg, apiProject, version)): self.writeJSONFile("assets/artifacts.json") else: - self.end_headers() + self.noApi() self.flushLogs() def do_POST(self): - self.start_response(HTTPStatus.OK) + self.start_response() if self.isApi('api/0/projects/{}/{}/files/difs/assemble/'.format(apiOrg, apiProject)): # Request body example: @@ -77,36 +77,37 @@ def do_POST(self): self.writeJSONFile("assets/debug-info-files.json") elif self.isApi('/api/0/projects/{}/{}/files/dsyms/associate/'.format(apiOrg, apiProject)): self.writeJSONFile("assets/associate-dsyms-response.json") + elif self.isApi('/api/0/projects/{}/{}/reprocessing/'.format(apiOrg, apiProject)): + self.writeJSON('{ }') + elif self.isApi('api/0/organizations/{}/chunk-upload/'.format(apiOrg)): + self.writeJSON('{ }') else: - self.end_headers() + self.noApi() self.flushLogs() def do_PUT(self): - self.start_response(HTTPStatus.OK) + self.start_response() - if self.isApi('/api/0/organizations/{}/releases/{}/'.format(apiOrg, version)): + if self.isApi('/api/0/organizations/{}/releases/{}@{}/'.format(apiOrg, appIdentifier, version)): self.writeJSONFile("assets/release.json") - if self.isApi('/api/0/projects/{}/{}/releases/{}@{}/'.format(apiOrg, apiProject, appIdentifier, version)): + elif self.isApi('/api/0/projects/{}/{}/releases/{}@{}/'.format(apiOrg, apiProject, appIdentifier, version)): self.writeJSONFile("assets/release.json") else: - self.end_headers() + self.noApi() self.flushLogs() - def start_response(self, code): + def start_response(self): self.body = None - self.log_request(code) - self.send_response_only(code) + self.log_request() - def log_request(self, code=None, size=None): - if isinstance(code, HTTPStatus): - code = code.value + def log_request(self, size=None): 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) + self.log_message('"%s" %s%s', + self.requestline, "({} 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): @@ -125,12 +126,21 @@ def isApi(self, api: str): return True return False + def noApi(self): + err = "Error: no API matched {} '{}'".format(self.command, self.path) + self.log_error(err) + self.send_response_only(HTTPStatus.NOT_IMPLEMENTED) + self.send_header("Content-type", "text/plain") + self.end_headers() + self.wfile.write(str.encode(err)); + 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_response_only(HTTPStatus.OK) self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(str.encode(string)) diff --git a/lib/sentry_dart_plugin.dart b/lib/sentry_dart_plugin.dart index cf62715..5f4e141 100644 --- a/lib/sentry_dart_plugin.dart +++ b/lib/sentry_dart_plugin.dart @@ -61,10 +61,11 @@ class SentryDartPlugin { List params = []; _setUrlAndTokenAndLog(params); - _addOrgAndProject(params); params.add('upload-dif'); + _addOrgAndProject(params); + if (_configuration.includeNativeSources) { params.add('--include-sources'); } else { @@ -83,8 +84,8 @@ class SentryDartPlugin { List _releasesCliParams() { final params = []; _setUrlAndTokenAndLog(params); - _addOrgAndProject(params); params.add('releases'); + _addOrgAndProject(params); return params; } diff --git a/test/plugin_test.dart b/test/plugin_test.dart index dceef45..0fc28bb 100644 --- a/test/plugin_test.dart +++ b/test/plugin_test.dart @@ -70,31 +70,30 @@ $configIndented url: http://127.0.0.1 log_level: debug '''); - const args = - '--url http://127.0.0.1 --auth-token t --log-level debug $orgAndProject'; + const args = '--url http://127.0.0.1 --auth-token t --log-level debug'; expect(commandLog, const [ - '$cli $args upload-dif --include-sources $buildDir', - '$cli $args releases new $release', - '$cli $args releases files $release upload-sourcemaps $buildDir/build/web --ext map --ext js', - '$cli $args releases files $release upload-sourcemaps $buildDir --ext dart', - '$cli $args releases set-commits $release --auto', - '$cli $args releases finalize $release' + '$cli $args upload-dif $orgAndProject --include-sources $buildDir', + '$cli $args releases $orgAndProject new $release', + '$cli $args releases $orgAndProject files $release upload-sourcemaps $buildDir/build/web --ext map --ext js', + '$cli $args releases $orgAndProject files $release upload-sourcemaps $buildDir --ext dart', + '$cli $args releases $orgAndProject set-commits $release --auto', + '$cli $args releases $orgAndProject finalize $release' ]); }); test('defaults', () async { final commandLog = await runWith(''); - const args = '--auth-token t $orgAndProject'; + const args = '--auth-token t'; expect(commandLog, const [ - '$cli $args upload-dif $buildDir', - '$cli $args releases new $release', - '$cli $args releases set-commits $release --auto', - '$cli $args releases finalize $release' + '$cli $args upload-dif $orgAndProject $buildDir', + '$cli $args releases $orgAndProject new $release', + '$cli $args releases $orgAndProject set-commits $release --auto', + '$cli $args releases $orgAndProject finalize $release' ]); }); group('commits', () { - const args = '--auth-token t $orgAndProject'; + const args = '--auth-token t'; // https://docs.sentry.io/product/cli/releases/#sentry-cli-commit-integration for (final value in const [ @@ -112,10 +111,10 @@ $configIndented ? '--auto' : '--commit $value'; expect(commandLog, [ - '$cli $args upload-dif $buildDir', - '$cli $args releases new $release', - '$cli $args releases set-commits $release $expectedArgs', - '$cli $args releases finalize $release' + '$cli $args upload-dif $orgAndProject $buildDir', + '$cli $args releases $orgAndProject new $release', + '$cli $args releases $orgAndProject set-commits $release $expectedArgs', + '$cli $args releases $orgAndProject finalize $release' ]); }); } @@ -124,9 +123,9 @@ $configIndented test('false', () async { final commandLog = await runWith('commits: false'); expect(commandLog, [ - '$cli $args upload-dif $buildDir', - '$cli $args releases new $release', - '$cli $args releases finalize $release' + '$cli $args upload-dif $orgAndProject $buildDir', + '$cli $args releases $orgAndProject new $release', + '$cli $args releases $orgAndProject finalize $release' ]); }); }); From 1ed75d8c4d57957fea2dcf77a4f2a6d9f9d07ee8 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 12 Oct 2022 09:28:48 +0200 Subject: [PATCH 06/11] chore: update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad2a0a8..5812251 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ * Add support to load release variable from environment ([#40](https://github.com/getsentry/sentry-dart-plugin/pull/40)) * Download Sentry CLI on first run ([#49](https://github.com/getsentry/sentry-dart-plugin/pull/49)) +* Support release commits ([#62](https://github.com/getsentry/sentry-dart-plugin/pull/62)) + ### Dependencies From 6f81fc50462a32bd84062daa8f67943e9afd80ba Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Thu, 13 Oct 2022 16:07:43 +0200 Subject: [PATCH 07/11] fix: integration-test-server --- integration-test/integration-test-server.py | 29 ++++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/integration-test/integration-test-server.py b/integration-test/integration-test-server.py index 8fc8178..65871e2 100644 --- a/integration-test/integration-test-server.py +++ b/integration-test/integration-test-server.py @@ -11,8 +11,9 @@ apiOrg = 'sentry-sdks' apiProject = 'sentry-dart-plugin' uri = urlparse(sys.argv[1] if len(sys.argv) > 1 else 'http://127.0.0.1:8000') -version='1.1.0' -appIdentifier='project' +version = '1.1.0' +appIdentifier = 'project' + class Handler(BaseHTTPRequestHandler): body = None @@ -37,7 +38,7 @@ def do_GET(self): elif self.isApi('/api/0/projects/{}/{}/releases/{}/files/?cursor='.format(apiOrg, apiProject, version)): self.writeJSONFile("assets/artifacts.json") else: - self.noApi() + self.writeNoApiMatchesError() self.flushLogs() @@ -82,7 +83,7 @@ def do_POST(self): elif self.isApi('api/0/organizations/{}/chunk-upload/'.format(apiOrg)): self.writeJSON('{ }') else: - self.noApi() + self.writeNoApiMatchesError() self.flushLogs() @@ -94,7 +95,7 @@ def do_PUT(self): elif self.isApi('/api/0/projects/{}/{}/releases/{}@{}/'.format(apiOrg, apiProject, appIdentifier, version)): self.writeJSONFile("assets/release.json") else: - self.noApi() + self.writeNoApiMatchesError() self.flushLogs() @@ -126,13 +127,11 @@ def isApi(self, api: str): return True return False - def noApi(self): + def writeNoApiMatchesError(self): err = "Error: no API matched {} '{}'".format(self.command, self.path) self.log_error(err) - self.send_response_only(HTTPStatus.NOT_IMPLEMENTED) - self.send_header("Content-type", "text/plain") - self.end_headers() - self.wfile.write(str.encode(err)); + self.writeResponse(HTTPStatus.NOT_IMPLEMENTED, + "text/plain", err) def writeJSONFile(self, file_name: str): json_file = open(file_name, "r") @@ -140,10 +139,14 @@ def writeJSONFile(self, file_name: str): json_file.close() def writeJSON(self, string: str): - self.send_response_only(HTTPStatus.OK) - self.send_header("Content-type", "application/json") + self.writeResponse(HTTPStatus.OK, "application/json", string) + + def writeResponse(self, code: HTTPStatus, type: str, body: str): + self.send_response_only(code) + self.send_header("Content-type", type) + self.send_header("Content-Length", len(body)) self.end_headers() - self.wfile.write(str.encode(string)) + self.wfile.write(str.encode(body)) def flushLogs(self): sys.stdout.flush() From fe51fa0d0c64696251beb76035a335dad7060328 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Thu, 13 Oct 2022 17:00:47 +0200 Subject: [PATCH 08/11] chore: update changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5812251..45742ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ * Download Sentry CLI on first run ([#49](https://github.com/getsentry/sentry-dart-plugin/pull/49)) * Support release commits ([#62](https://github.com/getsentry/sentry-dart-plugin/pull/62)) - ### Dependencies * Bump CLI from v2.6.0 to v2.7.0 ([#57](https://github.com/getsentry/sentry-dart-plugin/pull/57)) From 70942cc4421b05ceaac7063d41df6163aaca12cf Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 14 Oct 2022 10:03:20 +0200 Subject: [PATCH 09/11] chore: fix changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45742ca..2e14f4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,17 @@ # Changelog +## Unreleased + +### Features + +* Support release commits ([#62](https://github.com/getsentry/sentry-dart-plugin/pull/62)) + ## 1.0.0-beta.3 ### Features * Add support to load release variable from environment ([#40](https://github.com/getsentry/sentry-dart-plugin/pull/40)) * Download Sentry CLI on first run ([#49](https://github.com/getsentry/sentry-dart-plugin/pull/49)) -* Support release commits ([#62](https://github.com/getsentry/sentry-dart-plugin/pull/62)) ### Dependencies From 1fe350a29e4329004c4f626d910206ca4b0516fd Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 14 Oct 2022 10:05:39 +0200 Subject: [PATCH 10/11] chore: update docs --- README.md | 6 ++++-- lib/src/configuration.dart | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 95b6eb1..0a533e5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Sentry Dart Plugin [![Sentry Dart Plugin](https://github.com/getsentry/sentry-dart-plugin/actions/workflows/dart_plugin.yml/badge.svg)](https://github.com/getsentry/sentry-dart-plugin/actions/workflows/dart_plugin.yml) -[![pub package](https://img.shields.io/pub/v/sentry_dart_plugin.svg)](https://pub.dev/packages/sentry_dart_plugin) +[![pub package](https://img.shields.io/pub/v/sentry_dart_plugin.svg)](https://pub.dev/packages/sentry_dart_plugin) [![pub points](https://img.shields.io/pub/points/sentry_dart_plugin)](https://pub.dev/packages/sentry_dart_plugin/score) A Dart Build Plugin that uploads debug symbols for Android, iOS/macOS and source maps for Web to Sentry via [sentry-cli](https://docs.sentry.io/product/cli/). @@ -54,9 +54,10 @@ sentry: log_level: error # possible values: trace, debug, info, warn, error release: ... web_build_path: ... + commits: auto ``` -###### Available Configuration Fields: +### Available Configuration Fields | Configuration Name | Description | Default Value And Type | Required | Alternative Environment variable | | - | - | - | - | - | @@ -71,6 +72,7 @@ sentry: | log_level | Configures the log level for sentry-cli | warn (string) | no | SENTRY_LOG_LEVEL | | release | The release version for source maps, it should match the release set by the SDK | default: name@version from pubspec (string) | no | SENTRY_RELEASE | | web_build_path | The web build folder | default: build/web (string) | no | - | +| commits | Release commits integration | default: auto | no | - | ## Troubleshooting diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 11b70f2..9a40231 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -57,6 +57,11 @@ class Configuration { /// the Web Build folder, defaults to build/web late String webBuildFilesFolder; + /// Associate commits with the release. Defaults to `auto` which will discover + /// commits from the current project and compare them with the ones associated + /// to the previous release. See docs for other options: + /// https://docs.sentry.io/product/cli/releases/#sentry-cli-commit-integration + /// Set to `false` to disable this feature completely. late String commits; dynamic _getPubspec() { From 8a65d1142b6e11e8d0f610cc36df35741afa7e10 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 14 Oct 2022 10:27:47 +0200 Subject: [PATCH 11/11] test: all plugin tests with & without url specified --- test/plugin_test.dart | 155 ++++++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 73 deletions(-) diff --git a/test/plugin_test.dart b/test/plugin_test.dart index 0fc28bb..e83ee75 100644 --- a/test/plugin_test.dart +++ b/test/plugin_test.dart @@ -33,12 +33,24 @@ void main() { injector.registerSingleton(() => MockCLI(), override: true); }); - Future> runWith(String config) async { - // properly indent the configuration for the `sentry` section in the yaml - final configIndented = - config.trim().split('\n').map((l) => ' ${l.trim()}').join('\n'); - - fs.file('pubspec.yaml').writeAsStringSync(''' + for (final url in const ['http://127.0.0.1', null]) { + group('url: $url', () { + final commonArgs = + '${url == null ? '' : '--url http://127.0.0.1 '}--auth-token t'; + final commonCommands = [ + if (!Platform.isWindows) 'chmod +x $cli', + '$cli help' + ]; + + Future> runWith(String config) async { + // properly indent the configuration for the `sentry` section in the yaml + if (url != null) { + config = 'url: $url\n$config'; + } + final configIndented = + config.trim().split('\n').map((l) => ' ${l.trim()}').join('\n'); + + fs.file('pubspec.yaml').writeAsStringSync(''' name: $project version: $version @@ -49,86 +61,83 @@ sentry: $configIndented '''); - final exitCode = await plugin.run([]); - expect(exitCode, 0); - expect(pm.commandLog.take(2), const ['chmod +x $cli', '$cli help']); - return pm.commandLog.skip(2); - } + final exitCode = await plugin.run([]); + expect(exitCode, 0); + expect(pm.commandLog.take(commonCommands.length), commonCommands); + return pm.commandLog.skip(commonCommands.length); + } - test('fails without args and pubspec', () async { - final exitCode = await plugin.run([]); - expect(exitCode, 1); - expect(pm.commandLog, const ['chmod +x $cli', '$cli help']); - }); + test('fails without args and pubspec', () async { + final exitCode = await plugin.run([]); + expect(exitCode, 1); + expect(pm.commandLog, commonCommands); + }); - test('works with pubspec', () async { - // TODO: because `url` param affects all commands, make it a test-group argument and run all test cases with/without it. - final commandLog = await runWith(''' + test('works with pubspec', () async { + final commandLog = await runWith(''' upload_native_symbols: true include_native_sources: true upload_source_maps: true - url: http://127.0.0.1 log_level: debug '''); - const args = '--url http://127.0.0.1 --auth-token t --log-level debug'; - expect(commandLog, const [ - '$cli $args upload-dif $orgAndProject --include-sources $buildDir', - '$cli $args releases $orgAndProject new $release', - '$cli $args releases $orgAndProject files $release upload-sourcemaps $buildDir/build/web --ext map --ext js', - '$cli $args releases $orgAndProject files $release upload-sourcemaps $buildDir --ext dart', - '$cli $args releases $orgAndProject set-commits $release --auto', - '$cli $args releases $orgAndProject finalize $release' - ]); - }); - - test('defaults', () async { - final commandLog = await runWith(''); - const args = '--auth-token t'; - expect(commandLog, const [ - '$cli $args upload-dif $orgAndProject $buildDir', - '$cli $args releases $orgAndProject new $release', - '$cli $args releases $orgAndProject set-commits $release --auto', - '$cli $args releases $orgAndProject finalize $release' - ]); - }); - - group('commits', () { - const args = '--auth-token t'; - - // https://docs.sentry.io/product/cli/releases/#sentry-cli-commit-integration - for (final value in const [ - null, // test the implicit default - 'true', - 'auto', - 'repo_name@293ea41d67225d27a8c212f901637e771d73c0f7', - 'repo_name@293ea41d67225d27a8c212f901637e771d73c0f7..1e248e5e6c24b79a5c46a2e8be12cef0e41bd58d', - ]) { - test(value, () async { - final commandLog = - await runWith(value == null ? '' : 'commits: $value'); - final expectedArgs = - (value == null || value == 'auto' || value == 'true') - ? '--auto' - : '--commit $value'; + final args = '$commonArgs --log-level debug'; expect(commandLog, [ - '$cli $args upload-dif $orgAndProject $buildDir', + '$cli $args upload-dif $orgAndProject --include-sources $buildDir', '$cli $args releases $orgAndProject new $release', - '$cli $args releases $orgAndProject set-commits $release $expectedArgs', + '$cli $args releases $orgAndProject files $release upload-sourcemaps $buildDir/build/web --ext map --ext js', + '$cli $args releases $orgAndProject files $release upload-sourcemaps $buildDir --ext dart', + '$cli $args releases $orgAndProject set-commits $release --auto', '$cli $args releases $orgAndProject finalize $release' ]); }); - } - - // if explicitly disabled - test('false', () async { - final commandLog = await runWith('commits: false'); - expect(commandLog, [ - '$cli $args upload-dif $orgAndProject $buildDir', - '$cli $args releases $orgAndProject new $release', - '$cli $args releases $orgAndProject finalize $release' - ]); + + test('defaults', () async { + final commandLog = await runWith(''); + expect(commandLog, [ + '$cli $commonArgs upload-dif $orgAndProject $buildDir', + '$cli $commonArgs releases $orgAndProject new $release', + '$cli $commonArgs releases $orgAndProject set-commits $release --auto', + '$cli $commonArgs releases $orgAndProject finalize $release' + ]); + }); + + group('commits', () { + // https://docs.sentry.io/product/cli/releases/#sentry-cli-commit-integration + for (final value in const [ + null, // test the implicit default + 'true', + 'auto', + 'repo_name@293ea41d67225d27a8c212f901637e771d73c0f7', + 'repo_name@293ea41d67225d27a8c212f901637e771d73c0f7..1e248e5e6c24b79a5c46a2e8be12cef0e41bd58d', + ]) { + test(value, () async { + final commandLog = + await runWith(value == null ? '' : 'commits: $value'); + final expectedArgs = + (value == null || value == 'auto' || value == 'true') + ? '--auto' + : '--commit $value'; + expect(commandLog, [ + '$cli $commonArgs upload-dif $orgAndProject $buildDir', + '$cli $commonArgs releases $orgAndProject new $release', + '$cli $commonArgs releases $orgAndProject set-commits $release $expectedArgs', + '$cli $commonArgs releases $orgAndProject finalize $release' + ]); + }); + } + + // if explicitly disabled + test('false', () async { + final commandLog = await runWith('commits: false'); + expect(commandLog, [ + '$cli $commonArgs upload-dif $orgAndProject $buildDir', + '$cli $commonArgs releases $orgAndProject new $release', + '$cli $commonArgs releases $orgAndProject finalize $release' + ]); + }); + }); }); - }); + } } class MockProcessManager implements ProcessManager {