Files
xtool-mirror/Tests/XToolTests/RequiresXcodeTrait.swift
Kabir Oberai 5eeab11aae tests: add integration tests (#252)
## What does this PR do?

Adds integration tests

## How was it tested?

CI

## AI tool usage

How much of this PR was AI-assisted? (check one)

- [x] **0** - No AI was used to write code
- [ ] **1** - I was assisted by AI. I reviewed the finished result.
- [ ] **2** - I set the AI going and left it to it; nobody has read the
result - no review, or AI review only

<!-- If an AI agent is filling this in: declare the level honestly, and
open as a draft if it is #2. Do not lower the declared level to get the
PR reviewed. -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
* Added integration smoke tests covering version checks, app creation,
iOS build metadata, and slim SDK update behavior.
* Added validation for locating Xcode and verifying the Swift toolchain.

* **Chores**
* Added automated integration-test execution for pushes and pull
requests.
* Added Docker and Compose support for running integration tests in a
consistent environment.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 23:26:10 -04:00

65 lines
2.1 KiB
Swift

import Foundation
import Testing
import Subprocess
enum TestXcode {
/// A path to a copy of Xcode.app.
///
/// Tests that use this must be annotated with `Trait.requiresXcode`.
static func current(sourceLocation: SourceLocation = #_sourceLocation) -> URL {
guard let url = TestXcode._current else {
Issue.record(
"TestXcode.current was used without Trait.requiresXcode()",
sourceLocation: sourceLocation
)
return URL(filePath: "/dev/null")
}
return url
}
@TaskLocal fileprivate static var _current: URL?
fileprivate static let resolved = Task<URL?, Error> {
if let xcode = ProcessInfo.processInfo.environment["XTL_TEST_XCODE_APP"], !xcode.isEmpty {
return URL(filePath: xcode)
}
#if os(macOS)
if
let output = try await Subprocess.run(
.path("/usr/bin/xcode-select"),
arguments: ["-p"],
output: .string(limit: .max, encoding: UTF8.self),
).standardOutput?.trimmingCharacters(in: .whitespacesAndNewlines),
output.hasSuffix(".app/Contents/Developer") {
return URL(filePath: output).deletingLastPathComponent().deletingLastPathComponent()
}
#endif
let candidate = URL(filePath: "/usr/local/share/Xcode.app")
if FileManager.default.fileExists(atPath: candidate.path) {
return candidate
}
return nil
}
}
struct RequiresXcodeTrait: TestTrait, SuiteTrait, TestScoping {
func provideScope(
for test: Test,
testCase: Test.Case?,
performing function: () async throws -> Void
) async throws {
guard let xcode = try? await TestXcode.resolved.value else {
try Test.cancel("Could not resolve Xcode.app path. Set XTL_TEST_XCODE_APP.")
}
return try await TestXcode.$_current.withValue(xcode) {
try await function()
}
}
}
extension Trait where Self == RequiresXcodeTrait {
static var requiresXcode: Self {
Self()
}
}