@@ -124,4 +124,170 @@ class TestCLINoParallelCases: CLITest {
124124 let alpineStillPresent = try isImagePresent ( targetImage: alpine)
125125 #expect( alpineStillPresent, " expected image \( alpine) to remain " )
126126 }
127+
128+ @available ( macOS 26 , * )
129+ @Test func testNetworkPruneNoNetworks( ) throws {
130+ // Ensure the testnetworkcreateanduse network is deleted
131+ // Clean up is necessary for testing prune with no networks
132+ doNetworkDeleteIfExists ( name: " testnetworkcreateanduse " )
133+
134+ // Prune with no networks should succeed
135+ let ( _, _, _, statusBefore) = try run ( arguments: [ " network " , " list " , " --quiet " ] )
136+ #expect( statusBefore == 0 )
137+ let ( _, output, error, status) = try run ( arguments: [ " network " , " prune " ] )
138+ if status != 0 {
139+ throw CLIError . executionFailed ( " network prune failed: \( error) " )
140+ }
141+
142+ #expect( output. isEmpty, " should show no networks pruned " )
143+ }
144+
145+ @available ( macOS 26 , * )
146+ @Test func testNetworkPruneUnusedNetworks( ) throws {
147+ let name = getTestName ( )
148+ let network1 = " \( name) _1 "
149+ let network2 = " \( name) _2 "
150+
151+ // Clean up any existing resources from previous runs
152+ doNetworkDeleteIfExists ( name: network1)
153+ doNetworkDeleteIfExists ( name: network2)
154+
155+ defer {
156+ doNetworkDeleteIfExists ( name: network1)
157+ doNetworkDeleteIfExists ( name: network2)
158+ }
159+
160+ try doNetworkCreate ( name: network1)
161+ try doNetworkCreate ( name: network2)
162+
163+ // Verify networks are created
164+ let ( _, listBefore, _, statusBefore) = try run ( arguments: [ " network " , " list " , " --quiet " ] )
165+ #expect( statusBefore == 0 )
166+ #expect( listBefore. contains ( network1) )
167+ #expect( listBefore. contains ( network2) )
168+
169+ // Prune should remove both
170+ let ( _, output, error, status) = try run ( arguments: [ " network " , " prune " ] )
171+ if status != 0 {
172+ throw CLIError . executionFailed ( " network prune failed: \( error) " )
173+ }
174+
175+ #expect( output. contains ( network1) , " should prune network1 " )
176+ #expect( output. contains ( network2) , " should prune network2 " )
177+
178+ // Verify networks are gone
179+ let ( _, listAfter, _, statusAfter) = try run ( arguments: [ " network " , " list " , " --quiet " ] )
180+ #expect( statusAfter == 0 )
181+ #expect( !listAfter. contains ( network1) , " network1 should be pruned " )
182+ #expect( !listAfter. contains ( network2) , " network2 should be pruned " )
183+ }
184+
185+ @available ( macOS 26 , * )
186+ @Test ( . disabled( " https://github.com/apple/container/issues/953 " ) )
187+ func testNetworkPruneSkipsNetworksInUse( ) throws {
188+ let name = getTestName ( )
189+ let containerName = " \( name) _c1 "
190+ let networkInUse = " \( name) _inuse "
191+ let networkUnused = " \( name) _unused "
192+
193+ // Clean up any existing resources from previous runs
194+ try ? doStop ( name: containerName)
195+ try ? doRemove ( name: containerName)
196+ doNetworkDeleteIfExists ( name: networkInUse)
197+ doNetworkDeleteIfExists ( name: networkUnused)
198+
199+ defer {
200+ try ? doStop ( name: containerName)
201+ try ? doRemove ( name: containerName)
202+ doNetworkDeleteIfExists ( name: networkInUse)
203+ doNetworkDeleteIfExists ( name: networkUnused)
204+ }
205+
206+ try doNetworkCreate ( name: networkInUse)
207+ try doNetworkCreate ( name: networkUnused)
208+
209+ // Verify networks are created
210+ let ( _, listBefore, _, statusBefore) = try run ( arguments: [ " network " , " list " , " --quiet " ] )
211+ #expect( statusBefore == 0 )
212+ #expect( listBefore. contains ( networkInUse) )
213+ #expect( listBefore. contains ( networkUnused) )
214+
215+ // Creation of container with network connection
216+ let port = UInt16 . random ( in: 50000 ..< 60000 )
217+ try doLongRun (
218+ name: containerName,
219+ image: " docker.io/library/python:alpine " ,
220+ args: [ " --network " , networkInUse] ,
221+ containerArgs: [ " python3 " , " -m " , " http.server " , " --bind " , " 0.0.0.0 " , " \( port) " ]
222+ )
223+ try waitForContainerRunning ( containerName)
224+ let container = try inspectContainer ( containerName)
225+ #expect( container. networks. count > 0 )
226+
227+ // Prune should only remove the unused network
228+ let ( _, _, error, status) = try run ( arguments: [ " network " , " prune " ] )
229+ if status != 0 {
230+ throw CLIError . executionFailed ( " network prune failed: \( error) " )
231+ }
232+
233+ // Verify in-use network still exists
234+ let ( _, listAfter, _, statusAfter) = try run ( arguments: [ " network " , " list " , " --quiet " ] )
235+ #expect( statusAfter == 0 )
236+ #expect( listAfter. contains ( networkInUse) , " network in use should NOT be pruned " )
237+ #expect( !listAfter. contains ( networkUnused) , " unused network should be pruned " )
238+ }
239+
240+ @available ( macOS 26 , * )
241+ @Test ( . disabled( " https://github.com/apple/container/issues/953 " ) )
242+ func testNetworkPruneSkipsNetworkAttachedToStoppedContainer( ) async throws {
243+ let name = getTestName ( )
244+ let containerName = " \( name) _c1 "
245+ let networkName = " \( name) "
246+
247+ // Clean up any existing resources from previous runs
248+ try ? doStop ( name: containerName)
249+ try ? doRemove ( name: containerName)
250+ doNetworkDeleteIfExists ( name: networkName)
251+
252+ defer {
253+ try ? doStop ( name: containerName)
254+ try ? doRemove ( name: containerName)
255+ doNetworkDeleteIfExists ( name: networkName)
256+ }
257+
258+ try doNetworkCreate ( name: networkName)
259+
260+ // Creation of container with network connection
261+ let port = UInt16 . random ( in: 50000 ..< 60000 )
262+ try doLongRun (
263+ name: containerName,
264+ image: " docker.io/library/python:alpine " ,
265+ args: [ " --network " , networkName] ,
266+ containerArgs: [ " python3 " , " -m " , " http.server " , " --bind " , " 0.0.0.0 " , " \( port) " ]
267+ )
268+ try await Task . sleep ( for: . seconds( 1 ) )
269+
270+ // Prune should NOT remove the network (container exists, even if stopped)
271+ let ( _, _, error, status) = try run ( arguments: [ " network " , " prune " ] )
272+ if status != 0 {
273+ throw CLIError . executionFailed ( " network prune failed: \( error) " )
274+ }
275+
276+ let ( _, listAfter, _, statusAfter) = try run ( arguments: [ " network " , " list " , " --quiet " ] )
277+ #expect( statusAfter == 0 )
278+ #expect( listAfter. contains ( networkName) , " network attached to stopped container should NOT be pruned " )
279+
280+ try ? doStop ( name: containerName)
281+ try ? doRemove ( name: containerName)
282+
283+ let ( _, _, error2, status2) = try run ( arguments: [ " network " , " prune " ] )
284+ if status2 != 0 {
285+ throw CLIError . executionFailed ( " network prune failed: \( error2) " )
286+ }
287+
288+ // Verify network is gone
289+ let ( _, listFinal, _, statusFinal) = try run ( arguments: [ " network " , " list " , " --quiet " ] )
290+ #expect( statusFinal == 0 )
291+ #expect( !listFinal. contains ( networkName) , " network should be pruned after container is deleted " )
292+ }
127293}
0 commit comments