1515//===----------------------------------------------------------------------===//
1616
1717import ContainerTestSupport
18+ import Darwin
1819import Foundation
1920import Testing
2021
@@ -31,12 +32,11 @@ struct TestCLIClean {
3132 }
3233
3334 private func allocatedBytes( at url: URL ) throws -> Int64 {
34- let values = try url. resourceValues ( forKeys: [ . fileAllocatedSizeKey, . totalFileAllocatedSizeKey] )
35- let allocated = values. totalFileAllocatedSize ?? values. fileAllocatedSize
36- guard let allocated else {
37- throw CommandError . executionFailed ( " failed to read allocated size for \( url. path) " )
35+ var fileStatus = stat ( )
36+ guard lstat ( url. path, & fileStatus) == 0 else {
37+ throw POSIXError ( POSIXErrorCode ( rawValue: errno) ?? . EIO)
3838 }
39- return Int64 ( allocated )
39+ return Int64 ( fileStatus . st_blocks ) * 512
4040 }
4141
4242 private func containerRootfsBlockURL( _ fixture: ContainerFixture , name: String ) throws -> URL {
@@ -67,6 +67,63 @@ struct TestCLIClean {
6767 " clean should reclaim at least 80% of storage allocated by the test write " )
6868 }
6969
70+ private func waitForStableAllocatedSpace(
71+ at url: URL ,
72+ timeout: TimeInterval = 10
73+ ) async throws -> Int64 {
74+ let deadline = Date . now. addingTimeInterval ( timeout)
75+ var previous = try allocatedBytes ( at: url)
76+ var unchangedSamples = 0
77+
78+ while Date . now < deadline {
79+ try await Task . sleep ( for: . milliseconds( 250 ) )
80+ let current = try allocatedBytes ( at: url)
81+ if current == previous {
82+ unchangedSamples += 1
83+ if unchangedSamples == 4 {
84+ return current
85+ }
86+ } else {
87+ previous = current
88+ unchangedSamples = 0
89+ }
90+ }
91+ return previous
92+ }
93+
94+ private func waitForAllocatedSpace(
95+ after baseline: Int64 ,
96+ at url: URL ,
97+ timeout: TimeInterval = 10
98+ ) async throws -> Int64 {
99+ let deadline = Date . now. addingTimeInterval ( timeout)
100+ var allocated = try allocatedBytes ( at: url)
101+
102+ while allocated <= baseline, Date . now < deadline {
103+ try await Task . sleep ( for: . milliseconds( 250 ) )
104+ allocated = try allocatedBytes ( at: url)
105+ }
106+ return allocated
107+ }
108+
109+ private func waitForReclaimedSpace(
110+ beforeWrite: Int64 ,
111+ afterWrite: Int64 ,
112+ at url: URL ,
113+ timeout: TimeInterval = 10
114+ ) async throws -> Int64 {
115+ let allocatedByWrite = afterWrite - beforeWrite
116+ let minimumExpectedReclaimed = Int64 ( Double ( allocatedByWrite) * 0.8 )
117+ let deadline = Date . now. addingTimeInterval ( timeout)
118+ var afterClean = try allocatedBytes ( at: url)
119+
120+ while afterWrite - afterClean < minimumExpectedReclaimed, Date . now < deadline {
121+ try await Task . sleep ( for: . milliseconds( 250 ) )
122+ afterClean = try allocatedBytes ( at: url)
123+ }
124+ return afterClean
125+ }
126+
70127 @Test func cleanRejectsStoppedContainer( ) async throws {
71128 try await ContainerFixture . with { fixture in
72129 let name = " \( fixture. testID) -clean-stopped "
@@ -81,6 +138,9 @@ struct TestCLIClean {
81138
82139 let result = try fixture. run ( [ " clean " , name] )
83140 #expect( result. status != 0 , " clean should reject a stopped container " )
141+ #expect(
142+ result. error. contains ( " not running " ) ,
143+ " clean should report that the stopped container is not running; stderr: \( result. error) " )
84144 }
85145 }
86146
@@ -117,18 +177,23 @@ struct TestCLIClean {
117177 fixture. addCleanup { try ? fixture. doRemove ( name, force: true ) }
118178
119179 let rootfsBlockURL = try containerRootfsBlockURL ( fixture, name: name)
120- let beforeWrite = try allocatedBytes ( at: rootfsBlockURL)
180+ try fixture. doClean ( name)
181+ let beforeWrite = try await waitForStableAllocatedSpace ( at: rootfsBlockURL)
121182
122183 try fixture. doExec (
123184 name,
124- cmd: [ " sh " , " -c " , " dd if=/dev/urandom of=/rootfs-reclaim.dat bs=1M count=64 " ] )
185+ cmd: [ " sh " , " -c " , " dd if=/dev/urandom of=/rootfs-reclaim.dat bs=1M count=256 " ] )
125186 try fixture. doExec ( name, cmd: [ " sync " ] )
126- let afterWrite = try allocatedBytes ( at: rootfsBlockURL)
187+ let afterWrite = try await waitForAllocatedSpace ( after : beforeWrite , at: rootfsBlockURL)
127188
128189 try fixture. doExec ( name, cmd: [ " rm " , " /rootfs-reclaim.dat " ] )
129190 try fixture. doExec ( name, cmd: [ " sync " ] )
130191 try fixture. doClean ( name)
131- let afterClean = try allocatedBytes ( at: rootfsBlockURL)
192+ let afterClean = try await waitForReclaimedSpace (
193+ beforeWrite: beforeWrite,
194+ afterWrite: afterWrite,
195+ at: rootfsBlockURL)
196+ print ( " rootfs allocated bytes before= \( beforeWrite) afterWrite= \( afterWrite) afterClean= \( afterClean) " )
132197
133198 expectReclaimedSpace (
134199 beforeWrite: beforeWrite,
@@ -154,18 +219,23 @@ struct TestCLIClean {
154219 try await fixture. waitForContainerRunning ( name)
155220
156221 let volumeBlockURL = try volumeBlockURL ( fixture, name: volumeName)
157- let beforeWrite = try allocatedBytes ( at: volumeBlockURL)
222+ try fixture. doClean ( name)
223+ let beforeWrite = try await waitForStableAllocatedSpace ( at: volumeBlockURL)
158224
159225 try fixture. doExec (
160226 name,
161- cmd: [ " sh " , " -c " , " dd if=/dev/urandom of=/mnt/reclaim-data/volume-reclaim.dat bs=1M count=64 " ] )
227+ cmd: [ " sh " , " -c " , " dd if=/dev/urandom of=/mnt/reclaim-data/volume-reclaim.dat bs=1M count=256 " ] )
162228 try fixture. doExec ( name, cmd: [ " sync " ] )
163- let afterWrite = try allocatedBytes ( at: volumeBlockURL)
229+ let afterWrite = try await waitForAllocatedSpace ( after : beforeWrite , at: volumeBlockURL)
164230
165231 try fixture. doExec ( name, cmd: [ " rm " , " /mnt/reclaim-data/volume-reclaim.dat " ] )
166232 try fixture. doExec ( name, cmd: [ " sync " ] )
167233 try fixture. doClean ( name)
168- let afterClean = try allocatedBytes ( at: volumeBlockURL)
234+ let afterClean = try await waitForReclaimedSpace (
235+ beforeWrite: beforeWrite,
236+ afterWrite: afterWrite,
237+ at: volumeBlockURL)
238+ print ( " volume allocated bytes before= \( beforeWrite) afterWrite= \( afterWrite) afterClean= \( afterClean) " )
169239
170240 expectReclaimedSpace (
171241 beforeWrite: beforeWrite,
0 commit comments