diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-21 21:44:11 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-21 21:44:11 +0200 |
| commit | 08cb9dfe46f843114feb42cc9ffa599717ebcc32 (patch) | |
| tree | f09fdbce3bb8654bf52180ccf598173e3d8a8976 | |
| parent | d455543bf0838c7fe2250081c5ea8ed3e275d236 (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.go | 40 | ||||
| -rw-r--r-- | integrationtests/truncate_test.go | 21 |
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, + }, + }) +} |
