Skip to content

Commit bef850a

Browse files
committed
Update tag filtering to support regular expressions
1 parent 3f884da commit bef850a

2 files changed

Lines changed: 50 additions & 26 deletions

File tree

Sources/Testing/ABI/EntryPoints/EntryPoint.swift

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -625,51 +625,51 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr
625625
#endif
626626

627627
// Filtering
628+
629+
// Filters currently come in two flavors: those with a prefix and those
630+
// without. Those without a prefix are treated the same as those with an
631+
// "id:" prefix.
632+
enum FilterPrefix: String, CaseIterable {
633+
case id = "id:"
634+
case tag = "tag:"
635+
}
628636
var filters = [Configuration.TestFilter]()
629637
func testFilters(forOptionArguments optionArguments: [String]?, label: String, membership: Configuration.TestFilter.Membership) throws -> [Configuration.TestFilter] {
630-
631-
// Filters will come in two flavors: those with `tag:` as a prefix, and
632-
// those without. We split them into two collections, taking care to handle
633-
// an escaped colon, treating it as a pseudo-operator.
634-
let tagPrefix = "tag:"
635-
let escapedTagPrefix = #"tag\:"#
636-
var tags = [Tag]()
637-
var regexes = [String]()
638+
var tagPatterns = [String]()
639+
var idPatterns = [String]()
638640

639641
// Loop through all the option arguments, separating tags from regex filters
640642
for var optionArg in optionArguments ?? [] {
641-
if optionArg.hasPrefix(tagPrefix) {
642-
// Running into the `tag:` prefix means we should strip it and use the
643-
// actual tag name the user has provided
644-
let tagStringWithoutPrefix = String(optionArg.dropFirst(tagPrefix.count))
645-
tags.append(Tag(userProvidedStringValue: tagStringWithoutPrefix))
646-
} else {
647-
// If we run into the escaped tag prefix, the user has indicated they
648-
// want to us to treat it as a regex filter. We need to to unescape it
649-
// before adding it as a regex filter
650-
if optionArg.hasPrefix(escapedTagPrefix) {
651-
optionArg.replaceSubrange(escapedTagPrefix.startIndex..<escapedTagPrefix.endIndex, with: tagPrefix)
643+
if let prefix = FilterPrefix.allCases.first(where: { optionArg.hasPrefix($0.rawValue) }) {
644+
// We have encountered a prefix, so trim it off and add the supplied
645+
// argument to the appropriate filter list
646+
optionArg.trimPrefix(prefix.rawValue)
647+
switch prefix {
648+
case .id: idPatterns.append(optionArg)
649+
case .tag: tagPatterns.append(optionArg)
652650
}
653-
regexes.append(optionArg)
651+
} else {
652+
// No prefix was detected, so we treat this as a regex matching a test ID.
653+
idPatterns.append(optionArg)
654654
}
655655
}
656656

657657
// If we didn't find any tags, the tagFilter should be .unfiltered,
658658
// otherwise we construct it with the provided tags
659-
let tagFilter: Configuration.TestFilter = switch (membership, tags.isEmpty) {
659+
let tagFilter: Configuration.TestFilter = switch (membership, tagPatterns.isEmpty) {
660660
case (_, true): .unfiltered
661-
case (.including, false): Configuration.TestFilter(includingAnyOf: tags)
662-
case (.excluding, false): Configuration.TestFilter(excludingAnyOf: tags)
661+
case (.including, false): Configuration.TestFilter(includingTagsMatching: tagPatterns)
662+
case (.excluding, false): Configuration.TestFilter(excludingTagsMatching: tagPatterns)
663663
}
664664

665-
guard !regexes.isEmpty else {
665+
guard !idPatterns.isEmpty else {
666666
// Return early with just the tag filter, otherwise we try to match
667667
// against an empty array of regular expressions which is _not_
668668
// equivalent to `.unfiltered`.
669669
return [tagFilter]
670670
}
671671

672-
return [try Configuration.TestFilter(membership: membership, matchingAnyOf: regexes), tagFilter]
672+
return [try Configuration.TestFilter(membership: membership, matchingAnyOf: idPatterns), tagFilter]
673673
}
674674

675675
filters += try testFilters(forOptionArguments: args.filter, label: "--filter", membership: .including)

Sources/Testing/Running/Configuration.TestFilter.swift

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ extension Configuration {
5151
/// - membership: How to interpret the result when predicating tests.
5252
case tags(_ tags: Set<Tag>, anyOf: Bool, membership: Membership)
5353

54+
case tagPatterns(_ tagPatterns: [String], membership: Membership)
55+
5456
/// The test filter contains a pattern to predicate test IDs against.
5557
///
5658
/// - Parameters:
@@ -183,6 +185,14 @@ extension Configuration.TestFilter {
183185
public init(excludingAllOf tags: some Collection<Tag>) {
184186
self.init(_kind: .tags(Set(tags), anyOf: false, membership: .excluding))
185187
}
188+
189+
public init(includingTagsMatching tagPatterns: [String]) {
190+
self.init(_kind: .tagPatterns(tagPatterns, membership: .including))
191+
}
192+
193+
public init(excludingTagsMatching tagPatterns: [String]) {
194+
self.init(_kind: .tagPatterns(tagPatterns, membership: .excluding))
195+
}
186196
}
187197

188198
// MARK: - Operations
@@ -247,6 +257,20 @@ extension Configuration.TestFilter.Kind {
247257
{ $0.tags.isSuperset(of: tags) }
248258
}
249259
return .function(predicate, membership: membership)
260+
case let .tagPatterns(tagPatterns, membership):
261+
nonisolated(unsafe) let regexes = try tagPatterns.map(Regex.init)
262+
return .function({ item in
263+
let tagNames = item.tags.map { tag in
264+
switch tag.kind {
265+
case let .staticMember(tagName): tagName
266+
}
267+
}
268+
return tagNames.contains(where: { tagName in
269+
regexes.contains(where: { regex in
270+
tagName.contains(regex)
271+
})
272+
})
273+
}, membership: membership)
250274
case let .patterns(patterns, membership):
251275
nonisolated(unsafe) let regexes = try patterns.map(Regex.init)
252276
return .function({ item in
@@ -499,7 +523,7 @@ extension Configuration.TestFilter.Kind {
499523
.testIDs,
500524
.patterns:
501525
false
502-
case .tags:
526+
case .tags, .tagPatterns:
503527
true
504528
case let .combination(lhs, rhs, _):
505529
lhs.requiresTraitPropagation || rhs.requiresTraitPropagation

0 commit comments

Comments
 (0)