Skip to content

Commit 22fb093

Browse files
committed
[SR-710][Index] isTestCandidate access for Linux
The goal for https://bugs.swift.org/browse/SR-710 is to automatically generate a list of tests to run for XCTest on Linux. The prevailing approach is to generate this list using SourceKit, which is to be ported to Linux. SourceKit uses libIndex's concept of `isTestCandidate` to determine what constitutes a test. `isTestCandidate` is tested via `test/SourceKit/Indexing/index.swift`. On Linux, however, the list of tests to be run will be generated by some tool and placed in a file that is separate from the source file that defines the test method. Therefore, the test method must not be "private", since it needs to be accessed from a separate file. This commit adds two test files: one that verifies the behavior on Linux, and one that verifies the behavior on platforms with Objective-C interop. It also (1) simplifies the `isTestCandidate` function, and (2) adds the interop-specific logic. This commit does not remove the existing `isTestCandidate` tests in `test/SourceKit/Indexing/index.swift`; that is left for a future commit.
1 parent 1a2de3e commit 22fb093

File tree

5 files changed

+306
-19
lines changed

5 files changed

+306
-19
lines changed

lib/Index/Index.cpp

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -797,25 +797,42 @@ static bool isTestCandidate(ValueDecl *D) {
797797
if (!D->hasName())
798798
return false;
799799

800-
// A 'test candidate' is a class instance method that returns void, has no
801-
// parameters and starts with 'test'.
802-
// FIXME: Also test if it is ObjC exportable ?
803-
if (auto FD = dyn_cast<FuncDecl>(D)) {
804-
if (FD->isStatic())
805-
return false;
806-
if (!D->getDeclContext()->isTypeContext())
807-
return false;
808-
auto NTD = getNominalParent(D);
809-
if (!NTD)
810-
return false;
811-
Type RetTy = FD->getResultType();
812-
if (FD->getParameterLists().size() != 2)
813-
return false;
814-
auto paramList = FD->getParameterList(1);
815-
if (RetTy && RetTy->isVoid() && isa<ClassDecl>(NTD) &&
816-
paramList->size() == 0 && FD->getName().str().startswith("test"))
817-
return true;
818-
}
800+
// A 'test candidate' is:
801+
// 1. An instance method...
802+
auto FD = dyn_cast<FuncDecl>(D);
803+
if (!FD)
804+
return false;
805+
if (!D->isInstanceMember())
806+
return false;
807+
808+
// 2. ...on a class or extension (not a struct)...
809+
auto NTD = getNominalParent(D);
810+
if (!NTD)
811+
return false;
812+
if (!isa<ClassDecl>(NTD))
813+
return false;
814+
815+
// 3. ...that returns void...
816+
Type RetTy = FD->getResultType();
817+
if (RetTy && !RetTy->isVoid())
818+
return false;
819+
820+
// 4. ...takes no parameters...
821+
if (FD->getParameterLists().size() != 2)
822+
return false;
823+
if (FD->getParameterList(1)->size() != 0)
824+
return false;
825+
826+
// 5. ...is of at least 'internal' accessibility (unless we can use
827+
// Objective-C reflection)...
828+
#if SWIFT_OBJC_INTEROP
829+
if (D->getFormalAccess() < Accessibility::Internal)
830+
return false;
831+
#endif
832+
833+
// 6. ...and starts with "test".
834+
if (FD->getName().str().startswith("test"))
835+
return true;
819836

820837
return false;
821838
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: %sourcekitd-test -req=index %s -- -serialize-diagnostics -serialize-diagnostics-path %t.dia %s | %sed_clean > %t.response
2+
// RUN: diff -u %s.response %t.response
3+
4+
// This test verifies that, when Objective-C interop is disabled, private
5+
// methods are *not* surfaced as "test candidates".
6+
// FIXME: Ideally this test would run on any OS, provided Objective-C interop
7+
// was disabled.
8+
// REQUIRES: OS=linux-gnu
9+
10+
func test_takesNoParams_andReturnsVoid_butIsNotAnInstanceMethod() {}
11+
12+
struct MyStruct {
13+
func test_startsWithTest_takesNoParams_returnsVoid_butIsDefinedOnAStruct() {}
14+
}
15+
16+
private class MyPrivateClass() {
17+
func test_startsWithTest_takesNoParams_returnsVoid_butIsPrivate() {}
18+
}
19+
20+
public class MyClass {
21+
func doesNotStartWithTest() {}
22+
func test_startsWithTest_butTakesAParam(param: Int) {}
23+
func test_startsWithTest_andTakesNoParams_butReturnsNonVoid() -> Int {}
24+
private func test_startsWithTest_takesNoParams_andReturnsVoid_butIsPrivate() {}
25+
func test_startsWithTest_takesNoParams_returnsVoid() {}
26+
func test_startsWithTest_takesNoParams_returnsVoid_andThrows() throws {}
27+
}
28+
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
{
2+
key.hash: <hash>,
3+
key.dependencies: [
4+
{
5+
key.kind: source.lang.swift.import.module.swift,
6+
key.name: "Swift",
7+
key.filepath: Swift.swiftmodule,
8+
key.hash: <hash>,
9+
key.is_system: 1
10+
}
11+
],
12+
key.entities: [
13+
{
14+
key.kind: source.lang.swift.decl.function.free,
15+
key.name: "test_takesNoParams_andReturnsVoid_butIsNotAnInstanceMethod()",
16+
key.usr: "s:F23index_is_test_candidate58test_takesNoParams_andReturnsVoid_butIsNotAnInstanceMethodFT_T_",
17+
key.line: 4,
18+
key.column: 6
19+
},
20+
{
21+
key.kind: source.lang.swift.decl.class,
22+
key.name: "MyPrivateClass",
23+
key.usr: "s:C23index_is_test_candidateP33_E06F4E7BC5F577AB6E2EC6D3ECA1C8B914MyPrivateClass",
24+
key.line: 6,
25+
key.column: 15
26+
},
27+
{
28+
key.kind: source.lang.swift.decl.class,
29+
key.name: "MyClass",
30+
key.usr: "s:C23index_is_test_candidate7MyClass",
31+
key.line: 10,
32+
key.column: 14,
33+
key.entities: [
34+
{
35+
key.kind: source.lang.swift.decl.function.method.instance,
36+
key.name: "doesNotStartWithTest()",
37+
key.usr: "s:FC23index_is_test_candidate7MyClass20doesNotStartWithTestFT_T_",
38+
key.line: 11,
39+
key.column: 8
40+
},
41+
{
42+
key.kind: source.lang.swift.decl.function.method.instance,
43+
key.name: "test_startsWithTest_butTakesAParam(param:)",
44+
key.usr: "s:FC23index_is_test_candidate7MyClass34test_startsWithTest_butTakesAParamFT5paramSi_T_",
45+
key.line: 12,
46+
key.column: 8,
47+
key.entities: [
48+
{
49+
key.kind: source.lang.swift.ref.struct,
50+
key.name: "Int",
51+
key.usr: "s:Si",
52+
key.line: 12,
53+
key.column: 50
54+
}
55+
]
56+
},
57+
{
58+
key.kind: source.lang.swift.decl.function.method.instance,
59+
key.name: "test_startsWithTest_andTakesNoParams_butReturnsNonVoid()",
60+
key.usr: "s:FC23index_is_test_candidate7MyClass54test_startsWithTest_andTakesNoParams_butReturnsNonVoidFT_Si",
61+
key.line: 13,
62+
key.column: 8,
63+
key.entities: [
64+
{
65+
key.kind: source.lang.swift.ref.struct,
66+
key.name: "Int",
67+
key.usr: "s:Si",
68+
key.line: 13,
69+
key.column: 68
70+
}
71+
]
72+
},
73+
{
74+
key.kind: source.lang.swift.decl.function.method.instance,
75+
key.name: "test_startsWithTest_takesNoParams_andReturnsVoid_butIsPrivate()",
76+
key.usr: "s:FC23index_is_test_candidate7MyClassP33_E06F4E7BC5F577AB6E2EC6D3ECA1C8B961test_startsWithTest_takesNoParams_andReturnsVoid_butIsPrivateFT_T_",
77+
key.line: 14,
78+
key.column: 16,
79+
},
80+
{
81+
key.kind: source.lang.swift.decl.function.method.instance,
82+
key.name: "test_startsWithTest_takesNoParams_returnsVoid()",
83+
key.usr: "s:FC23index_is_test_candidate7MyClass45test_startsWithTest_takesNoParams_returnsVoidFT_T_",
84+
key.line: 15,
85+
key.column: 8,
86+
key.is_test_candidate: 1
87+
},
88+
{
89+
key.kind: source.lang.swift.decl.function.method.instance,
90+
key.name: "test_startsWithTest_takesNoParams_returnsVoid_andThrows()",
91+
key.usr: "s:FC23index_is_test_candidate7MyClass55test_startsWithTest_takesNoParams_returnsVoid_andThrowsFzT_T_",
92+
key.line: 16,
93+
key.column: 8,
94+
key.is_test_candidate: 1
95+
}
96+
]
97+
}
98+
]
99+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %sourcekitd-test -req=index %s -- -serialize-diagnostics -serialize-diagnostics-path %t.dia %s | %sed_clean > %t.response
2+
// RUN: diff -u %s.response %t.response
3+
4+
// This test verifies that, when Objective-C interop is enabled, all "test
5+
// candidate" methods are surfaced regardless of visibility. (On Linux, only
6+
// internal or public methods are considered "test candidates".)
7+
// REQUIRES: objc_interop
8+
9+
func test_takesNoParams_andReturnsVoid_butIsNotAnInstanceMethod() {}
10+
11+
struct MyStruct {
12+
func test_startsWithTest_takesNoParams_returnsVoid_butIsDefinedOnAStruct() {}
13+
}
14+
15+
private class MyPrivateClass() {
16+
func test_startsWithTest_takesNoParams_returnsVoid_andIsPrivate() {}
17+
}
18+
19+
public class MyClass {
20+
func doesNotStartWithTest() {}
21+
func test_startsWithTest_butTakesAParam(param: Int) {}
22+
func test_startsWithTest_andTakesNoParams_butReturnsNonVoid() -> Int {}
23+
private func test_startsWithTest_takesNoParams_returnsVoid_andIsPrivate() {}
24+
func test_startsWithTest_takesNoParams_returnsVoid() {}
25+
func test_startsWithTest_takesNoParams_returnsVoid_andThrows() throws {}
26+
}
27+
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
{
2+
key.hash: <hash>,
3+
key.dependencies: [
4+
{
5+
key.kind: source.lang.swift.import.module.swift,
6+
key.name: "Swift",
7+
key.filepath: Swift.swiftmodule,
8+
key.hash: <hash>,
9+
key.is_system: 1
10+
}
11+
],
12+
key.entities: [
13+
{
14+
key.kind: source.lang.swift.decl.function.free,
15+
key.name: "test_takesNoParams_andReturnsVoid_butIsNotAnInstanceMethod()",
16+
key.usr: "s:F28index_is_test_candidate_objc58test_takesNoParams_andReturnsVoid_butIsNotAnInstanceMethodFT_T_",
17+
key.line: 9,
18+
key.column: 6
19+
},
20+
{
21+
key.kind: source.lang.swift.decl.struct,
22+
key.name: "MyStruct",
23+
key.usr: "s:V28index_is_test_candidate_objc8MyStruct",
24+
key.line: 11,
25+
key.column: 8,
26+
key.entities: [
27+
{
28+
key.kind: source.lang.swift.decl.function.method.instance,
29+
key.name: "test_startsWithTest_takesNoParams_returnsVoid_butIsDefinedOnAStruct()",
30+
key.usr: "s:FV28index_is_test_candidate_objc8MyStruct67test_startsWithTest_takesNoParams_returnsVoid_butIsDefinedOnAStructFT_T_",
31+
key.line: 12,
32+
key.column: 8
33+
}
34+
]
35+
},
36+
{
37+
key.kind: source.lang.swift.decl.class,
38+
key.name: "MyPrivateClass",
39+
key.usr: "s:C28index_is_test_candidate_objcP33_32FED72643814BE1A523406CD2E729AA14MyPrivateClass",
40+
key.line: 15,
41+
key.column: 15
42+
},
43+
{
44+
key.kind: source.lang.swift.decl.class,
45+
key.name: "MyClass",
46+
key.usr: "s:C28index_is_test_candidate_objc7MyClass",
47+
key.line: 19,
48+
key.column: 14,
49+
key.entities: [
50+
{
51+
key.kind: source.lang.swift.decl.function.method.instance,
52+
key.name: "doesNotStartWithTest()",
53+
key.usr: "s:FC28index_is_test_candidate_objc7MyClass20doesNotStartWithTestFT_T_",
54+
key.line: 20,
55+
key.column: 8
56+
},
57+
{
58+
key.kind: source.lang.swift.decl.function.method.instance,
59+
key.name: "test_startsWithTest_butTakesAParam(param:)",
60+
key.usr: "s:FC28index_is_test_candidate_objc7MyClass34test_startsWithTest_butTakesAParamFT5paramSi_T_",
61+
key.line: 21,
62+
key.column: 8,
63+
key.entities: [
64+
{
65+
key.kind: source.lang.swift.ref.struct,
66+
key.name: "Int",
67+
key.usr: "s:Si",
68+
key.line: 21,
69+
key.column: 50
70+
}
71+
]
72+
},
73+
{
74+
key.kind: source.lang.swift.decl.function.method.instance,
75+
key.name: "test_startsWithTest_andTakesNoParams_butReturnsNonVoid()",
76+
key.usr: "s:FC28index_is_test_candidate_objc7MyClass54test_startsWithTest_andTakesNoParams_butReturnsNonVoidFT_Si",
77+
key.line: 22,
78+
key.column: 8,
79+
key.entities: [
80+
{
81+
key.kind: source.lang.swift.ref.struct,
82+
key.name: "Int",
83+
key.usr: "s:Si",
84+
key.line: 22,
85+
key.column: 68
86+
}
87+
]
88+
},
89+
{
90+
key.kind: source.lang.swift.decl.function.method.instance,
91+
key.name: "test_startsWithTest_takesNoParams_returnsVoid_andIsPrivate()",
92+
key.usr: "s:FC28index_is_test_candidate_objc7MyClassP33_32FED72643814BE1A523406CD2E729AA58test_startsWithTest_takesNoParams_returnsVoid_andIsPrivateFT_T_",
93+
key.line: 23,
94+
key.column: 16,
95+
key.is_test_candidate: 1
96+
},
97+
{
98+
key.kind: source.lang.swift.decl.function.method.instance,
99+
key.name: "test_startsWithTest_takesNoParams_returnsVoid()",
100+
key.usr: "s:FC28index_is_test_candidate_objc7MyClass45test_startsWithTest_takesNoParams_returnsVoidFT_T_",
101+
key.line: 24,
102+
key.column: 8,
103+
key.is_test_candidate: 1
104+
},
105+
{
106+
key.kind: source.lang.swift.decl.function.method.instance,
107+
key.name: "test_startsWithTest_takesNoParams_returnsVoid_andThrows()",
108+
key.usr: "s:FC28index_is_test_candidate_objc7MyClass55test_startsWithTest_takesNoParams_returnsVoid_andThrowsFzT_T_",
109+
key.line: 25,
110+
key.column: 8,
111+
key.is_test_candidate: 1
112+
}
113+
]
114+
}
115+
]
116+
}

0 commit comments

Comments
 (0)