package engine import ( "errors" "io/fs" "reflect" "strings" "testing" "testing/fstest" ) func TestNativeFilesystemImportsAndOverlay(t *testing.T) { filesystem := fstest.MapFS{ "config/main.yml": &fstest.MapFile{Data: []byte(`configkit: imports: - path: ../shared.yml - glob: fragments/*.yml items: {main: main} `)}, "shared.yml": &fstest.MapFile{Data: []byte("items: {shared: shared}\n")}, "config/fragments/b.yml": &fstest.MapFile{Data: []byte("items: {beta: beta}\n")}, "config/fragments/a.yml": &fstest.MapFile{Data: []byte("items: {alpha: alpha}\n")}, "overlays/testing.yml": &fstest.MapFile{Data: []byte("items: {main: overlaid}\n")}, } namespace := newNativeNamespace("bundle", filesystem) result, err := testLoad[importTestConfig](filesystemFile(namespace, "config/main.yml"), Options[importTestConfig]{Imports: true, Overlay: "testing"}) if err != nil { t.Fatal(err) } want := map[string]string{"main": "overlaid", "shared": "shared", "alpha": "alpha", "beta": "beta"} if !reflect.DeepEqual(result.Config.Items, want) { t.Fatalf("items = %#v, want %#v", result.Config.Items, want) } sources := result.Sources() wantPaths := []string{"config/main.yml", "shared.yml", "config/fragments/a.yml", "config/fragments/b.yml", "overlays/testing.yml"} gotPaths := make([]string, len(sources)) for index, source := range sources { gotPaths[index] = source.Path } if !reflect.DeepEqual(gotPaths, wantPaths) { t.Fatalf("source paths = %#v, want %#v", gotPaths, wantPaths) } } func TestNativeFilesystemItemImportUsesSharedScopedAssembly(t *testing.T) { t.Parallel() type item struct { ID string `yaml:"id"` Name string `yaml:"name"` } type config struct { Items []item `yaml:"items"` } filesystem := fstest.MapFS{ "main.yml": &fstest.MapFile{Data: []byte("configkit:\n imports:\n - path: records/item.yml\n destination: [items]\n placement: sequence-item\n")}, "records/item.yml": &fstest.MapFile{Data: []byte("configkit:\n imports:\n - path: helper.yml\nid: native\n")}, "records/helper.yml": &fstest.MapFile{Data: []byte("name: helper\n")}, } namespace := newNativeNamespace("bundle", filesystem) result, err := testLoad[config](filesystemFile(namespace, "main.yml"), Options[config]{Imports: true}) if err != nil { t.Fatalf("native item import: %v", err) } if len(result.Config.Items) != 1 || result.Config.Items[0].ID != "native" || result.Config.Items[0].Name != "helper" { t.Fatalf("native item = %#v", result.Config.Items) } sources := result.Sources() if len(sources) != 3 || sources[1].Destination.String() != "/items" || sources[2].Destination.String() != "/items/0" || sources[1].Path != "records/item.yml" || sources[2].Path != "records/helper.yml" { t.Fatalf("native item sources = %#v", sources) } } func TestNativeFilesystemNamespaceAndPathFailures(t *testing.T) { first := fstest.MapFS{"main.yml": &fstest.MapFile{Data: []byte("items: {name: first}\n")}} second := fstest.MapFS{"main.yml": &fstest.MapFile{Data: []byte("items: {name: second}\n")}} firstNamespace := newNativeNamespace("same", first) secondNamespace := newNativeNamespace("same", second) if reflect.TypeOf(first).Comparable() { t.Fatal("test filesystem must have a non-comparable dynamic value") } if firstNamespace.token == secondNamespace.token { t.Fatal("independent filesystem constructors shared identity") } if newNativeAccess(first, ".", firstNamespace.token).identity("main.yml") == newNativeAccess(second, ".", secondNamespace.token).identity("main.yml") { t.Fatal("equal paths from separate filesystems have the same identity") } access := newNativeAccess(first, ".", firstNamespace.token) if access.syntheticIdentity() == access.syntheticIdentity() || access.syntheticIdentity() == access.identity("") { t.Fatal("independent synthetic sources shared a file identity") } for _, test := range []struct { name string namespace *nativeNamespace want string }{ {name: "first", namespace: firstNamespace, want: "first"}, {name: "second", namespace: secondNamespace, want: "second"}, } { t.Run(test.name, func(t *testing.T) { result, err := testLoad[importTestConfig](filesystemFile(test.namespace, "main.yml")) if err != nil || result.Config.Items["name"] != test.want { t.Fatalf("result = %#v, %v", result, err) } }) } for _, name := range []string{".", "../main.yml", "/main.yml", "missing.yml"} { _, diagnostics := readSource(filesystemFile(firstNamespace, name), "source-1", defaultSourceLimits()) if len(diagnostics) != 1 { t.Errorf("%q diagnostics = %#v", name, diagnostics) } } missingImport := fstest.MapFS{"main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: host-only.yml, required: true}]}\n")}} _, err := testLoad[importTestConfig](filesystemFile(newNativeNamespace("bundle", missingImport), "main.yml"), Options[importTestConfig]{Imports: true}) if err == nil { t.Fatal("missing virtual import fell back to the host filesystem") } loadError, ok := err.(*LoadError) if !ok || loadError.Diagnostics()[0].Code != CodeImportMissing { t.Fatalf("missing import error = %#v", err) } } func TestNativeFilesystemClosesFilesOnSuccessAndFailure(t *testing.T) { for _, fallback := range []bool{false, true} { t.Run(map[bool]string{false: "ReadDirFile", true: "ReadDir fallback"}[fallback], func(t *testing.T) { filesystem := &observedFilesystem{files: fstest.MapFS{ "main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{glob: 'parts/*.yml'}]}\nitems: {main: main}\n")}, "parts/one.yml": &fstest.MapFile{Data: []byte("items: {one: one}\n")}, }, fallback: fallback} result, err := testLoad[importTestConfig](filesystemFile(newNativeNamespace("bundle", filesystem), "main.yml"), Options[importTestConfig]{Imports: true}) if err != nil || result.Config.Items["one"] != "one" { t.Fatalf("result = %#v, %v", result, err) } filesystem.assertClosed(t) }) } for _, failure := range []struct { name string file string failRead string failStat string failDir string }{ {name: "read", file: "items: {one: one}\n", failRead: "parts/one.yml"}, {name: "stat", file: "items: {one: one}\n", failStat: "parts/one.yml"}, {name: "parse", file: "items: [unterminated\n"}, {name: "directory", file: "items: {one: one}\n", failDir: "parts"}, } { t.Run(failure.name, func(t *testing.T) { filesystem := &observedFilesystem{files: fstest.MapFS{ "main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{glob: 'parts/*.yml'}]}\n")}, "parts/one.yml": &fstest.MapFile{Data: []byte(failure.file)}, }, failRead: failure.failRead, failStat: failure.failStat, failDir: failure.failDir} _, err := testLoad[importTestConfig](filesystemFile(newNativeNamespace("bundle", filesystem), "main.yml"), Options[importTestConfig]{Imports: true}) if err == nil { t.Fatal("expected a load failure") } if strings.Contains(err.Error(), "tracking failure canary") { t.Fatal("unsafe filesystem error escaped") } filesystem.assertClosed(t) }) } } func TestNativeFilesystemRootAndOptionalImports(t *testing.T) { filesystem := fstest.MapFS{ "config/sub/main.yml": &fstest.MapFile{Data: []byte(`configkit: imports: - path: ../shared.yml - path: absent.yml - glob: absent/*.yml `)}, "config/shared.yml": &fstest.MapFile{Data: []byte("items: {shared: retained}\n")}, "outside.yml": &fstest.MapFile{Data: []byte("items: {outside: blocked}\n")}, } namespace := newNativeNamespace("bundle", filesystem) source := filesystemFile(namespace, "config/sub/main.yml") parsed, diagnostics := parseSource(source, "source-1", defaultParseLimits()) if len(diagnostics) != 0 { t.Fatal(diagnostics) } access := newNativeAccess(filesystem, "config", namespace.token) composed, sources, diagnostics := loadImportedWithAccess(access, "config/sub", source.path, parsed, nil, defaultImportLimits(), &importAccounting{}) if len(diagnostics) != 0 || composed == nil || len(sources) != 2 || sources[1].Path != "config/shared.yml" { t.Fatalf("contained imports = (%#v, %#v, %#v)", composed, sources, diagnostics) } outside := fstest.MapFS{ "config/sub/main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: ../../outside.yml}]}\n")}, "outside.yml": &fstest.MapFile{Data: []byte("items: {outside: blocked}\n")}, } outsideNamespace := newNativeNamespace("bundle", outside) parsed, diagnostics = parseSource(filesystemFile(outsideNamespace, "config/sub/main.yml"), "source-1", defaultParseLimits()) if len(diagnostics) != 0 { t.Fatal(diagnostics) } _, _, diagnostics = loadImportedWithAccess(newNativeAccess(outside, "config", outsideNamespace.token), "config/sub", "config/sub/main.yml", parsed, nil, defaultImportLimits(), &importAccounting{}) if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportOutsideRoot { t.Fatalf("escaping import diagnostics = %#v", diagnostics) } } func TestNativeFilesystemDetectsRepeatedAndCyclicImports(t *testing.T) { for _, test := range []struct { name string files fstest.MapFS code Code }{ { name: "repeated", files: fstest.MapFS{ "main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: child.yml}, {path: child.yml}]}\n")}, "child.yml": &fstest.MapFile{Data: []byte("items: {child: child}\n")}, }, code: CodeImportRepeated, }, { name: "cycle", files: fstest.MapFS{ "main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: child.yml}]}\n")}, "child.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: main.yml}]}\n")}, }, code: CodeImportCycle, }, } { t.Run(test.name, func(t *testing.T) { _, err := testLoad[importTestConfig](filesystemFile(newNativeNamespace("bundle", test.files), "main.yml"), Options[importTestConfig]{Imports: true}) loadError, ok := err.(*LoadError) if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != test.code { t.Fatalf("load error = %#v, want %q", err, test.code) } }) } } type observedFilesystem struct { files fstest.MapFS opened int closed int fallback bool failRead string failStat string failDir string } func (f *observedFilesystem) Open(name string) (fs.File, error) { inner, err := f.files.Open(name) if err != nil { return nil, err } f.opened++ file := &observedFile{File: inner, owner: f, name: name} if directory, ok := inner.(fs.ReadDirFile); ok && !f.fallback { return &observedDirectoryFile{observedFile: file, directory: directory}, nil } return file, nil } func (f *observedFilesystem) ReadDir(name string) ([]fs.DirEntry, error) { if name == f.failDir { return nil, errors.New("tracking failure canary") } return fs.ReadDir(f.files, name) } func (f *observedFilesystem) assertClosed(t *testing.T) { t.Helper() if f.opened == 0 || f.opened != f.closed { t.Fatalf("filesystem opens=%d closes=%d", f.opened, f.closed) } } type observedFile struct { fs.File owner *observedFilesystem name string } func (f *observedFile) Read(data []byte) (int, error) { if f.name == f.owner.failRead { return 0, errors.New("tracking failure canary") } return f.File.Read(data) } func (f *observedFile) Stat() (fs.FileInfo, error) { if f.name == f.owner.failStat { return nil, errors.New("tracking failure canary") } return f.File.Stat() } func (f *observedFile) Close() error { f.owner.closed++ return f.File.Close() } type observedDirectoryFile struct { *observedFile directory fs.ReadDirFile } func (f *observedDirectoryFile) ReadDir(count int) ([]fs.DirEntry, error) { if f.name == f.owner.failDir { return nil, errors.New("tracking failure canary") } return f.directory.ReadDir(count) }