Skip to content

Commit eedf947

Browse files
authored
feat: add support for filtering test optimization (#1377)
1 parent adc1d8a commit eedf947

File tree

5 files changed

+324
-7
lines changed

5 files changed

+324
-7
lines changed

bricks/test_optimizer/hooks/lib/pre_gen.dart

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ typedef ExitFn = Never Function(int code);
88

99
ExitFn exitFn = exit;
1010

11+
String skipVeryGoodOptimizationTag = 'skip_very_good_optimization';
12+
RegExp skipVeryGoodOptimizationRegExp = RegExp(
13+
"@Tags\\s*\\(\\s*\\[[\\s\\S]*?[\"']$skipVeryGoodOptimizationTag[\"'][\\s\\S]*?\\]\\s*\\)",
14+
multiLine: true,
15+
);
16+
1117
Future<void> run(HookContext context) async {
1218
final packageRoot = context.vars['package-root'] as String;
1319
final testDir = Directory(path.join(packageRoot, 'test'));
@@ -29,8 +35,13 @@ Future<void> run(HookContext context) async {
2935

3036
final identifierGenerator = DartIdentifierGenerator();
3137
final testIdentifierTable = <Map<String, String>>[];
32-
for (final entity
33-
in testDir.listSync(recursive: true).where((entity) => entity.isTest)) {
38+
final tests = testDir
39+
.listSync(recursive: true)
40+
.where((entity) => entity.isTest);
41+
42+
final notOptimizedTests = await getNotOptimizedTests(tests, testDir.path);
43+
44+
for (final entity in tests) {
3445
final relativePath = path
3546
.relative(entity.path, from: testDir.path)
3647
.replaceAll(r'\', '/');
@@ -40,11 +51,52 @@ Future<void> run(HookContext context) async {
4051
});
4152
}
4253

43-
context.vars = {'tests': testIdentifierTable, 'isFlutter': isFlutter};
54+
final optimizedTestsIdentifierTable = testIdentifierTable
55+
.where((e) => !notOptimizedTests.contains(e['path']))
56+
.toList();
57+
58+
context.vars = {
59+
'tests': optimizedTestsIdentifierTable,
60+
'isFlutter': isFlutter,
61+
'notOptimizedTests': notOptimizedTests,
62+
};
4463
}
4564

4665
extension on FileSystemEntity {
4766
bool get isTest {
4867
return this is File && path.basename(this.path).endsWith('_test.dart');
4968
}
5069
}
70+
71+
Future<List<String>> getNotOptimizedTests(
72+
Iterable<FileSystemEntity> tests,
73+
String testDir,
74+
) async {
75+
final paths = tests.map((e) => e.path).toList();
76+
final formattedPaths = paths.map((e) => e.replaceAll('/./', '/')).toList();
77+
78+
final fileFutures = formattedPaths.map(_checkFileForSkipVeryGoodOptimization);
79+
final fileResults = await Future.wait(fileFutures);
80+
81+
final testWithVeryGoodTest = <String>[];
82+
for (var i = 0; i < formattedPaths.length; i++) {
83+
if (fileResults[i]) {
84+
testWithVeryGoodTest.add(formattedPaths[i]);
85+
}
86+
}
87+
88+
/// Format to relative path
89+
final relativePaths = testWithVeryGoodTest
90+
.map((e) => path.relative(e, from: testDir))
91+
.toList();
92+
93+
return relativePaths;
94+
}
95+
96+
/// Check if a single file contains skip_very_good_optimization tag
97+
Future<bool> _checkFileForSkipVeryGoodOptimization(String path) async {
98+
final file = File(path);
99+
if (!file.existsSync()) return false;
100+
final content = await file.readAsString();
101+
return skipVeryGoodOptimizationRegExp.hasMatch(content);
102+
}

bricks/test_optimizer/hooks/test/pre_gen_test.dart

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,26 @@ class _FakeContext extends Fake implements HookContext {
1616
Map<String, Object?> vars = {};
1717
}
1818

19+
final notOptimizedTestContent =
20+
'''
21+
@Tags(['${pre_gen.skipVeryGoodOptimizationTag}'])
22+
void main() {
23+
test('test', () {
24+
expect(1, 1);
25+
});
26+
}
27+
''';
28+
29+
final anotherNotOptimizedTestContent =
30+
'''
31+
@Tags(['${pre_gen.skipVeryGoodOptimizationTag}', 'another_tag'])
32+
void main() {
33+
test('another test', () {
34+
expect(1, 1);
35+
});
36+
}
37+
''';
38+
1939
void main() {
2040
late Directory tempDirectory;
2141

@@ -86,6 +106,57 @@ dependencies:
86106

87107
expect(context.vars['isFlutter'], true);
88108
});
109+
110+
test('with proper not optimized tests identification', () async {
111+
File(path.join(tempDirectory.path, 'pubspec.yaml')).createSync();
112+
113+
final testDir = Directory(path.join(tempDirectory.path, 'test'))
114+
..createSync();
115+
File(path.join(testDir.path, 'test1_test.dart')).createSync();
116+
File(path.join(testDir.path, 'test2_test.dart')).createSync();
117+
File(path.join(testDir.path, 'no_test_here.dart')).createSync();
118+
File(
119+
path.join(testDir.path, 'not_optimized_test.dart'),
120+
).writeAsStringSync(notOptimizedTestContent);
121+
File(
122+
path.join(testDir.path, 'another_not_optimized_test.dart'),
123+
).writeAsStringSync(anotherNotOptimizedTestContent);
124+
125+
context.vars['package-root'] = tempDirectory.absolute.path;
126+
127+
await pre_gen.run(context);
128+
129+
final tests = context.vars['tests'] as List<Map<String, String>>;
130+
final testsMap = <String, String>{};
131+
for (final test in tests) {
132+
final path = test['path']!;
133+
final identifier = test['identifier']!;
134+
testsMap[path] = identifier;
135+
}
136+
137+
final paths = testsMap.keys;
138+
expect(paths, contains('test1_test.dart'));
139+
expect(paths, contains('test2_test.dart'));
140+
expect(paths, isNot(contains('no_test_here.dart')));
141+
expect(paths, isNot(contains('not_optimized_test.dart')));
142+
expect(paths, isNot(contains('another_not_optimized_test.dart')));
143+
144+
expect(
145+
testsMap.values.toSet().length,
146+
equals(tests.length),
147+
reason: 'All tests files should have unique identifiers',
148+
);
149+
final notOptimizedTests =
150+
context.vars['notOptimizedTests'] as List<String>;
151+
expect(
152+
notOptimizedTests,
153+
contains('not_optimized_test.dart'),
154+
);
155+
expect(
156+
notOptimizedTests,
157+
contains('another_not_optimized_test.dart'),
158+
);
159+
});
89160
});
90161

91162
group('Fails', () {
@@ -155,5 +226,65 @@ dependencies:
155226
expect(context.vars['isFlutter'], isNull);
156227
});
157228
});
229+
230+
group('skipVeryGoodOptimizationRegExp regex', () {
231+
final regex = pre_gen.skipVeryGoodOptimizationRegExp;
232+
test('matches single-line tag', () {
233+
final content = "@Tags(['${pre_gen.skipVeryGoodOptimizationTag}'])";
234+
expect(regex.hasMatch(content), isTrue);
235+
});
236+
237+
test('matches single-line with multiple tags', () {
238+
final content =
239+
"@Tags(['${pre_gen.skipVeryGoodOptimizationTag}', 'chrome'])";
240+
expect(regex.hasMatch(content), isTrue);
241+
});
242+
243+
test('matches multi-line tag list', () {
244+
final content =
245+
'''
246+
@Tags([
247+
'${pre_gen.skipVeryGoodOptimizationTag}',
248+
'chrome',
249+
'test',
250+
])
251+
''';
252+
expect(regex.hasMatch(content), isTrue);
253+
});
254+
255+
test('matches multi-line where tag is not the first', () {
256+
final content =
257+
'''
258+
@Tags([
259+
'chrome',
260+
'${pre_gen.skipVeryGoodOptimizationTag}',
261+
'test',
262+
])
263+
''';
264+
expect(regex.hasMatch(content), isTrue);
265+
});
266+
267+
test('does not match when tag missing', () {
268+
const content = "@Tags(['chrome', 'test'])";
269+
expect(regex.hasMatch(content), isFalse);
270+
});
271+
272+
test(
273+
'does not match substring only (e.g. skip_very_good_optimization,test)',
274+
() {
275+
final content =
276+
'''
277+
@Tags([
278+
'${pre_gen.skipVeryGoodOptimizationTag},test',
279+
'chrome',
280+
])
281+
''';
282+
expect(
283+
regex.hasMatch(content),
284+
isFalse,
285+
); // only exact tag should match
286+
},
287+
);
288+
});
158289
});
159290
}

0 commit comments

Comments
 (0)