From 0e1f039055491eae3006cbfff75ff307c4df23d9 Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Fri, 8 Jun 2018 14:32:00 +1000 Subject: [PATCH] integrate XCTest with Spectre for nicer tests in Xcode --- Package.resolved | 10 +- Package.swift | 2 +- Tests/LinuxMain.swift | 9 - Tests/XcodeGenKitTests/FixtureTests.swift | 81 -- Tests/XcodeGenKitTests/GeneratorHelpers.swift | 47 ++ .../ProjectFixtureTests.swift | 83 ++ .../ProjectGeneratorTests.swift | 692 ++-------------- Tests/XcodeGenKitTests/ProjectSpecTests.swift | 82 +- .../SourceGeneratorTests.swift | 541 +++++++++++++ Tests/XcodeGenKitTests/SpecLoadingTests.swift | 761 +++++++++--------- Tests/XcodeGenKitTests/TestHelpers.swift | 21 +- Tests/XcodeGenKitTests/XCTest.swift | 12 - 12 files changed, 1191 insertions(+), 1150 deletions(-) delete mode 100644 Tests/LinuxMain.swift delete mode 100644 Tests/XcodeGenKitTests/FixtureTests.swift create mode 100644 Tests/XcodeGenKitTests/GeneratorHelpers.swift create mode 100644 Tests/XcodeGenKitTests/ProjectFixtureTests.swift create mode 100644 Tests/XcodeGenKitTests/SourceGeneratorTests.swift delete mode 100644 Tests/XcodeGenKitTests/XCTest.swift diff --git a/Package.resolved b/Package.resolved index c4777460..0a4ce8cc 100644 --- a/Package.resolved +++ b/Package.resolved @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/onevcat/Rainbow.git", "state": { "branch": null, - "revision": "f69961599ad524251d677fbec9e4bac57385d6fc", - "version": "3.1.1" + "revision": "797a68d0a642609424b08f11eb56974a54d5f6e2", + "version": "3.1.4" } }, { @@ -57,11 +57,11 @@ }, { "package": "Spectre", - "repositoryURL": "https://github.com/kylef/Spectre.git", + "repositoryURL": "https://github.com/yonaskolb/Spectre.git", "state": { "branch": null, - "revision": "e34d5687e1e9d865e3527dd58bc2f7464ef6d936", - "version": "0.8.0" + "revision": "2b520ec417fc515c70e7093ae3ee321c3237e215", + "version": "0.8.1" } }, { diff --git a/Package.swift b/Package.swift index 791e0112..6b03879c 100644 --- a/Package.swift +++ b/Package.swift @@ -14,7 +14,7 @@ let package = Package( .package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"), .package(url: "https://github.com/jpsim/Yams.git", from: "0.3.6"), .package(url: "https://github.com/yonaskolb/JSONUtilities.git", from: "3.3.0"), - .package(url: "https://github.com/kylef/Spectre.git", from: "0.8.0"), + .package(url: "https://github.com/yonaskolb/Spectre.git", from: "0.8.1"), .package(url: "https://github.com/onevcat/Rainbow.git", from: "3.0.0"), .package(url: "https://github.com/xcodeswift/xcproj.git", from: "4.1.0") ], diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index fd97ab24..00000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,9 +0,0 @@ -@testable import XcodeGenKitTests -import XCTest - -XCTMain([ - testCase(GeneratorTests.allTests), - testCase(SpecLoadingTests.allTests), - testCase(FixtureTests.allTests), - testCase(ProjectSpecTests.allTests), -]) diff --git a/Tests/XcodeGenKitTests/FixtureTests.swift b/Tests/XcodeGenKitTests/FixtureTests.swift deleted file mode 100644 index 7978774b..00000000 --- a/Tests/XcodeGenKitTests/FixtureTests.swift +++ /dev/null @@ -1,81 +0,0 @@ -import PathKit -import ProjectSpec -import Spectre -import XcodeGenKit -import xcproj - -let fixturePath = Path(#file).parent().parent() + "Fixtures" - -func generateXcodeProject(specPath: Path, projectPath: Path) throws -> XcodeProj { - let project = try Project(path: specPath) - let generator = ProjectGenerator(project: project) - let xcodeProject = try generator.generateXcodeProject() - let oldProject = try XcodeProj(path: projectPath) - let pbxProjPath = projectPath + XcodeProj.pbxprojPath(projectPath) - let oldProjectString: String = try pbxProjPath.read() - try xcodeProject.write(path: projectPath, override: true) - let newProjectString: String = try pbxProjPath.read() - - let newProject = try XcodeProj(path: projectPath) - let stringDiff = newProjectString != oldProjectString - if newProject != oldProject || stringDiff { - var message = "\(projectPath.string) has changed. If change is legitimate commit the change and run test again" - if stringDiff { - message += ":\n\n\(pbxProjPath):\n\(prettyFirstDifferenceBetweenStrings(oldProjectString, newProjectString))" - } - throw failure(message) - } - - return newProject -} - -func fixtureTests() { - - describe("Test Project") { - var xcodeProject: XcodeProj? - - $0.it("generates") { - xcodeProject = try generateXcodeProject(specPath: fixturePath + "TestProject/project.yml", projectPath: fixturePath + "TestProject/Project.xcodeproj") - } - - $0.it("generates variant group") { - guard let xcodeProject = xcodeProject else { return } - - func getFileReferences(_ path: String) -> [ObjectReference] { - return xcodeProject.pbxproj.objects.fileReferences.objectReferences.filter { $0.object.path == path } - } - - func getVariableGroups(_ name: String?) -> [PBXVariantGroup] { - return xcodeProject.pbxproj.objects.variantGroups.referenceValues.filter { $0.name == name } - } - - let resourceName = "LocalizedStoryboard.storyboard" - let baseResource = "Base.lproj/LocalizedStoryboard.storyboard" - let localizedResource = "en.lproj/LocalizedStoryboard.strings" - - guard let variableGroup = getVariableGroups(resourceName).first else { throw failure("Couldn't find the variable group") } - - do { - let refs = getFileReferences(baseResource) - try expect(refs.count) == 1 - try expect(variableGroup.children.filter { $0 == refs.first?.reference }.count) == 1 - } - - do { - let refs = getFileReferences(localizedResource) - try expect(refs.count) == 1 - try expect(variableGroup.children.filter { $0 == refs.first?.reference }.count) == 1 - } - } - - $0.it("generates scheme execution actions") { - guard let xcodeProject = xcodeProject else { return } - - let frameworkScheme = xcodeProject.sharedData?.schemes.first { $0.name == "Framework" } - try expect(frameworkScheme?.buildAction?.preActions.first?.scriptText) == "echo Starting Framework Build" - try expect(frameworkScheme?.buildAction?.preActions.first?.title) == "Run Script" - try expect(frameworkScheme?.buildAction?.preActions.first?.environmentBuildable?.blueprintName) == "Framework_iOS" - try expect(frameworkScheme?.buildAction?.preActions.first?.environmentBuildable?.buildableName) == "Framework.framework" - } - } -} diff --git a/Tests/XcodeGenKitTests/GeneratorHelpers.swift b/Tests/XcodeGenKitTests/GeneratorHelpers.swift new file mode 100644 index 00000000..11157fca --- /dev/null +++ b/Tests/XcodeGenKitTests/GeneratorHelpers.swift @@ -0,0 +1,47 @@ +import PathKit +import ProjectSpec +import Spectre +import XcodeGenKit +import xcproj +import XCTest +import Yams + +extension Project { + + func generateXcodeProject(file: String = #file, line: Int = #line) throws -> XcodeProj { + return try doThrowing(file: file, line: line) { + let generator = ProjectGenerator(project: self) + return try generator.generateXcodeProject() + } + } + + func generatePbxProj(file: String = #file, line: Int = #line) throws -> PBXProj { + return try doThrowing(file: file, line: line) { + let xcodeProject = try generateXcodeProject().pbxproj + try xcodeProject.validate() + return xcodeProject + } + } +} + +extension PBXProj { + + // validates that a PBXProj is correct + // TODO: Use xclint? + func validate() throws { + let mainGroup = try getMainGroup() + + func validateGroup(_ group: PBXGroup) throws { + let hasDuplicatedChildren = group.children.count != Set(group.children).count + if hasDuplicatedChildren { + throw failure("Group \"\(group.nameOrPath)\" has duplicated children:\n - \(group.children.sorted().joined(separator: "\n - "))") + } + for child in group.children { + if let group = objects.groups.getReference(child) { + try validateGroup(group) + } + } + } + try validateGroup(mainGroup) + } +} diff --git a/Tests/XcodeGenKitTests/ProjectFixtureTests.swift b/Tests/XcodeGenKitTests/ProjectFixtureTests.swift new file mode 100644 index 00000000..a6aca883 --- /dev/null +++ b/Tests/XcodeGenKitTests/ProjectFixtureTests.swift @@ -0,0 +1,83 @@ +import PathKit +import ProjectSpec +import Spectre +import XcodeGenKit +import xcproj +import XCTest + + +class ProjectFixtureTests: XCTestCase { + + func testProjectFixture() { + describe { + var xcodeProject: XcodeProj? + + $0.it("generates") { + xcodeProject = try generateXcodeProject(specPath: fixturePath + "TestProject/project.yml", projectPath: fixturePath + "TestProject/Project.xcodeproj") + } + + $0.it("generates variant group") { + guard let xcodeProject = xcodeProject else { return } + + func getFileReferences(_ path: String) -> [ObjectReference] { + return xcodeProject.pbxproj.objects.fileReferences.objectReferences.filter { $0.object.path == path } + } + + func getVariableGroups(_ name: String?) -> [PBXVariantGroup] { + return xcodeProject.pbxproj.objects.variantGroups.referenceValues.filter { $0.name == name } + } + + let resourceName = "LocalizedStoryboard.storyboard" + let baseResource = "Base.lproj/LocalizedStoryboard.storyboard" + let localizedResource = "en.lproj/LocalizedStoryboard.strings" + + guard let variableGroup = getVariableGroups(resourceName).first else { throw failure("Couldn't find the variable group") } + + do { + let refs = getFileReferences(baseResource) + try expect(refs.count) == 1 + try expect(variableGroup.children.filter { $0 == refs.first?.reference }.count) == 1 + } + + do { + let refs = getFileReferences(localizedResource) + try expect(refs.count) == 1 + try expect(variableGroup.children.filter { $0 == refs.first?.reference }.count) == 1 + } + } + + $0.it("generates scheme execution actions") { + guard let xcodeProject = xcodeProject else { return } + + let frameworkScheme = xcodeProject.sharedData?.schemes.first { $0.name == "Framework" } + try expect(frameworkScheme?.buildAction?.preActions.first?.scriptText) == "echo Starting Framework Build" + try expect(frameworkScheme?.buildAction?.preActions.first?.title) == "Run Script" + try expect(frameworkScheme?.buildAction?.preActions.first?.environmentBuildable?.blueprintName) == "Framework_iOS" + try expect(frameworkScheme?.buildAction?.preActions.first?.environmentBuildable?.buildableName) == "Framework.framework" + } + } + } +} + +fileprivate func generateXcodeProject(specPath: Path, projectPath: Path, file: String = #file, line: Int = #line) throws -> XcodeProj { + let project = try Project(path: specPath) + let generator = ProjectGenerator(project: project) + let xcodeProject = try generator.generateXcodeProject() + let oldProject = try XcodeProj(path: projectPath) + let pbxProjPath = projectPath + XcodeProj.pbxprojPath(projectPath) + let oldProjectString: String = try pbxProjPath.read() + try xcodeProject.write(path: projectPath, override: true) + let newProjectString: String = try pbxProjPath.read() + + let newProject = try XcodeProj(path: projectPath) + let stringDiff = newProjectString != oldProjectString + if newProject != oldProject || stringDiff { + var message = "\(projectPath.string) has changed. If change is legitimate commit the change and run test again" + if stringDiff { + message += ":\n\n\(pbxProjPath):\n\(prettyFirstDifferenceBetweenStrings(oldProjectString, newProjectString))" + } + throw failure(message, file: file, line: line) + } + + return newProject +} diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index b7bb1a6d..4de9ec7d 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -3,54 +3,44 @@ import ProjectSpec import Spectre import XcodeGenKit import xcproj +import XCTest import Yams -func projectGeneratorTests() { +fileprivate let app = Target( + name: "MyApp", + type: .application, + platform: .iOS, + settings: Settings(buildSettings: ["SETTING_1": "VALUE"]), + dependencies: [Dependency(type: .target, reference: "MyFramework")] +) - func getXcodeProject(_ project: Project) throws -> XcodeProj { - let generator = ProjectGenerator(project: project) - return try generator.generateXcodeProject() - } +fileprivate let framework = Target( + name: "MyFramework", + type: .framework, + platform: .iOS, + settings: Settings(buildSettings: ["SETTING_2": "VALUE"]) +) - func getPbxProj(_ project: Project) throws -> PBXProj { - let xcodeProject = try getXcodeProject(project).pbxproj - try xcodeProject.validate() - return xcodeProject - } +fileprivate let uiTest = Target( + name: "MyAppUITests", + type: .uiTestBundle, + platform: .iOS, + settings: Settings(buildSettings: ["SETTING_3": "VALUE"]), + dependencies: [Dependency(type: .target, reference: "MyApp")] +) - describe("Project Generator") { +fileprivate let targets = [app, framework, uiTest] - let application = Target( - name: "MyApp", - type: .application, - platform: .iOS, - settings: Settings(buildSettings: ["SETTING_1": "VALUE"]), - dependencies: [Dependency(type: .target, reference: "MyFramework")] - ) +class ProjectGeneratorTests: XCTestCase { - let framework = Target( - name: "MyFramework", - type: .framework, - platform: .iOS, - settings: Settings(buildSettings: ["SETTING_2": "VALUE"]) - ) + func testOptions() throws { - let uiTest = Target( - name: "MyAppUITests", - type: .uiTestBundle, - platform: .iOS, - settings: Settings(buildSettings: ["SETTING_3": "VALUE"]), - dependencies: [Dependency(type: .target, reference: "MyApp")] - ) - - let targets = [application, framework, uiTest] - - $0.describe("Options") { + describe { $0.it("generates bundle id") { let options = SpecOptions(bundleIdPrefix: "com.test") let project = Project(basePath: "", name: "test", targets: [framework], options: options) - let pbxProj = try getPbxProj(project) + let pbxProj = try project.generatePbxProj() guard let target = pbxProj.objects.nativeTargets.first?.value, let buildConfigList = target.buildConfigurationList, let buildConfigs = pbxProj.objects.configurationLists.getReference(buildConfigList), @@ -64,7 +54,7 @@ func projectGeneratorTests() { $0.it("clears setting presets") { let options = SpecOptions(settingPresets: .none) let project = Project(basePath: "", name: "test", targets: [framework], options: options) - let pbxProj = try getPbxProj(project) + let pbxProj = try project.generatePbxProj() let allSettings = pbxProj.objects.buildConfigurations.referenceValues.reduce([:]) { $0.merged($1.buildSettings) }.keys.sorted() try expect(allSettings) == ["SETTING_2"] } @@ -72,7 +62,7 @@ func projectGeneratorTests() { $0.it("generates development language") { let options = SpecOptions(developmentLanguage: "de") let project = Project(basePath: "", name: "test", options: options) - let pbxProj = try getPbxProj(project) + let pbxProj = try project.generatePbxProj() guard let pbxProject = pbxProj.objects.projects.first?.value else { throw failure("Could't find PBXProject") } @@ -100,7 +90,7 @@ func projectGeneratorTests() { $0.it("uses the default configuration name") { let options = SpecOptions(defaultConfig: "Bconfig") let project = Project(basePath: "", name: "test", configs: [Config(name: "Aconfig"), Config(name: "Bconfig")], targets: [framework], options: options) - let pbxProject = try getPbxProj(project) + let pbxProject = try project.generatePbxProj() guard let projectConfigListReference = pbxProject.objects.projects.values.first?.buildConfigurationList, let defaultConfigurationName = pbxProject.objects.configurationLists[projectConfigListReference]?.defaultConfigurationName @@ -111,12 +101,14 @@ func projectGeneratorTests() { try expect(defaultConfigurationName) == "Bconfig" } } + } - $0.describe("Config") { + func testConfigGenerator() { + describe { $0.it("generates config defaults") { let project = Project(basePath: "", name: "test") - let pbxProj = try getPbxProj(project) + let pbxProj = try project.generatePbxProj() let configs = pbxProj.objects.buildConfigurations.referenceValues try expect(configs.count) == 2 try expect(configs).contains(name: "Debug") @@ -129,7 +121,7 @@ func projectGeneratorTests() { name: "test", configs: [Config(name: "config1"), Config(name: "config2")] ) - let pbxProj = try getPbxProj(project) + let pbxProj = try project.generatePbxProj() let configs = pbxProj.objects.buildConfigurations.referenceValues try expect(configs.count) == 2 try expect(configs).contains(name: "config1") @@ -142,7 +134,7 @@ func projectGeneratorTests() { name: "test", configs: [Config(name: "config")] ) - let pbxProj = try getPbxProj(project) + let pbxProj = try project.generatePbxProj() guard let config = pbxProj.objects.buildConfigurations.first?.value else { throw failure("configuration not found") } @@ -193,35 +185,37 @@ func projectGeneratorTests() { try expect(buildSettings["SETTING2"] as? String) == "VALUE2" } } + } - $0.describe("Targets") { + func testTargets() { + describe { let project = Project(basePath: "", name: "test", targets: targets) $0.it("generates targets") { - let pbxProject = try getPbxProj(project) + let pbxProject = try project.generatePbxProj() let nativeTargets = pbxProject.objects.nativeTargets.referenceValues try expect(nativeTargets.count) == 3 - try expect(nativeTargets.contains { $0.name == application.name }).beTrue() + try expect(nativeTargets.contains { $0.name == app.name }).beTrue() try expect(nativeTargets.contains { $0.name == framework.name }).beTrue() try expect(nativeTargets.contains { $0.name == uiTest.name }).beTrue() } $0.it("generates target attributes") { - var appTargetWithAttributes = application + var appTargetWithAttributes = app appTargetWithAttributes.settings.buildSettings["DEVELOPMENT_TEAM"] = "123" appTargetWithAttributes.attributes = ["ProvisioningStyle": "Automatic"] var testTargetWithAttributes = uiTest testTargetWithAttributes.settings.buildSettings["CODE_SIGN_STYLE"] = "Manual" - var project = Project(basePath: "", name: "test", targets: [appTargetWithAttributes, framework, testTargetWithAttributes]) - let pbxProject = try getPbxProj(project) + let project = Project(basePath: "", name: "test", targets: [appTargetWithAttributes, framework, testTargetWithAttributes]) + let pbxProject = try project.generatePbxProj() guard let targetAttributes = pbxProject.objects.projects.referenceValues.first?.attributes["TargetAttributes"] as? [String: [String: Any]] else { throw failure("Couldn't find Project TargetAttributes") } - guard let appTarget = pbxProject.objects.targets(named: application.name).first else { + guard let appTarget = pbxProject.objects.targets(named: app.name).first else { throw failure("Couldn't find App Target") } @@ -239,7 +233,7 @@ func projectGeneratorTests() { let target = Target(name: "Target", type: .application, platform: .watchOS, deploymentTarget: "2.0") let project = Project(basePath: "", name: "", targets: [target], options: .init(deploymentTarget: DeploymentTarget(iOS: "10.0", watchOS: "3.0"))) - let pbxProject = try getPbxProj(project) + let pbxProject = try project.generatePbxProj() guard let projectConfigListReference = pbxProject.objects.projects.values.first?.buildConfigurationList, let projectConfigReference = pbxProject.objects.configurationLists[projectConfigListReference]?.buildConfigurations.first, @@ -265,20 +259,20 @@ func projectGeneratorTests() { } $0.it("generates dependencies") { - let pbxProject = try getPbxProj(project) + let pbxProject = try project.generatePbxProj() let nativeTargets = pbxProject.objects.nativeTargets.objectReferences let dependencies = pbxProject.objects.targetDependencies.objectReferences try expect(dependencies.count) == 2 try expect(dependencies[0].object.target) == nativeTargets.first { $0.object.name == framework.name }!.reference - try expect(dependencies[1].object.target) == nativeTargets.first { $0.object.name == application.name }!.reference + try expect(dependencies[1].object.target) == nativeTargets.first { $0.object.name == app.name }!.reference } $0.it("generates run scripts") { var scriptSpec = project scriptSpec.targets[0].prebuildScripts = [BuildScript(script: .script("script1"))] scriptSpec.targets[0].postbuildScripts = [BuildScript(script: .script("script2"))] - let pbxProject = try getPbxProj(scriptSpec) + let pbxProject = try scriptSpec.generatePbxProj() guard let nativeTarget = pbxProject.objects.nativeTargets.referenceValues .first(where: { $0.buildPhases.count >= 2 }) else { @@ -316,15 +310,17 @@ func projectGeneratorTests() { targets: [target1, target2] ) - _ = try getPbxProj(project) + _ = try project.generatePbxProj() } } + } - $0.describe("Schemes") { + func testSchemes() { + describe { - let buildTarget = Scheme.BuildTarget(target: application.name) + let buildTarget = Scheme.BuildTarget(target: app.name) $0.it("generates scheme") { - let preAction = Scheme.ExecutionAction(name: "Script", script: "echo Starting", settingsTarget: application.name) + let preAction = Scheme.ExecutionAction(name: "Script", script: "echo Starting", settingsTarget: app.name) let scheme = Scheme( name: "MyScheme", build: Scheme.Build(targets: [buildTarget], preActions: [preAction]) @@ -332,12 +328,12 @@ func projectGeneratorTests() { let project = Project( basePath: "", name: "test", - targets: [application, framework], + targets: [app, framework], schemes: [scheme] ) - let xcodeProject = try getXcodeProject(project) + let xcodeProject = try project.generateXcodeProject() guard let target = xcodeProject.pbxproj.objects.nativeTargets.objectReferences - .first(where: { $0.object.name == application.name }) else { + .first(where: { $0.object.name == app.name }) else { throw failure("Target not found") } guard let xcscheme = xcodeProject.sharedData?.schemes.first else { @@ -391,20 +387,17 @@ func projectGeneratorTests() { let project = Project( basePath: "", name: "test", - targets: [application, framework], + targets: [app, framework], schemes: [scheme] ) - let xcodeProject = try getXcodeProject(project) - - guard let target = xcodeProject.pbxproj.objects.nativeTargets.objectReferences - .first(where: { $0.object.name == application.name }) else { - throw failure("Target not found") - } + let xcodeProject = try project.generateXcodeProject() guard let xcscheme = xcodeProject.sharedData?.schemes.first else { throw failure("Scheme not found") } + try expect(xcodeProject.pbxproj.objects.nativeTargets.objectReferences + .contains(where: { $0.object.name == app.name })).beTrue() try expect(xcscheme.launchAction?.environmentVariables) == runVariables try expect(xcscheme.testAction?.environmentVariables).to.beNil() try expect(xcscheme.profileAction?.environmentVariables).to.beNil() @@ -412,7 +405,7 @@ func projectGeneratorTests() { $0.it("generates target schemes from config variant") { let configVariants = ["Test", "Production"] - var target = application + var target = app target.scheme = TargetScheme(configVariants: configVariants) let configs: [Config] = [ Config(name: "Test Debug", type: .debug), @@ -422,12 +415,12 @@ func projectGeneratorTests() { ] let project = Project(basePath: "", name: "test", configs: configs, targets: [target, framework]) - let xcodeProject = try getXcodeProject(project) + let xcodeProject = try project.generateXcodeProject() try expect(xcodeProject.sharedData?.schemes.count) == 2 guard let nativeTarget = xcodeProject.pbxproj.objects.nativeTargets.objectReferences - .first(where: { $0.object.name == application.name }) else { + .first(where: { $0.object.name == app.name }) else { throw failure("Target not found") } guard let xcscheme = xcodeProject.sharedData?.schemes @@ -448,11 +441,11 @@ func projectGeneratorTests() { $0.it("generates environment variables for target schemes") { let variables: [XCScheme.EnvironmentVariable] = [XCScheme.EnvironmentVariable(variable: "env", value: "var", enabled: false)] - var target = application + var target = app target.scheme = TargetScheme(environmentVariables: variables) let project = Project(basePath: "", name: "test", targets: [target, framework]) - let xcodeProject = try getXcodeProject(project) + let xcodeProject = try project.generateXcodeProject() try expect(xcodeProject.sharedData?.schemes.count) == 1 @@ -466,14 +459,14 @@ func projectGeneratorTests() { } $0.it("generates pre and post actions for target schemes") { - var target = application + var target = app target.scheme = TargetScheme( preActions: [.init(name: "Run", script: "do")], postActions: [.init(name: "Run2", script: "post", settingsTarget: "MyApp")] ) let project = Project(basePath: "", name: "test", targets: [target, framework]) - let xcodeProject = try getXcodeProject(project) + let xcodeProject = try project.generateXcodeProject() try expect(xcodeProject.sharedData?.schemes.count) == 1 @@ -491,553 +484,6 @@ func projectGeneratorTests() { try expect(xcscheme.testAction?.postActions.first?.environmentBuildable?.blueprintName) == "MyApp" } } - - $0.describe("Sources") { - - let directoryPath = Path("TestDirectory") - let outOfRootPath = Path("OtherDirectory") - - func createDirectories(_ directories: String) throws { - - let yaml = try Yams.load(yaml: directories)! - - func getFiles(_ file: Any, path: Path) -> [Path] { - if let array = file as? [Any] { - return array.flatMap { getFiles($0, path: path) } - } else if let string = file as? String { - return [path + string] - } else if let dictionary = file as? [String: Any] { - var array: [Path] = [] - for (key, value) in dictionary { - array += getFiles(value, path: path + key) - } - return array - } else { - return [] - } - } - - let files = getFiles(yaml, path: directoryPath).filter { $0.extension != nil } - for file in files { - try file.parent().mkpath() - try file.write("") - } - } - - func removeDirectories() { - try? directoryPath.delete() - try? outOfRootPath.delete() - } - - $0.before { - removeDirectories() - } - - $0.after { - removeDirectories() - } - - $0.it("generates source groups") { - let directories = """ - Sources: - A: - - a.swift - - B: - - b.swift - """ - try createDirectories(directories) - - let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"]) - let project = Project(basePath: directoryPath, name: "Test", targets: [target]) - - let pbxProj = try getPbxProj(project) - try pbxProj.expectFile(paths: ["Sources", "A", "a.swift"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["Sources", "A", "B", "b.swift"], buildPhase: .sources) - } - - $0.it("supports frameworks in sources") { - let directories = """ - Sources: - - Foo.framework - - Bar.swift - """ - - try createDirectories(directories) - - let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"]) - let project = Project(basePath: directoryPath, name: "Test", targets: [target]) - let pbxProj = try getPbxProj(project) - try pbxProj.expectFile(paths: ["Sources", "Bar.swift"], buildPhase: .sources) - let buildPhase = pbxProj.objects.copyFilesBuildPhases.referenceValues.first - try expect(buildPhase?.dstSubfolderSpec) == .frameworks - let fileReference = pbxProj.getFileReference(paths: ["Sources", "Foo.framework"], - names: ["Sources", "Foo.framework"])?.reference ?? "" - let buildFile = pbxProj.objects.buildFiles.objectReferences - .first(where: { $0.object.fileRef == fileReference })?.reference ?? "" - try expect(buildPhase?.files.count) == 1 - try expect(buildPhase?.files.contains(buildFile)) == true - } - - $0.it("generates core data models") { - let directories = """ - Sources: - model.xcdatamodeld: - - model.xcdatamodel - """ - try createDirectories(directories) - - let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"]) - let project = Project(basePath: directoryPath, name: "Test", targets: [target]) - - let pbxProj = try getPbxProj(project) - guard let fileReference = pbxProj.objects.fileReferences.first(where: { $0.value.nameOrPath == "model.xcdatamodel" }) else { - throw failure("Couldn't find model file reference") - } - guard let versionGroup = pbxProj.objects.versionGroups.values.first else { - throw failure("Couldn't find version group") - } - try expect(versionGroup.currentVersion) == fileReference.key - try expect(versionGroup.children) == [fileReference.key] - try expect(versionGroup.path) == "model.xcdatamodeld" - try expect(fileReference.value.path) == "model.xcdatamodel" - } - - $0.it("handles duplicate names") { - let directories = """ - Sources: - - a.swift - - a: - - a.swift - - a: - - a.swift - - """ - try createDirectories(directories) - - let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"]) - let project = Project( - basePath: directoryPath, - name: "Test", - targets: [target], - fileGroups: ["Sources"] - ) - - let pbxProj = try getPbxProj(project) - try pbxProj.expectFile(paths: ["Sources", "a.swift"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["Sources", "a", "a.swift"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["Sources", "a", "a", "a.swift"], buildPhase: .sources) - } - - $0.it("renames sources") { - let directories = """ - Sources: - - a.swift - OtherSource: - - b.swift - """ - try createDirectories(directories) - - let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ - TargetSource(path: "Sources", name: "NewSource"), - TargetSource(path: "OtherSource/b.swift", name: "c.swift"), - ]) - let project = Project(basePath: directoryPath, name: "Test", targets: [target]) - - let pbxProj = try getPbxProj(project) - try pbxProj.expectFile(paths: ["Sources", "a.swift"], names: ["NewSource", "a.swift"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["OtherSource", "b.swift"], names: ["OtherSource", "c.swift"], buildPhase: .sources) - } - - $0.it("excludes sources") { - let directories = """ - Sources: - - A: - - a.swift - - B: - - b.swift - - b.ignored - - a.ignored - - B: - - b.swift - - D: - - d.h - - d.m - - E: - - e.jpg - - e.h - - e.m - - F: - - f.swift - - G: - - H: - - h.swift - - types: - - a.swift - - a.m - - a.h - - a.x - - numbers: - - file1.a - - file2.a - - file3.a - - file4.a - - partial: - - file_part - - ignore.file - - a.ignored - - project.xcodeproj: - - project.pbxproj - """ - try createDirectories(directories) - - let excludes = [ - "B", - "d.m", - "E/F/*.swift", - "G/H/", - "types/*.[hx]", - "numbers/file[2-3].a", - "partial/*_part", - "ignore.file", - "*.ignored", - "*.xcodeproj", - // not supported - // "**/*.ignored", - ] - - let target = Target(name: "Test", type: .application, platform: .iOS, sources: [TargetSource(path: "Sources", excludes: excludes)]) - let project = Project(basePath: directoryPath, name: "Test", targets: [target]) - - let pbxProj = try getPbxProj(project) - try pbxProj.expectFile(paths: ["Sources", "A", "a.swift"]) - try pbxProj.expectFile(paths: ["Sources", "D", "d.h"]) - try pbxProj.expectFile(paths: ["Sources", "D", "d.m"]) - try pbxProj.expectFile(paths: ["Sources", "E", "e.jpg"]) - try pbxProj.expectFile(paths: ["Sources", "E", "e.m"]) - try pbxProj.expectFile(paths: ["Sources", "E", "e.h"]) - try pbxProj.expectFile(paths: ["Sources", "types", "a.swift"]) - try pbxProj.expectFile(paths: ["Sources", "numbers", "file1.a"]) - try pbxProj.expectFile(paths: ["Sources", "numbers", "file4.a"]) - try pbxProj.expectFileMissing(paths: ["Sources", "B", "b.swift"]) - try pbxProj.expectFileMissing(paths: ["Sources", "E", "F", "f.swift"]) - try pbxProj.expectFileMissing(paths: ["Sources", "G", "H", "h.swift"]) - try pbxProj.expectFileMissing(paths: ["Sources", "types", "a.h"]) - try pbxProj.expectFileMissing(paths: ["Sources", "types", "a.x"]) - try pbxProj.expectFileMissing(paths: ["Sources", "numbers", "file2.a"]) - try pbxProj.expectFileMissing(paths: ["Sources", "numbers", "file3.a"]) - try pbxProj.expectFileMissing(paths: ["Sources", "partial", "file_part"]) - try pbxProj.expectFileMissing(paths: ["Sources", "a.ignored"]) - try pbxProj.expectFileMissing(paths: ["Sources", "ignore.file"]) - try pbxProj.expectFileMissing(paths: ["Sources", "project.xcodeproj"]) - } - - $0.it("generates file sources") { - let directories = """ - Sources: - A: - - a.swift - - Assets.xcassets - - B: - - b.swift - - c.jpg - """ - try createDirectories(directories) - - let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ - "Sources/A/a.swift", - "Sources/A/B/b.swift", - "Sources/A/Assets.xcassets", - "Sources/A/B/c.jpg", - ]) - let project = Project(basePath: directoryPath, name: "Test", targets: [target]) - - let pbxProj = try getPbxProj(project) - try pbxProj.expectFile(paths: ["Sources/A", "a.swift"], names: ["A", "a.swift"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["Sources/A/B", "b.swift"], names: ["B", "b.swift"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["Sources/A/B", "c.jpg"], names: ["B", "c.jpg"], buildPhase: .resources) - try pbxProj.expectFile(paths: ["Sources/A", "Assets.xcassets"], names: ["A", "Assets.xcassets"], buildPhase: .resources) - } - - $0.it("generates shared sources") { - let directories = """ - Sources: - A: - - a.swift - - B: - - b.swift - - c.jpg - """ - try createDirectories(directories) - - let target1 = Target(name: "Test1", type: .framework, platform: .iOS, sources: ["Sources"]) - let target2 = Target(name: "Test2", type: .framework, platform: .tvOS, sources: ["Sources"]) - let project = Project(basePath: directoryPath, name: "Test", targets: [target1, target2]) - - _ = try getPbxProj(project) - // TODO: check there are build files for both targets - } - - $0.it("generates intermediate groups") { - - let directories = """ - Sources: - A: - - b.swift - F: - - G: - - h.swift - """ - try createDirectories(directories) - let outOfSourceFile = outOfRootPath + "C/D/e.swift" - try outOfSourceFile.parent().mkpath() - try outOfSourceFile.write("") - - let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ - "Sources/A/b.swift", - "Sources/F/G/h.swift", - "../OtherDirectory/C/D/e.swift", - ]) - let options = SpecOptions(createIntermediateGroups: true) - let project = Project(basePath: directoryPath, name: "Test", targets: [target], options: options) - - let pbxProj = try getPbxProj(project) - try pbxProj.expectFile(paths: ["Sources", "A", "b.swift"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["Sources", "F", "G", "h.swift"], buildPhase: .sources) - try pbxProj.expectFile(paths: [(outOfRootPath + "C/D").string, "e.swift"], names: ["D", "e.swift"], buildPhase: .sources) - } - - $0.it("generates folder references") { - let directories = """ - Sources: - A: - - a.resource - - b.resource - """ - try createDirectories(directories) - - let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ - TargetSource(path: "Sources/A", type: .folder), - ]) - let project = Project(basePath: directoryPath, name: "Test", targets: [target]) - - let pbxProj = try getPbxProj(project) - try pbxProj.expectFile(paths: ["Sources/A"], names: ["A"], buildPhase: .resources) - try pbxProj.expectFileMissing(paths: ["Sources", "A", "a.swift"]) - } - - $0.it("adds files to correct build phase") { - let directories = """ - A: - - file.swift - - file.xcassets - - file.h - - Info.plist - - file.xcconfig - B: - - file.swift - - file.xcassets - - file.h - - Info.plist - - file.xcconfig - C: - - file.swift - - file.m - - file.mm - - file.cpp - - file.c - - file.S - - file.h - - file.hh - - file.hpp - - file.ipp - - file.tpp - - file.hxx - - file.def - - file.xcconfig - - file.entitlements - - file.gpx - - file.apns - - file.123 - - file.xcassets - - Info.plist - """ - try createDirectories(directories) - - let target = Target(name: "Test", type: .framework, platform: .iOS, sources: [ - TargetSource(path: "A", buildPhase: .resources), - TargetSource(path: "B", buildPhase: .none), - TargetSource(path: "C", buildPhase: nil), - ]) - let project = Project(basePath: directoryPath, name: "Test", targets: [target]) - - let pbxProj = try getPbxProj(project) - try pbxProj.expectFile(paths: ["A", "file.swift"], buildPhase: .resources) - try pbxProj.expectFile(paths: ["A", "file.xcassets"], buildPhase: .resources) - try pbxProj.expectFile(paths: ["A", "file.h"], buildPhase: .resources) - try pbxProj.expectFile(paths: ["A", "Info.plist"], buildPhase: .none) - try pbxProj.expectFile(paths: ["A", "file.xcconfig"], buildPhase: .resources) - - try pbxProj.expectFile(paths: ["B", "file.swift"], buildPhase: .none) - try pbxProj.expectFile(paths: ["B", "file.xcassets"], buildPhase: .none) - try pbxProj.expectFile(paths: ["B", "file.h"], buildPhase: .none) - try pbxProj.expectFile(paths: ["B", "Info.plist"], buildPhase: .none) - try pbxProj.expectFile(paths: ["B", "file.xcconfig"], buildPhase: .none) - - try pbxProj.expectFile(paths: ["C", "file.swift"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["C", "file.m"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["C", "file.mm"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["C", "file.cpp"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["C", "file.c"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["C", "file.S"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["C", "file.h"], buildPhase: .headers) - try pbxProj.expectFile(paths: ["C", "file.hh"], buildPhase: .headers) - try pbxProj.expectFile(paths: ["C", "file.hpp"], buildPhase: .headers) - try pbxProj.expectFile(paths: ["C", "file.ipp"], buildPhase: .headers) - try pbxProj.expectFile(paths: ["C", "file.tpp"], buildPhase: .headers) - try pbxProj.expectFile(paths: ["C", "file.hxx"], buildPhase: .headers) - try pbxProj.expectFile(paths: ["C", "file.def"], buildPhase: .headers) - try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: .none) - try pbxProj.expectFile(paths: ["C", "file.entitlements"], buildPhase: .none) - try pbxProj.expectFile(paths: ["C", "file.gpx"], buildPhase: .none) - try pbxProj.expectFile(paths: ["C", "file.apns"], buildPhase: .none) - try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: .none) - try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: .none) - try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: .none) - try pbxProj.expectFile(paths: ["C", "file.xcassets"], buildPhase: .resources) - try pbxProj.expectFile(paths: ["C", "file.123"], buildPhase: .resources) - try pbxProj.expectFile(paths: ["C", "Info.plist"], buildPhase: .none) - } - - $0.it("duplicate TargetSource is included once in sources build phase") { - let directories = """ - Sources: - A: - - a.swift - """ - try createDirectories(directories) - - let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ - "Sources/A/a.swift", - "Sources/A/a.swift", - ]) - let project = Project(basePath: directoryPath, name: "Test", targets: [target]) - - let pbxProj = try getPbxProj(project) - try pbxProj.expectFile(paths: ["Sources/A", "a.swift"], names: ["A", "a.swift"], buildPhase: .sources) - - let sourcesBuildPhase = pbxProj.objects.buildPhases - .first(where: { $0.1.buildPhase == BuildPhase.sources })! - .value - - try expect(sourcesBuildPhase.files.count) == 1 - } - } - } -} - -extension PBXProj { - - // validates that a PBXProj is correct - // TODO: Use xclint? - func validate() throws { - let mainGroup = try getMainGroup() - - func validateGroup(_ group: PBXGroup) throws { - let hasDuplicatedChildren = group.children.count != Set(group.children).count - if hasDuplicatedChildren { - throw failure("Group \"\(group.nameOrPath)\" has duplicated children:\n - \(group.children.sorted().joined(separator: "\n - "))") - } - for child in group.children { - if let group = objects.groups.getReference(child) { - try validateGroup(group) - } - } - } - try validateGroup(mainGroup) - } -} - -extension PBXProj { - - /// expect a file within groups of the paths, using optional different names - func expectFile(paths: [String], names: [String]? = nil, buildPhase: TargetSource.BuildPhase? = nil) throws { - guard let fileReference = getFileReference(paths: paths, names: names ?? paths) else { - var error = "Could not find file at path \(paths.joined(separator: "/").quoted)" - if let names = names, names != paths { - error += " and name \(names.joined(separator: "/").quoted)" - } - throw failure(error) - } - - if let buildPhase = buildPhase { - let buildFile = objects.buildFiles.objectReferences - .first(where: { $0.object.fileRef == fileReference.reference }) - let actualBuildPhase = buildFile - .flatMap { buildFile in objects.buildPhases.referenceValues.first { $0.files.contains(buildFile.reference) } }?.buildPhase - - var error: String? - if let buildPhase = buildPhase.buildPhase { - if actualBuildPhase != buildPhase { - if let actualBuildPhase = actualBuildPhase { - error = "is in the \(actualBuildPhase.rawValue) build phase instead of the expected \(buildPhase.rawValue.quoted)" - } else { - error = "isn't in a build phase when it's expected to be in \(buildPhase.rawValue.quoted)" - } - } - } else if let actualBuildPhase = actualBuildPhase { - error = "is in the \(actualBuildPhase.rawValue.quoted) build phase when it's expected to not be in any" - } - if let error = error { - throw failure("File \(paths.joined(separator: "/").quoted) \(error)") - } - } - } - - /// expect a missing file within groups of the paths, using optional different names - func expectFileMissing(paths: [String], names: [String]? = nil) throws { - let names = names ?? paths - if getFileReference(paths: paths, names: names) != nil { - throw failure("Found unexpected file at path \(paths.joined(separator: "/").quoted) and name \(paths.joined(separator: "/").quoted)") - } - } - - func getFileReference(paths: [String], names: [String]) -> ObjectReference? { - guard let project = objects.projects.first?.value else { return nil } - guard let mainGroup = objects.groups.getReference(project.mainGroup) else { return nil } - - return getFileReference(group: mainGroup, paths: paths, names: names) - } - - func getMainGroup() throws -> PBXGroup { - guard let project = objects.projects.first?.value else { - throw failure("Couldn't find project") - } - guard let mainGroup = objects.groups.getReference(project.mainGroup) else { - throw failure("Couldn't find main group") - } - return mainGroup - } - - private func getFileReference(group: PBXGroup, paths: [String], names: [String]) -> ObjectReference? { - - guard !paths.isEmpty else { return nil } - let path = paths.first! - let name = names.first! - let restOfPath = Array(paths.dropFirst()) - let restOfName = Array(names.dropFirst()) - if restOfPath.isEmpty { - let fileReferences: [ObjectReference] = group.children.compactMap { reference in - if let fileReference = self.objects.fileReferences.getReference(reference) { - return ObjectReference(reference: reference, object: fileReference) - } else { - return nil - } - } - return fileReferences.first { $0.object.path == path && $0.object.nameOrPath == name } - } else { - let groups = group.children.compactMap { self.objects.groups.getReference($0) } - guard let group = groups.first(where: { $0.path == path && $0.nameOrPath == name }) else { return nil } - return getFileReference(group: group, paths: restOfPath, names: restOfName) - } } + } diff --git a/Tests/XcodeGenKitTests/ProjectSpecTests.swift b/Tests/XcodeGenKitTests/ProjectSpecTests.swift index 0f552165..baf68df7 100644 --- a/Tests/XcodeGenKitTests/ProjectSpecTests.swift +++ b/Tests/XcodeGenKitTests/ProjectSpecTests.swift @@ -3,46 +3,31 @@ import ProjectSpec import Spectre import XcodeGenKit import xcproj +import XCTest -func projectSpecTests() { +class ProjectSpecTests: XCTestCase { - describe("ProjectSpec") { + func testTargetType() { + describe { - let framework = Target( - name: "MyFramework", - type: .framework, - platform: .iOS, - settings: Settings(buildSettings: ["SETTING_2": "VALUE"]) - ) - let staticLibrary = Target( - name: "MyStaticLibrary", - type: .staticLibrary, - platform: .iOS, - settings: Settings(buildSettings: ["SETTING_2": "VALUE"]) - ) - let dynamicLibrary = Target( - name: "MyDynamicLibrary", - type: .dynamicLibrary, - platform: .iOS, - settings: Settings(buildSettings: ["SETTING_2": "VALUE"]) - ) - - func expectValidationError(_ project: Project, _ expectedError: SpecValidationError.ValidationError) throws { - do { - try project.validate() - } catch let error as SpecValidationError { - if !error.errors - .contains(where: { $0.description == expectedError.description }) { - throw failure("Supposed to fail with:\n\(expectedError)\nbut got:\n\(error.errors.map { $0.description }.joined(separator: "\n"))") - } - return - } catch { - throw failure("Supposed to fail with \"\(expectedError)\"") - } - throw failure("Supposed to fail with \"\(expectedError)\"") - } - - $0.describe("Types") { + let framework = Target( + name: "MyFramework", + type: .framework, + platform: .iOS, + settings: Settings(buildSettings: ["SETTING_2": "VALUE"]) + ) + let staticLibrary = Target( + name: "MyStaticLibrary", + type: .staticLibrary, + platform: .iOS, + settings: Settings(buildSettings: ["SETTING_2": "VALUE"]) + ) + let dynamicLibrary = Target( + name: "MyDynamicLibrary", + type: .dynamicLibrary, + platform: .iOS, + settings: Settings(buildSettings: ["SETTING_2": "VALUE"]) + ) $0.it("is a framework when it has the right extension") { try expect(framework.type.isFramework).to.beTrue() } @@ -52,8 +37,10 @@ func projectSpecTests() { try expect(dynamicLibrary.type.isLibrary).to.beTrue() } } + } - $0.describe("Deployment Target") { + func testDeploymentTarget() { + describe { $0.it("has correct build setting") { try expect(Platform.iOS.deploymentTargetSetting) == "IPHONEOS_DEPLOYMENT_TARGET" @@ -77,8 +64,10 @@ func projectSpecTests() { try expect(Version(2.1).deploymentTarget) == "2.1" } } + } - $0.describe("Validation") { + func testValidation() { + describe { let baseProject = Project(basePath: "", name: "", configs: [Config(name: "invalid")]) let invalidSettings = Settings( @@ -176,3 +165,18 @@ func projectSpecTests() { } } } + +fileprivate func expectValidationError(_ project: Project, _ expectedError: SpecValidationError.ValidationError, file: String = #file, line: Int = #line) throws { + do { + try project.validate() + } catch let error as SpecValidationError { + if !error.errors + .contains(where: { $0.description == expectedError.description }) { + throw failure("Supposed to fail with:\n\(expectedError)\nbut got:\n\(error.errors.map { $0.description }.joined(separator: "\n"))", file: file, line: line) + } + return + } catch { + throw failure("Supposed to fail with \"\(expectedError)\"", file: file, line: line) + } + throw failure("Supposed to fail with \"\(expectedError)\"", file: file, line: line) +} diff --git a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift new file mode 100644 index 00000000..4db14da8 --- /dev/null +++ b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift @@ -0,0 +1,541 @@ +import PathKit +import ProjectSpec +import Spectre +import XcodeGenKit +import xcproj +import XCTest +import Yams + +class SourceGeneratorTests: XCTestCase { + + func testSourceGenerator() { + describe { + + let directoryPath = Path("TestDirectory") + let outOfRootPath = Path("OtherDirectory") + + func createDirectories(_ directories: String) throws { + + let yaml = try Yams.load(yaml: directories)! + + func getFiles(_ file: Any, path: Path) -> [Path] { + if let array = file as? [Any] { + return array.flatMap { getFiles($0, path: path) } + } else if let string = file as? String { + return [path + string] + } else if let dictionary = file as? [String: Any] { + var array: [Path] = [] + for (key, value) in dictionary { + array += getFiles(value, path: path + key) + } + return array + } else { + return [] + } + } + + let files = getFiles(yaml, path: directoryPath).filter { $0.extension != nil } + for file in files { + try file.parent().mkpath() + try file.write("") + } + } + + func removeDirectories() { + try? directoryPath.delete() + try? outOfRootPath.delete() + } + + $0.before { + removeDirectories() + } + + $0.after { + removeDirectories() + } + + $0.it("generates source groups") { + let directories = """ + Sources: + A: + - a.swift + - B: + - b.swift + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + + let pbxProj = try project.generatePbxProj() + try pbxProj.expectFile(paths: ["Sources", "A", "a.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["Sources", "A", "B", "b.swift"], buildPhase: .sources) + } + + $0.it("supports frameworks in sources") { + let directories = """ + Sources: + - Foo.framework + - Bar.swift + """ + + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + let pbxProj = try project.generatePbxProj() + try pbxProj.expectFile(paths: ["Sources", "Bar.swift"], buildPhase: .sources) + let buildPhase = pbxProj.objects.copyFilesBuildPhases.referenceValues.first + try expect(buildPhase?.dstSubfolderSpec) == .frameworks + let fileReference = pbxProj.getFileReference( + paths: ["Sources", "Foo.framework"], + names: ["Sources", "Foo.framework"] + )?.reference ?? "" + let buildFile = pbxProj.objects.buildFiles.objectReferences + .first(where: { $0.object.fileRef == fileReference })?.reference ?? "" + try expect(buildPhase?.files.count) == 1 + try expect(buildPhase?.files.contains(buildFile)) == true + } + + $0.it("generates core data models") { + let directories = """ + Sources: + model.xcdatamodeld: + - model.xcdatamodel + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + + let pbxProj = try project.generatePbxProj() + guard let fileReference = pbxProj.objects.fileReferences.first(where: { $0.value.nameOrPath == "model.xcdatamodel" }) else { + throw failure("Couldn't find model file reference") + } + guard let versionGroup = pbxProj.objects.versionGroups.values.first else { + throw failure("Couldn't find version group") + } + try expect(versionGroup.currentVersion) == fileReference.key + try expect(versionGroup.children) == [fileReference.key] + try expect(versionGroup.path) == "model.xcdatamodeld" + try expect(fileReference.value.path) == "model.xcdatamodel" + } + + + $0.it("handles duplicate names") { + let directories = """ + Sources: + - a.swift + - a: + - a.swift + - a: + - a.swift + + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"]) + let project = Project( + basePath: directoryPath, + name: "Test", + targets: [target], + fileGroups: ["Sources"] + ) + + let pbxProj = try project.generatePbxProj() + try pbxProj.expectFile(paths: ["Sources", "a.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["Sources", "a", "a.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["Sources", "a", "a", "a.swift"], buildPhase: .sources) + } + + $0.it("renames sources") { + let directories = """ + Sources: + - a.swift + OtherSource: + - b.swift + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ + TargetSource(path: "Sources", name: "NewSource"), + TargetSource(path: "OtherSource/b.swift", name: "c.swift"), + ]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + + let pbxProj = try project.generatePbxProj() + try pbxProj.expectFile(paths: ["Sources", "a.swift"], names: ["NewSource", "a.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["OtherSource", "b.swift"], names: ["OtherSource", "c.swift"], buildPhase: .sources) + } + + $0.it("excludes sources") { + let directories = """ + Sources: + - A: + - a.swift + - B: + - b.swift + - b.ignored + - a.ignored + - B: + - b.swift + - D: + - d.h + - d.m + - E: + - e.jpg + - e.h + - e.m + - F: + - f.swift + - G: + - H: + - h.swift + - types: + - a.swift + - a.m + - a.h + - a.x + - numbers: + - file1.a + - file2.a + - file3.a + - file4.a + - partial: + - file_part + - ignore.file + - a.ignored + - project.xcodeproj: + - project.pbxproj + """ + try createDirectories(directories) + + let excludes = [ + "B", + "d.m", + "E/F/*.swift", + "G/H/", + "types/*.[hx]", + "numbers/file[2-3].a", + "partial/*_part", + "ignore.file", + "*.ignored", + "*.xcodeproj", + // not supported + // "**/*.ignored", + ] + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: [TargetSource(path: "Sources", excludes: excludes)]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + + let pbxProj = try project.generatePbxProj() + try pbxProj.expectFile(paths: ["Sources", "A", "a.swift"]) + try pbxProj.expectFile(paths: ["Sources", "D", "d.h"]) + try pbxProj.expectFile(paths: ["Sources", "D", "d.m"]) + try pbxProj.expectFile(paths: ["Sources", "E", "e.jpg"]) + try pbxProj.expectFile(paths: ["Sources", "E", "e.m"]) + try pbxProj.expectFile(paths: ["Sources", "E", "e.h"]) + try pbxProj.expectFile(paths: ["Sources", "types", "a.swift"]) + try pbxProj.expectFile(paths: ["Sources", "numbers", "file1.a"]) + try pbxProj.expectFile(paths: ["Sources", "numbers", "file4.a"]) + try pbxProj.expectFileMissing(paths: ["Sources", "B", "b.swift"]) + try pbxProj.expectFileMissing(paths: ["Sources", "E", "F", "f.swift"]) + try pbxProj.expectFileMissing(paths: ["Sources", "G", "H", "h.swift"]) + try pbxProj.expectFileMissing(paths: ["Sources", "types", "a.h"]) + try pbxProj.expectFileMissing(paths: ["Sources", "types", "a.x"]) + try pbxProj.expectFileMissing(paths: ["Sources", "numbers", "file2.a"]) + try pbxProj.expectFileMissing(paths: ["Sources", "numbers", "file3.a"]) + try pbxProj.expectFileMissing(paths: ["Sources", "partial", "file_part"]) + try pbxProj.expectFileMissing(paths: ["Sources", "a.ignored"]) + try pbxProj.expectFileMissing(paths: ["Sources", "ignore.file"]) + try pbxProj.expectFileMissing(paths: ["Sources", "project.xcodeproj"]) + } + + $0.it("generates file sources") { + let directories = """ + Sources: + A: + - a.swift + - Assets.xcassets + - B: + - b.swift + - c.jpg + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ + "Sources/A/a.swift", + "Sources/A/B/b.swift", + "Sources/A/Assets.xcassets", + "Sources/A/B/c.jpg", + ]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + + let pbxProj = try project.generatePbxProj() + try pbxProj.expectFile(paths: ["Sources/A", "a.swift"], names: ["A", "a.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["Sources/A/B", "b.swift"], names: ["B", "b.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["Sources/A/B", "c.jpg"], names: ["B", "c.jpg"], buildPhase: .resources) + try pbxProj.expectFile(paths: ["Sources/A", "Assets.xcassets"], names: ["A", "Assets.xcassets"], buildPhase: .resources) + } + + $0.it("generates shared sources") { + let directories = """ + Sources: + A: + - a.swift + - B: + - b.swift + - c.jpg + """ + try createDirectories(directories) + + let target1 = Target(name: "Test1", type: .framework, platform: .iOS, sources: ["Sources"]) + let target2 = Target(name: "Test2", type: .framework, platform: .tvOS, sources: ["Sources"]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target1, target2]) + + _ = try project.generatePbxProj() + // TODO: check there are build files for both targets + } + + $0.it("generates intermediate groups") { + + let directories = """ + Sources: + A: + - b.swift + F: + - G: + - h.swift + """ + try createDirectories(directories) + let outOfSourceFile = outOfRootPath + "C/D/e.swift" + try outOfSourceFile.parent().mkpath() + try outOfSourceFile.write("") + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ + "Sources/A/b.swift", + "Sources/F/G/h.swift", + "../OtherDirectory/C/D/e.swift", + ]) + let options = SpecOptions(createIntermediateGroups: true) + let project = Project(basePath: directoryPath, name: "Test", targets: [target], options: options) + + let pbxProj = try project.generatePbxProj() + try pbxProj.expectFile(paths: ["Sources", "A", "b.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["Sources", "F", "G", "h.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: [(outOfRootPath + "C/D").string, "e.swift"], names: ["D", "e.swift"], buildPhase: .sources) + } + + $0.it("generates folder references") { + let directories = """ + Sources: + A: + - a.resource + - b.resource + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ + TargetSource(path: "Sources/A", type: .folder), + ]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + + let pbxProj = try project.generatePbxProj() + try pbxProj.expectFile(paths: ["Sources/A"], names: ["A"], buildPhase: .resources) + try pbxProj.expectFileMissing(paths: ["Sources", "A", "a.swift"]) + } + + $0.it("adds files to correct build phase") { + let directories = """ + A: + - file.swift + - file.xcassets + - file.h + - Info.plist + - file.xcconfig + B: + - file.swift + - file.xcassets + - file.h + - Info.plist + - file.xcconfig + C: + - file.swift + - file.m + - file.mm + - file.cpp + - file.c + - file.S + - file.h + - file.hh + - file.hpp + - file.ipp + - file.tpp + - file.hxx + - file.def + - file.xcconfig + - file.entitlements + - file.gpx + - file.apns + - file.123 + - file.xcassets + - Info.plist + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .framework, platform: .iOS, sources: [ + TargetSource(path: "A", buildPhase: .resources), + TargetSource(path: "B", buildPhase: .none), + TargetSource(path: "C", buildPhase: nil), + ]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + + let pbxProj = try project.generatePbxProj() + try pbxProj.expectFile(paths: ["A", "file.swift"], buildPhase: .resources) + try pbxProj.expectFile(paths: ["A", "file.xcassets"], buildPhase: .resources) + try pbxProj.expectFile(paths: ["A", "file.h"], buildPhase: .resources) + try pbxProj.expectFile(paths: ["A", "Info.plist"], buildPhase: .none) + try pbxProj.expectFile(paths: ["A", "file.xcconfig"], buildPhase: .resources) + + try pbxProj.expectFile(paths: ["B", "file.swift"], buildPhase: .none) + try pbxProj.expectFile(paths: ["B", "file.xcassets"], buildPhase: .none) + try pbxProj.expectFile(paths: ["B", "file.h"], buildPhase: .none) + try pbxProj.expectFile(paths: ["B", "Info.plist"], buildPhase: .none) + try pbxProj.expectFile(paths: ["B", "file.xcconfig"], buildPhase: .none) + + try pbxProj.expectFile(paths: ["C", "file.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["C", "file.m"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["C", "file.mm"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["C", "file.cpp"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["C", "file.c"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["C", "file.S"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["C", "file.h"], buildPhase: .headers) + try pbxProj.expectFile(paths: ["C", "file.hh"], buildPhase: .headers) + try pbxProj.expectFile(paths: ["C", "file.hpp"], buildPhase: .headers) + try pbxProj.expectFile(paths: ["C", "file.ipp"], buildPhase: .headers) + try pbxProj.expectFile(paths: ["C", "file.tpp"], buildPhase: .headers) + try pbxProj.expectFile(paths: ["C", "file.hxx"], buildPhase: .headers) + try pbxProj.expectFile(paths: ["C", "file.def"], buildPhase: .headers) + try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: .none) + try pbxProj.expectFile(paths: ["C", "file.entitlements"], buildPhase: .none) + try pbxProj.expectFile(paths: ["C", "file.gpx"], buildPhase: .none) + try pbxProj.expectFile(paths: ["C", "file.apns"], buildPhase: .none) + try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: .none) + try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: .none) + try pbxProj.expectFile(paths: ["C", "file.xcconfig"], buildPhase: .none) + try pbxProj.expectFile(paths: ["C", "file.xcassets"], buildPhase: .resources) + try pbxProj.expectFile(paths: ["C", "file.123"], buildPhase: .resources) + try pbxProj.expectFile(paths: ["C", "Info.plist"], buildPhase: .none) + } + + $0.it("duplicate TargetSource is included once in sources build phase") { + let directories = """ + Sources: + A: + - a.swift + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ + "Sources/A/a.swift", + "Sources/A/a.swift", + ]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + + let pbxProj = try project.generatePbxProj() + try pbxProj.expectFile(paths: ["Sources/A", "a.swift"], names: ["A", "a.swift"], buildPhase: .sources) + + let sourcesBuildPhase = pbxProj.objects.buildPhases + .first(where: { $0.1.buildPhase == BuildPhase.sources })! + .value + + try expect(sourcesBuildPhase.files.count) == 1 + } + } + } +} + +extension PBXProj { + + /// expect a file within groups of the paths, using optional different names + func expectFile(paths: [String], names: [String]? = nil, buildPhase: TargetSource.BuildPhase? = nil, file: String = #file, line: Int = #line) throws { + guard let fileReference = getFileReference(paths: paths, names: names ?? paths) else { + var error = "Could not find file at path \(paths.joined(separator: "/").quoted)" + if let names = names, names != paths { + error += " and name \(names.joined(separator: "/").quoted)" + } + throw failure(error, file: file, line: line) + } + + if let buildPhase = buildPhase { + let buildFile = objects.buildFiles.objectReferences + .first(where: { $0.object.fileRef == fileReference.reference }) + let actualBuildPhase = buildFile + .flatMap { buildFile in objects.buildPhases.referenceValues.first { $0.files.contains(buildFile.reference) } }?.buildPhase + + var error: String? + if let buildPhase = buildPhase.buildPhase { + if actualBuildPhase != buildPhase { + if let actualBuildPhase = actualBuildPhase { + error = "is in the \(actualBuildPhase.rawValue) build phase instead of the expected \(buildPhase.rawValue.quoted)" + } else { + error = "isn't in a build phase when it's expected to be in \(buildPhase.rawValue.quoted)" + } + } + } else if let actualBuildPhase = actualBuildPhase { + error = "is in the \(actualBuildPhase.rawValue.quoted) build phase when it's expected to not be in any" + } + if let error = error { + throw failure("File \(paths.joined(separator: "/").quoted) \(error)", file: file, line: line) + } + } + } + + /// expect a missing file within groups of the paths, using optional different names + func expectFileMissing(paths: [String], names: [String]? = nil, file: String = #file, line: Int = #line) throws { + let names = names ?? paths + if getFileReference(paths: paths, names: names) != nil { + throw failure("Found unexpected file at path \(paths.joined(separator: "/").quoted) and name \(paths.joined(separator: "/").quoted)", file: file, line: line) + } + } + + func getFileReference(paths: [String], names: [String], file: String = #file, line: Int = #line) -> ObjectReference? { + guard let project = objects.projects.first?.value else { return nil } + guard let mainGroup = objects.groups.getReference(project.mainGroup) else { return nil } + + return getFileReference(group: mainGroup, paths: paths, names: names) + } + + func getMainGroup(function: String = #function, file: String = #file, line: Int = #line) throws -> PBXGroup { + guard let project = objects.projects.first?.value else { + throw failure("Couldn't find project", file: file, line: line) + } + guard let mainGroup = objects.groups.getReference(project.mainGroup) else { + throw failure("Couldn't find main group", file: file, line: line) + } + return mainGroup + } + + private func getFileReference(group: PBXGroup, paths: [String], names: [String]) -> ObjectReference? { + + guard !paths.isEmpty else { return nil } + let path = paths.first! + let name = names.first! + let restOfPath = Array(paths.dropFirst()) + let restOfName = Array(names.dropFirst()) + if restOfPath.isEmpty { + let fileReferences: [ObjectReference] = group.children.compactMap { reference in + if let fileReference = self.objects.fileReferences.getReference(reference) { + return ObjectReference(reference: reference, object: fileReference) + } else { + return nil + } + } + return fileReferences.first { $0.object.path == path && $0.object.nameOrPath == name } + } else { + let groups = group.children.compactMap { self.objects.groups.getReference($0) } + guard let group = groups.first(where: { $0.path == path && $0.nameOrPath == name }) else { return nil } + return getFileReference(group: group, paths: restOfPath, names: restOfName) + } + } +} diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index 86030ec9..ef2ed82c 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -4,417 +4,428 @@ import ProjectSpec import Spectre import XcodeGenKit import xcproj +import XCTest -func projectLoadingTests() { +class SpecLoadingTests: XCTestCase { - @discardableResult - func getProjectSpec(_ project: [String: Any]) throws -> Project { - var projectDictionary: [String: Any] = ["name": "test"] - for (key, value) in project { - projectDictionary[key] = value - } - return try Project(basePath: "", jsonDictionary: projectDictionary) - } + func testSpecLoader() { + describe { + $0.it("merges includes") { + let path = fixturePath + "include_test.yml" + let project = try Project(path: path) - func expectSpecError(_ project: [String: Any], _ expectedError: SpecParsingError) throws { - try expectError(expectedError) { - try getProjectSpec(project) - } - } + try expect(project.name) == "NewName" + try expect(project.settingGroups) == [ + "test": Settings(dictionary: ["MY_SETTING1": "NEW VALUE", "MY_SETTING2": "VALUE2", "MY_SETTING3": "VALUE3"]), + "new": Settings(dictionary: ["MY_SETTING": "VALUE"]), + "toReplace": Settings(dictionary: ["MY_SETTING2": "VALUE2"]), + ] + try expect(project.targets) == [ + Target(name: "IncludedTargetNew", type: .application, platform: .tvOS, sources: ["NewSource"]), + Target(name: "NewTarget", type: .application, platform: .iOS), + ] + } - func expectTargetError(_ target: [String: Any], _ expectedError: SpecParsingError) throws { - try expectError(expectedError) { - _ = try Target(name: "test", jsonDictionary: target) - } - } - - let validTarget: [String: Any] = ["type": "application", "platform": "iOS"] - let invalid = "invalid" - - describe("Spec Loader") { - $0.it("merges includes") { - let path = fixturePath + "include_test.yml" - let project = try Project(path: path) - - try expect(project.name) == "NewName" - try expect(project.settingGroups) == [ - "test": Settings(dictionary: ["MY_SETTING1": "NEW VALUE", "MY_SETTING2": "VALUE2", "MY_SETTING3": "VALUE3"]), - "new": Settings(dictionary: ["MY_SETTING": "VALUE"]), - "toReplace": Settings(dictionary: ["MY_SETTING2": "VALUE2"]), - ] - try expect(project.targets) == [ - Target(name: "IncludedTargetNew", type: .application, platform: .tvOS, sources: ["NewSource"]), - Target(name: "NewTarget", type: .application, platform: .iOS), - ] - } - - $0.it("parses yaml types") { - let path = fixturePath + "yaml.yml" - let dictionary = try loadYamlDictionary(path: path) - let expectedDictionary: [String: Any] = [ - "true": true, - "false": false, - "yes": true, - "no": false, - "yesQuote": "YES", - "noQuote": "NO", - "int": 1, - "intQuote": "1", - "float": 3.2, - "floatQuote": "10.10", - "string": "hello", - "stringQuote": "hello", - "space": " ", - "empty": "", - "emptyQuote": "", - "emptyDictionary": [String: Any](), - "arrayLiteral": [1, 2], - "arrayList": [1, 2], - ] - for (key, expectedValue) in expectedDictionary { - guard let parsedValue = dictionary[key] else { - throw failure("\(key) does not exist") + $0.it("parses yaml types") { + let path = fixturePath + "yaml.yml" + let dictionary = try loadYamlDictionary(path: path) + let expectedDictionary: [String: Any] = [ + "true": true, + "false": false, + "yes": true, + "no": false, + "yesQuote": "YES", + "noQuote": "NO", + "int": 1, + "intQuote": "1", + "float": 3.2, + "floatQuote": "10.10", + "string": "hello", + "stringQuote": "hello", + "space": " ", + "empty": "", + "emptyQuote": "", + "emptyDictionary": [String: Any](), + "arrayLiteral": [1, 2], + "arrayList": [1, 2], + ] + for (key, expectedValue) in expectedDictionary { + guard let parsedValue = dictionary[key] else { + throw failure("\(key) does not exist") + } + if String(describing: expectedValue) != String(describing: parsedValue) { + throw failure("\(key): \(parsedValue) does not equal \(expectedValue)") + } } - if String(describing: expectedValue) != String(describing: parsedValue) { - throw failure("\(key): \(parsedValue) does not equal \(expectedValue)") + if !(dictionary as NSDictionary).isEqual(expectedDictionary) { + throw failure("parsed yaml types don't match:\n\nParsed:\n\t\(dictionary.map { "\($0.key): \($0.value)" }.joined(separator: "\n\t"))\nExpected:\n\t\(expectedDictionary.map { "\($0.key): \($0.value)" }.joined(separator: "\n\t"))") } } - if !(dictionary as NSDictionary).isEqual(expectedDictionary) { - throw failure("parsed yaml types don't match:\n\nParsed:\n\t\(dictionary.map { "\($0.key): \($0.value)" }.joined(separator: "\n\t"))\nExpected:\n\t\(expectedDictionary.map { "\($0.key): \($0.value)" }.joined(separator: "\n\t"))") + } + } + + func testSpecLoaderLoadingJSON() { + describe { + $0.it("merges includes") { + let path = fixturePath + "include_test.json" + let project = try Project(path: path) + + try expect(project.name) == "NewName" + try expect(project.settingGroups) == [ + "test": Settings(dictionary: ["MY_SETTING1": "NEW VALUE", "MY_SETTING2": "VALUE2", "MY_SETTING3": "VALUE3"]), + "new": Settings(dictionary: ["MY_SETTING": "VALUE"]), + "toReplace": Settings(dictionary: ["MY_SETTING2": "VALUE2"]), + ] + try expect(project.targets) == [ + Target(name: "IncludedTargetNew", type: .application, platform: .tvOS, sources: ["NewSource"]), + Target(name: "NewTarget", type: .application, platform: .iOS), + ] } } } - describe("Spec Loader JSON") { - $0.it("merges includes") { - let path = fixturePath + "include_test.json" - let project = try Project(path: path) + func testProjectSpecParser() { + let validTarget: [String: Any] = ["type": "application", "platform": "iOS"] + let invalid = "invalid" - try expect(project.name) == "NewName" - try expect(project.settingGroups) == [ - "test": Settings(dictionary: ["MY_SETTING1": "NEW VALUE", "MY_SETTING2": "VALUE2", "MY_SETTING3": "VALUE3"]), - "new": Settings(dictionary: ["MY_SETTING": "VALUE"]), - "toReplace": Settings(dictionary: ["MY_SETTING2": "VALUE2"]), - ] - try expect(project.targets) == [ - Target(name: "IncludedTargetNew", type: .application, platform: .tvOS, sources: ["NewSource"]), - Target(name: "NewTarget", type: .application, platform: .iOS), - ] - } - } + describe { - describe("Project Spec Parser") { + $0.it("fails with incorrect platform") { + var target = validTarget + target["platform"] = invalid + try expectTargetError(target, .unknownTargetPlatform(invalid)) + } - $0.it("fails with incorrect platform") { - var target = validTarget - target["platform"] = invalid - try expectTargetError(target, .unknownTargetPlatform(invalid)) - } + $0.it("fails with incorrect product type") { + var target = validTarget + target["type"] = invalid + try expectTargetError(target, .unknownTargetType(invalid)) + } - $0.it("fails with incorrect product type") { - var target = validTarget - target["type"] = invalid - try expectTargetError(target, .unknownTargetType(invalid)) - } + $0.it("fails with invalid dependency") { + var target = validTarget + target["dependencies"] = [[invalid: "name"]] + try expectTargetError(target, .invalidDependency([invalid: "name"])) + } - $0.it("fails with invalid dependency") { - var target = validTarget - target["dependencies"] = [[invalid: "name"]] - try expectTargetError(target, .invalidDependency([invalid: "name"])) - } + $0.it("parses sources") { + var targetDictionary1 = validTarget + targetDictionary1["sources"] = [ + "sourceString", + ["path": "sourceObject"], + ["path": "sourceWithFlagsArray", "compilerFlags": ["-Werror"]], + ["path": "sourceWithFlagsString", "compilerFlags": "-Werror -Wextra"], + ["path": "sourceWithExcludes", "excludes": ["Foo.swift"]], + ["path": "sourceWithFileType", "type": "file"], + ["path": "sourceWithGroupType", "type": "group"], + ["path": "sourceWithFolderType", "type": "folder"], + ] + var targetDictionary2 = validTarget + targetDictionary2["sources"] = "source3" - $0.it("parses sources") { - var targetDictionary1 = validTarget - targetDictionary1["sources"] = [ - "sourceString", - ["path": "sourceObject"], - ["path": "sourceWithFlagsArray", "compilerFlags": ["-Werror"]], - ["path": "sourceWithFlagsString", "compilerFlags": "-Werror -Wextra"], - ["path": "sourceWithExcludes", "excludes": ["Foo.swift"]], - ["path": "sourceWithFileType", "type": "file"], - ["path": "sourceWithGroupType", "type": "group"], - ["path": "sourceWithFolderType", "type": "folder"], - ] - var targetDictionary2 = validTarget - targetDictionary2["sources"] = "source3" + let target1 = try Target(name: "test", jsonDictionary: targetDictionary1) + let target2 = try Target(name: "test", jsonDictionary: targetDictionary2) - let target1 = try Target(name: "test", jsonDictionary: targetDictionary1) - let target2 = try Target(name: "test", jsonDictionary: targetDictionary2) + let target1SourcesExpect = [ + TargetSource(path: "sourceString"), + TargetSource(path: "sourceObject"), + TargetSource(path: "sourceWithFlagsArray", compilerFlags: ["-Werror"]), + TargetSource(path: "sourceWithFlagsString", compilerFlags: ["-Werror", "-Wextra"]), + TargetSource(path: "sourceWithExcludes", excludes: ["Foo.swift"]), + TargetSource(path: "sourceWithFileType", type: .file), + TargetSource(path: "sourceWithGroupType", type: .group), + TargetSource(path: "sourceWithFolderType", type: .folder), + ] - let target1SourcesExpect = [ - TargetSource(path: "sourceString"), - TargetSource(path: "sourceObject"), - TargetSource(path: "sourceWithFlagsArray", compilerFlags: ["-Werror"]), - TargetSource(path: "sourceWithFlagsString", compilerFlags: ["-Werror", "-Wextra"]), - TargetSource(path: "sourceWithExcludes", excludes: ["Foo.swift"]), - TargetSource(path: "sourceWithFileType", type: .file), - TargetSource(path: "sourceWithGroupType", type: .group), - TargetSource(path: "sourceWithFolderType", type: .folder), - ] + try expect(target1.sources) == target1SourcesExpect + try expect(target2.sources) == ["source3"] + } - try expect(target1.sources) == target1SourcesExpect - try expect(target2.sources) == ["source3"] - } + $0.it("parses target dependencies") { + var targetDictionary = validTarget + targetDictionary["dependencies"] = [ + ["target": "name", "embed": false], + ["carthage": "name"], + ["framework": "path"], + ] + let target = try Target(name: "test", jsonDictionary: targetDictionary) + try expect(target.dependencies.count) == 3 + try expect(target.dependencies[0]) == Dependency(type: .target, reference: "name", embed: false) + try expect(target.dependencies[1]) == Dependency(type: .carthage, reference: "name") + try expect(target.dependencies[2]) == Dependency(type: .framework, reference: "path") + } - $0.it("parses target dependencies") { - var targetDictionary = validTarget - targetDictionary["dependencies"] = [ - ["target": "name", "embed": false], - ["carthage": "name"], - ["framework": "path"], - ] - let target = try Target(name: "test", jsonDictionary: targetDictionary) - try expect(target.dependencies.count) == 3 - try expect(target.dependencies[0]) == Dependency(type: .target, reference: "name", embed: false) - try expect(target.dependencies[1]) == Dependency(type: .carthage, reference: "name") - try expect(target.dependencies[2]) == Dependency(type: .framework, reference: "path") - } + $0.it("parses cross platform targets") { + let targetDictionary: [String: Any] = [ + "platform": ["iOS", "tvOS"], + "type": "framework", + "sources": ["Framework", "Framework $platform"], + "settings": ["SETTING": "value_$platform"], + ] - $0.it("parses cross platform targets") { - let targetDictionary: [String: Any] = [ - "platform": ["iOS", "tvOS"], - "type": "framework", - "sources": ["Framework", "Framework $platform"], - "settings": ["SETTING": "value_$platform"], - ] + let project = try getProjectSpec(["targets": ["Framework": targetDictionary]]) + var target_iOS = Target(name: "Framework_iOS", type: .framework, platform: .iOS) + var target_tvOS = Target(name: "Framework_tvOS", type: .framework, platform: .tvOS) - let project = try getProjectSpec(["targets": ["Framework": targetDictionary]]) - var target_iOS = Target(name: "Framework_iOS", type: .framework, platform: .iOS) - var target_tvOS = Target(name: "Framework_tvOS", type: .framework, platform: .tvOS) + target_iOS.sources = ["Framework", "Framework iOS"] + target_tvOS.sources = ["Framework", "Framework tvOS"] + target_iOS.settings = ["PRODUCT_NAME": "Framework", "SETTING": "value_iOS"] + target_tvOS.settings = ["PRODUCT_NAME": "Framework", "SETTING": "value_tvOS"] - target_iOS.sources = ["Framework", "Framework iOS"] - target_tvOS.sources = ["Framework", "Framework tvOS"] - target_iOS.settings = ["PRODUCT_NAME": "Framework", "SETTING": "value_iOS"] - target_tvOS.settings = ["PRODUCT_NAME": "Framework", "SETTING": "value_tvOS"] + try expect(project.targets.count) == 2 + try expect(project.targets) == [target_iOS, target_tvOS] + } - try expect(project.targets.count) == 2 - try expect(project.targets) == [target_iOS, target_tvOS] - } - - $0.it("parses target schemes") { - var targetDictionary = validTarget - targetDictionary["scheme"] = [ - "testTargets": ["t1", "t2"], - "configVariants": ["dev", "app-store"], - "commandLineArguments": [ - "ENV1": true, - ], - "gatherCoverageData": true, - "environmentVariables": [ - "TEST_VAR": "TEST_VAL", - ], - "preActions": [ - [ - "script": "dothing", - "name": "Do Thing", - "settingsTarget": "test", + $0.it("parses target schemes") { + var targetDictionary = validTarget + targetDictionary["scheme"] = [ + "testTargets": ["t1", "t2"], + "configVariants": ["dev", "app-store"], + "commandLineArguments": [ + "ENV1": true, ], - ], - "postActions": [ - [ - "script": "hello", - ], - ], - ] - - let target = try Target(name: "test", jsonDictionary: targetDictionary) - - let scheme = TargetScheme( - testTargets: ["t1", "t2"], - configVariants: ["dev", "app-store"], - gatherCoverageData: true, - commandLineArguments: ["ENV1": true], - environmentVariables: [XCScheme.EnvironmentVariable(variable: "TEST_VAR", value: "TEST_VAL", enabled: true)], - preActions: [.init(name: "Do Thing", script: "dothing", settingsTarget: "test")], - postActions: [.init(name: "Run Script", script: "hello")] - ) - - try expect(target.scheme) == scheme - } - - $0.it("parses schemes") { - let schemeDictionary: [String: Any] = [ - "build": [ - "parallelizeBuild": false, - "buildImplicitDependencies": false, - "targets": [ - "Target1": "all", - "Target2": "testing", - "Target3": "none", - "Target4": ["testing": true], - "Target5": ["testing": false], - "Target6": ["test", "analyze"], + "gatherCoverageData": true, + "environmentVariables": [ + "TEST_VAR": "TEST_VAL", ], "preActions": [ [ - "script": "echo Before Build", - "name": "Before Build", - "settingsTarget": "Target1", + "script": "dothing", + "name": "Do Thing", + "settingsTarget": "test", ], ], - ], - ] - let scheme = try Scheme(name: "Scheme", jsonDictionary: schemeDictionary) - let expectedTargets: [Scheme.BuildTarget] = [ - Scheme.BuildTarget(target: "Target1", buildTypes: BuildType.all), - Scheme.BuildTarget(target: "Target2", buildTypes: [.testing, .analyzing]), - Scheme.BuildTarget(target: "Target3", buildTypes: []), - Scheme.BuildTarget(target: "Target4", buildTypes: [.testing]), - Scheme.BuildTarget(target: "Target5", buildTypes: []), - Scheme.BuildTarget(target: "Target6", buildTypes: [.testing, .analyzing]), - ] - try expect(scheme.name) == "Scheme" - try expect(scheme.build.targets) == expectedTargets - try expect(scheme.build.preActions.first?.script) == "echo Before Build" - try expect(scheme.build.preActions.first?.name) == "Before Build" - try expect(scheme.build.preActions.first?.settingsTarget) == "Target1" - - try expect(scheme.build.parallelizeBuild) == false - try expect(scheme.build.buildImplicitDependencies) == false - } - - $0.it("parses schemes variables") { - let schemeDictionary: [String: Any] = [ - "build": [ - "targets": ["Target1": "all"], - ], - "run": [ - "environmentVariables": [ - ["variable": "BOOL_TRUE", "value": true], - ["variable": "BOOL_YES", "value": "YES"], - ["variable": "ENVIRONMENT", "value": "VARIABLE"], - ["variable": "OTHER_ENV_VAR", "value": "VAL", "isEnabled": false], + "postActions": [ + [ + "script": "hello", + ], ], - ], - "test": [ - "environmentVariables": [ - "BOOL_TRUE": true, - "BOOL_YES": "YES", - "TEST": "VARIABLE", - ], - ], - "profile": [ - "config": "Release", - ], - ] - - let scheme = try Scheme(name: "Scheme", jsonDictionary: schemeDictionary) - - let expectedRunVariables = [ - XCScheme.EnvironmentVariable(variable: "BOOL_TRUE", value: "YES", enabled: true), - XCScheme.EnvironmentVariable(variable: "BOOL_YES", value: "YES", enabled: true), - XCScheme.EnvironmentVariable(variable: "ENVIRONMENT", value: "VARIABLE", enabled: true), - XCScheme.EnvironmentVariable(variable: "OTHER_ENV_VAR", value: "VAL", enabled: false), - ] - - let expectedTestVariables = [ - XCScheme.EnvironmentVariable(variable: "BOOL_TRUE", value: "YES", enabled: true), - XCScheme.EnvironmentVariable(variable: "BOOL_YES", value: "YES", enabled: true), - XCScheme.EnvironmentVariable(variable: "TEST", value: "VARIABLE", enabled: true), - ] - - try expect(scheme.run?.environmentVariables) == expectedRunVariables - try expect(scheme.test?.environmentVariables) == expectedTestVariables - try expect(scheme.profile?.config) == "Release" - try expect(scheme.profile?.environmentVariables.isEmpty) == true - } - - $0.it("parses settings") { - let project = try Project(path: fixturePath + "settings_test.yml") - let buildSettings: BuildSettings = ["SETTING": "value"] - let configSettings: [String: Settings] = ["config1": Settings(buildSettings: ["SETTING1": "value"])] - let groups = ["preset1"] - - let preset1 = Settings(buildSettings: buildSettings, configSettings: [:], groups: []) - let preset2 = Settings(buildSettings: [:], configSettings: configSettings, groups: []) - let preset3 = Settings(buildSettings: buildSettings, configSettings: configSettings, groups: []) - let preset4 = Settings(buildSettings: buildSettings, configSettings: [:], groups: []) - let preset5 = Settings(buildSettings: buildSettings, configSettings: [:], groups: groups) - let preset6 = Settings(buildSettings: buildSettings, configSettings: configSettings, groups: groups) - let preset7 = Settings(buildSettings: buildSettings, configSettings: ["config1": Settings(buildSettings: buildSettings, groups: groups)]) - let preset8 = Settings(buildSettings: [:], configSettings: ["config1": Settings(configSettings: configSettings)]) - - try expect(project.settingGroups.count) == 8 - try expect(project.settingGroups["preset1"]) == preset1 - try expect(project.settingGroups["preset2"]) == preset2 - try expect(project.settingGroups["preset3"]) == preset3 - try expect(project.settingGroups["preset4"]) == preset4 - try expect(project.settingGroups["preset5"]) == preset5 - try expect(project.settingGroups["preset6"]) == preset6 - try expect(project.settingGroups["preset7"]) == preset7 - try expect(project.settingGroups["preset8"]) == preset8 - } - - $0.it("parses run scripts") { - var target = validTarget - let scripts: [[String: Any]] = [ - ["path": "script.sh"], - ["script": "shell script\ndo thing", "name": "myscript", "inputFiles": ["file", "file2"], "outputFiles": ["file", "file2"], "shell": "bin/customshell", "runOnlyWhenInstalling": true], - ] - target["prebuildScripts"] = scripts - target["postbuildScripts"] = scripts - - let expectedScripts = [ - BuildScript(script: .path("script.sh")), - BuildScript(script: .script("shell script\ndo thing"), name: "myscript", inputFiles: ["file", "file2"], outputFiles: ["file", "file2"], shell: "bin/customshell", runOnlyWhenInstalling: true), - ] - - let parsedTarget = try Target(name: "test", jsonDictionary: target) - try expect(parsedTarget.prebuildScripts) == expectedScripts - try expect(parsedTarget.postbuildScripts) == expectedScripts - } - - $0.it("parses build rules") { - var target = validTarget - let buildRules: [[String: Any]] = [ - [ - "name": "My Rule", - "script": "my script", - "filePattern": "*.swift", - "outputFiles": ["file1", "file2"], - "outputFilesCompilerFlags": ["-a","-b"] - ], - [ - "compilerSpec": "apple.tool", - "fileType": "sourcecode.swift", - ] - ] - target["buildRules"] = buildRules - - let expectedBuildRules = [ - BuildRule(fileType: .pattern("*.swift"), action: .script("my script"), name: "My Rule", outputFiles: ["file1", "file2"], outputFilesCompilerFlags: ["-a","-b"]), - BuildRule(fileType: .type("sourcecode.swift"), action: .compilerSpec("apple.tool")), ] - let parsedTarget = try Target(name: "test", jsonDictionary: target) - try expect(parsedTarget.buildRules) == expectedBuildRules - } + let target = try Target(name: "test", jsonDictionary: targetDictionary) - $0.it("parses options") { - let options = SpecOptions( - carthageBuildPath: "../Carthage/Build", - carthageExecutablePath: "../bin/carthage", - createIntermediateGroups: true, - bundleIdPrefix: "com.test", - developmentLanguage: "ja", - deploymentTarget: DeploymentTarget( - iOS: "11.1", - tvOS: "10.0", - watchOS: "3.0", - macOS: "10.12.1" + let scheme = TargetScheme( + testTargets: ["t1", "t2"], + configVariants: ["dev", "app-store"], + gatherCoverageData: true, + commandLineArguments: ["ENV1": true], + environmentVariables: [XCScheme.EnvironmentVariable(variable: "TEST_VAR", value: "TEST_VAL", enabled: true)], + preActions: [.init(name: "Do Thing", script: "dothing", settingsTarget: "test")], + postActions: [.init(name: "Run Script", script: "hello")] ) - ) - let expected = Project(basePath: "", name: "test", options: options) - let dictionary: [String: Any] = ["options": [ - "carthageBuildPath": "../Carthage/Build", - "carthageExecutablePath": "../bin/carthage", - "bundleIdPrefix": "com.test", - "createIntermediateGroups": true, - "developmentLanguage": "ja", - "deploymentTarget": ["iOS": 11.1, "tvOS": 10.0, "watchOS": "3", "macOS": "10.12.1"], - ]] - let parsedSpec = try getProjectSpec(dictionary) - try expect(parsedSpec) == expected + + try expect(target.scheme) == scheme + } + + $0.it("parses schemes") { + let schemeDictionary: [String: Any] = [ + "build": [ + "parallelizeBuild": false, + "buildImplicitDependencies": false, + "targets": [ + "Target1": "all", + "Target2": "testing", + "Target3": "none", + "Target4": ["testing": true], + "Target5": ["testing": false], + "Target6": ["test", "analyze"], + ], + "preActions": [ + [ + "script": "echo Before Build", + "name": "Before Build", + "settingsTarget": "Target1", + ], + ], + ], + ] + let scheme = try Scheme(name: "Scheme", jsonDictionary: schemeDictionary) + let expectedTargets: [Scheme.BuildTarget] = [ + Scheme.BuildTarget(target: "Target1", buildTypes: BuildType.all), + Scheme.BuildTarget(target: "Target2", buildTypes: [.testing, .analyzing]), + Scheme.BuildTarget(target: "Target3", buildTypes: []), + Scheme.BuildTarget(target: "Target4", buildTypes: [.testing]), + Scheme.BuildTarget(target: "Target5", buildTypes: []), + Scheme.BuildTarget(target: "Target6", buildTypes: [.testing, .analyzing]), + ] + try expect(scheme.name) == "Scheme" + try expect(scheme.build.targets) == expectedTargets + try expect(scheme.build.preActions.first?.script) == "echo Before Build" + try expect(scheme.build.preActions.first?.name) == "Before Build" + try expect(scheme.build.preActions.first?.settingsTarget) == "Target1" + + try expect(scheme.build.parallelizeBuild) == false + try expect(scheme.build.buildImplicitDependencies) == false + } + + $0.it("parses schemes variables") { + let schemeDictionary: [String: Any] = [ + "build": [ + "targets": ["Target1": "all"], + ], + "run": [ + "environmentVariables": [ + ["variable": "BOOL_TRUE", "value": true], + ["variable": "BOOL_YES", "value": "YES"], + ["variable": "ENVIRONMENT", "value": "VARIABLE"], + ["variable": "OTHER_ENV_VAR", "value": "VAL", "isEnabled": false], + ], + ], + "test": [ + "environmentVariables": [ + "BOOL_TRUE": true, + "BOOL_YES": "YES", + "TEST": "VARIABLE", + ], + ], + "profile": [ + "config": "Release", + ], + ] + + let scheme = try Scheme(name: "Scheme", jsonDictionary: schemeDictionary) + + let expectedRunVariables = [ + XCScheme.EnvironmentVariable(variable: "BOOL_TRUE", value: "YES", enabled: true), + XCScheme.EnvironmentVariable(variable: "BOOL_YES", value: "YES", enabled: true), + XCScheme.EnvironmentVariable(variable: "ENVIRONMENT", value: "VARIABLE", enabled: true), + XCScheme.EnvironmentVariable(variable: "OTHER_ENV_VAR", value: "VAL", enabled: false), + ] + + let expectedTestVariables = [ + XCScheme.EnvironmentVariable(variable: "BOOL_TRUE", value: "YES", enabled: true), + XCScheme.EnvironmentVariable(variable: "BOOL_YES", value: "YES", enabled: true), + XCScheme.EnvironmentVariable(variable: "TEST", value: "VARIABLE", enabled: true), + ] + + try expect(scheme.run?.environmentVariables) == expectedRunVariables + try expect(scheme.test?.environmentVariables) == expectedTestVariables + try expect(scheme.profile?.config) == "Release" + try expect(scheme.profile?.environmentVariables.isEmpty) == true + } + + $0.it("parses settings") { + let project = try Project(path: fixturePath + "settings_test.yml") + let buildSettings: BuildSettings = ["SETTING": "value"] + let configSettings: [String: Settings] = ["config1": Settings(buildSettings: ["SETTING1": "value"])] + let groups = ["preset1"] + + let preset1 = Settings(buildSettings: buildSettings, configSettings: [:], groups: []) + let preset2 = Settings(buildSettings: [:], configSettings: configSettings, groups: []) + let preset3 = Settings(buildSettings: buildSettings, configSettings: configSettings, groups: []) + let preset4 = Settings(buildSettings: buildSettings, configSettings: [:], groups: []) + let preset5 = Settings(buildSettings: buildSettings, configSettings: [:], groups: groups) + let preset6 = Settings(buildSettings: buildSettings, configSettings: configSettings, groups: groups) + let preset7 = Settings(buildSettings: buildSettings, configSettings: ["config1": Settings(buildSettings: buildSettings, groups: groups)]) + let preset8 = Settings(buildSettings: [:], configSettings: ["config1": Settings(configSettings: configSettings)]) + + try expect(project.settingGroups.count) == 8 + try expect(project.settingGroups["preset1"]) == preset1 + try expect(project.settingGroups["preset2"]) == preset2 + try expect(project.settingGroups["preset3"]) == preset3 + try expect(project.settingGroups["preset4"]) == preset4 + try expect(project.settingGroups["preset5"]) == preset5 + try expect(project.settingGroups["preset6"]) == preset6 + try expect(project.settingGroups["preset7"]) == preset7 + try expect(project.settingGroups["preset8"]) == preset8 + } + + $0.it("parses run scripts") { + var target = validTarget + let scripts: [[String: Any]] = [ + ["path": "script.sh"], + ["script": "shell script\ndo thing", "name": "myscript", "inputFiles": ["file", "file2"], "outputFiles": ["file", "file2"], "shell": "bin/customshell", "runOnlyWhenInstalling": true], + ] + target["prebuildScripts"] = scripts + target["postbuildScripts"] = scripts + + let expectedScripts = [ + BuildScript(script: .path("script.sh")), + BuildScript(script: .script("shell script\ndo thing"), name: "myscript", inputFiles: ["file", "file2"], outputFiles: ["file", "file2"], shell: "bin/customshell", runOnlyWhenInstalling: true), + ] + + let parsedTarget = try Target(name: "test", jsonDictionary: target) + try expect(parsedTarget.prebuildScripts) == expectedScripts + try expect(parsedTarget.postbuildScripts) == expectedScripts + } + + $0.it("parses build rules") { + var target = validTarget + let buildRules: [[String: Any]] = [ + [ + "name": "My Rule", + "script": "my script", + "filePattern": "*.swift", + "outputFiles": ["file1", "file2"], + "outputFilesCompilerFlags": ["-a", "-b"], + ], + [ + "compilerSpec": "apple.tool", + "fileType": "sourcecode.swift", + ], + ] + target["buildRules"] = buildRules + + let expectedBuildRules = [ + BuildRule(fileType: .pattern("*.swift"), action: .script("my script"), name: "My Rule", outputFiles: ["file1", "file2"], outputFilesCompilerFlags: ["-a", "-b"]), + BuildRule(fileType: .type("sourcecode.swift"), action: .compilerSpec("apple.tool")), + ] + + let parsedTarget = try Target(name: "test", jsonDictionary: target) + try expect(parsedTarget.buildRules) == expectedBuildRules + } + + $0.it("parses options") { + let options = SpecOptions( + carthageBuildPath: "../Carthage/Build", + carthageExecutablePath: "../bin/carthage", + createIntermediateGroups: true, + bundleIdPrefix: "com.test", + developmentLanguage: "ja", + deploymentTarget: DeploymentTarget( + iOS: "11.1", + tvOS: "10.0", + watchOS: "3.0", + macOS: "10.12.1" + ) + ) + let expected = Project(basePath: "", name: "test", options: options) + let dictionary: [String: Any] = ["options": [ + "carthageBuildPath": "../Carthage/Build", + "carthageExecutablePath": "../bin/carthage", + "bundleIdPrefix": "com.test", + "createIntermediateGroups": true, + "developmentLanguage": "ja", + "deploymentTarget": ["iOS": 11.1, "tvOS": 10.0, "watchOS": "3", "macOS": "10.12.1"], + ]] + let parsedSpec = try getProjectSpec(dictionary) + try expect(parsedSpec) == expected + } } } } + +@discardableResult +fileprivate func getProjectSpec(_ project: [String: Any], file: String = #file, line: Int = #line) throws -> Project { + var projectDictionary: [String: Any] = ["name": "test"] + for (key, value) in project { + projectDictionary[key] = value + } + do { + return try Project(basePath: "", jsonDictionary: projectDictionary) + } catch { + throw failure("\(error)", file: file, line: line) + } +} + +fileprivate func expectSpecError(_ project: [String: Any], _ expectedError: SpecParsingError, file: String = #file, line: Int = #line) throws { + try expectError(expectedError, file: file, line: line) { + try getProjectSpec(project) + } +} + +fileprivate func expectTargetError(_ target: [String: Any], _ expectedError: SpecParsingError, file: String = #file, line: Int = #line) throws { + try expectError(expectedError, file: file, line: line) { + _ = try Target(name: "test", jsonDictionary: target) + } +} diff --git a/Tests/XcodeGenKitTests/TestHelpers.swift b/Tests/XcodeGenKitTests/TestHelpers.swift index f9e65445..96d86c8c 100644 --- a/Tests/XcodeGenKitTests/TestHelpers.swift +++ b/Tests/XcodeGenKitTests/TestHelpers.swift @@ -2,17 +2,28 @@ import Foundation import ProjectSpec import Spectre import xcproj +import PathKit -func expectError(_ expectedError: T, _ closure: () throws -> Void) throws where T: CustomStringConvertible { +let fixturePath = Path(#file).parent().parent() + "Fixtures" + +func doThrowing(file: String = #file, line: Int = #line, _ closure: () throws -> T) throws -> T { + do { + return try closure() + } catch { + throw failure(String(describing: error), file: file, line: line) + } +} + +func expectError(_ expectedError: T, function: String = #function, file: String = #file, line: Int = #line, _ closure: () throws -> Void) throws where T: CustomStringConvertible { do { try closure() } catch let error as T { - try expect(error.description) == expectedError.description + try expect(error.description, file: file, line: line, function: function) == expectedError.description return } catch { - throw failure("Supposed to fail with \"\(expectedError)\"") + throw failure("Supposed to fail with \"\(expectedError)\"", function: function, file: file, line: line) } - throw failure("Supposed to fail with \"\(expectedError)\"") + throw failure("Supposed to fail with \"\(expectedError)\"", function: function, file: file, line: line) } struct ExpectationFailure: FailureType { @@ -32,7 +43,7 @@ struct ExpectationFailure: FailureType { open class ArrayExpectation: ExpectationType { public typealias ValueType = Array - open let expression: () throws -> ValueType? + public let expression: () throws -> ValueType? let file: String let line: Int diff --git a/Tests/XcodeGenKitTests/XCTest.swift b/Tests/XcodeGenKitTests/XCTest.swift deleted file mode 100644 index 8eaf6db8..00000000 --- a/Tests/XcodeGenKitTests/XCTest.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation -import XCTest - -class XCodeGenKitTests: XCTestCase { - - func testXcodeGenKit() { - projectGeneratorTests() - projectLoadingTests() - fixtureTests() - projectSpecTests() - } -}