mirror of
https://github.com/yonaskolb/XcodeGen.git
synced 2026-03-18 20:02:25 +00:00
Merge pull request #117 from yonaskolb/validation_tests
Add spec validation tests
This commit is contained in:
@@ -22,6 +22,8 @@ public struct Config: Equatable {
|
||||
public static func ==(lhs: Config, rhs: Config) -> Bool {
|
||||
return lhs.name == rhs.name && lhs.type == rhs.type
|
||||
}
|
||||
|
||||
public static var defaultConfigs: [Config] = [Config(name: "Debug", type: .debug), Config(name: "Release", type: .release)]
|
||||
}
|
||||
|
||||
public enum ConfigType: String {
|
||||
|
||||
@@ -57,7 +57,7 @@ public struct ProjectSpec {
|
||||
}
|
||||
}
|
||||
|
||||
public init(basePath: Path, name: String, configs: [Config] = [], targets: [Target] = [], settings: Settings = .empty, settingGroups: [String: Settings] = [:], schemes: [Scheme] = [], options: Options = Options(), fileGroups: [String] = [], configFiles: [String: String] = [:], attributes: [String: Any] = [:]) {
|
||||
public init(basePath: Path, name: String, configs: [Config] = Config.defaultConfigs, targets: [Target] = [], settings: Settings = .empty, settingGroups: [String: Settings] = [:], schemes: [Scheme] = [], options: Options = Options(), fileGroups: [String] = [], configFiles: [String: String] = [:], attributes: [String: Any] = [:]) {
|
||||
self.basePath = basePath
|
||||
self.name = name
|
||||
self.targets = targets
|
||||
@@ -135,7 +135,7 @@ extension ProjectSpec {
|
||||
settings = jsonDictionary.json(atKeyPath: "settings") ?? .empty
|
||||
settingGroups = jsonDictionary.json(atKeyPath: "settingGroups") ?? jsonDictionary.json(atKeyPath: "settingPresets") ?? [:]
|
||||
let configs: [String: String] = jsonDictionary.json(atKeyPath: "configs") ?? [:]
|
||||
self.configs = configs.map { Config(name: $0, type: ConfigType(rawValue: $1)) }.sorted { $0.name < $1.name }
|
||||
self.configs = configs.isEmpty ? Config.defaultConfigs : configs.map { Config(name: $0, type: ConfigType(rawValue: $1)) }.sorted { $0.name < $1.name }
|
||||
targets = try jsonDictionary.json(atKeyPath: "targets").sorted { $0.name < $1.name }
|
||||
schemes = try jsonDictionary.json(atKeyPath: "schemes")
|
||||
fileGroups = jsonDictionary.json(atKeyPath: "fileGroups") ?? []
|
||||
|
||||
+27
-30
@@ -6,21 +6,16 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ProjectSpec
|
||||
import PathKit
|
||||
|
||||
extension ProjectSpec {
|
||||
|
||||
public mutating func validate() throws {
|
||||
public func validate() throws {
|
||||
|
||||
if configs.isEmpty {
|
||||
configs = [Config(name: "Debug", type: .debug), Config(name: "Release", type: .release)]
|
||||
}
|
||||
var errors: [SpecValidationError.ValidationError] = []
|
||||
|
||||
var errors: [SpecValidationError.Error] = []
|
||||
|
||||
func validateSettings(_ settings: Settings) -> [SpecValidationError.Error] {
|
||||
var errors: [SpecValidationError.Error] = []
|
||||
func validateSettings(_ settings: Settings) -> [SpecValidationError.ValidationError] {
|
||||
var errors: [SpecValidationError.ValidationError] = []
|
||||
for group in settings.groups {
|
||||
if let settings = settingGroups[group] {
|
||||
errors += validateSettings(settings)
|
||||
@@ -30,12 +25,14 @@ extension ProjectSpec {
|
||||
}
|
||||
for config in settings.configSettings.keys {
|
||||
if !configs.contains(where: { $0.name.lowercased().contains(config.lowercased())}) {
|
||||
errors.append(.invalidConfigReference(config))
|
||||
errors.append(.invalidBuildSettingConfig(config))
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
errors += validateSettings(settings)
|
||||
|
||||
for fileGroup in fileGroups {
|
||||
if !(basePath + fileGroup).exists {
|
||||
errors.append(.invalidFileGroup(fileGroup))
|
||||
@@ -46,6 +43,9 @@ extension ProjectSpec {
|
||||
if !(basePath + configFile).exists {
|
||||
errors.append(.invalidConfigFile(configFile: configFile, config: config))
|
||||
}
|
||||
if getConfig(config) == nil {
|
||||
errors.append(.invalidConfigFileConfig(config))
|
||||
}
|
||||
}
|
||||
|
||||
for settings in settingGroups.values {
|
||||
@@ -61,13 +61,10 @@ extension ProjectSpec {
|
||||
|
||||
for (config, configFile) in target.configFiles {
|
||||
if !(basePath + configFile).exists {
|
||||
errors.append(.invalidTargetConfigFile(configFile: configFile, config: config, target: target.name))
|
||||
errors.append(.invalidTargetConfigFile(target: target.name, configFile: configFile, config: config))
|
||||
}
|
||||
}
|
||||
|
||||
for config in target.settings.configSettings.keys {
|
||||
if getConfig(config) == nil {
|
||||
errors.append(.invalidBuildSettingConfig(config))
|
||||
errors.append(.invalidConfigFileConfig(config))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +107,7 @@ extension ProjectSpec {
|
||||
if case let .path(pathString) = script.script {
|
||||
let scriptPath = basePath + pathString
|
||||
if !scriptPath.exists {
|
||||
errors.append(.invalidBuildScriptPath(target: target.name, path: pathString))
|
||||
errors.append(.invalidBuildScriptPath(target: target.name, name: script.name, path: pathString))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,39 +146,39 @@ extension ProjectSpec {
|
||||
|
||||
public struct SpecValidationError: Error, CustomStringConvertible {
|
||||
|
||||
public var errors: [Error]
|
||||
public var errors: [ValidationError]
|
||||
|
||||
public enum Error: CustomStringConvertible {
|
||||
public enum ValidationError: Error, CustomStringConvertible {
|
||||
case invalidTargetDependency(target: String, dependency: String)
|
||||
case missingTargetSource(target: String, source: String)
|
||||
case invalidTargetConfigFile(target: String, configFile: String, config: String)
|
||||
case invalidTargetSchemeConfigVariant(target: String, configVariant: String, configType: ConfigType)
|
||||
case invalidTargetSchemeTest(target: String, testTarget: String)
|
||||
case invalidSchemeTarget(scheme: String, target: String)
|
||||
case invalidSchemeConfig(scheme: String, config: String)
|
||||
case invalidConfigFile(configFile: String, config: String)
|
||||
case invalidTargetConfigFile(configFile: String, config: String, target: String)
|
||||
case invalidBuildSettingConfig(String)
|
||||
case invalidSettingsGroup(String)
|
||||
case missingTargetSource(target: String, source: String)
|
||||
case invalidBuildScriptPath(target: String, path: String)
|
||||
case invalidTargetSchemeConfigVariant(target: String, configVariant: String, configType: ConfigType)
|
||||
case invalidTargetSchemeTest(target: String, testTarget: String)
|
||||
case invalidBuildScriptPath(target: String, name: String?, path: String)
|
||||
case invalidFileGroup(String)
|
||||
case invalidConfigReference(String)
|
||||
case invalidConfigFileConfig(String)
|
||||
case missingConfigTypeForGeneratedTargetScheme(target: String, configType: ConfigType)
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .invalidTargetDependency(target, dependency): return "Target \(target.quoted) has invalid dependency: \(dependency.quoted)"
|
||||
case let .invalidTargetConfigFile(configFile, config, target): return "Target \(target.quoted) has invalid config file \(configFile.quoted) for config \(config.quoted)"
|
||||
case let .invalidTargetConfigFile(target, configFile, config): return "Target \(target.quoted) has invalid config file \(configFile.quoted) for config \(config.quoted)"
|
||||
case let .missingTargetSource(target, source): return "Target \(target.quoted) has a missing source directory \(source.quoted)"
|
||||
case let .invalidTargetSchemeConfigVariant(target, configVariant, configType): return "Target \(target.quoted) has an invalid scheme config variant which requires a config that has a \(configType.rawValue.quoted) type and contains the name \(configVariant.quoted)"
|
||||
case let .invalidTargetSchemeTest(target, test): return "Target \(target.quoted) scheme has invalid test \(test.quoted)"
|
||||
case let .invalidConfigFile(configFile, config): return "Invalid config file \(configFile.quoted) for config \(config.quoted)"
|
||||
case let .invalidSchemeTarget(scheme, target): return "Scheme \(scheme.quoted) has invalid build target \(target.quoted)"
|
||||
case let .invalidSchemeConfig(scheme, config): return "Scheme \(scheme.quoted) has invalid build configuration \(config.quoted)"
|
||||
case let .invalidBuildSettingConfig(config): return "Build setting has invalid build configuration \(config.quoted)"
|
||||
case let .missingTargetSource(target, source): return "Target \(target.quoted) has a missing source directory \(source.quoted)"
|
||||
case let .invalidSettingsGroup(group): return "Invalid settings group \(group.quoted)"
|
||||
case let .invalidBuildScriptPath(target, path): return "Target \(target.quoted) has a script path that doesn't exist \(path.quoted)"
|
||||
case let .invalidTargetSchemeConfigVariant(target, configVariant, configType): return "Target \(target.quoted) has an invalid scheme config variant which requires a config that has a \(configType.rawValue.quoted) type and contains the name \(configVariant.quoted)"
|
||||
case let .invalidTargetSchemeTest(target, test): return "Target \(target.quoted) scheme has invalid test \(test.quoted)"
|
||||
case let .invalidBuildScriptPath(target, name, path): return "Target \(target.quoted) has a script \(name != nil ? "\(name!.quoted) which has a " : "")path that doesn't exist \(path.quoted)"
|
||||
case let .invalidFileGroup(group): return "Invalid file group \(group.quoted)"
|
||||
case let .invalidConfigReference(config): return "Invalid config reference \(config.quoted)"
|
||||
case let .invalidConfigFileConfig(config): return "Config file has invalid config \(config.quoted)"
|
||||
case let .missingConfigTypeForGeneratedTargetScheme(target, configType): return "Target \(target.quoted) is missing a config of type \(configType.rawValue) to generate its scheme"
|
||||
}
|
||||
}
|
||||
@@ -135,8 +135,8 @@ extension Target: Equatable {
|
||||
}
|
||||
|
||||
public struct TargetScheme {
|
||||
public let testTargets: [String]
|
||||
public let configVariants: [String]
|
||||
public var testTargets: [String]
|
||||
public var configVariants: [String]
|
||||
|
||||
public init(testTargets: [String] = [], configVariants: [String] = []) {
|
||||
self.testTargets = testTargets
|
||||
|
||||
@@ -15,7 +15,7 @@ import ProjectSpec
|
||||
|
||||
public class ProjectGenerator {
|
||||
|
||||
var spec: ProjectSpec
|
||||
let spec: ProjectSpec
|
||||
let currentXcodeVersion = "0900"
|
||||
|
||||
public init(spec: ProjectSpec) {
|
||||
|
||||
@@ -24,5 +24,86 @@ func projectSpecTests() {
|
||||
try expect(dynamicLibrary.type.isLibrary).to.beTrue()
|
||||
}
|
||||
}
|
||||
|
||||
func expectValidationError(_ spec: ProjectSpec, _ expectedError: SpecValidationError.ValidationError) throws {
|
||||
do {
|
||||
try spec.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("Validation") {
|
||||
|
||||
let baseSpec = ProjectSpec(basePath: "", name: "", configs: [Config(name: "invalid")])
|
||||
let invalidSettings = Settings(configSettings: ["invalidConfig": [:]],
|
||||
groups: ["invalidSettingGroup"])
|
||||
$0.it("fails with invalid project") {
|
||||
var spec = baseSpec
|
||||
spec.settings = invalidSettings
|
||||
spec.configFiles = ["invalidConfig": "invalidConfigFile"]
|
||||
spec.fileGroups = ["invalidFileGroup"]
|
||||
spec.settingGroups = ["settingGroup1": Settings(configSettings: ["invalidSettingGroupConfig": [:]],
|
||||
groups: ["invalidSettingGroupSettingGroup"])]
|
||||
|
||||
try expectValidationError(spec, .invalidConfigFileConfig("invalidConfig"))
|
||||
try expectValidationError(spec, .invalidBuildSettingConfig("invalidConfig"))
|
||||
try expectValidationError(spec, .invalidConfigFile(configFile: "invalidConfigFile", config: "invalidConfig"))
|
||||
try expectValidationError(spec, .invalidSettingsGroup("invalidSettingGroup"))
|
||||
try expectValidationError(spec, .invalidFileGroup("invalidFileGroup"))
|
||||
try expectValidationError(spec, .invalidSettingsGroup("invalidSettingGroupSettingGroup"))
|
||||
try expectValidationError(spec, .invalidBuildSettingConfig("invalidSettingGroupConfig"))
|
||||
}
|
||||
|
||||
$0.it("fails with invalid target") {
|
||||
var spec = baseSpec
|
||||
spec.targets = [Target(name: "target1",
|
||||
type: .application,
|
||||
platform: .iOS,
|
||||
settings: invalidSettings,
|
||||
configFiles: ["invalidConfig": "invalidConfigFile"],
|
||||
sources: ["invalidSource"],
|
||||
dependencies: [Dependency(type: .target, reference: "invalidDependency")],
|
||||
prebuildScripts: [BuildScript(script: .path("invalidPrebuildScript"), name: "prebuildScript1", inputFiles:
|
||||
[], outputFiles: [])],
|
||||
postbuildScripts: [BuildScript(script: .path("invalidPostbuildScript"), inputFiles:
|
||||
[], outputFiles: [])],
|
||||
scheme: TargetScheme(testTargets: ["invalidTarget"])
|
||||
)]
|
||||
|
||||
try expectValidationError(spec, .invalidTargetDependency(target: "target1", dependency: "invalidDependency"))
|
||||
try expectValidationError(spec, .invalidTargetConfigFile(target: "target1", configFile: "invalidConfigFile", config: "invalidConfig"))
|
||||
try expectValidationError(spec, .invalidTargetSchemeTest(target: "target1", testTarget: "invalidTarget"))
|
||||
try expectValidationError(spec, .missingTargetSource(target: "target1", source: "invalidSource"))
|
||||
try expectValidationError(spec, .invalidBuildSettingConfig("invalidConfig"))
|
||||
try expectValidationError(spec, .invalidSettingsGroup("invalidSettingGroup"))
|
||||
try expectValidationError(spec, .invalidBuildScriptPath(target:"target1", name: "prebuildScript1", path: "invalidPrebuildScript"))
|
||||
try expectValidationError(spec, .invalidBuildScriptPath(target:"target1", name: nil, path: "invalidPostbuildScript"))
|
||||
|
||||
try expectValidationError(spec, .missingConfigTypeForGeneratedTargetScheme(target:"target1", configType: .debug))
|
||||
try expectValidationError(spec, .missingConfigTypeForGeneratedTargetScheme(target:"target1", configType: .release))
|
||||
|
||||
spec.targets[0].scheme?.configVariants = ["invalidVariant"]
|
||||
try expectValidationError(spec, .invalidTargetSchemeConfigVariant(target: "target1", configVariant: "invalidVariant", configType: .debug))
|
||||
}
|
||||
|
||||
$0.it("fails with invalid scheme") {
|
||||
var spec = baseSpec
|
||||
spec.schemes = [Scheme(name: "scheme1",
|
||||
targets: [Scheme.BuildTarget(target: "invalidTarget")],
|
||||
debugConfig: "debugInvalid",
|
||||
releaseConfig: "releaseInvalid")]
|
||||
|
||||
try expectValidationError(spec, .invalidSchemeTarget(scheme: "scheme1", target: "invalidTarget"))
|
||||
try expectValidationError(spec, .invalidSchemeConfig(scheme: "scheme1", config: "debugInvalid"))
|
||||
try expectValidationError(spec, .invalidSchemeConfig(scheme: "scheme1", config: "releaseInvalid"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func specLoadingTests() {
|
||||
}
|
||||
}
|
||||
|
||||
describe("Project Spec") {
|
||||
describe("Project Spec Parser") {
|
||||
|
||||
$0.it("fails with incorrect platform") {
|
||||
var target = validTarget
|
||||
|
||||
Reference in New Issue
Block a user