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 }