diff --git a/bricks/test_optimizer/hooks/lib/pre_gen.dart b/bricks/test_optimizer/hooks/lib/pre_gen.dart index d522785bb..c0348ef02 100644 --- a/bricks/test_optimizer/hooks/lib/pre_gen.dart +++ b/bricks/test_optimizer/hooks/lib/pre_gen.dart @@ -8,6 +8,12 @@ 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, +); + Future run(HookContext context) async { final packageRoot = context.vars['package-root'] as String; final testDir = Directory(path.join(packageRoot, 'test')); @@ -29,8 +35,13 @@ Future run(HookContext context) async { final identifierGenerator = DartIdentifierGenerator(); final testIdentifierTable = >[]; - for (final entity - in testDir.listSync(recursive: true).where((entity) => entity.isTest)) { + final tests = testDir + .listSync(recursive: true) + .where((entity) => entity.isTest); + + final notOptimizedTests = await getNotOptimizedTests(tests, testDir.path); + + for (final entity in tests) { final relativePath = path .relative(entity.path, from: testDir.path) .replaceAll(r'\', '/'); @@ -40,7 +51,15 @@ Future run(HookContext context) async { }); } - context.vars = {'tests': testIdentifierTable, 'isFlutter': isFlutter}; + final optimizedTestsIdentifierTable = testIdentifierTable + .where((e) => !notOptimizedTests.contains(e['path'])) + .toList(); + + context.vars = { + 'tests': optimizedTestsIdentifierTable, + 'isFlutter': isFlutter, + 'notOptimizedTests': notOptimizedTests, + }; } extension on FileSystemEntity { @@ -48,3 +67,36 @@ extension on FileSystemEntity { 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/test/pre_gen_test.dart b/bricks/test_optimizer/hooks/test/pre_gen_test.dart index b4fbde20a..2e96591cf 100644 --- a/bricks/test_optimizer/hooks/test/pre_gen_test.dart +++ b/bricks/test_optimizer/hooks/test/pre_gen_test.dart @@ -16,6 +16,26 @@ class _FakeContext extends Fake implements HookContext { Map vars = {}; } +final notOptimizedTestContent = + ''' +@Tags(['${pre_gen.skipVeryGoodOptimizationTag}']) +void main() { + test('test', () { + expect(1, 1); + }); +} +'''; + +final anotherNotOptimizedTestContent = + ''' +@Tags(['${pre_gen.skipVeryGoodOptimizationTag}', 'another_tag']) +void main() { + test('another test', () { + expect(1, 1); + }); +} +'''; + void main() { late Directory tempDirectory; @@ -86,6 +106,57 @@ dependencies: expect(context.vars['isFlutter'], true); }); + + test('with proper not optimized tests identification', () async { + File(path.join(tempDirectory.path, 'pubspec.yaml')).createSync(); + + final testDir = Directory(path.join(tempDirectory.path, 'test')) + ..createSync(); + File(path.join(testDir.path, 'test1_test.dart')).createSync(); + File(path.join(testDir.path, 'test2_test.dart')).createSync(); + File(path.join(testDir.path, 'no_test_here.dart')).createSync(); + File( + path.join(testDir.path, 'not_optimized_test.dart'), + ).writeAsStringSync(notOptimizedTestContent); + File( + path.join(testDir.path, 'another_not_optimized_test.dart'), + ).writeAsStringSync(anotherNotOptimizedTestContent); + + context.vars['package-root'] = tempDirectory.absolute.path; + + await pre_gen.run(context); + + final tests = context.vars['tests'] as List>; + final testsMap = {}; + for (final test in tests) { + final path = test['path']!; + final identifier = test['identifier']!; + testsMap[path] = identifier; + } + + final paths = testsMap.keys; + expect(paths, contains('test1_test.dart')); + expect(paths, contains('test2_test.dart')); + expect(paths, isNot(contains('no_test_here.dart'))); + expect(paths, isNot(contains('not_optimized_test.dart'))); + expect(paths, isNot(contains('another_not_optimized_test.dart'))); + + expect( + testsMap.values.toSet().length, + equals(tests.length), + reason: 'All tests files should have unique identifiers', + ); + final notOptimizedTests = + context.vars['notOptimizedTests'] as List; + expect( + notOptimizedTests, + contains('not_optimized_test.dart'), + ); + expect( + notOptimizedTests, + contains('another_not_optimized_test.dart'), + ); + }); }); group('Fails', () { @@ -155,5 +226,65 @@ 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); + }); + + test('matches single-line with multiple tags', () { + final content = + "@Tags(['${pre_gen.skipVeryGoodOptimizationTag}', 'chrome'])"; + expect(regex.hasMatch(content), isTrue); + }); + + test('matches multi-line tag list', () { + final content = + ''' + @Tags([ + '${pre_gen.skipVeryGoodOptimizationTag}', + 'chrome', + 'test', + ]) + '''; + expect(regex.hasMatch(content), isTrue); + }); + + test('matches multi-line where tag is not the first', () { + final content = + ''' + @Tags([ + 'chrome', + '${pre_gen.skipVeryGoodOptimizationTag}', + 'test', + ]) + '''; + expect(regex.hasMatch(content), isTrue); + }); + + test('does not match when tag missing', () { + const content = "@Tags(['chrome', 'test'])"; + expect(regex.hasMatch(content), isFalse); + }); + + 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 + }, + ); + }); }); } diff --git a/lib/src/cli/templates/test_optimizer_bundle.dart b/lib/src/cli/templates/test_optimizer_bundle.dart index 9d844641d..597bd5dff 100644 --- a/lib/src/cli/templates/test_optimizer_bundle.dart +++ b/lib/src/cli/templates/test_optimizer_bundle.dart @@ -23,7 +23,7 @@ final testOptimizerBundle = MasonBundle.fromJson({ { "path": "lib/pre_gen.dart", "data": - "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpob29rcy9kYXJ0X2lkZW50aWZpZXJfZ2VuZXJhdG9yLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bWFzb24vbWFzb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpwYXRoL3BhdGguZGFydCcgYXMgcGF0aDsKCnR5cGVkZWYgRXhpdEZuID0gTmV2ZXIgRnVuY3Rpb24oaW50IGNvZGUpOwoKRXhpdEZuIGV4aXRGbiA9IGV4aXQ7CgpGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQpIGFzeW5jIHsKICBmaW5hbCBwYWNrYWdlUm9vdCA9IGNvbnRleHQudmFyc1sncGFja2FnZS1yb290J10gYXMgU3RyaW5nOwogIGZpbmFsIHRlc3REaXIgPSBEaXJlY3RvcnkocGF0aC5qb2luKHBhY2thZ2VSb290LCAndGVzdCcpKTsKCiAgaWYgKCF0ZXN0RGlyLmV4aXN0c1N5bmMoKSkgewogICAgY29udGV4dC5sb2dnZXIuZXJyKCdDb3VsZCBub3QgZmluZCBkaXJlY3RvcnkgJHt0ZXN0RGlyLnBhdGh9Jyk7CiAgICBleGl0Rm4oMSk7CiAgfQoKICBmaW5hbCBwdWJzcGVjID0gRmlsZShwYXRoLmpvaW4ocGFja2FnZVJvb3QsICdwdWJzcGVjLnlhbWwnKSk7CiAgaWYgKCFwdWJzcGVjLmV4aXN0c1N5bmMoKSkgewogICAgY29udGV4dC5sb2dnZXIuZXJyKCdDb3VsZCBub3QgZmluZCBwdWJzcGVjLnlhbWwgYXQgJHt0ZXN0RGlyLnBhdGh9Jyk7CiAgICBleGl0Rm4oMSk7CiAgfQoKICBmaW5hbCBwdWJzcGVjQ29udGVudHMgPSBhd2FpdCBwdWJzcGVjLnJlYWRBc1N0cmluZygpOwogIGZpbmFsIGZsdXR0ZXJTZGtSZWdFeHAgPSBSZWdFeHAocidzZGs6XHMqZmx1dHRlciQnLCBtdWx0aUxpbmU6IHRydWUpOwogIGZpbmFsIGlzRmx1dHRlciA9IGZsdXR0ZXJTZGtSZWdFeHAuaGFzTWF0Y2gocHVic3BlY0NvbnRlbnRzKTsKCiAgZmluYWwgaWRlbnRpZmllckdlbmVyYXRvciA9IERhcnRJZGVudGlmaWVyR2VuZXJhdG9yKCk7CiAgZmluYWwgdGVzdElkZW50aWZpZXJUYWJsZSA9IDxNYXA8U3RyaW5nLCBTdHJpbmc+PltdOwogIGZvciAoZmluYWwgZW50aXR5CiAgICAgIGluIHRlc3REaXIubGlzdFN5bmMocmVjdXJzaXZlOiB0cnVlKS53aGVyZSgoZW50aXR5KSA9PiBlbnRpdHkuaXNUZXN0KSkgewogICAgZmluYWwgcmVsYXRpdmVQYXRoID0gcGF0aAogICAgICAgIC5yZWxhdGl2ZShlbnRpdHkucGF0aCwgZnJvbTogdGVzdERpci5wYXRoKQogICAgICAgIC5yZXBsYWNlQWxsKHInXCcsICcvJyk7CiAgICB0ZXN0SWRlbnRpZmllclRhYmxlLmFkZCh7CiAgICAgICdwYXRoJzogcmVsYXRpdmVQYXRoLAogICAgICAnaWRlbnRpZmllcic6IGlkZW50aWZpZXJHZW5lcmF0b3IubmV4dCgpLAogICAgfSk7CiAgfQoKICBjb250ZXh0LnZhcnMgPSB7J3Rlc3RzJzogdGVzdElkZW50aWZpZXJUYWJsZSwgJ2lzRmx1dHRlcic6IGlzRmx1dHRlcn07Cn0KCmV4dGVuc2lvbiBvbiBGaWxlU3lzdGVtRW50aXR5IHsKICBib29sIGdldCBpc1Rlc3QgewogICAgcmV0dXJuIHRoaXMgaXMgRmlsZSAmJiBwYXRoLmJhc2VuYW1lKHRoaXMucGF0aCkuZW5kc1dpdGgoJ190ZXN0LmRhcnQnKTsKICB9Cn0K", + "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpob29rcy9kYXJ0X2lkZW50aWZpZXJfZ2VuZXJhdG9yLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bWFzb24vbWFzb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpwYXRoL3BhdGguZGFydCcgYXMgcGF0aDsKCnR5cGVkZWYgRXhpdEZuID0gTmV2ZXIgRnVuY3Rpb24oaW50IGNvZGUpOwoKRXhpdEZuIGV4aXRGbiA9IGV4aXQ7CgpTdHJpbmcgc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnID0gJ3NraXBfdmVyeV9nb29kX29wdGltaXphdGlvbic7ClJlZ0V4cCBza2lwVmVyeUdvb2RPcHRpbWl6YXRpb25SZWdFeHAgPSBSZWdFeHAoCiAgIkBUYWdzXFxzKlxcKFxccypcXFtbXFxzXFxTXSo/W1wiJ10kc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnW1wiJ11bXFxzXFxTXSo/XFxdXFxzKlxcKSIsCiAgbXVsdGlMaW5lOiB0cnVlLAopOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgZmluYWwgcGFja2FnZVJvb3QgPSBjb250ZXh0LnZhcnNbJ3BhY2thZ2Utcm9vdCddIGFzIFN0cmluZzsKICBmaW5hbCB0ZXN0RGlyID0gRGlyZWN0b3J5KHBhdGguam9pbihwYWNrYWdlUm9vdCwgJ3Rlc3QnKSk7CgogIGlmICghdGVzdERpci5leGlzdHNTeW5jKCkpIHsKICAgIGNvbnRleHQubG9nZ2VyLmVycignQ291bGQgbm90IGZpbmQgZGlyZWN0b3J5ICR7dGVzdERpci5wYXRofScpOwogICAgZXhpdEZuKDEpOwogIH0KCiAgZmluYWwgcHVic3BlYyA9IEZpbGUocGF0aC5qb2luKHBhY2thZ2VSb290LCAncHVic3BlYy55YW1sJykpOwogIGlmICghcHVic3BlYy5leGlzdHNTeW5jKCkpIHsKICAgIGNvbnRleHQubG9nZ2VyLmVycignQ291bGQgbm90IGZpbmQgcHVic3BlYy55YW1sIGF0ICR7dGVzdERpci5wYXRofScpOwogICAgZXhpdEZuKDEpOwogIH0KCiAgZmluYWwgcHVic3BlY0NvbnRlbnRzID0gYXdhaXQgcHVic3BlYy5yZWFkQXNTdHJpbmcoKTsKICBmaW5hbCBmbHV0dGVyU2RrUmVnRXhwID0gUmVnRXhwKHInc2RrOlxzKmZsdXR0ZXIkJywgbXVsdGlMaW5lOiB0cnVlKTsKICBmaW5hbCBpc0ZsdXR0ZXIgPSBmbHV0dGVyU2RrUmVnRXhwLmhhc01hdGNoKHB1YnNwZWNDb250ZW50cyk7CgogIGZpbmFsIGlkZW50aWZpZXJHZW5lcmF0b3IgPSBEYXJ0SWRlbnRpZmllckdlbmVyYXRvcigpOwogIGZpbmFsIHRlc3RJZGVudGlmaWVyVGFibGUgPSA8TWFwPFN0cmluZywgU3RyaW5nPj5bXTsKICBmaW5hbCB0ZXN0cyA9IHRlc3REaXIKICAgICAgLmxpc3RTeW5jKHJlY3Vyc2l2ZTogdHJ1ZSkKICAgICAgLndoZXJlKChlbnRpdHkpID0+IGVudGl0eS5pc1Rlc3QpOwoKICBmaW5hbCBub3RPcHRpbWl6ZWRUZXN0cyA9IGF3YWl0IGdldE5vdE9wdGltaXplZFRlc3RzKHRlc3RzLCB0ZXN0RGlyLnBhdGgpOwoKICBmb3IgKGZpbmFsIGVudGl0eSBpbiB0ZXN0cykgewogICAgZmluYWwgcmVsYXRpdmVQYXRoID0gcGF0aAogICAgICAgIC5yZWxhdGl2ZShlbnRpdHkucGF0aCwgZnJvbTogdGVzdERpci5wYXRoKQogICAgICAgIC5yZXBsYWNlQWxsKHInXCcsICcvJyk7CiAgICB0ZXN0SWRlbnRpZmllclRhYmxlLmFkZCh7CiAgICAgICdwYXRoJzogcmVsYXRpdmVQYXRoLAogICAgICAnaWRlbnRpZmllcic6IGlkZW50aWZpZXJHZW5lcmF0b3IubmV4dCgpLAogICAgfSk7CiAgfQoKICBmaW5hbCBvcHRpbWl6ZWRUZXN0c0lkZW50aWZpZXJUYWJsZSA9IHRlc3RJZGVudGlmaWVyVGFibGUKICAgICAgLndoZXJlKChlKSA9PiAhbm90T3B0aW1pemVkVGVzdHMuY29udGFpbnMoZVsncGF0aCddKSkKICAgICAgLnRvTGlzdCgpOwoKICBjb250ZXh0LnZhcnMgPSB7CiAgICAndGVzdHMnOiBvcHRpbWl6ZWRUZXN0c0lkZW50aWZpZXJUYWJsZSwKICAgICdpc0ZsdXR0ZXInOiBpc0ZsdXR0ZXIsCiAgICAnbm90T3B0aW1pemVkVGVzdHMnOiBub3RPcHRpbWl6ZWRUZXN0cywKICB9Owp9CgpleHRlbnNpb24gb24gRmlsZVN5c3RlbUVudGl0eSB7CiAgYm9vbCBnZXQgaXNUZXN0IHsKICAgIHJldHVybiB0aGlzIGlzIEZpbGUgJiYgcGF0aC5iYXNlbmFtZSh0aGlzLnBhdGgpLmVuZHNXaXRoKCdfdGVzdC5kYXJ0Jyk7CiAgfQp9CgpGdXR1cmU8TGlzdDxTdHJpbmc+PiBnZXROb3RPcHRpbWl6ZWRUZXN0cygKICBJdGVyYWJsZTxGaWxlU3lzdGVtRW50aXR5PiB0ZXN0cywKICBTdHJpbmcgdGVzdERpciwKKSBhc3luYyB7CiAgZmluYWwgcGF0aHMgPSB0ZXN0cy5tYXAoKGUpID0+IGUucGF0aCkudG9MaXN0KCk7CiAgZmluYWwgZm9ybWF0dGVkUGF0aHMgPSBwYXRocy5tYXAoKGUpID0+IGUucmVwbGFjZUFsbCgnLy4vJywgJy8nKSkudG9MaXN0KCk7CgogIGZpbmFsIGZpbGVGdXR1cmVzID0gZm9ybWF0dGVkUGF0aHMubWFwKF9jaGVja0ZpbGVGb3JTa2lwVmVyeUdvb2RPcHRpbWl6YXRpb24pOwogIGZpbmFsIGZpbGVSZXN1bHRzID0gYXdhaXQgRnV0dXJlLndhaXQoZmlsZUZ1dHVyZXMpOwoKICBmaW5hbCB0ZXN0V2l0aFZlcnlHb29kVGVzdCA9IDxTdHJpbmc+W107CiAgZm9yICh2YXIgaSA9IDA7IGkgPCBmb3JtYXR0ZWRQYXRocy5sZW5ndGg7IGkrKykgewogICAgaWYgKGZpbGVSZXN1bHRzW2ldKSB7CiAgICAgIHRlc3RXaXRoVmVyeUdvb2RUZXN0LmFkZChmb3JtYXR0ZWRQYXRoc1tpXSk7CiAgICB9CiAgfQoKICAvLy8gRm9ybWF0IHRvIHJlbGF0aXZlIHBhdGgKICBmaW5hbCByZWxhdGl2ZVBhdGhzID0gdGVzdFdpdGhWZXJ5R29vZFRlc3QKICAgICAgLm1hcCgoZSkgPT4gcGF0aC5yZWxhdGl2ZShlLCBmcm9tOiB0ZXN0RGlyKSkKICAgICAgLnRvTGlzdCgpOwoKICByZXR1cm4gcmVsYXRpdmVQYXRoczsKfQoKLy8vIENoZWNrIGlmIGEgc2luZ2xlIGZpbGUgY29udGFpbnMgc2tpcF92ZXJ5X2dvb2Rfb3B0aW1pemF0aW9uIHRhZwpGdXR1cmU8Ym9vbD4gX2NoZWNrRmlsZUZvclNraXBWZXJ5R29vZE9wdGltaXphdGlvbihTdHJpbmcgcGF0aCkgYXN5bmMgewogIGZpbmFsIGZpbGUgPSBGaWxlKHBhdGgpOwogIGlmICghZmlsZS5leGlzdHNTeW5jKCkpIHJldHVybiBmYWxzZTsKICBmaW5hbCBjb250ZW50ID0gYXdhaXQgZmlsZS5yZWFkQXNTdHJpbmcoKTsKICByZXR1cm4gc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uUmVnRXhwLmhhc01hdGNoKGNvbnRlbnQpOwp9Cg==", "type": "text", }, { @@ -47,7 +47,7 @@ final testOptimizerBundle = MasonBundle.fromJson({ { "path": "test/pre_gen_test.dart", "data": - "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpob29rcy9wcmVfZ2VuLmRhcnQnIGFzIHByZV9nZW47CmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm1vY2t0YWlsL21vY2t0YWlsLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6cGF0aC9wYXRoLmRhcnQnIGFzIHBhdGg7CmltcG9ydCAncGFja2FnZTp0ZXN0L3Rlc3QuZGFydCc7CgpjbGFzcyBfTW9ja0xvZ2dlciBleHRlbmRzIE1vY2sgaW1wbGVtZW50cyBMb2dnZXIge30KCmNsYXNzIF9GYWtlQ29udGV4dCBleHRlbmRzIEZha2UgaW1wbGVtZW50cyBIb29rQ29udGV4dCB7CiAgQG92ZXJyaWRlCiAgZmluYWwgbG9nZ2VyID0gX01vY2tMb2dnZXIoKTsKCiAgQG92ZXJyaWRlCiAgTWFwPFN0cmluZywgT2JqZWN0Pz4gdmFycyA9IHt9Owp9Cgp2b2lkIG1haW4oKSB7CiAgbGF0ZSBEaXJlY3RvcnkgdGVtcERpcmVjdG9yeTsKCiAgc2V0VXAoKCkgewogICAgdGVtcERpcmVjdG9yeSA9IERpcmVjdG9yeS5zeXN0ZW1UZW1wLmNyZWF0ZVRlbXBTeW5jKCd0ZXN0X29wdGltaXplcicpOwogIH0pOwoKICB0ZWFyRG93bigoKSB7CiAgICB0ZW1wRGlyZWN0b3J5LmRlbGV0ZVN5bmMocmVjdXJzaXZlOiB0cnVlKTsKICB9KTsKCiAgZ3JvdXAoJ1ByZSBnZW4gaG9vaycsICgpIHsKICAgIGxhdGUgSG9va0NvbnRleHQgY29udGV4dDsKCiAgICBzZXRVcCgoKSB7CiAgICAgIGNvbnRleHQgPSBfRmFrZUNvbnRleHQoKTsKICAgIH0pOwoKICAgIGdyb3VwKCdDb21wbGV0ZXMnLCAoKSB7CiAgICAgIHRlc3QoJ3dpdGggdGVzdCBmaWxlcyBsaXN0JywgKCkgYXN5bmMgewogICAgICAgIEZpbGUocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3B1YnNwZWMueWFtbCcpKS5jcmVhdGVTeW5jKCk7CgogICAgICAgIGZpbmFsIHRlc3REaXIgPSBEaXJlY3RvcnkocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3Rlc3QnKSkKICAgICAgICAgIC4uY3JlYXRlU3luYygpOwogICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ3Rlc3QxX3Rlc3QuZGFydCcpKS5jcmVhdGVTeW5jKCk7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVzdERpci5wYXRoLCAndGVzdDJfdGVzdC5kYXJ0JykpLmNyZWF0ZVN5bmMoKTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICdub190ZXN0X2hlcmUuZGFydCcpKS5jcmVhdGVTeW5jKCk7CgogICAgICAgIGNvbnRleHQudmFyc1sncGFja2FnZS1yb290J10gPSB0ZW1wRGlyZWN0b3J5LmFic29sdXRlLnBhdGg7CgogICAgICAgIGF3YWl0IHByZV9nZW4ucnVuKGNvbnRleHQpOwoKICAgICAgICBmaW5hbCB0ZXN0cyA9IGNvbnRleHQudmFyc1sndGVzdHMnXSBhcyBMaXN0PE1hcDxTdHJpbmcsIFN0cmluZz4+OwogICAgICAgIGZpbmFsIHRlc3RzTWFwID0gPFN0cmluZywgU3RyaW5nPnt9OwogICAgICAgIGZvciAoZmluYWwgdGVzdCBpbiB0ZXN0cykgewogICAgICAgICAgZmluYWwgcGF0aCA9IHRlc3RbJ3BhdGgnXSE7CiAgICAgICAgICBmaW5hbCBpZGVudGlmaWVyID0gdGVzdFsnaWRlbnRpZmllciddITsKICAgICAgICAgIHRlc3RzTWFwW3BhdGhdID0gaWRlbnRpZmllcjsKICAgICAgICB9CgogICAgICAgIGZpbmFsIHBhdGhzID0gdGVzdHNNYXAua2V5czsKICAgICAgICBleHBlY3QocGF0aHMsIGNvbnRhaW5zKCd0ZXN0MV90ZXN0LmRhcnQnKSk7CiAgICAgICAgZXhwZWN0KHBhdGhzLCBjb250YWlucygndGVzdDJfdGVzdC5kYXJ0JykpOwogICAgICAgIGV4cGVjdChwYXRocywgaXNOb3QoY29udGFpbnMoJ25vX3Rlc3RfaGVyZS5kYXJ0JykpKTsKCiAgICAgICAgZXhwZWN0KAogICAgICAgICAgdGVzdHNNYXAudmFsdWVzLnRvU2V0KCkubGVuZ3RoLAogICAgICAgICAgZXF1YWxzKHRlc3RzLmxlbmd0aCksCiAgICAgICAgICByZWFzb246ICdBbGwgdGVzdHMgZmlsZXMgc2hvdWxkIGhhdmUgdW5pcXVlIGlkZW50aWZpZXJzJywKICAgICAgICApOwoKICAgICAgICBleHBlY3QoY29udGV4dC52YXJzWydpc0ZsdXR0ZXInXSwgZmFsc2UpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ3dpdGggcHJvcGVyIGlzRmx1dHRlciBpZGVudGlmaWNhdGlvbicsICgpIGFzeW5jIHsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICdwdWJzcGVjLnlhbWwnKSkKICAgICAgICAgIC4uY3JlYXRlU3luYygpCiAgICAgICAgICAuLndyaXRlQXNTdHJpbmdTeW5jKCcnJwpkZXBlbmRlbmNpZXM6CiAgZmx1dHRlcjoKICAgIHNkazogZmx1dHRlcicnJyk7CgogICAgICAgIERpcmVjdG9yeShwYXRoLmpvaW4odGVtcERpcmVjdG9yeS5wYXRoLCAndGVzdCcpKS5jcmVhdGVTeW5jKCk7CgogICAgICAgIGNvbnRleHQudmFyc1sncGFja2FnZS1yb290J10gPSB0ZW1wRGlyZWN0b3J5LmFic29sdXRlLnBhdGg7CgogICAgICAgIGF3YWl0IHByZV9nZW4ucnVuKGNvbnRleHQpOwoKICAgICAgICBleHBlY3QoY29udGV4dC52YXJzWydpc0ZsdXR0ZXInXSwgdHJ1ZSk7CiAgICAgIH0pOwogICAgfSk7CgogICAgZ3JvdXAoJ0ZhaWxzJywgKCkgewogICAgICBzZXRVcCgoKSB7CiAgICAgICAgcHJlX2dlbi5leGl0Rm4gPSAoY29kZSkgewogICAgICAgICAgdGhyb3cgUHJvY2Vzc0V4Y2VwdGlvbignZXhpdCcsIFtjb2RlLnRvU3RyaW5nKCldKTsKICAgICAgICB9OwogICAgICB9KTsKCiAgICAgIHRlYXJEb3duKCgpIHsKICAgICAgICBwcmVfZ2VuLmV4aXRGbiA9IGV4aXQ7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnd2hlbiB0YXJnZXQgdGVzdCBkaXIgZG9lcyBub3QgZXhpc3QnLCAoKSBhc3luYyB7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVtcERpcmVjdG9yeS5wYXRoLCAncHVic3BlYy55YW1sJykpLmNyZWF0ZVN5bmMoKTsKCiAgICAgICAgZmluYWwgdGVzdERpciA9IERpcmVjdG9yeShwYXRoLmpvaW4odGVtcERpcmVjdG9yeS5wYXRoLCAndGVzdCcpKTsKCiAgICAgICAgY29udGV4dC52YXJzWydwYWNrYWdlLXJvb3QnXSA9IHRlbXBEaXJlY3RvcnkuYWJzb2x1dGUucGF0aDsKCiAgICAgICAgYXdhaXQgZXhwZWN0TGF0ZXIoCiAgICAgICAgICAoKSA9PiBwcmVfZ2VuLnJ1bihjb250ZXh0KSwKICAgICAgICAgIHRocm93c0EoCiAgICAgICAgICAgIGlzQTxQcm9jZXNzRXhjZXB0aW9uPigpLmhhdmluZygKICAgICAgICAgICAgICAoZXgpID0+IGV4LmFyZ3VtZW50cy5maXJzdCwKICAgICAgICAgICAgICAnZXJyb3IgY29kZScsCiAgICAgICAgICAgICAgZXF1YWxzKCcxJyksCiAgICAgICAgICAgICksCiAgICAgICAgICApLAogICAgICAgICk7CgogICAgICAgIHZlcmlmeSgKICAgICAgICAgICgpID0+IGNvbnRleHQubG9nZ2VyLmVycignQ291bGQgbm90IGZpbmQgZGlyZWN0b3J5ICR7dGVzdERpci5wYXRofScpLAogICAgICAgICkuY2FsbGVkKDEpOwoKICAgICAgICBleHBlY3QoY29udGV4dC52YXJzWyd0ZXN0cyddLCBpc051bGwpOwogICAgICAgIGV4cGVjdChjb250ZXh0LnZhcnNbJ2lzRmx1dHRlciddLCBpc051bGwpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ3doZW4gdGFyZ2V0IGRpciBkb2VzIG5vdCBjb250YWluIGEgcHVic3BlYy55YW1sJywgKCkgYXN5bmMgewogICAgICAgIGZpbmFsIHRlc3REaXIgPSBEaXJlY3RvcnkocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3Rlc3QnKSkKICAgICAgICAgIC4uY3JlYXRlU3luYygpOwogICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ3Rlc3QxX3Rlc3QuZGFydCcpKS5jcmVhdGVTeW5jKCk7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVzdERpci5wYXRoLCAndGVzdDJfdGVzdC5kYXJ0JykpLmNyZWF0ZVN5bmMoKTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICdub190ZXN0X2hlcmUuZGFydCcpKS5jcmVhdGVTeW5jKCk7CgogICAgICAgIGNvbnRleHQudmFyc1sncGFja2FnZS1yb290J10gPSB0ZW1wRGlyZWN0b3J5LmFic29sdXRlLnBhdGg7CgogICAgICAgIGF3YWl0IGV4cGVjdExhdGVyKAogICAgICAgICAgKCkgPT4gcHJlX2dlbi5ydW4oY29udGV4dCksCiAgICAgICAgICB0aHJvd3NBKAogICAgICAgICAgICBpc0E8UHJvY2Vzc0V4Y2VwdGlvbj4oKS5oYXZpbmcoCiAgICAgICAgICAgICAgKGV4KSA9PiBleC5hcmd1bWVudHMuZmlyc3QsCiAgICAgICAgICAgICAgJ2Vycm9yIGNvZGUnLAogICAgICAgICAgICAgIGVxdWFscygnMScpLAogICAgICAgICAgICApLAogICAgICAgICAgKSwKICAgICAgICApOwoKICAgICAgICB2ZXJpZnkoCiAgICAgICAgICAoKSA9PiBjb250ZXh0LmxvZ2dlci5lcnIoCiAgICAgICAgICAgICdDb3VsZCBub3QgZmluZCBwdWJzcGVjLnlhbWwgYXQgJHt0ZXN0RGlyLnBhdGh9JywKICAgICAgICAgICksCiAgICAgICAgKS5jYWxsZWQoMSk7CgogICAgICAgIGV4cGVjdChjb250ZXh0LnZhcnNbJ3Rlc3RzJ10sIGlzTnVsbCk7CiAgICAgICAgZXhwZWN0KGNvbnRleHQudmFyc1snaXNGbHV0dGVyJ10sIGlzTnVsbCk7CiAgICAgIH0pOwogICAgfSk7CiAgfSk7Cn0K", + "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpob29rcy9wcmVfZ2VuLmRhcnQnIGFzIHByZV9nZW47CmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm1vY2t0YWlsL21vY2t0YWlsLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6cGF0aC9wYXRoLmRhcnQnIGFzIHBhdGg7CmltcG9ydCAncGFja2FnZTp0ZXN0L3Rlc3QuZGFydCc7CgpjbGFzcyBfTW9ja0xvZ2dlciBleHRlbmRzIE1vY2sgaW1wbGVtZW50cyBMb2dnZXIge30KCmNsYXNzIF9GYWtlQ29udGV4dCBleHRlbmRzIEZha2UgaW1wbGVtZW50cyBIb29rQ29udGV4dCB7CiAgQG92ZXJyaWRlCiAgZmluYWwgbG9nZ2VyID0gX01vY2tMb2dnZXIoKTsKCiAgQG92ZXJyaWRlCiAgTWFwPFN0cmluZywgT2JqZWN0Pz4gdmFycyA9IHt9Owp9CgpmaW5hbCBub3RPcHRpbWl6ZWRUZXN0Q29udGVudCA9CiAgICAnJycKQFRhZ3MoWycke3ByZV9nZW4uc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnfSddKQp2b2lkIG1haW4oKSB7CiAgdGVzdCgndGVzdCcsICgpIHsKICAgIGV4cGVjdCgxLCAxKTsKICB9KTsKfQonJyc7CgpmaW5hbCBhbm90aGVyTm90T3B0aW1pemVkVGVzdENvbnRlbnQgPQogICAgJycnCkBUYWdzKFsnJHtwcmVfZ2VuLnNraXBWZXJ5R29vZE9wdGltaXphdGlvblRhZ30nLCAnYW5vdGhlcl90YWcnXSkKdm9pZCBtYWluKCkgewogIHRlc3QoJ2Fub3RoZXIgdGVzdCcsICgpIHsKICAgIGV4cGVjdCgxLCAxKTsKICB9KTsKfQonJyc7Cgp2b2lkIG1haW4oKSB7CiAgbGF0ZSBEaXJlY3RvcnkgdGVtcERpcmVjdG9yeTsKCiAgc2V0VXAoKCkgewogICAgdGVtcERpcmVjdG9yeSA9IERpcmVjdG9yeS5zeXN0ZW1UZW1wLmNyZWF0ZVRlbXBTeW5jKCd0ZXN0X29wdGltaXplcicpOwogIH0pOwoKICB0ZWFyRG93bigoKSB7CiAgICB0ZW1wRGlyZWN0b3J5LmRlbGV0ZVN5bmMocmVjdXJzaXZlOiB0cnVlKTsKICB9KTsKCiAgZ3JvdXAoJ1ByZSBnZW4gaG9vaycsICgpIHsKICAgIGxhdGUgSG9va0NvbnRleHQgY29udGV4dDsKCiAgICBzZXRVcCgoKSB7CiAgICAgIGNvbnRleHQgPSBfRmFrZUNvbnRleHQoKTsKICAgIH0pOwoKICAgIGdyb3VwKCdDb21wbGV0ZXMnLCAoKSB7CiAgICAgIHRlc3QoJ3dpdGggdGVzdCBmaWxlcyBsaXN0JywgKCkgYXN5bmMgewogICAgICAgIEZpbGUocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3B1YnNwZWMueWFtbCcpKS5jcmVhdGVTeW5jKCk7CgogICAgICAgIGZpbmFsIHRlc3REaXIgPSBEaXJlY3RvcnkocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3Rlc3QnKSkKICAgICAgICAgIC4uY3JlYXRlU3luYygpOwogICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ3Rlc3QxX3Rlc3QuZGFydCcpKS5jcmVhdGVTeW5jKCk7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVzdERpci5wYXRoLCAndGVzdDJfdGVzdC5kYXJ0JykpLmNyZWF0ZVN5bmMoKTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICdub190ZXN0X2hlcmUuZGFydCcpKS5jcmVhdGVTeW5jKCk7CgogICAgICAgIGNvbnRleHQudmFyc1sncGFja2FnZS1yb290J10gPSB0ZW1wRGlyZWN0b3J5LmFic29sdXRlLnBhdGg7CgogICAgICAgIGF3YWl0IHByZV9nZW4ucnVuKGNvbnRleHQpOwoKICAgICAgICBmaW5hbCB0ZXN0cyA9IGNvbnRleHQudmFyc1sndGVzdHMnXSBhcyBMaXN0PE1hcDxTdHJpbmcsIFN0cmluZz4+OwogICAgICAgIGZpbmFsIHRlc3RzTWFwID0gPFN0cmluZywgU3RyaW5nPnt9OwogICAgICAgIGZvciAoZmluYWwgdGVzdCBpbiB0ZXN0cykgewogICAgICAgICAgZmluYWwgcGF0aCA9IHRlc3RbJ3BhdGgnXSE7CiAgICAgICAgICBmaW5hbCBpZGVudGlmaWVyID0gdGVzdFsnaWRlbnRpZmllciddITsKICAgICAgICAgIHRlc3RzTWFwW3BhdGhdID0gaWRlbnRpZmllcjsKICAgICAgICB9CgogICAgICAgIGZpbmFsIHBhdGhzID0gdGVzdHNNYXAua2V5czsKICAgICAgICBleHBlY3QocGF0aHMsIGNvbnRhaW5zKCd0ZXN0MV90ZXN0LmRhcnQnKSk7CiAgICAgICAgZXhwZWN0KHBhdGhzLCBjb250YWlucygndGVzdDJfdGVzdC5kYXJ0JykpOwogICAgICAgIGV4cGVjdChwYXRocywgaXNOb3QoY29udGFpbnMoJ25vX3Rlc3RfaGVyZS5kYXJ0JykpKTsKCiAgICAgICAgZXhwZWN0KAogICAgICAgICAgdGVzdHNNYXAudmFsdWVzLnRvU2V0KCkubGVuZ3RoLAogICAgICAgICAgZXF1YWxzKHRlc3RzLmxlbmd0aCksCiAgICAgICAgICByZWFzb246ICdBbGwgdGVzdHMgZmlsZXMgc2hvdWxkIGhhdmUgdW5pcXVlIGlkZW50aWZpZXJzJywKICAgICAgICApOwoKICAgICAgICBleHBlY3QoY29udGV4dC52YXJzWydpc0ZsdXR0ZXInXSwgZmFsc2UpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ3dpdGggcHJvcGVyIGlzRmx1dHRlciBpZGVudGlmaWNhdGlvbicsICgpIGFzeW5jIHsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICdwdWJzcGVjLnlhbWwnKSkKICAgICAgICAgIC4uY3JlYXRlU3luYygpCiAgICAgICAgICAuLndyaXRlQXNTdHJpbmdTeW5jKCcnJwpkZXBlbmRlbmNpZXM6CiAgZmx1dHRlcjoKICAgIHNkazogZmx1dHRlcicnJyk7CgogICAgICAgIERpcmVjdG9yeShwYXRoLmpvaW4odGVtcERpcmVjdG9yeS5wYXRoLCAndGVzdCcpKS5jcmVhdGVTeW5jKCk7CgogICAgICAgIGNvbnRleHQudmFyc1sncGFja2FnZS1yb290J10gPSB0ZW1wRGlyZWN0b3J5LmFic29sdXRlLnBhdGg7CgogICAgICAgIGF3YWl0IHByZV9nZW4ucnVuKGNvbnRleHQpOwoKICAgICAgICBleHBlY3QoY29udGV4dC52YXJzWydpc0ZsdXR0ZXInXSwgdHJ1ZSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnd2l0aCBwcm9wZXIgbm90IG9wdGltaXplZCB0ZXN0cyBpZGVudGlmaWNhdGlvbicsICgpIGFzeW5jIHsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICdwdWJzcGVjLnlhbWwnKSkuY3JlYXRlU3luYygpOwoKICAgICAgICBmaW5hbCB0ZXN0RGlyID0gRGlyZWN0b3J5KHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICd0ZXN0JykpCiAgICAgICAgICAuLmNyZWF0ZVN5bmMoKTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICd0ZXN0MV90ZXN0LmRhcnQnKSkuY3JlYXRlU3luYygpOwogICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ3Rlc3QyX3Rlc3QuZGFydCcpKS5jcmVhdGVTeW5jKCk7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVzdERpci5wYXRoLCAnbm9fdGVzdF9oZXJlLmRhcnQnKSkuY3JlYXRlU3luYygpOwogICAgICAgIEZpbGUoCiAgICAgICAgICBwYXRoLmpvaW4odGVzdERpci5wYXRoLCAnbm90X29wdGltaXplZF90ZXN0LmRhcnQnKSwKICAgICAgICApLndyaXRlQXNTdHJpbmdTeW5jKG5vdE9wdGltaXplZFRlc3RDb250ZW50KTsKICAgICAgICBGaWxlKAogICAgICAgICAgcGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ2Fub3RoZXJfbm90X29wdGltaXplZF90ZXN0LmRhcnQnKSwKICAgICAgICApLndyaXRlQXNTdHJpbmdTeW5jKGFub3RoZXJOb3RPcHRpbWl6ZWRUZXN0Q29udGVudCk7CgogICAgICAgIGNvbnRleHQudmFyc1sncGFja2FnZS1yb290J10gPSB0ZW1wRGlyZWN0b3J5LmFic29sdXRlLnBhdGg7CgogICAgICAgIGF3YWl0IHByZV9nZW4ucnVuKGNvbnRleHQpOwoKICAgICAgICBmaW5hbCB0ZXN0cyA9IGNvbnRleHQudmFyc1sndGVzdHMnXSBhcyBMaXN0PE1hcDxTdHJpbmcsIFN0cmluZz4+OwogICAgICAgIGZpbmFsIHRlc3RzTWFwID0gPFN0cmluZywgU3RyaW5nPnt9OwogICAgICAgIGZvciAoZmluYWwgdGVzdCBpbiB0ZXN0cykgewogICAgICAgICAgZmluYWwgcGF0aCA9IHRlc3RbJ3BhdGgnXSE7CiAgICAgICAgICBmaW5hbCBpZGVudGlmaWVyID0gdGVzdFsnaWRlbnRpZmllciddITsKICAgICAgICAgIHRlc3RzTWFwW3BhdGhdID0gaWRlbnRpZmllcjsKICAgICAgICB9CgogICAgICAgIGZpbmFsIHBhdGhzID0gdGVzdHNNYXAua2V5czsKICAgICAgICBleHBlY3QocGF0aHMsIGNvbnRhaW5zKCd0ZXN0MV90ZXN0LmRhcnQnKSk7CiAgICAgICAgZXhwZWN0KHBhdGhzLCBjb250YWlucygndGVzdDJfdGVzdC5kYXJ0JykpOwogICAgICAgIGV4cGVjdChwYXRocywgaXNOb3QoY29udGFpbnMoJ25vX3Rlc3RfaGVyZS5kYXJ0JykpKTsKICAgICAgICBleHBlY3QocGF0aHMsIGlzTm90KGNvbnRhaW5zKCdub3Rfb3B0aW1pemVkX3Rlc3QuZGFydCcpKSk7CiAgICAgICAgZXhwZWN0KHBhdGhzLCBpc05vdChjb250YWlucygnYW5vdGhlcl9ub3Rfb3B0aW1pemVkX3Rlc3QuZGFydCcpKSk7CgogICAgICAgIGV4cGVjdCgKICAgICAgICAgIHRlc3RzTWFwLnZhbHVlcy50b1NldCgpLmxlbmd0aCwKICAgICAgICAgIGVxdWFscyh0ZXN0cy5sZW5ndGgpLAogICAgICAgICAgcmVhc29uOiAnQWxsIHRlc3RzIGZpbGVzIHNob3VsZCBoYXZlIHVuaXF1ZSBpZGVudGlmaWVycycsCiAgICAgICAgKTsKICAgICAgICBmaW5hbCBub3RPcHRpbWl6ZWRUZXN0cyA9CiAgICAgICAgICAgIGNvbnRleHQudmFyc1snbm90T3B0aW1pemVkVGVzdHMnXSBhcyBMaXN0PFN0cmluZz47CiAgICAgICAgZXhwZWN0KAogICAgICAgICAgbm90T3B0aW1pemVkVGVzdHMsCiAgICAgICAgICBjb250YWlucygnbm90X29wdGltaXplZF90ZXN0LmRhcnQnKSwKICAgICAgICApOwogICAgICAgIGV4cGVjdCgKICAgICAgICAgIG5vdE9wdGltaXplZFRlc3RzLAogICAgICAgICAgY29udGFpbnMoJ2Fub3RoZXJfbm90X29wdGltaXplZF90ZXN0LmRhcnQnKSwKICAgICAgICApOwogICAgICB9KTsKICAgIH0pOwoKICAgIGdyb3VwKCdGYWlscycsICgpIHsKICAgICAgc2V0VXAoKCkgewogICAgICAgIHByZV9nZW4uZXhpdEZuID0gKGNvZGUpIHsKICAgICAgICAgIHRocm93IFByb2Nlc3NFeGNlcHRpb24oJ2V4aXQnLCBbY29kZS50b1N0cmluZygpXSk7CiAgICAgICAgfTsKICAgICAgfSk7CgogICAgICB0ZWFyRG93bigoKSB7CiAgICAgICAgcHJlX2dlbi5leGl0Rm4gPSBleGl0OwogICAgICB9KTsKCiAgICAgIHRlc3QoJ3doZW4gdGFyZ2V0IHRlc3QgZGlyIGRvZXMgbm90IGV4aXN0JywgKCkgYXN5bmMgewogICAgICAgIEZpbGUocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3B1YnNwZWMueWFtbCcpKS5jcmVhdGVTeW5jKCk7CgogICAgICAgIGZpbmFsIHRlc3REaXIgPSBEaXJlY3RvcnkocGF0aC5qb2luKHRlbXBEaXJlY3RvcnkucGF0aCwgJ3Rlc3QnKSk7CgogICAgICAgIGNvbnRleHQudmFyc1sncGFja2FnZS1yb290J10gPSB0ZW1wRGlyZWN0b3J5LmFic29sdXRlLnBhdGg7CgogICAgICAgIGF3YWl0IGV4cGVjdExhdGVyKAogICAgICAgICAgKCkgPT4gcHJlX2dlbi5ydW4oY29udGV4dCksCiAgICAgICAgICB0aHJvd3NBKAogICAgICAgICAgICBpc0E8UHJvY2Vzc0V4Y2VwdGlvbj4oKS5oYXZpbmcoCiAgICAgICAgICAgICAgKGV4KSA9PiBleC5hcmd1bWVudHMuZmlyc3QsCiAgICAgICAgICAgICAgJ2Vycm9yIGNvZGUnLAogICAgICAgICAgICAgIGVxdWFscygnMScpLAogICAgICAgICAgICApLAogICAgICAgICAgKSwKICAgICAgICApOwoKICAgICAgICB2ZXJpZnkoCiAgICAgICAgICAoKSA9PiBjb250ZXh0LmxvZ2dlci5lcnIoJ0NvdWxkIG5vdCBmaW5kIGRpcmVjdG9yeSAke3Rlc3REaXIucGF0aH0nKSwKICAgICAgICApLmNhbGxlZCgxKTsKCiAgICAgICAgZXhwZWN0KGNvbnRleHQudmFyc1sndGVzdHMnXSwgaXNOdWxsKTsKICAgICAgICBleHBlY3QoY29udGV4dC52YXJzWydpc0ZsdXR0ZXInXSwgaXNOdWxsKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCd3aGVuIHRhcmdldCBkaXIgZG9lcyBub3QgY29udGFpbiBhIHB1YnNwZWMueWFtbCcsICgpIGFzeW5jIHsKICAgICAgICBmaW5hbCB0ZXN0RGlyID0gRGlyZWN0b3J5KHBhdGguam9pbih0ZW1wRGlyZWN0b3J5LnBhdGgsICd0ZXN0JykpCiAgICAgICAgICAuLmNyZWF0ZVN5bmMoKTsKICAgICAgICBGaWxlKHBhdGguam9pbih0ZXN0RGlyLnBhdGgsICd0ZXN0MV90ZXN0LmRhcnQnKSkuY3JlYXRlU3luYygpOwogICAgICAgIEZpbGUocGF0aC5qb2luKHRlc3REaXIucGF0aCwgJ3Rlc3QyX3Rlc3QuZGFydCcpKS5jcmVhdGVTeW5jKCk7CiAgICAgICAgRmlsZShwYXRoLmpvaW4odGVzdERpci5wYXRoLCAnbm9fdGVzdF9oZXJlLmRhcnQnKSkuY3JlYXRlU3luYygpOwoKICAgICAgICBjb250ZXh0LnZhcnNbJ3BhY2thZ2Utcm9vdCddID0gdGVtcERpcmVjdG9yeS5hYnNvbHV0ZS5wYXRoOwoKICAgICAgICBhd2FpdCBleHBlY3RMYXRlcigKICAgICAgICAgICgpID0+IHByZV9nZW4ucnVuKGNvbnRleHQpLAogICAgICAgICAgdGhyb3dzQSgKICAgICAgICAgICAgaXNBPFByb2Nlc3NFeGNlcHRpb24+KCkuaGF2aW5nKAogICAgICAgICAgICAgIChleCkgPT4gZXguYXJndW1lbnRzLmZpcnN0LAogICAgICAgICAgICAgICdlcnJvciBjb2RlJywKICAgICAgICAgICAgICBlcXVhbHMoJzEnKSwKICAgICAgICAgICAgKSwKICAgICAgICAgICksCiAgICAgICAgKTsKCiAgICAgICAgdmVyaWZ5KAogICAgICAgICAgKCkgPT4gY29udGV4dC5sb2dnZXIuZXJyKAogICAgICAgICAgICAnQ291bGQgbm90IGZpbmQgcHVic3BlYy55YW1sIGF0ICR7dGVzdERpci5wYXRofScsCiAgICAgICAgICApLAogICAgICAgICkuY2FsbGVkKDEpOwoKICAgICAgICBleHBlY3QoY29udGV4dC52YXJzWyd0ZXN0cyddLCBpc051bGwpOwogICAgICAgIGV4cGVjdChjb250ZXh0LnZhcnNbJ2lzRmx1dHRlciddLCBpc051bGwpOwogICAgICB9KTsKICAgIH0pOwoKICAgIGdyb3VwKCdza2lwVmVyeUdvb2RPcHRpbWl6YXRpb25SZWdFeHAgcmVnZXgnLCAoKSB7CiAgICAgIGZpbmFsIHJlZ2V4ID0gcHJlX2dlbi5za2lwVmVyeUdvb2RPcHRpbWl6YXRpb25SZWdFeHA7CiAgICAgIHRlc3QoJ21hdGNoZXMgc2luZ2xlLWxpbmUgdGFnJywgKCkgewogICAgICAgIGZpbmFsIGNvbnRlbnQgPSAiQFRhZ3MoWycke3ByZV9nZW4uc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnfSddKSI7CiAgICAgICAgZXhwZWN0KHJlZ2V4Lmhhc01hdGNoKGNvbnRlbnQpLCBpc1RydWUpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ21hdGNoZXMgc2luZ2xlLWxpbmUgd2l0aCBtdWx0aXBsZSB0YWdzJywgKCkgewogICAgICAgIGZpbmFsIGNvbnRlbnQgPQogICAgICAgICAgICAiQFRhZ3MoWycke3ByZV9nZW4uc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnfScsICdjaHJvbWUnXSkiOwogICAgICAgIGV4cGVjdChyZWdleC5oYXNNYXRjaChjb250ZW50KSwgaXNUcnVlKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdtYXRjaGVzIG11bHRpLWxpbmUgdGFnIGxpc3QnLCAoKSB7CiAgICAgICAgZmluYWwgY29udGVudCA9CiAgICAgICAgICAgICcnJwogICAgICBAVGFncyhbCiAgICAgICAgJyR7cHJlX2dlbi5za2lwVmVyeUdvb2RPcHRpbWl6YXRpb25UYWd9JywKICAgICAgICAnY2hyb21lJywKICAgICAgICAndGVzdCcsCiAgICAgIF0pCiAgICAgICcnJzsKICAgICAgICBleHBlY3QocmVnZXguaGFzTWF0Y2goY29udGVudCksIGlzVHJ1ZSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnbWF0Y2hlcyBtdWx0aS1saW5lIHdoZXJlIHRhZyBpcyBub3QgdGhlIGZpcnN0JywgKCkgewogICAgICAgIGZpbmFsIGNvbnRlbnQgPQogICAgICAgICAgICAnJycKICAgICAgQFRhZ3MoWwogICAgICAgICdjaHJvbWUnLAogICAgICAgICcke3ByZV9nZW4uc2tpcFZlcnlHb29kT3B0aW1pemF0aW9uVGFnfScsCiAgICAgICAgJ3Rlc3QnLAogICAgICBdKQogICAgICAnJyc7CiAgICAgICAgZXhwZWN0KHJlZ2V4Lmhhc01hdGNoKGNvbnRlbnQpLCBpc1RydWUpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ2RvZXMgbm90IG1hdGNoIHdoZW4gdGFnIG1pc3NpbmcnLCAoKSB7CiAgICAgICAgY29uc3QgY29udGVudCA9ICJAVGFncyhbJ2Nocm9tZScsICd0ZXN0J10pIjsKICAgICAgICBleHBlY3QocmVnZXguaGFzTWF0Y2goY29udGVudCksIGlzRmFsc2UpOwogICAgICB9KTsKCiAgICAgIHRlc3QoCiAgICAgICAgJ2RvZXMgbm90IG1hdGNoIHN1YnN0cmluZyBvbmx5IChlLmcuIHNraXBfdmVyeV9nb29kX29wdGltaXphdGlvbix0ZXN0KScsCiAgICAgICAgKCkgewogICAgICAgICAgZmluYWwgY29udGVudCA9CiAgICAgICAgICAgICAgJycnCiAgICAgIEBUYWdzKFsKICAgICAgICAnJHtwcmVfZ2VuLnNraXBWZXJ5R29vZE9wdGltaXphdGlvblRhZ30sdGVzdCcsCiAgICAgICAgJ2Nocm9tZScsCiAgICAgIF0pCiAgICAgICcnJzsKICAgICAgICAgIGV4cGVjdCgKICAgICAgICAgICAgcmVnZXguaGFzTWF0Y2goY29udGVudCksCiAgICAgICAgICAgIGlzRmFsc2UsCiAgICAgICAgICApOyAvLyBvbmx5IGV4YWN0IHRhZyBzaG91bGQgbWF0Y2gKICAgICAgICB9LAogICAgICApOwogICAgfSk7CiAgfSk7Cn0K", "type": "text", }, ], diff --git a/lib/src/cli/test_cli_runner.dart b/lib/src/cli/test_cli_runner.dart index 8dd782cb6..68754712f 100644 --- a/lib/src/cli/test_cli_runner.dart +++ b/lib/src/cli/test_cli_runner.dart @@ -116,12 +116,11 @@ class TestCLIRunner { '''Shuffling test order with --test-randomize-ordering-seed=$randomSeed\n''', ); } - + var vars = {'package-root': workingDirectory}; if (optimizePerformance) { final optimizationProgress = logger.progress('Optimizing tests'); try { final generator = await buildGenerator(testOptimizerBundle); - var vars = {'package-root': workingDirectory}; await generator.hooks.preGen( vars: vars, onVarsChanged: (v) => vars = v, @@ -136,6 +135,9 @@ class TestCLIRunner { optimizationProgress.complete(); } } + + final notOptimizedTests = + vars['notOptimizedTests'] as List? ?? []; return _overrideAnsiOutput( forceAnsi, () => @@ -152,6 +154,11 @@ class TestCLIRunner { ], if (optimizePerformance) p.join('test', _testOptimizerFileName), + // Include non-optimized tests that require separate execution + if (notOptimizedTests.isNotEmpty && optimizePerformance) + ...notOptimizedTests.map( + (e) => p.join('test', e.toString()), + ), ], stdout: stdout ?? noop, stderr: stderr ?? noop, diff --git a/test/src/cli/test_runner_cli_test.dart b/test/src/cli/test_runner_cli_test.dart index 24f012324..8d9fe0034 100644 --- a/test/src/cli/test_runner_cli_test.dart +++ b/test/src/cli/test_runner_cli_test.dart @@ -1211,6 +1211,133 @@ void main() { ]), ); }); + + test( + 'pass not optimized tests along with optimized tests when optimization ' + 'is enabled but there are not optimized tests as well', + () async { + final tempDirectory = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDirectory.deleteSync(recursive: true)); + + final updatedVars = { + 'package-root': tempDirectory.path, + 'notOptimizedTests': [ + 'app/view/app_test.dart', + 'app/cubit/cubit_test.dart', + ], + }; + File(p.join(tempDirectory.path, 'pubspec.yaml')).createSync(); + Directory(p.join(tempDirectory.path, 'test')).createSync(); + when( + () => hooks.preGen( + vars: any(named: 'vars'), + onVarsChanged: any(named: 'onVarsChanged'), + workingDirectory: any(named: 'workingDirectory'), + ), + ).thenAnswer((invocation) async { + (invocation.namedArguments[#onVarsChanged] + as void Function( + Map vars, + )) + .call(updatedVars); + }); + await expectLater( + TestCLIRunner.test( + testType: TestRunType.flutter, + cwd: tempDirectory.path, + optimizePerformance: true, + stdout: stdoutLogs.add, + stderr: stderrLogs.add, + logger: logger, + overrideTestRunner: testRunner( + Stream.fromIterable( + [ + const DoneTestEvent(success: true, time: 0), + const ExitTestEvent(exitCode: 0, time: 0), + ], + ), + ), + buildGenerator: generatorBuilder(), + ), + completion(equals([ExitCode.success.code])), + ); + expect( + stdoutLogs, + equals([ + 'Running "flutter test" in . ...\n', + contains('All tests passed!'), + ]), + ); + expect( + testRunnerArgs, + equals([ + p.join('test', '.test_optimizer.dart'), + 'test/app/view/app_test.dart', + 'test/app/cubit/cubit_test.dart', + ]), + ); + }, + ); + + test( + 'do not pass not optimized tests along with optimized tests when ' + 'optimization is enabled but there are no not optimized tests', + () async { + final tempDirectory = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDirectory.deleteSync(recursive: true)); + + final updatedVars = { + 'package-root': tempDirectory.path, + 'notOptimizedTests': [], + }; + File(p.join(tempDirectory.path, 'pubspec.yaml')).createSync(); + Directory(p.join(tempDirectory.path, 'test')).createSync(); + when( + () => hooks.preGen( + vars: any(named: 'vars'), + onVarsChanged: any(named: 'onVarsChanged'), + workingDirectory: any(named: 'workingDirectory'), + ), + ).thenAnswer((invocation) async { + (invocation.namedArguments[#onVarsChanged] + as void Function( + Map vars, + )) + .call(updatedVars); + }); + await expectLater( + TestCLIRunner.test( + testType: TestRunType.flutter, + cwd: tempDirectory.path, + optimizePerformance: true, + stdout: stdoutLogs.add, + stderr: stderrLogs.add, + logger: logger, + overrideTestRunner: testRunner( + Stream.fromIterable( + [ + const DoneTestEvent(success: true, time: 0), + const ExitTestEvent(exitCode: 0, time: 0), + ], + ), + ), + buildGenerator: generatorBuilder(), + ), + completion(equals([ExitCode.success.code])), + ); + expect( + stdoutLogs, + equals([ + 'Running "flutter test" in . ...\n', + contains('All tests passed!'), + ]), + ); + expect( + testRunnerArgs, + equals([p.join('test', '.test_optimizer.dart')]), + ); + }, + ); }); }); }