summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-21 21:44:11 +0200
committerPaul Buetow <paul@buetow.org>2026-02-21 21:44:11 +0200
commit08cb9dfe46f843114feb42cc9ffa599717ebcc32 (patch)
treef09fdbce3bb8654bf52180ccf598173e3d8a8976
parentd455543bf0838c7fe2250081c5ea8ed3e275d236 (diff)
Add negative integration tests for truncate syscalls (task 348)
Add two negative test scenarios: - truncate-enoent: SYS_TRUNCATE on nonexistent file, expects ENOENT - truncate-ftruncate-ebadf: SYS_FTRUNCATE on invalid fd 99999, expects EBADF Both verify ior captures tracepoints even when syscalls fail. Amp-Thread-ID: https://ampcode.com/threads/T-019c81b7-9641-763e-b99e-20a0d3552005 Co-authored-by: Amp <amp@ampcode.com>
-rw-r--r--integrationtests/cmd/ioworkload/scenarios.go40
-rw-r--r--integrationtests/truncate_test.go21
2 files changed, 59 insertions, 2 deletions
diff --git a/integrationtests/cmd/ioworkload/scenarios.go b/integrationtests/cmd/ioworkload/scenarios.go
index 58c78e1..f25b8a4 100644
--- a/integrationtests/cmd/ioworkload/scenarios.go
+++ b/integrationtests/cmd/ioworkload/scenarios.go
@@ -86,8 +86,10 @@ var scenarios = map[string]func() error{
"sync-fsync-ebadf": syncFsyncEbadf,
"sync-fdatasync-ebadf": syncFdatasyncEbadf,
"sync-file-range-ebadf": syncFileRangeEbadf,
- "truncate-basic": truncateBasic,
- "truncate-ftruncate": truncateFtruncate,
+ "truncate-basic": truncateBasic,
+ "truncate-ftruncate": truncateFtruncate,
+ "truncate-enoent": truncateEnoent,
+ "truncate-ftruncate-ebadf": truncateFtruncateEbadf,
"iouring-setup": iouringSetup,
"iouring-enter": iouringEnter,
"iouring-register": iouringRegister,
@@ -2225,6 +2227,40 @@ func truncateFtruncate() error {
return syscall.Ftruncate(fd, 5)
}
+// truncateEnoent attempts to truncate a nonexistent file via raw SYS_TRUNCATE.
+// The syscall fails with ENOENT, but ior captures the enter_truncate
+// tracepoint because the path is read on entry.
+func truncateEnoent() error {
+ dir, cleanup, err := makeTempDir("truncate-enoent")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ path := filepath.Join(dir, "truncate-enoent-missing.txt")
+ pathBytes, err := syscall.BytePtrFromString(path)
+ if err != nil {
+ return fmt.Errorf("path bytes: %w", err)
+ }
+ _, _, errno := syscall.Syscall(syscall.SYS_TRUNCATE, uintptr(unsafe.Pointer(pathBytes)), 0, 0)
+ runtime.KeepAlive(pathBytes)
+ if errno == 0 {
+ return fmt.Errorf("expected ENOENT, but truncate succeeded")
+ }
+ return nil
+}
+
+// truncateFtruncateEbadf calls raw SYS_FTRUNCATE on an invalid fd (99999).
+// The syscall fails with EBADF, but ior captures the enter_ftruncate
+// tracepoint because it is recorded on syscall entry.
+func truncateFtruncateEbadf() error {
+ _, _, errno := syscall.Syscall(syscall.SYS_FTRUNCATE, 99999, 0, 0)
+ if errno == 0 {
+ return fmt.Errorf("expected EBADF, but ftruncate succeeded")
+ }
+ return nil
+}
+
// openByHandleAt creates a file, resolves its handle via name_to_handle_at,
// then opens it via open_by_handle_at. Requires root (CAP_DAC_READ_SEARCH).
// LockOSThread prevents goroutine migration between the two syscalls so that
diff --git a/integrationtests/truncate_test.go b/integrationtests/truncate_test.go
index 9b94eed..7d3f12e 100644
--- a/integrationtests/truncate_test.go
+++ b/integrationtests/truncate_test.go
@@ -23,3 +23,24 @@ func TestTruncateFtruncate(t *testing.T) {
},
})
}
+
+func TestTruncateEnoent(t *testing.T) {
+ runScenario(t, "truncate-enoent", []ExpectedEvent{
+ {
+ PathContains: "truncate-enoent-missing.txt",
+ Tracepoint: "enter_truncate",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+}
+
+func TestTruncateFtruncateEbadf(t *testing.T) {
+ runScenario(t, "truncate-ftruncate-ebadf", []ExpectedEvent{
+ {
+ Tracepoint: "enter_ftruncate",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+}