diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 69e784aa..29a30b3e 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -33,8 +33,8 @@ jobs: - name: jammy version: "22.04" swift: - - repo: swift - version: "6.0" + - repo: swiftlang/swift + version: "nightly-6.1" - repo: swiftlang/swift version: "nightly-main" container: ${{ matrix.swift.repo }}:${{ matrix.swift.version }}-${{ matrix.os.name }} diff --git a/Sources/MMIOUtilities/ShellCommand.swift b/Sources/MMIOUtilities/ShellCommand.swift index 5739d1f0..43350197 100644 --- a/Sources/MMIOUtilities/ShellCommand.swift +++ b/Sources/MMIOUtilities/ShellCommand.swift @@ -53,7 +53,7 @@ public func sh( _ command: String, collectStandardOutput: Bool = true, collectStandardError: Bool = true -) throws -> String { +) throws(ShellCommandError) -> String { let process = Process() process.executableURL = URL(fileURLWithPath: "/bin/sh") process.arguments = ["-c", "export PATH=$PATH:~/bin; \(command)"] diff --git a/Tests/MMIOFileCheckTests/FileCheck/SimpleFileCheck.swift b/Tests/MMIOFileCheckTests/FileCheck/SimpleFileCheck.swift index 7554eed9..e69638e1 100644 --- a/Tests/MMIOFileCheckTests/FileCheck/SimpleFileCheck.swift +++ b/Tests/MMIOFileCheckTests/FileCheck/SimpleFileCheck.swift @@ -16,18 +16,18 @@ import MMIOUtilities /// /// SimpleFileCheck is not intended for general use. struct SimpleFileCheck { - var inputFileURL: URL - var outputFileURL: URL + var inputFile: URL + var outputFile: URL } extension SimpleFileCheck { func run() -> [LLVMDiagnostic] { // Load the input file and split it into lines. If we can't load the file, // return and report diagnostics. - let inputPath = self.inputFileURL.path + let inputPath = self.inputFile.path let input: String do { - input = try String(contentsOf: self.inputFileURL, encoding: .utf8) + input = try String(contentsOf: self.inputFile, encoding: .utf8) } catch { return [.failedToLoadFile(at: inputPath, error: error)] } @@ -37,10 +37,10 @@ extension SimpleFileCheck { // Load the output file and split it into lines. If we can't load the file, // return and report diagnostics. - let outputPath = self.outputFileURL.path + let outputPath = self.outputFile.path let output: String do { - output = try String(contentsOf: self.outputFileURL, encoding: .utf8) + output = try String(contentsOf: self.outputFile, encoding: .utf8) } catch { return [.failedToLoadFile(at: outputPath, error: error)] } diff --git a/Tests/MMIOFileCheckTests/MMIOFileCheckTestCase.swift b/Tests/MMIOFileCheckTests/MMIOFileCheckTestCase.swift index 94adb7ca..4b564af5 100644 --- a/Tests/MMIOFileCheckTests/MMIOFileCheckTestCase.swift +++ b/Tests/MMIOFileCheckTests/MMIOFileCheckTestCase.swift @@ -15,59 +15,48 @@ import Dispatch import Foundation import MMIOUtilities -import XCTest +import Testing -final class MMIOFileCheckTests: XCTestCase, @unchecked Sendable { - func test() throws { - let selfFileURL = URL(fileURLWithPath: #filePath) - let selfDirectoryURL = - selfFileURL - .deletingLastPathComponent() - let packageDirectoryURL = - selfDirectoryURL - .deletingLastPathComponent() - .deletingLastPathComponent() - let testsDirectoryURL = - selfDirectoryURL - .appendingPathComponent("Tests") +struct MMIOFileCheckTests: @unchecked Sendable { + struct Configuration { + var hasLLVMFileCheck: Bool + var toolchainID: String + var packageDirectory: URL + var buildOutputs: URL - // Get a list of the test files from disk. - print("Finding Tests...") - let testFileURLs = FileManager.default.files( - inDirectory: testsDirectoryURL, - withPathExtension: "swift") - - // Run test setup step. - print("Running Test Setup...") - let start = DispatchTime.now() - let (hasLLVMFileCheck, toolchainID, buildOutputsURL) = try Self.prerun( - packageDirectoryURL: packageDirectoryURL) - let end = DispatchTime.now() + var buildModulesDirectory: URL { + self.buildOutputs + .appendingPathComponent("Modules") + } - // `DispatchTime.distance(to:)` is unavailable on linux. - let duration = end.uptimeNanoseconds - start.uptimeNanoseconds - print("Setup took \(duration) nanoseconds") + var buildMMIOMacrosFile: URL { + self.buildOutputs + .appendingPathComponent("MMIOMacros-tool") + } - print("Running Tests...") - DispatchQueue.concurrentPerform(iterations: testFileURLs.count) { index in - let testFileURL = testFileURLs[index] - let diagnostics = Self.run( - testFileURL: testFileURL, - hasLLVMFileCheck: hasLLVMFileCheck, - toolchainID: toolchainID, - packageDirectoryURL: packageDirectoryURL, - buildOutputsURL: buildOutputsURL) - for diagnostic in diagnostics { - self.record(diagnostic: diagnostic) - } + var mmioVolatileDirectory: URL { + self.packageDirectory + .appendingPathComponent("Sources") + .appendingPathComponent("MMIOVolatile") } + } - print("Finished running \(testFileURLs.count) tests") + static let _configuration = Result { try Self.configure() } + static var configuration: Configuration { + get throws { try self._configuration.get() } } - static func prerun( - packageDirectoryURL: URL - ) throws -> (Bool, String, URL) { + /// The test files in this target's "Tests" subdirectory. + static let testFiles: [URL] = { + let testsDirectory = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .appendingPathComponent("Tests") + return FileManager.default.files( + inDirectory: testsDirectory, + withPathExtension: "swift") + }() + + static func configure() throws -> Configuration { let environment = ProcessInfo.processInfo.environment let ci: Bool @@ -80,8 +69,9 @@ final class MMIOFileCheckTests: XCTestCase, @unchecked Sendable { print("Determining Swift Toolchain...") let toolchainID: String - - if let toolchain = environment["TOOLCHAINS"] { + if true { + toolchainID = "org.swift.62202412101a" + } else if let toolchain = environment["TOOLCHAINS"] { print("TOOLCHAINS set.") toolchainID = toolchain } else if ci { @@ -103,36 +93,35 @@ final class MMIOFileCheckTests: XCTestCase, @unchecked Sendable { print("Failed to locate toolchain by plist: \(error)") toolchainID = "" } - #else - toolchainID = "" #endif } print("Using TOOLCHAINS=\(toolchainID)") let hasLLVMFileCheck: Bool - do { - print("Locating FileCheck...") - _ = try sh("which FileCheck") - hasLLVMFileCheck = environment["SWIFT_MMIO_USE_SIMPLE_FILECHECK"] == nil - } catch { - print("Failed to locate FileCheck...") + if environment["SWIFT_MMIO_USE_SIMPLE_FILECHECK"] != nil { + print("Using Simple FileCheck (forced)") hasLLVMFileCheck = false - } - - if hasLLVMFileCheck { + } else if case .success = Result(catching: { try sh("which FileCheck") }) { print("Using LLVM FileCheck") + hasLLVMFileCheck = true } else { - print("Using Simple FileCheck") + print("Using Simple FileCheck (no llvm)") + hasLLVMFileCheck = false } + let packageDirectory = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + print("Determining Dependency Paths...") - let buildOutputsURL = URL( + let buildOutputs = URL( fileURLWithPath: try sh( """ TOOLCHAINS=\(toolchainID) swift build \ --ignore-lock \ --configuration release \ - --package-path \(packageDirectoryURL.path) \ + --package-path \(packageDirectory.path) \ --show-bin-path """)) @@ -143,106 +132,60 @@ final class MMIOFileCheckTests: XCTestCase, @unchecked Sendable { TOOLCHAINS=\(toolchainID) swift build \ --ignore-lock \ --configuration release \ - --package-path \(packageDirectoryURL.path) \ - --verbose + --package-path \(packageDirectory.path) """, collectStandardOutput: false) #endif - return (hasLLVMFileCheck, toolchainID, buildOutputsURL) + return Configuration( + hasLLVMFileCheck: hasLLVMFileCheck, + toolchainID: toolchainID, + packageDirectory: packageDirectory, + buildOutputs: buildOutputs) } - static func run( - testFileURL: URL, - hasLLVMFileCheck: Bool, - toolchainID: String, - packageDirectoryURL: URL, - buildOutputsURL: URL - ) -> [LLVMDiagnostic] { - do { - print("Running: \(testFileURL.lastPathComponent)") + @Test(arguments: Self.testFiles) + func fileCheck(testFile: URL) throws { + let configuration = try Self.configuration - let testOutputFileURL = - buildOutputsURL - .appendingPathComponent(testFileURL.lastPathComponent) - .appendingPathExtension("ll") - let buildModulesDirectoryURL = - buildOutputsURL - .appendingPathComponent("Modules") - let buildMMIOMacrosFileURL = - buildOutputsURL - .appendingPathComponent("MMIOMacros-tool") - let mmioVolatileDirectoryURL = - packageDirectoryURL - .appendingPathComponent("Sources") - .appendingPathComponent("MMIOVolatile") + let testOutputFile = configuration.buildOutputs + .appendingPathComponent(testFile.lastPathComponent) + .appendingPathExtension("ll") - _ = try sh( + let compileSuccess = LLVMDiagnostic.parsingShellOutput( + """ + TOOLCHAINS=\(configuration.toolchainID) swiftc \ + -emit-ir \(testFile.path) \ + -o \(testOutputFile.path) \ + -O -wmo \ + -I \(configuration.buildModulesDirectory.path) \ + -I \(configuration.mmioVolatileDirectory.path) \ + -load-plugin-executable \(configuration.buildMMIOMacrosFile.path)#MMIOMacros \ + -parse-as-library \ + -diagnostic-style llvm + """) + guard compileSuccess else { return } + + let fileCheckSuccess: Bool + if configuration.hasLLVMFileCheck { + fileCheckSuccess = LLVMDiagnostic.parsingShellOutput( """ - TOOLCHAINS=\(toolchainID) swiftc \ - -emit-ir \(testFileURL.path) \ - -o \(testOutputFileURL.path) \ - -O -wmo \ - -I \(buildModulesDirectoryURL.path) \ - -I \(mmioVolatileDirectoryURL.path) \ - -load-plugin-executable \(buildMMIOMacrosFileURL.path)#MMIOMacros \ - -parse-as-library \ - -diagnostic-style llvm \ - -v \ - -Rmodule-loading + FileCheck \ + \(testFile.path) \ + --input-file \(testOutputFile.path) \ + --dump-input never """) - - if hasLLVMFileCheck { - _ = try sh( - """ - FileCheck \ - \(testFileURL.path) \ - --input-file \(testOutputFileURL.path) \ - --dump-input never - """) - } else { - let fileCheck = SimpleFileCheck( - inputFileURL: testFileURL, - outputFileURL: testOutputFileURL) - let diagnostics = fileCheck.run() - guard diagnostics.isEmpty else { - return diagnostics - } - } - - } catch let error as ShellCommandError { - // Parse the errors. - var message = error.error[...] - let diagnostics = Parser.llvmDiagnostics.run(&message) - guard let diagnostics = diagnostics else { - XCTFail("Test failed: \(error)") - return [] - } - if !message.isEmpty { - XCTFail("Failed to parse all error diagnostics, remaining: \(message)") + } else { + let fileCheck = SimpleFileCheck( + inputFile: testFile, + outputFile: testOutputFile) + let diagnostics = fileCheck.run() + for diagnostic in diagnostics { + diagnostic.recordAsIssue() } - return diagnostics - } catch { - fatalError("Unexpected error: \(error)") + fileCheckSuccess = diagnostics.isEmpty } - return [] - } -} - -extension XCTestCase { - func record(diagnostic: LLVMDiagnostic) { - #if os(Linux) - XCTFail("Test failed with error: \(diagnostic)") - #else - let issue = XCTIssue( - type: .assertionFailure, // FIXME: this emits notes as errors - compactDescription: diagnostic.message, - sourceCodeContext: .init( - location: .init( - filePath: diagnostic.file, - lineNumber: diagnostic.line))) - self.record(issue) - #endif + guard fileCheckSuccess else { return } } } @@ -263,3 +206,39 @@ extension FileManager { .sorted { $0.path < $1.path } } } + +extension LLVMDiagnostic { + static func parsingShellOutput(_ command: String) -> Bool { + do throws(ShellCommandError) { + _ = try sh(command) + } catch let shellCommandError { + // Parse the errors. + var message = shellCommandError.error[...] + let diagnostics = Parser.llvmDiagnostics.run(&message) + guard let diagnostics = diagnostics else { + Issue.record(shellCommandError) + return false + } + if !message.isEmpty { + Issue.record( + "Failed to parse all error diagnostics, remaining: \(message)") + } + for diagnostic in diagnostics { + diagnostic.recordAsIssue() + } + return false + } + return true + } + + func recordAsIssue() { + // FIXME: this emits notes as errors + Issue.record( + Comment(rawValue: self.message), + sourceLocation: .init( + fileID: self.file, + filePath: self.file, + line: self.line, + column: self.column)) + } +} diff --git a/Tests/MMIOFileCheckTests/Tests/TestRegisterModifyProjectedSetClear.swift b/Tests/MMIOFileCheckTests/Tests/TestRegisterModifyProjectedSetClear.swift index b33b8738..24ea7570 100644 --- a/Tests/MMIOFileCheckTests/Tests/TestRegisterModifyProjectedSetClear.swift +++ b/Tests/MMIOFileCheckTests/Tests/TestRegisterModifyProjectedSetClear.swift @@ -55,7 +55,7 @@ public func main8() { } // CHECK: %0 = load volatile i8 // CHECK-NEXT: %1 = and i8 %0, 126 - // CHECK-NEXT: %2 = or i8 %1, -128 + // CHECK-NEXT: %2 = or disjoint i8 %1, -128 // CHECK-NEXT: store volatile i8 %2 } @@ -67,7 +67,7 @@ public func main16() { } // CHECK: %0 = load volatile i16 // CHECK-NEXT: %1 = and i16 %0, 32766 - // CHECK-NEXT: %2 = or i16 %1, -32768 + // CHECK-NEXT: %2 = or disjoint i16 %1, -32768 // CHECK-NEXT: store volatile i16 %2 } @@ -79,7 +79,7 @@ public func main32() { } // CHECK: %0 = load volatile i32 // CHECK-NEXT: %1 = and i32 %0, 2147483646 - // CHECK-NEXT: %2 = or i32 %1, -2147483648 + // CHECK-NEXT: %2 = or disjoint i32 %1, -2147483648 // CHECK-NEXT: store volatile i32 %2 } @@ -91,6 +91,6 @@ public func main64() { } // CHECK: %0 = load volatile i64 // CHECK-NEXT: %1 = and i64 %0, 9223372036854775806 - // CHECK-NEXT: %2 = or i64 %1, -9223372036854775808 + // CHECK-NEXT: %2 = or disjoint i64 %1, -9223372036854775808 // CHECK-NEXT: store volatile i64 %2 } diff --git a/Tests/MMIOFileCheckTests/Tests/TestRegisterModifyRawSetClear.swift b/Tests/MMIOFileCheckTests/Tests/TestRegisterModifyRawSetClear.swift index 0a5fd3ca..63a3dc00 100644 --- a/Tests/MMIOFileCheckTests/Tests/TestRegisterModifyRawSetClear.swift +++ b/Tests/MMIOFileCheckTests/Tests/TestRegisterModifyRawSetClear.swift @@ -55,7 +55,7 @@ public func main8() { } // CHECK: %0 = load volatile i8 // CHECK-NEXT: %1 = and i8 %0, 126 - // CHECK-NEXT: %2 = or i8 %1, -128 + // CHECK-NEXT: %2 = or disjoint i8 %1, -128 // CHECK-NEXT: store volatile i8 %2 } @@ -67,7 +67,7 @@ public func main16() { } // CHECK: %0 = load volatile i16 // CHECK-NEXT: %1 = and i16 %0, 32766 - // CHECK-NEXT: %2 = or i16 %1, -32768 + // CHECK-NEXT: %2 = or disjoint i16 %1, -32768 // CHECK-NEXT: store volatile i16 %2 } @@ -79,7 +79,7 @@ public func main32() { } // CHECK: %0 = load volatile i32 // CHECK-NEXT: %1 = and i32 %0, 2147483646 - // CHECK-NEXT: %2 = or i32 %1, -2147483648 + // CHECK-NEXT: %2 = or disjoint i32 %1, -2147483648 // CHECK-NEXT: store volatile i32 %2 } @@ -91,6 +91,6 @@ public func main64() { } // CHECK: %0 = load volatile i64 // CHECK-NEXT: %1 = and i64 %0, 9223372036854775806 - // CHECK-NEXT: %2 = or i64 %1, -9223372036854775808 + // CHECK-NEXT: %2 = or disjoint i64 %1, -9223372036854775808 // CHECK-NEXT: store volatile i64 %2 }