Repository: cilium/ebpf
Pull request #745 (2022-07-21 15:15:35)

Fix arm64 testsuite failures
Tests now pass on arm64:

```
$ go test -exec sudo ./...
ok  	github.com/cilium/ebpf	0.473s
ok  	github.com/cilium/ebpf/asm	0.014s
ok  	github.com/cilium/ebpf/btf	0.671s
ok  	github.com/cilium/ebpf/cmd/bpf2go	0.717s
ok  	github.com/cilium/ebpf/cmd/bpf2go/test	0.040s
ok  	github.com/cilium/ebpf/features	0.143s
ok  	github.com/cilium/ebpf/internal	0.024s
?   	github.com/cilium/ebpf/internal/cmd/gentypes	[no test files]
ok  	github.com/cilium/ebpf/internal/epoll	1.020s
ok  	github.com/cilium/ebpf/internal/sys	0.015s
?   	github.com/cilium/ebpf/internal/testutils	[no test files]
?   	github.com/cilium/ebpf/internal/unix	[no test files]
ok  	github.com/cilium/ebpf/link	1.653s
ok  	github.com/cilium/ebpf/perf	0.260s
ok  	github.com/cilium/ebpf/ringbuf	0.108s
ok  	github.com/cilium/ebpf/rlimit	0.007s
```

Best review on a per-commit basis.

Updates #266 

rgo3: @lmb The `HaveProgramHelper` API in the features package also returns a phew more unwrapped errors, specifically:
* `func validateProgramHelper`
* `func haveProgramHelper`
lmb: Thanks! Just saw the following as well:

```
// EPERM is kept as-is and is not converted or wrapped.
```

Why is that?
rgo3: Only real explanation I can give you is in @ti-mo's [commit](https://github.com/cilium/ebpf/pull/321/commits/9bd52a629ed4a3830618fe260939b4bfd8363f6d) message. To me returning EPERM directly feels reasonable, I'm not sure if there is any benefit to wrapping it.
lmb: Ok, I read that as "don't let EPERM fall into the default switch case". Wrapping the error forces use of `errors.Is` which gives you more flexibility in the future if you need to change the returned error somehow.
lmb: Ugh, finally a green build. I'll merge this tomorrow-ish unless someone objects.
ti-mo: > > `// EPERM is kept as-is and is not converted or wrapped.`
>
> Why is that?

Ha, I just asked myself the same question today while working on https://github.com/cilium/ebpf/pull/746. We should probably remove it, it complicates the logic unnecessarily.

I want to wrap everything except `ErrNotSupported` using a defer closure, as it'll make the `switch` statement much more straightforward and easier to extend. Looking at your patches here, we might as well wrap every returned error and set the error string containing `%w` depending on the error type.
ti-mo: @lmb I've dropped eb06124f819e3e28fd9ec46a779aa18471783d2b from the PR since it breaks API, and from what I understand this is more cosmetic than functional. Callers might already rely on ENOENT specifically being returned for missing symbols across all kernels, as well as when scanning for valid offsets within a known symbol.

The original meaning of ENOENT in this context is 'symbol not found', which, with some creativity, could be interpreted as 'offset not found' when trying to attach to specific offsets, although that might be a stretch. Could you maybe try to tackle this from another angle? I don't see a simple way out here.

Patch:
diff --git a/features/map.go b/features/map.go
index eab39bf..4d418c4 100644
--- a/features/map.go
+++ b/features/map.go
@@ -108,7 +108,7 @@ func HaveMapType(mt ebpf.MapType) error {
 
 func validateMaptype(mt ebpf.MapType) error {
 	if mt > mt.Max() {
-		return os.ErrInvalid
+		return fmt.Errorf("%w", os.ErrInvalid)
 	}
 
 	if mt == ebpf.StructOpsMap {
@@ -143,7 +143,7 @@ func haveMapType(mt ebpf.MapType) error {
 	// of the struct known by the running kernel, meaning the kernel is too old
 	// to support the given map type.
 	case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
-		err = ebpf.ErrNotSupported
+		err = fmt.Errorf("%w", ebpf.ErrNotSupported)
 
 	// EPERM is kept as-is and is not converted or wrapped.
 	case errors.Is(err, unix.EPERM):
diff --git a/features/misc.go b/features/misc.go
index 8a81f00..73d66d4 100644
--- a/features/misc.go
+++ b/features/misc.go
@@ -99,7 +99,7 @@ func probeMisc(mt miscType) error {
 	// of the struct known by the running kernel, meaning the kernel is too old
 	// to support the given map type.
 	case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
-		err = ebpf.ErrNotSupported
+		err = fmt.Errorf("%w", ebpf.ErrNotSupported)
 
 	// EPERM is kept as-is and is not converted or wrapped.
 	case errors.Is(err, unix.EPERM):
diff --git a/features/prog.go b/features/prog.go
index d38964a..2234ab1 100644
--- a/features/prog.go
+++ b/features/prog.go
@@ -112,7 +112,7 @@ func HaveProgramType(pt ebpf.ProgramType) error {
 
 func validateProgramType(pt ebpf.ProgramType) error {
 	if pt > pt.Max() {
-		return os.ErrInvalid
+		return fmt.Errorf("%w", os.ErrInvalid)
 	}
 
 	if progLoadProbeNotImplemented(pt) {
@@ -145,7 +145,7 @@ func haveProgramType(pt ebpf.ProgramType) error {
 	// of the struct known by the running kernel, meaning the kernel is too old
 	// to support the given prog type.
 	case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
-		err = ebpf.ErrNotSupported
+		err = fmt.Errorf("%w", ebpf.ErrNotSupported)
 
 	// EPERM is kept as-is and is not converted or wrapped.
 	case errors.Is(err, unix.EPERM):
@@ -191,7 +191,7 @@ func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
 
 func validateProgramHelper(helper asm.BuiltinFunc) error {
 	if helper > helper.Max() {
-		return os.ErrInvalid
+		return fmt.Errorf("%w", os.ErrInvalid)
 	}
 
 	return nil
@@ -230,7 +230,7 @@ func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
 	// to support the given prog type.
 	case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
 		// TODO: possibly we need to check verifier output here to be sure
-		err = ebpf.ErrNotSupported
+		err = fmt.Errorf("%w", ebpf.ErrNotSupported)
 
 	// EPERM is kept as-is and is not converted or wrapped.
 	case errors.Is(err, unix.EPERM):
diff --git a/internal/sys/syscall.go b/internal/sys/syscall.go
index 2a5935d..cfadedf 100644
--- a/internal/sys/syscall.go
+++ b/internal/sys/syscall.go
@@ -8,6 +8,11 @@ import (
 	"github.com/cilium/ebpf/internal/unix"
 )
 
+// ENOTSUPP is a Linux internal error code that has leaked into UAPI.
+//
+// It is not the same as ENOTSUP or EOPNOTSUPP.
+var ENOTSUPP = syscall.Errno(524)
+
 // BPF wraps SYS_BPF.
 //
 // Any pointers contained in attr must use the Pointer type from this package.
@@ -108,6 +113,13 @@ func (we wrappedErrno) Unwrap() error {
 	return we.Errno
 }
 
+func (we wrappedErrno) Error() string {
+	if we.Errno == ENOTSUPP {
+		return "operation not supported"
+	}
+	return we.Errno.Error()
+}
+
 type syscallError struct {
 	error
 	errno syscall.Errno
diff --git a/internal/unix/types_linux.go b/internal/unix/types_linux.go
index db4a1f5..8a030da 100644
--- a/internal/unix/types_linux.go
+++ b/internal/unix/types_linux.go
@@ -24,8 +24,7 @@ const (
 	E2BIG   = linux.E2BIG
 	EFAULT  = linux.EFAULT
 	EACCES  = linux.EACCES
-	// ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP
-	ENOTSUPP = syscall.Errno(0x20c)
+	EILSEQ  = linux.EILSEQ
 
 	BPF_F_NO_PREALLOC        = linux.BPF_F_NO_PREALLOC
 	BPF_F_NUMA_NODE          = linux.BPF_F_NUMA_NODE
diff --git a/internal/unix/types_other.go b/internal/unix/types_other.go
index 133c267..b1318f6 100644
--- a/internal/unix/types_other.go
+++ b/internal/unix/types_other.go
@@ -25,8 +25,7 @@ const (
 	E2BIG  = syscall.Errno(0)
 	EFAULT = syscall.EFAULT
 	EACCES = syscall.Errno(0)
-	// ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP
-	ENOTSUPP = syscall.Errno(0x20c)
+	EILSEQ = syscall.Errno(0)
 
 	BPF_F_NO_PREALLOC        = 0
 	BPF_F_NUMA_NODE          = 0
diff --git a/link/kprobe.go b/link/kprobe.go
index fdf622a..86741ad 100644
--- a/link/kprobe.go
+++ b/link/kprobe.go
@@ -307,14 +307,15 @@ func pmuProbe(typ probeType, args probeArgs) (*perfEvent, error) {
 	if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
 		return nil, fmt.Errorf("symbol '%s+%#x' not found: %w", args.symbol, args.offset, os.ErrNotExist)
 	}
-	// Since commit ab105a4fb894, -EILSEQ is returned when a kprobe sym+offset is resolved
-	// to an invalid insn boundary.
-	if errors.Is(err, syscall.EILSEQ) {
+	// Since commit ab105a4fb894, EILSEQ is returned when a kprobe sym+offset is resolved
+	// to an invalid insn boundary. The exact conditions that trigger this error are
+	// arch specific however.
+	if errors.Is(err, unix.EILSEQ) {
 		return nil, fmt.Errorf("symbol '%s+%#x' not found (bad insn boundary): %w", args.symbol, args.offset, os.ErrNotExist)
 	}
 	// Since at least commit cb9a19fe4aa51, ENOTSUPP is returned
 	// when attempting to set a uprobe on a trap instruction.
-	if errors.Is(err, unix.ENOTSUPP) {
+	if errors.Is(err, sys.ENOTSUPP) {
 		return nil, fmt.Errorf("failed setting uprobe on offset %#x (possible trap insn): %w", args.offset, err)
 	}
 	if err != nil {
diff --git a/link/link.go b/link/link.go
index 067d010..8ae1ba5 100644
--- a/link/link.go
+++ b/link/link.go
@@ -177,7 +177,7 @@ func AttachRawLink(opts RawLinkOptions) (*RawLink, error) {
 	}
 	fd, err := sys.LinkCreate(&attr)
 	if err != nil {
-		return nil, fmt.Errorf("can't create link: %s", err)
+		return nil, fmt.Errorf("create link: %w", err)
 	}
 
 	return &RawLink{fd, ""}, nil
diff --git a/link/tracing.go b/link/tracing.go
index e47e61a..5c07b77 100644
--- a/link/tracing.go
+++ b/link/tracing.go
@@ -1,6 +1,7 @@
 package link
 
 import (
+	"errors"
 	"fmt"
 
 	"github.com/cilium/ebpf"
@@ -70,6 +71,10 @@ func AttachFreplace(targetProg *ebpf.Program, name string, prog *ebpf.Program) (
 		Attach:  ebpf.AttachNone,
 		BTF:     typeID,
 	})
+	if errors.Is(err, sys.ENOTSUPP) {
+		// This may be returned by bpf_tracing_prog_attach via bpf_arch_text_poke.
+		return nil, fmt.Errorf("create raw tracepoint: %w", ErrNotSupported)
+	}
 	if err != nil {
 		return nil, err
 	}
@@ -99,8 +104,12 @@ func attachBTFID(program *ebpf.Program) (Link, error) {
 	fd, err := sys.RawTracepointOpen(&sys.RawTracepointOpenAttr{
 		ProgFd: uint32(program.FD()),
 	})
+	if errors.Is(err, sys.ENOTSUPP) {
+		// This may be returned by bpf_tracing_prog_attach via bpf_arch_text_poke.
+		return nil, fmt.Errorf("create raw tracepoint: %w", ErrNotSupported)
+	}
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("create raw tracepoint: %w", err)
 	}
 
 	raw := RawLink{fd: fd}
diff --git a/prog.go b/prog.go
index 844e1f9..a0ed434 100644
--- a/prog.go
+++ b/prog.go
@@ -600,7 +600,7 @@ var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() e
 		// We know that PROG_TEST_RUN is supported if we get EINTR.
 		return nil
 
-	case errors.Is(err, unix.ENOTSUPP):
+	case errors.Is(err, sys.ENOTSUPP):
 		// The first PROG_TEST_RUN patches shipped in 4.12 didn't include
 		// a test runner for SocketFilter. ENOTSUPP means PROG_TEST_RUN is
 		// supported, but not for the program type used in the probe.
@@ -661,7 +661,7 @@ func (p *Program) testRun(opts *RunOptions) (uint32, time.Duration, error) {
 			continue
 		}
 
-		if errors.Is(err, unix.ENOTSUPP) {
+		if errors.Is(err, sys.ENOTSUPP) {
 			return 0, 0, fmt.Errorf("kernel doesn't support testing program type %s: %w", p.Type(), ErrNotSupported)
 		}
 
diff --git a/syscalls.go b/syscalls.go
index e5c270a..c100142 100644
--- a/syscalls.go
+++ b/syscalls.go
@@ -143,8 +143,8 @@ func wrapMapError(err error) error {
 		return sys.Error(ErrKeyExist, unix.EEXIST)
 	}
 
-	if errors.Is(err, unix.ENOTSUPP) {
-		return sys.Error(ErrNotSupported, unix.ENOTSUPP)
+	if errors.Is(err, sys.ENOTSUPP) {
+		return sys.Error(ErrNotSupported, sys.ENOTSUPP)
 	}
 
 	if errors.Is(err, unix.E2BIG) {


Test patch:
diff --git a/features/map_test.go b/features/map_test.go
index 7eeeb62..aa12937 100644
--- a/features/map_test.go
+++ b/features/map_test.go
@@ -1,6 +1,7 @@
 package features
 
 import (
+	"errors"
 	"fmt"
 	"math"
 	"os"
@@ -70,13 +71,13 @@ func TestHaveMapType(t *testing.T) {
 }
 
 func TestHaveMapTypeUnsupported(t *testing.T) {
-	if err := haveMapType(ebpf.MapType(math.MaxUint32)); err != ebpf.ErrNotSupported {
+	if err := haveMapType(ebpf.MapType(math.MaxUint32)); !errors.Is(err, ebpf.ErrNotSupported) {
 		t.Fatalf("Expected ebpf.ErrNotSupported but was: %v", err)
 	}
 }
 
 func TestHaveMapTypeInvalid(t *testing.T) {
-	if err := HaveMapType(ebpf.MapType(math.MaxUint32)); err != os.ErrInvalid {
+	if err := HaveMapType(ebpf.MapType(math.MaxUint32)); !errors.Is(err, os.ErrInvalid) {
 		t.Fatalf("Expected os.ErrInvalid but was: %v", err)
 	}
 
diff --git a/features/prog_test.go b/features/prog_test.go
index 3fbc7e1..e4141af 100644
--- a/features/prog_test.go
+++ b/features/prog_test.go
@@ -85,13 +85,13 @@ func TestHaveProgramType(t *testing.T) {
 }
 
 func TestHaveProgramTypeUnsupported(t *testing.T) {
-	if err := haveProgramType(ebpf.ProgramType(math.MaxUint32)); err != ebpf.ErrNotSupported {
+	if err := haveProgramType(ebpf.ProgramType(math.MaxUint32)); !errors.Is(err, ebpf.ErrNotSupported) {
 		t.Fatalf("Expected ebpf.ErrNotSupported but was: %v", err)
 	}
 }
 
 func TestHaveProgramTypeInvalid(t *testing.T) {
-	if err := HaveProgramType(ebpf.ProgramType(math.MaxUint32)); err != os.ErrInvalid {
+	if err := HaveProgramType(ebpf.ProgramType(math.MaxUint32)); !errors.Is(err, os.ErrInvalid) {
 		t.Fatalf("Expected os.ErrInvalid but was: %v", err)
 	}
 }
@@ -174,7 +174,7 @@ func TestHaveProgramHelperUnsupported(t *testing.T) {
 
 	testutils.SkipOnOldKernel(t, minVersion, feature)
 
-	if err := haveProgramHelper(pt, asm.BuiltinFunc(math.MaxInt32)); err != ebpf.ErrNotSupported {
+	if err := haveProgramHelper(pt, asm.BuiltinFunc(math.MaxInt32)); !errors.Is(err, ebpf.ErrNotSupported) {
 		t.Fatalf("Expected ebpf.ErrNotSupported but was: %v", err)
 	}
 }
diff --git a/internal/sys/syscall_test.go b/internal/sys/syscall_test.go
index c39e7ec..dd0b394 100644
--- a/internal/sys/syscall_test.go
+++ b/internal/sys/syscall_test.go
@@ -5,6 +5,8 @@ import (
 	"testing"
 
 	"github.com/cilium/ebpf/internal/unix"
+
+	qt "github.com/frankban/quicktest"
 )
 
 func TestObjName(t *testing.T) {
@@ -32,6 +34,9 @@ func TestWrappedErrno(t *testing.T) {
 	if errors.Is(a, unix.EAGAIN) {
 		t.Error("errors.Is(wrappedErrno, EAGAIN) returns true")
 	}
+
+	notsupp := wrappedErrno{ENOTSUPP}
+	qt.Assert(t, notsupp.Error(), qt.Contains, "operation not supported")
 }
 
 func TestSyscallError(t *testing.T) {
diff --git a/link/kprobe_amd64_test.go b/link/kprobe_amd64_test.go
deleted file mode 100644
index 7bcad45..0000000
--- a/link/kprobe_amd64_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-//go:build amd64
-// +build amd64
-
-package link
-
-import (
-	"errors"
-	"os"
-	"testing"
-
-	"github.com/cilium/ebpf"
-)
-
-func TestKprobeOffset(t *testing.T) {
-	prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
-
-	for i := uint64(2); i < 10; i++ {
-		k, err := Kprobe("inet6_release", prog, &KprobeOptions{Offset: i})
-		if err != nil {
-			continue
-		}
-		k.Close()
-
-		_, err = Kprobe("inet6_release", prog, &KprobeOptions{Offset: i - 1})
-		if !errors.Is(err, os.ErrNotExist) {
-			t.Fatalf("expected 'os.ErrNotExist', got: '%v'", err)
-		}
-		return
-	}
-
-	t.Fatal("no valid offsets found")
-}
diff --git a/link/kprobe_test.go b/link/kprobe_test.go
index d181305..081b5c6 100644
--- a/link/kprobe_test.go
+++ b/link/kprobe_test.go
@@ -20,9 +20,8 @@ var ksym = "vprintk"
 // Collection of various symbols present in all tested kernels.
 // Compiler optimizations result in different names for these symbols.
 var symTests = []string{
-	"async_resume.cold",         // marked with 'cold' gcc attribute, unlikely to be executed
 	"echo_char.isra.0",          // function optimized by -fipa-sra
-	"get_buffer.constprop.0",    // optimized function with constant operands
+	"proc_get_long.constprop.0", // optimized function with constant operands
 	"unregister_kprobes.part.0", // function body that was split and partially inlined
 }
 
@@ -54,6 +53,23 @@ func TestKprobe(t *testing.T) {
 	testLink(t, k, prog)
 }
 
+func TestKprobeOffset(t *testing.T) {
+	prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
+
+	// The layout of a function is compiler and arch dependent, so we try to
+	// find a valid attach target in the first few bytes of the function.
+	for i := uint64(1); i < 16; i++ {
+		k, err := Kprobe("inet6_release", prog, &KprobeOptions{Offset: i})
+		if err != nil {
+			continue
+		}
+		k.Close()
+		return
+	}
+
+	t.Fatal("Can't attach with non-zero offset")
+}
+
 func TestKretprobe(t *testing.T) {
 	prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
 
diff --git a/link/perf_event_test.go b/link/perf_event_test.go
index e3b3e8e..aadaa7a 100644
--- a/link/perf_event_test.go
+++ b/link/perf_event_test.go
@@ -27,8 +27,7 @@ func TestTraceEventTypePMU(t *testing.T) {
 func TestTraceEventID(t *testing.T) {
 	c := qt.New(t)
 
-	// using sys_enter_mkdir here as it exist in old kernel (< 4.9) as well
-	eid, err := getTraceEventID("syscalls", "sys_enter_mkdir")
+	eid, err := getTraceEventID("syscalls", "sys_enter_mmap")
 	c.Assert(err, qt.IsNil)
 	c.Assert(eid, qt.Not(qt.Equals), 0)
 }
diff --git a/link/tracing_test.go b/link/tracing_test.go
index 8c846f5..60c3993 100644
--- a/link/tracing_test.go
+++ b/link/tracing_test.go
@@ -38,6 +38,7 @@ func TestFreplace(t *testing.T) {
 		defer replacement.Close()
 
 		freplace, err := AttachFreplace(nil, "", replacement)
+		testutils.SkipIfNotSupported(t, err)
 		if err != nil {
 			t.Fatal("Can't create freplace:", err)
 		}
@@ -48,6 +49,7 @@ func TestFreplace(t *testing.T) {
 
 func TestTracing(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "5.11", "BPF_LINK_TYPE_TRACING")
+
 	tests := []struct {
 		name        string
 		attachTo    string
@@ -85,6 +87,7 @@ func TestTracing(t *testing.T) {
 			prog := mustLoadProgram(t, tt.programType, tt.attachType, tt.attachTo)
 
 			link, err := AttachTracing(TracingOptions{Program: prog})
+			testutils.SkipIfNotSupported(t, err)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -100,6 +103,7 @@ func TestLSM(t *testing.T) {
 	prog := mustLoadProgram(t, ebpf.LSM, ebpf.AttachLSMMac, "file_mprotect")
 
 	link, err := AttachLSM(LSMOptions{Program: prog})
+	testutils.SkipIfNotSupported(t, err)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/link/uprobe_test.go b/link/uprobe_test.go
index 2c05890..33f57a8 100644
--- a/link/uprobe_test.go
+++ b/link/uprobe_test.go
@@ -94,9 +94,13 @@ func TestUprobeExtNotFound(t *testing.T) {
 func TestUprobeExtWithOpts(t *testing.T) {
 	prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
 
-	// This Uprobe is broken and will not work because the offset is not
-	// correct. This is expected since the offset is provided by the user.
-	up, err := bashEx.Uprobe("open", prog, &UprobeOptions{Address: 0x1})
+	// NB: It's not possible to invoke the uprobe since we use an arbitrary
+	// address.
+	up, err := bashEx.Uprobe("open", prog, &UprobeOptions{
+		// arm64 doesn't seem to allow addresses on the first page. Use
+		// the first byte of the second page.
+		Address: uint64(os.Getpagesize()),
+	})
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/map_test.go b/map_test.go
index 915c341..2660dde 100644
--- a/map_test.go
+++ b/map_test.go
@@ -382,20 +382,23 @@ func TestMapWithLock(t *testing.T) {
 			t.Fatalf("Want value 5, got %d", value.Cnt)
 		}
 
-		value.Cnt = 0
-		err = m.LookupAndDeleteWithFlags(&key, &value, LookupLock)
-		if err != nil {
-			t.Fatal(err)
-		}
-		if value.Cnt != 5 {
-			t.Fatalf("Want value 5, got %d", value.Cnt)
-		}
+		t.Run("LookupAndDelete", func(t *testing.T) {
+			testutils.SkipOnOldKernel(t, "5.14", "LOOKUP_AND_DELETE flags")
 
-		err = m.LookupWithFlags(&key, &value, LookupLock)
-		if err != nil && !errors.Is(err, ErrKeyNotExist) {
-			t.Fatal(err)
-		}
+			value.Cnt = 0
+			err = m.LookupAndDeleteWithFlags(&key, &value, LookupLock)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if value.Cnt != 5 {
+				t.Fatalf("Want value 5, got %d", value.Cnt)
+			}
 
+			err = m.LookupWithFlags(&key, &value, LookupLock)
+			if err != nil && !errors.Is(err, ErrKeyNotExist) {
+				t.Fatal(err)
+			}
+		})
 	})
 }