Add tests for `dev` and `bundle` commands (#196)

* carton dev and bundle default command tests

* removed tests_output

* removed .testfile

* carton dev and bundle default command tests

* removed localhost as localURL

* attempt to fix folder name issue

* removed Fixtures test folder

* added Fixtures test folder

* attempt to fix folder name issue

* attempt to fix server connect issue

* fixed dev testWithNoArguments with sleep delay

* Debugging bundle command for github actions

* Install wasmr prior to swift test

* Debug clean up

* Debug clean up

* Update Tests/CartonCommandTests/CommandTestHelper.swift

Co-authored-by: Max Desiatov <max@desiatov.com>

Co-authored-by: thecb4 <cavelle@tehcb4.io>
Co-authored-by: Max Desiatov <max@desiatov.com>
This commit is contained in:
thecb4 2020-12-28 13:13:27 -05:00 committed by GitHub
parent d7352b5a25
commit 22165481bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 402 additions and 88 deletions

View File

@ -15,9 +15,9 @@ jobs:
- name: Build on macOS 10.15 with Swift 5.2
run: |
sudo xcode-select --switch /Applications/Xcode_11.7.app/Contents/Developer
brew bundle
swift test -c release --enable-test-discovery
swift build -c release
brew bundle
cd TestApp && ../.build/release/carton test
../.build/release/carton test --environment defaultBrowser
../.build/release/carton bundle
@ -33,9 +33,9 @@ jobs:
- name: Build on macOS 11.0 with Swift 5.2
run: |
sudo xcode-select --switch /Applications/Xcode_11.7.app/Contents/Developer
brew bundle
swift test -c release --enable-test-discovery
swift build -c release
brew bundle
cd TestApp && ../.build/release/carton test
../.build/release/carton test --environment defaultBrowser
../.build/release/carton bundle
@ -50,9 +50,9 @@ jobs:
- name: Build on macOS 10.15 with Swift 5.3
run: |
sudo xcode-select --switch /Applications/Xcode_12.app/Contents/Developer
brew bundle
swift test -c release --enable-test-discovery
swift build -c release
brew bundle
cd TestApp && ../.build/release/carton test
../.build/release/carton test --environment defaultBrowser
../.build/release/carton bundle
@ -67,9 +67,9 @@ jobs:
- name: Build on macOS 11.0 with Swift 5.3
run: |
sudo xcode-select --switch /Applications/Xcode_12.2.app/Contents/Developer
brew bundle
swift test -c release --enable-test-discovery
swift build -c release
brew bundle
cd TestApp && ../.build/release/carton test
../.build/release/carton test --environment defaultBrowser
../.build/release/carton bundle
@ -84,11 +84,11 @@ jobs:
- name: Build on Ubuntu 18.04 with Swift 5.3
run: |
swift test -c release --enable-test-discovery
swift build -c release
sudo ./install_ubuntu_deps.sh
curl https://get.wasmer.io -sSfL | sh
source /home/runner/.wasmer/wasmer.sh
swift test -c release --enable-test-discovery
swift build -c release
cd TestApp && ../.build/release/carton test
../.build/release/carton bundle
env:
@ -101,11 +101,11 @@ jobs:
- uses: actions/checkout@v2
- name: Build on Ubuntu 20.04 with Swift 5.3
run: |
swift test -c release --enable-test-discovery
swift build -c release
sudo ./install_ubuntu_deps.sh
curl https://get.wasmer.io -sSfL | sh
source /home/runner/.wasmer/wasmer.sh
swift test -c release --enable-test-discovery
swift build -c release
cd TestApp && ../.build/release/carton test
../.build/release/carton bundle
env:

View File

@ -115,6 +115,8 @@ let package = Package(
name: "CartonCommandTests",
dependencies: [
"CartonCLI",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "AsyncHTTPClient", package: "async-http-client"),
]
),
]

View File

@ -109,6 +109,8 @@ let package = Package(
name: "CartonCommandTests",
dependencies: [
"CartonCLI",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "AsyncHTTPClient", package: "async-http-client"),
]
),
]

View File

@ -0,0 +1,59 @@
// 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/25/20.
//
@testable import CartonCLI
import TSCBasic
import XCTest
extension BundleCommandTests: Testable {}
final class BundleCommandTests: XCTestCase {
func testWithNoArguments() throws {
// given I've created a directory
let package = "Milk"
let packageDirectory = testFixturesDirectory.appending(component: package)
let bundle = "Bundle"
let bundleDirectory = packageDirectory.appending(component: bundle)
// it's ok if there is nothing to delete
do { try bundleDirectory.delete() } catch {}
XCTAssertFalse(bundleDirectory.exists, "The Bundle directory should not exist")
AssertExecuteCommand(
command: "carton bundle",
cwd: packageDirectory.url
)
// Confirm that the files are actually in the folder
XCTAssertTrue(bundleDirectory.exists, "The Bundle directory should exist")
XCTAssertTrue(bundleDirectory.ls().contains("index.html"), "Bundle does not have index.html")
XCTAssertFalse(
(bundleDirectory.ls().filter { $0.contains("wasm") }).isEmpty,
".wasm file does not exist"
)
XCTAssertFalse(
(bundleDirectory.ls().filter { $0.contains("js") }).isEmpty,
".js does not exist"
)
// finally, clean up
try bundleDirectory.delete()
try packageDirectory.appending(component: ".build").delete()
}
}

View File

@ -1,4 +1,4 @@
//===----------------------------------------------------------*- swift -*-===//
// ===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift Argument Parser open source project
//
@ -7,11 +7,20 @@
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
// ===----------------------------------------------------------------------===//
import ArgumentParser
import XCTest
public extension ExitCode {
static var quit = ExitCode(SIGQUIT)
}
public func stop(process id: Int32, exitCode: ExitCode = .success) {
print("sending stop command")
kill(id, exitCode.rawValue)
}
// extensions to the ParsableArguments protocol to facilitate XCTestExpectation support
public protocol TestableParsableArguments: ParsableArguments {
var didValidateExpectation: XCTestExpectation { get }
@ -177,19 +186,63 @@ public func AssertHelp<T: ParsableCommand, U: ParsableCommand>(
)
}
public class EmptyTest: XCTestCase {}
extension EmptyTest: Testable {}
public extension XCTest {
static var debugURL: URL {
let bundleURL = Bundle(for: EmptyTest.self).bundleURL
var debugURL: URL {
let bundleURL = Bundle(for: type(of: self)).bundleURL
return bundleURL.lastPathComponent.hasSuffix("xctest")
? bundleURL.deletingLastPathComponent()
: bundleURL
}
static func AssertExecuteCommand(
func executeCommand(
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
) -> Process? {
let splitCommand = command.split(separator: " ")
let arguments = splitCommand.dropFirst().map(String.init)
let commandName = String(splitCommand.first!)
let commandURL = debugURL.appendingPathComponent(commandName)
guard (try? commandURL.checkResourceIsReachable()) ?? false else {
XCTFail("No executable at '\(commandURL.standardizedFileURL.path)'.",
file: file, line: line)
return nil
}
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 nil
}
} else {
process.launch()
}
return process
}
func AssertExecuteCommand(
command: String,
cwd: URL? = nil, // To allow for testing of file based output
expected: String? = nil,
@ -233,74 +286,7 @@ public extension XCTest {
} 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
)
}
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()

View File

@ -15,10 +15,108 @@
// Created by Cavelle Benjamin on Dec/20/20.
//
import AsyncHTTPClient
@testable import CartonCLI
import XCTest
extension DevCommandTests: Testable {}
// Dev Command stub
final class DevCommandTests: XCTestCase {}
final class DevCommandTests: XCTestCase {
var client: HTTPClient?
override func tearDown() {
print("shutting down client")
try? client?.syncShutdown()
client = nil
}
func testWithNoArguments() throws {
let url = "http://127.0.0.1:8080"
// client time out for connecting and responding
let timeOut: Int64 = 60
// client delay... let the server start up
let delay: UInt32 = 30
// only try 5 times.
let polls = 5
// the directory was built using `carton init --template tokamak`
let package = "Milk"
let packageDirectory = testFixturesDirectory.appending(component: package)
XCTAssertTrue(
packageDirectory.exists,
"\(package) directory does not exist. Cannot execute tests."
)
do { try packageDirectory.appending(component: ".build").delete() } catch {}
let expectedHtml =
"""
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script type="text/javascript" src="dev.js"></script>
</head>
<body>
</body>
</html>
"""
guard let process = executeCommand(
command: "carton dev --verbose",
cwd: packageDirectory.url,
debug: true
) else {
XCTFail("Could not create process")
return
}
let timeout = HTTPClient.Configuration.Timeout(
connect: .seconds(timeOut),
read: .seconds(timeOut)
)
client = HTTPClient(eventLoopGroupProvider: .createNew,
configuration: HTTPClient.Configuration(timeout: timeout))
var response: HTTPClient.Response?
var count = 0
// give the server some time to start
repeat {
sleep(delay)
response = try? client?.get(url: url).wait()
count += 1
} while count < polls && response == nil
// end the process regardless of success
process.terminate()
if let response = response {
XCTAssertTrue(response.status == .ok, "Response was not ok")
guard let data = (response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) })
else {
XCTFail("Could not map data")
return
}
guard let actualHtml = String(data: data, encoding: .utf8) else {
XCTFail("Could convert data to string")
return
}
// test may be brittle as the template may change over time.
XCTAssertEqual(actualHtml, expectedHtml, "HTML output does not match")
} else {
print("no response from server after \(count) tries or \(Int(count) * Int(delay)) seconds")
XCTFail("Could not reach server")
}
// clean up
do { try packageDirectory.appending(component: ".build").delete() } catch {}
}
}

View File

@ -1,4 +1,4 @@
//===----------------------------------------------------------*- swift -*-===//
// ===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift Argument Parser open source project
//
@ -7,7 +7,7 @@
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
// ===----------------------------------------------------------------------===//
extension Substring {
func trimmed() -> Substring {

5
Tests/Fixtures/Milk/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/

View File

@ -0,0 +1,52 @@
{
"object": {
"pins": [
{
"package": "JavaScriptKit",
"repositoryURL": "https://github.com/swiftwasm/JavaScriptKit.git",
"state": {
"branch": null,
"revision": "b7a02434c6e973c08c3fd5069105ef44dd82b891",
"version": "0.9.0"
}
},
{
"package": "OpenCombine",
"repositoryURL": "https://github.com/TokamakUI/OpenCombine.git",
"state": {
"branch": null,
"revision": "bccff3e7c84bc559e1aa0aa9ca878400360d439d",
"version": "0.12.0-alpha2"
}
},
{
"package": "OpenCombineJS",
"repositoryURL": "https://github.com/swiftwasm/OpenCombineJS.git",
"state": {
"branch": null,
"revision": "b346f955ed21ab44576e204a7554210c77f69b9b",
"version": "0.0.1"
}
},
{
"package": "Runtime",
"repositoryURL": "https://github.com/MaxDesiatov/Runtime.git",
"state": {
"branch": null,
"revision": "a617ead8a125a97e69d6100e4d27922006e82e0a",
"version": "2.1.2"
}
},
{
"package": "Tokamak",
"repositoryURL": "https://github.com/TokamakUI/Tokamak",
"state": {
"branch": null,
"revision": "b4b0efca4de0ce9cff771649dfbcd13c5736e7f0",
"version": "0.6.1"
}
}
]
},
"version": 1
}

View File

@ -0,0 +1,24 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "Milk",
platforms: [.macOS(.v11)],
products: [
.executable(name: "Milk", targets: ["Milk"]),
],
dependencies: [
.package(name: "Tokamak", url: "https://github.com/TokamakUI/Tokamak", from: "0.6.1"),
],
targets: [
.target(
name: "Milk",
dependencies: [
.product(name: "TokamakShim", package: "Tokamak"),
]
),
.testTarget(
name: "MilkTests",
dependencies: ["Milk"]
),
]
)

View File

@ -0,0 +1,3 @@
# Milk
A description of this package.

View File

@ -0,0 +1,19 @@
import TokamakDOM
struct TokamakApp: App {
var body: some Scene {
WindowGroup("Tokamak App") {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
Text("Hello, world!")
}
}
// @main attribute is not supported in SwiftPM apps.
// See https://bugs.swift.org/browse/SR-12683 for more details.
TokamakApp.main()

View File

@ -0,0 +1,7 @@
import XCTest
import MilkTests
var tests = [XCTestCaseEntry]()
tests += MilkTests.allTests()
XCTMain(tests)

View File

@ -0,0 +1,47 @@
import class Foundation.Bundle
import XCTest
final class MilkTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
// Some of the APIs that we use below are available in macOS 10.13 and above.
guard #available(macOS 10.13, *) else {
return
}
let fooBinary = productsDirectory.appendingPathComponent("Milk")
let process = Process()
process.executableURL = fooBinary
let pipe = Pipe()
process.standardOutput = pipe
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
XCTAssertEqual(output, "Hello, world!\n")
}
/// Returns path to the built products directory.
var productsDirectory: URL {
#if os(macOS)
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
return bundle.bundleURL.deletingLastPathComponent()
}
fatalError("couldn't find the products directory")
#else
return Bundle.main.bundleURL
#endif
}
static var allTests = [
("testExample", testExample),
]
}

View File

@ -0,0 +1,9 @@
import XCTest
#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
[
testCase(MilkTests.allTests),
]
}
#endif

1
package-lock.json generated
View File

@ -4433,6 +4433,7 @@
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"