1869930d95
Signed-off-by: Igor Fedorenko <igor@ifedorenko.com>
226 lines
5.6 KiB
Go
226 lines
5.6 KiB
Go
package restorer
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"io/ioutil"
|
|
"testing"
|
|
|
|
"github.com/restic/restic/internal/crypto"
|
|
"github.com/restic/restic/internal/errors"
|
|
"github.com/restic/restic/internal/restic"
|
|
rtest "github.com/restic/restic/internal/test"
|
|
)
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// test helpers (TODO move to a dedicated file?)
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
type _Blob struct {
|
|
data string
|
|
pack string
|
|
}
|
|
|
|
type _File struct {
|
|
name string
|
|
blobs []_Blob
|
|
}
|
|
|
|
type _TestData struct {
|
|
key *crypto.Key
|
|
|
|
// pack names and ids
|
|
packsNameToID map[string]restic.ID
|
|
packsIDToName map[restic.ID]string
|
|
packsIDToData map[restic.ID][]byte
|
|
|
|
// blobs and files
|
|
blobs map[restic.ID][]restic.PackedBlob
|
|
files []*fileInfo
|
|
filesPathToContent map[string]string
|
|
|
|
//
|
|
loader func(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error
|
|
|
|
//
|
|
idx filePackTraverser
|
|
}
|
|
|
|
func (i *_TestData) Lookup(blobID restic.ID, _ restic.BlobType) ([]restic.PackedBlob, bool) {
|
|
packs, found := i.blobs[blobID]
|
|
return packs, found
|
|
}
|
|
|
|
func (i *_TestData) packName(pack *packInfo) string {
|
|
return i.packsIDToName[pack.id]
|
|
}
|
|
func (i *_TestData) packID(name string) restic.ID {
|
|
return i.packsNameToID[name]
|
|
}
|
|
|
|
func (i *_TestData) pack(queue *packQueue, name string) *packInfo {
|
|
id := i.packsNameToID[name]
|
|
return queue.packs[id]
|
|
}
|
|
|
|
func (i *_TestData) fileContent(file *fileInfo) string {
|
|
return i.filesPathToContent[file.path]
|
|
}
|
|
|
|
func _newTestData(_files []_File) *_TestData {
|
|
type _Pack struct {
|
|
name string
|
|
data []byte
|
|
blobs map[restic.ID]restic.Blob
|
|
}
|
|
_packs := make(map[string]_Pack)
|
|
|
|
key := crypto.NewRandomKey()
|
|
seal := func(data []byte) []byte {
|
|
ciphertext := restic.NewBlobBuffer(len(data))
|
|
ciphertext = ciphertext[:0] // TODO what does this actually do?
|
|
nonce := crypto.NewRandomNonce()
|
|
ciphertext = append(ciphertext, nonce...)
|
|
return key.Seal(ciphertext, nonce, data, nil)
|
|
}
|
|
|
|
filesPathToContent := make(map[string]string)
|
|
|
|
for _, _file := range _files {
|
|
var content string
|
|
for _, _blob := range _file.blobs {
|
|
content += _blob.data
|
|
|
|
// get the pack, create as necessary
|
|
var _pack _Pack
|
|
var found bool // TODO is there more concise way of doing this in go?
|
|
if _pack, found = _packs[_blob.pack]; !found {
|
|
_pack = _Pack{name: _blob.pack, blobs: make(map[restic.ID]restic.Blob)}
|
|
}
|
|
|
|
// calculate blob id and add to the pack as necessary
|
|
_blobID := restic.Hash([]byte(_blob.data))
|
|
if _, found := _pack.blobs[_blobID]; !found {
|
|
_blobData := seal([]byte(_blob.data))
|
|
_pack.blobs[_blobID] = restic.Blob{
|
|
Type: restic.DataBlob,
|
|
ID: _blobID,
|
|
Length: uint(len(_blobData)), // XXX is Length encrypted or plaintext?
|
|
Offset: uint(len(_pack.data)),
|
|
}
|
|
_pack.data = append(_pack.data, _blobData...)
|
|
}
|
|
|
|
_packs[_blob.pack] = _pack
|
|
}
|
|
filesPathToContent[_file.name] = content
|
|
}
|
|
|
|
blobs := make(map[restic.ID][]restic.PackedBlob)
|
|
packsIDToName := make(map[restic.ID]string)
|
|
packsIDToData := make(map[restic.ID][]byte)
|
|
packsNameToID := make(map[string]restic.ID)
|
|
|
|
for _, _pack := range _packs {
|
|
_packID := restic.Hash(_pack.data)
|
|
packsIDToName[_packID] = _pack.name
|
|
packsIDToData[_packID] = _pack.data
|
|
packsNameToID[_pack.name] = _packID
|
|
for blobID, blob := range _pack.blobs {
|
|
blobs[blobID] = append(blobs[blobID], restic.PackedBlob{Blob: blob, PackID: _packID})
|
|
}
|
|
}
|
|
|
|
var files []*fileInfo
|
|
for _, _file := range _files {
|
|
content := restic.IDs{}
|
|
for _, _blob := range _file.blobs {
|
|
content = append(content, restic.Hash([]byte(_blob.data)))
|
|
}
|
|
files = append(files, &fileInfo{path: _file.name, blobs: content})
|
|
}
|
|
|
|
_data := &_TestData{
|
|
key: key,
|
|
packsIDToName: packsIDToName,
|
|
packsIDToData: packsIDToData,
|
|
packsNameToID: packsNameToID,
|
|
blobs: blobs,
|
|
files: files,
|
|
filesPathToContent: filesPathToContent,
|
|
}
|
|
_data.idx = filePackTraverser{lookup: _data.Lookup}
|
|
_data.loader = func(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
|
packID, err := restic.ParseID(h.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rd := bytes.NewReader(_data.packsIDToData[packID][int(offset) : int(offset)+length])
|
|
return fn(rd)
|
|
}
|
|
|
|
return _data
|
|
}
|
|
|
|
func restoreAndVerify(t *testing.T, _files []_File) {
|
|
test := _newTestData(_files)
|
|
|
|
r := newFileRestorer(test.loader, test.key, test.idx)
|
|
r.files = test.files
|
|
|
|
r.restoreFiles(context.TODO(), func(path string, err error) {
|
|
rtest.OK(t, errors.Wrapf(err, "unexpected error"))
|
|
})
|
|
|
|
for _, file := range test.files {
|
|
data, err := ioutil.ReadFile(file.path)
|
|
if err != nil {
|
|
t.Errorf("unable to read file %v: %v", file.path, err)
|
|
continue
|
|
}
|
|
|
|
rtest.Equals(t, false, r.filesWriter.writers.Contains(file.path))
|
|
|
|
content := test.fileContent(file)
|
|
if !bytes.Equal(data, []byte(content)) {
|
|
t.Errorf("file %v has wrong content: want %q, got %q", file.path, content, data)
|
|
}
|
|
}
|
|
|
|
rtest.OK(t, nil)
|
|
}
|
|
|
|
func TestFileRestorer_basic(t *testing.T) {
|
|
tempdir, cleanup := rtest.TempDir(t)
|
|
defer cleanup()
|
|
|
|
restoreAndVerify(t, []_File{
|
|
_File{
|
|
name: tempdir + "/file1",
|
|
blobs: []_Blob{
|
|
_Blob{"data1-1", "pack1-1"},
|
|
_Blob{"data1-2", "pack1-2"},
|
|
},
|
|
},
|
|
_File{
|
|
name: tempdir + "/file2",
|
|
blobs: []_Blob{
|
|
_Blob{"data2-1", "pack2-1"},
|
|
_Blob{"data2-2", "pack2-2"},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestFileRestorer_emptyFile(t *testing.T) {
|
|
tempdir, cleanup := rtest.TempDir(t)
|
|
defer cleanup()
|
|
|
|
restoreAndVerify(t, []_File{
|
|
_File{
|
|
name: tempdir + "/empty",
|
|
blobs: []_Blob{},
|
|
},
|
|
})
|
|
}
|