Files
go-winio/backuptar/tar_test.go
T
Hamza El-Saawy 19a9f656e1 Update tests; run fuzzing (#294)
Run fuzzing tests in CI.

Use race detector when running tests.

Add missing `t.Helper()` calls.

Update test helpers in `pkg/bindfilter` to use `RtlGetNtVersionNumbers`
instead of reading registry, and skip tests if not running as admin.

Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>
2023-07-21 18:06:41 -04:00

240 lines
6.3 KiB
Go

//go:build windows
// +build windows
package backuptar
import (
"archive/tar"
"bytes"
"io"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/Microsoft/go-winio"
"golang.org/x/sys/windows"
)
func ensurePresent(t *testing.T, m map[string]string, keys ...string) {
t.Helper()
for _, k := range keys {
if _, ok := m[k]; !ok {
t.Error(k, "not present in tar header")
}
}
}
func setSparse(t *testing.T, f *os.File) {
t.Helper()
if err := windows.DeviceIoControl(windows.Handle(f.Fd()), windows.FSCTL_SET_SPARSE, nil, 0, nil, 0, nil, nil); err != nil {
t.Fatal(err)
}
}
// compareReaders validates that two readers contain the exact same data.
func compareReaders(t *testing.T, rActual io.Reader, rExpected io.Reader) {
t.Helper()
const size = 8 * 1024
var bufExpected, bufActual [size]byte
var readCount int64
// Loop, first reading from rExpected, then reading the same amount from rActual.
// For each set of reads, compare the bytes to make sure they are identical.
// When we run out of data in rExpected, exit the loop.
for {
// Do a read from rExpected and see how many bytes we get.
nExpected, err := rExpected.Read(bufExpected[:])
if err == io.EOF && nExpected == 0 {
break
} else if err != nil && err != io.EOF {
t.Fatalf("Failed reading from rExpected at %d: %s", readCount, err)
}
// Do a ReadFull from rActual for the same number of bytes we got from rExpected.
if nActual, err := io.ReadFull(rActual, bufActual[:nExpected]); err != nil {
t.Fatalf("Only read %d bytes out of %d from rActual at %d: %s", nActual, nExpected, readCount, err)
}
readCount += int64(nExpected)
for i, bExpected := range bufExpected[:nExpected] {
if bExpected != bufActual[i] {
t.Fatalf("Mismatched bytes at %d. got 0x%x, expected 0x%x", i, bufActual[i], bExpected)
}
}
}
// Now we just need to make sure there isn't any further data in rActual.
var b [1]byte
if n, err := rActual.Read(b[:]); n != 0 || err != io.EOF {
t.Fatalf("rActual didn't return EOF at expected end. Read %d bytes with error %s", n, err)
}
}
func TestRoundTrip(t *testing.T) {
// Each test case is a name mapped to a function which must create a file and return its path.
// The test then round-trips that file through backuptar, and validates the output matches the input.
//
//nolint:gosec // G306: Expect WriteFile permissions to be 0600 or less
for name, setup := range map[string]func(*testing.T) string{
"normalFile": func(t *testing.T) string {
t.Helper()
path := filepath.Join(t.TempDir(), "foo.txt")
if err := os.WriteFile(path, []byte("testing 1 2 3\n"), 0644); err != nil {
t.Fatal(err)
}
return path
},
"normalFileEmpty": func(t *testing.T) string {
t.Helper()
path := filepath.Join(t.TempDir(), "foo.txt")
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
t.Fatal(err)
}
defer f.Close()
return path
},
"sparseFileEmpty": func(t *testing.T) string {
t.Helper()
path := filepath.Join(t.TempDir(), "foo.txt")
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
t.Fatal(err)
}
defer f.Close()
setSparse(t, f)
return path
},
"sparseFileWithNoAllocatedRanges": func(t *testing.T) string {
t.Helper()
path := filepath.Join(t.TempDir(), "foo.txt")
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
t.Fatal(err)
}
defer f.Close()
setSparse(t, f)
// Set file size without writing data to produce a file with size > 0
// but no allocated ranges.
if err := f.Truncate(1000000); err != nil {
t.Fatal(err)
}
return path
},
"sparseFileWithOneAllocatedRange": func(t *testing.T) string {
t.Helper()
path := filepath.Join(t.TempDir(), "foo.txt")
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
t.Fatal(err)
}
defer f.Close()
setSparse(t, f)
if _, err := f.WriteString("test sparse data"); err != nil {
t.Fatal(err)
}
return path
},
"sparseFileWithMultipleAllocatedRanges": func(t *testing.T) string {
t.Helper()
path := filepath.Join(t.TempDir(), "foo.txt")
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
t.Fatal(err)
}
defer f.Close()
setSparse(t, f)
if _, err = f.Write([]byte("testing 1 2 3\n")); err != nil {
t.Fatal(err)
}
// The documentation talks about FSCTL_SET_ZERO_DATA, but seeking also
// seems to create a hole.
if _, err = f.Seek(1000000, 0); err != nil {
t.Fatal(err)
}
if _, err = f.Write([]byte("more data later\n")); err != nil {
t.Fatal(err)
}
return path
},
} {
t.Run(name, func(t *testing.T) {
path := setup(t)
f, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
t.Fatal(err)
}
bi, err := winio.GetFileBasicInfo(f)
if err != nil {
t.Fatal(err)
}
br := winio.NewBackupFileReader(f, true)
defer br.Close()
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
err = WriteTarFileFromBackupStream(tw, br, f.Name(), fi.Size(), bi)
if err != nil {
t.Fatal(err)
}
tr := tar.NewReader(&buf)
hdr, err := tr.Next()
if err != nil {
t.Fatal(err)
}
name, size, bi2, err := FileInfoFromHeader(hdr)
if err != nil {
t.Fatal(err)
}
if name != filepath.ToSlash(f.Name()) {
t.Errorf("got name %s, expected %s", name, filepath.ToSlash(f.Name()))
}
if size != fi.Size() {
t.Errorf("got size %d, expected %d", size, fi.Size())
}
if !reflect.DeepEqual(*bi2, *bi) {
t.Errorf("got %#v, expected %#v", *bi2, *bi)
}
ensurePresent(t, hdr.PAXRecords, "MSWINDOWS.fileattr", "MSWINDOWS.rawsd")
// Reset file position so we can compare file contents.
// The file contents of the actual file should match what we get from the tar.
if _, err := f.Seek(0, 0); err != nil {
t.Fatal(err)
}
compareReaders(t, tr, f)
})
}
}
func TestZeroReader(t *testing.T) {
const size = 512
var b [size]byte
var bExpected [size]byte
var r zeroReader
n, err := r.Read(b[:])
if err != nil {
t.Fatalf("Unexpected read error: %s", err)
}
if n != size {
t.Errorf("Wrong read size. got %d, expected %d", n, size)
}
for i := range b {
if b[i] != bExpected[i] {
t.Errorf("Wrong content at index %d. got %d, expected %d", i, b[i], bExpected[i])
}
}
}