Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/patrol_cli/CHANGELOG.md
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the changelog

Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## Unreleased
- Fix `--exclude` not working. (#2990)

## 4.4.0

- Fix iOS Simulator test crash on Xcode 26.4+ caused by missing platform frameworks path in xctestrun.
Expand All @@ -15,6 +18,7 @@
- Bump `patrol_log` to `^0.8.0`.
- Refactor develop command into reusable components and expose public API for programmatic usage.
- Reflect failed tests in Playwright report. (#2970)
- Fix `--exclude` not working. (#2990)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be removed


## 4.2.0

Expand Down
5 changes: 3 additions & 2 deletions packages/patrol_cli/lib/src/commands/build_android.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,13 @@ class BuildAndroidCommand extends PatrolCommand {
}

final testFinder = _testFinderFactory.create(testDirectory);
final excludes = stringsArg('exclude').toSet();

final target = stringsArg('target');
final targets = target.isNotEmpty
? testFinder.findTests(target, testFileSuffix)
? testFinder.findTests(target, testFileSuffix, excludes)
: testFinder.findAllTests(
excludes: stringsArg('exclude').toSet(),
excludes: excludes,
testFileSuffix: testFileSuffix,
);

Expand Down
5 changes: 3 additions & 2 deletions packages/patrol_cli/lib/src/commands/build_ios.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,13 @@ class BuildIOSCommand extends PatrolCommand {
}

final testFinder = _testFinderFactory.create(testDirectory);
final excludes = stringsArg('exclude').toSet();

final target = stringsArg('target');
final targets = target.isNotEmpty
? testFinder.findTests(target, testFileSuffix)
? testFinder.findTests(target, testFileSuffix, excludes)
: testFinder.findAllTests(
excludes: stringsArg('exclude').toSet(),
excludes: excludes,
testFileSuffix: testFileSuffix,
);

Expand Down
5 changes: 3 additions & 2 deletions packages/patrol_cli/lib/src/commands/build_macos.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,13 @@ class BuildMacOSCommand extends PatrolCommand {
}

final testFinder = _testFinderFactory.create(testDirectory);
final excludes = stringsArg('exclude').toSet();

final target = stringsArg('target');
final targets = target.isNotEmpty
? testFinder.findTests(target, testFileSuffix)
? testFinder.findTests(target, testFileSuffix, excludes)
: testFinder.findAllTests(
excludes: stringsArg('exclude').toSet(),
excludes: excludes,
testFileSuffix: testFileSuffix,
);

Expand Down
5 changes: 3 additions & 2 deletions packages/patrol_cli/lib/src/commands/test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,13 @@ class TestCommand extends PatrolCommand {
final testFileSuffix = config.testFileSuffix;

final testFinder = _testFinderFactory.create(testDirectory);
final excludes = stringsArg('exclude').toSet();

final target = stringsArg('target');
final targets = target.isNotEmpty
? testFinder.findTests(target, testFileSuffix)
? testFinder.findTests(target, testFileSuffix, excludes)
: testFinder.findAllTests(
excludes: stringsArg('exclude').toSet(),
excludes: excludes,
testFileSuffix: testFileSuffix,
);

Expand Down
76 changes: 46 additions & 30 deletions packages/patrol_cli/lib/src/test_finder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,25 @@ class TestFinder {
List<String> findTests(
List<String> targets, [
String testFileSuffix = _kDefaultTestFileSuffix,
Set<String> excludes = const {},
]) {
final testFiles = <String>[];
final absoluteExcludes = _toAbsoluteExcludes(excludes);

for (final target in targets) {
if (target.endsWith(testFileSuffix)) {
final isFile = _fs.isFileSync(target);
if (!isFile) {
throwToolExit('target file $target does not exist');
}
testFiles.add(_fs.file(target).absolute.path);
final absoluteTargetPath = _fs.file(target).absolute.path;
if (!_isExcluded(absoluteTargetPath, absoluteExcludes)) {
testFiles.add(absoluteTargetPath);
}
} else if (_fs.isDirectorySync(target)) {
final foundTargets = findAllTests(
directory: _fs.directory(target),
directory: _fs.directory(target).absolute,
excludes: excludes,
testFileSuffix: testFileSuffix,
);
if (foundTargets.isEmpty) {
Expand Down Expand Up @@ -86,16 +92,7 @@ class TestFinder {
throwToolExit("Directory ${directory.path} doesn't exist");
}

final absoluteExcludes = excludes.map((exclude) {
if (_fs.path.isAbsolute(exclude)) {
return exclude;
}
// Resolve relative to rootDir (not the process CWD).
// _rootDir.path is absolute in production (findRootDirectory returns
// an absolute directory), so avoid calling .absolute which would
// re-resolve against CWD.
return _fs.path.join(_rootDir.path, exclude);
}).toSet();
final absoluteExcludes = _toAbsoluteExcludes(excludes);

return directory
.listSync(recursive: true, followLinks: false)
Expand All @@ -110,28 +107,47 @@ class TestFinder {
.where((fileSystemEntity) {
final filePath = fileSystemEntity.path;

for (final exclude in absoluteExcludes) {
// Check if the file exactly matches an excluded file
if (filePath == exclude) {
return false;
}

// Check if the file is inside an excluded directory
// Need to add path separator to avoid matching prefixes
// e.g., "patrol_test/permissions" shouldn't match "patrol_test/permissions_other"
final excludeWithSeparator = exclude.endsWith(_fs.path.separator)
? exclude
: exclude + _fs.path.separator;
if (filePath.startsWith(excludeWithSeparator)) {
return false;
}
}

return true;
return !_isExcluded(filePath, absoluteExcludes);
})
.map((entity) => entity.absolute.path)
.toList();
}

/// Converts `excludes` to absolute paths.
///
/// Pass file paths or directory paths in `excludes`, either absolute or
/// relative to [_rootDir]. Absolute paths are kept as-is, and relative paths
/// are resolved against [_rootDir] (not the process working directory).
Set<String> _toAbsoluteExcludes(Set<String> excludes) {
Comment thread
Kendru98 marked this conversation as resolved.
return excludes.map((exclude) {
if (_fs.path.isAbsolute(exclude)) {
return exclude;
}
return _fs.path.join(_rootDir.path, exclude);
}).toSet();
}

/// Returns `true` when [filePath] should be filtered out by exclusions.
///
/// A file is excluded if it exactly matches an excluded path, or if it is
/// located under an excluded directory path.
bool _isExcluded(String filePath, Set<String> absoluteExcludes) {
Comment thread
Kendru98 marked this conversation as resolved.
for (final exclude in absoluteExcludes) {
// Check if the file exactly matches an excluded file.
if (filePath == exclude) {
return true;
}

final excludeWithSeparator = exclude.endsWith(_fs.path.separator)
? exclude
: exclude + _fs.path.separator;
if (filePath.startsWith(excludeWithSeparator)) {
return true;
}
}

return false;
}
}

class TestFinderFactory {
Expand Down
43 changes: 43 additions & 0 deletions packages/patrol_cli/test/general/test_finder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,49 @@ void _test(Platform platform) {
);
});

test('applies excludes when target is a directory', () {
// given
final included = fs.path.join(
'patrol_test',
'permissions',
'other_test.dart',
);
final excluded = fs.path.join(
'patrol_test',
'permissions',
'test',
'excluded_test.dart',
);
fs.file(included).createSync(recursive: true);
fs.file(excluded).createSync(recursive: true);

// when
final found = testFinder.findTests(
[fs.path.join('patrol_test', 'permissions')],
'_test.dart',
{fs.path.join('patrol_test', 'permissions', 'test', '')},
);

// then
expect(found, equals([fs.path.join(fs.currentDirectory.path, included)]));
});

test('applies excludes when target is a file', () {
// given
final targetFile = fs.path.join('patrol_test', 'app_test.dart');
fs.file(targetFile).createSync(recursive: true);

// when
final found = testFinder.findTests(
[targetFile],
'_test.dart',
{targetFile},
);

// then
expect(found, isEmpty);
});
Comment thread
Kendru98 marked this conversation as resolved.

test('finds tests when targets are files and directories', () {
// given
final testRoot = fs.directory('patrol_test')..createSync();
Expand Down
Loading