Skip to content

Simplify tests #896

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion Sources/LSPTestSupport/Assertions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import XCTest

/// Same as `assertNoThrow` but executes the trailing closure.
/// Same as `XCTAssertNoThrow` but executes the trailing closure.
public func assertNoThrow<T>(
_ expression: () throws -> T,
_ message: @autoclosure () -> String = "",
Expand All @@ -22,6 +22,20 @@ public func assertNoThrow<T>(
XCTAssertNoThrow(try expression(), message(), file: file, line: line)
}

/// Same as `assertNoThrow` but allows the closure to be `async`.
public func assertNoThrow<T>(
_ expression: () async throws -> T,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line
) async {
do {
_ = try await expression()
} catch {
XCTFail("Expression was not expected to throw but threw \(error)", file: file, line: line)
}
}

/// Same as `XCTAssertThrows` but executes the trailing closure.
public func assertThrowsError<T>(
_ expression: @autoclosure () async throws -> T,
Expand Down Expand Up @@ -77,6 +91,17 @@ public func assertNotNil<T: Equatable>(
XCTAssertNotNil(expression, message(), file: file, line: line)
}

/// Same as `XCTUnwrap` but doesn't take autoclosures and thus `expression`
/// can contain `await`.
public func unwrap<T>(
_ expression: T?,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line
) throws -> T {
return try XCTUnwrap(expression, file: file, line: line)
}

extension XCTestCase {
private struct ExpectationNotFulfilledError: Error, CustomStringConvertible {
var expecatations: [XCTestExpectation]
Expand Down
65 changes: 0 additions & 65 deletions Sources/LSPTestSupport/TestJSONRPCConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,6 @@ public final class TestClient: MessageHandler {
})
}

public func handleNextNotification<N>(_ handler: @escaping (Notification<N>) -> Void) {
guard oneShotNotificationHandlers.isEmpty else {
XCTFail("unexpected one shot notification handler registered")
return
}
appendOneShotNotificationHandler(handler)
}

public func handleNextRequest<R>(_ handler: @escaping (Request<R>) -> Void) {
precondition(oneShotRequestHandlers.isEmpty)
appendOneShotRequestHandler(handler)
}

public func handle<N>(_ params: N, from clientID: ObjectIdentifier) where N: NotificationType {
let notification = Notification(params, clientID: clientID)

Expand Down Expand Up @@ -168,58 +155,6 @@ extension TestClient: Connection {
) -> RequestID {
return server.send(request, reply: reply)
}

/// Send a notification and expect a notification in reply synchronously.
/// For testing notifications that behave like requests - e.g. didChange & publishDiagnostics.
public func sendNoteSync<NReply>(
_ notification: some NotificationType,
_ handler: @escaping (Notification<NReply>) -> Void
) {

let expectation = XCTestExpectation(description: "sendNoteSync - note received")

handleNextNotification { (note: Notification<NReply>) in
handler(note)
expectation.fulfill()
}

send(notification)

let result = XCTWaiter.wait(for: [expectation], timeout: defaultTimeout)
guard result == .completed else {
XCTFail("error \(result) waiting for notification in response to \(notification)")
return
}
}

/// Send a notification and expect two notifications in reply synchronously.
/// For testing notifications that behave like requests - e.g. didChange & publishDiagnostics.
public func sendNoteSync<NSend, NReply1, NReply2>(
_ notification: NSend,
_ handler1: @escaping (Notification<NReply1>) -> Void,
_ handler2: @escaping (Notification<NReply2>) -> Void
) where NSend: NotificationType {

let expectation = XCTestExpectation(description: "sendNoteSync - note received")
expectation.expectedFulfillmentCount = 2

handleNextNotification { (note: Notification<NReply1>) in
handler1(note)
expectation.fulfill()
}
appendOneShotNotificationHandler { (note: Notification<NReply2>) in
handler2(note)
expectation.fulfill()
}

send(notification)

let result = XCTWaiter.wait(for: [expectation], timeout: defaultTimeout)
guard result == .completed else {
XCTFail("wait for notification in response to \(notification) failed with \(result)")
return
}
}
}

public final class TestServer: MessageHandler {
Expand Down
23 changes: 13 additions & 10 deletions Sources/SKTestSupport/SKSwiftPMTestWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,16 @@ public final class SKSwiftPMTestWorkspace {
public let toolchain: Toolchain

/// Connection to the language server.
public let testServer: TestSourceKitServer

public var sk: TestClient { testServer.client }
public let testClient: TestSourceKitLSPClient

/// When `testServer` is not `nil`, the workspace will be opened in that server, otherwise a new server will be created for the workspace
public init(projectDir: URL, tmpDir: URL, toolchain: Toolchain, testServer: TestSourceKitServer? = nil) async throws {
self.testServer = testServer ?? TestSourceKitServer(connectionKind: .local)
public init(
projectDir: URL,
tmpDir: URL,
toolchain: Toolchain,
testClient: TestSourceKitLSPClient? = nil
) async throws {
self.testClient = testClient ?? TestSourceKitLSPClient()

self.projectDir = URL(
fileURLWithPath: try resolveSymlinks(AbsolutePath(validating: projectDir.path)).pathString
Expand Down Expand Up @@ -105,7 +108,7 @@ public final class SKSwiftPMTestWorkspace {
listenToUnitEvents: false
)

let server = self.testServer.server!
let server = self.testClient.server
let workspace = await Workspace(
documentManager: DocumentManager(),
rootUri: DocumentURI(sources.rootDirectory),
Expand Down Expand Up @@ -153,7 +156,7 @@ extension SKSwiftPMTestWorkspace {

extension SKSwiftPMTestWorkspace {
public func openDocument(_ url: URL, language: Language) throws {
sk.send(
testClient.send(
DidOpenTextDocumentNotification(
textDocument: TextDocumentItem(
uri: DocumentURI(url),
Expand All @@ -166,15 +169,15 @@ extension SKSwiftPMTestWorkspace {
}

public func closeDocument(_ url: URL) {
sk.send(DidCloseTextDocumentNotification(textDocument: TextDocumentIdentifier(DocumentURI(url))))
testClient.send(DidCloseTextDocumentNotification(textDocument: TextDocumentIdentifier(DocumentURI(url))))
}
}

extension XCTestCase {

public func staticSourceKitSwiftPMWorkspace(
name: String,
server: TestSourceKitServer? = nil
testClient: TestSourceKitLSPClient? = nil
) async throws -> SKSwiftPMTestWorkspace? {
let testDirName = testDirectoryName
let toolchain = ToolchainRegistry.shared.default!
Expand All @@ -183,7 +186,7 @@ extension XCTestCase {
tmpDir: URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
.appendingPathComponent("sk-test-data/\(testDirName)/\(name)", isDirectory: true),
toolchain: toolchain,
testServer: server
testClient: testClient
)

let hasClangFile: Bool = workspace.sources.locations.contains { _, loc in
Expand Down
21 changes: 10 additions & 11 deletions Sources/SKTestSupport/SKTibsTestWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,11 @@ fileprivate extension SourceKitServer {
public final class SKTibsTestWorkspace {

public let tibsWorkspace: TibsTestWorkspace
public let testServer: TestSourceKitServer
public let testClient: TestSourceKitLSPClient

public var index: IndexStoreDB { tibsWorkspace.index }
public var builder: TibsBuilder { tibsWorkspace.builder }
public var sources: TestSources { tibsWorkspace.sources }
public var sk: TestClient { testServer.client }

public init(
immutableProjectDir: URL,
Expand All @@ -50,9 +49,9 @@ public final class SKTibsTestWorkspace {
removeTmpDir: Bool,
toolchain: Toolchain,
clientCapabilities: ClientCapabilities,
testServer: TestSourceKitServer? = nil
testClient: TestSourceKitLSPClient? = nil
) async throws {
self.testServer = testServer ?? TestSourceKitServer(connectionKind: .local)
self.testClient = testClient ?? TestSourceKitLSPClient()
self.tibsWorkspace = try TibsTestWorkspace(
immutableProjectDir: immutableProjectDir,
persistentBuildDir: persistentBuildDir,
Expand All @@ -69,9 +68,9 @@ public final class SKTibsTestWorkspace {
tmpDir: URL,
toolchain: Toolchain,
clientCapabilities: ClientCapabilities,
testServer: TestSourceKitServer? = nil
testClient: TestSourceKitLSPClient? = nil
) async throws {
self.testServer = testServer ?? TestSourceKitServer(connectionKind: .local)
self.testClient = testClient ?? TestSourceKitLSPClient()

self.tibsWorkspace = try TibsTestWorkspace(
projectDir: projectDir,
Expand Down Expand Up @@ -99,8 +98,8 @@ public final class SKTibsTestWorkspace {
indexDelegate: indexDelegate
)

await workspace.buildSystemManager.setDelegate(testServer.server!)
await testServer.server!.setWorkspaces([workspace])
await workspace.buildSystemManager.setDelegate(testClient.server)
await testClient.server.setWorkspaces([workspace])
}
}

Expand All @@ -123,7 +122,7 @@ extension SKTibsTestWorkspace {

extension SKTibsTestWorkspace {
public func openDocument(_ url: URL, language: Language) throws {
sk.send(
testClient.send(
DidOpenTextDocumentNotification(
textDocument: TextDocumentItem(
uri: DocumentURI(url),
Expand All @@ -143,7 +142,7 @@ extension XCTestCase {
clientCapabilities: ClientCapabilities = .init(),
tmpDir: URL? = nil,
removeTmpDir: Bool = true,
server: TestSourceKitServer? = nil
testClient: TestSourceKitLSPClient? = nil
) async throws -> SKTibsTestWorkspace? {
let testDirName = testDirectoryName
let workspace = try await SKTibsTestWorkspace(
Expand All @@ -157,7 +156,7 @@ extension XCTestCase {
removeTmpDir: removeTmpDir,
toolchain: ToolchainRegistry.shared.default!,
clientCapabilities: clientCapabilities,
testServer: server
testClient: testClient
)

if workspace.builder.targets.contains(where: { target in !target.clangTUs.isEmpty })
Expand Down
Loading