blob: 9cbdf4f17dc8e77402969532eee0f3f02eb75ccb [file]
// Copyright 2016 The LUCI Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package main
import (
"fmt"
"io"
"io/ioutil"
"reflect"
"strconv"
"strings"
"testing"
"testing/iotest"
"github.com/kr/pretty"
"github.com/luci/luci-go/common/isolated"
)
func fakeOsOpen(name string) (io.ReadCloser, error) {
var r io.Reader
switch {
case name == "/err/open":
return nil, fmt.Errorf("failed to open %q", name)
case name == "/err/read":
r = iotest.TimeoutReader(strings.NewReader("sample-file"))
case strings.HasPrefix(name, "/size/"):
size, _ := strconv.Atoi(strings.TrimPrefix(name, "/size/"))
r = strings.NewReader(strings.Repeat("x", size))
default:
r = strings.NewReader(fmt.Sprintf("I am the file with name %q", name))
}
return ioutil.NopCloser(r), nil
}
func TestItemBundle(t *testing.T) {
oldOpen := osOpen
osOpen = fakeOsOpen
defer func() { osOpen = oldOpen }()
// All tests use an archive threshold of 5000 bytes.
const threshold = 5000
testCases := []struct {
desc string
sizes []int64
wantBundles []int // The number of items in each bundle.
wantDigests []isolated.HexDigest // The hash of each bundle's tar.
wantSize []int // The size of each bundle's tar.
}{
{
desc: "one bundle",
sizes: []int64{100, 200, 300},
wantBundles: []int{3},
wantSize: []int{1024 + 1024 + 1024 + 1024},
wantDigests: []isolated.HexDigest{
"003afa69f9b267a2cf267cc5ff715968cb05354b",
},
},
{
desc: "multi bundle",
sizes: []int64{100, 200, 300, 400, 500, 2000},
wantBundles: []int{3, 2, 1},
wantSize: []int{
1024 + 1024 + 1024 + 1024,
1024 + 1024 + 1024,
1024 + 2560,
},
wantDigests: []isolated.HexDigest{
"003afa69f9b267a2cf267cc5ff715968cb05354b",
"7f8cedcc12dc7ddcbe0849a46628b2748f5e84aa",
"e4a390dbfa081e564fe61368bac77bb9ef30a4e7",
},
},
{
desc: "file below boundary",
sizes: []int64{511},
wantBundles: []int{1},
wantSize: []int{2048},
wantDigests: []isolated.HexDigest{
"6103d5cc5fc494f9490107d61a952c6ce1e48be6",
},
},
{
desc: "file on boundary",
sizes: []int64{512},
wantBundles: []int{1},
wantSize: []int{2048},
wantDigests: []isolated.HexDigest{
"8c63e33b6264ade5f62ab62b322de8d8ac5eeb8b",
},
},
{
desc: "all items over threshold",
sizes: []int64{5000, 6000, 4500},
wantBundles: []int{1, 1, 1},
wantSize: []int{6656, 7680, 6144},
wantDigests: []isolated.HexDigest{
"2cd4cb7a965d9ca34cbb692e3885f7074f28a4a4",
"925b04df322c98d013b470727df5644644d93e70",
"1f576aa4d44aea793c751bf03de2d513a2dd65eb",
},
},
}
for _, tc := range testCases {
// Construct the input items and expected output.
var items []*Item
for _, size := range tc.sizes {
items = append(items, &Item{
RelPath: fmt.Sprintf("./%d", size),
Path: fmt.Sprintf("/size/%d", size),
Size: size,
})
}
var wantBundles []*ItemBundle
temp := items
for _, bSize := range tc.wantBundles {
bundle := &ItemBundle{
Items: temp[:bSize],
}
wantBundles = append(wantBundles, bundle)
temp = temp[bSize:]
for _, item := range bundle.Items {
bundle.ItemSize += item.Size
}
}
bundles := ShardItems(items, threshold)
if !reflect.DeepEqual(bundles, wantBundles) {
t.Errorf("%s:\ngot %s\nwant %s", tc.desc, pretty.Sprint(bundles), pretty.Sprint(wantBundles))
}
for i, bundle := range bundles {
digest, size, err := bundle.Digest()
if err != nil {
t.Errorf("%s: bundle[%d].Digest gave err %v, want nil", tc.desc, i, err)
continue
}
if digest != tc.wantDigests[i] {
t.Errorf("%s: bundle[%d].Digest() hash = %q, want %q", tc.desc, i, digest, tc.wantDigests[i])
}
if size != int64(tc.wantSize[i]) {
t.Errorf("%s: bundle[%d].Digest() size = %d, want %d", tc.desc, i, size, tc.wantSize[i])
}
rc, err := bundle.Contents()
if err != nil {
t.Errorf("%s: bundle[%d].Contents gave err %v, want nil", tc.desc, i, err)
continue
}
tar, err := ioutil.ReadAll(rc)
rc.Close()
if err != nil {
t.Errorf("%s: reading bundle[%d].Contents gave err %v, want nil", tc.desc, i, err)
}
if got := len(tar); got != tc.wantSize[i] {
t.Errorf("%s: bundle[%d].Contents had len %d, want %d", tc.desc, i, got, tc.wantSize[i])
}
}
}
}
func TestItemBundle_Errors(t *testing.T) {
oldOpen := osOpen
osOpen = fakeOsOpen
defer func() { osOpen = oldOpen }()
testItems := []*Item{
{
// File that fails to open.
RelPath: "./open",
Path: "/err/open",
Size: 123,
},
{
// File that fails to read.
RelPath: "./read",
Path: "/err/read",
Size: 123,
},
}
bundles := ShardItems(testItems, 0)
if len(bundles) != 2 {
t.Errorf("len(bundles) = %d, want 2", len(bundles))
}
for _, bundle := range bundles {
if _, _, err := bundle.Digest(); err == nil {
t.Errorf("Path %q, bundle.Digest gave nil error; want some error", bundle.Items[0].Path)
}
rc, err := bundle.Contents()
if err != nil {
t.Errorf("Path %q, bundle.Contents gave error %v; want nil error", bundle.Items[0].Path, err)
continue
}
_, err = ioutil.ReadAll(rc)
rc.Close()
if err == nil {
t.Errorf("Path %q, reading contents gave nil error, want some error", bundle.Items[0].Path)
}
}
}