Skip to content

Commit 3ecb250

Browse files
committed
fix integration test issues
1 parent 72dbe2b commit 3ecb250

File tree

2 files changed

+51
-34
lines changed

2 files changed

+51
-34
lines changed

Sources/Integration/VMTests.swift

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
//===----------------------------------------------------------------------===//
1616

1717
import ArgumentParser
18-
import Containerization
1918
import ContainerizationError
2019
import ContainerizationOCI
2120
import Foundation
2221
import Logging
2322
import NIOCore
2423
import NIOPosix
2524

25+
@testable import Containerization
26+
2627
extension 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

vminitd/Sources/vminitd/Server+GRPC.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,7 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
947947
func notifyFileSystemEvent(
948948
requestStream: GRPCAsyncRequestStream<Com_Apple_Containerization_Sandbox_V3_NotifyFileSystemEventRequest>,
949949
responseStream: GRPCAsyncResponseStreamWriter<Com_Apple_Containerization_Sandbox_V3_NotifyFileSystemEventResponse>,
950-
context: GRPCAsyncServerCallContext
950+
context: GRPC.GRPCAsyncServerCallContext
951951
) async throws {
952952
for try await request in requestStream {
953953
log.debug(
@@ -988,6 +988,10 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
988988
path: String,
989989
eventType: Com_Apple_Containerization_Sandbox_V3_FileSystemEventType
990990
) async throws {
991+
if eventType == .delete && !FileManager.default.fileExists(atPath: path) {
992+
return
993+
}
994+
991995
let attributes = try FileManager.default.attributesOfItem(atPath: path)
992996
guard let permissions = attributes[.posixPermissions] as? NSNumber else {
993997
throw GRPCStatus(code: .internalError, message: "Failed to get file permissions for path: \(path)")

0 commit comments

Comments
 (0)