1515//===----------------------------------------------------------------------===//
1616
1717import ArgumentParser
18- import Containerization
1918import ContainerizationError
2019import ContainerizationOCI
2120import Foundation
2221import Logging
2322import NIOCore
2423import NIOPosix
2524
25+ @testable import Containerization
26+
2627extension IntegrationSuite {
2728 func testMounts( ) async throws {
2829 let id = " test-cat-mount "
@@ -354,10 +355,10 @@ extension IntegrationSuite {
354355 let id = " test-fsnotify-events "
355356
356357 let bs = try await bootstrap ( )
358+ let directory = try createFSNotifyTestDirectory ( )
357359 let container = try LinuxContainer ( id, rootfs: bs. rootfs, vmm: bs. vmm) { config in
358- let directory = try createFSNotifyTestDirectory ( )
359360 config. process. arguments = [ " /bin/sh " , " -c " , " sleep 30 " ] // Keep container running
360- config. mounts. append ( . share( source: directory. path, destination: " /mnt/test " ) )
361+ config. mounts. append ( . share( source: directory. path, destination: " /mnt " ) )
361362 }
362363
363364 try await container. create ( )
@@ -368,51 +369,63 @@ extension IntegrationSuite {
368369 let group = MultiThreadedEventLoopGroup ( numberOfThreads: 1 )
369370 let agent = Vminitd ( connection: connection, group: group)
370371
372+ // Calculate the hashed tag name for the mount source and mount in VM
373+ let mountTag = try hashMountSource ( source: directory. path)
374+ let vmMountPath = " /tmp/fsnotify-test "
375+
376+ try await agent. mount ( . init( type: " virtiofs " , source: mountTag, destination: vmMountPath) )
377+
371378 // Test 1: CREATE event on existing file
372- print ( " Testing CREATE event on existing file... " )
373- let createResponse = try await agent . notifyFileSystemEvent ( path : " /mnt/test/existing.txt " , eventType : . create )
379+ let createResponse = try await agent . notifyFileSystemEvent ( path : " \( vmMountPath ) /existing.txt " , eventType : . create )
380+
374381 guard createResponse. success else {
375382 throw IntegrationError . assert ( msg: " CREATE event failed: \( createResponse. error) " )
376383 }
377384
378- // Verify CREATE event triggered inotify (file should still exist)
379- let buffer1 = BufferWriter ( )
380- let process1 = try await container. exec ( " check-create " ) { config in
381- config. arguments = [ " /bin/stat " , " /mnt/test/existing.txt " ]
382- config. stdout = buffer1
383- }
384-
385- try await process1. start ( )
386- let status1 = try await process1. wait ( )
387- guard status1 == 0 else {
388- throw IntegrationError . assert ( msg: " File stat verification failed for CREATE event " )
389- }
390-
391385 // Test 2: MODIFY event on existing file
392- print ( " Testing MODIFY event... " )
393- let modifyResponse = try await agent. notifyFileSystemEvent ( path: " /mnt/test/existing.txt " , eventType: . modify)
386+ let modifyResponse = try await agent. notifyFileSystemEvent ( path: " \( vmMountPath) /existing.txt " , eventType: . modify)
394387 guard modifyResponse. success else {
395388 throw IntegrationError . assert ( msg: " MODIFY event failed: \( modifyResponse. error) " )
396389 }
397390
398- // Verify file timestamp was updated
399- let buffer2 = BufferWriter ( )
400- let process2 = try await container. exec ( " check-modify " ) { config in
401- config. arguments = [ " /bin/stat " , " /mnt/test/existing.txt " ]
402- config. stdout = buffer2
391+ // Test 3: Verify inotify events are actually generated using inotifywait
392+ let inotifyBuffer = BufferWriter ( )
393+ let inotifyProcess = try await container. exec ( " test-inotify " ) { config in
394+ // Install inotify-tools and monitor the mount point for events
395+ config. arguments = [
396+ " /bin/sh " , " -c " ,
397+ """
398+ apk add --no-cache inotify-tools > /dev/null 2>&1 && \
399+ timeout 2 inotifywait -m /mnt -e modify,create,delete --format '%e %f' 2>/dev/null &
400+ INOTIFY_PID=$!
401+ sleep 0.1
402+ # Trigger a modify event that should be detected
403+ touch /mnt/test-inotify.txt
404+ echo " modify test-inotify.txt "
405+ wait $INOTIFY_PID 2>/dev/null || true
406+ """ ,
407+ ]
408+ config. stdout = inotifyBuffer
403409 }
404410
405- try await process2. start ( )
406- let status2 = try await process2. wait ( )
407- guard status2 == 0 else {
408- throw IntegrationError . assert ( msg: " File modification verification failed " )
411+ try await inotifyProcess. start ( )
412+
413+ // While inotify is running, send FSNotify events that should trigger inotify
414+ try await Task . sleep ( for: . milliseconds( 200 ) )
415+ let _ = try await agent. notifyFileSystemEvent ( path: " \( vmMountPath) /test-inotify.txt " , eventType: . modify)
416+
417+ let _ = try await inotifyProcess. wait ( )
418+ let inotifyOutput = String ( data: inotifyBuffer. data, encoding: . utf8) ?? " "
419+
420+ // Verify that inotify detected the modify event
421+ guard inotifyOutput. contains ( " modify test-inotify.txt " ) else {
422+ throw IntegrationError . assert ( msg: " inotify did not detect FSNotify-triggered modify event. Output: \( inotifyOutput) " )
409423 }
410424
411- // Test 3: Test unsupported DELETE event (should log warning but not fail)
412- print ( " Testing DELETE event (should log warning)... " )
413- let deleteResponse = try await agent. notifyFileSystemEvent ( path: " /mnt/test/nonexistent.txt " , eventType: . delete)
425+ // Test 4: DELETE event on non-existent file
426+ let deleteResponse = try await agent. notifyFileSystemEvent ( path: " \( vmMountPath) /nonexistent.txt " , eventType: . delete)
414427 guard deleteResponse. success else {
415- throw IntegrationError . assert ( msg: " DELETE event should succeed with warning, not fail " )
428+ throw IntegrationError . assert ( msg: " DELETE event failed: \( deleteResponse . error ) " )
416429 }
417430
418431 // Clean up
0 commit comments