mirror of
https://github.com/garethgeorge/backrest.git
synced 2025-12-15 01:55:35 +00:00
298 lines
6.2 KiB
Go
298 lines
6.2 KiB
Go
package logstore
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"slices"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestReadWrite(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ls, err := NewLogStore(t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("new log writer failed: %v", err)
|
|
}
|
|
defer ls.Close()
|
|
|
|
w, err := ls.Create("test", 0, 0)
|
|
if err != nil {
|
|
t.Fatalf("create failed: %v", err)
|
|
}
|
|
if _, err := w.Write([]byte("hello, world")); err != nil {
|
|
t.Fatalf("write failed: %v", err)
|
|
}
|
|
|
|
// assert that the file is on disk at this point
|
|
entries := getInprogressEntries(t, ls)
|
|
if len(entries) != 1 {
|
|
t.Fatalf("unexpected number of inprogress entries: %d", len(entries))
|
|
}
|
|
|
|
if err := w.Close(); err != nil {
|
|
t.Fatalf("close writer failed: %v", err)
|
|
}
|
|
|
|
r, err := ls.Open("test")
|
|
if err != nil {
|
|
t.Fatalf("open failed: %v", err)
|
|
}
|
|
|
|
data, err := io.ReadAll(r)
|
|
if err != nil {
|
|
t.Fatalf("read failed: %v", err)
|
|
}
|
|
if string(data) != "hello, world" {
|
|
t.Fatalf("unexpected content: %s", data)
|
|
}
|
|
|
|
entries = getInprogressEntries(t, ls)
|
|
if len(entries) != 0 {
|
|
t.Fatalf("unexpected number of inprogress entries: %d", len(entries))
|
|
}
|
|
}
|
|
|
|
func TestHugeReadWrite(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ls, err := NewLogStore(t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("new log writer failed: %v", err)
|
|
}
|
|
defer ls.Close()
|
|
|
|
w, err := ls.Create("test", 0, 0)
|
|
if err != nil {
|
|
t.Fatalf("create failed: %v", err)
|
|
}
|
|
|
|
data := bytes.Repeat([]byte("hello, world\n"), 1<<15)
|
|
if _, err := w.Write(data); err != nil {
|
|
t.Fatalf("write failed: %v", err)
|
|
}
|
|
if err := w.Close(); err != nil {
|
|
t.Fatalf("close writer failed: %v", err)
|
|
}
|
|
|
|
r, err := ls.Open("test")
|
|
if err != nil {
|
|
t.Fatalf("open failed: %v", err)
|
|
}
|
|
|
|
readData := bytes.NewBuffer(nil)
|
|
if _, err := io.Copy(readData, r); err != nil {
|
|
t.Fatalf("read failed: %v", err)
|
|
}
|
|
if !bytes.Equal(readData.Bytes(), data) {
|
|
t.Fatalf("unexpected content")
|
|
}
|
|
|
|
if err := r.Close(); err != nil {
|
|
t.Fatalf("close reader failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestReadWhileWrite(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ls, err := NewLogStore(t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("new log writer failed: %v", err)
|
|
}
|
|
defer ls.Close()
|
|
|
|
w, err := ls.Create("test", 0, 0)
|
|
if err != nil {
|
|
t.Fatalf("create failed: %v", err)
|
|
}
|
|
r, err := ls.Open("test")
|
|
if err != nil {
|
|
t.Fatalf("open failed: %v", err)
|
|
}
|
|
data := bytes.NewBuffer(nil)
|
|
wantData := bytes.NewBuffer(nil)
|
|
|
|
var wg sync.WaitGroup
|
|
var readn int64
|
|
var readerr error
|
|
wg.Add(1)
|
|
go func() {
|
|
defer r.Close()
|
|
readn, readerr = io.Copy(data, r)
|
|
wg.Done()
|
|
}()
|
|
|
|
for i := 0; i < 100; i++ {
|
|
str := fmt.Sprintf("hello, world %d\n", i)
|
|
wantData.WriteString(str)
|
|
|
|
if _, err := w.Write([]byte(str)); err != nil {
|
|
t.Fatalf("write failed: %v", err)
|
|
}
|
|
|
|
if i%2 == 0 {
|
|
time.Sleep(2 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
fmt.Printf("trying to close writer from test...")
|
|
if err := w.Close(); err != nil {
|
|
t.Fatalf("close writer failed: %v", err)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// check that the asynchronous read completed successfully
|
|
if readerr != nil {
|
|
t.Fatalf("read failed: %v", readerr)
|
|
}
|
|
if readn == 0 || readn != int64(wantData.Len()) {
|
|
t.Fatalf("unexpected read length: %d", readn)
|
|
}
|
|
if !bytes.Equal(data.Bytes(), wantData.Bytes()) {
|
|
t.Fatalf("unexpected content: %s", data.Bytes())
|
|
}
|
|
|
|
// check that the finalized data matches expectations
|
|
var finalizedData bytes.Buffer
|
|
r2, err := ls.Open("test")
|
|
if err != nil {
|
|
t.Fatalf("open failed: %v", err)
|
|
}
|
|
if _, err := io.Copy(&finalizedData, r2); err != nil {
|
|
t.Fatalf("read failed: %v", err)
|
|
}
|
|
if !bytes.Equal(finalizedData.Bytes(), wantData.Bytes()) {
|
|
t.Fatalf("unexpected content: %s", finalizedData.Bytes())
|
|
}
|
|
|
|
if err := r2.Close(); err != nil {
|
|
t.Fatalf("close reader failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateMany(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ls, err := NewLogStore(t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("new log writer failed: %v", err)
|
|
}
|
|
defer ls.Close()
|
|
|
|
const n = 10
|
|
for i := 0; i < n; i++ {
|
|
name := fmt.Sprintf("test%d", i)
|
|
w, err := ls.Create(name, 0, 0)
|
|
if err != nil {
|
|
t.Fatalf("create %q failed: %v", name, err)
|
|
}
|
|
if _, err := w.Write([]byte(fmt.Sprintf("hello, world %d", i))); err != nil {
|
|
t.Fatalf("write failed: %v", err)
|
|
}
|
|
if err := w.Close(); err != nil {
|
|
t.Fatalf("close writer failed: %v", err)
|
|
}
|
|
}
|
|
|
|
entries := getInprogressEntries(t, ls)
|
|
if len(entries) != 0 {
|
|
t.Fatalf("unexpected number of inprogress entries: %d", len(entries))
|
|
}
|
|
|
|
for i := 0; i < n; i++ {
|
|
name := fmt.Sprintf("test%d", i)
|
|
r, err := ls.Open(name)
|
|
if err != nil {
|
|
t.Fatalf("open %q failed: %v", name, err)
|
|
}
|
|
data, err := io.ReadAll(r)
|
|
if err != nil {
|
|
t.Fatalf("read failed: %v", err)
|
|
}
|
|
if string(data) != fmt.Sprintf("hello, world %d", i) {
|
|
t.Fatalf("unexpected content: %s", data)
|
|
}
|
|
if err := r.Close(); err != nil {
|
|
t.Fatalf("close reader failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReopenStore(t *testing.T) {
|
|
d := t.TempDir()
|
|
{
|
|
ls, err := NewLogStore(d)
|
|
if err != nil {
|
|
t.Fatalf("new log writer failed: %v", err)
|
|
}
|
|
|
|
w, err := ls.Create("test", 0, 0)
|
|
if err != nil {
|
|
t.Fatalf("create failed: %v", err)
|
|
}
|
|
|
|
if _, err := w.Write([]byte("hello, world")); err != nil {
|
|
t.Fatalf("write failed: %v", err)
|
|
}
|
|
|
|
if err := w.Close(); err != nil {
|
|
t.Fatalf("close writer failed: %v", err)
|
|
}
|
|
|
|
// confirm that the file is on disk
|
|
r, err := ls.Open("test")
|
|
if err != nil {
|
|
t.Fatalf("open first store failed: %v", err)
|
|
}
|
|
r.Close()
|
|
|
|
if err := ls.Close(); err != nil {
|
|
t.Fatalf("close log store failed: %v", err)
|
|
}
|
|
|
|
}
|
|
|
|
{
|
|
ls, err := NewLogStore(d)
|
|
if err != nil {
|
|
t.Fatalf("new log writer failed: %v", err)
|
|
}
|
|
|
|
r, err := ls.Open("test")
|
|
if err != nil {
|
|
t.Fatalf("open failed: %v", err)
|
|
}
|
|
|
|
data, err := io.ReadAll(r)
|
|
if err != nil {
|
|
t.Fatalf("read failed: %v", err)
|
|
}
|
|
if string(data) != "hello, world" {
|
|
t.Fatalf("unexpected content: %s", data)
|
|
}
|
|
if err := r.Close(); err != nil {
|
|
t.Fatalf("close reader failed: %v", err)
|
|
}
|
|
|
|
if err := ls.Close(); err != nil {
|
|
t.Fatalf("close log store failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func getInprogressEntries(t *testing.T, ls *LogStore) []os.DirEntry {
|
|
entries, err := os.ReadDir(ls.inprogressDir)
|
|
if err != nil {
|
|
t.Fatalf("read dir failed: %v", err)
|
|
}
|
|
|
|
entries = slices.DeleteFunc(entries, func(e os.DirEntry) bool { return e.IsDir() })
|
|
return entries
|
|
}
|