Files
sourcekit-lsp/Tests/SourceKitLSPTests/WorkspaceTestDiscoveryTests.swift
2025-12-08 15:16:36 -05:00

1152 lines
34 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. 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(Testing) import BuildServerIntegration
import Foundation
@_spi(SourceKitLSP) import LanguageServerProtocol
import SKLogging
import SKTestSupport
import SemanticIndex
@_spi(Testing) import SourceKitLSP
import SwiftExtensions
import ToolchainRegistry
@_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions
import XCTest
import struct TSCBasic.AbsolutePath
private let packageManifestWithTestTarget = """
let package = Package(
name: "MyLibrary",
targets: [.testTarget(name: "MyLibraryTests")]
)
"""
final class WorkspaceTestDiscoveryTests: SourceKitLSPTestCase {
func testIndexBasedWorkspaceXCTests() async throws {
try SkipUnless.longTestsEnabled()
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": """
import XCTest
class 1⃣MyTests: XCTestCase {
func 2⃣testMyLibrary() {}
func unrelatedFunc() {}
var testVariable: Int = 0
}
"""
],
manifest: packageManifestWithTestTarget,
enableBackgroundIndexing: true
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: Location(
uri: try project.uri(for: "MyTests.swift"),
range: Range(try project.position(of: "1", in: "MyTests.swift"))
),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testMyLibrary()",
label: "testMyLibrary()",
location: Location(
uri: try project.uri(for: "MyTests.swift"),
range: Range(try project.position(of: "2", in: "MyTests.swift"))
)
)
]
)
]
)
}
func testSyntacticWorkspaceXCTests() async throws {
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
"""
],
manifest: packageManifestWithTestTarget
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: try project.location(from: "1", to: "4", in: "MyTests.swift"),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testMyLibrary()",
label: "testMyLibrary()",
location: try project.location(from: "2", to: "3", in: "MyTests.swift")
)
]
)
]
)
}
func testSyntacticOrIndexBasedXCTestsBasedOnWhetherFileIsIndexed() async throws {
try SkipUnless.longTestsEnabled()
let initialIndexingFinished = AtomicBool(initialValue: false)
let syntacticWorkspaceRequestSent = WrappedSemaphore(name: "Syntactic workspace request sent")
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": """
import XCTest
class 1⃣MyTests: XCTestCase {
func 2⃣testMyLibrary() {}
func unrelatedFunc() {}
var testVariable: Int = 0
}
"""
],
manifest: packageManifestWithTestTarget,
hooks: Hooks(
indexHooks: IndexHooks(updateIndexStoreTaskDidStart: { _ in
if initialIndexingFinished.value {
syntacticWorkspaceRequestSent.waitOrXCTFail()
}
})
),
enableBackgroundIndexing: true
)
initialIndexingFinished.value = true
let myTestsUri = try project.uri(for: "MyTests.swift")
// First get the tests from the original file contents, which are computed by the semantic index.
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: Location(
uri: myTestsUri,
range: Range(try project.position(of: "1", in: "MyTests.swift"))
),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testMyLibrary()",
label: "testMyLibrary()",
location: Location(
uri: myTestsUri,
range: Range(try project.position(of: "2", in: "MyTests.swift"))
)
)
]
)
]
)
// Now update the file on disk and recompute tests. This should give use tests using the syntactic index, which will
// include the tests in here even though `NotQuiteTests` doesn't inherit from XCTest
let (_, newFilePositions) = try await project.changeFileOnDisk(
"MyTests.swift",
newMarkedContents: """
import XCTest
class ClassThatMayInheritFromXCTest {}
3⃣class NotQuiteTests: ClassThatMayInheritFromXCTest {
4⃣func testSomething() {}5
}6
"""
)
let testsAfterDocumentChanged = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
testsAfterDocumentChanged,
[
TestItem(
id: "MyLibraryTests.NotQuiteTests",
label: "NotQuiteTests",
location: Location(
uri: myTestsUri,
range: newFilePositions["3"]..<newFilePositions["6"]
),
children: [
TestItem(
id: "MyLibraryTests.NotQuiteTests/testSomething()",
label: "testSomething()",
location: Location(
uri: myTestsUri,
range: newFilePositions["4"]..<newFilePositions["5"]
)
)
]
)
]
)
syntacticWorkspaceRequestSent.signal()
// After building again, we should have updated the updated the semantic index and realize that `NotQuiteTests` does
// not inherit from XCTest and thus doesn't have any test methods.
try await SwiftPMTestProject.build(at: project.scratchDirectory)
let testsAfterRebuild = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(testsAfterRebuild, [])
}
func testWorkspaceSwiftTestingTests() async throws {
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": """
import Testing
1⃣struct MyTests {
2⃣@Test
func oneIsTwo() {
#expect(1 == 2)
}3
}4
"""
],
manifest: packageManifestWithTestTarget
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: try project.location(from: "1", to: "4", in: "MyTests.swift"),
children: [
TestItem(
id: "MyLibraryTests.MyTests/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: try project.location(from: "2", to: "3", in: "MyTests.swift")
)
]
)
]
)
}
func testSwiftTestingTestsWithDuplicateFunctionIdentifiersAcrossDocuments() async throws {
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests1.swift": """
import Testing
1⃣@Test(arguments: [1, 2, 3])
private func foo(_ x: Int) {}2
""",
"Tests/MyLibraryTests/MyTests2.swift": """
import Testing
3⃣@Test(arguments: [1, 2, 3])
private func foo(_ x: Int) {}4
""",
],
manifest: packageManifestWithTestTarget
)
let test1Position = try project.position(of: "1", in: "MyTests1.swift")
let test2Position = try project.position(of: "3", in: "MyTests2.swift")
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.foo(_:)/MyTests1.swift:\(test1Position.line + 1):\(test1Position.utf16index + 2)",
label: "foo(_:)",
style: TestStyle.swiftTesting,
location: try project.location(from: "1", to: "2", in: "MyTests1.swift")
),
TestItem(
id: "MyLibraryTests.foo(_:)/MyTests2.swift:\(test2Position.line + 1):\(test2Position.utf16index + 2)",
label: "foo(_:)",
style: TestStyle.swiftTesting,
location: try project.location(from: "3", to: "4", in: "MyTests2.swift")
),
]
)
}
func testSwiftTestingAndXCTestInTheSameFile() async throws {
try SkipUnless.longTestsEnabled()
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": """
import Testing
import XCTest
1⃣struct MyTests {
2⃣@Test
func oneIsTwo() {
#expect(1 == 2)
}3
}4
class 5⃣MyOldTests: XCTestCase {
func 6⃣testOld() {}
}
"""
],
manifest: packageManifestWithTestTarget,
enableBackgroundIndexing: true
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: try project.location(from: "1", to: "4", in: "MyTests.swift"),
children: [
TestItem(
id: "MyLibraryTests.MyTests/oneIsTwo()",
label: "oneIsTwo()",
style: TestStyle.swiftTesting,
location: try project.location(from: "2", to: "3", in: "MyTests.swift")
)
],
tags: []
),
TestItem(
id: "MyLibraryTests.MyOldTests",
label: "MyOldTests",
location: try project.location(from: "5", to: "5", in: "MyTests.swift"),
children: [
TestItem(
id: "MyLibraryTests.MyOldTests/testOld()",
label: "testOld()",
location: try project.location(from: "6", to: "6", in: "MyTests.swift")
)
]
),
]
)
}
func testTargetWithCustomModuleName() async throws {
let packageManifestWithCustomModuleName = """
let package = Package(
name: "MyLibrary",
targets: [
.testTarget(
name: "MyLibraryTests",
swiftSettings: [
.unsafeFlags(["-module-name", "Foo", "-module-name", "Bar"])
]
)
]
)
"""
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": """
import XCTest
1⃣class MyTests: XCTestCase {
2⃣func testMyLibrary() {}3
}4
"""
],
manifest: packageManifestWithCustomModuleName
)
// Last argument takes precedence, so expect Bar as the module name.
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "Bar.MyTests",
label: "MyTests",
location: try project.location(from: "1", to: "4", in: "MyTests.swift"),
children: [
TestItem(
id: "Bar.MyTests/testMyLibrary()",
label: "testMyLibrary()",
location: try project.location(from: "2", to: "3", in: "MyTests.swift")
)
]
)
]
)
}
func testMultipleTargetsWithSameXCTestClassName() async throws {
let packageManifestWithTwoTestTargets = """
let package = Package(
name: "MyLibrary",
targets: [.testTarget(name: "MyLibraryTests"), .testTarget(name: "MyLibraryTests2")]
)
"""
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": """
import XCTest
1⃣class MyTests: XCTestCase {
2⃣func testMyLibrary() {}3
}4
""",
"Tests/MyLibraryTests2/MyTests2.swift": """
import XCTest
5⃣class MyTests: XCTestCase {
6⃣func testMyLibrary() {}7
}8
""",
],
manifest: packageManifestWithTwoTestTargets
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: try project.location(from: "1", to: "4", in: "MyTests.swift"),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testMyLibrary()",
label: "testMyLibrary()",
location: try project.location(from: "2", to: "3", in: "MyTests.swift")
)
]
),
TestItem(
id: "MyLibraryTests2.MyTests",
label: "MyTests",
location: try project.location(from: "5", to: "8", in: "MyTests2.swift"),
children: [
TestItem(
id: "MyLibraryTests2.MyTests/testMyLibrary()",
label: "testMyLibrary()",
location: try project.location(from: "6", to: "7", in: "MyTests2.swift")
)
]
),
]
)
}
func testWorkspaceTestsForInMemoryEditedFile() async throws {
try SkipUnless.longTestsEnabled()
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": """
import XCTest
1⃣class 2⃣MyTests: XCTestCase {
3⃣func 4⃣testMyLibrary0⃣() {
}5
func unrelatedFunc() {}
var testVariable: Int = 0
}6
"""
],
manifest: packageManifestWithTestTarget,
enableBackgroundIndexing: true
)
let (uri, positions) = try project.openDocument("MyTests.swift")
// If the document has been opened but not modified in-memory, we can still use the semantic index and detect that
// `MyTests` does not inherit from `XCTestCase`.
let testsAfterDocumentOpen = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
testsAfterDocumentOpen,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: Location(uri: uri, range: positions["2"]..<positions["2"]),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testMyLibrary()",
label: "testMyLibrary()",
location: Location(uri: uri, range: positions["4"]..<positions["4"])
)
]
)
]
)
// After we have an in-memory change to the file, we can't use the semantic index to discover the tests anymore.
// Use the syntactic index instead.
project.testClient.send(
DidChangeTextDocumentNotification(
textDocument: VersionedTextDocumentIdentifier(uri, version: 2),
contentChanges: [
TextDocumentContentChangeEvent(range: Range(positions["0"]), text: "Updated")
]
)
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: Location(uri: uri, range: positions["1"]..<positions["6"]),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testMyLibraryUpdated()",
label: "testMyLibraryUpdated()",
location: Location(uri: uri, range: positions["3"]..<positions["5"])
)
]
)
]
)
}
func testWorkspaceTestsAfterOneFileHasBeenEdited() async throws {
try SkipUnless.longTestsEnabled()
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyFirstTests.swift": """
import XCTest
1⃣class MyFirstTests: XCTestCase {
2⃣func testOne0⃣() {
}3
}4
""",
"Tests/MyLibraryTests/MySecondTests.swift": """
import XCTest
class 5⃣MySecondTests: XCTestCase {
func 6⃣testTwo() {}
}
""",
],
manifest: packageManifestWithTestTarget,
enableBackgroundIndexing: true
)
let (uri, positions) = try project.openDocument("MyFirstTests.swift")
project.testClient.send(
DidChangeTextDocumentNotification(
textDocument: VersionedTextDocumentIdentifier(uri, version: 2),
contentChanges: [TextDocumentContentChangeEvent(range: Range(positions["0"]), text: "Updated")]
)
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyFirstTests",
label: "MyFirstTests",
location: Location(uri: uri, range: positions["1"]..<positions["4"]),
children: [
TestItem(
id: "MyLibraryTests.MyFirstTests/testOneUpdated()",
label: "testOneUpdated()",
location: Location(uri: uri, range: positions["2"]..<positions["3"])
)
]
),
TestItem(
id: "MyLibraryTests.MySecondTests",
label: "MySecondTests",
location: try project.location(from: "5", to: "5", in: "MySecondTests.swift"),
children: [
TestItem(
id: "MyLibraryTests.MySecondTests/testTwo()",
label: "testTwo()",
location: try project.location(from: "6", to: "6", in: "MySecondTests.swift")
)
]
),
]
)
}
func testRemoveFileWithSemanticIndex() async throws {
try SkipUnless.longTestsEnabled()
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": """
import XCTest
class 1⃣MyTests: XCTestCase {
func 2⃣testSomething() {
}
}
"""
],
manifest: packageManifestWithTestTarget,
enableBackgroundIndexing: true
)
try await project.changeFileOnDisk("MyTests.swift", newMarkedContents: nil)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(tests, [])
}
func testRemoveFileWithSyntacticIndex() async throws {
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": """
import XCTest
class 1⃣MyTests: XCTestCase {
func 2⃣testSomething() {
}
}
"""
],
manifest: packageManifestWithTestTarget
)
try await project.changeFileOnDisk("MyTests.swift", newMarkedContents: nil)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(tests, [])
}
func testAddFileToSyntacticIndex() async throws {
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": ""
],
manifest: packageManifestWithTestTarget
)
let (uri, positions) = try await project.changeFileOnDisk(
"MyTests.swift",
newMarkedContents: """
import XCTest
1⃣class 2⃣MyTests: XCTestCase {
3⃣func 4⃣testSomething() {}5
}6
"""
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: Location(uri: uri, range: positions["1"]..<positions["6"]),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testSomething()",
label: "testSomething()",
location: Location(uri: uri, range: positions["3"]..<positions["5"])
)
]
)
]
)
}
func testInMemoryFileWithFallbackBuildServer() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import XCTest
1⃣class 2⃣MyTests: XCTestCase {
3⃣func 4⃣testSomething() {}5
}6
""",
uri: uri
)
let tests = try await testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
location: Location(uri: uri, range: positions["1"]..<positions["6"]),
children: [
TestItem(
id: "MyTests/testSomething()",
label: "testSomething()",
location: Location(uri: uri, range: positions["3"]..<positions["5"])
)
]
)
]
)
}
func testIndexedFileWithCompilationDbBuildServer() async throws {
try SkipUnless.longTestsEnabled()
let project = try await IndexedSingleSwiftFileTestProject(
"""
import XCTest
class 1⃣MyTests: XCTestCase {
func 2⃣testSomething() {}
}
"""
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
location: Location(uri: project.fileURI, range: Range(project.positions["1"])),
children: [
TestItem(
id: "MyTests/testSomething()",
label: "testSomething()",
location: Location(uri: project.fileURI, range: Range(project.positions["2"]))
)
]
)
]
)
}
func testOnDiskFileWithCompilationDbBuildServer() async throws {
let project = try await MultiFileTestProject(files: [
"MyTests.swift": """
import XCTest
1⃣class MyTests: XCTestCase {
2⃣func testSomething() {}3
}4
""",
"compile_commands.json": "[]",
])
// When MyTests.swift is not part of the compilation database, the build server doesn't know about the file and thus
// doesn't return any tests for it.
let testsWithEmptyCompilationDatabase = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(testsWithEmptyCompilationDatabase, [])
let swiftc = try await unwrap(ToolchainRegistry.forTesting.default?.swiftc)
let uri = try project.uri(for: "MyTests.swift")
let compilationDatabase = JSONCompilationDatabase(
[
CompilationDatabaseCompileCommand(
directory: try project.scratchDirectory.filePath,
filename: uri.pseudoPath,
commandLine: [try swiftc.filePath, uri.pseudoPath]
)
],
compileCommandsDirectory: project.scratchDirectory
)
try await project.changeFileOnDisk(
JSONCompilationDatabaseBuildServer.dbName,
newMarkedContents: XCTUnwrap(String(data: JSONEncoder().encode(compilationDatabase), encoding: .utf8))
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyTests",
label: "MyTests",
location: try project.location(from: "1", to: "4", in: "MyTests.swift"),
children: [
TestItem(
id: "MyTests/testSomething()",
label: "testSomething()",
location: try project.location(from: "2", to: "3", in: "MyTests.swift")
)
]
)
]
)
}
func testDeleteFileAndAddItAgain() async throws {
let markedFileContents = """
import XCTest
1⃣class MyTests: XCTestCase {
2⃣func testMyLibrary() {}3
func unrelatedFunc() {}
var testVariable: Int = 0
}4
"""
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/MyTests.swift": markedFileContents
],
manifest: packageManifestWithTestTarget
)
let expectedTests = [
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: try project.location(from: "1", to: "4", in: "MyTests.swift"),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testMyLibrary()",
label: "testMyLibrary()",
location: try project.location(from: "2", to: "3", in: "MyTests.swift")
)
]
)
]
let testsBeforeFileRemove = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(testsBeforeFileRemove, expectedTests)
try await project.changeFileOnDisk("MyTests.swift", newMarkedContents: nil)
let testsAfterFileRemove = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(testsAfterFileRemove, [])
try await project.changeFileOnDisk("MyTests.swift", newMarkedContents: markedFileContents)
let testsAfterFileReAdded = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(testsAfterFileReAdded, expectedTests)
}
func testDontIncludeTestsFromDependentPackageInSyntacticIndex() async throws {
let dependencyProject = try await SwiftPMDependencyProject(files: [
"Sources/MyDependency/MyDependency.swift": """
class MySuperclass {}
class LooksALittleLikeTests: MySuperclass {
func testSomething() {}
}
"""
])
defer { dependencyProject.keepAlive() }
let project = try await SwiftPMTestProject(
files: [
"Test.swift": ""
],
manifest: """
let package = Package(
name: "MyLibrary",
dependencies: [.package(url: "\(dependencyProject.packageDirectory)", from: "1.0.0")],
targets: [
.target(
name: "MyLibrary",
dependencies: [.product(name: "MyDependency", package: "MyDependency")]
)
]
)
""",
workspaces: { scratchDirectory in
try await SwiftPMTestProject.resolvePackageDependencies(at: scratchDirectory)
return [WorkspaceFolder(uri: DocumentURI(scratchDirectory))]
}
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(tests, [])
}
func testAddNewClassToNotQuiteTestCase() async throws {
let originalContents = """
import XCTest
class NotQuiteTest: SomeClass {
func testMyLibrary() {}
}
"""
let project = try await IndexedSingleSwiftFileTestProject(originalContents, allowBuildFailure: true)
// Close the file so we don't have an in-memory version of it.
project.testClient.send(DidCloseTextDocumentNotification(textDocument: TextDocumentIdentifier(project.fileURI)))
let testsBeforeEdit = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(testsBeforeEdit, [])
let addedTest = """
class OtherNotQuiteTest: SomeClass {
func testSomethingElse() {}
}
"""
let uri = try XCTUnwrap(project.fileURI.fileURL)
try await (originalContents + addedTest).writeWithRetry(to: uri)
project.testClient.send(
DidChangeWatchedFilesNotification(changes: [FileEvent(uri: project.fileURI, type: .changed)])
)
// Ensure that we handle the `DidChangeWatchedFilesNotification`.
try await project.testClient.send(SynchronizeRequest())
let testsAfterEdit = try await project.testClient.send(WorkspaceTestsRequest())
// 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
@implementation 1⃣MyTests
- (void)2⃣testSomething {
}
@end
"""
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [.testTarget(name: "MyLibraryTests")]
)
""",
enableBackgroundIndexing: true
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
location: try project.location(from: "1", to: "1", in: "Test.m"),
children: [
TestItem(
id: "MyLibraryTests.MyTests/testSomething",
label: "testSomething",
location: try project.location(from: "2", to: "2", in: "Test.m")
)
]
)
]
)
}
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(WorkspaceTestsRequest())
// 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 testSwiftTestingExtensionAcrossMultipleFiles() async throws {
let fileAContents = """
extension MyTests {
5⃣@Test func inExtension() {}6
}
"""
let fileBContents = """
1⃣@Suite struct MyTests {
3⃣@Test func inStruct() {}4
}2
"""
let project = try await SwiftPMTestProject(
files: [
"Tests/MyLibraryTests/FileA.swift": fileAContents,
"Tests/MyLibraryTests/FileB.swift": fileBContents,
],
manifest: packageManifestWithTestTarget
)
let (fileAURI, fileAPositions) = try project.openDocument("FileA.swift")
let (fileBURI, fileBPositions) = try project.openDocument("FileB.swift")
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(
tests,
[
TestItem(
id: "MyLibraryTests.MyTests",
label: "MyTests",
style: TestStyle.swiftTesting,
location: Location(uri: fileBURI, range: fileBPositions["1"]..<fileBPositions["2"]),
children: [
TestItem(
id: "MyLibraryTests.MyTests/inStruct()",
label: "inStruct()",
style: TestStyle.swiftTesting,
location: Location(uri: fileBURI, range: fileBPositions["3"]..<fileBPositions["4"])
),
TestItem(
id: "MyLibraryTests.MyTests/inExtension()",
label: "inExtension()",
style: TestStyle.swiftTesting,
location: Location(uri: fileAURI, range: fileAPositions["5"]..<fileAPositions["6"])
),
]
)
]
)
}
func testSwiftTestingTestsAreNotDiscoveredInNonTestTargets() async throws {
let project = try await SwiftPMTestProject(
files: [
"FileA.swift": """
@Suite struct MyTests {
@Test func inStruct() {}
}
"""
]
)
let tests = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(tests, [])
}
}
extension TestItem {
init(
id: String,
label: String,
disabled: Bool = false,
style: String = TestStyle.xcTest,
location: Location,
children: [TestItem] = [],
tags: [TestTag] = []
) {
self.init(
id: id,
label: label,
description: nil,
sortText: nil,
disabled: disabled,
style: style,
location: location,
children: children,
tags: tags
)
}
}