1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
import Logging
import SystemPackage
import XCTest
@testable import SwiftSDKGenerator
extension FileManager {
func withTemporaryDirectory<T>(logger: Logger, cleanup: Bool = true, body: (URL) async throws -> T) async throws -> T {
// Create a temporary directory using a UUID. Throws if the directory already exists.
// The docs suggest using FileManager.url(for: .itemReplacementDirectory, ...) to create a temporary directory,
// but on Linux the directory name contains spaces, which means we need to be careful to quote it everywhere:
//
// `(A Document Being Saved By \(name))`
//
// https://github.com/swiftlang/swift-corelibs-foundation/blob/21b3196b33a64d53a0989881fc9a486227b4a316/Sources/Foundation/FileManager.swift#L152
var logger = logger
let temporaryDirectory = self.temporaryDirectory.appendingPathComponent(UUID().uuidString)
logger[metadataKey: "temporaryDirectory"] = "\(temporaryDirectory.path)"
try createDirectory(at: temporaryDirectory, withIntermediateDirectories: false)
defer {
// Best effort cleanup.
do {
if cleanup {
try removeItem(at: temporaryDirectory)
logger.info("Removed temporary directory")
} else {
logger.info("Keeping temporary directory")
}
} catch {}
}
logger.info("Created temporary directory")
return try await body(temporaryDirectory)
}
}
final class EndToEndTests: XCTestCase {
private let testcases = [
#"""
// Default program generated by swift package init
print("Hello, world!")
"""#,
#"""
// Check that libc_nonshared.a is linked properly
import Foundation
func fin() -> Void {
print("exiting")
}
atexit(fin)
"""#,
]
private let logger = Logger(label: "swift-sdk-generator")
// Building an SDK requires running the sdk-generator with `swift run swift-sdk-generator`.
// This takes a lock on `.build`, but if the tests are being run by `swift test` the outer Swift Package Manager
// instance will already hold this lock, causing the test to deadlock. We can work around this by giving
// the `swift run swift-sdk-generator` instance its own scratch directory.
func buildSDK(inDirectory packageDirectory: FilePath, scratchPath: String, withArguments runArguments: String) async throws -> String {
let generatorOutput = try await Shell.readStdout(
"cd \(packageDirectory) && swift run --scratch-path \"\(scratchPath)\" swift-sdk-generator make-linux-sdk \(runArguments)"
)
let installCommand = try XCTUnwrap(generatorOutput.split(separator: "\n").first {
$0.contains("swift experimental-sdk install")
})
let bundleName = try XCTUnwrap(
FilePath(String(XCTUnwrap(installCommand.split(separator: " ").last))).components.last
).stem
let installedSDKs = try await Shell.readStdout("swift experimental-sdk list").components(separatedBy: "\n")
// Make sure this bundle hasn't been installed already.
if installedSDKs.contains(bundleName) {
try await Shell.run("swift experimental-sdk remove \(bundleName)")
}
let installOutput = try await Shell.readStdout(String(installCommand))
XCTAssertTrue(installOutput.contains("successfully installed"))
return bundleName
}
func testPackageInitExecutable() async throws {
if ProcessInfo.processInfo.environment.keys.contains("JENKINS_URL") {
throw XCTSkip("EndToEnd tests cannot currently run in CI: https://github.com/swiftlang/swift-sdk-generator/issues/145")
}
var packageDirectory = FilePath(#filePath)
packageDirectory.removeLastComponent()
packageDirectory.removeLastComponent()
// Do multiple runs with different sets of arguments.
// Test with no arguments by default:
var possibleArguments = [""]
do {
try await Shell.run("docker ps")
possibleArguments.append("--with-docker --linux-distribution-name rhel --linux-distribution-version ubi9")
} catch {
self.logger.warning("Docker CLI does not seem to be working, skipping tests that involve Docker.")
}
for runArguments in possibleArguments {
if runArguments.contains("rhel") {
// Temporarily skip the RHEL-based SDK. XCTSkip() is not suitable as it would skipping the entire test case
logger.warning("RHEL-based SDKs currently do not work with Swift 6.0: https://github.com/swiftlang/swift-sdk-generator/issues/138")
continue
}
let bundleName = try await FileManager.default.withTemporaryDirectory(logger: logger) { tempDir in
try await buildSDK(inDirectory: packageDirectory, scratchPath: tempDir.path, withArguments: runArguments)
}
for testcase in self.testcases {
try await FileManager.default.withTemporaryDirectory(logger: logger) { tempDir in
let testPackageURL = tempDir.appendingPathComponent("swift-sdk-generator-test")
let testPackageDir = FilePath(testPackageURL.path)
try FileManager.default.createDirectory(atPath: testPackageDir.string, withIntermediateDirectories: true)
try await Shell.run("swift package --package-path \(testPackageDir) init --type executable")
let main_swift = testPackageURL.appendingPathComponent("Sources/main.swift")
try testcase.write(to: main_swift, atomically: true, encoding: .utf8)
var buildOutput = try await Shell.readStdout(
"swift build --package-path \(testPackageDir) --experimental-swift-sdk \(bundleName)"
)
XCTAssertTrue(buildOutput.contains("Build complete!"))
try await Shell.run("rm -rf \(testPackageDir.appending(".build"))")
buildOutput = try await Shell.readStdout(
"swift build --package-path \(testPackageDir) --experimental-swift-sdk \(bundleName) --static-swift-stdlib"
)
XCTAssertTrue(buildOutput.contains("Build complete!"))
}
}
}
}
func testRepeatedSDKBuilds() async throws {
if ProcessInfo.processInfo.environment.keys.contains("JENKINS_URL") {
throw XCTSkip("EndToEnd tests cannot currently run in CI: https://github.com/swiftlang/swift-sdk-generator/issues/145")
}
var packageDirectory = FilePath(#filePath)
packageDirectory.removeLastComponent()
packageDirectory.removeLastComponent()
// Test that an existing SDK can be rebuilt without cleaning up.
// Test with no arguments by default:
var possibleArguments = [""]
do {
try await Shell.run("docker ps")
possibleArguments.append("--with-docker --linux-distribution-name rhel --linux-distribution-version ubi9")
} catch {
self.logger.warning("Docker CLI does not seem to be working, skipping tests that involve Docker.")
}
for runArguments in possibleArguments {
if runArguments.contains("rhel") {
// Temporarily skip the RHEL-based SDK. XCTSkip() is not suitable as it would skipping the entire test case
logger.warning("RHEL-based SDKs currently do not work with Swift 6.0: https://github.com/swiftlang/swift-sdk-generator/issues/138")
continue
}
try await FileManager.default.withTemporaryDirectory(logger: logger) { tempDir in
let _ = try await buildSDK(inDirectory: packageDirectory, scratchPath: tempDir.path, withArguments: runArguments)
let _ = try await buildSDK(inDirectory: packageDirectory, scratchPath: tempDir.path, withArguments: runArguments)
}
}
}
}
|