Skip to content

Commit 979dab5

Browse files
committed
Add tests and fix an edge case around regex initialization caught by one of the tests
1 parent b54878b commit 979dab5

3 files changed

Lines changed: 91 additions & 4 deletions

File tree

Sources/Testing/ABI/EntryPoints/EntryPoint.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -696,8 +696,8 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr
696696
// otherwise we construct it with the provided tags
697697
let tagFilter: Configuration.TestFilter = switch (membership, tagPatterns.isEmpty) {
698698
case (_, true): .unfiltered
699-
case (.including, false): Configuration.TestFilter(includingTagsMatching: tagPatterns)
700-
case (.excluding, false): Configuration.TestFilter(excludingTagsMatching: tagPatterns)
699+
case (.including, false): try Configuration.TestFilter(includingTagsMatching: tagPatterns)
700+
case (.excluding, false): try Configuration.TestFilter(excludingTagsMatching: tagPatterns)
701701
}
702702

703703
guard !idPatterns.isEmpty else {

Sources/Testing/Running/Configuration.TestFilter.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,11 @@ extension Configuration.TestFilter {
200200
/// - Parameters:
201201
/// - tagPatterns: The patterns, expressed as a `Regex`-compatible regular
202202
/// expressions, to match test tags against.
203-
public init(includingTagsMatching tagPatterns: [String]) {
203+
public init(includingTagsMatching tagPatterns: [String]) throws {
204+
// See the comment above in init(membership:matchingAnyOf:) to understand why we construct regexes here.
205+
for pattern in tagPatterns {
206+
_ = try Regex(pattern)
207+
}
204208
self.init(_kind: .tagPatterns(tagPatterns, membership: .including))
205209
}
206210

@@ -209,7 +213,11 @@ extension Configuration.TestFilter {
209213
/// - Parameters:
210214
/// - tagPatterns: The patterns, expressed as a `Regex`-compatible regular
211215
/// expressions, to match test tags against.
212-
public init(excludingTagsMatching tagPatterns: [String]) {
216+
public init(excludingTagsMatching tagPatterns: [String]) throws {
217+
// See the comment above in init(membership:matchingAnyOf:) to understand why we construct regexes here.
218+
for pattern in tagPatterns {
219+
_ = try Regex(pattern)
220+
}
213221
self.init(_kind: .tagPatterns(tagPatterns, membership: .excluding))
214222
}
215223
}

Tests/TestingTests/SwiftPMTests.swift

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ private func configurationForEntryPoint(withArguments args: [String]) throws ->
2020

2121
private extension Tag {
2222
@Tag static var testTag: Self
23+
@Tag static var testTagOther: Self
24+
@Tag static var unrelatedTag: Self
2325
}
2426
/// Reads event stream output from the provided file matching event stream
2527
/// version `V`.
@@ -184,6 +186,83 @@ struct SwiftPMTests {
184186
#expect(planTests.contains(test2))
185187
}
186188

189+
@Test("--filter argument with tag: prefix supports regex patterns")
190+
func filterByTagRegex() async throws {
191+
let configuration = try configurationForEntryPoint(withArguments: ["PATH", "--filter", "tag:testTag.*"])
192+
let test1 = Test(.tags(.testTag), name: "hello") {}
193+
let test2 = Test(.tags(.testTagOther), name: "hi") {}
194+
let test3 = Test(.tags(.unrelatedTag), name: "goodbye") {}
195+
let test4 = Test(name: "untagged") {}
196+
let plan = await Runner.Plan(tests: [test1, test2, test3, test4], configuration: configuration)
197+
let planTests = plan.steps.map(\.test)
198+
#expect(planTests.contains(test1))
199+
#expect(planTests.contains(test2))
200+
#expect(!planTests.contains(test3))
201+
#expect(!planTests.contains(test4))
202+
}
203+
204+
@Test("--filter tag: argument strips backticks around tag names")
205+
func filterByTagStripsBackticks() async throws {
206+
let configuration = try configurationForEntryPoint(withArguments: ["PATH", "--filter", "tag:`testTag`"])
207+
let test1 = Test(.tags(.testTag), name: "hello") {}
208+
let test2 = Test(name: "goodbye") {}
209+
let plan = await Runner.Plan(tests: [test1, test2], configuration: configuration)
210+
let planTests = plan.steps.map(\.test)
211+
#expect(planTests.contains(test1))
212+
#expect(!planTests.contains(test2))
213+
}
214+
215+
@Test("--filter combining tag: and id: patterns AND's them together")
216+
func mixedPrefixedAndUnprefixedFilters() async throws {
217+
let configuration = try configurationForEntryPoint(withArguments: ["PATH", "--filter", "tag:testTag", "--filter", "hello"])
218+
let test1 = Test(.tags(.testTag), name: "hello") {}
219+
let test2 = Test(.tags(.testTag), name: "goodbye") {}
220+
let test3 = Test(name: "hello") {}
221+
let test4 = Test(name: "goodbye") {}
222+
let plan = await Runner.Plan(tests: [test1, test2, test3, test4], configuration: configuration)
223+
let planTests = plan.steps.map(\.test)
224+
#expect(planTests.contains(test1))
225+
#expect(!planTests.contains(test2))
226+
#expect(!planTests.contains(test3))
227+
#expect(!planTests.contains(test4))
228+
}
229+
230+
@Test("--filter argument with explicit id: prefix")
231+
func filterByExplicitIdPrefix() async throws {
232+
let configuration = try configurationForEntryPoint(withArguments: ["PATH", "--filter", "id:hello"])
233+
let test1 = Test(name: "hello") {}
234+
let test2 = Test(name: "goodbye") {}
235+
let plan = await Runner.Plan(tests: [test1, test2], configuration: configuration)
236+
let planTests = plan.steps.map(\.test)
237+
#expect(planTests.contains(test1))
238+
#expect(!planTests.contains(test2))
239+
}
240+
241+
@Test("--filter tag: combined with --skip id: in the same execution")
242+
func filterByTagAndSkipById() async throws {
243+
let configuration = try configurationForEntryPoint(withArguments: ["PATH", "--filter", "tag:testTag", "--skip", "id:goodbye"])
244+
let test1 = Test(.tags(.testTag), name: "hello") {}
245+
let test2 = Test(.tags(.testTag), name: "goodbye") {}
246+
let test3 = Test(.tags(.unrelatedTag), name: "hello") {}
247+
let test4 = Test(name: "untagged") {}
248+
let plan = await Runner.Plan(tests: [test1, test2, test3, test4], configuration: configuration)
249+
let planTests = plan.steps.map(\.test)
250+
#expect(planTests.contains(test1))
251+
#expect(!planTests.contains(test2))
252+
#expect(!planTests.contains(test3))
253+
#expect(!planTests.contains(test4))
254+
}
255+
256+
@Test("--filter or --skip tag: argument with bad regex")
257+
func filterByTagWithBadRegex() throws {
258+
#expect(throws: (any Error).self) {
259+
_ = try configurationForEntryPoint(withArguments: ["PATH", "--filter", "tag:("])
260+
}
261+
#expect(throws: (any Error).self) {
262+
_ = try configurationForEntryPoint(withArguments: ["PATH", "--skip", "tag:)"])
263+
}
264+
}
265+
187266
@Test("--filter or --skip argument as last argument")
188267
func filterOrSkipAsLast() async throws {
189268
_ = try configurationForEntryPoint(withArguments: ["PATH", "--filter"])

0 commit comments

Comments
 (0)