| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package build |
| |
| import ( |
| "bytes" |
| "crypto/rand" |
| "encoding/binary" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "io" |
| "os" |
| "path/filepath" |
| "sort" |
| "strings" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| |
| "go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pm/pkg" |
| far "go.fuchsia.dev/fuchsia/src/sys/pkg/lib/far/go" |
| "go.fuchsia.dev/fuchsia/src/sys/pkg/lib/merkle" |
| ) |
| |
| // Verifies package manifests can be properly loaded. |
| func TestLoadPackageManifest(t *testing.T) { |
| testCases := []struct { |
| name string |
| buildDirContents map[string]string |
| manifestPathToLoad string |
| expectedManifest PackageManifest |
| wantError bool |
| }{ |
| { |
| name: "success valid path, blobs, and subpackages", |
| buildDirContents: map[string]string{ |
| "package_manifest.json": `{ |
| "version": "1", |
| "blobs": [ |
| { "merkle": "0000000000000000000000000000000000000000000000000000000000000000" } |
| ], |
| "subpackages": [ |
| { |
| "name": "some_subpackage", |
| "merkle": "1111111111111111111111111111111111111111111111111111111111111111", |
| "manifest_path": "subpackage_manifest.json" |
| } |
| ] |
| }`, |
| }, |
| manifestPathToLoad: "package_manifest.json", |
| expectedManifest: PackageManifest{ |
| Version: "1", |
| Blobs: []PackageBlobInfo{ |
| {Merkle: MustDecodeMerkleRoot("0000000000000000000000000000000000000000000000000000000000000000")}, |
| }, |
| Subpackages: []PackageSubpackageInfo{ |
| { |
| Name: "some_subpackage", |
| Merkle: MustDecodeMerkleRoot("1111111111111111111111111111111111111111111111111111111111111111"), |
| ManifestPath: "subpackage_manifest.json", |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "failure incompatible version", |
| buildDirContents: map[string]string{ |
| "package_manifest.json": `{ |
| "version": "2", |
| }`, |
| }, |
| manifestPathToLoad: "package_manifest.json", |
| wantError: true, |
| }, |
| { |
| name: "failure json improperly formatted", |
| buildDirContents: map[string]string{ |
| "package_manifest.json": `{ |
| Oops. This is not valid json. |
| }`, |
| }, |
| manifestPathToLoad: "package_manifest.json", |
| wantError: true, |
| }, { |
| name: "failure package manifest does not exist", |
| manifestPathToLoad: "non_existent_manifest.json", |
| wantError: true, |
| }, |
| } |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| // Generate test env based on input. |
| tempDirPath := createBuildDir(t, tc.buildDirContents) |
| |
| // Now that we're set up, we can actually load the package manifest. |
| actualManifest, err := LoadPackageManifest(filepath.Join(tempDirPath, tc.manifestPathToLoad)) |
| |
| // Ensure the results match the expectations. |
| if (err == nil) == tc.wantError { |
| t.Fatalf("got error [%v], want error? %t", err, tc.wantError) |
| } |
| if diff := cmp.Diff(actualManifest, &tc.expectedManifest); err == nil && diff != "" { |
| t.Fatalf("got manifest %#v, expected %#v", actualManifest, tc.expectedManifest) |
| } |
| }) |
| } |
| } |
| |
| func createBuildDir(t *testing.T, files map[string]string) string { |
| dir := t.TempDir() |
| for filename, data := range files { |
| if err := os.MkdirAll(filepath.Join(dir, filepath.Dir(filename)), 0o700); err != nil { |
| t.Fatal(err) |
| } |
| if err := os.WriteFile(filepath.Join(dir, filename), []byte(data), 0o600); err != nil { |
| t.Fatal(err) |
| } |
| } |
| return dir |
| } |
| |
| func TestInit(t *testing.T) { |
| cfg := TestConfig() |
| defer os.RemoveAll(filepath.Dir(cfg.TempDir)) |
| |
| if err := Init(cfg); err != nil { |
| t.Fatal(err) |
| } |
| |
| f, err := os.Open(filepath.Join(cfg.OutputDir, "meta", "package")) |
| if err != nil { |
| t.Fatal(err) |
| } |
| var p pkg.Package |
| if err := json.NewDecoder(f).Decode(&p); err != nil { |
| t.Fatal(err) |
| } |
| f.Close() |
| |
| if want := cfg.PkgName; p.Name != want { |
| t.Errorf("got %q, want %q", p.Name, want) |
| } |
| |
| if want := "0"; p.Version != want { |
| t.Errorf("got %q, want %q", p.Version, want) |
| } |
| os.RemoveAll(filepath.Dir(cfg.TempDir)) |
| |
| // test condition where package name is empty, name should match the |
| // name of the output directory |
| cfg.PkgName = "" |
| if err = Init(cfg); err != nil { |
| t.Fatal(err) |
| } |
| |
| f, err = os.Open(filepath.Join(cfg.OutputDir, "meta", "package")) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if err = json.NewDecoder(f).Decode(&p); err != nil { |
| t.Fatal(err) |
| } |
| f.Close() |
| |
| if want := filepath.Base(cfg.OutputDir); p.Name != want { |
| t.Errorf("got %q, want %q", p.Name, want) |
| } |
| } |
| |
| func TestUpdate(t *testing.T) { |
| cfg := TestConfig() |
| defer os.RemoveAll(filepath.Dir(cfg.TempDir)) |
| |
| cfg.PkgABIRevision = TestABIRevision |
| |
| TestPackage(cfg) |
| |
| manifest, err := cfg.Manifest() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| for _, f := range manifest.Content() { |
| fd, err := os.Create(f) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if _, err := io.CopyN(fd, rand.Reader, 1024); err != nil { |
| t.Fatal(err) |
| } |
| fd.Close() |
| } |
| |
| if _, ok := manifest.Meta()["meta/contents"]; ok { |
| t.Fatalf("unexpected pre-existing meta/contents in manifest") |
| } |
| |
| if err := Update(cfg); err != nil { |
| t.Fatal(err) |
| } |
| |
| if _, ok := manifest.Meta()[abiRevisionKey]; !ok { |
| t.Fatalf("%s was not found in manifest after update", abiRevisionKey) |
| } |
| |
| contentsPath, ok := manifest.Meta()["meta/contents"] |
| if !ok { |
| t.Fatalf("meta/contents was not found in manifest after update") |
| } |
| |
| f, err := os.Open(contentsPath) |
| if err != nil { |
| t.Fatal(err) |
| } |
| buf, err := io.ReadAll(f) |
| f.Close() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| lines := strings.Split(string(buf), "\n") |
| sort.Strings(lines) |
| if lines[0] == "" { |
| lines = lines[1:] |
| } |
| |
| contentFiles := []string{} |
| for _, s := range TestFiles { |
| if strings.HasPrefix(s, "meta/") { |
| continue |
| } |
| contentFiles = append(contentFiles, s) |
| } |
| if len(lines) != len(contentFiles) { |
| t.Fatalf("content lines mismatch: %v\n%v", lines, contentFiles) |
| } |
| |
| files := []string{} |
| files = append(files, contentFiles...) |
| sort.Strings(files) |
| for i, pf := range files { |
| var tree merkle.Tree |
| f, err := os.Open(manifest.Paths[pf]) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if _, err := tree.ReadFrom(f); err != nil { |
| t.Fatal(err) |
| } |
| f.Close() |
| want := fmt.Sprintf("%s=%x", pf, tree.Root()) |
| if lines[i] != want { |
| t.Errorf("contents mismatch: got %q, want %q", lines[i], want) |
| break |
| } |
| } |
| } |
| |
| func TestUpdateTakesABIRevisionAndWritesABIRevision(t *testing.T) { |
| cfg := TestConfig() |
| defer os.RemoveAll(filepath.Dir(cfg.TempDir)) |
| |
| TestPackage(cfg) |
| |
| manifest, err := cfg.Manifest() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if err := Update(cfg); err != nil { |
| t.Fatal(err) |
| } |
| |
| // FIXME(https://fxbug.dev/42168416): In order to ease migration, initially the |
| // abi-revision is not required. |
| abiRevisionPath, ok := manifest.Meta()[abiRevisionKey] |
| if !ok { |
| t.Fatalf("%s was not found in manifest after update", abiRevisionKey) |
| } |
| |
| f, err := os.Open(abiRevisionPath) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer f.Close() |
| |
| var abiRevision uint64 |
| if err := binary.Read(f, binary.LittleEndian, &abiRevision); err != nil { |
| t.Fatal(err) |
| } |
| if abiRevision != TestABIRevision { |
| t.Fatalf("expected ABI revision to be %x, not %x", TestABIRevision, abiRevision) |
| } |
| } |
| |
| func TestUpdateDoesNotRequireABIRevision(t *testing.T) { |
| cfg := TestConfig() |
| defer os.RemoveAll(filepath.Dir(cfg.TempDir)) |
| |
| cfg.PkgABIRevision = 0 |
| |
| TestPackage(cfg) |
| |
| if err := Update(cfg); err != nil { |
| t.Fatalf("expected update to not fail because ABI revision was not specified") |
| } |
| } |
| |
| func TestUpdateReadsABIRevisionFromDirectory(t *testing.T) { |
| cfg := TestConfig() |
| defer os.RemoveAll(filepath.Dir(cfg.TempDir)) |
| |
| cfg.PkgABIRevision = 0 |
| |
| TestPackage(cfg) |
| addTestABIRevisionToManifest(cfg, TestABIRevision) |
| |
| if err := Update(cfg); err != nil { |
| t.Fatalf("expected update to read ABI revision from file: %v", err) |
| } |
| |
| abiOutputPath := filepath.Join(cfg.OutputDir, "meta", "fuchsia.pkg", "abi-revision") |
| if _, err := os.Stat(abiOutputPath); err == nil { |
| t.Fatalf("should not have created abi revision file: %s", abiOutputPath) |
| } |
| |
| } |
| |
| func TestUpdateRequiresManifestABIRevisionToMatchCliABIRevision(t *testing.T) { |
| cfg := TestConfig() |
| defer os.RemoveAll(filepath.Dir(cfg.TempDir)) |
| |
| TestPackage(cfg) |
| addTestABIRevisionToManifest(cfg, TestABIRevision2) |
| |
| if err := Update(cfg); err == nil { |
| t.Fatalf("expected update to fail because manifest ABI revision conflicted with CLI ABI revision") |
| } |
| |
| cfg.PkgABIRevision = TestABIRevision2 |
| |
| if err := Update(cfg); err != nil { |
| t.Fatalf("expected update to read ABI revision from file: %v", err) |
| } |
| |
| abiOutputPath := filepath.Join(cfg.OutputDir, "meta", "fuchsia.pkg", "abi-revision") |
| if _, err := os.Stat(abiOutputPath); err == nil { |
| t.Fatalf("should not have created abi revision file: %s", abiOutputPath) |
| } |
| } |
| |
| func TestValidate(t *testing.T) { |
| cfg := TestConfig() |
| defer os.RemoveAll(filepath.Dir(cfg.TempDir)) |
| |
| TestPackage(cfg) |
| |
| if err := Update(cfg); err != nil { |
| t.Fatal(err) |
| } |
| |
| err := Validate(cfg) |
| if err != nil { |
| t.Fatalf("Unexpected validation error") |
| } |
| |
| manifest, err := cfg.Manifest() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| for _, f := range RequiredFiles { |
| manifest.Paths[f+"a"] = manifest.Paths[f] |
| delete(manifest.Paths, f) |
| if err := Validate(cfg); err == nil { |
| t.Errorf("expected a validation error when %q is missing", f) |
| } |
| manifest.Paths[f] = manifest.Paths[f+"a"] |
| delete(manifest.Paths, f+"a") |
| } |
| } |
| |
| func TestSeal(t *testing.T) { |
| cfg := TestConfig() |
| defer os.RemoveAll(filepath.Dir(cfg.TempDir)) |
| |
| TestPackage(cfg) |
| |
| if err := Update(cfg); err != nil { |
| t.Fatal(err) |
| } |
| |
| if _, err := Seal(cfg); err != nil { |
| t.Fatal(err) |
| } |
| |
| // TODO(raggi): until we have far reader support this test only verifies that |
| // the package meta data was correctly updated, it doesn't actually verify |
| // that the contents of the far are correct. |
| metafar := filepath.Join(cfg.OutputDir, "meta.far") |
| f, err := os.Open(metafar) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| r, err := far.NewReader(f) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| abiBytes, err := r.ReadFile(abiRevisionKey) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| var abiRevision uint64 |
| if err := binary.Read(bytes.NewReader(abiBytes), binary.LittleEndian, &abiRevision); err != nil { |
| t.Fatal(err) |
| } |
| if abiRevision != TestABIRevision { |
| t.Fatalf("expected ABI revision to be %x, not %x", TestABIRevision, abiRevision) |
| } |
| } |
| |
| func TestSealDoesNotRequireABIRevision(t *testing.T) { |
| cfg := TestConfig() |
| defer os.RemoveAll(filepath.Dir(cfg.TempDir)) |
| |
| TestPackage(cfg) |
| |
| if err := Update(cfg); err != nil { |
| t.Fatal(err) |
| } |
| |
| cfg.PkgABIRevision = 0 |
| |
| if _, err := Seal(cfg); err != nil { |
| t.Fatal(err) |
| } |
| |
| // This test only verifies that the package meta data was correctly |
| // updated, it doesn't actually verify that the contents of the far are |
| // correct. |
| metafar := filepath.Join(cfg.OutputDir, "meta.far") |
| f, err := os.Open(metafar) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| r, err := far.NewReader(f) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // FIXME(https://fxbug.dev/42168416): In order to ease migration, initially the |
| // abi-revision is not required. |
| _, err = r.Open("meta/fuchsia.pkg/abi-revision") |
| if err == nil { |
| t.Fatalf("expected meta/fuchsia.pkg/abi-revision to not be present in meta.far") |
| } |
| if !errors.Is(err, os.ErrNotExist) { |
| t.Fatalf("expected error to be does not exist, not %s", err) |
| } |
| } |
| |
| func TestSealReadsABIRevisionFromDirectory(t *testing.T) { |
| cfg := TestConfig() |
| defer os.RemoveAll(filepath.Dir(cfg.TempDir)) |
| |
| cfg.PkgABIRevision = 0 |
| |
| TestPackage(cfg) |
| addTestABIRevisionToManifest(cfg, TestABIRevision) |
| |
| if err := Update(cfg); err != nil { |
| t.Fatalf("expected update to read ABI revision from file: %v", err) |
| } |
| |
| if _, err := Seal(cfg); err != nil { |
| t.Fatal(err) |
| } |
| |
| // This test only verifies that the package meta data was correctly |
| // updated, it doesn't actually verify that the contents of the far are |
| // correct. |
| metafar := filepath.Join(cfg.OutputDir, "meta.far") |
| f, err := os.Open(metafar) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| r, err := far.NewReader(f) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| abiBytes, err := r.ReadFile(abiRevisionKey) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| var abiRevision uint64 |
| if err := binary.Read(bytes.NewReader(abiBytes), binary.LittleEndian, &abiRevision); err != nil { |
| t.Fatal(err) |
| } |
| if abiRevision != TestABIRevision { |
| t.Fatalf("expected ABI revision to be %x, not %x", TestABIRevision, abiRevision) |
| } |
| } |
| |
| func TestSealValidatesInvalidPackageRepository(t *testing.T) { |
| defer func() { |
| if r := recover(); r == nil { |
| t.Fatalf("Expected to fail on invalid repository") |
| } |
| }() |
| |
| cfg := TestConfig() |
| defer os.RemoveAll(filepath.Dir(cfg.TempDir)) |
| cfg.PkgRepository = "x,y" |
| BuildTestPackage(cfg) |
| |
| if _, err := Seal(cfg); err == nil { |
| t.Fatalf("Expected invalid package repository to generate error.") |
| } |
| } |