| // Copyright 2020 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 packages |
| |
| import ( |
| "bytes" |
| "context" |
| "encoding/json" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "runtime" |
| "testing" |
| |
| versionHistory "go.fuchsia.dev/fuchsia/src/lib/versioning/version-history/go" |
| "go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pm/build" |
| "go.fuchsia.dev/fuchsia/src/testing/host-target-testing/ffx" |
| ) |
| |
| var hostDir = map[string]string{"arm64": "host_arm64", "amd64": "host_x64"}[runtime.GOARCH] |
| |
| // createTestPackage fills the given directory with a new repository. |
| func createTestPackage(t *testing.T, dir string) (*Repository, build.MerkleRoot) { |
| ctx := context.Background() |
| |
| // Initialize a repo. |
| t.Logf("Creating repo at %s", dir) |
| blobsDir := filepath.Join(dir, "blobs") |
| |
| // Create a config. |
| config := build.TestConfig() |
| t.Logf("Creating meta.far in %s", config.OutputDir) |
| |
| config.PkgABIRevision = versionHistory.History().ExampleSupportedAbiRevisionForTests() |
| |
| build.BuildTestPackage(config) |
| defer os.RemoveAll(filepath.Dir(config.OutputDir)) |
| |
| // Grab the merkle of the config's package manifest. |
| manifestPath := filepath.Join(config.OutputDir, "package_manifest.json") |
| manifest, err := os.ReadFile(manifestPath) |
| if err != nil { |
| t.Fatal(err) |
| } |
| var packageManifest build.PackageManifest |
| if err := json.Unmarshal(manifest, &packageManifest); err != nil { |
| t.Fatal(err) |
| } |
| |
| foundMerkle := false |
| var metaMerkle build.MerkleRoot |
| for _, blob := range packageManifest.Blobs { |
| if blob.Path == "meta/" { |
| foundMerkle = true |
| metaMerkle = blob.Merkle |
| break |
| } |
| } |
| if !foundMerkle { |
| t.Fatal("did not find meta.far in manifest") |
| } |
| |
| // Publish the config to the repo. |
| isolateDir := ffx.NewIsolateDir(filepath.Join(t.TempDir(), "ffx-isolate-dir")) |
| ffx, err := ffx.NewFFXTool("host-tools/ffx", isolateDir) |
| if err != nil { |
| t.Fatalf("failed to create FFXTool: %s", err) |
| } |
| keysDir := filepath.Join(hostDir, "test_data/ffx_lib_pkg/empty-repo/keys") |
| err = ffx.RepositoryCreate(ctx, dir, keysDir) |
| if err != nil { |
| t.Fatalf("failed to create repository: %s", err) |
| } |
| err = exec.Command("cp", "-r", keysDir, dir).Run() |
| if err != nil { |
| t.Fatalf("failed to copy keys: %s", err) |
| } |
| blobType := 1 |
| pkgRepo, err := NewRepository(ctx, dir, NewDirBlobStore(blobsDir), ffx, &blobType) |
| if err != nil { |
| t.Fatalf("failed to read repo: %s", err) |
| } |
| err = pkgRepo.Publish(ctx, manifestPath) |
| if err != nil { |
| t.Fatalf("failed to publish package: %s", err) |
| } |
| return pkgRepo, metaMerkle |
| } |
| |
| // expandPackage expands the given merkle from the given repository into the given directory. |
| func expandPackage(t *testing.T, pkgRepo *Repository, merkle build.MerkleRoot, dir string) { |
| ctx := context.Background() |
| |
| // Parse the package we want. |
| pkg, err := newPackage(ctx, pkgRepo, "", merkle) |
| if err != nil { |
| t.Fatalf("failed to read package: %s", err) |
| } |
| |
| // Expand to the given directory. |
| if err = pkg.Expand(ctx, dir); err != nil { |
| t.Fatalf("failed to expand to dir: %s", err) |
| } |
| } |
| |
| // createAndExpandPackage creates temporary directories and expands a test package, returning the expand directory. |
| func createAndExpandPackage(t *testing.T, parentDir string) (*Repository, string) { |
| dir := filepath.Join(parentDir, "package") |
| if err := os.Mkdir(dir, 0o700); err != nil { |
| t.Fatal(err) |
| } |
| pkgRepo, metaMerkle := createTestPackage(t, dir) |
| expand := filepath.Join(parentDir, "expand") |
| if err := os.Mkdir(expand, 0o700); err != nil { |
| t.Fatal(err) |
| } |
| expandPackage(t, pkgRepo, metaMerkle, expand) |
| return pkgRepo, expand |
| } |
| |
| func TestAddResource(t *testing.T) { |
| parentDir := t.TempDir() |
| _, expandDir := createAndExpandPackage(t, parentDir) |
| pkgBuilder, err := NewPackageBuilderFromDir(expandDir, "testpackage", "0", "testrepository.com") |
| if err != nil { |
| t.Fatalf("Failed to parse package from %s. %s", expandDir, err) |
| } |
| defer pkgBuilder.Close() |
| |
| newResource := "blah/z" |
| |
| // Confirm the new resource doesn't exist yet. |
| if _, ok := pkgBuilder.Contents[newResource]; ok { |
| t.Fatalf("Test resource %s should not exist yet in the package.", newResource) |
| } |
| |
| pkgBuilder.AddResource(newResource, bytes.NewReader([]byte(newResource))) |
| |
| // Confirm the file and contents were added. |
| path, ok := pkgBuilder.Contents[newResource] |
| if !ok { |
| t.Fatalf("Test resource %s failed to be added.", newResource) |
| } |
| newData, err := os.ReadFile(path) |
| if err != nil { |
| t.Fatalf("Failed to read contents of %s. %s", newResource, err) |
| } |
| if string(newData) != newResource { |
| t.Fatalf("%s expects to have %s, but has %s", newResource, newResource, string(newData)) |
| } |
| |
| expectedFiles := map[string]struct{}{ |
| "blah/z": {}, |
| "meta/contents": {}, |
| "meta/foo/one": {}, |
| "meta/fuchsia.abi/abi-revision": {}, |
| "meta/package": {}, |
| } |
| for _, item := range build.TestFiles { |
| expectedFiles[item] = struct{}{} |
| } |
| |
| for key := range pkgBuilder.Contents { |
| if _, ok := expectedFiles[key]; !ok { |
| t.Errorf("File %s is not expected", key) |
| } |
| } |
| |
| if len(expectedFiles) != len(pkgBuilder.Contents) { |
| t.Errorf("Package contents has %d files, should have %d", len(pkgBuilder.Contents), len(expectedFiles)) |
| } |
| |
| if err := pkgBuilder.AddResource(newResource, bytes.NewReader([]byte(newResource))); err == nil { |
| t.Fatalf("Resource %s should have failed to be added twice.", newResource) |
| } |
| } |
| |
| func TestPublish(t *testing.T) { |
| ctx := context.Background() |
| |
| parentDir := t.TempDir() |
| pkgRepo, expandDir := createAndExpandPackage(t, parentDir) |
| pkgBuilder, err := NewPackageBuilderFromDir(expandDir, "testpackage", "0", "testrepository.com") |
| if err != nil { |
| t.Fatalf("Failed to parse package from %s. %s", expandDir, err) |
| } |
| defer pkgBuilder.Close() |
| |
| fullPkgName := pkgBuilder.Name + "/" + pkgBuilder.Version |
| newResource := "blah/z" |
| |
| // Confirm package in repo is as expected. |
| pkg, err := pkgRepo.OpenPackage(ctx, fullPkgName) |
| if err != nil { |
| t.Fatalf("Repo does not contain '%s'. %s", fullPkgName, err) |
| } |
| if _, err := pkg.ReadFile(ctx, newResource); err == nil { |
| t.Fatalf("%s should not be found in package", newResource) |
| } |
| |
| // Add resource to package. |
| pkgBuilder.AddResource(newResource, bytes.NewReader([]byte(newResource))) |
| |
| // Update repo with updated package. We don't check the merkle since the package includes randomly generated files. |
| actualPkg, err := pkgBuilder.Publish(ctx, pkgRepo) |
| if err != nil { |
| t.Fatalf("Publishing package failed. %s", err) |
| } |
| |
| if actualPkg.Path() != fullPkgName { |
| t.Fatalf("package path should be %q, not %q", fullPkgName, actualPkg.Path()) |
| } |
| |
| deliveryBlobType := 1 |
| _, err = pkgRepo.blobStore.OpenBlob(ctx, &deliveryBlobType, actualPkg.Merkle()) |
| if err != nil { |
| t.Fatalf("Delivery blob does not exist '%s'. %s", actualPkg.Merkle(), err) |
| } |
| |
| isolateDir := ffx.NewIsolateDir(filepath.Join(t.TempDir(), "ffx-isolate-dir")) |
| ffx, err := ffx.NewFFXTool("host-tools/ffx", isolateDir) |
| if err != nil { |
| t.Fatalf("failed to create FFXTool: %s", err) |
| } |
| pkgRepo, err = NewRepository(ctx, pkgRepo.rootDir, pkgRepo.blobStore, ffx, nil) |
| |
| // Confirm that the package is published and updated. |
| pkg, err = pkgRepo.OpenPackage(ctx, fullPkgName) |
| if err != nil { |
| t.Fatalf("Repo does not contain '%s'. %s", fullPkgName, err) |
| } |
| if data, err := pkg.ReadFile(ctx, newResource); err != nil { |
| t.Fatalf("%s should be in package.", newResource) |
| } else { |
| if string(data) != newResource { |
| t.Fatalf("%s should have value %s but is %s", newResource, newResource, data) |
| } |
| } |
| } |