Repository: cilium/ebpf
Pull request #285 (2021-04-14 10:06:31)

run-test.sh cleanups
run-tests: run a custom command in the vm
    
    Let the user specify the command to execute in the VM. This is useful
    if you want to run only a subset of tests, for example.

run-tests: use go mod tidy
    
    go mod download for some reason modifies go.mod. According to an upstream
    Golang issue "go mod tidy" is the preferred command to prime the module
    cache.



Patch:
diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml
index 0845ccc..51d4c8d 100644
--- a/.semaphore/semaphore.yml
+++ b/.semaphore/semaphore.yml
@@ -53,6 +53,6 @@ blocks:
       - name: Run unit tests
         matrix:
           - env_var: KERNEL_VERSION
-            values: ["5.10", "5.9", "5.4", "4.19", "4.9"]
+            values: ["5.10", "5.4", "4.19", "4.9"]
         commands:
           - timeout -s KILL 600s ./run-tests.sh $KERNEL_VERSION
diff --git a/link/kprobe.go b/link/kprobe.go
index 9e519c2..3c1d6bd 100644
--- a/link/kprobe.go
+++ b/link/kprobe.go
@@ -158,11 +158,10 @@ func pmuKprobe(symbol string, ret bool) (*perfEvent, error) {
 
 	// Kernel has perf_kprobe PMU available, initialize perf event.
 	return &perfEvent{
-		fd:       internal.NewFD(uint32(fd)),
-		pmuID:    et,
-		name:     symbol,
-		ret:      ret,
-		progType: ebpf.Kprobe,
+		fd:    internal.NewFD(uint32(fd)),
+		pmuID: et,
+		name:  symbol,
+		typ:   kprobeEventType(ret),
 	}, nil
 }
 
@@ -214,9 +213,8 @@ func tracefsKprobe(symbol string, ret bool) (*perfEvent, error) {
 		fd:        fd,
 		group:     group,
 		name:      symbol,
-		ret:       ret,
 		tracefsID: tid,
-		progType:  ebpf.Kprobe, // kernel only allows attaching kprobe programs to kprobe events
+		typ:       kprobeEventType(ret),
 	}, nil
 }
 
@@ -335,3 +333,10 @@ func kretprobeBit() (uint64, error) {
 	})
 	return kprobeRetprobeBit.value, kprobeRetprobeBit.err
 }
+
+func kprobeEventType(ret bool) perfEventType {
+	if ret {
+		return kretprobeEvent
+	}
+	return kprobeEvent
+}
diff --git a/link/perf_event.go b/link/perf_event.go
index de973c7..1855d1a 100644
--- a/link/perf_event.go
+++ b/link/perf_event.go
@@ -52,6 +52,14 @@ const (
 	perfAllThreads = -1
 )
 
+type perfEventType uint8
+
+const (
+	tracepointEvent perfEventType = iota
+	kprobeEvent
+	kretprobeEvent
+)
+
 // A perfEvent represents a perf event kernel object. Exactly one eBPF program
 // can be attached to it. It is created based on a tracefs trace event or a
 // Performance Monitoring Unit (PMU).
@@ -66,11 +74,10 @@ type perfEvent struct {
 	// ID of the trace event read from tracefs. Valid IDs are non-zero.
 	tracefsID uint64
 
-	// True for kretprobes/uretprobes.
-	ret bool
+	// The event type determines the types of programs that can be attached.
+	typ perfEventType
 
-	fd       *internal.FD
-	progType ebpf.ProgramType
+	fd *internal.FD
 }
 
 func (pe *perfEvent) isLink() {}
@@ -117,13 +124,13 @@ func (pe *perfEvent) Close() error {
 		return fmt.Errorf("closing perf event fd: %w", err)
 	}
 
-	switch t := pe.progType; t {
-	case ebpf.Kprobe:
+	switch pe.typ {
+	case kprobeEvent, kretprobeEvent:
 		// For kprobes created using tracefs, clean up the <tracefs>/kprobe_events entry.
 		if pe.tracefsID != 0 {
 			return closeTraceFSKprobeEvent(pe.group, pe.name)
 		}
-	case ebpf.TracePoint:
+	case tracepointEvent:
 		// Tracepoint trace events don't hold any extra resources.
 		return nil
 	}
@@ -141,12 +148,21 @@ func (pe *perfEvent) attach(prog *ebpf.Program) error {
 	if pe.fd == nil {
 		return errors.New("cannot attach to nil perf event")
 	}
-	if t := prog.Type(); t != pe.progType {
-		return fmt.Errorf("invalid program type (expected %s): %s", pe.progType, t)
-	}
 	if prog.FD() < 0 {
 		return fmt.Errorf("invalid program: %w", internal.ErrClosedFd)
 	}
+	switch pe.typ {
+	case kprobeEvent, kretprobeEvent:
+		if t := prog.Type(); t != ebpf.Kprobe {
+			return fmt.Errorf("invalid program type (expected %s): %s", ebpf.Kprobe, t)
+		}
+	case tracepointEvent:
+		if t := prog.Type(); t != ebpf.TracePoint {
+			return fmt.Errorf("invalid program type (expected %s): %s", ebpf.TracePoint, t)
+		}
+	default:
+		return fmt.Errorf("unknown perf event type: %d", pe.typ)
+	}
 
 	// The ioctl below will fail when the fd is invalid.
 	kfd, _ := pe.fd.Value()
diff --git a/link/tracepoint.go b/link/tracepoint.go
index 1652957..b8ae04b 100644
--- a/link/tracepoint.go
+++ b/link/tracepoint.go
@@ -44,7 +44,7 @@ func Tracepoint(group, name string, prog *ebpf.Program) (Link, error) {
 		tracefsID: tid,
 		group:     group,
 		name:      name,
-		progType:  ebpf.TracePoint,
+		typ:       tracepointEvent,
 	}
 
 	if err := pe.attach(prog); err != nil {


Test patch:
diff --git a/link/kprobe_test.go b/link/kprobe_test.go
index e02a141..255e3d7 100644
--- a/link/kprobe_test.go
+++ b/link/kprobe_test.go
@@ -103,14 +103,14 @@ func TestKprobeCreatePMU(t *testing.T) {
 	c.Assert(err, qt.IsNil)
 	defer pk.Close()
 
-	c.Assert(pk.progType, qt.Equals, ebpf.Kprobe)
+	c.Assert(pk.typ, qt.Equals, kprobeEvent)
 
 	// kretprobe happy path.
 	pr, err := pmuKprobe("printk", true)
 	c.Assert(err, qt.IsNil)
 	defer pr.Close()
 
-	c.Assert(pr.progType, qt.Equals, ebpf.Kprobe)
+	c.Assert(pr.typ, qt.Equals, kretprobeEvent)
 
 	// Expect os.ErrNotExist when specifying a non-existent kernel symbol
 	// on kernels 4.17 and up.
@@ -160,21 +160,22 @@ func TestKprobeTraceFS(t *testing.T) {
 	kp, err := tracefsKprobe(symbol, false)
 	c.Assert(err, qt.IsNil)
 	c.Assert(kp.Close(), qt.IsNil)
+	c.Assert(kp.typ, qt.Equals, kprobeEvent)
+
 	kp, err = tracefsKprobe(symbol, true)
 	c.Assert(err, qt.IsNil)
 	c.Assert(kp.Close(), qt.IsNil)
+	c.Assert(kp.typ, qt.Equals, kretprobeEvent)
 
 	// Create two identical trace events, ensure their IDs differ.
 	k1, err := tracefsKprobe(symbol, false)
 	c.Assert(err, qt.IsNil)
 	defer k1.Close()
-	c.Assert(k1.progType, qt.Equals, ebpf.Kprobe)
 	c.Assert(k1.tracefsID, qt.Not(qt.Equals), 0)
 
 	k2, err := tracefsKprobe(symbol, false)
 	c.Assert(err, qt.IsNil)
 	defer k2.Close()
-	c.Assert(k2.progType, qt.Equals, ebpf.Kprobe)
 	c.Assert(k2.tracefsID, qt.Not(qt.Equals), 0)
 
 	// Compare the kprobes' tracefs IDs.
diff --git a/link/tracepoint_test.go b/link/tracepoint_test.go
index 736f765..760f49d 100644
--- a/link/tracepoint_test.go
+++ b/link/tracepoint_test.go
@@ -12,12 +12,8 @@ import (
 	qt "github.com/frankban/quicktest"
 )
 
-func TestTracepoint(t *testing.T) {
-
-	// Requires at least 4.7 (98b5c2c65c29 "perf, bpf: allow bpf programs attach to tracepoints")
-	testutils.SkipOnOldKernel(t, "4.7", "tracepoint support")
-
-	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
+var (
+	tracepointSpec = ebpf.ProgramSpec{
 		Type:    ebpf.TracePoint,
 		License: "MIT",
 		Instructions: asm.Instructions{
@@ -25,7 +21,15 @@ func TestTracepoint(t *testing.T) {
 			asm.Mov.Imm(asm.R0, 0),
 			asm.Return(),
 		},
-	})
+	}
+)
+
+func TestTracepoint(t *testing.T) {
+
+	// Requires at least 4.7 (98b5c2c65c29 "perf, bpf: allow bpf programs attach to tracepoints")
+	testutils.SkipOnOldKernel(t, "4.7", "tracepoint support")
+
+	prog, err := ebpf.NewProgram(&tracepointSpec)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/run-tests.sh b/run-tests.sh
index ed8679f..0d307c4 100755
--- a/run-tests.sh
+++ b/run-tests.sh
@@ -1,6 +1,11 @@
 #!/bin/bash
 # Test the current package under a different kernel.
 # Requires virtme and qemu to be installed.
+# Examples:
+#     Run all tests on a 5.4 kernel
+#     $ ./run-tests.sh 5.4
+#     Run a subset of tests:
+#     $ ./run-tests.sh 5.4 go test ./link
 
 set -eu
 set -o pipefail
@@ -21,18 +26,14 @@ if [[ "${1:-}" = "--in-vm" ]]; then
     export KERNEL_SELFTESTS="/run/input/bpf"
   fi
 
-  readonly output="${1}"
-  shift
-
-  echo Running tests...
-  go test -v -coverpkg=./... -coverprofile="$output/coverage.txt" -count 1 ./...
-  touch "$output/success"
+  eval "$@"
+  touch "/run/output/success"
   exit 0
 fi
 
 # Pull all dependencies, so that we can run tests without the
 # vm having network access.
-go mod download
+go mod tidy
 
 # Use sudo if /dev/kvm isn't accessible by the current user.
 sudo=""
@@ -46,6 +47,7 @@ if [[ -z "${kernel_version}" ]]; then
   echo "Expecting kernel version as first argument"
   exit 1
 fi
+shift
 
 readonly kernel="linux-${kernel_version}.bz"
 readonly selftests="linux-${kernel_version}-selftests-bpf.bz"
@@ -68,6 +70,12 @@ else
   echo "No selftests found, disabling"
 fi
 
+if (( $# > 0 )); then
+  printf -v cmd " %q" "$@"
+else
+  printf -v cmd " %q" go test -v -coverpkg=./... -coverprofile="/run/output/coverage.txt" -count 1 ./...
+fi
+
 echo Testing on "${kernel_version}"
 $sudo virtme-run --kimg "${tmp_dir}/${kernel}" --memory 512M --pwd \
   --rw \
@@ -75,7 +83,7 @@ $sudo virtme-run --kimg "${tmp_dir}/${kernel}" --memory 512M --pwd \
   --rwdir=/run/output="${output}" \
   --rodir=/run/go-path="$(go env GOPATH)" \
   --rwdir=/run/go-cache="$(go env GOCACHE)" \
-  --script-sh "PATH=\"$PATH\" $(realpath "$0") --in-vm /run/output" \
+  --script-sh "PATH=\"$PATH\" $(realpath "$0") --in-vm $cmd" \
   --qemu-opts -smp 2 # need at least two CPUs for some tests
 
 if [[ ! -e "${output}/success" ]]; then
@@ -83,7 +91,7 @@ if [[ ! -e "${output}/success" ]]; then
   exit 1
 else
   echo "Test successful on ${kernel_version}"
-  if [[ -v COVERALLS_TOKEN ]]; then
+  if [[ -v COVERALLS_TOKEN && -f "${output}/coverage.txt" ]]; then
     goveralls -coverprofile="${output}/coverage.txt" -service=semaphore -repotoken "$COVERALLS_TOKEN"
   fi
 fi