blob: 57020ccf8a2cedeb596508e16ed64c8ff2c099f2 [file] [log] [blame]
// 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 build
import (
"reflect"
"testing"
)
type mergedPackageFileRef struct {
Name string
Paths []string
}
// mergePackageReferences transforms a slice of package/path tuples into a
// slice of package/[]path tuples, preserving member order. Member order must
// be preserved to uphold SnapshotDelta's invariant of member sort order.
func mergePackageReferences(references []PackageFileRef) []mergedPackageFileRef {
res := []mergedPackageFileRef{}
var current mergedPackageFileRef
for _, ref := range references {
if current.Name == "" {
current.Name = ref.Name
}
if ref.Name == current.Name {
current.Paths = append(current.Paths, ref.Path)
} else {
res = append(res, current)
current.Name = ref.Name
current.Paths = []string{ref.Path}
}
}
return append(res, current)
}
// populatePackageAddedBlobs allows test cases to automatically generate any
// per-package AddedBlobs fields, since this data can be determined from the
// global AddedBlobs slice
func (d SnapshotDelta) populatePackageAddedBlobs() SnapshotDelta {
packages := map[string]*DeltaPackageStats{}
for i := range d.Packages {
packages[d.Packages[i].Name] = &d.Packages[i]
}
for _, blob := range d.AddedBlobs {
for _, ref := range mergePackageReferences(blob.References) {
pkg := packages[ref.Name]
pkg.AddedBlobs = append(pkg.AddedBlobs, DeltaPackageBlobStats{
Merkle: blob.Merkle,
Size: blob.Size,
Paths: ref.Paths,
})
}
}
return d
}
// verifySnapshotDelta will delta source and target, and ensure the expected
// delta and computed delta match
func verifySnapshotDelta(t *testing.T, source Snapshot, target Snapshot, expected SnapshotDelta) {
actual, err := DeltaSnapshots(source, target)
if err != nil {
t.Errorf("error producing snapshot delta: %v", err)
return
}
if !reflect.DeepEqual(actual, expected) {
t.Errorf("\nExpected:\n%v\n\nGot:\n%v", expected, actual)
}
}
func TestDeltaSnapshots_inconsistentBlobs(t *testing.T) {
source := newSnapshotBuilder(t).
File(1, fileRef{"a", "file"}).
Build()
target := newSnapshotBuilder(t).
File(2, fileRef{"different", "file?"}).
Build()
actual, err := DeltaSnapshots(source, target)
if err == nil {
t.Errorf("expected error, got %v", actual)
}
merkle := source.Packages["a"].Files["file"]
expected := ErrInconsistentSnapshotBlobs{
Merkle: merkle,
Source: source.Blobs[merkle],
Target: target.Blobs[merkle],
}
if err != expected {
t.Errorf("expected %v, got %v", expected, err)
}
}
func TestDeltaSnapshots_empty(t *testing.T) {
empty := newSnapshotBuilder(t).Build()
expected := SnapshotDelta{}
verifySnapshotDelta(t, empty, empty, expected)
}
func TestDeltaSnapshots_same(t *testing.T) {
s := makeTestSnapshot(t)
expected := SnapshotDelta{
UnchangedSize: s.Size(),
SourceSize: s.Size(),
TargetSize: s.Size(),
Packages: []DeltaPackageStats{
{
Name: "bar/0",
UnchangedSize: 1024 * 5,
},
{
Name: "foo/0",
UnchangedSize: 1024 * 5,
},
{
Name: "foobar/0",
UnchangedSize: 1024 * 4,
},
{
Name: "optional/0",
UnchangedSize: 1024 * 4,
},
{
Name: "system/0",
UnchangedSize: 1024 * 7,
},
},
}
verifySnapshotDelta(t, s, s, expected)
}
func TestDeltaSnapshots_changeBlob(t *testing.T) {
source := newSnapshotBuilder(t).
File(1, fileRef{"a", "same"}).
File(10, fileRef{"b", "same"}).
File(100, fileRef{"b", "update"}).
Build()
target := newSnapshotBuilder(t).
File(1, fileRef{"a", "same"}).
File(10, fileRef{"b", "same"}).
IncrementMerkleRootEpoch().
File(200, fileRef{"b", "update"}).
Build()
expected := SnapshotDelta{
DownloadSize: 200,
DiscardSize: 100,
UnchangedSize: 11,
SourceSize: 111,
TargetSize: 211,
AddedBlobs: []DeltaBlobStats{
{
Merkle: target.Packages["b"].Files["update"],
Size: 200,
References: []PackageFileRef{
{
Name: "b",
Path: "update",
},
},
},
},
Packages: []DeltaPackageStats{
{
Name: "b",
DownloadSize: 200,
DiscardSize: 100,
UnchangedSize: 10,
},
{
Name: "a",
UnchangedSize: 1,
},
},
}.populatePackageAddedBlobs()
verifySnapshotDelta(t, source, target, expected)
}
func TestDeltaSnapshots_addBlobs(t *testing.T) {
source := newSnapshotBuilder(t).
File(1, fileRef{"a", "same"}).
File(10, fileRef{"b", "same"}).
Build()
target := newSnapshotBuilder(t).
File(1, fileRef{"a", "same"}).
File(10, fileRef{"b", "same"}).
File(100, fileRef{"a", "new1"}).
File(1000, fileRef{"b", "new2"}).
Build()
expected := SnapshotDelta{
DownloadSize: 1100,
DiscardSize: 0,
UnchangedSize: 11,
SourceSize: 11,
TargetSize: 1111,
AddedBlobs: []DeltaBlobStats{
{
Merkle: target.Packages["b"].Files["new2"],
Size: 1000,
References: []PackageFileRef{
{
Name: "b",
Path: "new2",
},
},
},
{
Merkle: target.Packages["a"].Files["new1"],
Size: 100,
References: []PackageFileRef{
{
Name: "a",
Path: "new1",
},
},
},
},
Packages: []DeltaPackageStats{
{
Name: "b",
DownloadSize: 1000,
DiscardSize: 0,
UnchangedSize: 10,
},
{
Name: "a",
DownloadSize: 100,
DiscardSize: 0,
UnchangedSize: 1,
},
},
}.populatePackageAddedBlobs()
verifySnapshotDelta(t, source, target, expected)
}
func TestDeltaSnapshots_removeBlobs(t *testing.T) {
source := newSnapshotBuilder(t).
File(1, fileRef{"a", "same"}).
File(10, fileRef{"b", "same"}).
File(100, fileRef{"a", "new1"}).
File(1000, fileRef{"b", "new2"}).
Build()
target := newSnapshotBuilder(t).
File(1, fileRef{"a", "same"}).
File(10, fileRef{"b", "same"}).
Build()
expected := SnapshotDelta{
DownloadSize: 0,
DiscardSize: 1100,
UnchangedSize: 11,
SourceSize: 1111,
TargetSize: 11,
Packages: []DeltaPackageStats{
{
Name: "a",
DiscardSize: 100,
UnchangedSize: 1,
},
{
Name: "b",
DiscardSize: 1000,
UnchangedSize: 10,
},
},
}.populatePackageAddedBlobs()
verifySnapshotDelta(t, source, target, expected)
}
func TestDeltaSnapshots_updateAliasedBetweenPackages(t *testing.T) {
source := newSnapshotBuilder(t).
File(1,
fileRef{"a", "blob1"},
fileRef{"b", "blob2"},
).
Build()
target := newSnapshotBuilder(t).
IncrementMerkleRootEpoch().
File(10,
fileRef{"a", "blob1"},
fileRef{"b", "blob2"},
).
Build()
expected := SnapshotDelta{
DownloadSize: 10,
DiscardSize: 1,
UnchangedSize: 0,
SourceSize: 1,
TargetSize: 10,
AddedBlobs: []DeltaBlobStats{
{
Merkle: target.Packages["a"].Files["blob1"],
Size: 10,
References: []PackageFileRef{
{
Name: "a",
Path: "blob1",
},
{
Name: "b",
Path: "blob2",
},
},
},
},
Packages: []DeltaPackageStats{
{
Name: "a",
DownloadSize: 10,
DiscardSize: 1,
UnchangedSize: 0,
},
{
Name: "b",
DownloadSize: 10,
DiscardSize: 1,
UnchangedSize: 0,
},
},
}.populatePackageAddedBlobs()
verifySnapshotDelta(t, source, target, expected)
}
func TestDeltaSnapshots_updateAliasedWithinPackage(t *testing.T) {
source := newSnapshotBuilder(t).
File(1000, fileRef{"a", "static"}).
File(1,
fileRef{"a", "file"},
fileRef{"a", "samefile"},
).
Build()
target := newSnapshotBuilder(t).
File(1000, fileRef{"a", "static"}).
IncrementMerkleRootEpoch().
File(10,
fileRef{"a", "file"},
fileRef{"a", "samefile"},
).
Build()
expected := SnapshotDelta{
DownloadSize: 10,
DiscardSize: 1,
UnchangedSize: 1000,
SourceSize: 1001,
TargetSize: 1010,
AddedBlobs: []DeltaBlobStats{
{
Merkle: target.Packages["a"].Files["file"],
Size: 10,
References: []PackageFileRef{
{
Name: "a",
Path: "file",
},
{
Name: "a",
Path: "samefile",
},
},
},
},
Packages: []DeltaPackageStats{
{
Name: "a",
DownloadSize: 10,
DiscardSize: 1,
UnchangedSize: 1000,
},
},
}.populatePackageAddedBlobs()
verifySnapshotDelta(t, source, target, expected)
}
func TestDeltaSnapshots_addRemoveAliased(t *testing.T) {
source := newSnapshotBuilder(t).
File(1, fileRef{"a", "remove"}).
File(3, fileRef{"b", "remove"}).
Build()
target := newSnapshotBuilder(t).
IncrementMerkleRootEpoch().
File(10,
fileRef{"a", "add"},
fileRef{"b", "add"},
).
Build()
expected := SnapshotDelta{
DownloadSize: 10,
DiscardSize: 4,
UnchangedSize: 0,
SourceSize: 4,
TargetSize: 10,
AddedBlobs: []DeltaBlobStats{
{
Merkle: target.Packages["a"].Files["add"],
Size: 10,
References: []PackageFileRef{
{
Name: "a",
Path: "add",
},
{
Name: "b",
Path: "add",
},
},
},
},
Packages: []DeltaPackageStats{
{
Name: "a",
DownloadSize: 10,
DiscardSize: 1,
UnchangedSize: 0,
},
{
Name: "b",
DownloadSize: 10,
DiscardSize: 3,
UnchangedSize: 0,
},
},
}.populatePackageAddedBlobs()
verifySnapshotDelta(t, source, target, expected)
}
func TestDeltaSnapshots_addPackage(t *testing.T) {
source := newSnapshotBuilder(t).
File(1, fileRef{"a", "shared"}).
File(10, fileRef{"a", "unique_a"}).
Build()
target := newSnapshotBuilder(t).
File(1,
fileRef{"a", "shared"},
fileRef{"b", "shared"},
).
File(10, fileRef{"a", "unique_a"}).
File(100, fileRef{"b", "unique_b"}).
Build()
expected := SnapshotDelta{
DownloadSize: 100,
DiscardSize: 0,
UnchangedSize: 11,
SourceSize: 11,
TargetSize: 111,
AddedBlobs: []DeltaBlobStats{
{
Merkle: target.Packages["b"].Files["unique_b"],
Size: 100,
References: []PackageFileRef{
{
Name: "b",
Path: "unique_b",
},
},
},
},
Packages: []DeltaPackageStats{
{
Name: "b",
DownloadSize: 100,
DiscardSize: 0,
UnchangedSize: 1,
},
{
Name: "a",
DownloadSize: 0,
DiscardSize: 0,
UnchangedSize: 11,
},
},
}.populatePackageAddedBlobs()
verifySnapshotDelta(t, source, target, expected)
}
func TestDeltaSnapshots_removePackage(t *testing.T) {
source := newSnapshotBuilder(t).
File(1,
fileRef{"a", "shared"},
fileRef{"b", "shared"},
).
File(10, fileRef{"a", "unique_a"}).
File(100, fileRef{"b", "unique_b"}).
Build()
target := newSnapshotBuilder(t).
File(1, fileRef{"a", "shared"}).
File(10, fileRef{"a", "unique_a"}).
Build()
expected := SnapshotDelta{
DownloadSize: 0,
DiscardSize: 100,
UnchangedSize: 11,
SourceSize: 111,
TargetSize: 11,
Packages: []DeltaPackageStats{
{
Name: "a",
DownloadSize: 0,
DiscardSize: 0,
UnchangedSize: 11,
},
// SnapshotDelta.Packages does not contain
// DeltaPackageStats entries for packages present only
// in source.
// DeltaPackageStats{
// Name: "b",
// DownloadSize: 0,
// DiscardSize: 100,
// UnchangedSize: 1,
// },
},
}.populatePackageAddedBlobs()
verifySnapshotDelta(t, source, target, expected)
}