Files
sourcekit-lsp/Tests/SourceKitLSPTests/DocumentTestDiscoveryTests.swift
Rintaro Ishizaki 1c99daabef [TestDiscovery] Prefer syntactic scan results over semantic index
Previously, test discovery used the semantic index as the primary
source and fell back to the syntactic index only for files where the
semantic index was out-of-date. This meant test locations came from the
semantic index, which only records a point position rather than the
full symbol range.

Flip the priority: use syntactic scan results as the primary source
(which have correct location ranges) and supplement with semantic index
results. The semantic results are range-fixed via
'textDocument/documentSymbol' before being returned.

This logic is unified into a single 'combineTests' helper shared by
both 'workspaceTests' and 'documentTests'.

Also fix 'SyntacticSwiftXCTestScanner' to emit extensions as proper
'AnnotatedTestItem' nodes (with 'isExtension: true') rather than a flat
list of methods, so extension test methods are correctly merged into
their class via 'mergingTestsInExtensions'. The class and extension
visitors are unified through a shared 'handleClassOrExtension' helper.
2026-03-04 16:58:21 -08:00

2002 lines
57 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
@_spi(SourceKitLSP) import LanguageServerProtocol
import SKLogging
import SKTestSupport
import SourceKitLSP
import XCTest
final class DocumentTestDiscoveryTests: SourceKitLSPTestCase {
func testIndexBasedDocumentTests() async throws {
try SkipUnless.longTestsEnabled()
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": """
import XCTest
1️⃣class MyTests: XCTestCase {
2️⃣func testMyLibrary() {}3️⃣
func unrelatedFunc() {}
var testVariable: Int = 0
}4️⃣
""",
"Tests/MyLibraryTests/MoreTests.swift": """
import XCTest
class MoreTests: XCTestCase {
func testSomeMore() {}
}
""",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [.testTarget(name: "MyLibraryTests")]
)
""",
enableBackgroundIndexing: true
)
let (uri, positions) = try project.openDocument("MyTests.swift")
let tests = try await project.testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testMyLibrary()",
label: "testMyLibrary()",
location: Location(uri: try project.uri(for: "MyTests.swift"), range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testSyntacticDocumentTestsSwift() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import XCTest
1️⃣class MyTests: XCTestCase {
2️⃣func testMyLibrary() {}3️⃣
func testWithAnArgument(x: Int) {}
func unrelatedFunc() {}
var testVariable: Int = 0
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/testMyLibrary()",
label: "testMyLibrary()",
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testDocumentTestsGetRefinedWithIndexedFile() async throws {
try SkipUnless.longTestsEnabled()
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": """
import XCTest
class LooksLikeTestCaseButIsNot {}
1️⃣class MyTests: LooksLikeTestCaseButIsNot {
2️⃣func testMyLibrary() {}3️⃣
func unrelatedFunc() {}
var testVariable: Int = 0
}4️⃣
"""
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [.testTarget(name: "MyLibraryTests")]
)
"""
)
let (uri, positions) = try project.openDocument("MyTests.swift")
// Syntactically we can't tell that `LooksLikeTestCaseButIsNot` is not a subclass of `XCTestCase`.
// We are thus conservative and report it as tests.
let syntacticTests = try await project.testClient.send(
DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri))
)
XCTAssertEqual(
syntacticTests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testMyLibrary()",
label: "testMyLibrary()",
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
try await SwiftPMTestProject.build(at: project.scratchDirectory)
try await project.testClient.send(SynchronizeRequest(index: true))
// After indexing, we know that `LooksLikeTestCaseButIsNot` does not inherit from `XCTestCase` and we don't report any tests.
let indexBasedTests = try await project.testClient.send(
DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri))
)
XCTAssertEqual(indexBasedTests, [])
}
func testSwiftTestingDocumentTests() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣struct MyTests {
2️⃣@Test
func oneIsTwo() {
#expect(1 == 2)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testSwiftTestingDocumentTestsInIndexedProject() async throws {
let project = try await IndexedSingleSwiftFileTestProject(
"""
import Testing
1️⃣struct MyTests {
2️⃣@Test
func oneIsTwo() {
#expect(1 == 2)
}3️⃣
}4️⃣
""",
allowBuildFailure: true
)
let tests = try await project.testClient.send(
DocumentTestsRequest(textDocument: TextDocumentIdentifier(project.fileURI))
)
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: project.fileURI, range: project.positions["1️⃣"]..<project.positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: project.fileURI, range: project.positions["2️⃣"]..<project.positions["3️⃣"])
)
]
)
]
)
}
func testSwiftTestingNestedSuites() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣struct MyTests {
2️⃣struct Inner {
3️⃣@Test
func oneIsTwo() {
#expect(1 == 2)
}4️⃣
}5️⃣
}6️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["6️⃣"]),
children: [
TestItem(
id: "MyTests/Inner",
label: "Inner",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["5️⃣"]),
children: [
TestItem(
id: "MyTests/Inner/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["3️⃣"]..<positions["4️⃣"])
)
]
)
]
)
]
)
}
func testSwiftTestingParameterizedTest() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣struct MyTests {
2️⃣@Test(arguments: [0, 1, 2])
func numbersAreOne(x: Int) {
#expect(x == 1)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/numbersAreOne(x:)",
label: "numbersAreOne(x:)",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testSwiftTestingParameterizedTestWithAnonymousArgument() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣struct MyTests {
2️⃣@Test(arguments: [0, 1, 2])
func numbersAreOne(_ x: Int) {
#expect(x == 1)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/numbersAreOne(_:)",
label: "numbersAreOne(_:)",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"]),
children: [],
tags: []
)
],
tags: []
)
]
)
}
func testSwiftTestingParameterizedTestWithCommentInSignature() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣struct MyTests {
2️⃣@Test(arguments: [0, 1, 2])
func numbersAreOne(x /* hello */: Int) {
#expect(x == 1)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/numbersAreOne(x:)",
label: "numbersAreOne(x:)",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testSwiftTestingTestsWithDuplicateFunctionIdentifiers() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣@Test(arguments: [1, 2, 3])
func foo(_ x: Int) {}2️⃣
3️⃣@Test(arguments: ["a", "b", "c"])
func foo(_ x: String) {}4️⃣
""",
uri: uri
)
let filename = uri.fileURL?.lastPathComponent ?? ""
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "foo(_:)/\(filename):\(positions["1️⃣"].line + 1):\(positions["1️⃣"].utf16index + 2)",
label: "foo(_:)",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"])
),
TestItem(
id: "foo(_:)/\(filename):\(positions["3️⃣"].line + 1):\(positions["3️⃣"].utf16index + 2)",
label: "foo(_:)",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["3️⃣"]..<positions["4️⃣"])
),
]
)
}
func testSwiftTestingTestsWithDuplicateFunctionIdentifiersInSuite() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣struct MySuite {
3️⃣@Test(arguments: [1, 2, 3])
func foo(_ x: Int) {}4️⃣
5️⃣@Test(arguments: ["a", "b", "c"])
func foo(_ x: String) {}6️⃣
}2️⃣
""",
uri: uri
)
let filename = uri.fileURL?.lastPathComponent ?? ""
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MySuite",
label: "MySuite",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"]),
children: [
TestItem(
id: "MySuite/foo(_:)/\(filename):\(positions["3️⃣"].line + 1):\(positions["3️⃣"].utf16index + 2)",
label: "foo(_:)",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["3️⃣"]..<positions["4️⃣"])
),
TestItem(
id: "MySuite/foo(_:)/\(filename):\(positions["5️⃣"].line + 1):\(positions["5️⃣"].utf16index + 2)",
label: "foo(_:)",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["5️⃣"]..<positions["6️⃣"])
),
]
)
]
)
}
func testSwiftTestingTestsWithDuplicateFunctionIdentifiersInExtension() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣struct MySuite {
3️⃣@Test(arguments: [1, 2, 3])
func foo(_ x: Int) {}4️⃣
}2️⃣
extension MySuite {
5️⃣@Test(arguments: ["a", "b", "c"])
func foo(_ x: String) {}6️⃣
}
""",
uri: uri
)
let filename = uri.fileURL?.lastPathComponent ?? ""
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MySuite",
label: "MySuite",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"]),
children: [
TestItem(
id: "MySuite/foo(_:)/\(filename):\(positions["3️⃣"].line + 1):\(positions["3️⃣"].utf16index + 2)",
label: "foo(_:)",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["3️⃣"]..<positions["4️⃣"])
),
TestItem(
id: "MySuite/foo(_:)/\(filename):\(positions["5️⃣"].line + 1):\(positions["5️⃣"].utf16index + 2)",
label: "foo(_:)",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["5️⃣"]..<positions["6️⃣"])
),
]
)
]
)
}
func testSwiftTestingSuiteWithNoTests() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣@Suite
struct MyTests {
}2️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"])
)
]
)
}
func testSwiftTestingSuiteWithCustomName() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣@Suite("My tests")
struct MyTests {
}2️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "My tests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"])
)
]
)
}
func testSwiftTestingTestWithCustomName() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣@Test("One is two")
func oneIsTwo() {
#expect(1 == 2)
}2️⃣
3️⃣@Test("One is two")
func `one is two`() {
#expect(1 == 2)
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "oneIsTwo()",
label: "One is two",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"])
),
TestItem(
id: "`one is two`()",
label: "One is two",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["3️⃣"]..<positions["4️⃣"])
),
]
)
}
func testSwiftTestingTestWithBackticksInName() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣struct `MyTests` {
2️⃣@Test
func `oneIsTwo`(`foo`: Int) {
#expect(1 == 2)
}3️⃣
}4️⃣
5️⃣extension `MyTests` {
6️⃣@Test
func `twoIsThree`() {
#expect(2 == 3)
}7️⃣
}8️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo(foo:)",
label: "oneIsTwo(foo:)",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
),
TestItem(
id: "MyTests/twoIsThree()",
label: "twoIsThree()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["6️⃣"]..<positions["7️⃣"])
),
]
)
]
)
}
func testSwiftTestingTestWithRawIdentifiers() async throws {
try await SkipUnless.haveRawIdentifiers()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣struct MyTests {
2️⃣@Test
func `one two`(`a b`: Int, c: Int, `3`: Int, `$`: Int, `+1`: Int) {
#expect(1 == 2)
}3️⃣
}4️⃣
extension MyTests {
5️⃣@Test
func `3four`() {
#expect(2 == 3)
}6️⃣
// Don't include operators
@Test
func +() {
#expect(2 == 3)
}
// This is invalid, but we'll pick it up as identifier.
7️⃣@Test
func `+`() {
#expect(2 == 3)
}8️⃣
// Also invalid.
9️⃣@Test
func ``() {
#expect(2 == 3)
}🔟
}
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/`one two`(`a b`:c:`3`:`$`:`+1`:)",
label: "one two",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
),
TestItem(
id: "MyTests/`3four`()",
label: "3four",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["5️⃣"]..<positions["6️⃣"])
),
TestItem(
id: "MyTests/`+`()",
label: "+",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["7️⃣"]..<positions["8️⃣"])
),
TestItem(
id: "MyTests/``()",
label: "``",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["9️⃣"]..<positions["🔟"])
),
]
)
]
)
}
func testSwiftTestingTestWithNestedRawIdentifiers() async throws {
try await SkipUnless.haveRawIdentifiers()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣struct `A B` {
2️⃣struct `C` {
3️⃣struct `D.E` {
4️⃣@Test
func `foo bar`() {
#expect(1 == 2)
}5️⃣
}6️⃣
}7️⃣
}8️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "`A B`",
label: "A B",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["8️⃣"]),
children: [
TestItem(
id: "`A B`/C",
label: "C",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["7️⃣"]),
children: [
TestItem(
id: "`A B`/C/`D.E`",
label: "D.E",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["3️⃣"]..<positions["6️⃣"]),
children: [
TestItem(
id: "`A B`/C/`D.E`/`foo bar`()",
label: "foo bar",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["4️⃣"]..<positions["5️⃣"])
)
]
)
]
)
]
)
]
)
}
func testSwiftTestingTestWithNestedRawIdentifiersExtension() async throws {
try await SkipUnless.haveRawIdentifiers()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
struct `A.B` {
struct `C` {
struct `D E` {}
}
}
1️⃣extension `A.B`.`C`.`D E` {
2️⃣@Test
func `foo bar`() {
#expect(1 == 2)
}3️⃣
}4️⃣
5️⃣extension `A.B` {
6️⃣@Test
func `bar baz`() {
#expect(1 == 2)
}7️⃣
}8️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "`A.B`/C/`D E`",
label: "D E",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "`A.B`/C/`D E`/`foo bar`()",
label: "foo bar",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
),
TestItem(
id: "`A.B`",
label: "A.B",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["5️⃣"]..<positions["8️⃣"]),
children: [
TestItem(
id: "`A.B`/`bar baz`()",
label: "bar baz",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["6️⃣"]..<positions["7️⃣"])
)
]
),
]
)
}
func testSwiftTestingTestWithSlashRawIdentifiers() async throws {
try await SkipUnless.haveRawIdentifiers()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣struct MyTests {
2️⃣@Test
func `x/y`() {
#expect(1 == 2)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/`x/y`()",
label: "x/y",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testSwiftTestingTestDisabledTest() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣@Test("One is two", .disabled())
func oneIsTwo() {
#expect(1 == 2)
}2️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "oneIsTwo()",
label: "One is two",
disabled: true,
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"])
)
]
)
}
func testSwiftTestingTestInDisabledSuite() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣@Suite(.disabled())
struct MyTests {
2️⃣@Test
func oneIsTwo() {
#expect(1 == 2)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
disabled: true,
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "oneIsTwo()",
disabled: true,
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testSwiftTestingHiddenTest() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
testClient.openDocument(
"""
import Testing
@Test("One is two", .hidden)
func oneIsTwo() {
#expect(1 == 2)
}
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[]
)
}
func testSwiftTestingTestWithTags() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣@Suite(.tags(.green))
struct MyTests {
2️⃣@Test(.tags(.red, .blue))
func oneIsTwo() {
#expect(1 == 2)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"]),
tags: [TestTag(id: "red"), TestTag(id: "blue")]
)
],
tags: [TestTag(id: "green")]
)
]
)
}
func testSwiftTestingTestWithCustomTags() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
extension Tag {
@Tag static var suite: Self
@Tag static var foo: Self
@Tag static var bar: Self
@Tag static var baz: Self
struct Nested {
@Tag static var foo: Tag
}
}
1️⃣@Suite(.tags(.suite))
struct MyTests {
2️⃣@Test(.tags(.foo, Nested.foo, Testing.Tag.bar, Tag.baz))
func oneIsTwo() {
#expect(1 == 2)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"]),
tags: [
TestTag(id: "foo"),
TestTag(id: "Nested.foo"),
TestTag(id: "bar"),
TestTag(id: "baz"),
]
)
],
tags: [TestTag(id: "suite")]
)
]
)
}
func testSwiftTestingTestsWithExtension() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣struct MyTests {
2️⃣@Test
func oneIsTwo() {
#expect(1 == 2)
}3️⃣
}4️⃣
5️⃣extension MyTests {
6️⃣@Test
func twoIsThree() {
#expect(2 == 3)
}7️⃣
}8️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
disabled: false,
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
),
TestItem(
id: "MyTests/twoIsThree()",
label: "twoIsThree()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["6️⃣"]..<positions["7️⃣"])
),
]
)
]
)
}
func testSwiftTestingTestSuitesWithExtension() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import XCTest
1️⃣@Suite struct MyTests {
2️⃣@Test func oneIsTwo() {}3️⃣
}4️⃣
extension MyTests {
5️⃣@Test func twoIsThree() {}6️⃣
}
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
),
TestItem(
id: "MyTests/twoIsThree()",
label: "twoIsThree()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["5️⃣"]..<positions["6️⃣"])
),
]
)
]
)
}
func testSwiftTestingNestedTestSuiteWithExtension() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣@Suite struct Outer {
3️⃣@Suite struct Inner {
5️⃣@Test func oneIsTwo {}6️⃣
}4️⃣
}2️⃣
extension Outer.Inner {
7️⃣@Test func twoIsThree() {}8️⃣
}
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "Outer",
label: "Outer",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"]),
children: [
TestItem(
id: "Outer/Inner",
label: "Inner",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["3️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "Outer/Inner/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["5️⃣"]..<positions["6️⃣"])
),
TestItem(
id: "Outer/Inner/twoIsThree()",
label: "twoIsThree()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["7️⃣"]..<positions["8️⃣"])
),
]
)
]
)
]
)
}
func testSwiftTestingExtensionOfTypeInAnotherFile() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣extension MyTests {
2️⃣@Test
func oneIsTwo() {
#expect(1 == 2)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testSwiftTestingExtensionOfNestedType() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
struct MyTests {
struct Inner {}
}
1️⃣extension MyTests.Inner {
2️⃣@Test
func oneIsTwo() {
#expect(1 == 2)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests/Inner",
label: "Inner",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/Inner/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testSwiftTestingTwoExtensionsNoDeclaration() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣extension MyTests {
3️⃣@Test func oneIsTwo() {}4️⃣
}2️⃣
extension MyTests {
5️⃣@Test func twoIsThree() {}6️⃣
}
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["3️⃣"]..<positions["4️⃣"]),
children: [],
tags: []
),
TestItem(
id: "MyTests/twoIsThree()",
label: "twoIsThree()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["5️⃣"]..<positions["6️⃣"])
),
]
)
]
)
}
func testSwiftTestingEnumSuite() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣enum MyTests {
2️⃣@Test
static func oneIsTwo() {
#expect(1 == 2)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testSwiftTestingActorSuite() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣actor MyTests {
2️⃣@Test
static func oneIsTwo() {
#expect(1 == 2)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testSwiftTestingFullyQualifyTestAttribute() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import Testing
1️⃣@Testing.Suite("My Tests")
struct MyTests {
2️⃣@Testing.Test("one is two")
func oneIsTwo() {
#expect(1 == 2)
}3️⃣
}4️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "My Tests",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyTests/oneIsTwo()",
label: "one is two",
style: TestStyle.swiftTesting,
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testXCTestTestsWithExtension() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import XCTest
1️⃣final class MyTests: XCTestCase {}2️⃣
extension MyTests {
3️⃣func testOneIsTwo() {}4️⃣
}
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"]),
children: [
TestItem(
id: "MyTests/testOneIsTwo()",
label: "testOneIsTwo()",
location: Location(uri: uri, range: positions["3️⃣"]..<positions["4️⃣"])
)
]
)
]
)
}
func testXCTestIndexedTestsWithExtension() async throws {
let project = try await IndexedSingleSwiftFileTestProject(
"""
import XCTest
1️⃣final class MyTests: XCTestCase {}2️⃣
extension MyTests {
3️⃣func testOneIsTwo() {}4️⃣
}
""",
allowBuildFailure: true
)
let tests = try await project.testClient.send(
DocumentTestsRequest(textDocument: TextDocumentIdentifier(project.fileURI))
)
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
location: Location(uri: project.fileURI, range: project.positions["1️⃣"]..<project.positions["2️⃣"]),
children: [
TestItem(
id: "MyTests/testOneIsTwo()",
label: "testOneIsTwo()",
location: Location(uri: project.fileURI, range: project.positions["3️⃣"]..<project.positions["4️⃣"])
)
]
)
]
)
}
func testXCTestIndexedTestsWithExtensionInSeparateFile() async throws {
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": """
import XCTest
class MyTests: XCTestCase {
}
""",
"Tests/MyLibraryTests/MoreTests.swift": """
import XCTest
1️⃣extension MyTests {
3️⃣func testMe() {}4️⃣
}2️⃣
""",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [.testTarget(name: "MyLibraryTests")]
)
""",
enableBackgroundIndexing: true
)
let (uri, positions) = try project.openDocument("MoreTests.swift")
let tests = try await project.testClient.send(
DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri))
)
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"]),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testMe()",
label: "testMe()",
location: Location(uri: uri, range: positions["3️⃣"]..<positions["4️⃣"])
)
]
)
]
)
}
func testXCTestInvalidXCTestSuiteConstructions() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import XCTest
// This comment contains the string XCTestCase
final class NSObjectInheritance: NSObject {}
final class BaseClass {}
final class MyEmptyTests: BaseClass {}
1️⃣final class MyTests: XCTestCase {
static func testStaticFuncIsNotATest() {}
}2️⃣
""",
uri: uri
)
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
location: Location(uri: uri, range: positions["1️⃣"]..<positions["2️⃣"]),
children: []
)
]
)
}
func testAddNewMethodToNotQuiteTestCase() async throws {
let project = try await IndexedSingleSwiftFileTestProject(
"""
import XCTest
class NotQuiteTest: SomeClass {
func testMyLibrary() {}
2️⃣
}
""",
allowBuildFailure: true
)
let testsBeforeEdit = try await project.testClient.send(
DocumentTestsRequest(textDocument: TextDocumentIdentifier(project.fileURI))
)
XCTAssertEqual(testsBeforeEdit, [])
project.testClient.send(
DidChangeTextDocumentNotification(
textDocument: VersionedTextDocumentIdentifier(project.fileURI, version: 2),
contentChanges: [
TextDocumentContentChangeEvent(range: Range(project.positions["2️⃣"]), text: "func testSomethingElse() {}")
]
)
)
let testsAfterEdit = try await project.testClient.send(
DocumentTestsRequest(textDocument: TextDocumentIdentifier(project.fileURI))
)
XCTAssertEqual(testsAfterEdit, [])
}
func testAddNewClassToNotQuiteTestCase() async throws {
let project = try await IndexedSingleSwiftFileTestProject(
"""
import XCTest
class NotQuiteTest: SomeClass {
func testMyLibrary() {}
}
2️⃣
""",
allowBuildFailure: true
)
let testsBeforeEdit = try await project.testClient.send(
DocumentTestsRequest(textDocument: TextDocumentIdentifier(project.fileURI))
)
XCTAssertEqual(testsBeforeEdit, [])
project.testClient.send(
DidChangeTextDocumentNotification(
textDocument: VersionedTextDocumentIdentifier(project.fileURI, version: 2),
contentChanges: [
TextDocumentContentChangeEvent(
range: Range(project.positions["2️⃣"]),
text: """
class OtherNotQuiteTest: SomeClass {
func testSomethingElse() {}
}
"""
)
]
)
)
let testsAfterEdit = try await project.testClient.send(
DocumentTestsRequest(textDocument: TextDocumentIdentifier(project.fileURI))
)
// We know from the semantic index that NotQuiteTest does not inherit from XCTestCase, so we should not include it.
// We don't have any semantic knowledge about `OtherNotQuiteTest`, so we are conservative and should include it.
XCTAssertFalse(testsAfterEdit.contains { $0.label == "NotQuiteTest" })
XCTAssertTrue(testsAfterEdit.contains { $0.label == "OtherNotQuiteTest" })
}
func testObjectiveCTestFromSemanticIndex() async throws {
try SkipUnless.platformIsDarwin("Non-Darwin platforms don't support Objective-C")
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/Test.m": """
#import <XCTest/XCTest.h>
@interface MyTests : XCTestCase
@end
1️⃣@implementation MyTests
2️⃣- (void)testSomething {
}3️⃣
@4️⃣end
"""
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [.testTarget(name: "MyLibraryTests")]
)
""",
enableBackgroundIndexing: true
)
let (uri, positions) = try project.openDocument("Test.m")
let tests = try await project.testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testSomething",
label: "testSomething",
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testObjectiveCTestsAfterInMemoryEdit() async throws {
try SkipUnless.platformIsDarwin("Non-Darwin platforms don't support Objective-C")
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/Test.m": """
#import <XCTest/XCTest.h>
@interface MyTests : XCTestCase
@end
1️⃣@implementation MyTests
2️⃣- (void)testSomething {}3️⃣
0️⃣
@4️⃣end
"""
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [.testTarget(name: "MyLibraryTests")]
)
""",
enableBackgroundIndexing: true
)
let (uri, positions) = try project.openDocument("Test.m")
project.testClient.send(
DidChangeTextDocumentNotification(
textDocument: VersionedTextDocumentIdentifier(uri, version: 2),
contentChanges: [
TextDocumentContentChangeEvent(
range: Range(positions["0️⃣"]),
text: """
- (void)testSomethingElse {}
"""
)
]
)
)
let tests = try await project.testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
// Since we don't have syntactic test discovery for clang-languages, we don't discover `testSomethingElse` as a
// test method until we perform a build
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testSomething",
label: "testSomething",
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"])
)
]
)
]
)
}
func testSwiftTestingTestsAreNotReportedInNonTestTargets() async throws {
let project = try await SwiftPMTestProject(
files: [
"FileA.swift": """
@Suite struct MyTests {
@Test func inStruct() {}
}
"""
]
)
let (uri, _) = try project.openDocument("FileA.swift")
let tests = try await project.testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
XCTAssertEqual(tests, [])
}
func testXCTestsInInactiveIfRegionsAreDiscovered() async throws {
// XCTest declarations inside inactive `#if` branches are not indexed by the semantic index because
// the compiler never sees them. Verify that they are still surfaced via syntactic scanning of the
// inactive region after the semantic index is available.
let project = try await IndexedSingleSwiftFileTestProject(
"""
import XCTest
// Active test class – found by the semantic index.
1️⃣class ActiveTests: XCTestCase {
2️⃣func testActiveMethod() {}3️⃣
}4️⃣
// Inactive region – the compiler never compiles this, so the semantic index
// won't have an entry for it. We expect syntactic scanning to pick it up.
#if false
5️⃣class InactiveTests: XCTestCase {
6️⃣func testInactiveMethod() {}7️⃣
}8️⃣
#endif
""",
allowBuildFailure: true
)
let tests = try await project.testClient.send(
DocumentTestsRequest(textDocument: TextDocumentIdentifier(project.fileURI))
)
// Both the active-region class (from the index) and the inactive-region class
// (found syntactically) should be present.
XCTAssertEqual(
tests,
[
TestItem(
id: "ActiveTests",
label: "ActiveTests",
location: Location(
uri: project.fileURI,
range: project.positions["1️⃣"]..<project.positions["4️⃣"]
),
children: [
TestItem(
id: "ActiveTests/testActiveMethod()",
label: "testActiveMethod()",
location: Location(
uri: project.fileURI,
range: project.positions["2️⃣"]..<project.positions["3️⃣"]
)
)
]
),
TestItem(
id: "InactiveTests",
label: "InactiveTests",
location: Location(
uri: project.fileURI,
range: project.positions["5️⃣"]..<project.positions["8️⃣"]
),
children: [
TestItem(
id: "InactiveTests/testInactiveMethod()",
label: "testInactiveMethod()",
location: Location(
uri: project.fileURI,
range: project.positions["6️⃣"]..<project.positions["7️⃣"]
)
)
]
),
]
)
}
}