From d7352b5a250c0b3a21ec1a79489e3e0d751f422b Mon Sep 17 00:00:00 2001 From: thecb4 Date: Thu, 24 Dec 2020 03:49:28 -0500 Subject: [PATCH] Add Dev tests and fix hard coded paths (#192) * Added Dev tests and fixed hard coded paths, fixes #183 * Fixed Path dependency in 5.2 Package * Removed mxcl/Path dependency * Removed mxcl/Path dependency, Added confirmation of file existence in Init test * Added sdk install head of init command test * Moved carton sdk install to static setup method for tests * Moved carton sdk install to static setup method for tests * Moved carton sdk install to static setup method for tests * Moved carton sdk install to static setup method for tests * Moved carton sdk install to static setup method for tests * changed order of actions for github automations to support testing * modified test run to exclude release * changed sdk install to run per test * adjusted github workflows to create carton sdk folders * adjusted github workflows to use /home/runner instead of root * adjusted test to wait for sdk installation * adjusted ubuntu tests for passing * adjusted Dockerfile to run tests * added tmate session for debugging * Removed tmate session, added directory creation for carton sdk and sym link * Adjusted order of commands for runner * Fixed mkdir from worker to runner * Added more terminal output for debugging * Added more terminal output for debugging * Added more terminal output for debugging * Added more terminal output for debugging * Added more terminal output for debugging * Fixed hard coded paths * Removed test investigation fixtures * Removed test investigation fixtures Co-authored-by: thecb4 --- .github/workflows/swift.yml | 1 - Dockerfile | 10 +- Package.resolved | 9 -- Package.swift | 4 +- Package@swift-5.2.swift | 2 +- Sources/SwiftToolchain/Toolchain.swift | 3 +- .../SwiftToolchain/ToolchainManagement.swift | 2 +- Sources/carton-release/HashArchive.swift | 10 +- Tests/CartonCLITests/DevCommandTests.swift | 63 ------------- .../CommandTestHelper.swift | 84 ++++++++++++++++- .../CartonCommandTests/DevCommandTests.swift | 24 +++++ .../CartonCommandTests/InitCommandTests.swift | 76 ++++++++++++++++ .../StringHelpers.swift | 0 Tests/CartonCommandTests/Testable.swift | 91 +++++++++++++++++++ 14 files changed, 291 insertions(+), 88 deletions(-) delete mode 100644 Tests/CartonCLITests/DevCommandTests.swift rename Tests/{CartonCLITests => CartonCommandTests}/CommandTestHelper.swift (74%) create mode 100644 Tests/CartonCommandTests/DevCommandTests.swift create mode 100644 Tests/CartonCommandTests/InitCommandTests.swift rename Tests/{CartonCLITests => CartonCommandTests}/StringHelpers.swift (100%) create mode 100644 Tests/CartonCommandTests/Testable.swift diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 0b028d7..c971494 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -99,7 +99,6 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Build on Ubuntu 20.04 with Swift 5.3 run: | swift test -c release --enable-test-discovery diff --git a/Dockerfile b/Dockerfile index a39825b..a70a226 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,10 +5,11 @@ LABEL Description="Carton is a watcher, bundler, and test runner for your SwiftW LABEL org.opencontainers.image.source https://github.com/swiftwasm/carton RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true && apt-get -q update && \ - apt-get -q install -y \ - libsqlite3-0 \ - libsqlite3-dev \ - curl unzip \ + apt-get -q install -y \ + build-essential \ + libsqlite3-0 \ + libsqlite3-dev \ + curl unzip \ && export WASMER_DIR=/usr/local && curl https://get.wasmer.io -sSfL | sh && \ rm -r /var/lib/apt/lists/* @@ -23,6 +24,7 @@ COPY . carton/ RUN cd carton && \ ./install_ubuntu_deps.sh && \ + swift test -c release --enable-test-discovery && \ swift build -c release && \ cd TestApp && ../.build/release/carton test && cd .. && \ mv .build/release/carton /usr/bin && \ diff --git a/Package.resolved b/Package.resolved index 8ab737e..90dc1cd 100644 --- a/Package.resolved +++ b/Package.resolved @@ -37,15 +37,6 @@ "version": "0.11.0" } }, - { - "package": "Path.swift", - "repositoryURL": "https://github.com/mxcl/Path.swift.git", - "state": { - "branch": null, - "revision": "f062ed9ce3729fb3ba3f43573136d2a0e0c6d209", - "version": "1.0.0" - } - }, { "package": "routing-kit", "repositoryURL": "https://github.com/vapor/routing-kit.git", diff --git a/Package.swift b/Package.swift index 6b252c9..482428b 100644 --- a/Package.swift +++ b/Package.swift @@ -35,7 +35,6 @@ let package = Package( .package(url: "https://github.com/apple/swift-crypto.git", from: "1.1.0"), .package(url: "https://github.com/JohnSundell/Splash.git", from: "0.14.0"), .package(url: "https://github.com/swiftwasm/WasmTransformer", .upToNextMinor(from: "0.0.2")), - .package(url: "https://github.com/mxcl/Path.swift.git", .exact("1.0.0")), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module @@ -110,11 +109,10 @@ let package = Package( "CartonHelpers", .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"), .product(name: "ArgumentParser", package: "swift-argument-parser"), - .product(name: "Path", package: "Path.swift"), ] ), .testTarget( - name: "CartonCLITests", + name: "CartonCommandTests", dependencies: [ "CartonCLI", ] diff --git a/Package@swift-5.2.swift b/Package@swift-5.2.swift index 5317299..b9a29ab 100644 --- a/Package@swift-5.2.swift +++ b/Package@swift-5.2.swift @@ -106,7 +106,7 @@ let package = Package( ] ), .testTarget( - name: "CartonCLITests", + name: "CartonCommandTests", dependencies: [ "CartonCLI", ] diff --git a/Sources/SwiftToolchain/Toolchain.swift b/Sources/SwiftToolchain/Toolchain.swift index 6e78aa6..544942f 100644 --- a/Sources/SwiftToolchain/Toolchain.swift +++ b/Sources/SwiftToolchain/Toolchain.swift @@ -172,10 +172,11 @@ public final class Toolchain { let package = try self.package.get() let targetPaths = package.targets.compactMap { target -> String? in + guard let path = target.path else { switch target.type { case .regular: - return "Sources/\(target.name)" + return RelativePath("Sources").appending(component: target.name).pathString case .test, .system: return nil } diff --git a/Sources/SwiftToolchain/ToolchainManagement.swift b/Sources/SwiftToolchain/ToolchainManagement.swift index e4d8cae..2f11f05 100644 --- a/Sources/SwiftToolchain/ToolchainManagement.swift +++ b/Sources/SwiftToolchain/ToolchainManagement.swift @@ -169,7 +169,7 @@ public class ToolchainSystem { #if os(macOS) let platformSuffixes = ["osx", "catalina", "macos"] #elseif os(Linux) - let releaseFile = AbsolutePath("/etc/lsb-release") + let releaseFile = AbsolutePath("/etc").appending(component: "lsb-release") guard fileSystem.isFile(releaseFile) else { throw ToolchainError.unsupportedOperatingSystem } diff --git a/Sources/carton-release/HashArchive.swift b/Sources/carton-release/HashArchive.swift index be94cb5..ce5471d 100644 --- a/Sources/carton-release/HashArchive.swift +++ b/Sources/carton-release/HashArchive.swift @@ -39,7 +39,10 @@ struct HashArchive: ParsableCommand { let terminal = InteractiveWriter.stdout let cwd = localFileSystem.currentWorkingDirectory! let staticPath = AbsolutePath(cwd, "static") - let dotFilesStaticPath = AbsolutePath(localFileSystem.homeDirectory, ".carton/static") + let dotFilesStaticPath = localFileSystem.homeDirectory.appending( + components: ".carton", + "static" + ) try localFileSystem.createDirectory(dotFilesStaticPath, recursive: true) let hashes = try ["dev", "bundle", "test"].map { entrypoint -> (String, String) in @@ -85,7 +88,10 @@ struct HashArchive: ParsableCommand { """ try localFileSystem.writeFileContents( - AbsolutePath(cwd, RelativePath("Sources/carton/Server/StaticArchive.swift")), + AbsolutePath( + cwd, + RelativePath("Sources").appending(components: "carton", "Server", "StaticArchive.swift") + ), bytes: ByteString(encodingAsUTF8: hashesFileContent) ) } diff --git a/Tests/CartonCLITests/DevCommandTests.swift b/Tests/CartonCLITests/DevCommandTests.swift deleted file mode 100644 index dbd3dc2..0000000 --- a/Tests/CartonCLITests/DevCommandTests.swift +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2020 Carton contributors -// -// 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. -// -// Created by Cavelle Benjamin on Dec/20/20. -// - -@testable import CartonCLI -import XCTest - -final class DevCommandTests: XCTestCase { - func testDefaultArgumentParsing() throws { - // given - let arguments: [String] = [] - - // when - - AssertParse(Dev.self, arguments) { command in - // then - XCTAssertNotNil(command) - } - } - - func testHelpString() throws { - // given - let expectation = - """ - OVERVIEW: Watch the current directory, host the app, rebuild on change. - - USAGE: carton dev [--product ] [--destination ] [--custom-index-page ] [--release] [--verbose] [--port ] [--skip-auto-open] - - OPTIONS: - --product Specify name of an executable product in development. - --destination - This option has no effect and will be removed in a - future version of `carton` - --custom-index-page - Specify a path to a custom `index.html` file to be - used for your app. - --release When specified, build in the release mode. - -v, --verbose Don't clear terminal window after files change. - -p, --port Set the HTTP port the development server will run on. - (default: 8080) - --skip-auto-open Skip automatically opening app in system browser. - --version Show the version. - -h, --help Show help information. - """ - // when - // then - - AssertExecuteCommand(command: "carton dev -h", expected: expectation) - } -} diff --git a/Tests/CartonCLITests/CommandTestHelper.swift b/Tests/CartonCommandTests/CommandTestHelper.swift similarity index 74% rename from Tests/CartonCLITests/CommandTestHelper.swift rename to Tests/CartonCommandTests/CommandTestHelper.swift index 3dff4c7..d417165 100644 --- a/Tests/CartonCLITests/CommandTestHelper.swift +++ b/Tests/CartonCommandTests/CommandTestHelper.swift @@ -177,18 +177,24 @@ public func AssertHelp( ) } +public class EmptyTest: XCTestCase {} + +extension EmptyTest: Testable {} + public extension XCTest { - var debugURL: URL { - let bundleURL = Bundle(for: type(of: self)).bundleURL + static var debugURL: URL { + let bundleURL = Bundle(for: EmptyTest.self).bundleURL return bundleURL.lastPathComponent.hasSuffix("xctest") ? bundleURL.deletingLastPathComponent() : bundleURL } - func AssertExecuteCommand( + static func AssertExecuteCommand( command: String, + cwd: URL? = nil, // To allow for testing of file based output expected: String? = nil, exitCode: ExitCode = .success, + debug: Bool = false, file: StaticString = #file, line: UInt = #line ) { let splitCommand = command.split(separator: " ") @@ -210,6 +216,10 @@ public extension XCTest { } process.arguments = arguments + if let workingDirectory = cwd { + process.currentDirectoryURL = workingDirectory + } + let output = Pipe() process.standardOutput = output let error = Pipe() @@ -229,6 +239,8 @@ public extension XCTest { let outputActual = String(data: outputData, encoding: .utf8)! .trimmingCharacters(in: .whitespacesAndNewlines) + if debug { print(outputActual) } + let errorData = error.fileHandleForReading.readDataToEndOfFile() let errorActual = String(data: errorData, encoding: .utf8)! .trimmingCharacters(in: .whitespacesAndNewlines) @@ -244,4 +256,70 @@ public extension XCTest { XCTAssertEqual(process.terminationStatus, exitCode.rawValue, file: file, line: line) } + + func AssertExecuteCommand( + command: String, + cwd: URL? = nil, // To allow for testing of file based output + expected: String? = nil, + exitCode: ExitCode = .success, + debug: Bool = false, + file: StaticString = #file, line: UInt = #line + ) { + let splitCommand = command.split(separator: " ") + let arguments = splitCommand.dropFirst().map(String.init) + + let commandName = String(splitCommand.first!) + let commandURL = XCTest.debugURL.appendingPathComponent(commandName) + guard (try? commandURL.checkResourceIsReachable()) ?? false else { + XCTFail("No executable at '\(commandURL.standardizedFileURL.path)'.", + file: file, line: line) + return + } + + let process = Process() + if #available(macOS 10.13, *) { + process.executableURL = commandURL + } else { + process.launchPath = commandURL.path + } + process.arguments = arguments + + if let workingDirectory = cwd { + process.currentDirectoryURL = workingDirectory + } + + let output = Pipe() + process.standardOutput = output + let error = Pipe() + process.standardError = error + + if #available(macOS 10.13, *) { + guard (try? process.run()) != nil else { + XCTFail("Couldn't run command process.", file: file, line: line) + return + } + } else { + process.launch() + } + process.waitUntilExit() + + let outputData = output.fileHandleForReading.readDataToEndOfFile() + let outputActual = String(data: outputData, encoding: .utf8)! + .trimmingCharacters(in: .whitespacesAndNewlines) + + if debug { print(outputActual) } + + let errorData = error.fileHandleForReading.readDataToEndOfFile() + let errorActual = String(data: errorData, encoding: .utf8)! + .trimmingCharacters(in: .whitespacesAndNewlines) + + if let expected = expected { + AssertEqualStringsIgnoringTrailingWhitespace( + expected, + errorActual + outputActual, + file: file, + line: line + ) + } + } } diff --git a/Tests/CartonCommandTests/DevCommandTests.swift b/Tests/CartonCommandTests/DevCommandTests.swift new file mode 100644 index 0000000..8425771 --- /dev/null +++ b/Tests/CartonCommandTests/DevCommandTests.swift @@ -0,0 +1,24 @@ +// Copyright 2020 Carton contributors +// +// 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. +// +// Created by Cavelle Benjamin on Dec/20/20. +// + +@testable import CartonCLI +import XCTest + +extension DevCommandTests: Testable {} + +// Dev Command stub +final class DevCommandTests: XCTestCase {} diff --git a/Tests/CartonCommandTests/InitCommandTests.swift b/Tests/CartonCommandTests/InitCommandTests.swift new file mode 100644 index 0000000..9b1532e --- /dev/null +++ b/Tests/CartonCommandTests/InitCommandTests.swift @@ -0,0 +1,76 @@ +// Copyright 2020 Carton contributors +// +// 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. +// +// Created by Cavelle Benjamin on Dec/20/20. +// + +@testable import CartonCLI +import TSCBasic +import XCTest + +extension InitCommandTests: Testable {} + +final class InitCommandTests: XCTestCase { + func testWithNoArguments() throws { + // given I've created a directory + let package = "wasp" + let packageDirectory = testFixturesDirectory.appending(component: package) + + // it's ok if there is nothing to delete + do { try packageDirectory.delete() } catch {} + + try packageDirectory.mkdir() + + XCTAssertTrue(packageDirectory.exists, "Did not create \(package) directory") + + AssertExecuteCommand( + command: "carton init", + cwd: packageDirectory.url + ) + + // Confirm that the files are actually in the folder + XCTAssertTrue(packageDirectory.ls().contains("Package.swift"), "Package.swift does not exist") + XCTAssertTrue(packageDirectory.ls().contains("README.md"), "README.md does not exist") + XCTAssertTrue(packageDirectory.ls().contains(".gitignore"), ".gitignore does not exist") + XCTAssertTrue(packageDirectory.ls().contains("Sources"), "Sources does not exist") + XCTAssertTrue( + packageDirectory.ls().contains("Sources/\(package)"), + "Sources/\(package) does not exist" + ) + XCTAssertTrue( + packageDirectory.ls().contains("Sources/\(package)/main.swift"), + "Sources/\(package)/main.swift does not exist" + ) + XCTAssertTrue(packageDirectory.ls().contains("Tests"), "Tests does not exist") + XCTAssertTrue( + packageDirectory.ls().contains("Tests/LinuxMain.swift"), + "Tests/LinuxMain.swift does not exist" + ) + XCTAssertTrue( + packageDirectory.ls().contains("Tests/\(package)Tests"), + "Tests/\(package)Tests does not exist" + ) + XCTAssertTrue( + packageDirectory.ls().contains("Tests/\(package)Tests/\(package)Tests.swift"), + "Tests/\(package)Tests/\(package)Tests.swift does not exist" + ) + XCTAssertTrue( + packageDirectory.ls().contains("Tests/\(package)Tests/XCTestManifests.swift"), + "Tests/\(package)Tests/XCTestManifests.swift does not exist" + ) + + // finally, clean up + try packageDirectory.delete() + } +} diff --git a/Tests/CartonCLITests/StringHelpers.swift b/Tests/CartonCommandTests/StringHelpers.swift similarity index 100% rename from Tests/CartonCLITests/StringHelpers.swift rename to Tests/CartonCommandTests/StringHelpers.swift diff --git a/Tests/CartonCommandTests/Testable.swift b/Tests/CartonCommandTests/Testable.swift new file mode 100644 index 0000000..5415214 --- /dev/null +++ b/Tests/CartonCommandTests/Testable.swift @@ -0,0 +1,91 @@ +// Copyright 2020 Carton contributors +// +// 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. +// +// Created by Cavelle Benjamin on Dec/20/20. +// + +import Foundation +import TSCBasic + +public protocol Testable { + var productsDirectory: AbsolutePath { get } + var testFixturesDirectory: AbsolutePath { get } + var packageDirectory: AbsolutePath { get } +} + +public extension Testable { + /// Returns path to the built products directory. + var productsDirectory: AbsolutePath { + #if os(macOS) + for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { + return AbsolutePath(bundle.bundleURL.deletingLastPathComponent().path) + } + fatalError("couldn't find the products directory") + #else + return AbsolutePath(Bundle.main.bundleURL.path) + #endif + } + + var testFixturesDirectory: AbsolutePath { + packageDirectory.appending(components: "Tests", "Fixtures") + } + + var packageDirectory: AbsolutePath { + // necessary if you are using xcode + if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil { + return productsDirectory + .parentDirectory + .parentDirectory + .parentDirectory + .parentDirectory + .parentDirectory + } + + return productsDirectory + .parentDirectory + .parentDirectory + .parentDirectory + } +} + +extension AbsolutePath { + func mkdir() throws { + _ = try FileManager.default.createDirectory( + atPath: pathString, + withIntermediateDirectories: true + ) + } + + func delete() throws { + _ = try FileManager.default.removeItem(atPath: pathString) + } + + var url: URL { + URL(fileURLWithPath: pathString) + } + + var exists: Bool { + FileManager.default.fileExists(atPath: pathString) + } + + func ls() -> [String] { + guard let paths = try? FileManager.default.subpathsOfDirectory(atPath: pathString) + else { return [] } + return paths + } + + static var home: AbsolutePath { + AbsolutePath(FileManager.default.homeDirectoryForCurrentUser.path) + } +}