tailscale/taildrop/resume_test.go
Joe Tsai b1867eb23f
taildrop: add logic for resuming partial files (#9785)
We add the following API:
* type FileChecksums
* type Checksum
* func Manager.PartialFiles
* func Manager.HashPartialFile
* func ResumeReader

The Manager methods provide the ability to query for partial files
and retrieve a list of checksums for a given partial file.
The ResumeReader function is a helper that wraps an io.Reader
to discard content that is identical locally and remotely.
The FileChecksums type represents the checksums of a file
and is safe to JSON marshal and send over the wire.

Updates tailscale/corp#14772

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
Co-authored-by: Rhea Ghosh <rhea@tailscale.com>
2023-10-12 16:50:11 -07:00

64 lines
1.6 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package taildrop
import (
"bytes"
"io"
"math/rand"
"os"
"testing"
"testing/iotest"
"tailscale.com/util/must"
)
func TestResume(t *testing.T) {
oldBlockSize := blockSize
defer func() { blockSize = oldBlockSize }()
blockSize = 256
m := Manager{Logf: t.Logf, Dir: t.TempDir()}
rn := rand.New(rand.NewSource(0))
want := make([]byte, 12345)
must.Get(io.ReadFull(rn, want))
t.Run("resume-noop", func(t *testing.T) {
r := io.Reader(bytes.NewReader(want))
offset, r, err := ResumeReader(r, func(offset, length int64) (FileChecksums, error) {
return m.HashPartialFile("", "foo", offset, length)
})
must.Do(err)
must.Get(m.PutFile("", "foo", r, offset, -1))
got := must.Get(os.ReadFile(must.Get(m.joinDir("foo"))))
if !bytes.Equal(got, want) {
t.Errorf("content mismatches")
}
})
t.Run("resume-retry", func(t *testing.T) {
rn := rand.New(rand.NewSource(0))
for {
r := io.Reader(bytes.NewReader(want))
offset, r, err := ResumeReader(r, func(offset, length int64) (FileChecksums, error) {
return m.HashPartialFile("", "foo", offset, length)
})
must.Do(err)
numWant := rn.Int63n(min(int64(len(want))-offset, 1000) + 1)
if offset < int64(len(want)) {
r = io.MultiReader(io.LimitReader(r, numWant), iotest.ErrReader(io.ErrClosedPipe))
}
if _, err := m.PutFile("", "foo", r, offset, -1); err == nil {
break
}
}
got := must.Get(os.ReadFile(must.Get(m.joinDir("foo"))))
if !bytes.Equal(got, want) {
t.Errorf("content mismatches")
}
})
}