@@ -80,38 +80,54 @@ public final class ContainerFixture: Sendable {
8080 public static func with< T> ( _ body: ( ContainerFixture ) async throws -> T ) async throws -> T {
8181 let testID = String ( UUID ( ) . uuidString. prefix ( 8 ) ) . lowercased ( )
8282
83- let scratchRoot =
84- ProcessInfo . processInfo. environment [ " CLITEST_SCRATCH_ROOT " ]
85- . map { FilePath ( $0) }
86- ?? FilePath ( FileManager . default. temporaryDirectory. path)
87-
8883 let testName =
8984 Test . current. map { $0. name. hasSuffix ( " () " ) ? String ( $0. name. dropLast ( 2 ) ) : $0. name }
9085 ?? testID
91- let suiteName = Test . current. map { " \( type ( of: $0) ) " } ?? " unknown "
92-
93- // Name the scratch directory so it's immediately identifiable when browsing:
94- // {sanitizedTestName}-{testID}
95- let safeName = testName. replacingOccurrences (
96- of: " [^a-zA-Z0-9] " , with: " - " , options: . regularExpression)
97- let testDir = scratchRoot. appending ( " \( safeName) - \( testID) " )
98- try FileManager . default. createDirectory (
99- atPath: testDir. string, withIntermediateDirectories: true , attributes: nil )
100-
86+ // Test.current is a value describing the running test, not an instance of the suite
87+ // type, so `type(of:)` always yields `Test` itself. Derive the suite from the test's
88+ // fully-qualified ID instead (e.g. "IntegrationTests.TestCLIStatus/explicitTableFormat()/...")
89+ // — the same identifier format used in the swift-testing event-stream JSON.
90+ let testIdentifier = Test . current. map { " \( $0. id) " }
91+ let suiteName = testIdentifier? . split ( separator: " / " , maxSplits: 1 ) . first. map ( String . init) ?? " unknown "
92+
93+ // Swift Testing doesn't expose a stable per-case identifier or the case's arguments
94+ // publicly, only `isParameterized`. Parameterized tests share one `testName` across all
95+ // their concurrently-running cases, so fall back to the per-invocation `testID` to keep
96+ // each case's log file distinct.
97+ let isParameterized = Test . Case. current? . isParameterized ?? false
98+ let logFileName = isParameterized ? " \( testName) - \( testID) .log " : " \( testName) .log "
99+
100+ // Set up logging before any fixture work (scratch dir creation, etc.) so a "test start"
101+ // message is the first thing recorded — bookended by "test end" once `body` returns.
101102 var logger = Logger ( label: " com.apple.container.test " ) { label in
102103 if let root = ProcessInfo . processInfo. environment [ " CLITEST_LOG_ROOT " ] , !root. isEmpty {
103104 let path =
104105 FilePath ( root)
105106 . appending ( " clitests " )
106107 . appending ( suiteName)
107- . appending ( testName + " .log " )
108+ . appending ( logFileName )
108109 if let handler = try ? FileLogHandler ( label: label, category: " clitests " , path: path) {
109110 return handler
110111 }
111112 }
112113 return StreamLogHandler . standardOutput ( label: label)
113114 }
114115 logger [ metadataKey: " testID " ] = " \( testID) "
116+ logger [ metadataKey: " test " ] = " \( testIdentifier ?? testName) "
117+ logger. info ( " test start " )
118+
119+ let scratchRoot =
120+ ProcessInfo . processInfo. environment [ " CLITEST_SCRATCH_ROOT " ]
121+ . map { FilePath ( $0) }
122+ ?? FilePath ( FileManager . default. temporaryDirectory. path)
123+
124+ // Name the scratch directory so it's immediately identifiable when browsing:
125+ // {sanitizedTestName}-{testID}
126+ let safeName = testName. replacingOccurrences (
127+ of: " [^a-zA-Z0-9] " , with: " - " , options: . regularExpression)
128+ let testDir = scratchRoot. appending ( " \( safeName) - \( testID) " )
129+ try FileManager . default. createDirectory (
130+ atPath: testDir. string, withIntermediateDirectories: true , attributes: nil )
115131
116132 let fixture = ContainerFixture ( testID: testID, testDir: testDir, log: logger)
117133
@@ -123,10 +139,14 @@ public final class ContainerFixture: Sendable {
123139
124140 do {
125141 let result = try await body ( fixture)
142+ logger. info ( " test end " , metadata: [ " result " : " pass " ] )
126143 await fixture. runCleanup ( )
144+ logger. info ( " test cleaned up " )
127145 return result
128146 } catch {
147+ logger. info ( " test end " , metadata: [ " result " : " fail " , " error " : " \( error) " ] )
129148 await fixture. runCleanup ( )
149+ logger. info ( " test cleaned up " )
130150 throw error
131151 }
132152 }
0 commit comments