forked from apple/swift-driver
-
Notifications
You must be signed in to change notification settings - Fork 0
/
IncrementalBuildPerformanceTests.swift
144 lines (129 loc) · 6.03 KB
/
IncrementalBuildPerformanceTests.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Created by David Ungar on 7/28/21.
//
import XCTest
@_spi(Testing) import SwiftDriver
import TSCBasic
class IncrementalBuildPerformanceTests: XCTestCase {
enum WhatToMeasure { case readingSwiftDeps, writing, readingPriors }
/// Test the cost of reading `swiftdeps` files without doing a full build. Use the files in "TestInputs/SampleSwiftDeps"
///
/// When doing an incremental but clean build, after every file is compiled, its `swiftdeps` file must be
/// deserialized and integrated into the `ModuleDependencyGraph`.
/// This test allows us to profile an optimize this work. (Set up the scheme to run optimized code.)
/// It reads and integrages every swiftdeps file in a given directory.
///
/// This test relies on sample `swiftdeps` files to be present in `<project-folder>/TestInputs/SampleSwiftDeps`.
/// If the serialization format changes, they will need to be regenerated.
/// To regenerate them:
/// `cd` to the package directory, then:
/// `rm TestInputs/SampleSwiftDeps/*; rm -rf .build; swift build; find .build -name \*.swiftdeps -a -exec cp \{\} TestInputs/SampleSwiftDeps \;`
func testCleanBuildSwiftDepsPerformance() throws {
try testPerformance(.readingSwiftDeps)
}
func testSavingPriorsPerformance() throws {
try testPerformance(.writing)
}
func testReadingPriorsPerformance() throws {
try testPerformance(.readingPriors)
}
func testPerformance(_ whatToMeasure: WhatToMeasure) throws {
#if !os(macOS)
// rdar://81411914
throw XCTSkip()
#else
let packageRootPath = try AbsolutePath(validating: #file)
.parentDirectory
.parentDirectory
.parentDirectory
let swiftDepsDirectoryPath = packageRootPath.appending(components: "TestInputs", "SampleSwiftDeps")
#if DEBUG
let limit = 5 // Just a few times to be sure it works
#else
let limit = 100 // This is the real test, optimized code.
#endif
try test(swiftDepsDirectory: swiftDepsDirectoryPath.pathString, atMost: limit, whatToMeasure)
#endif
}
/// Test the cost of reading `swiftdeps` files without doing a full build.
///
/// When doing an incremental but clean build, after every file is compiled, its `swiftdeps` file must be
/// deserialized and integrated into the `ModuleDependencyGraph`.
/// This test allows us to profile an optimize this work. (Set up the scheme to run optimized code.)
/// It reads and integrages every swiftdeps file in a given directory.
/// - Parameters:
/// - swiftDepsDirectory: where the swiftdeps files are, either absolute, or relative to the current directory
/// - limit: the maximum number of swiftdeps files to process.
func test(swiftDepsDirectory: String, atMost limit: Int = .max, _ whatToMeasure: WhatToMeasure) throws {
let (outputFileMap, inputs) = try createOFMAndInputs(swiftDepsDirectory, atMost: limit)
let info = IncrementalCompilationState.IncrementalDependencyAndInputSetup
.mock(options: [], outputFileMap: outputFileMap)
let g = ModuleDependencyGraph.createForSimulatingCleanBuild(info.buildRecordInfo.buildRecord([], []), info)
g.blockingConcurrentAccessOrMutation {
switch whatToMeasure {
case .readingSwiftDeps:
measure {readSwiftDeps(for: inputs, into: g)}
case .writing:
readSwiftDeps(for: inputs, into: g)
measure {
_ = ModuleDependencyGraph.Serializer.serialize(
g,
g.buildRecord,
ModuleDependencyGraph.serializedGraphVersion)
}
case .readingPriors:
readSwiftDeps(for: inputs, into: g)
let data = ModuleDependencyGraph.Serializer.serialize(
g,
g.buildRecord,
ModuleDependencyGraph.serializedGraphVersion)
measure {
try? XCTAssertNoThrow(ModuleDependencyGraph.deserialize(data, info: info))
}
}
}
}
/// Build the `OutputFileMap` and input vector for ``testCleanBuildSwiftDepsPerformance(_, atMost)``
private func createOFMAndInputs(_ swiftDepsDirectory: String,
atMost limit: Int
) throws -> (OutputFileMap, [SwiftSourceFile]) {
let workingDirectory = localFileSystem.currentWorkingDirectory!
let swiftDepsDirPath = try VirtualPath.init(path: swiftDepsDirectory).resolvedRelativePath(base: workingDirectory).absolutePath!
let withoutExtensions: ArraySlice<Substring> = try localFileSystem.getDirectoryContents(swiftDepsDirPath)
.compactMap {
fileName -> Substring? in
guard let suffixRange = fileName.range(of: ".swiftdeps"),
suffixRange.upperBound == fileName.endIndex
else {
return nil
}
let withoutExtension = fileName.prefix(upTo: suffixRange.lowerBound)
guard !withoutExtension.hasSuffix("-master") else { return nil }
return withoutExtension
}
.sorted()
.prefix(limit)
print("reading", withoutExtensions.count, "swiftdeps files")
func mkPath( _ name: Substring, _ type: FileType) -> TypedVirtualPath {
TypedVirtualPath(
file: VirtualPath.absolute(swiftDepsDirPath.appending(component: name + "." + type.rawValue)).intern(),
type: type)
}
let inputs = withoutExtensions.map {mkPath($0, .swift)}.swiftSourceFiles
let swiftDepsVPs = withoutExtensions.map {mkPath($0, .swiftDeps)}
let entries = Dictionary(
uniqueKeysWithValues:
zip(inputs, swiftDepsVPs).map {input, swiftDeps in
(input.fileHandle, [swiftDeps.type: swiftDeps.fileHandle])
})
return (OutputFileMap(entries: entries), inputs)
}
/// Read the `swiftdeps` files for each input into a `ModuleDependencyGraph`
private func readSwiftDeps(for inputs: [SwiftSourceFile], into g: ModuleDependencyGraph) {
let result = inputs.reduce(into: Set()) { invalidatedInputs, primaryInput in
// too verbose: print("processing", primaryInput)
invalidatedInputs.formUnion(g.collectInputsRequiringCompilation(byCompiling: primaryInput)!)
}
.subtracting(inputs) // have already compiled these
XCTAssertEqual(result.count, 0, "Should be no invalid inputs left")
}
}