aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-06-12 05:20:35 +0000
committerGravatar Runxi Yu2026-06-12 05:21:27 +0000
commit4e27e87536ae946ade5d45a5d0188a4d914ba897 (patch)
treed2b0fbabe08afbdb111ff9615021e988b8be67dc
parentinternal/mmap: Add Mmap (diff)
internal/mmap: Add tests
-rw-r--r--internal/mmap/mmap.go2
-rw-r--r--internal/mmap/mmap_test.go111
2 files changed, 112 insertions, 1 deletions
diff --git a/internal/mmap/mmap.go b/internal/mmap/mmap.go
index d437b12d..a8644a3b 100644
--- a/internal/mmap/mmap.go
+++ b/internal/mmap/mmap.go
@@ -74,7 +74,7 @@ func (mmap *Mmap) Data() []byte {
// Close unmaps the mapping,
// invalidating previously returned data.
//
-// Labels: Idem-Yes.
+// Labels: Idem-Yes, MT-Unsafe.
func (mmap *Mmap) Close() error {
if mmap.data == nil {
return nil
diff --git a/internal/mmap/mmap_test.go b/internal/mmap/mmap_test.go
new file mode 100644
index 00000000..814c23a3
--- /dev/null
+++ b/internal/mmap/mmap_test.go
@@ -0,0 +1,111 @@
+//go:build unix
+
+package mmap_test
+
+import (
+ "bytes"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "lindenii.org/go/furgit/internal/mmap"
+)
+
+// makeTempFileWithContents creates one file with content and opens it for reading.
+func makeTempFileWithContents(t *testing.T, content []byte) *os.File {
+ t.Helper()
+
+ path := filepath.Join(t.TempDir(), "data")
+
+ err := os.WriteFile(path, content, 0o600)
+ if err != nil {
+ t.Fatalf("WriteFile: %v", err)
+ }
+
+ file, err := os.Open(path) //nolint:gosec
+ if err != nil {
+ t.Fatalf("Open: %v", err)
+ }
+
+ t.Cleanup(func() { _ = file.Close() })
+
+ return file
+}
+
+// makeLargeTempFileLongerThanPage creates and opens one file
+// whose content repeats seed
+// until it spans more than one OS page.
+func makeLargeTempFileLongerThanPage(t *testing.T, seed []byte) (*os.File, []byte) {
+ t.Helper()
+
+ content := bytes.Repeat(seed, os.Getpagesize()/len(seed)+2)
+
+ return makeTempFileWithContents(t, content), content
+}
+
+func TestOpenData(t *testing.T) {
+ t.Parallel()
+
+ file, content := makeLargeTempFileLongerThanPage(t, []byte("mapped bytes\n"))
+
+ mapping, err := mmap.Open(file)
+ if err != nil {
+ t.Fatalf("Open: %v", err)
+ }
+
+ // The mapping must survive closing the originating file.
+ err = file.Close()
+ if err != nil {
+ t.Fatalf("file Close: %v", err)
+ }
+
+ if !bytes.Equal(mapping.Data(), content) {
+ t.Fatalf("Data mismatch")
+ }
+
+ err = mapping.Close()
+ if err != nil {
+ t.Fatalf("Close: %v", err)
+ }
+}
+
+func TestOpenEmpty(t *testing.T) {
+ t.Parallel()
+
+ file := makeTempFileWithContents(t, nil)
+
+ mapping, err := mmap.Open(file)
+ if err != nil {
+ t.Fatalf("Open: %v", err)
+ }
+
+ if len(mapping.Data()) != 0 {
+ t.Fatalf("Data = %d bytes, want empty", len(mapping.Data()))
+ }
+
+ err = mapping.Close()
+ if err != nil {
+ t.Fatalf("Close: %v", err)
+ }
+}
+
+func TestCloseIdempotent(t *testing.T) {
+ t.Parallel()
+
+ file, _ := makeLargeTempFileLongerThanPage(t, []byte("once"))
+
+ mapping, err := mmap.Open(file)
+ if err != nil {
+ t.Fatalf("Open: %v", err)
+ }
+
+ err = mapping.Close()
+ if err != nil {
+ t.Fatalf("first Close: %v", err)
+ }
+
+ err = mapping.Close()
+ if err != nil {
+ t.Fatalf("second Close: %v", err)
+ }
+}