diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index b9e412d85..908549ea4 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -32,6 +32,7 @@ jobs: - test/commands/test/async_main/async_main_test.dart - test/commands/test/compilation_error/compilation_error_test.dart - test/commands/test/no_project/no_project_test.dart + - test/commands/test/optimization_annotations/optimization_annotations_test.dart - test/commands/test/optimization_exclude/optimization_exclude_test.dart - test/commands/test/spaced_golden_file_name/spaced_golden_file_name_test.dart - test/commands/test/very_good_config/very_good_config_test.dart diff --git a/bricks/test_optimizer/__brick__/test/.test_optimizer.dart b/bricks/test_optimizer/__brick__/test/.test_optimizer.dart index 22e570742..aa76afcf0 100644 --- a/bricks/test_optimizer/__brick__/test/.test_optimizer.dart +++ b/bricks/test_optimizer/__brick__/test/.test_optimizer.dart @@ -4,14 +4,15 @@ {{#isFlutter}}import 'dart:io'; import 'dart:typed_data'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_test/flutter_test.dart' hide group; +import 'package:test_api/scaffolding.dart' show group; {{/isFlutter}}{{^isFlutter}}import 'package:test/test.dart';{{/isFlutter}} {{#tests}}import '{{{path}}}' as {{identifier}}; {{/tests}} void main() { {{#isFlutter}} goldenFileComparator = _TestOptimizationAwareGoldenFileComparator(goldenFileComparator as LocalFileComparator);{{/isFlutter}} -{{#tests}} group('{{{path}}}', () { {{identifier}}.main(); }); +{{#tests}} group('{{{path}}}', () { {{identifier}}.main(); }{{{groupArguments}}}); {{/tests}}} {{#isFlutter}} diff --git a/bricks/test_optimizer/hooks/lib/pre_gen.dart b/bricks/test_optimizer/hooks/lib/pre_gen.dart index c0348ef02..d2361e265 100644 --- a/bricks/test_optimizer/hooks/lib/pre_gen.dart +++ b/bricks/test_optimizer/hooks/lib/pre_gen.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:hooks/dart_identifier_generator.dart'; +import 'package:hooks/test_metadata.dart'; import 'package:mason/mason.dart'; import 'package:path/path.dart' as path; @@ -8,11 +9,17 @@ typedef ExitFn = Never Function(int code); ExitFn exitFn = exit; -String skipVeryGoodOptimizationTag = 'skip_very_good_optimization'; -RegExp skipVeryGoodOptimizationRegExp = RegExp( - "@Tags\\s*\\(\\s*\\[[\\s\\S]*?[\"']$skipVeryGoodOptimizationTag[\"'][\\s\\S]*?\\]\\s*\\)", - multiLine: true, -); +/// The tag that opts a test file out of the optimized bundle. +const skipVeryGoodOptimizationTag = 'skip_very_good_optimization'; + +extension TestMetadataBundle on TestMetadata { + bool get skipsOptimization => tagNames.contains(skipVeryGoodOptimizationTag); + + /// The named arguments to append to a `group` call, each prefixed by `, `. + String get groupArguments { + return arguments.entries.map((e) => ', ${e.key}: ${e.value}').join(); + } +} Future run(HookContext context) async { final packageRoot = context.vars['package-root'] as String; @@ -34,69 +41,63 @@ Future run(HookContext context) async { final isFlutter = flutterSdkRegExp.hasMatch(pubspecContents); final identifierGenerator = DartIdentifierGenerator(); - final testIdentifierTable = >[]; + final optimizedTests = >[]; + final notOptimizedTests = []; + final tests = testDir .listSync(recursive: true) - .where((entity) => entity.isTest); - - final notOptimizedTests = await getNotOptimizedTests(tests, testDir.path); + .where((entity) => entity.isTest) + .cast(); + final parsedTests = await Future.wait(tests.map(_parse)); - for (final entity in tests) { + for (final (file, content, metadata) in parsedTests) { final relativePath = path - .relative(entity.path, from: testDir.path) + .relative(file.path, from: testDir.path) .replaceAll(r'\', '/'); - testIdentifierTable.add({ + + if (metadata.skipsOptimization) { + notOptimizedTests.add(relativePath); + continue; + } + + if (content.contains(skipVeryGoodOptimizationTag)) { + context.logger.warn( + '$relativePath names $skipVeryGoodOptimizationTag but was optimized ' + 'anyway: package:test reads @Tags only from the metadata of the ' + "file's first directive.", + ); + } + + for (final annotation in metadata.droppedAnnotations) { + context.logger.warn( + '$relativePath: left $annotation out of the optimized bundle, which ' + 'cannot resolve every name it references.', + ); + } + + optimizedTests.add({ 'path': relativePath, 'identifier': identifierGenerator.next(), + 'groupArguments': metadata.groupArguments, }); } - final optimizedTestsIdentifierTable = testIdentifierTable - .where((e) => !notOptimizedTests.contains(e['path'])) - .toList(); - context.vars = { - 'tests': optimizedTestsIdentifierTable, + 'tests': optimizedTests, 'isFlutter': isFlutter, 'notOptimizedTests': notOptimizedTests, }; } +typedef _ParsedTest = (File file, String content, TestMetadata metadata); + +Future<_ParsedTest> _parse(File file) async { + final content = await file.readAsString(); + return (file, content, parseTestMetadata(content, path: file.path)); +} + extension on FileSystemEntity { bool get isTest { return this is File && path.basename(this.path).endsWith('_test.dart'); } } - -Future> getNotOptimizedTests( - Iterable tests, - String testDir, -) async { - final paths = tests.map((e) => e.path).toList(); - final formattedPaths = paths.map((e) => e.replaceAll('/./', '/')).toList(); - - final fileFutures = formattedPaths.map(_checkFileForSkipVeryGoodOptimization); - final fileResults = await Future.wait(fileFutures); - - final testWithVeryGoodTest = []; - for (var i = 0; i < formattedPaths.length; i++) { - if (fileResults[i]) { - testWithVeryGoodTest.add(formattedPaths[i]); - } - } - - /// Format to relative path - final relativePaths = testWithVeryGoodTest - .map((e) => path.relative(e, from: testDir)) - .toList(); - - return relativePaths; -} - -/// Check if a single file contains skip_very_good_optimization tag -Future _checkFileForSkipVeryGoodOptimization(String path) async { - final file = File(path); - if (!file.existsSync()) return false; - final content = await file.readAsString(); - return skipVeryGoodOptimizationRegExp.hasMatch(content); -} diff --git a/bricks/test_optimizer/hooks/lib/test_metadata.dart b/bricks/test_optimizer/hooks/lib/test_metadata.dart new file mode 100644 index 000000000..2bfca3781 --- /dev/null +++ b/bricks/test_optimizer/hooks/lib/test_metadata.dart @@ -0,0 +1,189 @@ +import 'package:analyzer/dart/analysis/utilities.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; + +/// {@template test_metadata} +/// The library level annotations `package:test` reads from a test file, held +/// as source text so they can be forwarded to the `group` that wraps the file +/// in the optimized bundle. +/// {@endtemplate} +class TestMetadata { + /// {@macro test_metadata} + const new({ + this.arguments = const {}, + this.tagNames = const {}, + this.droppedAnnotations = const [], + }); + + /// `group` arguments keyed by parameter name, in a fixed order rather than + /// the order the file wrote its annotations. + final Map arguments; + + /// The string literal tags of a `@Tags` annotation, read even when the + /// annotation itself was dropped. + final Set tagNames; + + /// The annotations left out because the bundle cannot resolve them. + final List droppedAnnotations; +} + +/// The names an annotation argument may reference. +/// +/// The bundle imports only `package:test` (or `package:flutter_test`) and +/// `dart:core`, so an argument naming anything else does not resolve there. +const _resolvableNames = { + 'Duration', + 'Timeout', + 'Skip', + 'factor', + 'none', + 'zero', +}; + +/// The `group` parameter each forwarded annotation supplies. +/// +/// Iteration order fixes the order [TestMetadata.arguments] emits. +const _groupParameters = { + 'TestOn': 'testOn', + 'Timeout': 'timeout', + 'Skip': 'skip', + 'Tags': 'tags', + 'OnPlatform': 'onPlatform', + 'Retry': 'retry', +}; + +/// Parses the library level test annotations of [content], the source of the +/// test file at [path]. Only the first directive's metadata is read, the one +/// place `package:test` looks. +/// +/// An annotation naming something the bundle cannot resolve, as in +/// `@Timeout(kSlowSuite)`, is recorded in [TestMetadata.droppedAnnotations] +/// instead of being forwarded, so one file cannot break the whole bundle. +TestMetadata parseTestMetadata(String content, {String? path}) { + final unit = parseString( + content: content, + path: path, + throwIfDiagnostics: false, + ).unit; + + final directives = unit.directives; + if (directives.isEmpty) return const TestMetadata(); + + final prefixes = directives + .whereType() + .map((directive) => directive.prefix?.name) + .nonNulls + .toSet(); + + var tagNames = const {}; + final droppedAnnotations = []; + final sources = {}; + + for (final annotation in directives.first.metadata) { + final (className, constructorName) = _resolveConstructor( + annotation.name, + annotation.constructorName, + prefixes, + ); + if (!_groupParameters.containsKey(className)) continue; + + final arguments = annotation.arguments; + + if (className == 'Tags') { + final argument = arguments?.arguments.firstOrNull; + if (argument is ListLiteral) { + tagNames = argument.elements + .whereType() + .map((element) => element.stringValue) + .nonNulls + .toSet(); + } + } + + if (!_isResolvable(arguments, constructorName)) { + droppedAnnotations.add(annotation.toSource()); + continue; + } + + final source = _argumentSource(className, constructorName, arguments); + if (source != null) sources[className] = source; + } + + return TestMetadata( + arguments: { + for (final MapEntry(key: annotation, value: parameter) + in _groupParameters.entries) + parameter: ?sources[annotation], + }, + tagNames: tagNames, + droppedAnnotations: droppedAnnotations, + ); +} + +/// The source forwarded for one annotation, or `null` when it has none, as for +/// a bare `@Timeout` or `@Skip` that names the type rather than a value. +String? _argumentSource( + String className, + String? constructorName, + ArgumentList? arguments, +) { + final argument = arguments?.arguments.firstOrNull; + return switch (className) { + 'Timeout' when arguments == null && constructorName == null => null, + 'Timeout' => + 'Timeout${constructorName == null ? '' : '.$constructorName'}' + '${arguments?.toSource() ?? ''}', + 'Skip' when arguments == null => null, + 'Skip' => argument?.toSource() ?? 'true', + _ => argument?.toSource(), + }; +} + +/// Whether every name [arguments] and [constructorName] reference resolves in +/// the optimized bundle. +bool _isResolvable(ArgumentList? arguments, String? constructorName) { + final referenced = _ReferencedNames(); + arguments?.accept(referenced); + return [ + ?constructorName, + ...referenced.names, + ].every(_resolvableNames.contains); +} + +/// Collects every name an expression references. +/// +/// Named argument labels are tokens rather than identifiers, so `minutes` in +/// `Duration(minutes: 5)` is not collected, while `Duration` is. +class _ReferencedNames extends RecursiveAstVisitor { + final names = []; + + @override + void visitSimpleIdentifier(SimpleIdentifier node) => names.add(node.name); + + @override + void visitNamedType(NamedType node) { + names.add(node.name.lexeme); + super.visitNamedType(node); + } +} + +/// Resolves the class and named constructor an annotation refers to. +/// +/// The syntax is ambiguous, so [prefixes] disambiguates: `@Timeout.none` names +/// a constructor, while `@test.Skip()` names `Skip` behind the `test` prefix. +(String, String?) _resolveConstructor( + Identifier name, + SimpleIdentifier? constructorName, + Set prefixes, +) { + if (name is PrefixedIdentifier && + !prefixes.contains(name.prefix.name) && + constructorName == null) { + return (name.prefix.name, name.identifier.name); + } + + final className = name is PrefixedIdentifier + ? name.identifier.name + : name.name; + return (className, constructorName?.name); +} diff --git a/bricks/test_optimizer/hooks/pubspec.yaml b/bricks/test_optimizer/hooks/pubspec.yaml index 32815b597..e03b5d30e 100644 --- a/bricks/test_optimizer/hooks/pubspec.yaml +++ b/bricks/test_optimizer/hooks/pubspec.yaml @@ -5,6 +5,7 @@ environment: sdk: ^3.13.0 dependencies: + analyzer: ^14.0.0 mason: ^0.1.0 path: ^1.8.1 diff --git a/bricks/test_optimizer/hooks/test/pre_gen_test.dart b/bricks/test_optimizer/hooks/test/pre_gen_test.dart index 67a2ccf19..d86e17bb0 100644 --- a/bricks/test_optimizer/hooks/test/pre_gen_test.dart +++ b/bricks/test_optimizer/hooks/test/pre_gen_test.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:hooks/pre_gen.dart' as pre_gen; +import 'package:hooks/test_metadata.dart'; import 'package:mason/mason.dart'; import 'package:mocktail/mocktail.dart'; import 'package:path/path.dart' as path; @@ -16,9 +17,11 @@ class _FakeContext extends Fake implements HookContext { Map vars = {}; } -final notOptimizedTestContent = +const notOptimizedTestContent = ''' @Tags(['${pre_gen.skipVeryGoodOptimizationTag}']) +library; + void main() { test('test', () { expect(1, 1); @@ -26,9 +29,11 @@ void main() { } '''; -final anotherNotOptimizedTestContent = +const anotherNotOptimizedTestContent = ''' @Tags(['${pre_gen.skipVeryGoodOptimizationTag}', 'another_tag']) +import 'package:test/test.dart'; + void main() { test('another test', () { expect(1, 1); @@ -87,6 +92,12 @@ void main() { reason: 'All tests files should have unique identifiers', ); + expect( + tests.map((test) => test['groupArguments']), + everyElement(isEmpty), + reason: 'Files without annotations forward no group arguments', + ); + expect(context.vars['isFlutter'], false); }); @@ -149,6 +160,112 @@ dependencies: expect(notOptimizedTests, contains('not_optimized_test.dart')); expect(notOptimizedTests, contains('another_not_optimized_test.dart')); }); + + test( + 'with file level annotations forwarded as group arguments', + () async { + File(path.join(tempDirectory.path, 'pubspec.yaml')).createSync(); + + final testDir = Directory(path.join(tempDirectory.path, 'test')) + ..createSync(); + File(path.join(testDir.path, 'annotated_test.dart')) + .writeAsStringSync(''' +@Tags(['slow']) +@Timeout(Duration(minutes: 5)) +library; + +void main() {} +'''); + + context.vars['package-root'] = tempDirectory.absolute.path; + + await pre_gen.run(context); + + final tests = context.vars['tests'] as List>; + expect(tests, hasLength(1)); + expect( + tests.single['groupArguments'], + equals(", timeout: Timeout(Duration(minutes: 5)), tags: ['slow']"), + ); + }, + ); + + test( + 'with a skip tag package:test would ignore left optimized', + () async { + File(path.join(tempDirectory.path, 'pubspec.yaml')).createSync(); + + final testDir = Directory(path.join(tempDirectory.path, 'test')) + ..createSync(); + File(path.join(testDir.path, 'no_directive_test.dart')) + .writeAsStringSync(''' +@Tags(['${pre_gen.skipVeryGoodOptimizationTag}']) +void main() {} +'''); + + context.vars['package-root'] = tempDirectory.absolute.path; + + await pre_gen.run(context); + + final tests = context.vars['tests'] as List>; + expect( + tests.map((test) => test['path']), + contains('no_directive_test.dart'), + ); + expect(context.vars['notOptimizedTests'], isEmpty); + + verify( + () => context.logger.warn( + any( + that: allOf( + contains('no_directive_test.dart'), + contains(pre_gen.skipVeryGoodOptimizationTag), + ), + ), + ), + ).called(1); + }, + ); + + test( + 'with an unresolvable annotation left out and warned about', + () async { + File(path.join(tempDirectory.path, 'pubspec.yaml')).createSync(); + + final testDir = Directory(path.join(tempDirectory.path, 'test')) + ..createSync(); + File(path.join(testDir.path, 'unresolvable_test.dart')) + .writeAsStringSync(''' +@Timeout(kSlowSuite) +@Retry(2) +library; + +void main() {} +'''); + + context.vars['package-root'] = tempDirectory.absolute.path; + + await pre_gen.run(context); + + final tests = context.vars['tests'] as List>; + expect( + tests.single['groupArguments'], + equals(', retry: 2'), + reason: 'The resolvable sibling is still forwarded', + ); + + verify( + () => context.logger.warn( + any( + that: allOf( + contains('unresolvable_test.dart'), + contains('@Timeout(kSlowSuite)'), + ), + ), + ), + ).called(1); + }, + ); }); group('Fails', () { @@ -218,65 +335,82 @@ dependencies: expect(context.vars['isFlutter'], isNull); }); }); + }); - group('skipVeryGoodOptimizationRegExp regex', () { - final regex = pre_gen.skipVeryGoodOptimizationRegExp; - test('matches single-line tag', () { - final content = "@Tags(['${pre_gen.skipVeryGoodOptimizationTag}'])"; - expect(regex.hasMatch(content), isTrue); + group('TestMetadataBundle', () { + group('groupArguments', () { + test('is empty without annotations', () { + final metadata = parseTestMetadata('void main() {}'); + + expect(metadata.groupArguments, isEmpty); }); - test('matches single-line with multiple tags', () { - final content = - "@Tags(['${pre_gen.skipVeryGoodOptimizationTag}', 'chrome'])"; - expect(regex.hasMatch(content), isTrue); + test('emits a single annotation', () { + final metadata = parseTestMetadata("@Skip('not ready')\nlibrary;"); + + expect(metadata.groupArguments, equals(", skip: 'not ready'")); }); - test('matches multi-line tag list', () { - final content = - ''' - @Tags([ - '${pre_gen.skipVeryGoodOptimizationTag}', - 'chrome', - 'test', - ]) - '''; - expect(regex.hasMatch(content), isTrue); + test('emits a fixed order regardless of source order', () { + final metadata = parseTestMetadata(''' +@Retry(3) +@Tags(['slow']) +@Skip() +@Timeout.none +@TestOn('vm') +library; +'''); + + expect( + metadata.groupArguments, + equals( + ", testOn: 'vm', timeout: Timeout.none, skip: true, " + "tags: ['slow'], retry: 3", + ), + ); }); + }); + + group('skipsOptimization', () { + test('is true for the skip tag on its own', () { + final metadata = parseTestMetadata( + "@Tags(['${pre_gen.skipVeryGoodOptimizationTag}'])\nlibrary;", + ); - test('matches multi-line where tag is not the first', () { - final content = - ''' - @Tags([ - 'chrome', - '${pre_gen.skipVeryGoodOptimizationTag}', - 'test', - ]) - '''; - expect(regex.hasMatch(content), isTrue); + expect(metadata.skipsOptimization, isTrue); }); - test('does not match when tag missing', () { - const content = "@Tags(['chrome', 'test'])"; - expect(regex.hasMatch(content), isFalse); + test('is true when the skip tag is not first', () { + final metadata = parseTestMetadata( + "@Tags(['other', '${pre_gen.skipVeryGoodOptimizationTag}'])\n" + 'library;', + ); + + expect(metadata.skipsOptimization, isTrue); }); - test( - 'does not match substring only (e.g. skip_very_good_optimization,test)', - () { - final content = - ''' - @Tags([ - '${pre_gen.skipVeryGoodOptimizationTag},test', - 'chrome', - ]) - '''; - expect( - regex.hasMatch(content), - isFalse, - ); // only exact tag should match - }, - ); + test('is false without the tag', () { + final metadata = parseTestMetadata("@Tags(['other'])\nlibrary;"); + + expect(metadata.skipsOptimization, isFalse); + }); + + test('is false when the tag is only a substring', () { + final metadata = parseTestMetadata( + "@Tags(['${pre_gen.skipVeryGoodOptimizationTag},test'])\nlibrary;", + ); + + expect(metadata.skipsOptimization, isFalse); + }); + + test('is false when the annotation is not on the first directive', () { + final metadata = parseTestMetadata(''' +@Tags(['${pre_gen.skipVeryGoodOptimizationTag}']) +void main() {} +'''); + + expect(metadata.skipsOptimization, isFalse); + }); }); }); } diff --git a/bricks/test_optimizer/hooks/test/test_metadata_test.dart b/bricks/test_optimizer/hooks/test/test_metadata_test.dart new file mode 100644 index 000000000..386f96001 --- /dev/null +++ b/bricks/test_optimizer/hooks/test/test_metadata_test.dart @@ -0,0 +1,335 @@ +import 'package:hooks/test_metadata.dart'; +import 'package:test/test.dart'; + +/// Wraps [annotations] above a `library;` directive, the shape `package:test` +/// reads metadata from. +String library(String annotations) => + '$annotations\nlibrary;\n\nvoid main() {}'; + +/// Matches metadata that forwards no annotation to the bundle. +final Matcher forwardsNothing = isA().having( + (metadata) => metadata.arguments, + 'arguments', + isEmpty, +); + +void main() { + group('parseTestMetadata', () { + group('forwards nothing', () { + test('for empty content', () { + final metadata = parseTestMetadata(''); + + expect(metadata, forwardsNothing); + expect(metadata.tagNames, isEmpty); + expect(metadata.droppedAnnotations, isEmpty); + }); + + test('for a file without directives', () { + final metadata = parseTestMetadata('void main() {}'); + + expect(metadata, forwardsNothing); + }); + + test('for a file whose annotations sit above main', () { + final metadata = parseTestMetadata(''' +import 'package:test/test.dart'; + +@Skip('ignored by package:test') +void main() {} +'''); + + expect(metadata, forwardsNothing); + }); + + test('for annotations on a directive other than the first', () { + final metadata = parseTestMetadata(''' +import 'package:test/test.dart'; + +@Skip('ignored by package:test') +import 'dart:async'; + +void main() {} +'''); + + expect(metadata, forwardsNothing); + }); + + test('for a bare annotation with no argument list', () { + final metadata = parseTestMetadata(library('@Skip')); + + expect(metadata, forwardsNothing); + }); + + test('for a bare @Timeout, which names the type rather than a value', () { + final metadata = parseTestMetadata(library('@Timeout')); + + expect(metadata, forwardsNothing); + }); + }); + + group('ignores unrecognized annotations', () { + test('without dropping them', () { + final metadata = parseTestMetadata(library("@Deprecated('nope')")); + + expect(metadata, forwardsNothing); + expect(metadata.droppedAnnotations, isEmpty); + }); + + test('even when they reference an unresolvable name', () { + final metadata = parseTestMetadata(library('@Deprecated(reason)')); + + expect(metadata, forwardsNothing); + expect(metadata.droppedAnnotations, isEmpty); + }); + }); + + group('parses', () { + test('@Skip without a reason', () { + final metadata = parseTestMetadata(library('@Skip()')); + + expect(metadata.arguments['skip'], equals('true')); + }); + + test('@Skip with a reason', () { + final metadata = parseTestMetadata(''' +@Skip('not ready') +import 'package:test/test.dart'; + +void main() {} +'''); + + expect(metadata.arguments['skip'], equals("'not ready'")); + }); + + test('@Tags', () { + final metadata = parseTestMetadata( + library("@Tags(['slow', 'integration'])"), + ); + + expect(metadata.arguments['tags'], equals("['slow', 'integration']")); + expect(metadata.tagNames, equals({'slow', 'integration'})); + }); + + test('@Tags spanning several lines onto a single one', () { + final metadata = parseTestMetadata( + library("@Tags([\n 'slow',\n 'integration',\n])"), + ); + + expect(metadata.arguments['tags'], equals("['slow', 'integration']")); + }); + + test('@Timeout with a duration', () { + final metadata = parseTestMetadata( + library('@Timeout(Duration(minutes: 5))'), + ); + + expect( + metadata.arguments['timeout'], + equals('Timeout(Duration(minutes: 5))'), + ); + }); + + test('@Timeout with a const duration', () { + final metadata = parseTestMetadata( + library('@Timeout(const Duration(seconds: 1))'), + ); + + expect( + metadata.arguments['timeout'], + equals('Timeout(const Duration(seconds: 1))'), + ); + }); + + test('@Timeout.factor', () { + final metadata = parseTestMetadata(library('@Timeout.factor(2)')); + + expect(metadata.arguments['timeout'], equals('Timeout.factor(2)')); + }); + + test('@Timeout.none', () { + final metadata = parseTestMetadata(library('@Timeout.none')); + + expect(metadata.arguments['timeout'], equals('Timeout.none')); + }); + + test('@TestOn', () { + final metadata = parseTestMetadata(library("@TestOn('vm')")); + + expect(metadata.arguments['testOn'], equals("'vm'")); + }); + + test('@OnPlatform', () { + final metadata = parseTestMetadata( + library("@OnPlatform({'chrome': Timeout.factor(2)})"), + ); + + expect( + metadata.arguments['onPlatform'], + equals("{'chrome' : Timeout.factor(2)}"), + ); + }); + + test('@OnPlatform with a Skip value', () { + final metadata = parseTestMetadata( + library("@OnPlatform({'vm': Skip('not on the vm')})"), + ); + + expect( + metadata.arguments['onPlatform'], + equals("{'vm' : Skip('not on the vm')}"), + ); + }); + + test('@Retry', () { + final metadata = parseTestMetadata(library('@Retry(3)')); + + expect(metadata.arguments['retry'], equals('3')); + }); + + test('a file that fails to parse', () { + final metadata = parseTestMetadata(''' +@Skip('still read') +library; + +void main() { this is ( not valid dart +'''); + + expect(metadata.arguments['skip'], equals("'still read'")); + }); + }); + + group('drops an annotation the bundle cannot resolve', () { + test('naming a constant', () { + final metadata = parseTestMetadata(library('@Timeout(kSlowSuite)')); + + expect(metadata.arguments['timeout'], isNull); + expect(metadata.droppedAnnotations, equals(['@Timeout(kSlowSuite)'])); + }); + + test('naming a constant inside a collection', () { + final metadata = parseTestMetadata(library('@Tags([slow])')); + + expect(metadata.arguments['tags'], isNull); + expect(metadata.droppedAnnotations, equals(['@Tags([slow])'])); + }); + + test('naming a class the bundle does not import', () { + final metadata = parseTestMetadata( + library('@Timeout(const SlowDuration())'), + ); + + expect(metadata.arguments['timeout'], isNull); + expect( + metadata.droppedAnnotations, + equals(['@Timeout(const SlowDuration())']), + ); + }); + + test('naming an unknown constructor', () { + final metadata = parseTestMetadata(library('@Timeout.forever(2)')); + + expect(metadata.arguments['timeout'], isNull); + expect(metadata.droppedAnnotations, equals(['@Timeout.forever(2)'])); + }); + + test('reaching a class through an import prefix', () { + final metadata = parseTestMetadata(''' +@OnPlatform({'chrome': t.Timeout.factor(2)}) +import 'package:test/test.dart' as t; + +void main() {} +'''); + + expect(metadata.arguments['onPlatform'], isNull); + expect(metadata.droppedAnnotations, hasLength(1)); + }); + + test('while still reading its string literal tags', () { + final metadata = parseTestMetadata(library("@Tags([slow, 'fast'])")); + + expect(metadata.arguments['tags'], isNull); + expect(metadata.tagNames, equals({'fast'})); + expect(metadata.droppedAnnotations, hasLength(1)); + }); + + test('without dropping its resolvable siblings', () { + final metadata = parseTestMetadata( + library('@Retry(attempts)\n@Timeout.none'), + ); + + expect(metadata.arguments['retry'], isNull); + expect(metadata.arguments['timeout'], equals('Timeout.none')); + expect(metadata.droppedAnnotations, equals(['@Retry(attempts)'])); + }); + }); + + group('resolves import prefixes', () { + test('on an annotation with arguments', () { + final metadata = parseTestMetadata(''' +@t.Skip('prefixed') +@t.Tags(['slow']) +import 'package:test/test.dart' as t; + +void main() {} +'''); + + expect(metadata.arguments['skip'], equals("'prefixed'")); + expect(metadata.arguments['tags'], equals("['slow']")); + expect(metadata.tagNames, equals({'slow'})); + }); + + test('on a prefixed named constructor', () { + final metadata = parseTestMetadata(''' +@t.Timeout.factor(2) +import 'package:test/test.dart' as t; + +void main() {} +'''); + + expect(metadata.arguments['timeout'], equals('Timeout.factor(2)')); + }); + + test('without confusing a named constructor for a prefix', () { + final metadata = parseTestMetadata(''' +@Timeout.none +import 'package:test/test.dart' as t; + +void main() {} +'''); + + expect(metadata.arguments['timeout'], equals('Timeout.none')); + }); + }); + + group('reads tag names', () { + test('from a single tag', () { + final metadata = parseTestMetadata(library("@Tags(['only'])")); + + expect(metadata.tagNames, equals({'only'})); + }); + + test('from a list spanning several lines', () { + final metadata = parseTestMetadata( + library("@Tags([\n 'other',\n 'last',\n])"), + ); + + expect(metadata.tagNames, equals({'other', 'last'})); + }); + + test('as empty for an argument that is not a list literal', () { + final metadata = parseTestMetadata(library('@Tags(someTags)')); + + expect(metadata.tagNames, isEmpty); + }); + + test('as empty when the annotation is not on the first directive', () { + final metadata = parseTestMetadata(''' +@Tags(['ignored']) +void main() {} +'''); + + expect(metadata.tagNames, isEmpty); + }); + }); + }); +} diff --git a/e2e/test/commands/test/optimization_annotations/fixture/dart_test.yaml b/e2e/test/commands/test/optimization_annotations/fixture/dart_test.yaml new file mode 100644 index 000000000..cebbe60bc --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/fixture/dart_test.yaml @@ -0,0 +1,4 @@ +tags: + integration: + slow: + skip: "Only run in CI" diff --git a/e2e/test/commands/test/optimization_annotations/fixture/pubspec.yaml b/e2e/test/commands/test/optimization_annotations/fixture/pubspec.yaml new file mode 100644 index 000000000..552c6f8eb --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/fixture/pubspec.yaml @@ -0,0 +1,11 @@ +name: optimization_annotations_fixture +description: Fixture for testing file level test annotations. +version: 0.1.0+1 +publish_to: none + +environment: + sdk: ^3.13.0 + +dev_dependencies: + test: ^1.24.3 + test_api: ^0.7.0 diff --git a/e2e/test/commands/test/optimization_annotations/fixture/test/browser_only_test.dart b/e2e/test/commands/test/optimization_annotations/fixture/test/browser_only_test.dart new file mode 100644 index 000000000..6cce363bd --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/fixture/test/browser_only_test.dart @@ -0,0 +1,10 @@ +@TestOn('browser') +library; + +import 'package:test/test.dart'; + +void main() { + test('never runs on the vm', () { + fail('@TestOn was dropped by the optimizer'); + }); +} diff --git a/e2e/test/commands/test/optimization_annotations/fixture/test/integration_test.dart b/e2e/test/commands/test/optimization_annotations/fixture/test/integration_test.dart new file mode 100644 index 000000000..ba41ed38d --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/fixture/test/integration_test.dart @@ -0,0 +1,10 @@ +@Tags(['integration']) +library; + +import 'package:test/test.dart'; + +void main() { + test('never runs, --exclude-tags drops the integration tag', () { + fail('@Tags was dropped by the optimizer'); + }); +} diff --git a/e2e/test/commands/test/optimization_annotations/fixture/test/on_platform_test.dart b/e2e/test/commands/test/optimization_annotations/fixture/test/on_platform_test.dart new file mode 100644 index 000000000..6d1250028 --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/fixture/test/on_platform_test.dart @@ -0,0 +1,10 @@ +@OnPlatform({'vm': Skip('proves @OnPlatform reaches the optimized bundle')}) +library; + +import 'package:test/test.dart'; + +void main() { + test('never runs on the vm', () { + fail('@OnPlatform was dropped by the optimizer'); + }); +} diff --git a/e2e/test/commands/test/optimization_annotations/fixture/test/passing_test.dart b/e2e/test/commands/test/optimization_annotations/fixture/test/passing_test.dart new file mode 100644 index 000000000..f704a9ff6 --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/fixture/test/passing_test.dart @@ -0,0 +1,8 @@ +import 'package:test/test.dart'; +import 'package:test_api/hooks.dart'; + +void main() { + test('runs inside the optimized bundle', () { + expect(TestHandle.current.name, contains('passing_test.dart')); + }); +} diff --git a/e2e/test/commands/test/optimization_annotations/fixture/test/retry_test.dart b/e2e/test/commands/test/optimization_annotations/fixture/test/retry_test.dart new file mode 100644 index 000000000..cf0bd6f77 --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/fixture/test/retry_test.dart @@ -0,0 +1,13 @@ +@Retry(1) +library; + +import 'package:test/test.dart'; + +var _attempts = 0; + +void main() { + test('passes only on the retry', () { + _attempts++; + if (_attempts == 1) fail('@Retry was dropped by the optimizer'); + }); +} diff --git a/e2e/test/commands/test/optimization_annotations/fixture/test/skipped_test.dart b/e2e/test/commands/test/optimization_annotations/fixture/test/skipped_test.dart new file mode 100644 index 000000000..0c5925d17 --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/fixture/test/skipped_test.dart @@ -0,0 +1,10 @@ +@Skip('proves @Skip reaches the optimized bundle') +library; + +import 'package:test/test.dart'; + +void main() { + test('never runs', () { + fail('@Skip was dropped by the optimizer'); + }); +} diff --git a/e2e/test/commands/test/optimization_annotations/fixture/test/slow_test.dart b/e2e/test/commands/test/optimization_annotations/fixture/test/slow_test.dart new file mode 100644 index 000000000..a29c99686 --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/fixture/test/slow_test.dart @@ -0,0 +1,10 @@ +@Tags(['slow']) +library; + +import 'package:test/test.dart'; + +void main() { + test('never runs, dart_test.yaml skips the slow tag', () { + fail('@Tags was dropped by the optimizer'); + }); +} diff --git a/e2e/test/commands/test/optimization_annotations/flutter_fixture/pubspec.yaml b/e2e/test/commands/test/optimization_annotations/flutter_fixture/pubspec.yaml new file mode 100644 index 000000000..a4f2be04e --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/flutter_fixture/pubspec.yaml @@ -0,0 +1,15 @@ +name: optimization_annotations_flutter_fixture +description: Fixture for testing file level annotations in a Flutter bundle. +version: 0.1.0+1 +publish_to: none + +environment: + sdk: ^3.13.0 + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter diff --git a/e2e/test/commands/test/optimization_annotations/flutter_fixture/test/timeout_test.dart b/e2e/test/commands/test/optimization_annotations/flutter_fixture/test/timeout_test.dart new file mode 100644 index 000000000..e69199ecb --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/flutter_fixture/test/timeout_test.dart @@ -0,0 +1,10 @@ +@Timeout(Duration(milliseconds: 100)) +library; + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('outlives its file level timeout', () async { + await Future.delayed(const Duration(seconds: 2)); + }); +} diff --git a/e2e/test/commands/test/optimization_annotations/optimization_annotations_test.dart b/e2e/test/commands/test/optimization_annotations/optimization_annotations_test.dart new file mode 100644 index 000000000..5913c7e97 --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/optimization_annotations_test.dart @@ -0,0 +1,88 @@ +import 'package:mason/mason.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; +import 'package:universal_io/io.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + group('file level annotations', () { + Future setUpFixture(String name) async { + final tempDirectory = Directory.systemTemp.createTempSync(name); + addTearDown(() => tempDirectory.deleteSync(recursive: true)); + + final fixture = Directory( + path.join( + Directory.current.path, + 'test/commands/test/optimization_annotations/$name', + ), + ); + + await copyDirectory(fixture, tempDirectory); + + await expectSuccessfulProcessResult('flutter', [ + 'pub', + 'get', + ], workingDirectory: tempDirectory.path); + + final cwd = Directory.current; + Directory.current = tempDirectory; + addTearDown(() => Directory.current = cwd); + } + + test( + 'are honored by the optimized bundle', + timeout: const Timeout(Duration(minutes: 2)), + withRunner((commandRunner, logger, updater, logs, progressLogs) async { + await setUpFixture('fixture'); + await expectLater( + commandRunner.run(['test', '-x', 'integration']), + completion(equals(ExitCode.success.code)), + ); + }), + ); + + test( + 'apply a file level timeout to the optimized bundle', + timeout: const Timeout(Duration(minutes: 2)), + withRunner((commandRunner, logger, updater, logs, progressLogs) async { + await setUpFixture('timeout_fixture'); + + await expectLater( + commandRunner.run(['test']), + completion(equals(ExitCode.software.code)), + ); + + verify(() => logger.err(any(that: contains('Test timed out')))) + .called(greaterThanOrEqualTo(1)); + verify( + () => logger.err( + any(that: contains('outlives its file level timeout')), + ), + ).called(greaterThanOrEqualTo(1)); + }), + ); + + test( + 'apply a file level timeout to the optimized Flutter bundle', + timeout: const Timeout(Duration(minutes: 5)), + withRunner((commandRunner, logger, updater, logs, progressLogs) async { + await setUpFixture('flutter_fixture'); + + await expectLater( + commandRunner.run(['test']), + completion(equals(ExitCode.software.code)), + ); + + verify(() => logger.err(any(that: contains('Test timed out')))) + .called(greaterThanOrEqualTo(1)); + verify( + () => logger.err( + any(that: contains('outlives its file level timeout')), + ), + ).called(greaterThanOrEqualTo(1)); + }), + ); + }); +} diff --git a/e2e/test/commands/test/optimization_annotations/timeout_fixture/pubspec.yaml b/e2e/test/commands/test/optimization_annotations/timeout_fixture/pubspec.yaml new file mode 100644 index 000000000..b0378dd39 --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/timeout_fixture/pubspec.yaml @@ -0,0 +1,10 @@ +name: optimization_annotations_timeout_fixture +description: Fixture for testing a file level timeout. +version: 0.1.0+1 +publish_to: none + +environment: + sdk: ^3.13.0 + +dev_dependencies: + test: ^1.24.3 diff --git a/e2e/test/commands/test/optimization_annotations/timeout_fixture/test/timeout_test.dart b/e2e/test/commands/test/optimization_annotations/timeout_fixture/test/timeout_test.dart new file mode 100644 index 000000000..e8c926786 --- /dev/null +++ b/e2e/test/commands/test/optimization_annotations/timeout_fixture/test/timeout_test.dart @@ -0,0 +1,10 @@ +@Timeout(Duration(milliseconds: 100)) +library; + +import 'package:test/test.dart'; + +void main() { + test('outlives its file level timeout', () async { + await Future.delayed(const Duration(seconds: 2)); + }); +} diff --git a/e2e/test/commands/test/optimization_exclude/fixture/dart_test.yaml b/e2e/test/commands/test/optimization_exclude/fixture/dart_test.yaml deleted file mode 100644 index a6404d8f3..000000000 --- a/e2e/test/commands/test/optimization_exclude/fixture/dart_test.yaml +++ /dev/null @@ -1,2 +0,0 @@ -tags: - integration: diff --git a/e2e/test/commands/test/optimization_exclude/fixture/pubspec.yaml b/e2e/test/commands/test/optimization_exclude/fixture/pubspec.yaml index 6b5cee411..c4f6e3e9f 100644 --- a/e2e/test/commands/test/optimization_exclude/fixture/pubspec.yaml +++ b/e2e/test/commands/test/optimization_exclude/fixture/pubspec.yaml @@ -8,3 +8,4 @@ environment: dev_dependencies: test: ^1.24.3 + test_api: ^0.7.0 diff --git a/e2e/test/commands/test/optimization_exclude/fixture/test/integration/excluded_test.dart b/e2e/test/commands/test/optimization_exclude/fixture/test/integration/excluded_test.dart index 528f0ec06..9e32a65b0 100644 --- a/e2e/test/commands/test/optimization_exclude/fixture/test/integration/excluded_test.dart +++ b/e2e/test/commands/test/optimization_exclude/fixture/test/integration/excluded_test.dart @@ -1,12 +1,10 @@ -@Tags(['integration']) -library; - import 'package:test/test.dart'; +import 'package:test_api/hooks.dart'; void main() { group('optimization_exclude_fixture', () { - test('fails unless excluded, so a passing run proves it was', () { - fail('this test should have been excluded by its tag'); + test('runs as its own suite, outside the optimized bundle', () { + expect(TestHandle.current.name, isNot(contains('excluded_test.dart'))); }); }); } diff --git a/e2e/test/commands/test/optimization_exclude/optimization_exclude_test.dart b/e2e/test/commands/test/optimization_exclude/optimization_exclude_test.dart index 073ad0b47..aefd402e3 100644 --- a/e2e/test/commands/test/optimization_exclude/optimization_exclude_test.dart +++ b/e2e/test/commands/test/optimization_exclude/optimization_exclude_test.dart @@ -33,12 +33,12 @@ void main() { } test( - 'runs an excluded suite on its own, so its file-level tags are honored', + 'runs an excluded suite on its own, outside the optimized bundle', timeout: const Timeout(Duration(minutes: 2)), withRunner((commandRunner, logger, updater, logs, progressLogs) async { await setUpFixture('optimization_exclude'); await expectLater( - commandRunner.run(['test', '-x', 'integration']), + commandRunner.run(['test']), completion(equals(ExitCode.success.code)), ); }), @@ -52,8 +52,6 @@ void main() { await expectLater( commandRunner.run([ 'test', - '-x', - 'integration', '--exclude-optimization', 'test/nothing_here', ]), diff --git a/lib/src/cli/templates/test_optimizer_bundle.dart b/lib/src/cli/templates/test_optimizer_bundle.dart index 63eb98a44..899b874d3 100644 --- a/lib/src/cli/templates/test_optimizer_bundle.dart +++ b/lib/src/cli/templates/test_optimizer_bundle.dart @@ -8,7 +8,7 @@ final testOptimizerBundle = MasonBundle.fromJson({ "files": [ { "path": "test/.test_optimizer.dart", - "data": "Ly8gR0VORVJBVEVEIENPREUgLSBETyBOT1QgTU9ESUZZIEJZIEhBTkQKLy8gQ29uc2lkZXIgYWRkaW5nIHRoaXMgZmlsZSB0byB5b3VyIC5naXRpZ25vcmUuCgp7eyNpc0ZsdXR0ZXJ9fWltcG9ydCAnZGFydDppbyc7CmltcG9ydCAnZGFydDp0eXBlZF9kYXRhJzsKCmltcG9ydCAncGFja2FnZTpmbHV0dGVyX3Rlc3QvZmx1dHRlcl90ZXN0LmRhcnQnOwp7ey9pc0ZsdXR0ZXJ9fXt7XmlzRmx1dHRlcn19aW1wb3J0ICdwYWNrYWdlOnRlc3QvdGVzdC5kYXJ0Jzt7ey9pc0ZsdXR0ZXJ9fQoKe3sjdGVzdHN9fWltcG9ydCAne3t7cGF0aH19fScgYXMge3tpZGVudGlmaWVyfX07Cnt7L3Rlc3RzfX0Kdm9pZCBtYWluKCkgewp7eyNpc0ZsdXR0ZXJ9fSAgZ29sZGVuRmlsZUNvbXBhcmF0b3IgPSBfVGVzdE9wdGltaXphdGlvbkF3YXJlR29sZGVuRmlsZUNvbXBhcmF0b3IoZ29sZGVuRmlsZUNvbXBhcmF0b3IgYXMgTG9jYWxGaWxlQ29tcGFyYXRvcik7e3svaXNGbHV0dGVyfX0Ke3sjdGVzdHN9fSAgZ3JvdXAoJ3t7e3BhdGh9fX0nLCAoKSB7IHt7aWRlbnRpZmllcn19Lm1haW4oKTsgfSk7Cnt7L3Rlc3RzfX19Cgp7eyNpc0ZsdXR0ZXJ9fQpjbGFzcyBfVGVzdE9wdGltaXphdGlvbkF3YXJlR29sZGVuRmlsZUNvbXBhcmF0b3IgZXh0ZW5kcyBHb2xkZW5GaWxlQ29tcGFyYXRvciB7CiAgZmluYWwgTGlzdDxTdHJpbmc+IGdvbGRlbkZpbGVQYXRoczsKICBmaW5hbCBMb2NhbEZpbGVDb21wYXJhdG9yIHByZXZpb3VzR29sZGVuRmlsZUNvbXBhcmF0b3I7CgogIF9UZXN0T3B0aW1pemF0aW9uQXdhcmVHb2xkZW5GaWxlQ29tcGFyYXRvcih0aGlzLnByZXZpb3VzR29sZGVuRmlsZUNvbXBhcmF0b3IpCiAgICAgIDogZ29sZGVuRmlsZVBhdGhzID0gX2dvbGRlbkZpbGVQYXRoczsKCiAgc3RhdGljIExpc3Q8U3RyaW5nPiBnZXQgX2dvbGRlbkZpbGVQYXRocyA9PgogICAgICBEaXJlY3RvcnkuZnJvbVVyaSgoZ29sZGVuRmlsZUNvbXBhcmF0b3IgYXMgTG9jYWxGaWxlQ29tcGFyYXRvcikuYmFzZWRpcikKICAgICAgICAgIC5saXN0U3luYyhyZWN1cnNpdmU6IHRydWUsIGZvbGxvd0xpbmtzOiB0cnVlKQogICAgICAgICAgLndoZXJlVHlwZTxGaWxlPigpCiAgICAgICAgICAubWFwKChmaWxlKSA9PiBmaWxlLnBhdGgpCiAgICAgICAgICAud2hlcmUoKHBhdGgpID0+IHBhdGguZW5kc1dpdGgoJy5wbmcnKSkKICAgICAgICAgIC50b0xpc3QoKTsKICBAb3ZlcnJpZGUKICBGdXR1cmU8Ym9vbD4gY29tcGFyZShVaW50OExpc3QgaW1hZ2VCeXRlcywgVXJpIGdvbGRlbikgID0+IHByZXZpb3VzR29sZGVuRmlsZUNvbXBhcmF0b3IuY29tcGFyZShpbWFnZUJ5dGVzLCBnb2xkZW4pOwoKICBAb3ZlcnJpZGUKICBVcmkgZ2V0VGVzdFVyaShVcmkga2V5LCBpbnQ/IHZlcnNpb24pIHsKICAgIGZpbmFsIGtleVN0cmluZyA9IGtleS50b0ZpbGVQYXRoKCk7CiAgICByZXR1cm4gVXJpLnBhcnNlKGdvbGRlbkZpbGVQYXRocwogICAgICAgIC5zaW5nbGVXaGVyZSgoZ29sZGVuRmlsZVBhdGgpID0+IGdvbGRlbkZpbGVQYXRoLmVuZHNXaXRoKGtleVN0cmluZykpKTsKICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiB1cGRhdGUoVXJpIGdvbGRlbiwgVWludDhMaXN0IGltYWdlQnl0ZXMpID0+IHByZXZpb3VzR29sZGVuRmlsZUNvbXBhcmF0b3IudXBkYXRlKGdvbGRlbiwgaW1hZ2VCeXRlcyk7Cgp9Cnt7L2lzRmx1dHRlcn19", + "data": "Ly8gR0VORVJBVEVEIENPREUgLSBETyBOT1QgTU9ESUZZIEJZIEhBTkQKLy8gQ29uc2lkZXIgYWRkaW5nIHRoaXMgZmlsZSB0byB5b3VyIC5naXRpZ25vcmUuCgp7eyNpc0ZsdXR0ZXJ9fWltcG9ydCAnZGFydDppbyc7CmltcG9ydCAnZGFydDp0eXBlZF9kYXRhJzsKCmltcG9ydCAncGFja2FnZTpmbHV0dGVyX3Rlc3QvZmx1dHRlcl90ZXN0LmRhcnQnIGhpZGUgZ3JvdXA7CmltcG9ydCAncGFja2FnZTp0ZXN0X2FwaS9zY2FmZm9sZGluZy5kYXJ0JyBzaG93IGdyb3VwOwp7ey9pc0ZsdXR0ZXJ9fXt7XmlzRmx1dHRlcn19aW1wb3J0ICdwYWNrYWdlOnRlc3QvdGVzdC5kYXJ0Jzt7ey9pc0ZsdXR0ZXJ9fQoKe3sjdGVzdHN9fWltcG9ydCAne3t7cGF0aH19fScgYXMge3tpZGVudGlmaWVyfX07Cnt7L3Rlc3RzfX0Kdm9pZCBtYWluKCkgewp7eyNpc0ZsdXR0ZXJ9fSAgZ29sZGVuRmlsZUNvbXBhcmF0b3IgPSBfVGVzdE9wdGltaXphdGlvbkF3YXJlR29sZGVuRmlsZUNvbXBhcmF0b3IoZ29sZGVuRmlsZUNvbXBhcmF0b3IgYXMgTG9jYWxGaWxlQ29tcGFyYXRvcik7e3svaXNGbHV0dGVyfX0Ke3sjdGVzdHN9fSAgZ3JvdXAoJ3t7e3BhdGh9fX0nLCAoKSB7IHt7aWRlbnRpZmllcn19Lm1haW4oKTsgfXt7e2dyb3VwQXJndW1lbnRzfX19KTsKe3svdGVzdHN9fX0KCnt7I2lzRmx1dHRlcn19CmNsYXNzIF9UZXN0T3B0aW1pemF0aW9uQXdhcmVHb2xkZW5GaWxlQ29tcGFyYXRvciBleHRlbmRzIEdvbGRlbkZpbGVDb21wYXJhdG9yIHsKICBmaW5hbCBMaXN0PFN0cmluZz4gZ29sZGVuRmlsZVBhdGhzOwogIGZpbmFsIExvY2FsRmlsZUNvbXBhcmF0b3IgcHJldmlvdXNHb2xkZW5GaWxlQ29tcGFyYXRvcjsKCiAgX1Rlc3RPcHRpbWl6YXRpb25Bd2FyZUdvbGRlbkZpbGVDb21wYXJhdG9yKHRoaXMucHJldmlvdXNHb2xkZW5GaWxlQ29tcGFyYXRvcikKICAgICAgOiBnb2xkZW5GaWxlUGF0aHMgPSBfZ29sZGVuRmlsZVBhdGhzOwoKICBzdGF0aWMgTGlzdDxTdHJpbmc+IGdldCBfZ29sZGVuRmlsZVBhdGhzID0+CiAgICAgIERpcmVjdG9yeS5mcm9tVXJpKChnb2xkZW5GaWxlQ29tcGFyYXRvciBhcyBMb2NhbEZpbGVDb21wYXJhdG9yKS5iYXNlZGlyKQogICAgICAgICAgLmxpc3RTeW5jKHJlY3Vyc2l2ZTogdHJ1ZSwgZm9sbG93TGlua3M6IHRydWUpCiAgICAgICAgICAud2hlcmVUeXBlPEZpbGU+KCkKICAgICAgICAgIC5tYXAoKGZpbGUpID0+IGZpbGUucGF0aCkKICAgICAgICAgIC53aGVyZSgocGF0aCkgPT4gcGF0aC5lbmRzV2l0aCgnLnBuZycpKQogICAgICAgICAgLnRvTGlzdCgpOwogIEBvdmVycmlkZQogIEZ1dHVyZTxib29sPiBjb21wYXJlKFVpbnQ4TGlzdCBpbWFnZUJ5dGVzLCBVcmkgZ29sZGVuKSAgPT4gcHJldmlvdXNHb2xkZW5GaWxlQ29tcGFyYXRvci5jb21wYXJlKGltYWdlQnl0ZXMsIGdvbGRlbik7CgogIEBvdmVycmlkZQogIFVyaSBnZXRUZXN0VXJpKFVyaSBrZXksIGludD8gdmVyc2lvbikgewogICAgZmluYWwga2V5U3RyaW5nID0ga2V5LnRvRmlsZVBhdGgoKTsKICAgIHJldHVybiBVcmkucGFyc2UoZ29sZGVuRmlsZVBhdGhzCiAgICAgICAgLnNpbmdsZVdoZXJlKChnb2xkZW5GaWxlUGF0aCkgPT4gZ29sZGVuRmlsZVBhdGguZW5kc1dpdGgoa2V5U3RyaW5nKSkpOwogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHVwZGF0ZShVcmkgZ29sZGVuLCBVaW50OExpc3QgaW1hZ2VCeXRlcykgPT4gcHJldmlvdXNHb2xkZW5GaWxlQ29tcGFyYXRvci51cGRhdGUoZ29sZGVuLCBpbWFnZUJ5dGVzKTsKCn0Ke3svaXNGbHV0dGVyfX0=", "type": "text", }, ], @@ -20,7 +20,12 @@ final testOptimizerBundle = MasonBundle.fromJson({ }, { "path": "lib/pre_gen.dart", - "data": "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpob29rcy9kYXJ0X2lkZW50aWZpZXJfZ2VuZXJhdG9yLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bWFzb24vbWFzb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpwYXRoL3BhdGguZGFydCcgYXMgcGF0aDsKCnR5cGVkZWYgRXhpdEZuID0gTmV2ZXIgRnVuY3Rpb24oaW50IGNvZGUpOwoKRXhpdEZuIGV4aXRGbiA9IGV4aXQ7CgpTdHJpbmcgc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnID0gJ3NraXBfdmVyeV9nb29kX29wdGltaXphdGlvbic7ClJlZ0V4cCBza2lwVmVyeUdvb2RPcHRpbWl6YXRpb25SZWdFeHAgPSBSZWdFeHAoCiAgIkBUYWdzXFxzKlxcKFxccypcXFtbXFxzXFxTXSo/W1wiJ10kc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnW1wiJ11bXFxzXFxTXSo/XFxdXFxzKlxcKSIsCiAgbXVsdGlMaW5lOiB0cnVlLAopOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgZmluYWwgcGFja2FnZVJvb3QgPSBjb250ZXh0LnZhcnNbJ3BhY2thZ2Utcm9vdCddIGFzIFN0cmluZzsKICBmaW5hbCB0ZXN0RGlyID0gRGlyZWN0b3J5KHBhdGguam9pbihwYWNrYWdlUm9vdCwgJ3Rlc3QnKSk7CgogIGlmICghdGVzdERpci5leGlzdHNTeW5jKCkpIHsKICAgIGNvbnRleHQubG9nZ2VyLmVycignQ291bGQgbm90IGZpbmQgZGlyZWN0b3J5ICR7dGVzdERpci5wYXRofScpOwogICAgZXhpdEZuKDEpOwogIH0KCiAgZmluYWwgcHVic3BlYyA9IEZpbGUocGF0aC5qb2luKHBhY2thZ2VSb290LCAncHVic3BlYy55YW1sJykpOwogIGlmICghcHVic3BlYy5leGlzdHNTeW5jKCkpIHsKICAgIGNvbnRleHQubG9nZ2VyLmVycignQ291bGQgbm90IGZpbmQgcHVic3BlYy55YW1sIGF0ICR7dGVzdERpci5wYXRofScpOwogICAgZXhpdEZuKDEpOwogIH0KCiAgZmluYWwgcHVic3BlY0NvbnRlbnRzID0gYXdhaXQgcHVic3BlYy5yZWFkQXNTdHJpbmcoKTsKICBmaW5hbCBmbHV0dGVyU2RrUmVnRXhwID0gUmVnRXhwKHInc2RrOlxzKmZsdXR0ZXIkJywgbXVsdGlMaW5lOiB0cnVlKTsKICBmaW5hbCBpc0ZsdXR0ZXIgPSBmbHV0dGVyU2RrUmVnRXhwLmhhc01hdGNoKHB1YnNwZWNDb250ZW50cyk7CgogIGZpbmFsIGlkZW50aWZpZXJHZW5lcmF0b3IgPSBEYXJ0SWRlbnRpZmllckdlbmVyYXRvcigpOwogIGZpbmFsIHRlc3RJZGVudGlmaWVyVGFibGUgPSA8TWFwPFN0cmluZywgU3RyaW5nPj5bXTsKICBmaW5hbCB0ZXN0cyA9IHRlc3REaXIKICAgICAgLmxpc3RTeW5jKHJlY3Vyc2l2ZTogdHJ1ZSkKICAgICAgLndoZXJlKChlbnRpdHkpID0+IGVudGl0eS5pc1Rlc3QpOwoKICBmaW5hbCBub3RPcHRpbWl6ZWRUZXN0cyA9IGF3YWl0IGdldE5vdE9wdGltaXplZFRlc3RzKHRlc3RzLCB0ZXN0RGlyLnBhdGgpOwoKICBmb3IgKGZpbmFsIGVudGl0eSBpbiB0ZXN0cykgewogICAgZmluYWwgcmVsYXRpdmVQYXRoID0gcGF0aAogICAgICAgIC5yZWxhdGl2ZShlbnRpdHkucGF0aCwgZnJvbTogdGVzdERpci5wYXRoKQogICAgICAgIC5yZXBsYWNlQWxsKHInXCcsICcvJyk7CiAgICB0ZXN0SWRlbnRpZmllclRhYmxlLmFkZCh7CiAgICAgICdwYXRoJzogcmVsYXRpdmVQYXRoLAogICAgICAnaWRlbnRpZmllcic6IGlkZW50aWZpZXJHZW5lcmF0b3IubmV4dCgpLAogICAgfSk7CiAgfQoKICBmaW5hbCBvcHRpbWl6ZWRUZXN0c0lkZW50aWZpZXJUYWJsZSA9IHRlc3RJZGVudGlmaWVyVGFibGUKICAgICAgLndoZXJlKChlKSA9PiAhbm90T3B0aW1pemVkVGVzdHMuY29udGFpbnMoZVsncGF0aCddKSkKICAgICAgLnRvTGlzdCgpOwoKICBjb250ZXh0LnZhcnMgPSB7CiAgICAndGVzdHMnOiBvcHRpbWl6ZWRUZXN0c0lkZW50aWZpZXJUYWJsZSwKICAgICdpc0ZsdXR0ZXInOiBpc0ZsdXR0ZXIsCiAgICAnbm90T3B0aW1pemVkVGVzdHMnOiBub3RPcHRpbWl6ZWRUZXN0cywKICB9Owp9CgpleHRlbnNpb24gb24gRmlsZVN5c3RlbUVudGl0eSB7CiAgYm9vbCBnZXQgaXNUZXN0IHsKICAgIHJldHVybiB0aGlzIGlzIEZpbGUgJiYgcGF0aC5iYXNlbmFtZSh0aGlzLnBhdGgpLmVuZHNXaXRoKCdfdGVzdC5kYXJ0Jyk7CiAgfQp9CgpGdXR1cmU8TGlzdDxTdHJpbmc+PiBnZXROb3RPcHRpbWl6ZWRUZXN0cygKICBJdGVyYWJsZTxGaWxlU3lzdGVtRW50aXR5PiB0ZXN0cywKICBTdHJpbmcgdGVzdERpciwKKSBhc3luYyB7CiAgZmluYWwgcGF0aHMgPSB0ZXN0cy5tYXAoKGUpID0+IGUucGF0aCkudG9MaXN0KCk7CiAgZmluYWwgZm9ybWF0dGVkUGF0aHMgPSBwYXRocy5tYXAoKGUpID0+IGUucmVwbGFjZUFsbCgnLy4vJywgJy8nKSkudG9MaXN0KCk7CgogIGZpbmFsIGZpbGVGdXR1cmVzID0gZm9ybWF0dGVkUGF0aHMubWFwKF9jaGVja0ZpbGVGb3JTa2lwVmVyeUdvb2RPcHRpbWl6YXRpb24pOwogIGZpbmFsIGZpbGVSZXN1bHRzID0gYXdhaXQgRnV0dXJlLndhaXQoZmlsZUZ1dHVyZXMpOwoKICBmaW5hbCB0ZXN0V2l0aFZlcnlHb29kVGVzdCA9IDxTdHJpbmc+W107CiAgZm9yICh2YXIgaSA9IDA7IGkgPCBmb3JtYXR0ZWRQYXRocy5sZW5ndGg7IGkrKykgewogICAgaWYgKGZpbGVSZXN1bHRzW2ldKSB7CiAgICAgIHRlc3RXaXRoVmVyeUdvb2RUZXN0LmFkZChmb3JtYXR0ZWRQYXRoc1tpXSk7CiAgICB9CiAgfQoKICAvLy8gRm9ybWF0IHRvIHJlbGF0aXZlIHBhdGgKICBmaW5hbCByZWxhdGl2ZVBhdGhzID0gdGVzdFdpdGhWZXJ5R29vZFRlc3QKICAgICAgLm1hcCgoZSkgPT4gcGF0aC5yZWxhdGl2ZShlLCBmcm9tOiB0ZXN0RGlyKSkKICAgICAgLnRvTGlzdCgpOwoKICByZXR1cm4gcmVsYXRpdmVQYXRoczsKfQoKLy8vIENoZWNrIGlmIGEgc2luZ2xlIGZpbGUgY29udGFpbnMgc2tpcF92ZXJ5X2dvb2Rfb3B0aW1pemF0aW9uIHRhZwpGdXR1cmU8Ym9vbD4gX2NoZWNrRmlsZUZvclNraXBWZXJ5R29vZE9wdGltaXphdGlvbihTdHJpbmcgcGF0aCkgYXN5bmMgewogIGZpbmFsIGZpbGUgPSBGaWxlKHBhdGgpOwogIGlmICghZmlsZS5leGlzdHNTeW5jKCkpIHJldHVybiBmYWxzZTsKICBmaW5hbCBjb250ZW50ID0gYXdhaXQgZmlsZS5yZWFkQXNTdHJpbmcoKTsKICByZXR1cm4gc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uUmVnRXhwLmhhc01hdGNoKGNvbnRlbnQpOwp9Cg==", + "data": "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpob29rcy9kYXJ0X2lkZW50aWZpZXJfZ2VuZXJhdG9yLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6aG9va3MvdGVzdF9tZXRhZGF0YS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6cGF0aC9wYXRoLmRhcnQnIGFzIHBhdGg7Cgp0eXBlZGVmIEV4aXRGbiA9IE5ldmVyIEZ1bmN0aW9uKGludCBjb2RlKTsKCkV4aXRGbiBleGl0Rm4gPSBleGl0OwoKLy8vIFRoZSB0YWcgdGhhdCBvcHRzIGEgdGVzdCBmaWxlIG91dCBvZiB0aGUgb3B0aW1pemVkIGJ1bmRsZS4KY29uc3Qgc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnID0gJ3NraXBfdmVyeV9nb29kX29wdGltaXphdGlvbic7CgpleHRlbnNpb24gVGVzdE1ldGFkYXRhQnVuZGxlIG9uIFRlc3RNZXRhZGF0YSB7CiAgYm9vbCBnZXQgc2tpcHNPcHRpbWl6YXRpb24gPT4gdGFnTmFtZXMuY29udGFpbnMoc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnKTsKCiAgLy8vIFRoZSBuYW1lZCBhcmd1bWVudHMgdG8gYXBwZW5kIHRvIGEgYGdyb3VwYCBjYWxsLCBlYWNoIHByZWZpeGVkIGJ5IGAsIGAuCiAgU3RyaW5nIGdldCBncm91cEFyZ3VtZW50cyB7CiAgICByZXR1cm4gYXJndW1lbnRzLmVudHJpZXMubWFwKChlKSA9PiAnLCAke2Uua2V5fTogJHtlLnZhbHVlfScpLmpvaW4oKTsKICB9Cn0KCkZ1dHVyZTx2b2lkPiBydW4oSG9va0NvbnRleHQgY29udGV4dCkgYXN5bmMgewogIGZpbmFsIHBhY2thZ2VSb290ID0gY29udGV4dC52YXJzWydwYWNrYWdlLXJvb3QnXSBhcyBTdHJpbmc7CiAgZmluYWwgdGVzdERpciA9IERpcmVjdG9yeShwYXRoLmpvaW4ocGFja2FnZVJvb3QsICd0ZXN0JykpOwoKICBpZiAoIXRlc3REaXIuZXhpc3RzU3luYygpKSB7CiAgICBjb250ZXh0LmxvZ2dlci5lcnIoJ0NvdWxkIG5vdCBmaW5kIGRpcmVjdG9yeSAke3Rlc3REaXIucGF0aH0nKTsKICAgIGV4aXRGbigxKTsKICB9CgogIGZpbmFsIHB1YnNwZWMgPSBGaWxlKHBhdGguam9pbihwYWNrYWdlUm9vdCwgJ3B1YnNwZWMueWFtbCcpKTsKICBpZiAoIXB1YnNwZWMuZXhpc3RzU3luYygpKSB7CiAgICBjb250ZXh0LmxvZ2dlci5lcnIoJ0NvdWxkIG5vdCBmaW5kIHB1YnNwZWMueWFtbCBhdCAke3Rlc3REaXIucGF0aH0nKTsKICAgIGV4aXRGbigxKTsKICB9CgogIGZpbmFsIHB1YnNwZWNDb250ZW50cyA9IGF3YWl0IHB1YnNwZWMucmVhZEFzU3RyaW5nKCk7CiAgZmluYWwgZmx1dHRlclNka1JlZ0V4cCA9IFJlZ0V4cChyJ3NkazpccypmbHV0dGVyJCcsIG11bHRpTGluZTogdHJ1ZSk7CiAgZmluYWwgaXNGbHV0dGVyID0gZmx1dHRlclNka1JlZ0V4cC5oYXNNYXRjaChwdWJzcGVjQ29udGVudHMpOwoKICBmaW5hbCBpZGVudGlmaWVyR2VuZXJhdG9yID0gRGFydElkZW50aWZpZXJHZW5lcmF0b3IoKTsKICBmaW5hbCBvcHRpbWl6ZWRUZXN0cyA9IDxNYXA8U3RyaW5nLCBTdHJpbmc+PltdOwogIGZpbmFsIG5vdE9wdGltaXplZFRlc3RzID0gPFN0cmluZz5bXTsKCiAgZmluYWwgdGVzdHMgPSB0ZXN0RGlyCiAgICAgIC5saXN0U3luYyhyZWN1cnNpdmU6IHRydWUpCiAgICAgIC53aGVyZSgoZW50aXR5KSA9PiBlbnRpdHkuaXNUZXN0KQogICAgICAuY2FzdDxGaWxlPigpOwogIGZpbmFsIHBhcnNlZFRlc3RzID0gYXdhaXQgRnV0dXJlLndhaXQodGVzdHMubWFwKF9wYXJzZSkpOwoKICBmb3IgKGZpbmFsIChmaWxlLCBjb250ZW50LCBtZXRhZGF0YSkgaW4gcGFyc2VkVGVzdHMpIHsKICAgIGZpbmFsIHJlbGF0aXZlUGF0aCA9IHBhdGgKICAgICAgICAucmVsYXRpdmUoZmlsZS5wYXRoLCBmcm9tOiB0ZXN0RGlyLnBhdGgpCiAgICAgICAgLnJlcGxhY2VBbGwocidcJywgJy8nKTsKCiAgICBpZiAobWV0YWRhdGEuc2tpcHNPcHRpbWl6YXRpb24pIHsKICAgICAgbm90T3B0aW1pemVkVGVzdHMuYWRkKHJlbGF0aXZlUGF0aCk7CiAgICAgIGNvbnRpbnVlOwogICAgfQoKICAgIGlmIChjb250ZW50LmNvbnRhaW5zKHNraXBWZXJ5R29vZE9wdGltaXphdGlvblRhZykpIHsKICAgICAgY29udGV4dC5sb2dnZXIud2FybigKICAgICAgICAnJHJlbGF0aXZlUGF0aCBuYW1lcyAkc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnIGJ1dCB3YXMgb3B0aW1pemVkICcKICAgICAgICAnYW55d2F5OiBwYWNrYWdlOnRlc3QgcmVhZHMgQFRhZ3Mgb25seSBmcm9tIHRoZSBtZXRhZGF0YSBvZiB0aGUgJwogICAgICAgICJmaWxlJ3MgZmlyc3QgZGlyZWN0aXZlLiIsCiAgICAgICk7CiAgICB9CgogICAgZm9yIChmaW5hbCBhbm5vdGF0aW9uIGluIG1ldGFkYXRhLmRyb3BwZWRBbm5vdGF0aW9ucykgewogICAgICBjb250ZXh0LmxvZ2dlci53YXJuKAogICAgICAgICckcmVsYXRpdmVQYXRoOiBsZWZ0ICRhbm5vdGF0aW9uIG91dCBvZiB0aGUgb3B0aW1pemVkIGJ1bmRsZSwgd2hpY2ggJwogICAgICAgICdjYW5ub3QgcmVzb2x2ZSBldmVyeSBuYW1lIGl0IHJlZmVyZW5jZXMuJywKICAgICAgKTsKICAgIH0KCiAgICBvcHRpbWl6ZWRUZXN0cy5hZGQoewogICAgICAncGF0aCc6IHJlbGF0aXZlUGF0aCwKICAgICAgJ2lkZW50aWZpZXInOiBpZGVudGlmaWVyR2VuZXJhdG9yLm5leHQoKSwKICAgICAgJ2dyb3VwQXJndW1lbnRzJzogbWV0YWRhdGEuZ3JvdXBBcmd1bWVudHMsCiAgICB9KTsKICB9CgogIGNvbnRleHQudmFycyA9IHsKICAgICd0ZXN0cyc6IG9wdGltaXplZFRlc3RzLAogICAgJ2lzRmx1dHRlcic6IGlzRmx1dHRlciwKICAgICdub3RPcHRpbWl6ZWRUZXN0cyc6IG5vdE9wdGltaXplZFRlc3RzLAogIH07Cn0KCnR5cGVkZWYgX1BhcnNlZFRlc3QgPSAoRmlsZSBmaWxlLCBTdHJpbmcgY29udGVudCwgVGVzdE1ldGFkYXRhIG1ldGFkYXRhKTsKCkZ1dHVyZTxfUGFyc2VkVGVzdD4gX3BhcnNlKEZpbGUgZmlsZSkgYXN5bmMgewogIGZpbmFsIGNvbnRlbnQgPSBhd2FpdCBmaWxlLnJlYWRBc1N0cmluZygpOwogIHJldHVybiAoZmlsZSwgY29udGVudCwgcGFyc2VUZXN0TWV0YWRhdGEoY29udGVudCwgcGF0aDogZmlsZS5wYXRoKSk7Cn0KCmV4dGVuc2lvbiBvbiBGaWxlU3lzdGVtRW50aXR5IHsKICBib29sIGdldCBpc1Rlc3QgewogICAgcmV0dXJuIHRoaXMgaXMgRmlsZSAmJiBwYXRoLmJhc2VuYW1lKHRoaXMucGF0aCkuZW5kc1dpdGgoJ190ZXN0LmRhcnQnKTsKICB9Cn0K", + "type": "text", + }, + { + "path": "lib/test_metadata.dart", + "data": "aW1wb3J0ICdwYWNrYWdlOmFuYWx5emVyL2RhcnQvYW5hbHlzaXMvdXRpbGl0aWVzLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6YW5hbHl6ZXIvZGFydC9hc3QvYXN0LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6YW5hbHl6ZXIvZGFydC9hc3QvdmlzaXRvci5kYXJ0JzsKCi8vLyB7QHRlbXBsYXRlIHRlc3RfbWV0YWRhdGF9Ci8vLyBUaGUgbGlicmFyeSBsZXZlbCBhbm5vdGF0aW9ucyBgcGFja2FnZTp0ZXN0YCByZWFkcyBmcm9tIGEgdGVzdCBmaWxlLCBoZWxkCi8vLyBhcyBzb3VyY2UgdGV4dCBzbyB0aGV5IGNhbiBiZSBmb3J3YXJkZWQgdG8gdGhlIGBncm91cGAgdGhhdCB3cmFwcyB0aGUgZmlsZQovLy8gaW4gdGhlIG9wdGltaXplZCBidW5kbGUuCi8vLyB7QGVuZHRlbXBsYXRlfQpjbGFzcyBUZXN0TWV0YWRhdGEgewogIC8vLyB7QG1hY3JvIHRlc3RfbWV0YWRhdGF9CiAgY29uc3QgbmV3KHsKICAgIHRoaXMuYXJndW1lbnRzID0gY29uc3Qge30sCiAgICB0aGlzLnRhZ05hbWVzID0gY29uc3Qge30sCiAgICB0aGlzLmRyb3BwZWRBbm5vdGF0aW9ucyA9IGNvbnN0IFtdLAogIH0pOwoKICAvLy8gYGdyb3VwYCBhcmd1bWVudHMga2V5ZWQgYnkgcGFyYW1ldGVyIG5hbWUsIGluIGEgZml4ZWQgb3JkZXIgcmF0aGVyIHRoYW4KICAvLy8gdGhlIG9yZGVyIHRoZSBmaWxlIHdyb3RlIGl0cyBhbm5vdGF0aW9ucy4KICBmaW5hbCBNYXA8U3RyaW5nLCBTdHJpbmc+IGFyZ3VtZW50czsKCiAgLy8vIFRoZSBzdHJpbmcgbGl0ZXJhbCB0YWdzIG9mIGEgYEBUYWdzYCBhbm5vdGF0aW9uLCByZWFkIGV2ZW4gd2hlbiB0aGUKICAvLy8gYW5ub3RhdGlvbiBpdHNlbGYgd2FzIGRyb3BwZWQuCiAgZmluYWwgU2V0PFN0cmluZz4gdGFnTmFtZXM7CgogIC8vLyBUaGUgYW5ub3RhdGlvbnMgbGVmdCBvdXQgYmVjYXVzZSB0aGUgYnVuZGxlIGNhbm5vdCByZXNvbHZlIHRoZW0uCiAgZmluYWwgTGlzdDxTdHJpbmc+IGRyb3BwZWRBbm5vdGF0aW9uczsKfQoKLy8vIFRoZSBuYW1lcyBhbiBhbm5vdGF0aW9uIGFyZ3VtZW50IG1heSByZWZlcmVuY2UuCi8vLwovLy8gVGhlIGJ1bmRsZSBpbXBvcnRzIG9ubHkgYHBhY2thZ2U6dGVzdGAgKG9yIGBwYWNrYWdlOmZsdXR0ZXJfdGVzdGApIGFuZAovLy8gYGRhcnQ6Y29yZWAsIHNvIGFuIGFyZ3VtZW50IG5hbWluZyBhbnl0aGluZyBlbHNlIGRvZXMgbm90IHJlc29sdmUgdGhlcmUuCmNvbnN0IF9yZXNvbHZhYmxlTmFtZXMgPSB7CiAgJ0R1cmF0aW9uJywKICAnVGltZW91dCcsCiAgJ1NraXAnLAogICdmYWN0b3InLAogICdub25lJywKICAnemVybycsCn07CgovLy8gVGhlIGBncm91cGAgcGFyYW1ldGVyIGVhY2ggZm9yd2FyZGVkIGFubm90YXRpb24gc3VwcGxpZXMuCi8vLwovLy8gSXRlcmF0aW9uIG9yZGVyIGZpeGVzIHRoZSBvcmRlciBbVGVzdE1ldGFkYXRhLmFyZ3VtZW50c10gZW1pdHMuCmNvbnN0IF9ncm91cFBhcmFtZXRlcnMgPSB7CiAgJ1Rlc3RPbic6ICd0ZXN0T24nLAogICdUaW1lb3V0JzogJ3RpbWVvdXQnLAogICdTa2lwJzogJ3NraXAnLAogICdUYWdzJzogJ3RhZ3MnLAogICdPblBsYXRmb3JtJzogJ29uUGxhdGZvcm0nLAogICdSZXRyeSc6ICdyZXRyeScsCn07CgovLy8gUGFyc2VzIHRoZSBsaWJyYXJ5IGxldmVsIHRlc3QgYW5ub3RhdGlvbnMgb2YgW2NvbnRlbnRdLCB0aGUgc291cmNlIG9mIHRoZQovLy8gdGVzdCBmaWxlIGF0IFtwYXRoXS4gT25seSB0aGUgZmlyc3QgZGlyZWN0aXZlJ3MgbWV0YWRhdGEgaXMgcmVhZCwgdGhlIG9uZQovLy8gcGxhY2UgYHBhY2thZ2U6dGVzdGAgbG9va3MuCi8vLwovLy8gQW4gYW5ub3RhdGlvbiBuYW1pbmcgc29tZXRoaW5nIHRoZSBidW5kbGUgY2Fubm90IHJlc29sdmUsIGFzIGluCi8vLyBgQFRpbWVvdXQoa1Nsb3dTdWl0ZSlgLCBpcyByZWNvcmRlZCBpbiBbVGVzdE1ldGFkYXRhLmRyb3BwZWRBbm5vdGF0aW9uc10KLy8vIGluc3RlYWQgb2YgYmVpbmcgZm9yd2FyZGVkLCBzbyBvbmUgZmlsZSBjYW5ub3QgYnJlYWsgdGhlIHdob2xlIGJ1bmRsZS4KVGVzdE1ldGFkYXRhIHBhcnNlVGVzdE1ldGFkYXRhKFN0cmluZyBjb250ZW50LCB7U3RyaW5nPyBwYXRofSkgewogIGZpbmFsIHVuaXQgPSBwYXJzZVN0cmluZygKICAgIGNvbnRlbnQ6IGNvbnRlbnQsCiAgICBwYXRoOiBwYXRoLAogICAgdGhyb3dJZkRpYWdub3N0aWNzOiBmYWxzZSwKICApLnVuaXQ7CgogIGZpbmFsIGRpcmVjdGl2ZXMgPSB1bml0LmRpcmVjdGl2ZXM7CiAgaWYgKGRpcmVjdGl2ZXMuaXNFbXB0eSkgcmV0dXJuIGNvbnN0IFRlc3RNZXRhZGF0YSgpOwoKICBmaW5hbCBwcmVmaXhlcyA9IGRpcmVjdGl2ZXMKICAgICAgLndoZXJlVHlwZTxJbXBvcnREaXJlY3RpdmU+KCkKICAgICAgLm1hcCgoZGlyZWN0aXZlKSA9PiBkaXJlY3RpdmUucHJlZml4Py5uYW1lKQogICAgICAubm9uTnVsbHMKICAgICAgLnRvU2V0KCk7CgogIHZhciB0YWdOYW1lcyA9IGNvbnN0IDxTdHJpbmc+e307CiAgZmluYWwgZHJvcHBlZEFubm90YXRpb25zID0gPFN0cmluZz5bXTsKICBmaW5hbCBzb3VyY2VzID0gPFN0cmluZywgU3RyaW5nPnt9OwoKICBmb3IgKGZpbmFsIGFubm90YXRpb24gaW4gZGlyZWN0aXZlcy5maXJzdC5tZXRhZGF0YSkgewogICAgZmluYWwgKGNsYXNzTmFtZSwgY29uc3RydWN0b3JOYW1lKSA9IF9yZXNvbHZlQ29uc3RydWN0b3IoCiAgICAgIGFubm90YXRpb24ubmFtZSwKICAgICAgYW5ub3RhdGlvbi5jb25zdHJ1Y3Rvck5hbWUsCiAgICAgIHByZWZpeGVzLAogICAgKTsKICAgIGlmICghX2dyb3VwUGFyYW1ldGVycy5jb250YWluc0tleShjbGFzc05hbWUpKSBjb250aW51ZTsKCiAgICBmaW5hbCBhcmd1bWVudHMgPSBhbm5vdGF0aW9uLmFyZ3VtZW50czsKCiAgICBpZiAoY2xhc3NOYW1lID09ICdUYWdzJykgewogICAgICBmaW5hbCBhcmd1bWVudCA9IGFyZ3VtZW50cz8uYXJndW1lbnRzLmZpcnN0T3JOdWxsOwogICAgICBpZiAoYXJndW1lbnQgaXMgTGlzdExpdGVyYWwpIHsKICAgICAgICB0YWdOYW1lcyA9IGFyZ3VtZW50LmVsZW1lbnRzCiAgICAgICAgICAgIC53aGVyZVR5cGU8U3RyaW5nTGl0ZXJhbD4oKQogICAgICAgICAgICAubWFwKChlbGVtZW50KSA9PiBlbGVtZW50LnN0cmluZ1ZhbHVlKQogICAgICAgICAgICAubm9uTnVsbHMKICAgICAgICAgICAgLnRvU2V0KCk7CiAgICAgIH0KICAgIH0KCiAgICBpZiAoIV9pc1Jlc29sdmFibGUoYXJndW1lbnRzLCBjb25zdHJ1Y3Rvck5hbWUpKSB7CiAgICAgIGRyb3BwZWRBbm5vdGF0aW9ucy5hZGQoYW5ub3RhdGlvbi50b1NvdXJjZSgpKTsKICAgICAgY29udGludWU7CiAgICB9CgogICAgZmluYWwgc291cmNlID0gX2FyZ3VtZW50U291cmNlKGNsYXNzTmFtZSwgY29uc3RydWN0b3JOYW1lLCBhcmd1bWVudHMpOwogICAgaWYgKHNvdXJjZSAhPSBudWxsKSBzb3VyY2VzW2NsYXNzTmFtZV0gPSBzb3VyY2U7CiAgfQoKICByZXR1cm4gVGVzdE1ldGFkYXRhKAogICAgYXJndW1lbnRzOiB7CiAgICAgIGZvciAoZmluYWwgTWFwRW50cnkoa2V5OiBhbm5vdGF0aW9uLCB2YWx1ZTogcGFyYW1ldGVyKQogICAgICAgICAgaW4gX2dyb3VwUGFyYW1ldGVycy5lbnRyaWVzKQogICAgICAgIHBhcmFtZXRlcjogP3NvdXJjZXNbYW5ub3RhdGlvbl0sCiAgICB9LAogICAgdGFnTmFtZXM6IHRhZ05hbWVzLAogICAgZHJvcHBlZEFubm90YXRpb25zOiBkcm9wcGVkQW5ub3RhdGlvbnMsCiAgKTsKfQoKLy8vIFRoZSBzb3VyY2UgZm9yd2FyZGVkIGZvciBvbmUgYW5ub3RhdGlvbiwgb3IgYG51bGxgIHdoZW4gaXQgaGFzIG5vbmUsIGFzIGZvcgovLy8gYSBiYXJlIGBAVGltZW91dGAgb3IgYEBTa2lwYCB0aGF0IG5hbWVzIHRoZSB0eXBlIHJhdGhlciB0aGFuIGEgdmFsdWUuClN0cmluZz8gX2FyZ3VtZW50U291cmNlKAogIFN0cmluZyBjbGFzc05hbWUsCiAgU3RyaW5nPyBjb25zdHJ1Y3Rvck5hbWUsCiAgQXJndW1lbnRMaXN0PyBhcmd1bWVudHMsCikgewogIGZpbmFsIGFyZ3VtZW50ID0gYXJndW1lbnRzPy5hcmd1bWVudHMuZmlyc3RPck51bGw7CiAgcmV0dXJuIHN3aXRjaCAoY2xhc3NOYW1lKSB7CiAgICAnVGltZW91dCcgd2hlbiBhcmd1bWVudHMgPT0gbnVsbCAmJiBjb25zdHJ1Y3Rvck5hbWUgPT0gbnVsbCA9PiBudWxsLAogICAgJ1RpbWVvdXQnID0+CiAgICAgICdUaW1lb3V0JHtjb25zdHJ1Y3Rvck5hbWUgPT0gbnVsbCA/ICcnIDogJy4kY29uc3RydWN0b3JOYW1lJ30nCiAgICAgICAgICAnJHthcmd1bWVudHM/LnRvU291cmNlKCkgPz8gJyd9JywKICAgICdTa2lwJyB3aGVuIGFyZ3VtZW50cyA9PSBudWxsID0+IG51bGwsCiAgICAnU2tpcCcgPT4gYXJndW1lbnQ/LnRvU291cmNlKCkgPz8gJ3RydWUnLAogICAgXyA9PiBhcmd1bWVudD8udG9Tb3VyY2UoKSwKICB9Owp9CgovLy8gV2hldGhlciBldmVyeSBuYW1lIFthcmd1bWVudHNdIGFuZCBbY29uc3RydWN0b3JOYW1lXSByZWZlcmVuY2UgcmVzb2x2ZXMgaW4KLy8vIHRoZSBvcHRpbWl6ZWQgYnVuZGxlLgpib29sIF9pc1Jlc29sdmFibGUoQXJndW1lbnRMaXN0PyBhcmd1bWVudHMsIFN0cmluZz8gY29uc3RydWN0b3JOYW1lKSB7CiAgZmluYWwgcmVmZXJlbmNlZCA9IF9SZWZlcmVuY2VkTmFtZXMoKTsKICBhcmd1bWVudHM/LmFjY2VwdChyZWZlcmVuY2VkKTsKICByZXR1cm4gWwogICAgP2NvbnN0cnVjdG9yTmFtZSwKICAgIC4uLnJlZmVyZW5jZWQubmFtZXMsCiAgXS5ldmVyeShfcmVzb2x2YWJsZU5hbWVzLmNvbnRhaW5zKTsKfQoKLy8vIENvbGxlY3RzIGV2ZXJ5IG5hbWUgYW4gZXhwcmVzc2lvbiByZWZlcmVuY2VzLgovLy8KLy8vIE5hbWVkIGFyZ3VtZW50IGxhYmVscyBhcmUgdG9rZW5zIHJhdGhlciB0aGFuIGlkZW50aWZpZXJzLCBzbyBgbWludXRlc2AgaW4KLy8vIGBEdXJhdGlvbihtaW51dGVzOiA1KWAgaXMgbm90IGNvbGxlY3RlZCwgd2hpbGUgYER1cmF0aW9uYCBpcy4KY2xhc3MgX1JlZmVyZW5jZWROYW1lcyBleHRlbmRzIFJlY3Vyc2l2ZUFzdFZpc2l0b3I8dm9pZD4gewogIGZpbmFsIG5hbWVzID0gPFN0cmluZz5bXTsKCiAgQG92ZXJyaWRlCiAgdm9pZCB2aXNpdFNpbXBsZUlkZW50aWZpZXIoU2ltcGxlSWRlbnRpZmllciBub2RlKSA9PiBuYW1lcy5hZGQobm9kZS5uYW1lKTsKCiAgQG92ZXJyaWRlCiAgdm9pZCB2aXNpdE5hbWVkVHlwZShOYW1lZFR5cGUgbm9kZSkgewogICAgbmFtZXMuYWRkKG5vZGUubmFtZS5sZXhlbWUpOwogICAgc3VwZXIudmlzaXROYW1lZFR5cGUobm9kZSk7CiAgfQp9CgovLy8gUmVzb2x2ZXMgdGhlIGNsYXNzIGFuZCBuYW1lZCBjb25zdHJ1Y3RvciBhbiBhbm5vdGF0aW9uIHJlZmVycyB0by4KLy8vCi8vLyBUaGUgc3ludGF4IGlzIGFtYmlndW91cywgc28gW3ByZWZpeGVzXSBkaXNhbWJpZ3VhdGVzOiBgQFRpbWVvdXQubm9uZWAgbmFtZXMKLy8vIGEgY29uc3RydWN0b3IsIHdoaWxlIGBAdGVzdC5Ta2lwKClgIG5hbWVzIGBTa2lwYCBiZWhpbmQgdGhlIGB0ZXN0YCBwcmVmaXguCihTdHJpbmcsIFN0cmluZz8pIF9yZXNvbHZlQ29uc3RydWN0b3IoCiAgSWRlbnRpZmllciBuYW1lLAogIFNpbXBsZUlkZW50aWZpZXI/IGNvbnN0cnVjdG9yTmFtZSwKICBTZXQ8U3RyaW5nPiBwcmVmaXhlcywKKSB7CiAgaWYgKG5hbWUgaXMgUHJlZml4ZWRJZGVudGlmaWVyICYmCiAgICAgICFwcmVmaXhlcy5jb250YWlucyhuYW1lLnByZWZpeC5uYW1lKSAmJgogICAgICBjb25zdHJ1Y3Rvck5hbWUgPT0gbnVsbCkgewogICAgcmV0dXJuIChuYW1lLnByZWZpeC5uYW1lLCBuYW1lLmlkZW50aWZpZXIubmFtZSk7CiAgfQoKICBmaW5hbCBjbGFzc05hbWUgPSBuYW1lIGlzIFByZWZpeGVkSWRlbnRpZmllcgogICAgICA/IG5hbWUuaWRlbnRpZmllci5uYW1lCiAgICAgIDogbmFtZS5uYW1lOwogIHJldHVybiAoY2xhc3NOYW1lLCBjb25zdHJ1Y3Rvck5hbWU/Lm5hbWUpOwp9Cg==", "type": "text", }, { @@ -30,7 +35,7 @@ final testOptimizerBundle = MasonBundle.fromJson({ }, { "path": "pubspec.yaml", - "data": "bmFtZTogaG9va3MKcHVibGlzaF90bzogbm9uZQoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy4xMy4wCgpkZXBlbmRlbmNpZXM6CiAgbWFzb246IF4wLjEuMAogIHBhdGg6IF4xLjguMQoKIyBCZXdhcmU6IG9uIGhvb2tzLCBldmVuIGRldiBkZXBlbmRlbmNpZXMgaGF2ZSB0byBiZSBjb21wYXRpYmxlIHRvIGFsbCBkYXJ0IHZlcnNpb25zIGNvdmVyZWQgYnkKIyB0aGUgc2RrIGNvbnN0cmFpbnRzIGFib3ZlCmRldl9kZXBlbmRlbmNpZXM6CiAgbW9ja3RhaWw6IF4xLjAuMAogIHRlc3Q6IF4xLjI1LjAKICB2ZXJ5X2dvb2RfYW5hbHlzaXM6IF4xMS4wLjAK", + "data": "bmFtZTogaG9va3MKcHVibGlzaF90bzogbm9uZQoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy4xMy4wCgpkZXBlbmRlbmNpZXM6CiAgYW5hbHl6ZXI6IF4xNC4wLjAKICBtYXNvbjogXjAuMS4wCiAgcGF0aDogXjEuOC4xCgojIEJld2FyZTogb24gaG9va3MsIGV2ZW4gZGV2IGRlcGVuZGVuY2llcyBoYXZlIHRvIGJlIGNvbXBhdGlibGUgdG8gYWxsIGRhcnQgdmVyc2lvbnMgY292ZXJlZCBieQojIHRoZSBzZGsgY29uc3RyYWludHMgYWJvdmUKZGV2X2RlcGVuZGVuY2llczoKICBtb2NrdGFpbDogXjEuMC4wCiAgdGVzdDogXjEuMjUuMAogIHZlcnlfZ29vZF9hbmFseXNpczogXjExLjAuMAo=", "type": "text", }, { @@ -40,7 +45,12 @@ final testOptimizerBundle = MasonBundle.fromJson({ }, { "path": "test/pre_gen_test.dart", - "data": "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpob29rcy9wcmVfZ2VuLmRhcnQnIGFzIHByZV9nZW47CmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm1vY2t0YWlsL21vY2t0YWlsLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6cGF0aC9wYXRoLmRhcnQnIGFzIHBhdGg7CmltcG9ydCAncGFja2FnZTp0ZXN0L3Rlc3QuZGFydCc7CgpjbGFzcyBfTW9ja0xvZ2dlciBleHRlbmRzIE1vY2sgaW1wbGVtZW50cyBMb2dnZXI7CgpjbGFzcyBfRmFrZUNvbnRleHQgZXh0ZW5kcyBGYWtlIGltcGxlbWVudHMgSG9va0NvbnRleHQgewogIEBvdmVycmlkZQogIGZpbmFsIGxvZ2dlciA9IF9Nb2NrTG9nZ2VyKCk7CgogIEBvdmVycmlkZQogIE1hcDxTdHJpbmcsIE9iamVjdD8+IHZhcnMgPSB7fTsKfQoKZmluYWwgbm90T3B0aW1pemVkVGVzdENvbnRlbnQgPQogICAgJycnCkBUYWdzKFsnJHtwcmVfZ2VuLnNraXBWZXJ5R29vZE9wdGltaXphdGlvblRhZ30nXSkKdm9pZCBtYWluKCkgewogIHRlc3QoJ3Rlc3QnLCAoKSB7CiAgICBleHBlY3QoMSwgMSk7CiAgfSk7Cn0KJycnOwoKZmluYWwgYW5vdGhlck5vdE9wdGltaXplZFRlc3RDb250ZW50ID0KICAgICcnJwpAVGFncyhbJyR7cHJlX2dlbi5za2lwVmVyeUdvb2RPcHRpbWl6YXRpb25UYWd9JywgJ2Fub3RoZXJfdGFnJ10pCnZvaWQgbWFpbigpIHsKICB0ZXN0KCdhbm90aGVyIHRlc3QnLCAoKSB7CiAgICBleHBlY3QoMSwgMSk7CiAgfSk7Cn0KJycnOwoKdm9pZCBtYWluKCkgewogIGxhdGUgRGlyZWN0b3J5IHRlbXBEaXJlY3Rvcnk7CgogIHNldFVwKCgpIHsKICAgIHRlbXBEaXJlY3RvcnkgPSBEaXJlY3Rvcnkuc3lzdGVtVGVtcC5jcmVhdGVUZW1wU3luYygndGVzdF9vcHRpbWl6ZXInKTsKICB9KTsKCiAgdGVhckRvd24oKCkgewogICAgdGVtcERpcmVjdG9yeS5kZWxldGVTeW5jKHJlY3Vyc2l2ZTogdHJ1ZSk7CiAgfSk7CgogIGdyb3VwKCdQcmUgZ2VuIGhvb2snLCAoKSB7CiAgICBsYXRlIEhvb2tDb250ZXh0IGNvbnRleHQ7CgogICAgc2V0VXAoKCkgewogICAgICBjb250ZXh0ID0gX0Zha2VDb250ZXh0KCk7CiAgICB9KTsKCiAgICBncm91cCgnQ29tcGxldGVzJywgKCkgewogICAgICB0ZXN0KCd3aXRoIHRlc3QgZmlsZXMgbGlzdCcsICgpIGFzeW5jIHsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICdwdWJzcGVjLnlhbWwnKSkuY3JlYXRlU3luYygpOwoKICAgICAgICBmaW5hbCB0ZXN0RGlyID0gRGlyZWN0b3J5KHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICd0ZXN0JykpCiAgICAgICAgICAuLmNyZWF0ZVN5bmMoKTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICd0ZXN0MV90ZXN0LmRhcnQnKSkuY3JlYXRlU3luYygpOwogICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ3Rlc3QyX3Rlc3QuZGFydCcpKS5jcmVhdGVTeW5jKCk7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVzdERpci5wYXRoLCAnbm9fdGVzdF9oZXJlLmRhcnQnKSkuY3JlYXRlU3luYygpOwoKICAgICAgICBjb250ZXh0LnZhcnNbJ3BhY2thZ2Utcm9vdCddID0gdGVtcERpcmVjdG9yeS5hYnNvbHV0ZS5wYXRoOwoKICAgICAgICBhd2FpdCBwcmVfZ2VuLnJ1bihjb250ZXh0KTsKCiAgICAgICAgZmluYWwgdGVzdHMgPSBjb250ZXh0LnZhcnNbJ3Rlc3RzJ10gYXMgTGlzdDxNYXA8U3RyaW5nLCBTdHJpbmc+PjsKICAgICAgICBmaW5hbCB0ZXN0c01hcCA9IDxTdHJpbmcsIFN0cmluZz57fTsKICAgICAgICBmb3IgKGZpbmFsIHRlc3QgaW4gdGVzdHMpIHsKICAgICAgICAgIGZpbmFsIHBhdGggPSB0ZXN0WydwYXRoJ10hOwogICAgICAgICAgZmluYWwgaWRlbnRpZmllciA9IHRlc3RbJ2lkZW50aWZpZXInXSE7CiAgICAgICAgICB0ZXN0c01hcFtwYXRoXSA9IGlkZW50aWZpZXI7CiAgICAgICAgfQoKICAgICAgICBmaW5hbCBwYXRocyA9IHRlc3RzTWFwLmtleXM7CiAgICAgICAgZXhwZWN0KHBhdGhzLCBjb250YWlucygndGVzdDFfdGVzdC5kYXJ0JykpOwogICAgICAgIGV4cGVjdChwYXRocywgY29udGFpbnMoJ3Rlc3QyX3Rlc3QuZGFydCcpKTsKICAgICAgICBleHBlY3QocGF0aHMsIGlzTm90KGNvbnRhaW5zKCdub190ZXN0X2hlcmUuZGFydCcpKSk7CgogICAgICAgIGV4cGVjdCgKICAgICAgICAgIHRlc3RzTWFwLnZhbHVlcy50b1NldCgpLmxlbmd0aCwKICAgICAgICAgIGVxdWFscyh0ZXN0cy5sZW5ndGgpLAogICAgICAgICAgcmVhc29uOiAnQWxsIHRlc3RzIGZpbGVzIHNob3VsZCBoYXZlIHVuaXF1ZSBpZGVudGlmaWVycycsCiAgICAgICAgKTsKCiAgICAgICAgZXhwZWN0KGNvbnRleHQudmFyc1snaXNGbHV0dGVyJ10sIGZhbHNlKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCd3aXRoIHByb3BlciBpc0ZsdXR0ZXIgaWRlbnRpZmljYXRpb24nLCAoKSBhc3luYyB7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVtcERpcmVjdG9yeS5wYXRoLCAncHVic3BlYy55YW1sJykpCiAgICAgICAgICAuLmNyZWF0ZVN5bmMoKQogICAgICAgICAgLi53cml0ZUFzU3RyaW5nU3luYygnJycKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXInJycpOwoKICAgICAgICBEaXJlY3RvcnkocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3Rlc3QnKSkuY3JlYXRlU3luYygpOwoKICAgICAgICBjb250ZXh0LnZhcnNbJ3BhY2thZ2Utcm9vdCddID0gdGVtcERpcmVjdG9yeS5hYnNvbHV0ZS5wYXRoOwoKICAgICAgICBhd2FpdCBwcmVfZ2VuLnJ1bihjb250ZXh0KTsKCiAgICAgICAgZXhwZWN0KGNvbnRleHQudmFyc1snaXNGbHV0dGVyJ10sIHRydWUpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ3dpdGggcHJvcGVyIG5vdCBvcHRpbWl6ZWQgdGVzdHMgaWRlbnRpZmljYXRpb24nLCAoKSBhc3luYyB7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVtcERpcmVjdG9yeS5wYXRoLCAncHVic3BlYy55YW1sJykpLmNyZWF0ZVN5bmMoKTsKCiAgICAgICAgZmluYWwgdGVzdERpciA9IERpcmVjdG9yeShwYXRoLmpvaW4odGVtcERpcmVjdG9yeS5wYXRoLCAndGVzdCcpKQogICAgICAgICAgLi5jcmVhdGVTeW5jKCk7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVzdERpci5wYXRoLCAndGVzdDFfdGVzdC5kYXJ0JykpLmNyZWF0ZVN5bmMoKTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICd0ZXN0Ml90ZXN0LmRhcnQnKSkuY3JlYXRlU3luYygpOwogICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ25vX3Rlc3RfaGVyZS5kYXJ0JykpLmNyZWF0ZVN5bmMoKTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICdub3Rfb3B0aW1pemVkX3Rlc3QuZGFydCcpKQogICAgICAgICAgICAud3JpdGVBc1N0cmluZ1N5bmMobm90T3B0aW1pemVkVGVzdENvbnRlbnQpOwogICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ2Fub3RoZXJfbm90X29wdGltaXplZF90ZXN0LmRhcnQnKSkKICAgICAgICAgICAgLndyaXRlQXNTdHJpbmdTeW5jKGFub3RoZXJOb3RPcHRpbWl6ZWRUZXN0Q29udGVudCk7CgogICAgICAgIGNvbnRleHQudmFyc1sncGFja2FnZS1yb290J10gPSB0ZW1wRGlyZWN0b3J5LmFic29sdXRlLnBhdGg7CgogICAgICAgIGF3YWl0IHByZV9nZW4ucnVuKGNvbnRleHQpOwoKICAgICAgICBmaW5hbCB0ZXN0cyA9IGNvbnRleHQudmFyc1sndGVzdHMnXSBhcyBMaXN0PE1hcDxTdHJpbmcsIFN0cmluZz4+OwogICAgICAgIGZpbmFsIHRlc3RzTWFwID0gPFN0cmluZywgU3RyaW5nPnt9OwogICAgICAgIGZvciAoZmluYWwgdGVzdCBpbiB0ZXN0cykgewogICAgICAgICAgZmluYWwgcGF0aCA9IHRlc3RbJ3BhdGgnXSE7CiAgICAgICAgICBmaW5hbCBpZGVudGlmaWVyID0gdGVzdFsnaWRlbnRpZmllciddITsKICAgICAgICAgIHRlc3RzTWFwW3BhdGhdID0gaWRlbnRpZmllcjsKICAgICAgICB9CgogICAgICAgIGZpbmFsIHBhdGhzID0gdGVzdHNNYXAua2V5czsKICAgICAgICBleHBlY3QocGF0aHMsIGNvbnRhaW5zKCd0ZXN0MV90ZXN0LmRhcnQnKSk7CiAgICAgICAgZXhwZWN0KHBhdGhzLCBjb250YWlucygndGVzdDJfdGVzdC5kYXJ0JykpOwogICAgICAgIGV4cGVjdChwYXRocywgaXNOb3QoY29udGFpbnMoJ25vX3Rlc3RfaGVyZS5kYXJ0JykpKTsKICAgICAgICBleHBlY3QocGF0aHMsIGlzTm90KGNvbnRhaW5zKCdub3Rfb3B0aW1pemVkX3Rlc3QuZGFydCcpKSk7CiAgICAgICAgZXhwZWN0KHBhdGhzLCBpc05vdChjb250YWlucygnYW5vdGhlcl9ub3Rfb3B0aW1pemVkX3Rlc3QuZGFydCcpKSk7CgogICAgICAgIGV4cGVjdCgKICAgICAgICAgIHRlc3RzTWFwLnZhbHVlcy50b1NldCgpLmxlbmd0aCwKICAgICAgICAgIGVxdWFscyh0ZXN0cy5sZW5ndGgpLAogICAgICAgICAgcmVhc29uOiAnQWxsIHRlc3RzIGZpbGVzIHNob3VsZCBoYXZlIHVuaXF1ZSBpZGVudGlmaWVycycsCiAgICAgICAgKTsKICAgICAgICBmaW5hbCBub3RPcHRpbWl6ZWRUZXN0cyA9CiAgICAgICAgICAgIGNvbnRleHQudmFyc1snbm90T3B0aW1pemVkVGVzdHMnXSBhcyBMaXN0PFN0cmluZz47CiAgICAgICAgZXhwZWN0KG5vdE9wdGltaXplZFRlc3RzLCBjb250YWlucygnbm90X29wdGltaXplZF90ZXN0LmRhcnQnKSk7CiAgICAgICAgZXhwZWN0KG5vdE9wdGltaXplZFRlc3RzLCBjb250YWlucygnYW5vdGhlcl9ub3Rfb3B0aW1pemVkX3Rlc3QuZGFydCcpKTsKICAgICAgfSk7CiAgICB9KTsKCiAgICBncm91cCgnRmFpbHMnLCAoKSB7CiAgICAgIHNldFVwKCgpIHsKICAgICAgICBwcmVfZ2VuLmV4aXRGbiA9IChjb2RlKSB7CiAgICAgICAgICB0aHJvdyBQcm9jZXNzRXhjZXB0aW9uKCdleGl0JywgW2NvZGUudG9TdHJpbmcoKV0pOwogICAgICAgIH07CiAgICAgIH0pOwoKICAgICAgdGVhckRvd24oKCkgewogICAgICAgIHByZV9nZW4uZXhpdEZuID0gZXhpdDsKICAgICAgfSk7CgogICAgICB0ZXN0KCd3aGVuIHRhcmdldCB0ZXN0IGRpciBkb2VzIG5vdCBleGlzdCcsICgpIGFzeW5jIHsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICdwdWJzcGVjLnlhbWwnKSkuY3JlYXRlU3luYygpOwoKICAgICAgICBmaW5hbCB0ZXN0RGlyID0gRGlyZWN0b3J5KHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICd0ZXN0JykpOwoKICAgICAgICBjb250ZXh0LnZhcnNbJ3BhY2thZ2Utcm9vdCddID0gdGVtcERpcmVjdG9yeS5hYnNvbHV0ZS5wYXRoOwoKICAgICAgICBhd2FpdCBleHBlY3RMYXRlcigKICAgICAgICAgICgpID0+IHByZV9nZW4ucnVuKGNvbnRleHQpLAogICAgICAgICAgdGhyb3dzQSgKICAgICAgICAgICAgaXNBPFByb2Nlc3NFeGNlcHRpb24+KCkuaGF2aW5nKAogICAgICAgICAgICAgIChleCkgPT4gZXguYXJndW1lbnRzLmZpcnN0LAogICAgICAgICAgICAgICdlcnJvciBjb2RlJywKICAgICAgICAgICAgICBlcXVhbHMoJzEnKSwKICAgICAgICAgICAgKSwKICAgICAgICAgICksCiAgICAgICAgKTsKCiAgICAgICAgdmVyaWZ5KAogICAgICAgICAgKCkgPT4gY29udGV4dC5sb2dnZXIuZXJyKCdDb3VsZCBub3QgZmluZCBkaXJlY3RvcnkgJHt0ZXN0RGlyLnBhdGh9JyksCiAgICAgICAgKS5jYWxsZWQoMSk7CgogICAgICAgIGV4cGVjdChjb250ZXh0LnZhcnNbJ3Rlc3RzJ10sIGlzTnVsbCk7CiAgICAgICAgZXhwZWN0KGNvbnRleHQudmFyc1snaXNGbHV0dGVyJ10sIGlzTnVsbCk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnd2hlbiB0YXJnZXQgZGlyIGRvZXMgbm90IGNvbnRhaW4gYSBwdWJzcGVjLnlhbWwnLCAoKSBhc3luYyB7CiAgICAgICAgZmluYWwgdGVzdERpciA9IERpcmVjdG9yeShwYXRoLmpvaW4odGVtcERpcmVjdG9yeS5wYXRoLCAndGVzdCcpKQogICAgICAgICAgLi5jcmVhdGVTeW5jKCk7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVzdERpci5wYXRoLCAndGVzdDFfdGVzdC5kYXJ0JykpLmNyZWF0ZVN5bmMoKTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICd0ZXN0Ml90ZXN0LmRhcnQnKSkuY3JlYXRlU3luYygpOwogICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ25vX3Rlc3RfaGVyZS5kYXJ0JykpLmNyZWF0ZVN5bmMoKTsKCiAgICAgICAgY29udGV4dC52YXJzWydwYWNrYWdlLXJvb3QnXSA9IHRlbXBEaXJlY3RvcnkuYWJzb2x1dGUucGF0aDsKCiAgICAgICAgYXdhaXQgZXhwZWN0TGF0ZXIoCiAgICAgICAgICAoKSA9PiBwcmVfZ2VuLnJ1bihjb250ZXh0KSwKICAgICAgICAgIHRocm93c0EoCiAgICAgICAgICAgIGlzQTxQcm9jZXNzRXhjZXB0aW9uPigpLmhhdmluZygKICAgICAgICAgICAgICAoZXgpID0+IGV4LmFyZ3VtZW50cy5maXJzdCwKICAgICAgICAgICAgICAnZXJyb3IgY29kZScsCiAgICAgICAgICAgICAgZXF1YWxzKCcxJyksCiAgICAgICAgICAgICksCiAgICAgICAgICApLAogICAgICAgICk7CgogICAgICAgIHZlcmlmeSgKICAgICAgICAgICgpID0+IGNvbnRleHQubG9nZ2VyLmVycigKICAgICAgICAgICAgJ0NvdWxkIG5vdCBmaW5kIHB1YnNwZWMueWFtbCBhdCAke3Rlc3REaXIucGF0aH0nLAogICAgICAgICAgKSwKICAgICAgICApLmNhbGxlZCgxKTsKCiAgICAgICAgZXhwZWN0KGNvbnRleHQudmFyc1sndGVzdHMnXSwgaXNOdWxsKTsKICAgICAgICBleHBlY3QoY29udGV4dC52YXJzWydpc0ZsdXR0ZXInXSwgaXNOdWxsKTsKICAgICAgfSk7CiAgICB9KTsKCiAgICBncm91cCgnc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uUmVnRXhwIHJlZ2V4JywgKCkgewogICAgICBmaW5hbCByZWdleCA9IHByZV9nZW4uc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uUmVnRXhwOwogICAgICB0ZXN0KCdtYXRjaGVzIHNpbmdsZS1saW5lIHRhZycsICgpIHsKICAgICAgICBmaW5hbCBjb250ZW50ID0gIkBUYWdzKFsnJHtwcmVfZ2VuLnNraXBWZXJ5R29vZE9wdGltaXphdGlvblRhZ30nXSkiOwogICAgICAgIGV4cGVjdChyZWdleC5oYXNNYXRjaChjb250ZW50KSwgaXNUcnVlKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdtYXRjaGVzIHNpbmdsZS1saW5lIHdpdGggbXVsdGlwbGUgdGFncycsICgpIHsKICAgICAgICBmaW5hbCBjb250ZW50ID0KICAgICAgICAgICAgIkBUYWdzKFsnJHtwcmVfZ2VuLnNraXBWZXJ5R29vZE9wdGltaXphdGlvblRhZ30nLCAnY2hyb21lJ10pIjsKICAgICAgICBleHBlY3QocmVnZXguaGFzTWF0Y2goY29udGVudCksIGlzVHJ1ZSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnbWF0Y2hlcyBtdWx0aS1saW5lIHRhZyBsaXN0JywgKCkgewogICAgICAgIGZpbmFsIGNvbnRlbnQgPQogICAgICAgICAgICAnJycKICAgICAgQFRhZ3MoWwogICAgICAgICcke3ByZV9nZW4uc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnfScsCiAgICAgICAgJ2Nocm9tZScsCiAgICAgICAgJ3Rlc3QnLAogICAgICBdKQogICAgICAnJyc7CiAgICAgICAgZXhwZWN0KHJlZ2V4Lmhhc01hdGNoKGNvbnRlbnQpLCBpc1RydWUpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ21hdGNoZXMgbXVsdGktbGluZSB3aGVyZSB0YWcgaXMgbm90IHRoZSBmaXJzdCcsICgpIHsKICAgICAgICBmaW5hbCBjb250ZW50ID0KICAgICAgICAgICAgJycnCiAgICAgIEBUYWdzKFsKICAgICAgICAnY2hyb21lJywKICAgICAgICAnJHtwcmVfZ2VuLnNraXBWZXJ5R29vZE9wdGltaXphdGlvblRhZ30nLAogICAgICAgICd0ZXN0JywKICAgICAgXSkKICAgICAgJycnOwogICAgICAgIGV4cGVjdChyZWdleC5oYXNNYXRjaChjb250ZW50KSwgaXNUcnVlKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdkb2VzIG5vdCBtYXRjaCB3aGVuIHRhZyBtaXNzaW5nJywgKCkgewogICAgICAgIGNvbnN0IGNvbnRlbnQgPSAiQFRhZ3MoWydjaHJvbWUnLCAndGVzdCddKSI7CiAgICAgICAgZXhwZWN0KHJlZ2V4Lmhhc01hdGNoKGNvbnRlbnQpLCBpc0ZhbHNlKTsKICAgICAgfSk7CgogICAgICB0ZXN0KAogICAgICAgICdkb2VzIG5vdCBtYXRjaCBzdWJzdHJpbmcgb25seSAoZS5nLiBza2lwX3ZlcnlfZ29vZF9vcHRpbWl6YXRpb24sdGVzdCknLAogICAgICAgICgpIHsKICAgICAgICAgIGZpbmFsIGNvbnRlbnQgPQogICAgICAgICAgICAgICcnJwogICAgICBAVGFncyhbCiAgICAgICAgJyR7cHJlX2dlbi5za2lwVmVyeUdvb2RPcHRpbWl6YXRpb25UYWd9LHRlc3QnLAogICAgICAgICdjaHJvbWUnLAogICAgICBdKQogICAgICAnJyc7CiAgICAgICAgICBleHBlY3QoCiAgICAgICAgICAgIHJlZ2V4Lmhhc01hdGNoKGNvbnRlbnQpLAogICAgICAgICAgICBpc0ZhbHNlLAogICAgICAgICAgKTsgLy8gb25seSBleGFjdCB0YWcgc2hvdWxkIG1hdGNoCiAgICAgICAgfSwKICAgICAgKTsKICAgIH0pOwogIH0pOwp9Cg==", + "data": "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpob29rcy9wcmVfZ2VuLmRhcnQnIGFzIHByZV9nZW47CmltcG9ydCAncGFja2FnZTpob29rcy90ZXN0X21ldGFkYXRhLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bWFzb24vbWFzb24uZGFydCc7CmltcG9ydCAncGFja2FnZTptb2NrdGFpbC9tb2NrdGFpbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOnBhdGgvcGF0aC5kYXJ0JyBhcyBwYXRoOwppbXBvcnQgJ3BhY2thZ2U6dGVzdC90ZXN0LmRhcnQnOwoKY2xhc3MgX01vY2tMb2dnZXIgZXh0ZW5kcyBNb2NrIGltcGxlbWVudHMgTG9nZ2VyOwoKY2xhc3MgX0Zha2VDb250ZXh0IGV4dGVuZHMgRmFrZSBpbXBsZW1lbnRzIEhvb2tDb250ZXh0IHsKICBAb3ZlcnJpZGUKICBmaW5hbCBsb2dnZXIgPSBfTW9ja0xvZ2dlcigpOwoKICBAb3ZlcnJpZGUKICBNYXA8U3RyaW5nLCBPYmplY3Q/PiB2YXJzID0ge307Cn0KCmNvbnN0IG5vdE9wdGltaXplZFRlc3RDb250ZW50ID0KICAgICcnJwpAVGFncyhbJyR7cHJlX2dlbi5za2lwVmVyeUdvb2RPcHRpbWl6YXRpb25UYWd9J10pCmxpYnJhcnk7Cgp2b2lkIG1haW4oKSB7CiAgdGVzdCgndGVzdCcsICgpIHsKICAgIGV4cGVjdCgxLCAxKTsKICB9KTsKfQonJyc7Cgpjb25zdCBhbm90aGVyTm90T3B0aW1pemVkVGVzdENvbnRlbnQgPQogICAgJycnCkBUYWdzKFsnJHtwcmVfZ2VuLnNraXBWZXJ5R29vZE9wdGltaXphdGlvblRhZ30nLCAnYW5vdGhlcl90YWcnXSkKaW1wb3J0ICdwYWNrYWdlOnRlc3QvdGVzdC5kYXJ0JzsKCnZvaWQgbWFpbigpIHsKICB0ZXN0KCdhbm90aGVyIHRlc3QnLCAoKSB7CiAgICBleHBlY3QoMSwgMSk7CiAgfSk7Cn0KJycnOwoKdm9pZCBtYWluKCkgewogIGxhdGUgRGlyZWN0b3J5IHRlbXBEaXJlY3Rvcnk7CgogIHNldFVwKCgpIHsKICAgIHRlbXBEaXJlY3RvcnkgPSBEaXJlY3Rvcnkuc3lzdGVtVGVtcC5jcmVhdGVUZW1wU3luYygndGVzdF9vcHRpbWl6ZXInKTsKICB9KTsKCiAgdGVhckRvd24oKCkgewogICAgdGVtcERpcmVjdG9yeS5kZWxldGVTeW5jKHJlY3Vyc2l2ZTogdHJ1ZSk7CiAgfSk7CgogIGdyb3VwKCdQcmUgZ2VuIGhvb2snLCAoKSB7CiAgICBsYXRlIEhvb2tDb250ZXh0IGNvbnRleHQ7CgogICAgc2V0VXAoKCkgewogICAgICBjb250ZXh0ID0gX0Zha2VDb250ZXh0KCk7CiAgICB9KTsKCiAgICBncm91cCgnQ29tcGxldGVzJywgKCkgewogICAgICB0ZXN0KCd3aXRoIHRlc3QgZmlsZXMgbGlzdCcsICgpIGFzeW5jIHsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICdwdWJzcGVjLnlhbWwnKSkuY3JlYXRlU3luYygpOwoKICAgICAgICBmaW5hbCB0ZXN0RGlyID0gRGlyZWN0b3J5KHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICd0ZXN0JykpCiAgICAgICAgICAuLmNyZWF0ZVN5bmMoKTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICd0ZXN0MV90ZXN0LmRhcnQnKSkuY3JlYXRlU3luYygpOwogICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ3Rlc3QyX3Rlc3QuZGFydCcpKS5jcmVhdGVTeW5jKCk7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVzdERpci5wYXRoLCAnbm9fdGVzdF9oZXJlLmRhcnQnKSkuY3JlYXRlU3luYygpOwoKICAgICAgICBjb250ZXh0LnZhcnNbJ3BhY2thZ2Utcm9vdCddID0gdGVtcERpcmVjdG9yeS5hYnNvbHV0ZS5wYXRoOwoKICAgICAgICBhd2FpdCBwcmVfZ2VuLnJ1bihjb250ZXh0KTsKCiAgICAgICAgZmluYWwgdGVzdHMgPSBjb250ZXh0LnZhcnNbJ3Rlc3RzJ10gYXMgTGlzdDxNYXA8U3RyaW5nLCBTdHJpbmc+PjsKICAgICAgICBmaW5hbCB0ZXN0c01hcCA9IDxTdHJpbmcsIFN0cmluZz57fTsKICAgICAgICBmb3IgKGZpbmFsIHRlc3QgaW4gdGVzdHMpIHsKICAgICAgICAgIGZpbmFsIHBhdGggPSB0ZXN0WydwYXRoJ10hOwogICAgICAgICAgZmluYWwgaWRlbnRpZmllciA9IHRlc3RbJ2lkZW50aWZpZXInXSE7CiAgICAgICAgICB0ZXN0c01hcFtwYXRoXSA9IGlkZW50aWZpZXI7CiAgICAgICAgfQoKICAgICAgICBmaW5hbCBwYXRocyA9IHRlc3RzTWFwLmtleXM7CiAgICAgICAgZXhwZWN0KHBhdGhzLCBjb250YWlucygndGVzdDFfdGVzdC5kYXJ0JykpOwogICAgICAgIGV4cGVjdChwYXRocywgY29udGFpbnMoJ3Rlc3QyX3Rlc3QuZGFydCcpKTsKICAgICAgICBleHBlY3QocGF0aHMsIGlzTm90KGNvbnRhaW5zKCdub190ZXN0X2hlcmUuZGFydCcpKSk7CgogICAgICAgIGV4cGVjdCgKICAgICAgICAgIHRlc3RzTWFwLnZhbHVlcy50b1NldCgpLmxlbmd0aCwKICAgICAgICAgIGVxdWFscyh0ZXN0cy5sZW5ndGgpLAogICAgICAgICAgcmVhc29uOiAnQWxsIHRlc3RzIGZpbGVzIHNob3VsZCBoYXZlIHVuaXF1ZSBpZGVudGlmaWVycycsCiAgICAgICAgKTsKCiAgICAgICAgZXhwZWN0KAogICAgICAgICAgdGVzdHMubWFwKCh0ZXN0KSA9PiB0ZXN0Wydncm91cEFyZ3VtZW50cyddKSwKICAgICAgICAgIGV2ZXJ5RWxlbWVudChpc0VtcHR5KSwKICAgICAgICAgIHJlYXNvbjogJ0ZpbGVzIHdpdGhvdXQgYW5ub3RhdGlvbnMgZm9yd2FyZCBubyBncm91cCBhcmd1bWVudHMnLAogICAgICAgICk7CgogICAgICAgIGV4cGVjdChjb250ZXh0LnZhcnNbJ2lzRmx1dHRlciddLCBmYWxzZSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnd2l0aCBwcm9wZXIgaXNGbHV0dGVyIGlkZW50aWZpY2F0aW9uJywgKCkgYXN5bmMgewogICAgICAgIEZpbGUocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3B1YnNwZWMueWFtbCcpKQogICAgICAgICAgLi5jcmVhdGVTeW5jKCkKICAgICAgICAgIC4ud3JpdGVBc1N0cmluZ1N5bmMoJycnCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyJycnKTsKCiAgICAgICAgRGlyZWN0b3J5KHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICd0ZXN0JykpLmNyZWF0ZVN5bmMoKTsKCiAgICAgICAgY29udGV4dC52YXJzWydwYWNrYWdlLXJvb3QnXSA9IHRlbXBEaXJlY3RvcnkuYWJzb2x1dGUucGF0aDsKCiAgICAgICAgYXdhaXQgcHJlX2dlbi5ydW4oY29udGV4dCk7CgogICAgICAgIGV4cGVjdChjb250ZXh0LnZhcnNbJ2lzRmx1dHRlciddLCB0cnVlKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCd3aXRoIHByb3BlciBub3Qgb3B0aW1pemVkIHRlc3RzIGlkZW50aWZpY2F0aW9uJywgKCkgYXN5bmMgewogICAgICAgIEZpbGUocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3B1YnNwZWMueWFtbCcpKS5jcmVhdGVTeW5jKCk7CgogICAgICAgIGZpbmFsIHRlc3REaXIgPSBEaXJlY3RvcnkocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3Rlc3QnKSkKICAgICAgICAgIC4uY3JlYXRlU3luYygpOwogICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ3Rlc3QxX3Rlc3QuZGFydCcpKS5jcmVhdGVTeW5jKCk7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVzdERpci5wYXRoLCAndGVzdDJfdGVzdC5kYXJ0JykpLmNyZWF0ZVN5bmMoKTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICdub190ZXN0X2hlcmUuZGFydCcpKS5jcmVhdGVTeW5jKCk7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVzdERpci5wYXRoLCAnbm90X29wdGltaXplZF90ZXN0LmRhcnQnKSkKICAgICAgICAgICAgLndyaXRlQXNTdHJpbmdTeW5jKG5vdE9wdGltaXplZFRlc3RDb250ZW50KTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICdhbm90aGVyX25vdF9vcHRpbWl6ZWRfdGVzdC5kYXJ0JykpCiAgICAgICAgICAgIC53cml0ZUFzU3RyaW5nU3luYyhhbm90aGVyTm90T3B0aW1pemVkVGVzdENvbnRlbnQpOwoKICAgICAgICBjb250ZXh0LnZhcnNbJ3BhY2thZ2Utcm9vdCddID0gdGVtcERpcmVjdG9yeS5hYnNvbHV0ZS5wYXRoOwoKICAgICAgICBhd2FpdCBwcmVfZ2VuLnJ1bihjb250ZXh0KTsKCiAgICAgICAgZmluYWwgdGVzdHMgPSBjb250ZXh0LnZhcnNbJ3Rlc3RzJ10gYXMgTGlzdDxNYXA8U3RyaW5nLCBTdHJpbmc+PjsKICAgICAgICBmaW5hbCB0ZXN0c01hcCA9IDxTdHJpbmcsIFN0cmluZz57fTsKICAgICAgICBmb3IgKGZpbmFsIHRlc3QgaW4gdGVzdHMpIHsKICAgICAgICAgIGZpbmFsIHBhdGggPSB0ZXN0WydwYXRoJ10hOwogICAgICAgICAgZmluYWwgaWRlbnRpZmllciA9IHRlc3RbJ2lkZW50aWZpZXInXSE7CiAgICAgICAgICB0ZXN0c01hcFtwYXRoXSA9IGlkZW50aWZpZXI7CiAgICAgICAgfQoKICAgICAgICBmaW5hbCBwYXRocyA9IHRlc3RzTWFwLmtleXM7CiAgICAgICAgZXhwZWN0KHBhdGhzLCBjb250YWlucygndGVzdDFfdGVzdC5kYXJ0JykpOwogICAgICAgIGV4cGVjdChwYXRocywgY29udGFpbnMoJ3Rlc3QyX3Rlc3QuZGFydCcpKTsKICAgICAgICBleHBlY3QocGF0aHMsIGlzTm90KGNvbnRhaW5zKCdub190ZXN0X2hlcmUuZGFydCcpKSk7CiAgICAgICAgZXhwZWN0KHBhdGhzLCBpc05vdChjb250YWlucygnbm90X29wdGltaXplZF90ZXN0LmRhcnQnKSkpOwogICAgICAgIGV4cGVjdChwYXRocywgaXNOb3QoY29udGFpbnMoJ2Fub3RoZXJfbm90X29wdGltaXplZF90ZXN0LmRhcnQnKSkpOwoKICAgICAgICBleHBlY3QoCiAgICAgICAgICB0ZXN0c01hcC52YWx1ZXMudG9TZXQoKS5sZW5ndGgsCiAgICAgICAgICBlcXVhbHModGVzdHMubGVuZ3RoKSwKICAgICAgICAgIHJlYXNvbjogJ0FsbCB0ZXN0cyBmaWxlcyBzaG91bGQgaGF2ZSB1bmlxdWUgaWRlbnRpZmllcnMnLAogICAgICAgICk7CiAgICAgICAgZmluYWwgbm90T3B0aW1pemVkVGVzdHMgPQogICAgICAgICAgICBjb250ZXh0LnZhcnNbJ25vdE9wdGltaXplZFRlc3RzJ10gYXMgTGlzdDxTdHJpbmc+OwogICAgICAgIGV4cGVjdChub3RPcHRpbWl6ZWRUZXN0cywgY29udGFpbnMoJ25vdF9vcHRpbWl6ZWRfdGVzdC5kYXJ0JykpOwogICAgICAgIGV4cGVjdChub3RPcHRpbWl6ZWRUZXN0cywgY29udGFpbnMoJ2Fub3RoZXJfbm90X29wdGltaXplZF90ZXN0LmRhcnQnKSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgKICAgICAgICAnd2l0aCBmaWxlIGxldmVsIGFubm90YXRpb25zIGZvcndhcmRlZCBhcyBncm91cCBhcmd1bWVudHMnLAogICAgICAgICgpIGFzeW5jIHsKICAgICAgICAgIEZpbGUocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3B1YnNwZWMueWFtbCcpKS5jcmVhdGVTeW5jKCk7CgogICAgICAgICAgZmluYWwgdGVzdERpciA9IERpcmVjdG9yeShwYXRoLmpvaW4odGVtcERpcmVjdG9yeS5wYXRoLCAndGVzdCcpKQogICAgICAgICAgICAuLmNyZWF0ZVN5bmMoKTsKICAgICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ2Fubm90YXRlZF90ZXN0LmRhcnQnKSkKICAgICAgICAgICAgICAud3JpdGVBc1N0cmluZ1N5bmMoJycnCkBUYWdzKFsnc2xvdyddKQpAVGltZW91dChEdXJhdGlvbihtaW51dGVzOiA1KSkKbGlicmFyeTsKCnZvaWQgbWFpbigpIHt9CicnJyk7CgogICAgICAgICAgY29udGV4dC52YXJzWydwYWNrYWdlLXJvb3QnXSA9IHRlbXBEaXJlY3RvcnkuYWJzb2x1dGUucGF0aDsKCiAgICAgICAgICBhd2FpdCBwcmVfZ2VuLnJ1bihjb250ZXh0KTsKCiAgICAgICAgICBmaW5hbCB0ZXN0cyA9IGNvbnRleHQudmFyc1sndGVzdHMnXSBhcyBMaXN0PE1hcDxTdHJpbmcsIFN0cmluZz4+OwogICAgICAgICAgZXhwZWN0KHRlc3RzLCBoYXNMZW5ndGgoMSkpOwogICAgICAgICAgZXhwZWN0KAogICAgICAgICAgICB0ZXN0cy5zaW5nbGVbJ2dyb3VwQXJndW1lbnRzJ10sCiAgICAgICAgICAgIGVxdWFscygiLCB0aW1lb3V0OiBUaW1lb3V0KER1cmF0aW9uKG1pbnV0ZXM6IDUpKSwgdGFnczogWydzbG93J10iKSwKICAgICAgICAgICk7CiAgICAgICAgfSwKICAgICAgKTsKCiAgICAgIHRlc3QoCiAgICAgICAgJ3dpdGggYSBza2lwIHRhZyBwYWNrYWdlOnRlc3Qgd291bGQgaWdub3JlIGxlZnQgb3B0aW1pemVkJywKICAgICAgICAoKSBhc3luYyB7CiAgICAgICAgICBGaWxlKHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICdwdWJzcGVjLnlhbWwnKSkuY3JlYXRlU3luYygpOwoKICAgICAgICAgIGZpbmFsIHRlc3REaXIgPSBEaXJlY3RvcnkocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3Rlc3QnKSkKICAgICAgICAgICAgLi5jcmVhdGVTeW5jKCk7CiAgICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICdub19kaXJlY3RpdmVfdGVzdC5kYXJ0JykpCiAgICAgICAgICAgICAgLndyaXRlQXNTdHJpbmdTeW5jKCcnJwpAVGFncyhbJyR7cHJlX2dlbi5za2lwVmVyeUdvb2RPcHRpbWl6YXRpb25UYWd9J10pCnZvaWQgbWFpbigpIHt9CicnJyk7CgogICAgICAgICAgY29udGV4dC52YXJzWydwYWNrYWdlLXJvb3QnXSA9IHRlbXBEaXJlY3RvcnkuYWJzb2x1dGUucGF0aDsKCiAgICAgICAgICBhd2FpdCBwcmVfZ2VuLnJ1bihjb250ZXh0KTsKCiAgICAgICAgICBmaW5hbCB0ZXN0cyA9IGNvbnRleHQudmFyc1sndGVzdHMnXSBhcyBMaXN0PE1hcDxTdHJpbmcsIFN0cmluZz4+OwogICAgICAgICAgZXhwZWN0KAogICAgICAgICAgICB0ZXN0cy5tYXAoKHRlc3QpID0+IHRlc3RbJ3BhdGgnXSksCiAgICAgICAgICAgIGNvbnRhaW5zKCdub19kaXJlY3RpdmVfdGVzdC5kYXJ0JyksCiAgICAgICAgICApOwogICAgICAgICAgZXhwZWN0KGNvbnRleHQudmFyc1snbm90T3B0aW1pemVkVGVzdHMnXSwgaXNFbXB0eSk7CgogICAgICAgICAgdmVyaWZ5KAogICAgICAgICAgICAoKSA9PiBjb250ZXh0LmxvZ2dlci53YXJuKAogICAgICAgICAgICAgIGFueSgKICAgICAgICAgICAgICAgIHRoYXQ6IGFsbE9mKAogICAgICAgICAgICAgICAgICBjb250YWlucygnbm9fZGlyZWN0aXZlX3Rlc3QuZGFydCcpLAogICAgICAgICAgICAgICAgICBjb250YWlucyhwcmVfZ2VuLnNraXBWZXJ5R29vZE9wdGltaXphdGlvblRhZyksCiAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICksCiAgICAgICAgICAgICksCiAgICAgICAgICApLmNhbGxlZCgxKTsKICAgICAgICB9LAogICAgICApOwoKICAgICAgdGVzdCgKICAgICAgICAnd2l0aCBhbiB1bnJlc29sdmFibGUgYW5ub3RhdGlvbiBsZWZ0IG91dCBhbmQgd2FybmVkIGFib3V0JywKICAgICAgICAoKSBhc3luYyB7CiAgICAgICAgICBGaWxlKHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICdwdWJzcGVjLnlhbWwnKSkuY3JlYXRlU3luYygpOwoKICAgICAgICAgIGZpbmFsIHRlc3REaXIgPSBEaXJlY3RvcnkocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3Rlc3QnKSkKICAgICAgICAgICAgLi5jcmVhdGVTeW5jKCk7CiAgICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICd1bnJlc29sdmFibGVfdGVzdC5kYXJ0JykpCiAgICAgICAgICAgICAgLndyaXRlQXNTdHJpbmdTeW5jKCcnJwpAVGltZW91dChrU2xvd1N1aXRlKQpAUmV0cnkoMikKbGlicmFyeTsKCnZvaWQgbWFpbigpIHt9CicnJyk7CgogICAgICAgICAgY29udGV4dC52YXJzWydwYWNrYWdlLXJvb3QnXSA9IHRlbXBEaXJlY3RvcnkuYWJzb2x1dGUucGF0aDsKCiAgICAgICAgICBhd2FpdCBwcmVfZ2VuLnJ1bihjb250ZXh0KTsKCiAgICAgICAgICBmaW5hbCB0ZXN0cyA9IGNvbnRleHQudmFyc1sndGVzdHMnXSBhcyBMaXN0PE1hcDxTdHJpbmcsIFN0cmluZz4+OwogICAgICAgICAgZXhwZWN0KAogICAgICAgICAgICB0ZXN0cy5zaW5nbGVbJ2dyb3VwQXJndW1lbnRzJ10sCiAgICAgICAgICAgIGVxdWFscygnLCByZXRyeTogMicpLAogICAgICAgICAgICByZWFzb246ICdUaGUgcmVzb2x2YWJsZSBzaWJsaW5nIGlzIHN0aWxsIGZvcndhcmRlZCcsCiAgICAgICAgICApOwoKICAgICAgICAgIHZlcmlmeSgKICAgICAgICAgICAgKCkgPT4gY29udGV4dC5sb2dnZXIud2FybigKICAgICAgICAgICAgICBhbnkoCiAgICAgICAgICAgICAgICB0aGF0OiBhbGxPZigKICAgICAgICAgICAgICAgICAgY29udGFpbnMoJ3VucmVzb2x2YWJsZV90ZXN0LmRhcnQnKSwKICAgICAgICAgICAgICAgICAgY29udGFpbnMoJ0BUaW1lb3V0KGtTbG93U3VpdGUpJyksCiAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICksCiAgICAgICAgICAgICksCiAgICAgICAgICApLmNhbGxlZCgxKTsKICAgICAgICB9LAogICAgICApOwogICAgfSk7CgogICAgZ3JvdXAoJ0ZhaWxzJywgKCkgewogICAgICBzZXRVcCgoKSB7CiAgICAgICAgcHJlX2dlbi5leGl0Rm4gPSAoY29kZSkgewogICAgICAgICAgdGhyb3cgUHJvY2Vzc0V4Y2VwdGlvbignZXhpdCcsIFtjb2RlLnRvU3RyaW5nKCldKTsKICAgICAgICB9OwogICAgICB9KTsKCiAgICAgIHRlYXJEb3duKCgpIHsKICAgICAgICBwcmVfZ2VuLmV4aXRGbiA9IGV4aXQ7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnd2hlbiB0YXJnZXQgdGVzdCBkaXIgZG9lcyBub3QgZXhpc3QnLCAoKSBhc3luYyB7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVtcERpcmVjdG9yeS5wYXRoLCAncHVic3BlYy55YW1sJykpLmNyZWF0ZVN5bmMoKTsKCiAgICAgICAgZmluYWwgdGVzdERpciA9IERpcmVjdG9yeShwYXRoLmpvaW4odGVtcERpcmVjdG9yeS5wYXRoLCAndGVzdCcpKTsKCiAgICAgICAgY29udGV4dC52YXJzWydwYWNrYWdlLXJvb3QnXSA9IHRlbXBEaXJlY3RvcnkuYWJzb2x1dGUucGF0aDsKCiAgICAgICAgYXdhaXQgZXhwZWN0TGF0ZXIoCiAgICAgICAgICAoKSA9PiBwcmVfZ2VuLnJ1bihjb250ZXh0KSwKICAgICAgICAgIHRocm93c0EoCiAgICAgICAgICAgIGlzQTxQcm9jZXNzRXhjZXB0aW9uPigpLmhhdmluZygKICAgICAgICAgICAgICAoZXgpID0+IGV4LmFyZ3VtZW50cy5maXJzdCwKICAgICAgICAgICAgICAnZXJyb3IgY29kZScsCiAgICAgICAgICAgICAgZXF1YWxzKCcxJyksCiAgICAgICAgICAgICksCiAgICAgICAgICApLAogICAgICAgICk7CgogICAgICAgIHZlcmlmeSgKICAgICAgICAgICgpID0+IGNvbnRleHQubG9nZ2VyLmVycignQ291bGQgbm90IGZpbmQgZGlyZWN0b3J5ICR7dGVzdERpci5wYXRofScpLAogICAgICAgICkuY2FsbGVkKDEpOwoKICAgICAgICBleHBlY3QoY29udGV4dC52YXJzWyd0ZXN0cyddLCBpc051bGwpOwogICAgICAgIGV4cGVjdChjb250ZXh0LnZhcnNbJ2lzRmx1dHRlciddLCBpc051bGwpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ3doZW4gdGFyZ2V0IGRpciBkb2VzIG5vdCBjb250YWluIGEgcHVic3BlYy55YW1sJywgKCkgYXN5bmMgewogICAgICAgIGZpbmFsIHRlc3REaXIgPSBEaXJlY3RvcnkocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3Rlc3QnKSkKICAgICAgICAgIC4uY3JlYXRlU3luYygpOwogICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ3Rlc3QxX3Rlc3QuZGFydCcpKS5jcmVhdGVTeW5jKCk7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVzdERpci5wYXRoLCAndGVzdDJfdGVzdC5kYXJ0JykpLmNyZWF0ZVN5bmMoKTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICdub190ZXN0X2hlcmUuZGFydCcpKS5jcmVhdGVTeW5jKCk7CgogICAgICAgIGNvbnRleHQudmFyc1sncGFja2FnZS1yb290J10gPSB0ZW1wRGlyZWN0b3J5LmFic29sdXRlLnBhdGg7CgogICAgICAgIGF3YWl0IGV4cGVjdExhdGVyKAogICAgICAgICAgKCkgPT4gcHJlX2dlbi5ydW4oY29udGV4dCksCiAgICAgICAgICB0aHJvd3NBKAogICAgICAgICAgICBpc0E8UHJvY2Vzc0V4Y2VwdGlvbj4oKS5oYXZpbmcoCiAgICAgICAgICAgICAgKGV4KSA9PiBleC5hcmd1bWVudHMuZmlyc3QsCiAgICAgICAgICAgICAgJ2Vycm9yIGNvZGUnLAogICAgICAgICAgICAgIGVxdWFscygnMScpLAogICAgICAgICAgICApLAogICAgICAgICAgKSwKICAgICAgICApOwoKICAgICAgICB2ZXJpZnkoCiAgICAgICAgICAoKSA9PiBjb250ZXh0LmxvZ2dlci5lcnIoCiAgICAgICAgICAgICdDb3VsZCBub3QgZmluZCBwdWJzcGVjLnlhbWwgYXQgJHt0ZXN0RGlyLnBhdGh9JywKICAgICAgICAgICksCiAgICAgICAgKS5jYWxsZWQoMSk7CgogICAgICAgIGV4cGVjdChjb250ZXh0LnZhcnNbJ3Rlc3RzJ10sIGlzTnVsbCk7CiAgICAgICAgZXhwZWN0KGNvbnRleHQudmFyc1snaXNGbHV0dGVyJ10sIGlzTnVsbCk7CiAgICAgIH0pOwogICAgfSk7CiAgfSk7CgogIGdyb3VwKCdUZXN0TWV0YWRhdGFCdW5kbGUnLCAoKSB7CiAgICBncm91cCgnZ3JvdXBBcmd1bWVudHMnLCAoKSB7CiAgICAgIHRlc3QoJ2lzIGVtcHR5IHdpdGhvdXQgYW5ub3RhdGlvbnMnLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YSgndm9pZCBtYWluKCkge30nKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmdyb3VwQXJndW1lbnRzLCBpc0VtcHR5KTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdlbWl0cyBhIHNpbmdsZSBhbm5vdGF0aW9uJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEoIkBTa2lwKCdub3QgcmVhZHknKVxubGlicmFyeTsiKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmdyb3VwQXJndW1lbnRzLCBlcXVhbHMoIiwgc2tpcDogJ25vdCByZWFkeSciKSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnZW1pdHMgYSBmaXhlZCBvcmRlciByZWdhcmRsZXNzIG9mIHNvdXJjZSBvcmRlcicsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKCcnJwpAUmV0cnkoMykKQFRhZ3MoWydzbG93J10pCkBTa2lwKCkKQFRpbWVvdXQubm9uZQpAVGVzdE9uKCd2bScpCmxpYnJhcnk7CicnJyk7CgogICAgICAgIGV4cGVjdCgKICAgICAgICAgIG1ldGFkYXRhLmdyb3VwQXJndW1lbnRzLAogICAgICAgICAgZXF1YWxzKAogICAgICAgICAgICAiLCB0ZXN0T246ICd2bScsIHRpbWVvdXQ6IFRpbWVvdXQubm9uZSwgc2tpcDogdHJ1ZSwgIgogICAgICAgICAgICAidGFnczogWydzbG93J10sIHJldHJ5OiAzIiwKICAgICAgICAgICksCiAgICAgICAgKTsKICAgICAgfSk7CiAgICB9KTsKCiAgICBncm91cCgnc2tpcHNPcHRpbWl6YXRpb24nLCAoKSB7CiAgICAgIHRlc3QoJ2lzIHRydWUgZm9yIHRoZSBza2lwIHRhZyBvbiBpdHMgb3duJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEoCiAgICAgICAgICAiQFRhZ3MoWycke3ByZV9nZW4uc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnfSddKVxubGlicmFyeTsiLAogICAgICAgICk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YS5za2lwc09wdGltaXphdGlvbiwgaXNUcnVlKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdpcyB0cnVlIHdoZW4gdGhlIHNraXAgdGFnIGlzIG5vdCBmaXJzdCcsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKAogICAgICAgICAgIkBUYWdzKFsnb3RoZXInLCAnJHtwcmVfZ2VuLnNraXBWZXJ5R29vZE9wdGltaXphdGlvblRhZ30nXSlcbiIKICAgICAgICAgICdsaWJyYXJ5OycsCiAgICAgICAgKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLnNraXBzT3B0aW1pemF0aW9uLCBpc1RydWUpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ2lzIGZhbHNlIHdpdGhvdXQgdGhlIHRhZycsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKCJAVGFncyhbJ290aGVyJ10pXG5saWJyYXJ5OyIpOwoKICAgICAgICBleHBlY3QobWV0YWRhdGEuc2tpcHNPcHRpbWl6YXRpb24sIGlzRmFsc2UpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ2lzIGZhbHNlIHdoZW4gdGhlIHRhZyBpcyBvbmx5IGEgc3Vic3RyaW5nJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEoCiAgICAgICAgICAiQFRhZ3MoWycke3ByZV9nZW4uc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnfSx0ZXN0J10pXG5saWJyYXJ5OyIsCiAgICAgICAgKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLnNraXBzT3B0aW1pemF0aW9uLCBpc0ZhbHNlKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdpcyBmYWxzZSB3aGVuIHRoZSBhbm5vdGF0aW9uIGlzIG5vdCBvbiB0aGUgZmlyc3QgZGlyZWN0aXZlJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEoJycnCkBUYWdzKFsnJHtwcmVfZ2VuLnNraXBWZXJ5R29vZE9wdGltaXphdGlvblRhZ30nXSkKdm9pZCBtYWluKCkge30KJycnKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLnNraXBzT3B0aW1pemF0aW9uLCBpc0ZhbHNlKTsKICAgICAgfSk7CiAgICB9KTsKICB9KTsKfQo=", + "type": "text", + }, + { + "path": "test/test_metadata_test.dart", + "data": "aW1wb3J0ICdwYWNrYWdlOmhvb2tzL3Rlc3RfbWV0YWRhdGEuZGFydCc7CmltcG9ydCAncGFja2FnZTp0ZXN0L3Rlc3QuZGFydCc7CgovLy8gV3JhcHMgW2Fubm90YXRpb25zXSBhYm92ZSBhIGBsaWJyYXJ5O2AgZGlyZWN0aXZlLCB0aGUgc2hhcGUgYHBhY2thZ2U6dGVzdGAKLy8vIHJlYWRzIG1ldGFkYXRhIGZyb20uClN0cmluZyBsaWJyYXJ5KFN0cmluZyBhbm5vdGF0aW9ucykgPT4KICAgICckYW5ub3RhdGlvbnNcbmxpYnJhcnk7XG5cbnZvaWQgbWFpbigpIHt9JzsKCi8vLyBNYXRjaGVzIG1ldGFkYXRhIHRoYXQgZm9yd2FyZHMgbm8gYW5ub3RhdGlvbiB0byB0aGUgYnVuZGxlLgpmaW5hbCBNYXRjaGVyIGZvcndhcmRzTm90aGluZyA9IGlzQTxUZXN0TWV0YWRhdGE+KCkuaGF2aW5nKAogIChtZXRhZGF0YSkgPT4gbWV0YWRhdGEuYXJndW1lbnRzLAogICdhcmd1bWVudHMnLAogIGlzRW1wdHksCik7Cgp2b2lkIG1haW4oKSB7CiAgZ3JvdXAoJ3BhcnNlVGVzdE1ldGFkYXRhJywgKCkgewogICAgZ3JvdXAoJ2ZvcndhcmRzIG5vdGhpbmcnLCAoKSB7CiAgICAgIHRlc3QoJ2ZvciBlbXB0eSBjb250ZW50JywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEoJycpOwoKICAgICAgICBleHBlY3QobWV0YWRhdGEsIGZvcndhcmRzTm90aGluZyk7CiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLnRhZ05hbWVzLCBpc0VtcHR5KTsKICAgICAgICBleHBlY3QobWV0YWRhdGEuZHJvcHBlZEFubm90YXRpb25zLCBpc0VtcHR5KTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdmb3IgYSBmaWxlIHdpdGhvdXQgZGlyZWN0aXZlcycsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKCd2b2lkIG1haW4oKSB7fScpOwoKICAgICAgICBleHBlY3QobWV0YWRhdGEsIGZvcndhcmRzTm90aGluZyk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnZm9yIGEgZmlsZSB3aG9zZSBhbm5vdGF0aW9ucyBzaXQgYWJvdmUgbWFpbicsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKCcnJwppbXBvcnQgJ3BhY2thZ2U6dGVzdC90ZXN0LmRhcnQnOwoKQFNraXAoJ2lnbm9yZWQgYnkgcGFja2FnZTp0ZXN0JykKdm9pZCBtYWluKCkge30KJycnKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLCBmb3J3YXJkc05vdGhpbmcpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ2ZvciBhbm5vdGF0aW9ucyBvbiBhIGRpcmVjdGl2ZSBvdGhlciB0aGFuIHRoZSBmaXJzdCcsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKCcnJwppbXBvcnQgJ3BhY2thZ2U6dGVzdC90ZXN0LmRhcnQnOwoKQFNraXAoJ2lnbm9yZWQgYnkgcGFja2FnZTp0ZXN0JykKaW1wb3J0ICdkYXJ0OmFzeW5jJzsKCnZvaWQgbWFpbigpIHt9CicnJyk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YSwgZm9yd2FyZHNOb3RoaW5nKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdmb3IgYSBiYXJlIGFubm90YXRpb24gd2l0aCBubyBhcmd1bWVudCBsaXN0JywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEobGlicmFyeSgnQFNraXAnKSk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YSwgZm9yd2FyZHNOb3RoaW5nKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdmb3IgYSBiYXJlIEBUaW1lb3V0LCB3aGljaCBuYW1lcyB0aGUgdHlwZSByYXRoZXIgdGhhbiBhIHZhbHVlJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEobGlicmFyeSgnQFRpbWVvdXQnKSk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YSwgZm9yd2FyZHNOb3RoaW5nKTsKICAgICAgfSk7CiAgICB9KTsKCiAgICBncm91cCgnaWdub3JlcyB1bnJlY29nbml6ZWQgYW5ub3RhdGlvbnMnLCAoKSB7CiAgICAgIHRlc3QoJ3dpdGhvdXQgZHJvcHBpbmcgdGhlbScsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKGxpYnJhcnkoIkBEZXByZWNhdGVkKCdub3BlJykiKSk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YSwgZm9yd2FyZHNOb3RoaW5nKTsKICAgICAgICBleHBlY3QobWV0YWRhdGEuZHJvcHBlZEFubm90YXRpb25zLCBpc0VtcHR5KTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdldmVuIHdoZW4gdGhleSByZWZlcmVuY2UgYW4gdW5yZXNvbHZhYmxlIG5hbWUnLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YShsaWJyYXJ5KCdARGVwcmVjYXRlZChyZWFzb24pJykpOwoKICAgICAgICBleHBlY3QobWV0YWRhdGEsIGZvcndhcmRzTm90aGluZyk7CiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmRyb3BwZWRBbm5vdGF0aW9ucywgaXNFbXB0eSk7CiAgICAgIH0pOwogICAgfSk7CgogICAgZ3JvdXAoJ3BhcnNlcycsICgpIHsKICAgICAgdGVzdCgnQFNraXAgd2l0aG91dCBhIHJlYXNvbicsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKGxpYnJhcnkoJ0BTa2lwKCknKSk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YS5hcmd1bWVudHNbJ3NraXAnXSwgZXF1YWxzKCd0cnVlJykpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ0BTa2lwIHdpdGggYSByZWFzb24nLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YSgnJycKQFNraXAoJ25vdCByZWFkeScpCmltcG9ydCAncGFja2FnZTp0ZXN0L3Rlc3QuZGFydCc7Cgp2b2lkIG1haW4oKSB7fQonJycpOwoKICAgICAgICBleHBlY3QobWV0YWRhdGEuYXJndW1lbnRzWydza2lwJ10sIGVxdWFscygiJ25vdCByZWFkeSciKSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnQFRhZ3MnLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YSgKICAgICAgICAgIGxpYnJhcnkoIkBUYWdzKFsnc2xvdycsICdpbnRlZ3JhdGlvbiddKSIpLAogICAgICAgICk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YS5hcmd1bWVudHNbJ3RhZ3MnXSwgZXF1YWxzKCJbJ3Nsb3cnLCAnaW50ZWdyYXRpb24nXSIpKTsKICAgICAgICBleHBlY3QobWV0YWRhdGEudGFnTmFtZXMsIGVxdWFscyh7J3Nsb3cnLCAnaW50ZWdyYXRpb24nfSkpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ0BUYWdzIHNwYW5uaW5nIHNldmVyYWwgbGluZXMgb250byBhIHNpbmdsZSBvbmUnLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YSgKICAgICAgICAgIGxpYnJhcnkoIkBUYWdzKFtcbiAgJ3Nsb3cnLFxuICAnaW50ZWdyYXRpb24nLFxuXSkiKSwKICAgICAgICApOwoKICAgICAgICBleHBlY3QobWV0YWRhdGEuYXJndW1lbnRzWyd0YWdzJ10sIGVxdWFscygiWydzbG93JywgJ2ludGVncmF0aW9uJ10iKSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnQFRpbWVvdXQgd2l0aCBhIGR1cmF0aW9uJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEoCiAgICAgICAgICBsaWJyYXJ5KCdAVGltZW91dChEdXJhdGlvbihtaW51dGVzOiA1KSknKSwKICAgICAgICApOwoKICAgICAgICBleHBlY3QoCiAgICAgICAgICBtZXRhZGF0YS5hcmd1bWVudHNbJ3RpbWVvdXQnXSwKICAgICAgICAgIGVxdWFscygnVGltZW91dChEdXJhdGlvbihtaW51dGVzOiA1KSknKSwKICAgICAgICApOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ0BUaW1lb3V0IHdpdGggYSBjb25zdCBkdXJhdGlvbicsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKAogICAgICAgICAgbGlicmFyeSgnQFRpbWVvdXQoY29uc3QgRHVyYXRpb24oc2Vjb25kczogMSkpJyksCiAgICAgICAgKTsKCiAgICAgICAgZXhwZWN0KAogICAgICAgICAgbWV0YWRhdGEuYXJndW1lbnRzWyd0aW1lb3V0J10sCiAgICAgICAgICBlcXVhbHMoJ1RpbWVvdXQoY29uc3QgRHVyYXRpb24oc2Vjb25kczogMSkpJyksCiAgICAgICAgKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdAVGltZW91dC5mYWN0b3InLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YShsaWJyYXJ5KCdAVGltZW91dC5mYWN0b3IoMiknKSk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YS5hcmd1bWVudHNbJ3RpbWVvdXQnXSwgZXF1YWxzKCdUaW1lb3V0LmZhY3RvcigyKScpKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdAVGltZW91dC5ub25lJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEobGlicmFyeSgnQFRpbWVvdXQubm9uZScpKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmFyZ3VtZW50c1sndGltZW91dCddLCBlcXVhbHMoJ1RpbWVvdXQubm9uZScpKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdAVGVzdE9uJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEobGlicmFyeSgiQFRlc3RPbigndm0nKSIpKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmFyZ3VtZW50c1sndGVzdE9uJ10sIGVxdWFscygiJ3ZtJyIpKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdAT25QbGF0Zm9ybScsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKAogICAgICAgICAgbGlicmFyeSgiQE9uUGxhdGZvcm0oeydjaHJvbWUnOiBUaW1lb3V0LmZhY3RvcigyKX0pIiksCiAgICAgICAgKTsKCiAgICAgICAgZXhwZWN0KAogICAgICAgICAgbWV0YWRhdGEuYXJndW1lbnRzWydvblBsYXRmb3JtJ10sCiAgICAgICAgICBlcXVhbHMoInsnY2hyb21lJyA6IFRpbWVvdXQuZmFjdG9yKDIpfSIpLAogICAgICAgICk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnQE9uUGxhdGZvcm0gd2l0aCBhIFNraXAgdmFsdWUnLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YSgKICAgICAgICAgIGxpYnJhcnkoIkBPblBsYXRmb3JtKHsndm0nOiBTa2lwKCdub3Qgb24gdGhlIHZtJyl9KSIpLAogICAgICAgICk7CgogICAgICAgIGV4cGVjdCgKICAgICAgICAgIG1ldGFkYXRhLmFyZ3VtZW50c1snb25QbGF0Zm9ybSddLAogICAgICAgICAgZXF1YWxzKCJ7J3ZtJyA6IFNraXAoJ25vdCBvbiB0aGUgdm0nKX0iKSwKICAgICAgICApOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ0BSZXRyeScsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKGxpYnJhcnkoJ0BSZXRyeSgzKScpKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmFyZ3VtZW50c1sncmV0cnknXSwgZXF1YWxzKCczJykpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ2EgZmlsZSB0aGF0IGZhaWxzIHRvIHBhcnNlJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEoJycnCkBTa2lwKCdzdGlsbCByZWFkJykKbGlicmFyeTsKCnZvaWQgbWFpbigpIHsgdGhpcyBpcyAoIG5vdCB2YWxpZCBkYXJ0CicnJyk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YS5hcmd1bWVudHNbJ3NraXAnXSwgZXF1YWxzKCInc3RpbGwgcmVhZCciKSk7CiAgICAgIH0pOwogICAgfSk7CgogICAgZ3JvdXAoJ2Ryb3BzIGFuIGFubm90YXRpb24gdGhlIGJ1bmRsZSBjYW5ub3QgcmVzb2x2ZScsICgpIHsKICAgICAgdGVzdCgnbmFtaW5nIGEgY29uc3RhbnQnLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YShsaWJyYXJ5KCdAVGltZW91dChrU2xvd1N1aXRlKScpKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmFyZ3VtZW50c1sndGltZW91dCddLCBpc051bGwpOwogICAgICAgIGV4cGVjdChtZXRhZGF0YS5kcm9wcGVkQW5ub3RhdGlvbnMsIGVxdWFscyhbJ0BUaW1lb3V0KGtTbG93U3VpdGUpJ10pKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCduYW1pbmcgYSBjb25zdGFudCBpbnNpZGUgYSBjb2xsZWN0aW9uJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEobGlicmFyeSgnQFRhZ3MoW3Nsb3ddKScpKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmFyZ3VtZW50c1sndGFncyddLCBpc051bGwpOwogICAgICAgIGV4cGVjdChtZXRhZGF0YS5kcm9wcGVkQW5ub3RhdGlvbnMsIGVxdWFscyhbJ0BUYWdzKFtzbG93XSknXSkpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ25hbWluZyBhIGNsYXNzIHRoZSBidW5kbGUgZG9lcyBub3QgaW1wb3J0JywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEoCiAgICAgICAgICBsaWJyYXJ5KCdAVGltZW91dChjb25zdCBTbG93RHVyYXRpb24oKSknKSwKICAgICAgICApOwoKICAgICAgICBleHBlY3QobWV0YWRhdGEuYXJndW1lbnRzWyd0aW1lb3V0J10sIGlzTnVsbCk7CiAgICAgICAgZXhwZWN0KAogICAgICAgICAgbWV0YWRhdGEuZHJvcHBlZEFubm90YXRpb25zLAogICAgICAgICAgZXF1YWxzKFsnQFRpbWVvdXQoY29uc3QgU2xvd0R1cmF0aW9uKCkpJ10pLAogICAgICAgICk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnbmFtaW5nIGFuIHVua25vd24gY29uc3RydWN0b3InLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YShsaWJyYXJ5KCdAVGltZW91dC5mb3JldmVyKDIpJykpOwoKICAgICAgICBleHBlY3QobWV0YWRhdGEuYXJndW1lbnRzWyd0aW1lb3V0J10sIGlzTnVsbCk7CiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmRyb3BwZWRBbm5vdGF0aW9ucywgZXF1YWxzKFsnQFRpbWVvdXQuZm9yZXZlcigyKSddKSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgncmVhY2hpbmcgYSBjbGFzcyB0aHJvdWdoIGFuIGltcG9ydCBwcmVmaXgnLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YSgnJycKQE9uUGxhdGZvcm0oeydjaHJvbWUnOiB0LlRpbWVvdXQuZmFjdG9yKDIpfSkKaW1wb3J0ICdwYWNrYWdlOnRlc3QvdGVzdC5kYXJ0JyBhcyB0OwoKdm9pZCBtYWluKCkge30KJycnKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmFyZ3VtZW50c1snb25QbGF0Zm9ybSddLCBpc051bGwpOwogICAgICAgIGV4cGVjdChtZXRhZGF0YS5kcm9wcGVkQW5ub3RhdGlvbnMsIGhhc0xlbmd0aCgxKSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnd2hpbGUgc3RpbGwgcmVhZGluZyBpdHMgc3RyaW5nIGxpdGVyYWwgdGFncycsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKGxpYnJhcnkoIkBUYWdzKFtzbG93LCAnZmFzdCddKSIpKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmFyZ3VtZW50c1sndGFncyddLCBpc051bGwpOwogICAgICAgIGV4cGVjdChtZXRhZGF0YS50YWdOYW1lcywgZXF1YWxzKHsnZmFzdCd9KSk7CiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmRyb3BwZWRBbm5vdGF0aW9ucywgaGFzTGVuZ3RoKDEpKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCd3aXRob3V0IGRyb3BwaW5nIGl0cyByZXNvbHZhYmxlIHNpYmxpbmdzJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEoCiAgICAgICAgICBsaWJyYXJ5KCdAUmV0cnkoYXR0ZW1wdHMpXG5AVGltZW91dC5ub25lJyksCiAgICAgICAgKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLmFyZ3VtZW50c1sncmV0cnknXSwgaXNOdWxsKTsKICAgICAgICBleHBlY3QobWV0YWRhdGEuYXJndW1lbnRzWyd0aW1lb3V0J10sIGVxdWFscygnVGltZW91dC5ub25lJykpOwogICAgICAgIGV4cGVjdChtZXRhZGF0YS5kcm9wcGVkQW5ub3RhdGlvbnMsIGVxdWFscyhbJ0BSZXRyeShhdHRlbXB0cyknXSkpOwogICAgICB9KTsKICAgIH0pOwoKICAgIGdyb3VwKCdyZXNvbHZlcyBpbXBvcnQgcHJlZml4ZXMnLCAoKSB7CiAgICAgIHRlc3QoJ29uIGFuIGFubm90YXRpb24gd2l0aCBhcmd1bWVudHMnLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YSgnJycKQHQuU2tpcCgncHJlZml4ZWQnKQpAdC5UYWdzKFsnc2xvdyddKQppbXBvcnQgJ3BhY2thZ2U6dGVzdC90ZXN0LmRhcnQnIGFzIHQ7Cgp2b2lkIG1haW4oKSB7fQonJycpOwoKICAgICAgICBleHBlY3QobWV0YWRhdGEuYXJndW1lbnRzWydza2lwJ10sIGVxdWFscygiJ3ByZWZpeGVkJyIpKTsKICAgICAgICBleHBlY3QobWV0YWRhdGEuYXJndW1lbnRzWyd0YWdzJ10sIGVxdWFscygiWydzbG93J10iKSk7CiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLnRhZ05hbWVzLCBlcXVhbHMoeydzbG93J30pKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdvbiBhIHByZWZpeGVkIG5hbWVkIGNvbnN0cnVjdG9yJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEoJycnCkB0LlRpbWVvdXQuZmFjdG9yKDIpCmltcG9ydCAncGFja2FnZTp0ZXN0L3Rlc3QuZGFydCcgYXMgdDsKCnZvaWQgbWFpbigpIHt9CicnJyk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YS5hcmd1bWVudHNbJ3RpbWVvdXQnXSwgZXF1YWxzKCdUaW1lb3V0LmZhY3RvcigyKScpKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCd3aXRob3V0IGNvbmZ1c2luZyBhIG5hbWVkIGNvbnN0cnVjdG9yIGZvciBhIHByZWZpeCcsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKCcnJwpAVGltZW91dC5ub25lCmltcG9ydCAncGFja2FnZTp0ZXN0L3Rlc3QuZGFydCcgYXMgdDsKCnZvaWQgbWFpbigpIHt9CicnJyk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YS5hcmd1bWVudHNbJ3RpbWVvdXQnXSwgZXF1YWxzKCdUaW1lb3V0Lm5vbmUnKSk7CiAgICAgIH0pOwogICAgfSk7CgogICAgZ3JvdXAoJ3JlYWRzIHRhZyBuYW1lcycsICgpIHsKICAgICAgdGVzdCgnZnJvbSBhIHNpbmdsZSB0YWcnLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YShsaWJyYXJ5KCJAVGFncyhbJ29ubHknXSkiKSk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YS50YWdOYW1lcywgZXF1YWxzKHsnb25seSd9KSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnZnJvbSBhIGxpc3Qgc3Bhbm5pbmcgc2V2ZXJhbCBsaW5lcycsICgpIHsKICAgICAgICBmaW5hbCBtZXRhZGF0YSA9IHBhcnNlVGVzdE1ldGFkYXRhKAogICAgICAgICAgbGlicmFyeSgiQFRhZ3MoW1xuICAnb3RoZXInLFxuICAnbGFzdCcsXG5dKSIpLAogICAgICAgICk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YS50YWdOYW1lcywgZXF1YWxzKHsnb3RoZXInLCAnbGFzdCd9KSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnYXMgZW1wdHkgZm9yIGFuIGFyZ3VtZW50IHRoYXQgaXMgbm90IGEgbGlzdCBsaXRlcmFsJywgKCkgewogICAgICAgIGZpbmFsIG1ldGFkYXRhID0gcGFyc2VUZXN0TWV0YWRhdGEobGlicmFyeSgnQFRhZ3Moc29tZVRhZ3MpJykpOwoKICAgICAgICBleHBlY3QobWV0YWRhdGEudGFnTmFtZXMsIGlzRW1wdHkpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ2FzIGVtcHR5IHdoZW4gdGhlIGFubm90YXRpb24gaXMgbm90IG9uIHRoZSBmaXJzdCBkaXJlY3RpdmUnLCAoKSB7CiAgICAgICAgZmluYWwgbWV0YWRhdGEgPSBwYXJzZVRlc3RNZXRhZGF0YSgnJycKQFRhZ3MoWydpZ25vcmVkJ10pCnZvaWQgbWFpbigpIHt9CicnJyk7CgogICAgICAgIGV4cGVjdChtZXRhZGF0YS50YWdOYW1lcywgaXNFbXB0eSk7CiAgICAgIH0pOwogICAgfSk7CiAgfSk7Cn0K", "type": "text", }, ], diff --git a/test/src/test_optimizer/test_optimizer_test.dart b/test/src/test_optimizer/test_optimizer_test.dart index fecc80282..d0afbc02b 100644 --- a/test/src/test_optimizer/test_optimizer_test.dart +++ b/test/src/test_optimizer/test_optimizer_test.dart @@ -182,8 +182,16 @@ void main() { test('moves tests matching a glob out of the bundle', () async { preGenVars['tests'] = [ - {'path': 'app/view/app_test.dart', 'identifier': '_a'}, - {'path': 'integration/login_test.dart', 'identifier': '_b'}, + { + 'path': 'app/view/app_test.dart', + 'identifier': '_a', + 'groupArguments': ", tags: ['slow']", + }, + { + 'path': 'integration/login_test.dart', + 'identifier': '_b', + 'groupArguments': '', + }, ]; final optimization = await buildOptimizer( @@ -199,7 +207,11 @@ void main() { any(), vars: { 'tests': [ - {'path': 'app/view/app_test.dart', 'identifier': '_a'}, + { + 'path': 'app/view/app_test.dart', + 'identifier': '_a', + 'groupArguments': ", tags: ['slow']", + }, ], 'notOptimizedTests': ['integration/login_test.dart'], },