Merge pull request #6999 from akyrtzi/index-kinds-test-subclass

[index] Mark unit test methods if the class subclasses XCTestCase.
diff --git a/lib/Index/Index.cpp b/lib/Index/Index.cpp
index 79f6b7e..054424e 100644
--- a/lib/Index/Index.cpp
+++ b/lib/Index/Index.cpp
@@ -860,7 +860,19 @@
   return Ty->getAnyNominal();
 }
 
-static bool isTestCandidate(ValueDecl *D) {
+/// \returns true if \c D is a subclass of 'XCTestCase'.
+static bool isUnitTestCase(const ClassDecl *D) {
+  if (!D)
+    return false;
+  while (auto *SuperD = D->getSuperclassDecl()) {
+    if (SuperD->getNameStr() == "XCTestCase")
+      return true;
+    D = SuperD;
+  }
+  return false;
+}
+
+static bool isUnitTest(ValueDecl *D) {
   if (!D->hasName())
     return false;
 
@@ -872,12 +884,14 @@
   if (!D->isInstanceMember())
     return false;
 
-  // 2. ...on a class or extension (not a struct)...
+  // 2. ...on a class or extension (not a struct) subclass of XCTestCase...
   auto parentNTD = getNominalParent(D);
   if (!parentNTD)
     return false;
   if (!isa<ClassDecl>(parentNTD))
     return false;
+  if (!isUnitTestCase(cast<ClassDecl>(parentNTD)))
+    return false;
 
   // 3. ...that returns void...
   Type RetTy = FD->getResultInterfaceType();
@@ -909,7 +923,7 @@
   if (initIndexSymbol(D, D->getLoc(), /*IsRef=*/false, Info))
     return true;
 
-  if (isTestCandidate(D))
+  if (isUnitTest(D))
     Info.symInfo.Properties |= SymbolProperty::UnitTest;
 
   if (auto Group = D->getGroupName())
diff --git a/test/Index/kinds.swift b/test/Index/kinds.swift
index 5f966d2..9a1f18c 100644
--- a/test/Index/kinds.swift
+++ b/test/Index/kinds.swift
@@ -175,5 +175,24 @@
 func +(a: AStruct, b: AStruct) -> AStruct { return a }
 // CHECK: [[@LINE-1]]:6 | function/infix-operator/Swift | +(_:_:) | s:F14swift_ide_testoi1pFTVS_7AStructS0__S0_ | Def | rel: 0
 
-
-// TODO: UnitTest
+class XCTestCase {}
+class MyTestCase : XCTestCase {
+  func testMe() {}
+  // CHECK: [[@LINE-1]]:8 | instance-method(test)/Swift | testMe() |
+  func testResult() -> Int? { return nil }
+  // CHECK: [[@LINE-1]]:8 | instance-method/Swift | testResult() |
+  func test(withInt: Int) {}
+  // CHECK: [[@LINE-1]]:8 | instance-method/Swift | test(withInt:) |
+}
+class SubTestCase : MyTestCase {
+  func testIt2() {}
+  // CHECK: [[@LINE-1]]:8 | instance-method(test)/Swift | testIt2() |
+}
+extension SubTestCase {
+  func testIt3() {}
+  // CHECK: [[@LINE-1]]:8 | instance-method(test)/Swift | testIt3() |
+}
+class NonTestCase {
+  func testMeNot() {}
+  // CHECK: [[@LINE-1]]:8 | instance-method/Swift | testMeNot() |
+}
diff --git a/test/SourceKit/Indexing/index.swift b/test/SourceKit/Indexing/index.swift
index bc37807..8f08060 100644
--- a/test/SourceKit/Indexing/index.swift
+++ b/test/SourceKit/Indexing/index.swift
@@ -130,22 +130,6 @@
   s.sfoo()
 }
 
-// Test candidates.
-struct S3 {
-  func test() {} // no.
-}
-protocol P2 {
-  func test() // no.
-}
-class CC3 {
-  func meth() {} // no.
-  class func test1() {} // no.
-  func test2() {} // yes.
-}
-extension CC3 {
-  func test3() {} // yes.
-}
-
 extension Undeclared {
   func meth() {}
 }
diff --git a/test/SourceKit/Indexing/index.swift.response b/test/SourceKit/Indexing/index.swift.response
index 47bfa66..1663765 100644
--- a/test/SourceKit/Indexing/index.swift.response
+++ b/test/SourceKit/Indexing/index.swift.response
@@ -1108,125 +1108,38 @@
       ]
     },
     {
-      key.kind: source.lang.swift.decl.struct,
-      key.name: "S3",
-      key.usr: "s:V5index2S3",
-      key.line: 134,
-      key.column: 8,
-      key.entities: [
-        {
-          key.kind: source.lang.swift.decl.function.method.instance,
-          key.name: "test()",
-          key.usr: "s:FV5index2S34testFT_T_",
-          key.line: 135,
-          key.column: 8
-        }
-      ]
-    },
-    {
-      key.kind: source.lang.swift.decl.protocol,
-      key.name: "P2",
-      key.usr: "s:P5index2P2",
-      key.line: 137,
-      key.column: 10,
-      key.entities: [
-        {
-          key.kind: source.lang.swift.decl.function.method.instance,
-          key.name: "test()",
-          key.usr: "s:FP5index2P24testFT_T_",
-          key.line: 138,
-          key.column: 8
-        }
-      ]
-    },
-    {
-      key.kind: source.lang.swift.decl.class,
-      key.name: "CC3",
-      key.usr: "s:C5index3CC3",
-      key.line: 140,
-      key.column: 7,
-      key.entities: [
-        {
-          key.kind: source.lang.swift.decl.function.method.instance,
-          key.name: "meth()",
-          key.usr: "s:FC5index3CC34methFT_T_",
-          key.line: 141,
-          key.column: 8
-        },
-        {
-          key.kind: source.lang.swift.decl.function.method.class,
-          key.name: "test1()",
-          key.usr: "s:ZFC5index3CC35test1FT_T_",
-          key.line: 142,
-          key.column: 14
-        },
-        {
-          key.kind: source.lang.swift.decl.function.method.instance,
-          key.name: "test2()",
-          key.usr: "s:FC5index3CC35test2FT_T_",
-          key.line: 143,
-          key.column: 8,
-          key.is_test_candidate: 1
-        }
-      ]
-    },
-    {
-      key.kind: source.lang.swift.decl.extension.class,
-      key.name: "CC3",
-      key.usr: "s:C5index3CC3",
-      key.line: 145,
-      key.column: 11,
-      key.entities: [
-        {
-          key.kind: source.lang.swift.ref.class,
-          key.name: "CC3",
-          key.usr: "s:C5index3CC3",
-          key.line: 145,
-          key.column: 11
-        },
-        {
-          key.kind: source.lang.swift.decl.function.method.instance,
-          key.name: "test3()",
-          key.usr: "s:FC5index3CC35test3FT_T_",
-          key.line: 146,
-          key.column: 8,
-          key.is_test_candidate: 1
-        }
-      ]
-    },
-    {
       key.kind: source.lang.swift.decl.function.method.instance,
       key.name: "meth()",
       key.usr: "s:F5index4methERR",
-      key.line: 150,
+      key.line: 134,
       key.column: 8
     },
     {
       key.kind: source.lang.swift.decl.class,
       key.name: "CC4",
       key.usr: "s:C5index3CC4",
-      key.line: 153,
+      key.line: 137,
       key.column: 7,
       key.entities: [
         {
           key.kind: source.lang.swift.decl.function.constructor,
           key.name: "init(x:)",
           key.usr: "s:FC5index3CC4cFT1xSi_S0_",
-          key.line: 154,
+          key.line: 138,
           key.column: 15,
           key.entities: [
             {
               key.kind: source.lang.swift.ref.struct,
               key.name: "Int",
               key.usr: "s:Si",
-              key.line: 154,
+              key.line: 138,
               key.column: 23
             },
             {
               key.kind: source.lang.swift.ref.function.constructor,
               key.name: "init(x:)",
               key.usr: "s:FC5index3CC4cFT1xSi_S0_",
-              key.line: 155,
+              key.line: 139,
               key.column: 10,
               key.receiver_usr: "s:C5index3CC4",
               key.is_dynamic: 1
@@ -1244,14 +1157,14 @@
       key.kind: source.lang.swift.decl.class,
       key.name: "SubCC4",
       key.usr: "s:C5index6SubCC4",
-      key.line: 159,
+      key.line: 143,
       key.column: 7,
       key.related: [
         {
           key.kind: source.lang.swift.ref.class,
           key.name: "CC4",
           key.usr: "s:C5index3CC4",
-          key.line: 159,
+          key.line: 143,
           key.column: 16
         }
       ],
@@ -1260,14 +1173,14 @@
           key.kind: source.lang.swift.ref.class,
           key.name: "CC4",
           key.usr: "s:C5index3CC4",
-          key.line: 159,
+          key.line: 143,
           key.column: 16
         },
         {
           key.kind: source.lang.swift.decl.function.constructor,
           key.name: "init(x:)",
           key.usr: "s:FC5index6SubCC4cFT1xSi_S0_",
-          key.line: 160,
+          key.line: 144,
           key.column: 3,
           key.related: [
             {
@@ -1281,14 +1194,14 @@
               key.kind: source.lang.swift.ref.struct,
               key.name: "Int",
               key.usr: "s:Si",
-              key.line: 160,
+              key.line: 144,
               key.column: 11
             },
             {
               key.kind: source.lang.swift.ref.function.constructor,
               key.name: "init(x:)",
               key.usr: "s:FC5index3CC4cFT1xSi_S0_",
-              key.line: 161,
+              key.line: 145,
               key.column: 11,
               key.receiver_usr: "s:C5index3CC4"
             }
@@ -1300,35 +1213,35 @@
       key.kind: source.lang.swift.decl.class,
       key.name: "Observing",
       key.usr: "s:C5index9Observing",
-      key.line: 165,
+      key.line: 149,
       key.column: 7,
       key.entities: [
         {
           key.kind: source.lang.swift.decl.function.constructor,
           key.name: "init()",
           key.usr: "s:FC5index9ObservingcFT_S0_",
-          key.line: 166,
+          key.line: 150,
           key.column: 3
         },
         {
           key.kind: source.lang.swift.decl.var.instance,
           key.name: "globObserving",
           key.usr: "s:vC5index9Observing13globObservingSi",
-          key.line: 167,
+          key.line: 151,
           key.column: 7,
           key.entities: [
             {
               key.kind: source.lang.swift.decl.function.accessor.willset,
               key.name: "willSet:globObserving",
               key.usr: "s:FC5index9Observingw13globObservingSi",
-              key.line: 168,
+              key.line: 152,
               key.column: 5,
               key.entities: [
                 {
                   key.kind: source.lang.swift.ref.function.free,
                   key.name: "test2()",
                   key.usr: "s:F5index5test2FT_T_",
-                  key.line: 169,
+                  key.line: 153,
                   key.column: 7
                 }
               ],
@@ -1342,14 +1255,14 @@
               key.kind: source.lang.swift.decl.function.accessor.didset,
               key.name: "didSet:globObserving",
               key.usr: "s:FC5index9ObservingW13globObservingSi",
-              key.line: 171,
+              key.line: 155,
               key.column: 5,
               key.entities: [
                 {
                   key.kind: source.lang.swift.ref.function.free,
                   key.name: "test2()",
                   key.usr: "s:F5index5test2FT_T_",
-                  key.line: 172,
+                  key.line: 156,
                   key.column: 7
                 }
               ],
@@ -1365,7 +1278,7 @@
           key.kind: source.lang.swift.ref.struct,
           key.name: "Int",
           key.usr: "s:Si",
-          key.line: 167,
+          key.line: 151,
           key.column: 23
         }
       ]
@@ -1374,21 +1287,21 @@
       key.kind: source.lang.swift.decl.class,
       key.name: "rdar18640140",
       key.usr: "s:C5index12rdar18640140",
-      key.line: 178,
+      key.line: 162,
       key.column: 7,
       key.entities: [
         {
           key.kind: source.lang.swift.decl.var.instance,
           key.name: "S1",
           key.usr: "s:vC5index12rdar186401402S1Si",
-          key.line: 180,
+          key.line: 164,
           key.column: 7,
           key.entities: [
             {
               key.kind: source.lang.swift.decl.function.accessor.didset,
               key.name: "didSet:S1",
               key.usr: "s:FC5index12rdar18640140W2S1Si",
-              key.line: 186,
+              key.line: 170,
               key.column: 5,
               key.attributes: [
                 {
@@ -1402,7 +1315,7 @@
           key.kind: source.lang.swift.ref.struct,
           key.name: "Int",
           key.usr: "s:Si",
-          key.line: 180,
+          key.line: 164,
           key.column: 11
         }
       ]
@@ -1411,28 +1324,28 @@
       key.kind: source.lang.swift.decl.protocol,
       key.name: "rdar18640140Protocol",
       key.usr: "s:P5index20rdar18640140Protocol",
-      key.line: 191,
+      key.line: 175,
       key.column: 10,
       key.entities: [
         {
           key.kind: source.lang.swift.decl.var.instance,
           key.name: "S1",
           key.usr: "s:vP5index20rdar18640140Protocol2S1Si",
-          key.line: 192,
+          key.line: 176,
           key.column: 7,
           key.entities: [
             {
               key.kind: source.lang.swift.decl.function.accessor.getter,
               key.name: "getter:S1",
               key.usr: "s:FP5index20rdar18640140Protocolg2S1Si",
-              key.line: 195,
+              key.line: 179,
               key.column: 5
             },
             {
               key.kind: source.lang.swift.decl.function.accessor.setter,
               key.name: "setter:S1",
               key.usr: "s:FP5index20rdar18640140Protocols2S1Si",
-              key.line: 194,
+              key.line: 178,
               key.column: 5
             }
           ]
@@ -1441,7 +1354,7 @@
           key.kind: source.lang.swift.ref.struct,
           key.name: "Int",
           key.usr: "s:Si",
-          key.line: 192,
+          key.line: 176,
           key.column: 11
         }
       ]
@@ -1450,7 +1363,7 @@
       key.kind: source.lang.swift.decl.class,
       key.name: "ConditionalUnavailableClass1",
       key.usr: "s:C5index28ConditionalUnavailableClass1",
-      key.line: 204,
+      key.line: 188,
       key.column: 7,
       key.attributes: [
         {
@@ -1462,7 +1375,7 @@
       key.kind: source.lang.swift.decl.class,
       key.name: "ConditionalUnavailableClass2",
       key.usr: "s:C5index28ConditionalUnavailableClass2",
-      key.line: 208,
+      key.line: 192,
       key.column: 7,
       key.attributes: [
         {
diff --git a/test/SourceKit/Indexing/index_is_test_candidate.swift b/test/SourceKit/Indexing/index_is_test_candidate.swift
index 3d0d9f6..f919927 100644
--- a/test/SourceKit/Indexing/index_is_test_candidate.swift
+++ b/test/SourceKit/Indexing/index_is_test_candidate.swift
@@ -12,12 +12,12 @@
 struct MyStruct {
   func test_startsWithTest_takesNoParams_returnsVoid_butIsDefinedOnAStruct() {}
 }
-
-private class MyPrivateClass {
+class XCTestCase {}
+private class MyPrivateClass : XCTestCase {
   func test_startsWithTest_takesNoParams_returnsVoid_butIsPrivate() {}
 }
 
-public class MyClass {
+public class MyClass : XCTestCase {
   func doesNotStartWithTest() {}
   func test_startsWithTest_butTakesAParam(param: Int) {}
   func test_startsWithTest_andTakesNoParams_butReturnsNonVoid() -> Int {}
diff --git a/test/SourceKit/Indexing/index_is_test_candidate.swift.response b/test/SourceKit/Indexing/index_is_test_candidate.swift.response
index c761e49..0f8335c 100644
--- a/test/SourceKit/Indexing/index_is_test_candidate.swift.response
+++ b/test/SourceKit/Indexing/index_is_test_candidate.swift.response
@@ -35,12 +35,35 @@
     },
     {
       key.kind: source.lang.swift.decl.class,
+      key.name: "XCTestCase",
+      key.usr: "s:C28index_is_test_candidate_objc10XCTestCase",
+      key.line: 15,
+      key.column: 7
+    },
+    {
+      key.kind: source.lang.swift.decl.class,
       key.name: "MyPrivateClass",
       key.usr: "s:C23index_is_test_candidateP33_E06F4E7BC5F577AB6E2EC6D3ECA1C8B914MyPrivateClass",
       key.line: 16,
       key.column: 15,
+      key.related: [
+        {
+          key.kind: source.lang.swift.ref.class,
+          key.name: "XCTestCase",
+          key.usr: "s:C28index_is_test_candidate_objc10XCTestCase",
+          key.line: 16,
+          key.column: 32
+        }
+      ],
       key.entities: [
         {
+          key.kind: source.lang.swift.ref.class,
+          key.name: "XCTestCase",
+          key.usr: "s:C28index_is_test_candidate_objc10XCTestCase",
+          key.line: 16,
+          key.column: 32
+        },
+        {
           key.kind: source.lang.swift.decl.function.method.instance,
           key.name: "test_startsWithTest_takesNoParams_returnsVoid_butIsPrivate()",
           key.usr: "s:FC23index_is_test_candidateP33_E06F4E7BC5F577AB6E2EC6D3ECA1C8B914MyPrivateClass58test_startsWithTest_takesNoParams_returnsVoid_butIsPrivateFT_T_",
@@ -55,8 +78,24 @@
       key.usr: "s:C23index_is_test_candidate7MyClass",
       key.line: 20,
       key.column: 14,
+      key.related: [
+        {
+          key.kind: source.lang.swift.ref.class,
+          key.name: "XCTestCase",
+          key.usr: "s:C28index_is_test_candidate_objc10XCTestCase",
+          key.line: 20,
+          key.column: 24
+        }
+      ],
       key.entities: [
         {
+          key.kind: source.lang.swift.ref.class,
+          key.name: "XCTestCase",
+          key.usr: "s:C28index_is_test_candidate_objc10XCTestCase",
+          key.line: 20,
+          key.column: 24
+        },
+        {
           key.kind: source.lang.swift.decl.function.method.instance,
           key.name: "doesNotStartWithTest()",
           key.usr: "s:FC23index_is_test_candidate7MyClass20doesNotStartWithTestFT_T_",
diff --git a/test/SourceKit/Indexing/index_is_test_candidate_objc.swift b/test/SourceKit/Indexing/index_is_test_candidate_objc.swift
index cfb74af..23b7766 100644
--- a/test/SourceKit/Indexing/index_is_test_candidate_objc.swift
+++ b/test/SourceKit/Indexing/index_is_test_candidate_objc.swift
@@ -11,12 +11,12 @@
 struct MyStruct {
   func test_startsWithTest_takesNoParams_returnsVoid_butIsDefinedOnAStruct() {}
 }
-
-private class MyPrivateClass {
+class XCTestCase {}
+private class MyPrivateClass : XCTestCase {
   func test_startsWithTest_takesNoParams_returnsVoid_andIsPrivate() {}
 }
 
-public class MyClass {
+public class MyClass : XCTestCase {
   func doesNotStartWithTest() {}
   func test_startsWithTest_butTakesAParam(param: Int) {}
   func test_startsWithTest_andTakesNoParams_butReturnsNonVoid() -> Int {}
diff --git a/test/SourceKit/Indexing/index_is_test_candidate_objc.swift.response b/test/SourceKit/Indexing/index_is_test_candidate_objc.swift.response
index e953402..9dfb1ee 100644
--- a/test/SourceKit/Indexing/index_is_test_candidate_objc.swift.response
+++ b/test/SourceKit/Indexing/index_is_test_candidate_objc.swift.response
@@ -35,12 +35,35 @@
     },
     {
       key.kind: source.lang.swift.decl.class,
+      key.name: "XCTestCase",
+      key.usr: "s:C28index_is_test_candidate_objc10XCTestCase",
+      key.line: 14,
+      key.column: 7
+    },
+    {
+      key.kind: source.lang.swift.decl.class,
       key.name: "MyPrivateClass",
       key.usr: "s:C28index_is_test_candidate_objcP33_32FED72643814BE1A523406CD2E729AA14MyPrivateClass",
       key.line: 15,
       key.column: 15,
+      key.related: [
+        {
+          key.kind: source.lang.swift.ref.class,
+          key.name: "XCTestCase",
+          key.usr: "s:C28index_is_test_candidate_objc10XCTestCase",
+          key.line: 15,
+          key.column: 32
+        }
+      ],
       key.entities: [
         {
+          key.kind: source.lang.swift.ref.class,
+          key.name: "XCTestCase",
+          key.usr: "s:C28index_is_test_candidate_objc10XCTestCase",
+          key.line: 15,
+          key.column: 32
+        },
+        {
           key.kind: source.lang.swift.decl.function.method.instance,
           key.name: "test_startsWithTest_takesNoParams_returnsVoid_andIsPrivate()",
           key.usr: "s:FC28index_is_test_candidate_objcP33_32FED72643814BE1A523406CD2E729AA14MyPrivateClass58test_startsWithTest_takesNoParams_returnsVoid_andIsPrivateFT_T_",
@@ -56,8 +79,24 @@
       key.usr: "s:C28index_is_test_candidate_objc7MyClass",
       key.line: 19,
       key.column: 14,
+      key.related: [
+        {
+          key.kind: source.lang.swift.ref.class,
+          key.name: "XCTestCase",
+          key.usr: "s:C28index_is_test_candidate_objc10XCTestCase",
+          key.line: 19,
+          key.column: 24
+        }
+      ],
       key.entities: [
         {
+          key.kind: source.lang.swift.ref.class,
+          key.name: "XCTestCase",
+          key.usr: "s:C28index_is_test_candidate_objc10XCTestCase",
+          key.line: 19,
+          key.column: 24
+        },
+        {
           key.kind: source.lang.swift.decl.function.method.instance,
           key.name: "doesNotStartWithTest()",
           key.usr: "s:FC28index_is_test_candidate_objc7MyClass20doesNotStartWithTestFT_T_",