Skip to content

Commit 33a937c

Browse files
committed
Validate optimizations when a match fails
This allows us to catch the case where a match occurs without optimizations, but doesn't occur with optimizations. Additionally fix the `xfail` param such that it can't be used on tests that actually match expectations.
1 parent 92a051a commit 33a937c

File tree

1 file changed

+42
-40
lines changed

1 file changed

+42
-40
lines changed

Tests/RegexTests/MatchTests.swift

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,33 @@ func _firstMatch(
2626
validateOptimizations: Bool,
2727
semanticLevel: RegexSemanticLevel = .graphemeCluster,
2828
syntax: SyntaxOptions = .traditional
29-
) throws -> (String, [String?]) {
29+
) throws -> (String, [String?])? {
3030
var regex = try Regex(regexStr, syntax: syntax).matchingSemantics(semanticLevel)
31-
guard let result = try regex.firstMatch(in: input) else {
32-
throw MatchError("match not found for \(regexStr) in \(input)")
33-
}
34-
let caps = result.output.slices(from: input)
35-
31+
let result = try regex.firstMatch(in: input)
32+
3633
if validateOptimizations {
3734
regex._setCompilerOptionsForTesting(.disableOptimizations)
38-
guard let unoptResult = try regex.firstMatch(in: input) else {
35+
let unoptResult = try regex.firstMatch(in: input)
36+
if result != nil && unoptResult == nil {
3937
throw MatchError("match not found for unoptimized \(regexStr) in \(input)")
4038
}
41-
XCTAssertEqual(
42-
String(input[result.range]),
43-
String(input[unoptResult.range]),
44-
"Unoptimized regex returned a different result")
39+
if result == nil && unoptResult != nil {
40+
throw MatchError("match not found in optimized \(regexStr) in \(input)")
41+
}
42+
if let result = result, let unoptResult = unoptResult {
43+
let optMatch = String(input[result.range])
44+
let unoptMatch = String(input[unoptResult.range])
45+
if optMatch != unoptMatch {
46+
throw MatchError("""
47+
48+
Unoptimized regex returned: '\(unoptMatch)'
49+
Optimized regex returned: '\(optMatch)'
50+
""")
51+
}
52+
}
4553
}
54+
guard let result = result else { return nil }
55+
let caps = result.output.slices(from: input)
4656
return (String(input[result.range]), caps.map { $0.map(String.init) })
4757
}
4858

@@ -153,22 +163,20 @@ func firstMatchTest(
153163
line: UInt = #line
154164
) {
155165
do {
156-
let (found, _) = try _firstMatch(
166+
let found = try _firstMatch(
157167
regex,
158168
input: input,
159169
validateOptimizations: validateOptimizations,
160170
semanticLevel: semanticLevel,
161-
syntax: syntax)
171+
syntax: syntax)?.0
162172

163173
if xfail {
164174
XCTAssertNotEqual(found, match, file: file, line: line)
165175
} else {
166176
XCTAssertEqual(found, match, "Incorrect match", file: file, line: line)
167177
}
168178
} catch {
169-
// FIXME: This allows non-matches to succeed even when xfail'd
170-
// When xfail == true, this should report failure for match == nil
171-
if !xfail && match != nil {
179+
if !xfail {
172180
XCTFail("\(error)", file: file, line: line)
173181
}
174182
return
@@ -428,8 +436,7 @@ extension RegexTests {
428436
"a++a",
429437
("babc", nil),
430438
("baaabc", nil),
431-
("bb", nil),
432-
xfail: true)
439+
("bb", nil))
433440
firstMatchTests(
434441
"a+?a",
435442
("babc", nil),
@@ -505,23 +512,19 @@ extension RegexTests {
505512
("baabc", nil),
506513
("bb", nil))
507514

508-
// XFAIL'd versions of the above
509515
firstMatchTests(
510516
"a{2,4}+a",
511-
("baaabc", nil),
512-
xfail: true)
517+
("baaabc", nil))
513518
firstMatchTests(
514519
"a{,4}+a",
515520
("babc", nil),
516521
("baabc", nil),
517-
("baaabc", nil),
518-
xfail: true)
522+
("baaabc", nil))
519523
firstMatchTests(
520524
"a{2,}+a",
521525
("baaabc", nil),
522526
("baaaaabc", nil),
523-
("baaaaaaaabc", nil),
524-
xfail: true)
527+
("baaaaaaaabc", nil))
525528

526529
// XFAIL'd possessive tests
527530
firstMatchTests(
@@ -773,6 +776,11 @@ extension RegexTests {
773776
}
774777
firstMatchTest(#"[\t-\t]"#, input: "\u{8}\u{A}\u{9}", match: "\u{9}")
775778

779+
// FIXME: This produces a different result with and without optimizations.
780+
firstMatchTest(#"[1-2]"#, input: "1️⃣", match: nil, xfail: true)
781+
firstMatchTest(#"[1-2]"#, input: "1️⃣", match: nil,
782+
validateOptimizations: false)
783+
776784
// Currently not supported in the matching engine.
777785
for c: UnicodeScalar in ["a", "b", "c"] {
778786
firstMatchTest(#"[\c!-\C-#]"#, input: "def\(c)", match: "\(c)",
@@ -1118,8 +1126,8 @@ extension RegexTests {
11181126
// TODO: Oniguruma \y and \Y
11191127
firstMatchTests(
11201128
#"\u{65}"#, // Scalar 'e' is present in both
1121-
("Cafe\u{301}", nil), // but scalar mode requires boundary at end of match
1122-
xfail: true)
1129+
("Cafe\u{301}", nil)) // but scalar mode requires boundary at end of match
1130+
11231131
firstMatchTests(
11241132
#"\u{65}"#, // Scalar 'e' is present in both
11251133
("Sol Cafe", "e")) // standalone is okay
@@ -1711,19 +1719,15 @@ extension RegexTests {
17111719
firstMatchTest(#"\u{65 301}$"#, input: eComposed, match: eComposed)
17121720

17131721
// FIXME: Implicit \y at end of match
1714-
firstMatchTest(#"\u{65}"#, input: eDecomposed, match: nil,
1715-
xfail: true)
1722+
firstMatchTest(#"\u{65}"#, input: eDecomposed, match: nil)
17161723
firstMatchTest(#"\u{65}$"#, input: eDecomposed, match: nil)
1717-
// FIXME: \y is unsupported
1718-
firstMatchTest(#"\u{65}\y"#, input: eDecomposed, match: nil,
1719-
xfail: true)
1724+
firstMatchTest(#"\u{65}\y"#, input: eDecomposed, match: nil)
17201725

17211726
// FIXME: Unicode scalars are only matched at the start of a grapheme cluster
17221727
firstMatchTest(#"\u{301}"#, input: eDecomposed, match: "\u{301}",
17231728
xfail: true)
1724-
// FIXME: \y is unsupported
1725-
firstMatchTest(#"\y\u{301}"#, input: eDecomposed, match: nil,
1726-
xfail: true)
1729+
1730+
firstMatchTest(#"\y\u{301}"#, input: eDecomposed, match: nil)
17271731
}
17281732

17291733
func testCanonicalEquivalence() throws {
@@ -1781,13 +1785,11 @@ extension RegexTests {
17811785
// \s
17821786
firstMatchTest(#"\s"#, input: " ", match: " ")
17831787
// FIXME: \s shouldn't match a number composed with a non-number character
1784-
firstMatchTest(#"\s\u{305}"#, input: " ", match: nil,
1785-
xfail: true)
1788+
firstMatchTest(#"\s\u{305}"#, input: " ", match: nil)
17861789
// \p{Whitespace}
17871790
firstMatchTest(#"\s"#, input: " ", match: " ")
1788-
// FIXME: \p{Whitespace} shouldn't match whitespace composed with a non-whitespace character
1789-
firstMatchTest(#"\s\u{305}"#, input: " ", match: nil,
1790-
xfail: true)
1791+
// \p{Whitespace} shouldn't match whitespace composed with a non-whitespace character
1792+
firstMatchTest(#"\s\u{305}"#, input: " ", match: nil)
17911793
}
17921794

17931795
func testCanonicalEquivalenceCustomCharacterClass() throws {

0 commit comments

Comments
 (0)