Add internal engine relocation candidate
This commit is contained in:
202
internal/engine/source_test.go
Normal file
202
internal/engine/source_test.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSourceConstructorsAndOwnership(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := []byte("value: before\n")
|
||||
source := Bytes("memory", data)
|
||||
copy(data, "value: after!\n")
|
||||
read, diagnostics := readSource(source, "source-1", defaultSourceLimits())
|
||||
if len(diagnostics) != 0 || string(read.data) != "value: before\n" {
|
||||
t.Fatalf("byte snapshot = %q, diagnostics = %#v", read.data, diagnostics)
|
||||
}
|
||||
read.data[0] = 'X'
|
||||
again, diagnostics := readSource(source, "source-1", defaultSourceLimits())
|
||||
if len(diagnostics) != 0 || string(again.data) != "value: before\n" {
|
||||
t.Fatal("returned bytes alias source snapshot")
|
||||
}
|
||||
|
||||
closer := &observedReadCloser{Reader: strings.NewReader("value: reader\n")}
|
||||
readerSource := Reader("stream", closer)
|
||||
read, diagnostics = readSource(readerSource, "source-1", defaultSourceLimits())
|
||||
if len(diagnostics) != 0 || string(read.data) != "value: reader\n" || closer.closed {
|
||||
t.Fatalf("reader result = %q, closed=%v diagnostics=%#v", read.data, closer.closed, diagnostics)
|
||||
}
|
||||
_, diagnostics = readSource(readerSource, "source-1", defaultSourceLimits())
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceReaderUsed {
|
||||
t.Fatalf("second reader diagnostics = %#v", diagnostics)
|
||||
}
|
||||
|
||||
if read, diagnostics := readSource(NoSource(), "source-1", defaultSourceLimits()); len(diagnostics) != 0 || read.info != nil || read.data != nil {
|
||||
t.Fatalf("NoSource read = %#v, %#v", read, diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSourcesReopenAndClassifyFailures(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
directory := t.TempDir()
|
||||
path := filepath.Join(directory, "config.yml")
|
||||
if err := os.WriteFile(path, []byte("value: first\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
source := File(path)
|
||||
first, diagnostics := readSource(source, "source-1", defaultSourceLimits())
|
||||
if len(diagnostics) != 0 || string(first.data) != "value: first\n" || first.info.Path != canonicalTestPath(t, path) || first.info.Label != path {
|
||||
t.Fatalf("first read = %#v, %#v", first, diagnostics)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte("value: second\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
second, diagnostics := readSource(source, "source-1", defaultSourceLimits())
|
||||
if len(diagnostics) != 0 || string(second.data) != "value: second\n" {
|
||||
t.Fatalf("second read = %#v, %#v", second, diagnostics)
|
||||
}
|
||||
|
||||
missing, diagnostics := readSource(File(filepath.Join(directory, "missing.yml")), "source-2", defaultSourceLimits())
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceMissing || missing.info == nil {
|
||||
t.Fatalf("missing diagnostics = %#v, info=%#v", diagnostics, missing.info)
|
||||
}
|
||||
_, diagnostics = readSource(File(directory), "source-3", defaultSourceLimits())
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceNotRegular {
|
||||
t.Fatalf("directory diagnostics = %#v", diagnostics)
|
||||
}
|
||||
|
||||
permissionPath := filepath.Join(directory, "private.yml")
|
||||
if err := os.WriteFile(permissionPath, []byte("value: private\n"), 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, diagnostics = readSource(File(permissionPath), "source-4", defaultSourceLimits())
|
||||
if os.Geteuid() != 0 {
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourcePermission {
|
||||
t.Fatalf("permission diagnostics = %#v", diagnostics)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceReadLimitsAndErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
limits := sourceLimits{perSource: 5, remaining: 20}
|
||||
read, diagnostics := readSource(Reader("exact", &fragmentReader{data: []byte("12345"), width: 1}), "source-1", limits)
|
||||
if len(diagnostics) != 0 || string(read.data) != "12345" {
|
||||
t.Fatalf("exact limit read = %q, %#v", read.data, diagnostics)
|
||||
}
|
||||
_, diagnostics = readSource(Reader("over", strings.NewReader("123456")), "source-2", limits)
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceLimitExceeded {
|
||||
t.Fatalf("over-limit diagnostics = %#v", diagnostics)
|
||||
}
|
||||
_, diagnostics = readSource(Reader("total", strings.NewReader("1234")), "source-3", sourceLimits{perSource: 10, remaining: 3})
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceLimitExceeded {
|
||||
t.Fatalf("total-limit diagnostics = %#v", diagnostics)
|
||||
}
|
||||
want := errors.New("read canary: must not escape")
|
||||
_, diagnostics = readSource(Reader("failing", &dataErrorReader{data: []byte("partial"), err: want}), "source-4", limits)
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceRead || strings.Contains(diagnostics[0].Message, "canary") {
|
||||
t.Fatalf("read-error diagnostics = %#v", diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReaderSourceCopiesCompeteForOneClaim(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
source := Reader("shared", strings.NewReader("value: one\n"))
|
||||
copyOfSource := source
|
||||
start := make(chan struct{})
|
||||
results := make(chan []Diagnostic, 2)
|
||||
var group sync.WaitGroup
|
||||
for _, candidate := range []Source{source, copyOfSource} {
|
||||
group.Add(1)
|
||||
go func(candidate Source) {
|
||||
defer group.Done()
|
||||
<-start
|
||||
_, diagnostics := readSource(candidate, "source-1", defaultSourceLimits())
|
||||
results <- diagnostics
|
||||
}(candidate)
|
||||
}
|
||||
close(start)
|
||||
group.Wait()
|
||||
close(results)
|
||||
|
||||
successes, used := 0, 0
|
||||
for diagnostics := range results {
|
||||
if len(diagnostics) == 0 {
|
||||
successes++
|
||||
} else if len(diagnostics) == 1 && diagnostics[0].Code == CodeSourceReaderUsed {
|
||||
used++
|
||||
}
|
||||
}
|
||||
if successes != 1 || used != 1 {
|
||||
t.Fatalf("successes=%d reader-used=%d", successes, used)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidSourcesFailWithoutConsumption(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var typedNil *strings.Reader
|
||||
for name, source := range map[string]Source{
|
||||
"zero": {},
|
||||
"empty file": File(""),
|
||||
"empty bytes": Bytes("", nil),
|
||||
"nil reader": Reader("reader", nil),
|
||||
"typed nil": Reader("reader", typedNil),
|
||||
"large bytes": Bytes("large", make([]byte, maxSourceBytes+1)),
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
_, diagnostics := readSource(source, "source-1", defaultSourceLimits())
|
||||
if len(diagnostics) != 1 {
|
||||
t.Fatalf("diagnostics = %#v", diagnostics)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type observedReadCloser struct {
|
||||
io.Reader
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (r *observedReadCloser) Close() error {
|
||||
r.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
type fragmentReader struct {
|
||||
data []byte
|
||||
width int
|
||||
}
|
||||
|
||||
func (r *fragmentReader) Read(p []byte) (int, error) {
|
||||
if len(r.data) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n := min(len(p), r.width, len(r.data))
|
||||
copy(p, r.data[:n])
|
||||
r.data = r.data[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
type dataErrorReader struct {
|
||||
data []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *dataErrorReader) Read(p []byte) (int, error) {
|
||||
if len(r.data) == 0 {
|
||||
return 0, r.err
|
||||
}
|
||||
n := copy(p, r.data)
|
||||
r.data = r.data[n:]
|
||||
return n, r.err
|
||||
}
|
||||
Reference in New Issue
Block a user