blob: dc1587c229dbb55960da761f32b808bdb26eb183 [file] [log] [blame]
// 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.")
}
}