518 lines
21 KiB
Go
518 lines
21 KiB
Go
package engine
|
|
|
|
import (
|
|
"errors"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestSearchDescriptionEmptyAndSlugRequirements(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if diagnostics := validateSearch(Search{}, ""); len(diagnostics) != 0 {
|
|
t.Fatalf("zero search diagnostics = %#v", diagnostics)
|
|
}
|
|
if diagnostics := validateSearch(Searches(), ""); len(diagnostics) != 0 {
|
|
t.Fatalf("empty concatenation diagnostics = %#v", diagnostics)
|
|
}
|
|
if diagnostics := validateSearch(Paths(), ""); len(diagnostics) != 0 {
|
|
t.Fatalf("empty path list diagnostics = %#v", diagnostics)
|
|
}
|
|
if diagnostics := validateSearch(Paths("relative/config.yml"), ""); len(diagnostics) != 0 {
|
|
t.Fatalf("literal path unexpectedly requires a slug: %#v", diagnostics)
|
|
}
|
|
generic := SearchDirectory("configs", DirectorySearchOptions{Filenames: []FilenameGroup{GenericFilenames()}})
|
|
if diagnostics := validateSearch(generic, ""); len(diagnostics) != 0 {
|
|
t.Fatalf("generic-only directory unexpectedly requires a slug: %#v", diagnostics)
|
|
}
|
|
if diagnostics := validateSearch(SearchDirectory("configs"), ""); len(diagnostics) == 0 || diagnostics[0].Code != CodeOptionsInvalid {
|
|
t.Fatalf("default named directory accepted an empty slug: %#v", diagnostics)
|
|
}
|
|
if diagnostics := validateSearch(UnixSystemDirectories(), "myapp"); len(diagnostics) != 0 {
|
|
t.Fatalf("valid preset slug diagnostics = %#v", diagnostics)
|
|
}
|
|
}
|
|
|
|
func TestSearchDescriptionSnapshotsInputs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
paths := []string{"first.yml", "second.yml"}
|
|
pathSearch := Paths(paths...)
|
|
paths[0] = "changed.yml"
|
|
|
|
customNames := []string{"one.yml", "two.yml"}
|
|
group := CustomFilenames(customNames...)
|
|
customNames[0] = "changed.yml"
|
|
groups := []FilenameGroup{group}
|
|
options := DirectorySearchOptions{Filenames: groups}
|
|
directorySearch := SearchDirectory("configs", options)
|
|
groups[0] = GenericFilenames()
|
|
options.Filenames[0] = NamedFilenames()
|
|
|
|
searches := []Search{pathSearch, directorySearch}
|
|
combined := Searches(searches...)
|
|
searches[0] = Search{}
|
|
|
|
if got := combined.entries[0].path; got != "first.yml" {
|
|
t.Fatalf("snapshotted path = %q", got)
|
|
}
|
|
gotNames := combined.entries[2].filenames[0].names
|
|
if len(gotNames) != 2 || gotNames[0] != "one.yml" || gotNames[1] != "two.yml" {
|
|
t.Fatalf("snapshotted custom names = %#v", gotNames)
|
|
}
|
|
if diagnostics := validateSearch(combined, ""); len(diagnostics) != 0 {
|
|
t.Fatalf("snapshot description diagnostics = %#v", diagnostics)
|
|
}
|
|
}
|
|
|
|
func TestDirectoryFilenameDefaultsAndExplicitEmpty(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
omitted := SearchDirectory("configs")
|
|
if len(omitted.entries) != 1 || len(omitted.entries[0].filenames) != 2 || omitted.entries[0].filenames[0].kind != filenameGroupNamed || omitted.entries[0].filenames[1].kind != filenameGroupGeneric {
|
|
t.Fatalf("omitted groups = %#v", omitted.entries)
|
|
}
|
|
explicitNil := SearchDirectory("configs", DirectorySearchOptions{})
|
|
if len(explicitNil.entries[0].filenames) != 2 {
|
|
t.Fatalf("nil filename selection did not use defaults: %#v", explicitNil.entries[0].filenames)
|
|
}
|
|
explicitEmpty := SearchDirectory("configs", DirectorySearchOptions{Filenames: []FilenameGroup{}})
|
|
if explicitEmpty.entries[0].filenames == nil || len(explicitEmpty.entries[0].filenames) != 0 {
|
|
t.Fatalf("explicit empty selection lost its distinction: %#v", explicitEmpty.entries[0].filenames)
|
|
}
|
|
if len(validateSearch(explicitEmpty, "")) != 0 {
|
|
t.Fatal("explicit empty filename selection should need no slug")
|
|
}
|
|
}
|
|
|
|
func TestSearchDescriptionArityAndErrorConcatenation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tooMany := SearchDirectory("configs", DirectorySearchOptions{}, DirectorySearchOptions{})
|
|
if diagnostics := validateSearch(tooMany, "app"); len(diagnostics) == 0 {
|
|
t.Fatal("multiple option values should be invalid")
|
|
}
|
|
invalid := Paths("")
|
|
combined := Searches(Paths("good.yml"), invalid)
|
|
if diagnostics := validateSearch(combined, ""); len(diagnostics) == 0 {
|
|
t.Fatal("concatenation lost a constructor error")
|
|
}
|
|
if diagnostics := validateSearch(Paths("good.yml", ""), ""); len(diagnostics) == 0 {
|
|
t.Fatal("later invalid path was hidden by an earlier component")
|
|
}
|
|
}
|
|
|
|
func TestSearchDescriptionPreservesFilenameGroupOrder(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
groups := []FilenameGroup{GenericFilenames(), NamedFilenames(), CustomFilenames("override.yml")}
|
|
search := SearchDirectory("configs", DirectorySearchOptions{Filenames: groups})
|
|
got := search.entries[0].filenames
|
|
if len(got) != 3 || got[0].kind != filenameGroupGeneric || got[1].kind != filenameGroupNamed || got[2].names[0] != "override.yml" {
|
|
t.Fatalf("filename group order = %#v", got)
|
|
}
|
|
if got := SearchDirectory("configs", DirectorySearchOptions{Filenames: []FilenameGroup{CustomFilenames()}}).entries[0].filenames[0].names; len(got) != 0 {
|
|
t.Fatalf("empty custom group = %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestFilenameComponentValidation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, value := range []string{"", ".", "..", "nested/name", "bad\x00name"} {
|
|
if validFilenameComponent(value) {
|
|
t.Errorf("validFilenameComponent(%q) = true", value)
|
|
}
|
|
}
|
|
if runtime.GOOS == "windows" {
|
|
for _, value := range []string{"CON", "nul.txt", "name.", "bad:name"} {
|
|
if validFilenameComponent(value) {
|
|
t.Errorf("Windows component %q unexpectedly accepted", value)
|
|
}
|
|
}
|
|
} else {
|
|
for _, value := range []string{"name with spaces", "naïve"} {
|
|
if !validFilenameComponent(value) {
|
|
t.Errorf("host-valid component %q rejected", value)
|
|
}
|
|
}
|
|
}
|
|
if diagnostics := validateSearch(SearchDirectory("configs", DirectorySearchOptions{Filenames: []FilenameGroup{CustomFilenames("bad/name")}}), ""); len(diagnostics) == 0 {
|
|
t.Fatal("invalid custom filename was not retained for validation")
|
|
}
|
|
for _, value := range []string{"CON", "nul.txt", "COM¹.log", "LPT³.txt", "name.", "bad:name", "bad\x01name"} {
|
|
if validWindowsFilenameComponent(value) {
|
|
t.Errorf("Windows component %q unexpectedly accepted", value)
|
|
}
|
|
}
|
|
for _, value := range []string{"name with spaces", "naïve"} {
|
|
if !validWindowsFilenameComponent(value) {
|
|
t.Errorf("valid Windows component %q rejected", value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSearchCandidatesPreserveDirectoryAndGroupOrder(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
root := t.TempDir()
|
|
helpers := discoveryDirectoryHelpers{workingDir: func() (string, error) { return root, nil }}
|
|
search := Searches(
|
|
Paths("first.yml"),
|
|
SearchDirectory("settings", DirectorySearchOptions{Filenames: []FilenameGroup{
|
|
GenericFilenames(), CustomFilenames("special.yml"),
|
|
}}),
|
|
Paths("last.yml"),
|
|
)
|
|
candidates := collectSearchCandidates(t, search, "app", helpers)
|
|
want := []string{
|
|
filepath.Join(root, "first.yml"),
|
|
filepath.Join(root, "settings", "config.yml"),
|
|
filepath.Join(root, "settings", "config.yaml"),
|
|
filepath.Join(root, "settings", "main.yml"),
|
|
filepath.Join(root, "settings", "main.yaml"),
|
|
filepath.Join(root, "settings", "special.yml"),
|
|
filepath.Join(root, "last.yml"),
|
|
}
|
|
assertCandidatePaths(t, candidates, want)
|
|
|
|
reversed := SearchDirectory("settings", DirectorySearchOptions{Filenames: []FilenameGroup{NamedFilenames(), GenericFilenames()}})
|
|
reversedCandidates := collectSearchCandidates(t, reversed, "app", helpers)
|
|
assertCandidatePaths(t, reversedCandidates[:2], []string{
|
|
filepath.Join(root, "settings", "app.yml"),
|
|
filepath.Join(root, "settings", "app.yaml"),
|
|
})
|
|
}
|
|
|
|
func TestSearchPresetCandidateLists(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
helpers := discoveryDirectoryHelpers{
|
|
userConfigDir: func() (string, error) { return "/controlled/config", nil },
|
|
userHomeDir: func() (string, error) { return "/controlled/home", nil },
|
|
workingDir: func() (string, error) { return "/controlled/work", nil },
|
|
unixSupported: func() bool { return true },
|
|
}
|
|
|
|
unix := collectSearchCandidates(t, UnixSystemDirectories(), "app", helpers)
|
|
if len(unix) != 14 {
|
|
t.Fatalf("Unix preset candidate count = %d: %#v", len(unix), unix)
|
|
}
|
|
assertCandidatePaths(t, unix, []string{
|
|
"/usr/local/etc/app/app.yml", "/usr/local/etc/app/app.yaml", "/usr/local/etc/app/config.yml", "/usr/local/etc/app/config.yaml", "/usr/local/etc/app/main.yml", "/usr/local/etc/app/main.yaml",
|
|
"/etc/app/app.yml", "/etc/app/app.yaml", "/etc/app/config.yml", "/etc/app/config.yaml", "/etc/app/main.yml", "/etc/app/main.yaml",
|
|
"/etc/app.yml", "/etc/app.yaml",
|
|
})
|
|
|
|
user := collectSearchCandidates(t, UserConfigDirectory(), "app", helpers)
|
|
assertCandidatePaths(t, user, []string{
|
|
"/controlled/config/app/app.yml", "/controlled/config/app/app.yaml", "/controlled/config/app/config.yml", "/controlled/config/app/config.yaml", "/controlled/config/app/main.yml", "/controlled/config/app/main.yaml",
|
|
"/controlled/config/app.yml", "/controlled/config/app.yaml",
|
|
})
|
|
home := collectSearchCandidates(t, HomeDirectory(), "app", helpers)
|
|
assertCandidatePaths(t, home, []string{
|
|
"/controlled/home/.app/app.yml", "/controlled/home/.app/app.yaml", "/controlled/home/.app/config.yml", "/controlled/home/.app/config.yaml", "/controlled/home/.app/main.yml", "/controlled/home/.app/main.yaml",
|
|
"/controlled/home/app.yml", "/controlled/home/app.yaml",
|
|
})
|
|
working := collectSearchCandidates(t, WorkingDirectory(), "app", helpers)
|
|
assertCandidatePaths(t, working, []string{"/controlled/work/app.yml", "/controlled/work/app.yaml"})
|
|
}
|
|
|
|
func TestSearchCandidateExpansionIsLazyAndSkipsEmptyDirectories(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
configCalls := 0
|
|
helpers := discoveryDirectoryHelpers{
|
|
userConfigDir: func() (string, error) {
|
|
configCalls++
|
|
return "/controlled/config", nil
|
|
},
|
|
workingDir: func() (string, error) { return "/controlled/work", nil },
|
|
unixSupported: func() bool { return true },
|
|
}
|
|
search := Searches(Paths("/selected.yml"), UserConfigDirectory())
|
|
iterator, diagnostics := newSearchCandidateIterator(search, "app", helpers)
|
|
if len(diagnostics) != 0 {
|
|
t.Fatalf("iterator diagnostics = %#v", diagnostics)
|
|
}
|
|
if candidate, ok, err := iterator.next(); err != nil || !ok || candidate != "/selected.yml" {
|
|
t.Fatalf("first candidate = %q, %v, %v", candidate, ok, err)
|
|
}
|
|
if configCalls != 0 {
|
|
t.Fatalf("later user directory resolved before consumption: %d calls", configCalls)
|
|
}
|
|
|
|
emptyOnly := SearchDirectory("should-not-resolve", DirectorySearchOptions{Filenames: []FilenameGroup{}})
|
|
emptyCandidates := collectSearchCandidates(t, emptyOnly, "", helpers)
|
|
if len(emptyCandidates) != 0 || configCalls != 0 {
|
|
t.Fatalf("empty directory candidates = %#v, helper calls = %d", emptyCandidates, configCalls)
|
|
}
|
|
continued := collectSearchCandidates(t, Searches(emptyOnly, Paths("later.yml")), "", helpers)
|
|
assertCandidatePaths(t, continued, []string{filepath.Join("/controlled/work", "later.yml")})
|
|
if configCalls != 0 {
|
|
t.Fatalf("empty directory base was resolved: %d calls", configCalls)
|
|
}
|
|
}
|
|
|
|
func TestSearchCandidateBaseFailuresAndPlatformApplicability(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
baseFailure := errors.New("controlled base failure")
|
|
helpers := discoveryDirectoryHelpers{
|
|
userConfigDir: func() (string, error) { return "", baseFailure },
|
|
workingDir: func() (string, error) { return "/controlled/work", nil },
|
|
unixSupported: func() bool { return false },
|
|
}
|
|
iterator, diagnostics := newSearchCandidateIterator(UserConfigDirectory(), "app", helpers)
|
|
if len(diagnostics) != 0 {
|
|
t.Fatalf("user preset validation = %#v", diagnostics)
|
|
}
|
|
if _, ok, err := iterator.next(); ok || !errors.Is(err, baseFailure) {
|
|
t.Fatalf("unavailable user base = ok %v, err %v", ok, err)
|
|
}
|
|
|
|
iterator, diagnostics = newSearchCandidateIterator(UnixSystemDirectories(), "app", helpers)
|
|
if len(diagnostics) != 0 {
|
|
t.Fatalf("Unix preset validation = %#v", diagnostics)
|
|
}
|
|
if _, ok, err := iterator.next(); ok || !errors.Is(err, errUnixSystemPresetUnavailable) {
|
|
t.Fatalf("unsupported Unix preset = ok %v, err %v", ok, err)
|
|
}
|
|
|
|
iterator, diagnostics = newSearchCandidateIterator(Searches(Paths("/earlier.yml"), UnixSystemDirectories()), "app", helpers)
|
|
if len(diagnostics) != 0 {
|
|
t.Fatalf("composed Unix preset validation = %#v", diagnostics)
|
|
}
|
|
if candidate, ok, err := iterator.next(); err != nil || !ok || candidate != "/earlier.yml" {
|
|
t.Fatalf("earlier candidate = %q, %v, %v", candidate, ok, err)
|
|
}
|
|
}
|
|
|
|
func TestRelativeAndShellLikeSearchPathsRemainLiteral(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
root := t.TempDir()
|
|
helpers := discoveryDirectoryHelpers{workingDir: func() (string, error) { return root, nil }}
|
|
search := Searches(Paths("~/literal.yml", "$CONFIG/*.yaml"), SearchDirectory("./settings", DirectorySearchOptions{Filenames: []FilenameGroup{CustomFilenames("name with spaces.yml")}}))
|
|
candidates := collectSearchCandidates(t, search, "", helpers)
|
|
assertCandidatePaths(t, candidates, []string{
|
|
filepath.Join(root, "~/literal.yml"),
|
|
filepath.Join(root, "$CONFIG/*.yaml"),
|
|
filepath.Join(root, "settings", "name with spaces.yml"),
|
|
})
|
|
}
|
|
|
|
func collectSearchCandidates(t *testing.T, search Search, application string, helpers discoveryDirectoryHelpers) []string {
|
|
t.Helper()
|
|
iterator, diagnostics := newSearchCandidateIterator(search, application, helpers)
|
|
if len(diagnostics) != 0 {
|
|
t.Fatalf("search validation = %#v", diagnostics)
|
|
}
|
|
var candidates []string
|
|
for {
|
|
candidate, ok, err := iterator.next()
|
|
if err != nil {
|
|
t.Fatalf("expand candidate: %v", err)
|
|
}
|
|
if !ok {
|
|
return candidates
|
|
}
|
|
candidates = append(candidates, candidate)
|
|
}
|
|
}
|
|
|
|
func assertCandidatePaths(t *testing.T, got, want []string) {
|
|
t.Helper()
|
|
if len(got) != len(want) {
|
|
t.Fatalf("candidate count = %d, want %d: %#v", len(got), len(want), got)
|
|
}
|
|
for i := range want {
|
|
if got[i] != want[i] {
|
|
t.Fatalf("candidate %d = %q, want %q", i, got[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResolveSearchSelectsFirstExistingCandidate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
root := t.TempDir()
|
|
first := filepath.Join(root, "first.yml")
|
|
second := filepath.Join(root, "second.yml")
|
|
writeDiscoveryFile(t, first, "value: first\n")
|
|
writeDiscoveryFile(t, second, "value: second\n")
|
|
selected, diagnostics := resolveSearchSource(Source{}, ptrSearch(Paths(filepath.Join(root, "missing.yml"), first, second)), "", false, discoveryDirectoryHelpers{}, discoveryHostProbe{})
|
|
if len(diagnostics) != 0 || selected.kind != sourceFile || selected.path != first {
|
|
t.Fatalf("selection = %#v, diagnostics %#v", selected, diagnostics)
|
|
}
|
|
read, diagnostics := readSource(selected, "source-1", defaultSourceLimits())
|
|
if len(diagnostics) != 0 || string(read.data) != "value: first\n" || read.info.Path != canonicalTestPath(t, first) {
|
|
t.Fatalf("selected source read = %#v, diagnostics %#v", read, diagnostics)
|
|
}
|
|
}
|
|
|
|
func TestResolveSearchNamedExtensionsAndNonregularSelection(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
root := t.TempDir()
|
|
writeDiscoveryFile(t, filepath.Join(root, "app.yml"), "value: yml\n")
|
|
writeDiscoveryFile(t, filepath.Join(root, "app.yaml"), "value: yaml\n")
|
|
selected, diagnostics := resolveSearchSource(Source{}, ptrSearch(SearchDirectory(root)), "app", false, discoveryDirectoryHelpers{}, discoveryHostProbe{})
|
|
if len(diagnostics) != 0 || selected.path != filepath.Join(root, "app.yml") {
|
|
t.Fatalf("named extension selection = %#v, %#v", selected, diagnostics)
|
|
}
|
|
|
|
directory := filepath.Join(root, "subdirectory")
|
|
if err := os.Mkdir(directory, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
good := filepath.Join(root, "good.yml")
|
|
writeDiscoveryFile(t, good, "value: later\n")
|
|
selected, diagnostics = resolveSearchSource(Source{}, ptrSearch(Paths(directory, good)), "", false, discoveryDirectoryHelpers{}, discoveryHostProbe{})
|
|
if len(diagnostics) != 0 || selected.path != directory {
|
|
t.Fatalf("directory candidate selection = %#v, %#v", selected, diagnostics)
|
|
}
|
|
_, diagnostics = readSource(selected, "source-1", defaultSourceLimits())
|
|
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceNotRegular {
|
|
t.Fatalf("selected directory read diagnostics = %#v", diagnostics)
|
|
}
|
|
}
|
|
|
|
func TestResolveSearchContainedSymlinkAndNoFallbackAfterReadFailure(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
root := t.TempDir()
|
|
target := filepath.Join(root, "actual.yml")
|
|
link := filepath.Join(root, "linked.yml")
|
|
writeDiscoveryFile(t, target, "value: linked\n")
|
|
if err := os.Symlink(target, link); err != nil {
|
|
t.Skipf("symlinks unavailable: %v", err)
|
|
}
|
|
writeDiscoveryFile(t, filepath.Join(root, "later.yml"), "value: later\n")
|
|
selected, diagnostics := resolveSearchSource(Source{}, ptrSearch(Paths(link, filepath.Join(root, "later.yml"))), "", false, discoveryDirectoryHelpers{}, discoveryHostProbe{})
|
|
if len(diagnostics) != 0 || selected.path != link {
|
|
t.Fatalf("symlink selection = %#v, %#v", selected, diagnostics)
|
|
}
|
|
read, diagnostics := readSource(selected, "source-1", defaultSourceLimits())
|
|
if len(diagnostics) != 0 || string(read.data) != "value: linked\n" || read.info.Path != canonicalTestPath(t, target) {
|
|
t.Fatalf("symlink read = %#v, %#v", read, diagnostics)
|
|
}
|
|
if err := os.Remove(target); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, diagnostics = readSource(selected, "source-1", defaultSourceLimits())
|
|
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceMissing {
|
|
t.Fatalf("selected source read failure = %#v", diagnostics)
|
|
}
|
|
}
|
|
|
|
func TestResolveSearchRequiredAndOptionalExhaustion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
optional, diagnostics := resolveSearchSource(Source{}, ptrSearch(Search{}), "", false, discoveryDirectoryHelpers{}, discoveryHostProbe{})
|
|
if len(diagnostics) != 0 || optional.kind != sourceNone {
|
|
t.Fatalf("optional empty search = %#v, %#v", optional, diagnostics)
|
|
}
|
|
_, diagnostics = resolveSearchSource(Source{}, ptrSearch(Search{}), "", true, discoveryDirectoryHelpers{}, discoveryHostProbe{})
|
|
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceMissing || diagnostics[0].Primary != nil || !strings.Contains(diagnostics[0].Message, "search") {
|
|
t.Fatalf("required empty search = %#v", diagnostics)
|
|
}
|
|
|
|
root := t.TempDir()
|
|
missing := filepath.Join(root, "missing.yml")
|
|
optional, diagnostics = resolveSearchSource(Source{}, ptrSearch(Paths(missing)), "", false, discoveryDirectoryHelpers{}, discoveryHostProbe{})
|
|
if len(diagnostics) != 0 || optional.kind != sourceNone {
|
|
t.Fatalf("optional missing search = %#v, %#v", optional, diagnostics)
|
|
}
|
|
_, diagnostics = resolveSearchSource(Source{}, ptrSearch(Paths(missing)), "", true, discoveryDirectoryHelpers{}, discoveryHostProbe{})
|
|
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceMissing {
|
|
t.Fatalf("required missing search = %#v", diagnostics)
|
|
}
|
|
}
|
|
|
|
func TestResolveSearchLeavesNonSearchSourcesUntouched(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
source := Bytes("memory", []byte("value: unchanged\n"))
|
|
resolved, diagnostics := resolveSearchSource(source, nil, "", false, discoveryDirectoryHelpers{}, discoveryHostProbe{})
|
|
if len(diagnostics) != 0 || resolved.kind != sourceBytes || string(resolved.data) != "value: unchanged\n" {
|
|
t.Fatalf("non-search source = %#v, diagnostics = %#v", resolved, diagnostics)
|
|
}
|
|
}
|
|
|
|
func TestResolveSearchStopsOnReachedProbeError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
root := t.TempDir()
|
|
first := filepath.Join(root, "first.yml")
|
|
second := filepath.Join(root, "second.yml")
|
|
writeDiscoveryFile(t, first, "value: first\n")
|
|
writeDiscoveryFile(t, second, "value: second\n")
|
|
statCalls := 0
|
|
probe := discoveryHostProbe{stat: func(path string) (fs.FileInfo, error) {
|
|
statCalls++
|
|
return nil, fs.ErrPermission
|
|
}}
|
|
selected, info, diagnostics := resolveSearchInput(SearchConfig("", Paths(first, second)), "source-3", discoveryDirectoryHelpers{}, probe)
|
|
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourcePermission || statCalls != 1 {
|
|
t.Fatalf("probe failure = %#v, stat calls = %d", diagnostics, statCalls)
|
|
}
|
|
if selected.kind != sourceFile || info == nil || info.Path != canonicalTestPath(t, first) || info.Label != first || info.Kind != SourceKindFile || info.ID != "source-3" {
|
|
t.Fatalf("failed candidate evidence = %#v, %#v", selected, info)
|
|
}
|
|
if diagnostics[0].Primary == nil || diagnostics[0].Primary.SourceID != info.ID {
|
|
t.Fatalf("failed candidate diagnostic does not identify its file: %#v", diagnostics)
|
|
}
|
|
}
|
|
|
|
func TestResolveSearchLeavesLaterDirectoryUnresolvedAfterSelection(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
root := t.TempDir()
|
|
first := filepath.Join(root, "selected.yml")
|
|
writeDiscoveryFile(t, first, "value: selected\n")
|
|
baseCalls := 0
|
|
helpers := discoveryDirectoryHelpers{userConfigDir: func() (string, error) {
|
|
baseCalls++
|
|
return "", errors.New("later base should not be reached")
|
|
}}
|
|
search := ptrSearch(Searches(Paths(first), UserConfigDirectory()))
|
|
selected, diagnostics := resolveSearchSource(Source{}, search, "app", false, helpers, discoveryHostProbe{})
|
|
if len(diagnostics) != 0 || selected.path != first || baseCalls != 0 {
|
|
t.Fatalf("selected = %#v, diagnostics = %#v, later base calls = %d", selected, diagnostics, baseCalls)
|
|
}
|
|
}
|
|
|
|
func TestResolveSearchTreatsShellLikeNamesLiterally(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
root := t.TempDir()
|
|
paths := []string{"~/literal.yml", "$CONFIG/literal.yml", "*.yaml"}
|
|
for _, name := range paths {
|
|
writeDiscoveryFile(t, filepath.Join(root, name), "value: literal\n")
|
|
}
|
|
helper := discoveryDirectoryHelpers{workingDir: func() (string, error) { return root, nil }}
|
|
for _, name := range paths {
|
|
selected, diagnostics := resolveSearchSource(Source{}, ptrSearch(Paths(name)), "", false, helper, discoveryHostProbe{})
|
|
if len(diagnostics) != 0 || selected.path != filepath.Join(root, name) {
|
|
t.Fatalf("literal %q selection = %#v, %#v", name, selected, diagnostics)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ptrSearch(search Search) *Search { return &search }
|
|
|
|
func writeDiscoveryFile(t *testing.T, path, contents string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|