aboutsummaryrefslogtreecommitdiff
path: root/internal/persistence/atomic_test.go
blob: 05710f1ea2dbdee3cd428fe6db288d0b9710f270 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package persistence

import (
	"bytes"
	"os"
	"path/filepath"
	"sync"
	"testing"
)

func TestWriteFileReplacesAndPreservesPermissions(t *testing.T) {
	path := filepath.Join(t.TempDir(), "save.yaml")
	if err := WriteFile(path, []byte("old"), 0600); err != nil {
		t.Fatal(err)
	}
	if err := WriteFile(path, []byte("new"), 0644); err != nil {
		t.Fatal(err)
	}
	got, err := os.ReadFile(path)
	if err != nil || string(got) != "new" {
		t.Fatalf("read: %q, %v", got, err)
	}
	info, err := os.Stat(path)
	if err != nil || info.Mode().Perm() != 0600 {
		t.Fatalf("permissions: %v, %v", info, err)
	}
	entries, err := os.ReadDir(filepath.Dir(path))
	if err != nil || len(entries) != 1 {
		t.Fatalf("temporary files left behind: %v, %v", entries, err)
	}
}

func TestFailedRenameCleansTempFile(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "destination")
	if err := os.Mkdir(path, 0755); err != nil {
		t.Fatal(err)
	}
	if err := WriteFile(path, []byte("new"), 0600); err == nil {
		t.Fatal("expected rename over directory to fail")
	}
	entries, err := os.ReadDir(dir)
	if err != nil || len(entries) != 1 || !entries[0].IsDir() {
		t.Fatalf("destination damaged or temporary file leaked: %v, %v", entries, err)
	}
}

func TestReadersSeeOnlyCompleteSaves(t *testing.T) {
	path := filepath.Join(t.TempDir(), "save.yaml")
	a, b := bytes.Repeat([]byte("a"), 8192), bytes.Repeat([]byte("b"), 16384)
	if err := WriteFile(path, a, 0600); err != nil {
		t.Fatal(err)
	}
	var wg sync.WaitGroup
	for _, data := range [][]byte{a, b} {
		wg.Add(1)
		go func(data []byte) {
			defer wg.Done()
			for i := 0; i < 30; i++ {
				if err := WriteFile(path, data, 0600); err != nil {
					t.Error(err)
					return
				}
			}
		}(data)
	}
	for i := 0; i < 300; i++ {
		got, err := os.ReadFile(path)
		if err != nil || (!bytes.Equal(got, a) && !bytes.Equal(got, b)) {
			t.Errorf("reader saw partial or missing save: length=%d, err=%v", len(got), err)
			break
		}
	}
	wg.Wait()
}