summaryrefslogtreecommitdiff
path: root/integrationtests
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-22 22:31:35 +0200
committerPaul Buetow <paul@buetow.org>2026-02-22 22:31:35 +0200
commit4cd2c4e818a1438bf63d1ca05a6cf134f39bc06b (patch)
tree40b4ad1ab60a6f50973a66c4e273c91e0d8d265b /integrationtests
parent8e52ba5a8661c717f45e00608ad64f0adc6de3e1 (diff)
Add copy_file_range support and tracepoint attach tests
Diffstat (limited to 'integrationtests')
-rw-r--r--integrationtests/attach_tracepoints_test.go59
-rw-r--r--integrationtests/cmd/ioworkload/scenario_copy_file_range.go78
-rw-r--r--integrationtests/cmd/ioworkload/scenarios.go2
-rw-r--r--integrationtests/copy_file_range_test.go25
-rw-r--r--integrationtests/harness.go15
5 files changed, 175 insertions, 4 deletions
diff --git a/integrationtests/attach_tracepoints_test.go b/integrationtests/attach_tracepoints_test.go
new file mode 100644
index 0000000..89db494
--- /dev/null
+++ b/integrationtests/attach_tracepoints_test.go
@@ -0,0 +1,59 @@
+package integrationtests
+
+import "testing"
+
+func TestAttachTracepointsIncludeFilter(t *testing.T) {
+ h := newTestHarness(t)
+
+ // Only load openat tracepoints so write events from the workload are not captured.
+ result, pid, err := h.RunWithIorArgs("open-rdonly-write", defaultDuration, []string{
+ "-tps", "^sys_enter_openat$,^sys_exit_openat$",
+ })
+ if err != nil {
+ t.Fatalf("run scenario open-rdonly-write with include filter: %v", err)
+ }
+
+ AssertNoUnexpectedPID(t, result, pid)
+ AssertNoUnexpectedComm(t, result, "ioworkload")
+ AssertEventsPresent(t, result, []ExpectedEvent{
+ {
+ PathContains: "rdonlyfile.txt",
+ Tracepoint: "enter_openat",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+ AssertEventsAbsent(t, result, []ExpectedEvent{
+ {
+ PathContains: "rdonlyfile.txt",
+ Tracepoint: "enter_write",
+ Comm: "ioworkload",
+ },
+ })
+}
+
+func TestAttachTracepointsExcludeByInclusion(t *testing.T) {
+ h := newTestHarness(t)
+
+ // Negative case: include only write tracepoints; openat must not be captured.
+ result, pid, err := h.RunWithIorArgs("open-rdonly-write", defaultDuration, []string{
+ "-tps", "^sys_enter_write$,^sys_exit_write$",
+ })
+ if err != nil {
+ t.Fatalf("run scenario open-rdonly-write with write-only include filter: %v", err)
+ }
+
+ AssertNoUnexpectedPID(t, result, pid)
+ AssertNoUnexpectedComm(t, result, "ioworkload")
+ AssertEventsPresent(t, result, []ExpectedEvent{
+ {
+ Tracepoint: "enter_write",
+ MinCount: 1,
+ },
+ })
+ AssertEventsAbsent(t, result, []ExpectedEvent{
+ {
+ Tracepoint: "enter_openat",
+ },
+ })
+}
diff --git a/integrationtests/cmd/ioworkload/scenario_copy_file_range.go b/integrationtests/cmd/ioworkload/scenario_copy_file_range.go
new file mode 100644
index 0000000..87531b0
--- /dev/null
+++ b/integrationtests/cmd/ioworkload/scenario_copy_file_range.go
@@ -0,0 +1,78 @@
+package main
+
+import (
+ "fmt"
+ "path/filepath"
+ "syscall"
+)
+
+// SYS_COPY_FILE_RANGE on x86_64 Linux.
+const sysCopyFileRange = 326
+
+// copyFileRangeBasic copies bytes from a source file to a destination file
+// using copy_file_range(2) with flags=0 as required by the manpage.
+func copyFileRangeBasic() error {
+ dir, cleanup, err := makeTempDir("copy-file-range-basic")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ srcPath := filepath.Join(dir, "copyrangesrc.txt")
+ dstPath := filepath.Join(dir, "copyrangedst.txt")
+
+ srcFd, err := syscall.Open(srcPath, syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0o644)
+ if err != nil {
+ return fmt.Errorf("open source: %w", err)
+ }
+ defer syscall.Close(srcFd)
+
+ dstFd, err := syscall.Open(dstPath, syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0o644)
+ if err != nil {
+ return fmt.Errorf("open destination: %w", err)
+ }
+ defer syscall.Close(dstFd)
+
+ data := []byte("copy_file_range integration data")
+ if _, err := syscall.Write(srcFd, data); err != nil {
+ return fmt.Errorf("write source: %w", err)
+ }
+
+ n, _, errno := syscall.Syscall6(uintptr(sysCopyFileRange), uintptr(srcFd), 0, uintptr(dstFd), 0, uintptr(len(data)), 0)
+ if errno != 0 {
+ return fmt.Errorf("copy_file_range: %w", errno)
+ }
+ if n == 0 {
+ return fmt.Errorf("copy_file_range copied 0 bytes")
+ }
+
+ return nil
+}
+
+// copyFileRangeBadDstFd calls copy_file_range(2) with an invalid destination fd.
+// The syscall should fail with EBADF, while still emitting the enter tracepoint.
+func copyFileRangeBadDstFd() error {
+ dir, cleanup, err := makeTempDir("copy-file-range-bad-dst")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ srcPath := filepath.Join(dir, "copyrangeebadfsrc.txt")
+ srcFd, err := syscall.Open(srcPath, syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0o644)
+ if err != nil {
+ return fmt.Errorf("open source: %w", err)
+ }
+ defer syscall.Close(srcFd)
+
+ if _, err := syscall.Write(srcFd, []byte("copy_file_range ebadf data")); err != nil {
+ return fmt.Errorf("write source: %w", err)
+ }
+
+ _, _, errno := syscall.Syscall6(uintptr(sysCopyFileRange), uintptr(srcFd), 0, uintptr(99999), 0, uintptr(16), 0)
+ if errno != syscall.EBADF {
+ return fmt.Errorf("expected EBADF from copy_file_range with invalid dst fd, got %v", errno)
+ }
+
+ return nil
+}
diff --git a/integrationtests/cmd/ioworkload/scenarios.go b/integrationtests/cmd/ioworkload/scenarios.go
index d99b584..f9a8e47 100644
--- a/integrationtests/cmd/ioworkload/scenarios.go
+++ b/integrationtests/cmd/ioworkload/scenarios.go
@@ -85,6 +85,8 @@ var scenarios = map[string]func() error{
"mmap-basic": mmapBasic,
"mmap-msync-sync": mmapMsyncSync,
"mmap-msync-invalid-flags": mmapMsyncInvalidFlags,
+ "copy-file-range-basic": copyFileRangeBasic,
+ "copy-file-range-bad-dst-fd": copyFileRangeBadDstFd,
"truncate-basic": truncateBasic,
"truncate-ftruncate": truncateFtruncate,
"truncate-enoent": truncateEnoent,
diff --git a/integrationtests/copy_file_range_test.go b/integrationtests/copy_file_range_test.go
new file mode 100644
index 0000000..d87c5af
--- /dev/null
+++ b/integrationtests/copy_file_range_test.go
@@ -0,0 +1,25 @@
+package integrationtests
+
+import "testing"
+
+func TestCopyFileRangeBasic(t *testing.T) {
+ runScenario(t, "copy-file-range-basic", []ExpectedEvent{
+ {
+ PathContains: "copyrangesrc.txt",
+ Tracepoint: "enter_copy_file_range",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+}
+
+func TestCopyFileRangeBadDstFd(t *testing.T) {
+ runScenario(t, "copy-file-range-bad-dst-fd", []ExpectedEvent{
+ {
+ PathContains: "copyrangeebadfsrc.txt",
+ Tracepoint: "enter_copy_file_range",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+}
diff --git a/integrationtests/harness.go b/integrationtests/harness.go
index fde52e6..a130c85 100644
--- a/integrationtests/harness.go
+++ b/integrationtests/harness.go
@@ -30,12 +30,17 @@ type TestHarness struct {
// binary, reads its PID from stdout, launches ior with a PID filter, waits
// for both to finish, and parses the resulting .ior.zst file.
func (h *TestHarness) Run(scenario string, duration int) (TestResult, int, error) {
+ return h.RunWithIorArgs(scenario, duration, nil)
+}
+
+// RunWithIorArgs behaves like Run but forwards additional args to ior.
+func (h *TestHarness) RunWithIorArgs(scenario string, duration int, extraIorArgs []string) (TestResult, int, error) {
workloadCmd, workloadPID, err := h.startWorkload(scenario)
if err != nil {
return TestResult{}, 0, err
}
- iorCmd, err := h.startIor(workloadPID, scenario, duration)
+ iorCmd, err := h.startIor(workloadPID, scenario, duration, extraIorArgs)
if err != nil {
workloadCmd.Process.Kill()
workloadCmd.Wait()
@@ -111,18 +116,20 @@ func (h *TestHarness) startWorkload(scenario string) (*exec.Cmd, int, error) {
}
}
-func (h *TestHarness) startIor(pid int, scenario string, duration int) (*exec.Cmd, error) {
+func (h *TestHarness) startIor(pid int, scenario string, duration int, extraArgs []string) (*exec.Cmd, error) {
bpfLink := filepath.Join(h.OutputDir, "ior.bpf.o")
if err := os.Symlink(h.BpfObject, bpfLink); err != nil {
return nil, fmt.Errorf("symlink bpf object: %w", err)
}
- cmd := exec.Command(h.IorBinary,
+ args := []string{
"-pid", strconv.Itoa(pid),
"-flamegraph",
"-name", scenario,
"-duration", strconv.Itoa(duration),
- )
+ }
+ args = append(args, extraArgs...)
+ cmd := exec.Command(h.IorBinary, args...)
cmd.Dir = h.OutputDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr