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>
This commit is contained in:
Hamza El-Saawy
2023-07-21 18:06:41 -04:00
committed by GitHub
parent 10d5703c7b
commit 19a9f656e1
11 changed files with 86 additions and 46 deletions
+11 -2
View File
@@ -23,7 +23,7 @@ jobs:
- name: Run golangci-lint - name: Run golangci-lint
uses: golangci/golangci-lint-action@v3 uses: golangci/golangci-lint-action@v3
with: with:
version: v1.52 version: v1.53
args: >- args: >-
--verbose --verbose
--timeout=5m --timeout=5m
@@ -73,6 +73,7 @@ jobs:
- go-generate - go-generate
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false
matrix: matrix:
os: [windows-2019, windows-2022, ubuntu-latest] os: [windows-2019, windows-2022, ubuntu-latest]
steps: steps:
@@ -88,7 +89,15 @@ jobs:
run: go install gotest.tools/gotestsum@${{ env.GOTESTSUM_VERSION }} run: go install gotest.tools/gotestsum@${{ env.GOTESTSUM_VERSION }}
- name: Test repo - name: Test repo
run: gotestsum --format standard-verbose --debug -- -gcflags=all=-d=checkptr -v ./... run: gotestsum --format standard-verbose --debug -- -gcflags=all=-d=checkptr -race -v ./...
# Fuzzing was added in go1.18, so all stable/supported versions of go should support it.
# hvsock fuzzing fails on windows 2019, even though tests pass.
#
# If fuzzing tests are added to different packages, add them here.
- name: Fuzz repo
if: ${{ matrix.os == 'windows-2022' }}
run: gotestsum --format standard-verbose --debug -- -run "^#" -fuzztime 500x -fuzz "FuzzHvSock"
build: build:
name: Build Repo name: Build Repo
+1
View File
@@ -20,6 +20,7 @@ linters:
- gofmt # files are gofmt'ed - gofmt # files are gofmt'ed
- gosec # security - gosec # security
- nilerr # returns nil even with non-nil error - nilerr # returns nil even with non-nil error
- thelper # test helpers without t.Helper()
- unparam # unused function params - unparam # unused function params
issues: issues:
+18
View File
@@ -17,6 +17,8 @@ import (
) )
func ensurePresent(t *testing.T, m map[string]string, keys ...string) { func ensurePresent(t *testing.T, m map[string]string, keys ...string) {
t.Helper()
for _, k := range keys { for _, k := range keys {
if _, ok := m[k]; !ok { if _, ok := m[k]; !ok {
t.Error(k, "not present in tar header") t.Error(k, "not present in tar header")
@@ -25,6 +27,8 @@ func ensurePresent(t *testing.T, m map[string]string, keys ...string) {
} }
func setSparse(t *testing.T, f *os.File) { 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 { if err := windows.DeviceIoControl(windows.Handle(f.Fd()), windows.FSCTL_SET_SPARSE, nil, 0, nil, 0, nil, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -32,6 +36,8 @@ func setSparse(t *testing.T, f *os.File) {
// compareReaders validates that two readers contain the exact same data. // compareReaders validates that two readers contain the exact same data.
func compareReaders(t *testing.T, rActual io.Reader, rExpected io.Reader) { func compareReaders(t *testing.T, rActual io.Reader, rExpected io.Reader) {
t.Helper()
const size = 8 * 1024 const size = 8 * 1024
var bufExpected, bufActual [size]byte var bufExpected, bufActual [size]byte
var readCount int64 var readCount int64
@@ -71,6 +77,8 @@ func TestRoundTrip(t *testing.T) {
//nolint:gosec // G306: Expect WriteFile permissions to be 0600 or less //nolint:gosec // G306: Expect WriteFile permissions to be 0600 or less
for name, setup := range map[string]func(*testing.T) string{ for name, setup := range map[string]func(*testing.T) string{
"normalFile": func(t *testing.T) string { "normalFile": func(t *testing.T) string {
t.Helper()
path := filepath.Join(t.TempDir(), "foo.txt") path := filepath.Join(t.TempDir(), "foo.txt")
if err := os.WriteFile(path, []byte("testing 1 2 3\n"), 0644); err != nil { if err := os.WriteFile(path, []byte("testing 1 2 3\n"), 0644); err != nil {
t.Fatal(err) t.Fatal(err)
@@ -78,6 +86,8 @@ func TestRoundTrip(t *testing.T) {
return path return path
}, },
"normalFileEmpty": func(t *testing.T) string { "normalFileEmpty": func(t *testing.T) string {
t.Helper()
path := filepath.Join(t.TempDir(), "foo.txt") path := filepath.Join(t.TempDir(), "foo.txt")
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
@@ -87,6 +97,8 @@ func TestRoundTrip(t *testing.T) {
return path return path
}, },
"sparseFileEmpty": func(t *testing.T) string { "sparseFileEmpty": func(t *testing.T) string {
t.Helper()
path := filepath.Join(t.TempDir(), "foo.txt") path := filepath.Join(t.TempDir(), "foo.txt")
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
@@ -97,6 +109,8 @@ func TestRoundTrip(t *testing.T) {
return path return path
}, },
"sparseFileWithNoAllocatedRanges": func(t *testing.T) string { "sparseFileWithNoAllocatedRanges": func(t *testing.T) string {
t.Helper()
path := filepath.Join(t.TempDir(), "foo.txt") path := filepath.Join(t.TempDir(), "foo.txt")
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
@@ -112,6 +126,8 @@ func TestRoundTrip(t *testing.T) {
return path return path
}, },
"sparseFileWithOneAllocatedRange": func(t *testing.T) string { "sparseFileWithOneAllocatedRange": func(t *testing.T) string {
t.Helper()
path := filepath.Join(t.TempDir(), "foo.txt") path := filepath.Join(t.TempDir(), "foo.txt")
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
@@ -125,6 +141,8 @@ func TestRoundTrip(t *testing.T) {
return path return path
}, },
"sparseFileWithMultipleAllocatedRanges": func(t *testing.T) string { "sparseFileWithMultipleAllocatedRanges": func(t *testing.T) string {
t.Helper()
path := filepath.Join(t.TempDir(), "foo.txt") path := filepath.Join(t.TempDir(), "foo.txt")
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
+2
View File
@@ -21,6 +21,8 @@ import (
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes //sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult //sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
//todo (go1.19): switch to [atomic.Bool]
type atomicBool int32 type atomicBool int32
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 } func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
+2
View File
@@ -14,6 +14,8 @@ import (
// so we check that the current.AllocationSize is >= expected.AllocationSize. // so we check that the current.AllocationSize is >= expected.AllocationSize.
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/5afa7f66-619c-48f3-955f-68c4ece704ae // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/5afa7f66-619c-48f3-955f-68c4ece704ae
func checkFileStandardInfo(t *testing.T, current, expected *FileStandardInfo) { func checkFileStandardInfo(t *testing.T, current, expected *FileStandardInfo) {
t.Helper()
if current.AllocationSize < expected.AllocationSize { if current.AllocationSize < expected.AllocationSize {
t.Fatalf("FileStandardInfo unexpectedly had AllocationSize %d, expecting >=%d", current.AllocationSize, expected.AllocationSize) t.Fatalf("FileStandardInfo unexpectedly had AllocationSize %d, expecting >=%d", current.AllocationSize, expected.AllocationSize)
} }
+5 -2
View File
@@ -37,6 +37,7 @@ func serverListen(u testUtil) (l *HvsockListener, a *HvsockAddr) {
u.T.Logf("address collision %v", a) u.T.Logf("address collision %v", a)
continue continue
} }
u.T.Logf("listening on %v", a)
break break
} }
u.Must(err, "could not listen") u.Must(err, "could not listen")
@@ -579,9 +580,11 @@ type testUtil struct {
T testing.TB T testing.TB
} }
func newUtil(t testing.TB) testUtil { func newUtil(tb testing.TB) testUtil {
tb.Helper()
return testUtil{ return testUtil{
T: t, T: tb,
} }
} }
+2
View File
@@ -223,6 +223,8 @@ func TestCloseAbortsListen(t *testing.T) {
} }
func ensureEOFOnClose(t *testing.T, r io.Reader, w io.Closer) { func ensureEOFOnClose(t *testing.T, r io.Reader, w io.Closer) {
t.Helper()
b := make([]byte, 10) b := make([]byte, 10)
w.Close() w.Close()
n, err := r.Read(b) n, err := r.Read(b)
+35 -42
View File
@@ -8,14 +8,15 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"testing" "testing"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows"
) )
func TestApplyFileBinding(t *testing.T) { func TestApplyFileBinding(t *testing.T) {
requireElevated(t)
source := t.TempDir() source := t.TempDir()
destination := t.TempDir() destination := t.TempDir()
fileName := "testFile.txt" fileName := "testFile.txt"
@@ -55,12 +56,15 @@ func TestApplyFileBinding(t *testing.T) {
} }
func removeFileBinding(t *testing.T, mountpoint string) { func removeFileBinding(t *testing.T, mountpoint string) {
t.Helper()
if err := RemoveFileBinding(mountpoint); err != nil { if err := RemoveFileBinding(mountpoint); err != nil {
t.Logf("failed to remove file binding from %s: %q", mountpoint, err) t.Logf("failed to remove file binding from %s: %q", mountpoint, err)
} }
} }
func TestApplyFileBindingReadOnly(t *testing.T) { func TestApplyFileBindingReadOnly(t *testing.T) {
requireElevated(t)
source := t.TempDir() source := t.TempDir()
destination := t.TempDir() destination := t.TempDir()
fileName := "testFile.txt" fileName := "testFile.txt"
@@ -108,19 +112,14 @@ func TestApplyFileBindingReadOnly(t *testing.T) {
} }
func TestEnsureOnlyOneTargetCanBeMounted(t *testing.T) { func TestEnsureOnlyOneTargetCanBeMounted(t *testing.T) {
version, err := getWindowsBuildNumber() requireElevated(t)
if err != nil { requireBuild(t, RS5+1) // support added after RS5
t.Fatalf("couldn't get version number: %s", err)
}
if version <= 17763 {
t.Skip("not supported on RS5 or earlier")
}
source := t.TempDir() source := t.TempDir()
secondarySource := t.TempDir() secondarySource := t.TempDir()
destination := t.TempDir() destination := t.TempDir()
err = ApplyFileBinding(destination, source, false) err := ApplyFileBinding(destination, source, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -159,14 +158,9 @@ func checkSourceIsMountedOnDestination(src, dst string) (bool, error) {
} }
func TestGetBindMappings(t *testing.T) { func TestGetBindMappings(t *testing.T) {
version, err := getWindowsBuildNumber() requireElevated(t)
if err != nil { requireBuild(t, RS5+1) // support added after RS5
t.Fatalf("couldn't get version number: %s", err)
}
if version <= 17763 {
t.Skip("not supported on RS5 or earlier")
}
// GetBindMappings will expand short paths like ADMINI~1 and PROGRA~1 to their // GetBindMappings will expand short paths like ADMINI~1 and PROGRA~1 to their
// full names. In order to properly match the names later, we expand them here. // full names. In order to properly match the names later, we expand them here.
srcShort := t.TempDir() srcShort := t.TempDir()
@@ -198,6 +192,8 @@ func TestGetBindMappings(t *testing.T) {
} }
func TestRemoveFileBinding(t *testing.T) { func TestRemoveFileBinding(t *testing.T) {
requireElevated(t)
srcShort := t.TempDir() srcShort := t.TempDir()
source, err := getFinalPath(srcShort) source, err := getFinalPath(srcShort)
if err != nil { if err != nil {
@@ -238,32 +234,9 @@ func TestRemoveFileBinding(t *testing.T) {
} }
} }
func getWindowsBuildNumber() (int, error) {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
if err != nil {
return 0, fmt.Errorf("read CurrentVersion reg key: %w", err)
}
defer k.Close()
buildNumStr, _, err := k.GetStringValue("CurrentBuild")
if err != nil {
return 0, fmt.Errorf("read CurrentBuild reg value: %w", err)
}
buildNum, err := strconv.Atoi(buildNumStr)
if err != nil {
return 0, err
}
return buildNum, nil
}
func TestGetBindMappingsSymlinks(t *testing.T) { func TestGetBindMappingsSymlinks(t *testing.T) {
version, err := getWindowsBuildNumber() requireElevated(t)
if err != nil { requireBuild(t, RS5+1) // support added after RS5
t.Fatalf("couldn't get version number: %s", err)
}
if version <= 17763 {
t.Skip("not supported on RS5 or earlier")
}
srcShort := t.TempDir() srcShort := t.TempDir()
sourceNested := filepath.Join(srcShort, "source") sourceNested := filepath.Join(srcShort, "source")
@@ -307,3 +280,23 @@ func TestGetBindMappingsSymlinks(t *testing.T) {
t.Fatalf("expected to find %s mounted on %s, but could not", source, destination) t.Fatalf("expected to find %s mounted on %s, but could not", source, destination)
} }
} }
func requireElevated(tb testing.TB) {
tb.Helper()
if !windows.GetCurrentProcessToken().IsElevated() {
tb.Skip("requires elevated privileges")
}
}
const RS5 = 17763
//todo: also check that `bindfltapi.dll` exists
// require current build to be >= build
func requireBuild(tb testing.TB, build uint32) {
tb.Helper()
_, _, b := windows.RtlGetNtVersionNumbers()
if b < build {
tb.Skipf("requires build %d+; current build is %d", build, b)
}
}
+2
View File
@@ -10,6 +10,8 @@ import (
) )
func mustGUIDFromString(t *testing.T, s string) guid.GUID { func mustGUIDFromString(t *testing.T, s string) guid.GUID {
t.Helper()
g, err := guid.FromString(s) g, err := guid.FromString(s)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
+6
View File
@@ -7,6 +7,8 @@ import (
) )
func mustNewV4(t *testing.T) GUID { func mustNewV4(t *testing.T) GUID {
t.Helper()
g, err := NewV4() g, err := NewV4()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -15,6 +17,8 @@ func mustNewV4(t *testing.T) GUID {
} }
func mustNewV5(t *testing.T, namespace GUID, name []byte) GUID { func mustNewV5(t *testing.T, namespace GUID, name []byte) GUID {
t.Helper()
g, err := NewV5(namespace, name) g, err := NewV5(namespace, name)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -23,6 +27,8 @@ func mustNewV5(t *testing.T, namespace GUID, name []byte) GUID {
} }
func mustFromString(t *testing.T, s string) GUID { func mustFromString(t *testing.T, s string) GUID {
t.Helper()
g, err := FromString(s) g, err := FromString(s)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
+2
View File
@@ -87,6 +87,8 @@ func TestGrantVmGroupAccess(t *testing.T) {
} }
func verifyVMAccountDACLs(t *testing.T, name string, permissions []string) { func verifyVMAccountDACLs(t *testing.T, name string, permissions []string) {
t.Helper()
cmd := exec.Command("icacls", name) cmd := exec.Command("icacls", name)
outb, err := cmd.CombinedOutput() outb, err := cmd.CombinedOutput()
if err != nil { if err != nil {