diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 8912a1a..99efcb1 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -30,7 +30,13 @@ jobs: - name: Build spmgraph for Xcode 16.4 run: swift build - timeout-minutes: 4 + timeout-minutes: 3 + + - name: Run tests on Xcode 16.4 + env: + IS_RUNNING_TESTS: 1 + run: swift test + timeout-minutes: 3 - name: Select Xcode 26.0 toolchain uses: ./.github/actions/xcode-version @@ -40,4 +46,4 @@ jobs: - name: Build spmgraph for Xcode 26.0 run: swift build - timeout-minutes: 4 + timeout-minutes: 3 diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/spmgraph.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/spmgraph.xcscheme index 25bacdf..29cc732 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/spmgraph.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/spmgraph.xcscheme @@ -26,13 +26,19 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - shouldAutocreateTestPlan = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + With it, you can visualize your dependency graph, run selective testing, and enforce architectural rules for optimal modular setups. @@ -146,12 +149,11 @@ mint install getyourguide/spmgraph ## Acknowledgments - Inspired by the work that the [Tuist](https://tuist.dev/) team does for the Apple developers community and their focus on leveraging the dependency graph to provide amazing features for engineers. Also, a source of inspiration for our shell abstraction layer. -## Open roadmap +## Open roadmap - [ ] Cover the core logic of Lint, Map, and Visualize libs with tests - [ ] Improve the `unusedDependencies` lint rule to cover products with multiple targets - [ ] Support macros (to become a GitHub issue) -- [ ] Create Danger plugin for the linter functionality Ideas -- [ ] Lint - see if it can be improved to cover auto-exported dependencies. For example, usages of `import Dependencies` justify linking `DependenciesExtras` as a dependency. - [ ] Add fix-it suggestion to lint errors +- [ ] Create Danger plugin for the linter functionality diff --git a/Sources/Core/Extensions/ProcessInfo+Core.swift b/Sources/Core/Extensions/ProcessInfo+Core.swift new file mode 100644 index 0000000..3887c47 --- /dev/null +++ b/Sources/Core/Extensions/ProcessInfo+Core.swift @@ -0,0 +1,25 @@ +// +// +// Copyright (c) 2025 GetYourGuide GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// + +import Foundation + +public extension ProcessInfo { + static var isRunningTests: Bool { + processInfo.environment["IS_RUNNING_TESTS"] != nil + } +} diff --git a/Sources/SPMGraphConfigSetup/Resources/Package.txt b/Sources/SPMGraphConfigSetup/Resources/Package.txt index 6a8b628..be1efd2 100644 --- a/Sources/SPMGraphConfigSetup/Resources/Package.txt +++ b/Sources/SPMGraphConfigSetup/Resources/Package.txt @@ -20,7 +20,7 @@ let package = Package( // TODO: Change it to HTTP when made public .package( url: "git@github.com:getyourguide/spmgraph.git", - branch: "main" + revision: "bda3a77facf780f921bab267628ce1c36afaadb1" ), // TODO: Review which tag / Swift release to use diff --git a/Sources/SPMGraphConfigSetup/Resources/DoNotEdit_DynamicLoading.txt b/Sources/SPMGraphConfigSetup/Resources/_DoNotEdit_DynamicLoading.txt similarity index 100% rename from Sources/SPMGraphConfigSetup/Resources/DoNotEdit_DynamicLoading.txt rename to Sources/SPMGraphConfigSetup/Resources/_DoNotEdit_DynamicLoading.txt diff --git a/Sources/SPMGraphConfigSetup/SPMGraphEdit.swift b/Sources/SPMGraphConfigSetup/SPMGraphEdit.swift index 80a34bf..f5f070b 100644 --- a/Sources/SPMGraphConfigSetup/SPMGraphEdit.swift +++ b/Sources/SPMGraphConfigSetup/SPMGraphEdit.swift @@ -125,6 +125,11 @@ public final class SPMGraphEdit: SPMGraphEditProtocol { private extension SPMGraphEdit { func openEditPackage() throws(SPMGraphEditError) { + guard !ProcessInfo.isRunningTests else { + print("Skipped opening the edit package in tests...") + return + } + if verbose { print("Opening the edit package...") } diff --git a/Sources/SPMGraphConfigSetup/SPMGraphLoad.swift b/Sources/SPMGraphConfigSetup/SPMGraphLoad.swift index 467c771..67ea2ad 100644 --- a/Sources/SPMGraphConfigSetup/SPMGraphLoad.swift +++ b/Sources/SPMGraphConfigSetup/SPMGraphLoad.swift @@ -70,7 +70,7 @@ public final class SPMGraphLoad: SPMGraphLoadProtocol { editPackageDirectory .appending(component: "Sources") .appending(component: "SPMGraphConfig") - .appending(component: "DoNotEdit_DynamicLoading") + .appending(component: "_DoNotEdit_DynamicLoading") .appending(extension: "swift") public init(input: SPMGraphLoadInput) throws(SPMGraphLoadError) { @@ -90,6 +90,8 @@ private extension SPMGraphLoad { func load(userConfigFile: AbsolutePath) throws(SPMGraphLoadError) { print("Loading your SPMGraphConfig.swift into spmgraph... please await") + defer { try? removeDynamicLoadingFile() } + try includeDynamicLoadingFile() do { @@ -115,8 +117,6 @@ private extension SPMGraphLoad { ) } - defer { try? removeDynamicLoadingFile() } - print("Finished loading") } @@ -124,7 +124,7 @@ private extension SPMGraphLoad { do { guard let dynamicLoadingTemplateURL = Bundle.module.url( - forResource: "Resources/DoNotEdit_DynamicLoading", + forResource: "Resources/_DoNotEdit_DynamicLoading", withExtension: "txt" ) else { diff --git a/Sources/SPMGraphExecutable/Subcommands/Tests.swift b/Sources/SPMGraphExecutable/Subcommands/Tests.swift index 8b24049..1c52736 100644 --- a/Sources/SPMGraphExecutable/Subcommands/Tests.swift +++ b/Sources/SPMGraphExecutable/Subcommands/Tests.swift @@ -24,7 +24,7 @@ struct TestsArguments: ParsableArguments { @Option( name: [.customLong("files"), .customLong("changedFiles")], - help: "Optional list of changed files. Otherwise git versioning is used" + help: "Optional list of changed files. Otherwise git versioning is used. It supports both absolute and relative paths" ) var changedFiles: [String] = [] // TODO: Change to AbsolutePath diff --git a/Sources/SPMGraphExecutable/Subcommands/Visualize.swift b/Sources/SPMGraphExecutable/Subcommands/Visualize.swift index b03b8de..709c477 100644 --- a/Sources/SPMGraphExecutable/Subcommands/Visualize.swift +++ b/Sources/SPMGraphExecutable/Subcommands/Visualize.swift @@ -37,7 +37,7 @@ struct VisualizeArguments: ParsableArguments { @Option( name: [.customShort("o"), .customLong("output", withSingleDash: false)], help: - "Custom output file path for the generated PNG file. Default will generate a 'graph.png' file in the current directory" + "Custom output file path for the generated PNG file. By default a 'graph.png' file is generated in the current directory" ) var outputFilePath: String? diff --git a/Sources/SPMGraphTests/SPMGraphTests.swift b/Sources/SPMGraphTests/SPMGraphTests.swift index 68574b6..b433f56 100644 --- a/Sources/SPMGraphTests/SPMGraphTests.swift +++ b/Sources/SPMGraphTests/SPMGraphTests.swift @@ -137,8 +137,11 @@ public final class SPMGraphTests: SSPMGraphTestsProtocol { } else { let changedFilesString = input.changedFiles changedFiles = try changedFilesString.map { - let relativePath = try RelativePath(validating: $0) - return AbsolutePath.currentDir.appending(relativePath) + // Uses the AbsolutePath initializer that accepts either a relative or an absolute path + try AbsolutePath( + validating: $0, + relativeTo: .currentDir + ) } } diff --git a/Sources/SPMGraphVisualize/SPMGraphVisualize.swift b/Sources/SPMGraphVisualize/SPMGraphVisualize.swift index efec4d1..9b3df6d 100644 --- a/Sources/SPMGraphVisualize/SPMGraphVisualize.swift +++ b/Sources/SPMGraphVisualize/SPMGraphVisualize.swift @@ -34,7 +34,7 @@ public struct SPMGraphVisualizeInput { /// Flag to exclude third-party dependencies from the graph declared in the `Package.swift` let excludeThirdPartyDependencies: Bool /// Custom output file path for the generated PNG file. Default will generate a 'graph.png' file in the current directory - let outputFilePath: String? + let outputFilePath: AbsolutePath /// Minimum vertical spacing between the ranks (levels) of the graph. Default is set to 4. Is a double value in inches. let rankSpacing: Double /// Show extra logging for troubleshooting purposes @@ -54,9 +54,20 @@ public struct SPMGraphVisualizeInput { self.excludedSuffixes = excludedSuffixes self.focusedModule = focusedModule self.excludeThirdPartyDependencies = excludeThirdPartyDependencies - self.outputFilePath = outputFilePath self.rankSpacing = rankSpacing self.verbose = verbose + + if let outputFilePath { + self.outputFilePath = try AbsolutePath( + validating: outputFilePath, + relativeTo: .currentDir + ) + } else { + // the default output path + self.outputFilePath = AbsolutePath.currentDir + .appending(component: "graph") + .appending(extension: "png") + } } } @@ -123,32 +134,25 @@ private extension SPMGraphVisualize { graph.render(using: .dot, to: .png) { [weak system] result in switch result { case let .success(data): - let fileURL = URL( - fileURLWithPath: input.outputFilePath - ?? FileManager.default.currentDirectoryPath.appending("/graph.png") - ) - do { - try data.write(to: fileURL) - - try system? - .run( - "open", - fileURL.absoluteString, - verbose: true - ) + try data.write(to: input.outputFilePath.asURL) + + // Opens the generated graph unless running tests + if !ProcessInfo.isRunningTests { + try system? + .run( + "open", + input.outputFilePath.pathString, + verbose: true + ) + } + continuation.resume(returning: ()) } catch { - try? system? - .echo( - "Failed save and open graph visualization file with error: \(error.localizedDescription)" - ) + continuation.resume(throwing: error) } case let .failure(error): - try? system? - .echo("Failed to render dependency graph with error: \(error.localizedDescription)") + continuation.resume(throwing: error) } - - continuation.resume(returning: ()) } } } diff --git a/Tests/Fixtures/Package/Package.swift b/Tests/Fixtures/Package/Package.swift new file mode 100644 index 0000000..530eb3a --- /dev/null +++ b/Tests/Fixtures/Package/Package.swift @@ -0,0 +1,40 @@ +// swift-tools-version: 6.0 + +import PackageDescription + +let package = Package( + name: "FixturePackage", + products: [ + .library( + name: "Library", + targets: [ + "TargetA", + "TargetB", + ] + ), + ], + targets: [ + .target( + name: "TargetA", + dependencies: [ + "TargetB" + ] + ), + .target( + name: "TargetB" + ), + + .testTarget( + name: "TargetATests", + dependencies: [ + "TargetA" + ] + ), + .testTarget( + name: "TargetBTests", + dependencies: [ + "TargetB" + ] + ), + ] +) diff --git a/Tests/Fixtures/Package/Sources/TargetA/Example.swift b/Tests/Fixtures/Package/Sources/TargetA/Example.swift new file mode 100644 index 0000000..88a4223 --- /dev/null +++ b/Tests/Fixtures/Package/Sources/TargetA/Example.swift @@ -0,0 +1,8 @@ +import TargetB + +struct Example { + static func main() { + print(Foo()) + print(Bar()) + } +} diff --git a/Tests/Fixtures/Package/Sources/TargetB/Example.swift b/Tests/Fixtures/Package/Sources/TargetB/Example.swift new file mode 100644 index 0000000..8591c49 --- /dev/null +++ b/Tests/Fixtures/Package/Sources/TargetB/Example.swift @@ -0,0 +1,11 @@ +package struct Foo { + package let a = "dummy" + + package init() {} +} + +package struct Bar { + package let b = "baz" + + package init() {} +} diff --git a/Tests/Fixtures/Package/Tests/TargetATests/ExampleTests.swift b/Tests/Fixtures/Package/Tests/TargetATests/ExampleTests.swift new file mode 100644 index 0000000..527240b --- /dev/null +++ b/Tests/Fixtures/Package/Tests/TargetATests/ExampleTests.swift @@ -0,0 +1,7 @@ +import TargetA +import Testing + +@Suite +struct FooTests { + @Test bar() {} +} diff --git a/Tests/Fixtures/Package/Tests/TargetBTests/ExampleTests.swift b/Tests/Fixtures/Package/Tests/TargetBTests/ExampleTests.swift new file mode 100644 index 0000000..9f46dc0 --- /dev/null +++ b/Tests/Fixtures/Package/Tests/TargetBTests/ExampleTests.swift @@ -0,0 +1,7 @@ +import TargetB +import Testing + +@Suite +struct FooTests { + @Test bar() {} +} diff --git a/Tests/SPMGraphExecutableTests/SPMGraphExecutableE2ETests.swift b/Tests/SPMGraphExecutableTests/SPMGraphExecutableE2ETests.swift new file mode 100644 index 0000000..3510a45 --- /dev/null +++ b/Tests/SPMGraphExecutableTests/SPMGraphExecutableE2ETests.swift @@ -0,0 +1,399 @@ +// +// +// Copyright (c) 2025 GetYourGuide GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// + +import Basics +import Foundation +import Testing + +@Suite(.serialized) +struct SPMGraphExecutableE2ETests { + // auto reset to its initial value before it test runs + private let process = Process() + private let outputPipe = Pipe() + private let errorPipe = Pipe() + + @Test( + arguments: [ + "", + "visualize ", + "tests ", + "config ", + "load ", + "lint ", + ] + ) + func help(command: String) throws { + try runToolProcess(command: "\(command)--help") + assertProcess() + } + + /// Tests the visualize feature + /// + /// - warning: For this to work it has to be run via the spmgraph testplan, where the Address Sanitizer is enabled, + /// which works around potential memory crashes with graphviz in debug + @Test(.enabled(if: ProcessInfo.isSpmgraphTestPlan)) + func visualize() async throws { + // GIVEN + let outputPath = try localFileSystem.tempDirectory + .appending(component: "graph") + .appending(extension: "png") + + // WHEN + try runToolProcess(command: "visualize \(AbsolutePath.fixturePackagePath) -o \(outputPath)") + + // THEN + assertProcess() + + #expect(localFileSystem.exists(outputPath)) + + // Cleanup + try localFileSystem.removeFileTree(outputPath) + } + + @Test(arguments: ["textDump", "textFile"]) + func tests(outputMode: String) throws { + // GIVEN + let changedFilePath = AbsolutePath.fixturePackagePath + .appending(component: "Sources") + .appending(component: "TargetB") + .appending(component: "Example") + .appending(extension: "swift") + + // WHEN + try runToolProcess( + command: "tests \(AbsolutePath.fixturePackagePath) --files \(changedFilePath) --output \(outputMode)" + ) + + // THEN + assertProcess( + outputContains: outputMode == "textDump" + ? "TargetBTests,TargetATests" + : "saved the formatted list of test modules to" + ) + + if outputMode == "textFile" { + let outputPath = try #require(localFileSystem.currentWorkingDirectory) + .appending(component: "output") + .appending(extension: "txt") + #expect(localFileSystem.exists(outputPath)) + try localFileSystem.removeFileTree(outputPath) + } + } + + @Test func initialConfig() async throws { + // WHEN + try runToolProcess( + command: "config \(AbsolutePath.fixturePackagePath) -d \(AbsolutePath.buildDir)", + waitForExit: false + ) + + // THEN + + // Await for the package to be created and loaded + try await Task.sleep(for: .seconds(4)) + + #expect( + localFileSystem.exists(.configPackagePath), + "It creates the config package in the buildDir" + ) + #expect( + try localFileSystem.getDirectoryContents(.configPackagePath) == + [ + "Package.swift", + "Sources" + ] + ) + #expect( + localFileSystem.exists(.userConfigFilePath), + "It creates a spmgraph config file in the same dir as the Package" + ) + + process.terminate() + assertProcess() + + // The config package outlives the process + #expect(localFileSystem.exists(.configPackagePath)) + + // Cleanup + try localFileSystem.removeFileTree(.userConfigFilePath) + try localFileSystem.removeFileTree(.dirtyConfigFilePath) + try localFileSystem.removeFileTree(.configPackagePath) + } + + @Test func configWhenEditing() async throws { + // GIVEN + createUserConfigFile() + try stubUserConfigFile() + + // WHEN + let buildDir = try localFileSystem.tempDirectory + .appending(component: "buildDir") + try runToolProcess( + command: "config \(AbsolutePath.fixturePackagePath) -d \(buildDir)", + waitForExit: false + ) + + // THEN + + // Await for the package to be created and loaded + try await Task.sleep(for: .seconds(2)) + + // It creates the config package in the buildDir + #expect(localFileSystem.exists(.configPackagePath)) + #expect( + try localFileSystem.getDirectoryContents(.configPackagePath) == + [ + "Package.swift", + "Sources" + ] + ) + #expect( + try localFileSystem.getDirectoryContents(.configPackageSources) == + [ + "SPMGraphConfig.swift" + ] + ) + #expect( + try localFileSystem.readFileContents(.configPackageConfigFile) == + .userConfigStub, + "The config package config file has the same content as the user config file" + ) + + // WHEN - the user config file is updated + + let updatedConfigContent = """ + import Foundation + import PackageModel + import SPMGraphDescriptionInterface + + let spmGraphConfig = SPMGraphConfig( + lint: SPMGraphConfig.Lint(isStrict: true) + ) + """ + try stubUserConfigFile(with: updatedConfigContent) + + // Ensure the file is updated + try await Task.sleep(for: .seconds(1)) + + // THEN + #expect( + try localFileSystem.readFileContents(.userConfigFilePath) == + updatedConfigContent, + "The user spmgraph config file should be updated reflecting the changes done" + ) + + process.terminate() + assertProcess() + + // The config package outlives the process + #expect(localFileSystem.exists(.configPackagePath)) + + // Cleanup + try localFileSystem.removeFileTree(.userConfigFilePath) + try localFileSystem.removeFileTree(.dirtyConfigFilePath) + } + + /// Tests the `load functionality`, which feeds the user configuration into spmgraph, by building it and dynamically loading it's dylib. + /// + /// - warning: It depends on the serial execution of the tests and on the config package being loaded into memory beforehand. + /// + /// - note: This could be improved to rely on two separate `Process`s to run both `config` and `load` in sequence, + /// instead of relying on the serial order of tests. + @Test(.disabled("Disabled while spmgraph is not open source and can't be easily resolved as a package")) + func testLoad() async throws { + createUserConfigFile() + try stubUserConfigFile() + + // Ensure the file is updated + try await Task.sleep(for: .seconds(1)) + + let buildDir = try localFileSystem.tempDirectory + .appending(component: "buildDir") + + try runToolProcess( + command: "load \(AbsolutePath.fixturePackagePath) -d \(buildDir)", + waitForExit: true + ) + assertProcess() + } + + /// Tests the `lint functionality`, which depends on the the user config being loaded into spmgraph. + /// + /// - warning: It depends on the serial execution of the tests and on the user config dylib being generated. + /// + /// - note: This could be improved to rely on two separate `Process`s to run `config`, `load` and `lint` in sequence, + /// instead of relying on the serial order of tests. + @Test(.disabled("Disabled due to issue with duplicate symbols")) + func testLint() async throws { + let outputPath = "lint_output" + + try runToolProcess( + command: "lint \(AbsolutePath.fixturePackagePath) --strict -o \(outputPath) -d \(AbsolutePath.buildDir) --verbose", + waitForExit: true + ) + + let outputAbsolutePath = try #require(localFileSystem.currentWorkingDirectory) + .appending(component: "lint_output") + .appending(extension: "txt") + #expect(localFileSystem.exists(outputAbsolutePath)) + try localFileSystem.removeFileTree(outputAbsolutePath) + } +} + +// MARK: - Helpers + +private extension SPMGraphExecutableE2ETests { + func runToolProcess( + command: String, + waitForExit: Bool = true + ) throws { + let commands = command.split(whereSeparator: \.isWhitespace) + let arguments = commands.map(String.init) + + let executableURL = Bundle.productsDirectory.appendingPathComponent("spmgraph") + + process.executableURL = executableURL + process.arguments = arguments + process.standardOutput = outputPipe + process.standardError = errorPipe + + try process.run() + if waitForExit { + process.waitUntilExit() + } + } + + func assertProcess( + expectsError: Bool = false, + outputContains: String? = nil, + _ sourceLocation: SourceLocation = #_sourceLocation + ) { + let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() + let outputContent = String(data: outputData, encoding: .utf8) ?? "" + let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile() + let errorContent = String(data: errorData, encoding: .utf8) ?? "" + + #expect( + errorContent.isEmpty != expectsError, + expectsError + ? "Expected error, but none was found." + : "Unexpected error found: \(errorContent)", + sourceLocation: sourceLocation + ) + + if let outputContains { + #expect( + outputContent.contains(outputContains), + sourceLocation: sourceLocation + ) + } + } + + func createUserConfigFile() { + localFileSystem.createEmptyFiles( + at: .fixturePackagePath, + files: "SPMGraphConfig.swift" + ) + } + + func stubUserConfigFile(with content: String = .userConfigStub) throws { + try localFileSystem.writeFileContents( + .userConfigFilePath, + string: content + ) + + if !localFileSystem.exists(.userConfigFilePath) { + Issue.record("Missing SPMGraphConfig.swift fixture file") + } + } +} + +private extension ProcessInfo { + static var isSpmgraphTestPlan: Bool { + processInfo.environment["TESTPLAN"] == "spmgraph" + } +} + +private extension Bundle { + /// Returns path to the built products directory. + static var productsDirectory: URL { + #if os(macOS) + if ProcessInfo.isSpmgraphTestPlan { + for bundle in allBundles where bundle.bundlePath.hasSuffix(".xctest") { + return bundle.bundleURL.deletingLastPathComponent() + } + fatalError("couldn't find the products directory") + } else { + return localFileSystem.currentWorkingDirectory! + .appending(".build") + .appending(component: "debug") + .asURL + } + #else + return main.bundleURL + #endif + } +} + +private extension AbsolutePath { + static var fixturePackagePath: AbsolutePath { + do { + return try AbsolutePath( + validating: "../../Fixtures/Package", + relativeTo: .init(validating: #filePath) + ) + } catch { + Issue.record("Unable to resolve fixture package path") + preconditionFailure("Unable to resolve fixture package path") + } + } + + static var buildDir: AbsolutePath { + do { + return try localFileSystem.tempDirectory + .appending(component: "buildDir") + } catch { + Issue.record("Unable to resolve custom build directory path") + preconditionFailure("Unable to resolve custom build directory path") + } + } + + static let configPackagePath: AbsolutePath = buildDir.appending(component: "spmgraph-config") + static let configPackageSources = configPackagePath + .appending("Sources") + .appending("SPMGraphConfig") + static let configPackageConfigFile = configPackagePath + .appending("Sources") + .appending("SPMGraphConfig") + .appending("SPMGraphConfig.swift") + + static let userConfigFilePath: AbsolutePath = fixturePackagePath + .appending(component: "SPMGraphConfig.swift") + static let dirtyConfigFilePath: AbsolutePath = fixturePackagePath + .appending(component: "PMGraphConfig.swift") +} + +private extension String { + static let userConfigStub: String = """ + import Foundation + import PackageModel + import SPMGraphDescriptionInterface + + let spmGraphConfig = SPMGraphConfig.default + """ +} diff --git a/spmgraph.xctestplan b/spmgraph.xctestplan new file mode 100644 index 0000000..c6ea084 --- /dev/null +++ b/spmgraph.xctestplan @@ -0,0 +1,65 @@ +{ + "configurations" : [ + { + "id" : "5D7B73B1-8F29-45BF-A398-A3A25640665A", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "addressSanitizer" : { + "enabled" : true + }, + "codeCoverage" : false, + "commandLineArgumentEntries" : [ + { + "argument" : "load", + "enabled" : false + }, + { + "argument" : "" + }, + { + "argument" : "edit", + "enabled" : false + }, + { + "argument" : "visualize" + }, + { + "argument" : "lint", + "enabled" : false + }, + { + "argument" : "$path_to_your_package" + } + ], + "environmentVariableEntries" : [ + { + "key" : "TESTPLAN", + "value" : "spmgraph" + }, + { + "key" : "IS_RUNNING_TESTS", + "value" : "1" + } + ], + "targetForVariableExpansion" : { + "containerPath" : "container:", + "identifier" : "spmgraph", + "name" : "spmgraph" + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:", + "identifier" : "SPMGraphExecutableTests", + "name" : "SPMGraphExecutableTests" + } + } + ], + "version" : 1 +}