blob: 4479750b227ec7d55b92a386c4aa198d36d58c52 [file] [log] [blame]
// Copyright 2017 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package archiver
import (
"errors"
"os"
"reflect"
"testing"
"go.chromium.org/luci/client/internal/common"
"go.chromium.org/luci/common/data/stringset"
)
// basicFileInfo implements some of os.FileInfo, and panics if unexpected parts
// of that interface are called.
type basicFileInfo struct {
size int64
mode os.FileMode
isDir bool
os.FileInfo
}
func (bfi basicFileInfo) Size() int64 { return bfi.size }
func (bfi basicFileInfo) Mode() os.FileMode { return bfi.mode }
func (bfi basicFileInfo) IsDir() bool { return bfi.isDir }
type file struct {
path string
bfi basicFileInfo
}
func symlink(path string, size int64) file {
return file{
path: path,
bfi: basicFileInfo{
size: size,
mode: os.ModeSymlink,
isDir: false,
},
}
}
func regularFile(path string, size int64) file {
return file{
path: path,
bfi: basicFileInfo{
size: size,
mode: 0,
isDir: false,
},
}
}
func directory(path string) file {
return file{
path: path,
bfi: basicFileInfo{
size: 0,
mode: os.ModeDir,
isDir: true,
},
}
}
var errBang = errors.New("bang")
// fsView constructs a FilesystemView with no blacklist.
func fsView(root string) common.FilesystemView {
fsView, err := common.NewFilesystemView("/rootDir", nil)
if err != nil {
// NewFilesystemView only fails due to bad blacklists. So this should never occur.
panic("unexpected failure to construct FilesytemView")
}
return fsView
}
func TestWalkFn(t *testing.T) {
type testCase struct {
name string
files []file
walkFnErr error
want partitionedDeps
}
testCases := []testCase{
{
name: "partitions files",
files: []file{
symlink("/rootDir/patha", 10e3),
regularFile("/rootDir/pathb", 100e3),
// The duplicate entry will be skipped.
regularFile("/rootDir/pathb", 100e3),
regularFile("/rootDir/pathc", 1000e3),
},
want: partitionedDeps{
links: itemGroup{
items: []*Item{
{
Path: "/rootDir/patha",
RelPath: "patha",
Mode: os.ModeSymlink,
Size: 10e3,
},
},
totalSize: 10e3,
},
filesToArchive: itemGroup{
items: []*Item{
{
Path: "/rootDir/pathb",
RelPath: "pathb",
Mode: 0,
Size: 100e3,
},
},
totalSize: 100e3,
},
indivFiles: itemGroup{
items: []*Item{
{
Path: "/rootDir/pathc",
RelPath: "pathc",
Mode: 0,
Size: 1000e3,
},
},
totalSize: 1000e3,
},
},
},
{
name: "handles zero-size files",
files: []file{
symlink("/rootDir/patha", 0),
regularFile("/rootDir/pathb", 0),
},
want: partitionedDeps{
links: itemGroup{
items: []*Item{
{
Path: "/rootDir/patha",
RelPath: "patha",
Mode: os.ModeSymlink,
Size: 0,
},
},
totalSize: 0,
},
filesToArchive: itemGroup{
items: []*Item{
{
Path: "/rootDir/pathb",
RelPath: "pathb",
Mode: 0,
Size: 0,
},
},
totalSize: 0,
},
},
},
{
name: "aggregates link sizes",
files: []file{
symlink("/rootDir/patha", 1),
symlink("/rootDir/pathb", 1<<1),
},
want: partitionedDeps{
links: itemGroup{
items: []*Item{
{
Path: "/rootDir/patha",
RelPath: "patha",
Mode: os.ModeSymlink,
Size: 1,
},
{
Path: "/rootDir/pathb",
RelPath: "pathb",
Mode: os.ModeSymlink,
Size: 1 << 1,
},
},
totalSize: 3,
},
},
},
{
name: "aggregates large file sizes",
files: []file{
regularFile("/rootDir/patha", 1024<<10),
regularFile("/rootDir/pathb", 1024<<11),
},
want: partitionedDeps{
indivFiles: itemGroup{
items: []*Item{
{
Path: "/rootDir/patha",
RelPath: "patha",
Mode: 0,
Size: 1024 << 10,
},
{
Path: "/rootDir/pathb",
RelPath: "pathb",
Mode: 0,
Size: 1024 << 11,
},
},
totalSize: (1024 << 10) + (1024 << 11),
},
},
},
{
name: "aggregates small file sizes",
files: []file{
regularFile("/rootDir/patha", 1024),
regularFile("/rootDir/pathb", 1024<<1),
},
want: partitionedDeps{
filesToArchive: itemGroup{
items: []*Item{
{
Path: "/rootDir/patha",
RelPath: "patha",
Mode: 0,
Size: 1024,
},
{
Path: "/rootDir/pathb",
RelPath: "pathb",
Mode: 0,
Size: 1024 << 1,
},
},
totalSize: 1024 + 2048,
},
},
},
}
TestCases:
for _, tc := range testCases {
pw := partitioningWalker{fsView: fsView("/rootDir"), seen: stringset.New(0)}
for _, f := range tc.files {
if err := pw.walkFn(f.path, f.bfi, tc.walkFnErr); err != nil {
t.Errorf("partitioning deps(%s): walkFn got err %v; want nil", tc.name, err)
continue TestCases
}
}
if got, want := pw.parts, tc.want; !reflect.DeepEqual(got, want) {
t.Errorf("partitioning deps(%s): got %#v; want %#v", tc.name, got, want)
}
}
}
func TestWalkFn_BadRelpath(t *testing.T) {
pw := partitioningWalker{fsView: fsView("/rootDir"), seen: stringset.New(0)}
f := regularFile("./patha", 1)
if err := pw.walkFn(f.path, f.bfi, nil); err == nil {
t.Errorf("testing bad relpath: walkFn returned nil err; want non-nil err")
}
}
func TestWalkFn_ReturnsErrorsUnchanged(t *testing.T) {
pw := partitioningWalker{fsView: fsView("/rootDir"), seen: stringset.New(0)}
f := regularFile("/rootDir/patha", 1)
if err := pw.walkFn(f.path, f.bfi, errBang); err != errBang {
t.Errorf("walkFn err: got: %v; want %v", err, errBang)
}
}
func TestWalkFn_DoesNothingForDirectories(t *testing.T) {
pw := partitioningWalker{fsView: fsView("/rootDir"), seen: stringset.New(0)}
f := directory("/rootDir/patha")
if err := pw.walkFn(f.path, f.bfi, nil); err != nil {
t.Errorf("walkFn err: got: %v; want nil", err)
}
}