Skip to content

Commit 82edc2b

Browse files
committed
[Tests] Automatically substitute [[@line]] numbers
Enhance xctest_checker such that it substitutes the string "[[@line]]" and "[[@line+<offset>]]" with the current line number of the source file when performing checks. This allows us to add and remove arbitary lines in the functional test suite without manually updating line numbers in most cases. Line offsets will still need to be updated when the distance between the CHECK and the test failure changes. To test the offsets out, use them in one of the asynchronous tests.
1 parent 1bb2e9e commit 82edc2b

File tree

4 files changed

+53
-5
lines changed

4 files changed

+53
-5
lines changed

Tests/Functional/Asynchronous/Misuse/main.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@
1414
// CHECK: Test Suite 'MisuseTestCase' started at \d+:\d+:\d+\.\d+
1515
class MisuseTestCase: XCTestCase {
1616
// CHECK: Test Case 'MisuseTestCase.test_whenExpectationsAreMade_butNotWaitedFor_fails' started at \d+:\d+:\d+\.\d+
17-
// CHECK: .*/Tests/Functional/Asynchronous/Misuse/main.swift:21: error: MisuseTestCase.test_whenExpectationsAreMade_butNotWaitedFor_fails : Failed due to unwaited expectations.
17+
// CHECK: .*/Tests/Functional/Asynchronous/Misuse/main.swift:[[@LINE+4]]: error: MisuseTestCase.test_whenExpectationsAreMade_butNotWaitedFor_fails : Failed due to unwaited expectations.
1818
// CHECK: Test Case 'MisuseTestCase.test_whenExpectationsAreMade_butNotWaitedFor_fails' failed \(\d+\.\d+ seconds\).
1919
func test_whenExpectationsAreMade_butNotWaitedFor_fails() {
2020
self.expectation(withDescription: "the first expectation")
2121
self.expectation(withDescription: "the second expectation (the file and line number for this one are included in the failure message")
2222
}
2323

2424
// CHECK: Test Case 'MisuseTestCase.test_whenNoExpectationsAreMade_butTheyAreWaitedFor_fails' started at \d+:\d+:\d+\.\d+
25-
// CHECK: .*/Tests/Functional/Asynchronous/Misuse/main.swift:28: error: MisuseTestCase.test_whenNoExpectationsAreMade_butTheyAreWaitedFor_fails : API violation - call made to wait without any expectations having been set.
25+
// CHECK: .*/Tests/Functional/Asynchronous/Misuse/main.swift:[[@LINE+3]]: error: MisuseTestCase.test_whenNoExpectationsAreMade_butTheyAreWaitedFor_fails : API violation - call made to wait without any expectations having been set.
2626
// CHECK: Test Case 'MisuseTestCase.test_whenNoExpectationsAreMade_butTheyAreWaitedFor_fails' failed \(\d+\.\d+ seconds\).
2727
func test_whenNoExpectationsAreMade_butTheyAreWaitedFor_fails() {
2828
self.waitForExpectations(withTimeout: 0.1)
2929
}
3030

3131
// CHECK: Test Case 'MisuseTestCase.test_whenExpectationIsFulfilledMultipleTimes_fails' started at \d+:\d+:\d+\.\d+
32-
// CHECK: .*/Tests/Functional/Asynchronous/Misuse/main.swift:38: error: MisuseTestCase.test_whenExpectationIsFulfilledMultipleTimes_fails : API violation - multiple calls made to XCTestExpectation.fulfill\(\) for rob.
33-
// CHECK: .*/Tests/Functional/Asynchronous/Misuse/main.swift:48: error: MisuseTestCase.test_whenExpectationIsFulfilledMultipleTimes_fails : API violation - multiple calls made to XCTestExpectation.fulfill\(\) for rob.
32+
// CHECK: .*/Tests/Functional/Asynchronous/Misuse/main.swift:[[@LINE+6]]: error: MisuseTestCase.test_whenExpectationIsFulfilledMultipleTimes_fails : API violation - multiple calls made to XCTestExpectation.fulfill\(\) for rob.
33+
// CHECK: .*/Tests/Functional/Asynchronous/Misuse/main.swift:[[@LINE+15]]: error: MisuseTestCase.test_whenExpectationIsFulfilledMultipleTimes_fails : API violation - multiple calls made to XCTestExpectation.fulfill\(\) for rob.
3434
// CHECK: Test Case 'MisuseTestCase.test_whenExpectationIsFulfilledMultipleTimes_fails' failed \(\d+\.\d+ seconds\).
3535
func test_whenExpectationIsFulfilledMultipleTimes_fails() {
3636
let expectation = self.expectation(withDescription: "rob")

Tests/Functional/xctest_checker/tests/test_compare.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,10 @@ def test_can_explicitly_match_leading_and_trailing_whitespace(self):
8787
expected = _tmpfile('c: foo\nc: ^ bar \nc: baz $\n')
8888
compare.compare(actual, expected, check_prefix='c:')
8989

90+
def test_line_number_substitution(self):
91+
actual = _tmpfile('beep 1\nboop 5\n')
92+
expected = _tmpfile('c: beep [[@LINE]]\nc: boop [[@LINE+3]]')
93+
compare.compare(actual, expected, check_prefix='c: ')
94+
9095
if __name__ == "__main__":
9196
unittest.main()

Tests/Functional/xctest_checker/xctest_checker/compare.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import re
1212

1313
from .error import XCTestCheckerError
14+
from .line import replace_offsets
1415

1516

1617
def _actual_lines(path):
@@ -40,7 +41,8 @@ def _expected_lines_and_line_numbers(path, check_prefix):
4041

4142
components = line.split(check_prefix)
4243
if len(components) == 2:
43-
yield components[1].strip(), line_number
44+
yield (replace_offsets(components[1].strip(), line_number),
45+
line_number)
4446
elif len(components) > 2:
4547
# Include a newline, then the file name and line number in the
4648
# exception in order to have it appear as an inline failure in
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# xctest_checker/line.py - Replaces [[@LINE]] with line numbers -*- python -*-
2+
#
3+
# This source file is part of the Swift.org open source project
4+
#
5+
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
6+
# Licensed under Apache License v2.0 with Runtime Library Exception
7+
#
8+
# See http://swift.org/LICENSE.txt for license information
9+
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
11+
import re
12+
13+
14+
def replace_offsets(line, line_number):
15+
"""
16+
Replace all line directives in the given line with the given line number.
17+
18+
Line directives come in two forms:
19+
1. "[[@LINE]]", with no offset.
20+
2. "[[@LINE+10]]" or "[[@LINE-3]]", with a positive or negative offset.
21+
"""
22+
pattern = re.compile(r'\[\[@LINE(?P<offset>[+-]\d+)?\]\]')
23+
24+
result = line
25+
for match in pattern.finditer(line):
26+
offset_string = match.groupdict()['offset']
27+
if offset_string is None:
28+
offset_string = '0'
29+
try:
30+
offset = int(offset_string)
31+
except ValueError:
32+
# Re-raise the error, but with a friendlier explanation of what
33+
# went wrong.
34+
raise ValueError(
35+
'Invalid line offset: "{}". Line offsets must be numerical, '
36+
'such as "[[@LINE+10]]" or "[[@LINE-2]]"'.format(
37+
match.group()))
38+
result = result.replace(match.group(), str(line_number + offset))
39+
40+
return result
41+

0 commit comments

Comments
 (0)