Files
sourcekit-lsp/Tests/SourceKitLSPTests/WorkspaceTestDiscoveryTests.swift
Alex Hoppen be546308ca Use URL in many cases where we used AbsolutePath
We made quite a few fixes recently to make sure that path handling works correctly using `URL` on Windows. Use `URL` in most places to have a single type that represents file paths instead of sometimes using `AbsolutePath`.

While doing so, also remove usages of `TSCBasic.FileSystem` an `InMemoryFileSystem`. The pattern of using `InMemoryFileSystem` for tests was never consistently used and it was a little confusing that some types took a `FileSystem` parameter while other always assumed to work on the local file system.
2024-11-18 18:19:48 -08:00

1152 lines
35 KiB
Swift

//===----------------------------------------------------------------------===//
//
// 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 BuildSystemIntegration
import Foundation
import LanguageServerProtocol
import SKTestSupport
@_spi(Testing) import SourceKitLSP
import SwiftExtensions
import ToolchainRegistry
import XCTest
import struct TSCBasic.AbsolutePath
private let packageManifestWithTestTarget = """
let package = Package(
name: "MyLibrary",
targets: [.testTarget(name: "MyLibraryTests")]
)
"""
final class WorkspaceTestDiscoveryTests: XCTestCase {
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 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 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, newFileContents) = DocumentPositions.extract(
from: """
import XCTest
class ClassThatMayInheritFromXCTest {}
3️⃣class NotQuiteTests: ClassThatMayInheritFromXCTest {
4️⃣func testSomething() {}5️⃣
}6️⃣
"""
)
try newFileContents.write(to: try XCTUnwrap(myTestsUri.fileURL), atomically: true, encoding: .utf8)
project.testClient.send(DidChangeWatchedFilesNotification(changes: [FileEvent(uri: myTestsUri, type: .changed)]))
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️⃣"]
)
)
]
)
]
)
// 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
)
let uri = try project.uri(for: "MyTests.swift")
try FileManager.default.removeItem(at: XCTUnwrap(uri.fileURL))
project.testClient.send(DidChangeWatchedFilesNotification(changes: [FileEvent(uri: uri, type: .deleted)]))
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
)
let uri = try project.uri(for: "MyTests.swift")
try FileManager.default.removeItem(at: XCTUnwrap(uri.fileURL))
project.testClient.send(DidChangeWatchedFilesNotification(changes: [FileEvent(uri: uri, type: .deleted)]))
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 (positions, fileContents) = DocumentPositions.extract(
from: """
import XCTest
1️⃣class 2️⃣MyTests: XCTestCase {
3️⃣func 4️⃣testSomething() {}5️⃣
}6️⃣
"""
)
let url = try XCTUnwrap(project.uri(for: "MyTests.swift").fileURL)
.deletingLastPathComponent()
.appendingPathComponent("MyNewTests.swift")
let uri = DocumentURI(url)
try fileContents.write(to: url, atomically: true, encoding: .utf8)
project.testClient.send(
DidChangeWatchedFilesNotification(changes: [FileEvent(uri: uri, type: .created)])
)
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 testInMemoryFileWithFallbackBuildSystem() 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 testIndexedFileWithCompilationDbBuildSystem() 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 testOnDiskFileWithCompilationDbBuildSystem() 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 system 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([
JSONCompilationDatabase.Command(
directory: try project.scratchDirectory.filePath,
filename: uri.pseudoPath,
commandLine: [try swiftc.filePath, uri.pseudoPath]
)
])
try JSONEncoder()
.encode(compilationDatabase).write(to: XCTUnwrap(project.uri(for: "compile_commands.json").fileURL))
project.testClient.send(
DidChangeWatchedFilesNotification(changes: [
FileEvent(uri: try project.uri(for: "compile_commands.json"), type: .changed)
])
)
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)
let myTestsUri = try project.uri(for: "MyTests.swift")
let myTestsUrl = try XCTUnwrap(myTestsUri.fileURL)
try FileManager.default.removeItem(at: myTestsUrl)
project.testClient.send(DidChangeWatchedFilesNotification(changes: [FileEvent(uri: myTestsUri, type: .deleted)]))
let testsAfterFileRemove = try await project.testClient.send(WorkspaceTestsRequest())
XCTAssertEqual(testsAfterFileRemove, [])
try extractMarkers(markedFileContents).textWithoutMarkers.write(to: myTestsUrl, atomically: true, encoding: .utf8)
project.testClient.send(DidChangeWatchedFilesNotification(changes: [FileEvent(uri: myTestsUri, type: .created)]))
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 (originalContents + addedTest).write(to: uri, atomically: true, encoding: .utf8)
project.testClient.send(
DidChangeWatchedFilesNotification(changes: [FileEvent(uri: project.fileURI, type: .changed)])
)
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
)
}
}