Skip to content

Commit 259d49e

Browse files
committed
Share module cache between test projects
This improves serial test execution time of eg. `DocumentTestDiscoveryTests` from 36s to 22s because we don’t need to re-build the XCTest module from its interface when using an open source toolchain. This also uncovered that we weren‘t passing the build setup flags to the prepare command. rdar://126493151
1 parent 0e0594b commit 259d49e

File tree

8 files changed

+111
-33
lines changed

8 files changed

+111
-33
lines changed

Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,30 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
574574
"--disable-index-store",
575575
"--target", target.targetID,
576576
]
577+
if self.toolsBuildParameters.configuration != self.destinationBuildParameters.configuration {
578+
logger.fault(
579+
"""
580+
Preparation is assuming that tools and destination are built using the same configuration, \
581+
got tools: \(String(describing: self.toolsBuildParameters.configuration), privacy: .public), \
582+
destination: \(String(describing: self.destinationBuildParameters.configuration), privacy: .public)
583+
"""
584+
)
585+
}
586+
arguments += ["-c", self.destinationBuildParameters.configuration.rawValue]
587+
if self.toolsBuildParameters.flags != self.destinationBuildParameters.flags {
588+
logger.fault(
589+
"""
590+
Preparation is assuming that tools and destination are built using the same build flags, \
591+
got tools: \(String(describing: self.toolsBuildParameters.flags)), \
592+
destination: \(String(describing: self.destinationBuildParameters.configuration))
593+
"""
594+
)
595+
}
596+
arguments += self.destinationBuildParameters.flags.cCompilerFlags.flatMap { ["-Xcc", $0] }
597+
arguments += self.destinationBuildParameters.flags.cxxCompilerFlags.flatMap { ["-Xcxx", $0] }
598+
arguments += self.destinationBuildParameters.flags.swiftCompilerFlags.flatMap { ["-Xswiftc", $0] }
599+
arguments += self.destinationBuildParameters.flags.linkerFlags.flatMap { ["-Xlinker", $0] }
600+
arguments += self.destinationBuildParameters.flags.xcbuildFlags?.flatMap { ["-Xxcbuild", $0] } ?? []
577601
if await swiftBuildSupportsPrepareForIndexing {
578602
arguments.append("--experimental-prepare-for-indexing")
579603
}

Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ public struct IndexedSingleSwiftFileTestProject {
6161
"-index-store-path", indexURL.path,
6262
"-typecheck",
6363
]
64+
if let globalModuleCache {
65+
compilerArguments += [
66+
"-module-cache-path", globalModuleCache.path,
67+
]
68+
}
6469
if !indexSystemModules {
6570
compilerArguments.append("-index-ignore-system-modules")
6671
}

Sources/SKTestSupport/SwiftPMTestProject.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,14 +209,19 @@ public class SwiftPMTestProject: MultiFileTestProject {
209209
guard let swift = await ToolchainRegistry.forTesting.default?.swift?.asURL else {
210210
throw Error.swiftNotFound
211211
}
212-
let arguments = [
212+
var arguments = [
213213
swift.path,
214214
"build",
215215
"--package-path", path.path,
216216
"--build-tests",
217217
"-Xswiftc", "-index-ignore-system-modules",
218218
"-Xcc", "-index-ignore-system-symbols",
219219
]
220+
if let globalModuleCache {
221+
arguments += [
222+
"-Xswiftc", "-module-cache-path", "-Xswiftc", globalModuleCache.path,
223+
]
224+
}
220225
var environment = ProcessEnv.block
221226
// FIXME: SwiftPM does not index-while-building on non-Darwin platforms for C-family files (rdar://117744039).
222227
// Force-enable index-while-building with the environment variable.

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,6 @@ public final class TestSourceKitLSPClient: MessageHandler {
4444
/// `nonisolated(unsafe)` is fine because `nextRequestID` is atomic.
4545
private nonisolated(unsafe) var nextRequestID = AtomicUInt32(initialValue: 0)
4646

47-
/// If the server is not using the global module cache, the path of the local
48-
/// module cache.
49-
///
50-
/// This module cache will be deleted when the test server is destroyed.
51-
private let moduleCache: URL?
52-
5347
/// The server that handles the requests.
5448
public let server: SourceKitLSPServer
5549

@@ -102,7 +96,6 @@ public final class TestSourceKitLSPClient: MessageHandler {
10296
/// needed.
10397
public init(
10498
serverOptions: SourceKitLSPServer.Options = .testDefault,
105-
useGlobalModuleCache: Bool = true,
10699
initialize: Bool = true,
107100
initializationOptions: LSPAny? = nil,
108101
capabilities: ClientCapabilities = ClientCapabilities(),
@@ -112,14 +105,9 @@ public final class TestSourceKitLSPClient: MessageHandler {
112105
preInitialization: ((TestSourceKitLSPClient) -> Void)? = nil,
113106
cleanUp: @Sendable @escaping () -> Void = {}
114107
) async throws {
115-
if !useGlobalModuleCache {
116-
moduleCache = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
117-
} else {
118-
moduleCache = nil
119-
}
120108
var serverOptions = serverOptions
121-
if let moduleCache {
122-
serverOptions.buildSetup.flags.swiftCompilerFlags += ["-module-cache-path", moduleCache.path]
109+
if let globalModuleCache {
110+
serverOptions.buildSetup.flags.swiftCompilerFlags += ["-module-cache-path", globalModuleCache.path]
123111
}
124112
if enableBackgroundIndexing {
125113
serverOptions.experimentalFeatures.append(.backgroundIndexing)
@@ -191,9 +179,6 @@ public final class TestSourceKitLSPClient: MessageHandler {
191179
sema.wait()
192180
self.send(ExitNotification())
193181

194-
if let moduleCache {
195-
try? FileManager.default.removeItem(at: moduleCache)
196-
}
197182
cleanUp()
198183
}
199184

Sources/SKTestSupport/Utils.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,15 @@ fileprivate extension URL {
112112
#endif
113113
}
114114
}
115+
116+
var globalModuleCache: URL? = {
117+
if let customModuleCache = ProcessInfo.processInfo.environment["SOURCEKIT_LSP_TEST_MODULE_CACHE"] {
118+
if customModuleCache.isEmpty {
119+
return nil
120+
}
121+
return URL(fileURLWithPath: customModuleCache)
122+
}
123+
return FileManager.default.temporaryDirectory.realpath
124+
.appendingPathComponent("sourcekit-lsp-test-scratch")
125+
.appendingPathComponent("shared-module-cache")
126+
}()

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,4 +928,53 @@ final class BackgroundIndexingTests: XCTestCase {
928928
satisfying: { $0.message.contains("Preparing") }
929929
)
930930
}
931+
932+
func testUseBuildFlagsDuringPreparation() async throws {
933+
var serverOptions = SourceKitLSPServer.Options.testDefault
934+
serverOptions.buildSetup.flags.swiftCompilerFlags += ["-D", "MY_FLAG"]
935+
let project = try await SwiftPMTestProject(
936+
files: [
937+
"Lib/Lib.swift": """
938+
#if MY_FLAG
939+
public func foo() -> Int { 1 }
940+
#endif
941+
""",
942+
"Client/Client.swift": """
943+
import Lib
944+
945+
func test() -> String {
946+
return foo()
947+
}
948+
""",
949+
],
950+
manifest: """
951+
let package = Package(
952+
name: "MyLibrary",
953+
targets: [
954+
.target(name: "Lib"),
955+
.target(name: "Client", dependencies: ["Lib"]),
956+
]
957+
)
958+
""",
959+
serverOptions: serverOptions,
960+
enableBackgroundIndexing: true
961+
)
962+
963+
// Check that we get an error about the return type of `foo` (`Int`) not being convertible to the return type of
964+
// `test` (`String`), which indicates that `Lib` had `foo` and was thus compiled with `-D MY_FLAG`
965+
let (uri, _) = try project.openDocument("Client.swift")
966+
let diagnostics = try await project.testClient.send(
967+
DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri))
968+
)
969+
guard case .full(let diagnostics) = diagnostics else {
970+
XCTFail("Expected full diagnostics report")
971+
return
972+
}
973+
XCTAssert(
974+
diagnostics.items.contains(where: {
975+
$0.message == "Cannot convert return expression of type 'Int' to return type 'String'"
976+
}),
977+
"Did not get expected diagnostic: \(diagnostics)"
978+
)
979+
}
931980
}

Tests/SourceKitLSPTests/SwiftInterfaceTests.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,7 @@ import XCTest
2222

2323
final class SwiftInterfaceTests: XCTestCase {
2424
func testSystemModuleInterface() async throws {
25-
// This is the only test that references modules from the SDK (Foundation).
26-
// `testSystemModuleInterface` has been flaky for a long while and a
27-
// hypothesis is that it was failing because of a malformed global module
28-
// cache that might still be present from previous CI runs. If we use a
29-
// local module cache, we define away that source of bugs.
30-
let testClient = try await TestSourceKitLSPClient(useGlobalModuleCache: false)
25+
let testClient = try await TestSourceKitLSPClient()
3126
let url = URL(fileURLWithPath: "/\(UUID())/a.swift")
3227
let uri = DocumentURI(url)
3328

Utilities/build-script-helper.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import shutil
88
import subprocess
99
import sys
10+
import tempfile
1011
from typing import Dict, List
1112

1213

@@ -228,15 +229,17 @@ def run_tests(swift_exec: str, args: argparse.Namespace) -> None:
228229
'--test-product', 'SourceKitLSPPackageTests'
229230
] + swiftpm_args
230231

231-
# Try running tests in parallel. If that fails, run tests in serial to get capture more readable output.
232-
try:
233-
check_call(cmd + ['--parallel'], additional_env=additional_env, verbose=args.verbose)
234-
except:
235-
print('--- Running tests in parallel failed. Re-running tests serially to capture more actionable output.')
236-
sys.stdout.flush()
237-
check_call(cmd, additional_env=additional_env, verbose=args.verbose)
238-
# Return with non-zero exit code even if serial test execution succeeds.
239-
raise SystemExit(1)
232+
with tempfile.TemporaryDirectory() as test_module_cache:
233+
additional_env['SOURCEKIT_LSP_TEST_MODULE_CACHE'] = f"{test_module_cache}/module-cache"
234+
# Try running tests in parallel. If that fails, run tests in serial to get capture more readable output.
235+
try:
236+
check_call(cmd + ['--parallel'], additional_env=additional_env, verbose=args.verbose)
237+
except:
238+
print('--- Running tests in parallel failed. Re-running tests serially to capture more actionable output.')
239+
sys.stdout.flush()
240+
check_call(cmd, additional_env=additional_env, verbose=args.verbose)
241+
# Return with non-zero exit code even if serial test execution succeeds.
242+
raise SystemExit(1)
240243

241244

242245
def install_binary(exe: str, source_dir: str, install_dir: str, verbose: bool) -> None:

0 commit comments

Comments
 (0)