forked from TrueCloudLab/restic
32d5ceba87
Signed-off-by: Igor Fedorenko <igor@ifedorenko.com>
305 lines
8.2 KiB
Go
305 lines
8.2 KiB
Go
package restorer
|
|
|
|
import (
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/restic/restic/internal/errors"
|
|
"github.com/restic/restic/internal/restic"
|
|
rtest "github.com/restic/restic/internal/test"
|
|
)
|
|
|
|
func assertNotOK(t *testing.T, msg string, err error) {
|
|
rtest.Assert(t, err != nil, msg+" did not fail")
|
|
}
|
|
|
|
func TestBytesWriterSeeker(t *testing.T) {
|
|
wr := &bytesWriteSeeker{data: make([]byte, 10)}
|
|
|
|
n, err := wr.Write([]byte{1, 2})
|
|
rtest.OK(t, err)
|
|
rtest.Equals(t, 2, n)
|
|
rtest.Equals(t, []byte{1, 2}, wr.data[0:2])
|
|
|
|
n64, err := wr.Seek(0, io.SeekStart)
|
|
rtest.OK(t, err)
|
|
rtest.Equals(t, int64(0), n64)
|
|
|
|
n, err = wr.Write([]byte{0, 1, 2, 3, 4})
|
|
rtest.OK(t, err)
|
|
rtest.Equals(t, 5, n)
|
|
n, err = wr.Write([]byte{5, 6, 7, 8, 9})
|
|
rtest.OK(t, err)
|
|
rtest.Equals(t, 5, n)
|
|
rtest.Equals(t, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, wr.data)
|
|
|
|
// negative tests
|
|
_, err = wr.Write([]byte{1})
|
|
assertNotOK(t, "write overflow", err)
|
|
_, err = wr.Seek(1, io.SeekStart)
|
|
assertNotOK(t, "unsupported seek", err)
|
|
}
|
|
|
|
func TestPackCacheBasic(t *testing.T) {
|
|
assertReader := func(expected []byte, offset int64, rd io.ReaderAt) {
|
|
actual := make([]byte, len(expected))
|
|
rd.ReadAt(actual, offset)
|
|
rtest.Equals(t, expected, actual)
|
|
}
|
|
|
|
c := newPackCache(10)
|
|
|
|
id := restic.NewRandomID()
|
|
|
|
// load pack to the cache
|
|
rd, err := c.get(id, 10, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
rtest.Equals(t, int64(10), offset)
|
|
rtest.Equals(t, 5, length)
|
|
wr.Write([]byte{1, 2, 3, 4, 5})
|
|
return nil
|
|
})
|
|
rtest.OK(t, err)
|
|
assertReader([]byte{1, 2, 3, 4, 5}, 10, rd)
|
|
|
|
// must close pack reader before can request it again
|
|
_, err = c.get(id, 10, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected cache load call")
|
|
return nil
|
|
})
|
|
assertNotOK(t, "double-reservation", err)
|
|
|
|
// close the pack reader and get it from cache
|
|
rd.Close()
|
|
rd, err = c.get(id, 10, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected cache load call")
|
|
return nil
|
|
})
|
|
rtest.OK(t, err)
|
|
assertReader([]byte{1, 2, 3, 4, 5}, 10, rd)
|
|
|
|
// close the pack reader and remove the pack from cache, assert the pack is loaded on request
|
|
rd.Close()
|
|
c.remove(id)
|
|
rd, err = c.get(id, 10, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
rtest.Equals(t, int64(10), offset)
|
|
rtest.Equals(t, 5, length)
|
|
wr.Write([]byte{1, 2, 3, 4, 5})
|
|
return nil
|
|
})
|
|
rtest.OK(t, err)
|
|
assertReader([]byte{1, 2, 3, 4, 5}, 10, rd)
|
|
}
|
|
|
|
func TestPackCacheInvalidRange(t *testing.T) {
|
|
c := newPackCache(10)
|
|
|
|
id := restic.NewRandomID()
|
|
|
|
_, err := c.get(id, -1, 1, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected cache load call")
|
|
return nil
|
|
})
|
|
assertNotOK(t, "negative offset request", err)
|
|
|
|
_, err = c.get(id, 0, 0, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected cache load call")
|
|
return nil
|
|
})
|
|
assertNotOK(t, "zero length request", err)
|
|
|
|
_, err = c.get(id, 0, -1, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected cache load call")
|
|
return nil
|
|
})
|
|
assertNotOK(t, "negative length", err)
|
|
}
|
|
|
|
func TestPackCacheCapacity(t *testing.T) {
|
|
c := newPackCache(10)
|
|
|
|
id1, id2, id3 := restic.NewRandomID(), restic.NewRandomID(), restic.NewRandomID()
|
|
|
|
// load and reserve pack1
|
|
rd1, err := c.get(id1, 0, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
wr.Write([]byte{1, 2, 3, 4, 5})
|
|
return nil
|
|
})
|
|
rtest.OK(t, err)
|
|
|
|
// load and reserve pack2
|
|
_, err = c.get(id2, 0, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
wr.Write([]byte{1, 2, 3, 4, 5})
|
|
return nil
|
|
})
|
|
rtest.OK(t, err)
|
|
|
|
// can't load pack3 because not enough space in the cache
|
|
_, err = c.get(id3, 0, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected cache load call")
|
|
return nil
|
|
})
|
|
assertNotOK(t, "request over capacity", err)
|
|
|
|
// release pack1 and try again
|
|
rd1.Close()
|
|
rd3, err := c.get(id3, 0, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
wr.Write([]byte{1, 2, 3, 4, 5})
|
|
return nil
|
|
})
|
|
rtest.OK(t, err)
|
|
|
|
// release pack3 and load pack1 (should not come from cache)
|
|
rd3.Close()
|
|
loaded := false
|
|
rd1, err = c.get(id1, 0, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
wr.Write([]byte{1, 2, 3, 4, 5})
|
|
loaded = true
|
|
return nil
|
|
})
|
|
rtest.OK(t, err)
|
|
rtest.Equals(t, true, loaded)
|
|
}
|
|
|
|
func TestPackCacheDownsizeRecord(t *testing.T) {
|
|
c := newPackCache(10)
|
|
|
|
id := restic.NewRandomID()
|
|
|
|
// get bigger range first
|
|
rd, err := c.get(id, 5, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
wr.Write([]byte{1, 2, 3, 4, 5})
|
|
return nil
|
|
})
|
|
rtest.OK(t, err)
|
|
rd.Close()
|
|
|
|
// invalid "resize" requests
|
|
_, err = c.get(id, 5, 10, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected pack load")
|
|
return nil
|
|
})
|
|
assertNotOK(t, "resize cached record", err)
|
|
|
|
// invalid before cached range request
|
|
_, err = c.get(id, 0, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected pack load")
|
|
return nil
|
|
})
|
|
assertNotOK(t, "before cached range request", err)
|
|
|
|
// invalid after cached range request
|
|
_, err = c.get(id, 10, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected pack load")
|
|
return nil
|
|
})
|
|
assertNotOK(t, "after cached range request", err)
|
|
|
|
// now get smaller "nested" range
|
|
rd, err = c.get(id, 7, 1, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected pack load")
|
|
return nil
|
|
})
|
|
rtest.OK(t, err)
|
|
|
|
// assert expected data
|
|
buf := make([]byte, 1)
|
|
rd.ReadAt(buf, 7)
|
|
rtest.Equals(t, byte(3), buf[0])
|
|
_, err = rd.ReadAt(buf, 0)
|
|
assertNotOK(t, "read before downsized pack range", err)
|
|
_, err = rd.ReadAt(buf, 9)
|
|
assertNotOK(t, "read after downsized pack range", err)
|
|
|
|
// can't request downsized record again
|
|
_, err = c.get(id, 7, 1, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected pack load")
|
|
return nil
|
|
})
|
|
assertNotOK(t, "double-allocation of cache record subrange", err)
|
|
|
|
// can't request another subrange of the original record
|
|
_, err = c.get(id, 6, 1, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected pack load")
|
|
return nil
|
|
})
|
|
assertNotOK(t, "allocation of another subrange of cache record", err)
|
|
|
|
// release downsized record and assert the original is back in the cache
|
|
rd.Close()
|
|
rd, err = c.get(id, 5, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
t.Error("unexpected pack load")
|
|
return nil
|
|
})
|
|
rtest.OK(t, err)
|
|
rd.Close()
|
|
}
|
|
|
|
func TestPackCacheFailedDownload(t *testing.T) {
|
|
c := newPackCache(10)
|
|
assertEmpty := func() {
|
|
rtest.Equals(t, 0, len(c.cachedPacks))
|
|
rtest.Equals(t, 10, c.capacity)
|
|
rtest.Equals(t, 0, c.reservedCapacity)
|
|
rtest.Equals(t, 0, c.allocatedCapacity)
|
|
}
|
|
|
|
_, err := c.get(restic.NewRandomID(), 0, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
return errors.Errorf("expected induced test error")
|
|
})
|
|
assertNotOK(t, "not enough bytes read", err)
|
|
assertEmpty()
|
|
|
|
_, err = c.get(restic.NewRandomID(), 0, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
wr.Write([]byte{1})
|
|
return nil
|
|
})
|
|
assertNotOK(t, "not enough bytes read", err)
|
|
assertEmpty()
|
|
|
|
_, err = c.get(restic.NewRandomID(), 0, 5, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
wr.Write([]byte{1, 2, 3, 4, 5, 6})
|
|
return nil
|
|
})
|
|
assertNotOK(t, "too many bytes read", err)
|
|
assertEmpty()
|
|
}
|
|
|
|
func TestPackCacheInvalidRequests(t *testing.T) {
|
|
c := newPackCache(10)
|
|
|
|
id := restic.NewRandomID()
|
|
|
|
//
|
|
rd, _ := c.get(id, 0, 1, func(offset int64, length int, wr io.WriteSeeker) error {
|
|
wr.Write([]byte{1})
|
|
return nil
|
|
})
|
|
assertNotOK(t, "remove() reserved pack", c.remove(id))
|
|
rtest.OK(t, rd.Close())
|
|
assertNotOK(t, "multiple reader Close() calls)", rd.Close())
|
|
|
|
//
|
|
rtest.OK(t, c.remove(id))
|
|
assertNotOK(t, "double remove() the same pack", c.remove(id))
|
|
}
|
|
|
|
func TestPackCacheRecord(t *testing.T) {
|
|
rd := &packCacheRecord{
|
|
offset: 10,
|
|
data: []byte{1},
|
|
}
|
|
buf := make([]byte, 1)
|
|
n, err := rd.ReadAt(buf, 10)
|
|
rtest.OK(t, err)
|
|
rtest.Equals(t, 1, n)
|
|
rtest.Equals(t, byte(1), buf[0])
|
|
|
|
_, err = rd.ReadAt(buf, 0)
|
|
assertNotOK(t, "read before loaded range", err)
|
|
|
|
_, err = rd.ReadAt(buf, 11)
|
|
assertNotOK(t, "read after loaded range", err)
|
|
|
|
_, err = rd.ReadAt(make([]byte, 2), 10)
|
|
assertNotOK(t, "read more than available data", err)
|
|
}
|