Skip to content

Commit 32ba01a

Browse files
committed
[Xcodeproj] Add unit testing infra
This adds some unit testing infrastructure to XcodeprojTest module to test mock PackageGraphs on the Xcode.project model.
1 parent 4d52caf commit 32ba01a

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

Package.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ let package = Package(
140140
Target(
141141
name: "UtilityTests",
142142
dependencies: ["Utility", "TestSupport"]),
143+
Target(
144+
name: "XcodeprojTests",
145+
dependencies: ["Xcodeproj", "TestSupport"]),
143146
])
144147

145148

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright 2016 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import XCTest
12+
13+
import Basic
14+
import PackageGraph
15+
import PackageDescription
16+
import TestSupport
17+
@testable import Xcodeproj
18+
19+
class PackageGraphTests: XCTestCase {
20+
func testBasics() throws {
21+
let fs = InMemoryFileSystem(emptyFiles:
22+
"/Foo/foo.swift",
23+
"/Foo/Tests/FooTests/fooTests.swift",
24+
"/Bar/Sources/Bar/bar.swift",
25+
"/Bar/Sources/Sea/include/Sea.h",
26+
"/Bar/Sources/Sea/Sea.c",
27+
"/Bar/Sources/Sea2/include/Sea2.h",
28+
"/Bar/Sources/Sea2/include/module.modulemap",
29+
"/Bar/Sources/Sea2/Sea2.c",
30+
"/Bar/Tests/BarTests/barTests.swift"
31+
)
32+
33+
let g = try loadMockPackageGraph([
34+
"/Foo": Package(name: "Foo"),
35+
"/Bar": Package(name: "Bar", dependencies: [.Package(url: "/Foo", majorVersion: 1)]),
36+
], root: "/Bar", in: fs)
37+
38+
let project = try xcodeProject(xcodeprojPath: AbsolutePath.root.appending(component: "xcodeproj"), graph: g, extraDirs: [], options: XcodeprojOptions(), fileSystem: fs)
39+
40+
XcodeProjectTester(project) { result in
41+
result.check(projectDir: "Bar")
42+
43+
result.check(references:
44+
"Package.swift",
45+
"Sources/Foo/foo.swift",
46+
"Sources/Sea2/Sea2.c",
47+
"Sources/Bar/bar.swift",
48+
"Sources/Sea/Sea.c",
49+
"Tests/BarTests/barTests.swift",
50+
"Products/Foo.framework",
51+
"Products/Sea2.framework",
52+
"Products/Bar.framework",
53+
"Products/Sea.framework",
54+
"Products/BarTests.xctest"
55+
)
56+
57+
result.check(target: "Foo") { targetResult in
58+
targetResult.check(productType: .framework)
59+
targetResult.check(dependencies: [])
60+
}
61+
62+
result.check(target: "Bar") { targetResult in
63+
targetResult.check(productType: .framework)
64+
targetResult.check(dependencies: ["Foo"])
65+
XCTAssertEqual(targetResult.commonBuildSettings.LD_RUNPATH_SEARCH_PATHS ?? [], ["$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"])
66+
}
67+
68+
result.check(target: "Sea") { targetResult in
69+
targetResult.check(productType: .framework)
70+
targetResult.check(dependencies: ["Foo"])
71+
XCTAssertEqual(targetResult.commonBuildSettings.MODULEMAP_FILE ?? "", "xcodeproj/GeneratedModuleMap/Sea/module.modulemap")
72+
}
73+
74+
result.check(target: "Sea2") { targetResult in
75+
targetResult.check(productType: .framework)
76+
targetResult.check(dependencies: ["Foo"])
77+
XCTAssertEqual(targetResult.commonBuildSettings.MODULEMAP_FILE ?? "", "Bar/Sources/Sea2/include/module.modulemap")
78+
}
79+
80+
result.check(target: "BarTests") { targetResult in
81+
targetResult.check(productType: .unitTest)
82+
targetResult.check(dependencies: ["Foo", "Bar"])
83+
XCTAssertEqual(targetResult.commonBuildSettings.LD_RUNPATH_SEARCH_PATHS ?? [], ["@loader_path/../Frameworks"])
84+
}
85+
}
86+
}
87+
88+
static var allTests = [
89+
("testBasics", testBasics),
90+
]
91+
}
92+
93+
private func XcodeProjectTester(_ project: Xcode.Project, _ result: (XcodeProjectResult) -> Void) {
94+
result(XcodeProjectResult(project))
95+
}
96+
97+
private class XcodeProjectResult {
98+
let project: Xcode.Project
99+
let targetMap: [String: Xcode.Target]
100+
101+
init(_ project: Xcode.Project) {
102+
self.project = project
103+
self.targetMap = Dictionary(items: project.targets.map { target -> (String, Xcode.Target) in (target.name, target) })
104+
}
105+
106+
func check(projectDir: String, file: StaticString = #file, line: UInt = #line) {
107+
XCTAssertEqual(project.projectDir, projectDir, file: file, line: line)
108+
}
109+
110+
func check(target name: String, file: StaticString = #file, line: UInt = #line, _ body: ((TargetResult) -> Void)) {
111+
guard let target = targetMap[name] else {
112+
return XCTFail("Expected target not present \(self)", file: file, line: line)
113+
}
114+
body(TargetResult(target))
115+
}
116+
117+
func check(references: String..., file: StaticString = #file, line: UInt = #line) {
118+
XCTAssertEqual(recursiveRefPaths(project.mainGroup).sorted(), references.sorted(), file: file, line: line)
119+
}
120+
121+
class TargetResult {
122+
let target: Xcode.Target
123+
var commonBuildSettings: Xcode.BuildSettingsTable.BuildSettings {
124+
return target.buildSettings.common
125+
}
126+
init(_ target: Xcode.Target) {
127+
self.target = target
128+
}
129+
130+
func check(productType: Xcode.Target.ProductType, file: StaticString = #file, line: UInt = #line) {
131+
XCTAssertEqual(target.productType, productType, file: file, line: line)
132+
}
133+
134+
func check(dependencies: [String], file: StaticString = #file, line: UInt = #line) {
135+
XCTAssertEqual(target.dependencies.map{$0.target.name}, dependencies, file: file, line: line)
136+
}
137+
}
138+
}
139+
140+
extension Xcode.Reference {
141+
/// Returns name of the reference if present otherwise last path component.
142+
var basename: String {
143+
if let name = name {
144+
return name
145+
}
146+
// If path is empty (root), Path basename API returns `.`
147+
if path.isEmpty {
148+
return ""
149+
}
150+
if path.characters.first == "/" {
151+
return AbsolutePath(path).basename
152+
}
153+
return RelativePath(path).basename
154+
}
155+
}
156+
157+
/// Returns array of paths from Xcode references.
158+
private func recursiveRefPaths(_ ref: Xcode.Reference, parents: [Xcode.Reference] = []) -> [String] {
159+
if case let group as Xcode.Group = ref {
160+
return group.subitems.flatMap { recursiveRefPaths($0, parents: parents + [ref]) }
161+
}
162+
return [(parents + [ref]).filter{!$0.basename.isEmpty}.map{$0.basename}.joined(separator: "/")]
163+
}

Tests/XcodeprojTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public func allTests() -> [XCTestCaseEntry] {
1515
return [
1616
testCase(GenerateXcodeprojTests.allTests),
1717
testCase(FunctionalTests.allTests),
18+
testCase(PackageGraphTests.allTests),
1819
testCase(PlistTests.allTests),
1920
testCase(XcodeProjectModelTests.allTests),
2021
testCase(XcodeProjectModelSerializationTests.allTests),

0 commit comments

Comments
 (0)