mirror of
https://github.com/GoogleContainerTools/kaniko
synced 2026-10-04 18:55:44 +02:00
* Extend .dockerignore integration test with copies in later stages .dockerignore should continue to apply when copying from the build context in later stages, but it currently doesn't * Replace excluded global with passed along FileContext struct This new FileContext struct allows much cleaner handling of context specific file exclusions. The global excluded file state is no longer needed. Additionally this also fixes the issue where excluded files aren't being applied for build context copies in later build stages.
1445 lines
32 KiB
Go
1445 lines
32 KiB
Go
/*
|
|
Copyright 2018 Google LLC
|
|
|
|
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 util
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
|
"github.com/GoogleContainerTools/kaniko/pkg/mocks/go-containerregistry/mockv1"
|
|
"github.com/GoogleContainerTools/kaniko/testutil"
|
|
"github.com/golang/mock/gomock"
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"github.com/google/go-containerregistry/pkg/v1/types"
|
|
)
|
|
|
|
func Test_DetectFilesystemSkiplist(t *testing.T) {
|
|
testDir, err := ioutil.TempDir("", "")
|
|
if err != nil {
|
|
t.Fatalf("Error creating tempdir: %s", err)
|
|
}
|
|
fileContents := `
|
|
228 122 0:90 / / rw,relatime - aufs none rw,si=f8e2406af90782bc,dio,dirperm1
|
|
229 228 0:98 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
|
|
230 228 0:99 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
|
|
231 230 0:100 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
|
|
232 228 0:101 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro`
|
|
|
|
path := filepath.Join(testDir, "mountinfo")
|
|
if err := os.MkdirAll(filepath.Dir(path), 0750); err != nil {
|
|
t.Fatalf("Error creating tempdir: %s", err)
|
|
}
|
|
if err := ioutil.WriteFile(path, []byte(fileContents), 0644); err != nil {
|
|
t.Fatalf("Error writing file contents to %s: %s", path, err)
|
|
}
|
|
|
|
err = DetectFilesystemIgnoreList(path)
|
|
expectedSkiplist := []IgnoreListEntry{
|
|
{"/kaniko", false},
|
|
{"/proc", false},
|
|
{"/dev", false},
|
|
{"/dev/pts", false},
|
|
{"/sys", false},
|
|
{"/etc/mtab", false},
|
|
{"/tmp/apt-key-gpghome", true},
|
|
}
|
|
actualSkiplist := ignorelist
|
|
sort.Slice(actualSkiplist, func(i, j int) bool {
|
|
return actualSkiplist[i].Path < actualSkiplist[j].Path
|
|
})
|
|
sort.Slice(expectedSkiplist, func(i, j int) bool {
|
|
return expectedSkiplist[i].Path < expectedSkiplist[j].Path
|
|
})
|
|
testutil.CheckErrorAndDeepEqual(t, false, err, expectedSkiplist, actualSkiplist)
|
|
}
|
|
|
|
func Test_AddToIgnoreList(t *testing.T) {
|
|
AddToIgnoreList(IgnoreListEntry{
|
|
Path: "/tmp",
|
|
PrefixMatchOnly: false,
|
|
})
|
|
|
|
if !CheckIgnoreList("/tmp") {
|
|
t.Errorf("CheckIgnoreList() = %v, want %v", false, true)
|
|
}
|
|
}
|
|
|
|
var tests = []struct {
|
|
files map[string]string
|
|
directory string
|
|
expectedFiles []string
|
|
}{
|
|
{
|
|
files: map[string]string{
|
|
"/workspace/foo/a": "baz1",
|
|
"/workspace/foo/b": "baz2",
|
|
"/kaniko/file": "file",
|
|
},
|
|
directory: "/workspace/foo/",
|
|
expectedFiles: []string{
|
|
"workspace/foo/a",
|
|
"workspace/foo/b",
|
|
"workspace/foo",
|
|
},
|
|
},
|
|
{
|
|
files: map[string]string{
|
|
"/workspace/foo/a": "baz1",
|
|
},
|
|
directory: "/workspace/foo/a",
|
|
expectedFiles: []string{
|
|
"workspace/foo/a",
|
|
},
|
|
},
|
|
{
|
|
files: map[string]string{
|
|
"/workspace/foo/a": "baz1",
|
|
"/workspace/foo/b": "baz2",
|
|
"/workspace/baz": "hey",
|
|
"/kaniko/file": "file",
|
|
},
|
|
directory: "/workspace",
|
|
expectedFiles: []string{
|
|
"workspace/foo/a",
|
|
"workspace/foo/b",
|
|
"workspace/baz",
|
|
"workspace",
|
|
"workspace/foo",
|
|
},
|
|
},
|
|
{
|
|
files: map[string]string{
|
|
"/workspace/foo/a": "baz1",
|
|
"/workspace/foo/b": "baz2",
|
|
},
|
|
directory: "",
|
|
expectedFiles: []string{
|
|
"workspace/foo/a",
|
|
"workspace/foo/b",
|
|
"workspace",
|
|
"workspace/foo",
|
|
".",
|
|
},
|
|
},
|
|
}
|
|
|
|
func Test_RelativeFiles(t *testing.T) {
|
|
for _, test := range tests {
|
|
testDir, err := ioutil.TempDir("", "")
|
|
if err != nil {
|
|
t.Fatalf("err setting up temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(testDir)
|
|
if err := testutil.SetupFiles(testDir, test.files); err != nil {
|
|
t.Fatalf("err setting up files: %v", err)
|
|
}
|
|
actualFiles, err := RelativeFiles(test.directory, testDir)
|
|
sort.Strings(actualFiles)
|
|
sort.Strings(test.expectedFiles)
|
|
testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFiles, actualFiles)
|
|
}
|
|
}
|
|
|
|
func Test_ParentDirectories(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
rootDir string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "regular path",
|
|
path: "/path/to/dir",
|
|
rootDir: "/",
|
|
expected: []string{
|
|
"/",
|
|
"/path",
|
|
"/path/to",
|
|
},
|
|
},
|
|
{
|
|
name: "current directory",
|
|
path: ".",
|
|
rootDir: "/",
|
|
expected: []string{
|
|
"/",
|
|
},
|
|
},
|
|
{
|
|
name: "non / root directory",
|
|
path: "/tmp/kaniko/test/another/dir",
|
|
rootDir: "/tmp/kaniko/",
|
|
expected: []string{
|
|
"/tmp/kaniko",
|
|
"/tmp/kaniko/test",
|
|
"/tmp/kaniko/test/another",
|
|
},
|
|
},
|
|
{
|
|
name: "non / root director same path",
|
|
path: "/tmp/123",
|
|
rootDir: "/tmp/123",
|
|
expected: []string{
|
|
"/tmp/123",
|
|
},
|
|
},
|
|
{
|
|
name: "non / root directory path",
|
|
path: "/tmp/120162240/kaniko",
|
|
rootDir: "/tmp/120162240",
|
|
expected: []string{
|
|
"/tmp/120162240",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
original := config.RootDir
|
|
defer func() { config.RootDir = original }()
|
|
config.RootDir = tt.rootDir
|
|
actual := ParentDirectories(tt.path)
|
|
|
|
testutil.CheckErrorAndDeepEqual(t, false, nil, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_ParentDirectoriesWithoutLeadingSlash(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "regular path",
|
|
path: "/path/to/dir",
|
|
expected: []string{
|
|
"/",
|
|
"path",
|
|
"path/to",
|
|
},
|
|
},
|
|
{
|
|
name: "current directory",
|
|
path: ".",
|
|
expected: []string{
|
|
"/",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
actual := ParentDirectoriesWithoutLeadingSlash(tt.path)
|
|
testutil.CheckErrorAndDeepEqual(t, false, nil, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_CheckIgnoreList(t *testing.T) {
|
|
type args struct {
|
|
path string
|
|
ignorelist []IgnoreListEntry
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want bool
|
|
}{
|
|
{
|
|
name: "file ignored",
|
|
args: args{
|
|
path: "/foo",
|
|
ignorelist: []IgnoreListEntry{{"/foo", false}},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "directory ignored",
|
|
args: args{
|
|
path: "/foo/bar",
|
|
ignorelist: []IgnoreListEntry{{"/foo", false}},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "grandparent ignored",
|
|
args: args{
|
|
path: "/foo/bar/baz",
|
|
ignorelist: []IgnoreListEntry{{"/foo", false}},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "sibling ignored",
|
|
args: args{
|
|
path: "/foo/bar/baz",
|
|
ignorelist: []IgnoreListEntry{{"/foo/bat", false}},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "prefix match only ",
|
|
args: args{
|
|
path: "/tmp/apt-key-gpghome.xft/gpg.key",
|
|
ignorelist: []IgnoreListEntry{{"/tmp/apt-key-gpghome.*", true}},
|
|
},
|
|
want: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
original := ignorelist
|
|
defer func() {
|
|
ignorelist = original
|
|
}()
|
|
ignorelist = tt.args.ignorelist
|
|
got := CheckIgnoreList(tt.args.path)
|
|
if got != tt.want {
|
|
t.Errorf("CheckIgnoreList() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHasFilepathPrefix(t *testing.T) {
|
|
type args struct {
|
|
path string
|
|
prefix string
|
|
prefixMatchOnly bool
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want bool
|
|
}{
|
|
{
|
|
name: "parent",
|
|
args: args{
|
|
path: "/foo/bar",
|
|
prefix: "/foo",
|
|
prefixMatchOnly: false,
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "nested parent",
|
|
args: args{
|
|
path: "/foo/bar/baz",
|
|
prefix: "/foo/bar",
|
|
prefixMatchOnly: false,
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "sibling",
|
|
args: args{
|
|
path: "/foo/bar",
|
|
prefix: "/bar",
|
|
prefixMatchOnly: false,
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "nested sibling",
|
|
args: args{
|
|
path: "/foo/bar/baz",
|
|
prefix: "/foo/bar",
|
|
prefixMatchOnly: false,
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "name prefix",
|
|
args: args{
|
|
path: "/foo2/bar",
|
|
prefix: "/foo",
|
|
prefixMatchOnly: false,
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "prefix match only (volume)",
|
|
args: args{
|
|
path: "/foo",
|
|
prefix: "/foo",
|
|
prefixMatchOnly: true,
|
|
},
|
|
want: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := HasFilepathPrefix(tt.args.path, tt.args.prefix, tt.args.prefixMatchOnly); got != tt.want {
|
|
t.Errorf("HasFilepathPrefix() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkHasFilepathPrefix(b *testing.B) {
|
|
tests := []struct {
|
|
path string
|
|
prefix string
|
|
prefixMatchOnly bool
|
|
}{
|
|
{
|
|
path: "/foo/bar",
|
|
prefix: "/foo",
|
|
prefixMatchOnly: true,
|
|
},
|
|
{
|
|
path: "/foo/bar/baz",
|
|
prefix: "/foo",
|
|
prefixMatchOnly: true,
|
|
},
|
|
{
|
|
path: "/foo/bar/baz/foo",
|
|
prefix: "/foo",
|
|
prefixMatchOnly: true,
|
|
},
|
|
{
|
|
path: "/foo/bar/baz/foo/foobar",
|
|
prefix: "/foo",
|
|
prefixMatchOnly: true,
|
|
},
|
|
{
|
|
path: "/foo/bar",
|
|
prefix: "/foo/bar",
|
|
prefixMatchOnly: true,
|
|
},
|
|
{
|
|
path: "/foo/bar/baz",
|
|
prefix: "/foo/bar",
|
|
prefixMatchOnly: true,
|
|
},
|
|
{
|
|
path: "/foo/bar/baz/foo",
|
|
prefix: "/foo/bar",
|
|
prefixMatchOnly: true,
|
|
},
|
|
{
|
|
path: "/foo/bar/baz/foo/foobar",
|
|
prefix: "/foo/bar",
|
|
prefixMatchOnly: true,
|
|
},
|
|
{
|
|
path: "/foo/bar",
|
|
prefix: "/foo/bar/baz",
|
|
prefixMatchOnly: true,
|
|
},
|
|
{
|
|
path: "/foo/bar/baz",
|
|
prefix: "/foo/bar/baz",
|
|
prefixMatchOnly: true,
|
|
},
|
|
{
|
|
path: "/foo/bar/baz/foo",
|
|
prefix: "/foo/bar/baz",
|
|
prefixMatchOnly: true,
|
|
},
|
|
{
|
|
path: "/foo/bar/baz/foo/foobar",
|
|
prefix: "/foo/bar/baz",
|
|
prefixMatchOnly: true,
|
|
},
|
|
}
|
|
for _, ts := range tests {
|
|
name := fmt.Sprint("PathDepth=", strings.Count(ts.path, "/"), ",PrefixDepth=", strings.Count(ts.prefix, "/"))
|
|
b.Run(name, func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
HasFilepathPrefix(ts.path, ts.prefix, ts.prefixMatchOnly)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type checker func(root string, t *testing.T)
|
|
|
|
func fileExists(p string) checker {
|
|
return func(root string, t *testing.T) {
|
|
_, err := os.Stat(filepath.Join(root, p))
|
|
if err != nil {
|
|
t.Fatalf("File %s does not exist", filepath.Join(root, p))
|
|
}
|
|
}
|
|
}
|
|
|
|
func fileMatches(p string, c []byte) checker {
|
|
return func(root string, t *testing.T) {
|
|
actual, err := ioutil.ReadFile(filepath.Join(root, p))
|
|
if err != nil {
|
|
t.Fatalf("error reading file: %s", p)
|
|
}
|
|
if !reflect.DeepEqual(actual, c) {
|
|
t.Errorf("file contents do not match. %v!=%v", actual, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func timesMatch(p string, fTime time.Time) checker {
|
|
return func(root string, t *testing.T) {
|
|
fi, err := os.Stat(filepath.Join(root, p))
|
|
if err != nil {
|
|
t.Fatalf("error statting file %s", p)
|
|
}
|
|
|
|
if fi.ModTime().UTC() != fTime.UTC() {
|
|
t.Errorf("Expected modtime to equal %v but was %v", fTime, fi.ModTime())
|
|
}
|
|
}
|
|
}
|
|
|
|
func permissionsMatch(p string, perms os.FileMode) checker {
|
|
return func(root string, t *testing.T) {
|
|
fi, err := os.Stat(filepath.Join(root, p))
|
|
if err != nil {
|
|
t.Fatalf("error statting file %s", p)
|
|
}
|
|
if fi.Mode() != perms {
|
|
t.Errorf("Permissions do not match. %s != %s", fi.Mode(), perms)
|
|
}
|
|
}
|
|
}
|
|
|
|
func linkPointsTo(src, dst string) checker {
|
|
return func(root string, t *testing.T) {
|
|
link := filepath.Join(root, src)
|
|
got, err := os.Readlink(link)
|
|
if err != nil {
|
|
t.Fatalf("error reading link %s: %s", link, err)
|
|
}
|
|
if got != dst {
|
|
t.Errorf("link destination does not match: %s != %s", got, dst)
|
|
}
|
|
}
|
|
}
|
|
|
|
func filesAreHardlinks(first, second string) checker {
|
|
return func(root string, t *testing.T) {
|
|
fi1, err := os.Stat(filepath.Join(root, first))
|
|
if err != nil {
|
|
t.Fatalf("error getting file %s", first)
|
|
}
|
|
fi2, err := os.Stat(filepath.Join(root, second))
|
|
if err != nil {
|
|
t.Fatalf("error getting file %s", second)
|
|
}
|
|
stat1 := getSyscallStatT(fi1)
|
|
stat2 := getSyscallStatT(fi2)
|
|
if stat1.Ino != stat2.Ino {
|
|
t.Errorf("%s and %s aren't hardlinks as they dont' have the same inode", first, second)
|
|
}
|
|
}
|
|
}
|
|
|
|
func fileHeader(name string, contents string, mode int64, fTime time.Time) *tar.Header {
|
|
return &tar.Header{
|
|
Name: name,
|
|
Size: int64(len(contents)),
|
|
Mode: mode,
|
|
Typeflag: tar.TypeReg,
|
|
Uid: os.Getuid(),
|
|
Gid: os.Getgid(),
|
|
AccessTime: fTime,
|
|
ModTime: fTime,
|
|
}
|
|
}
|
|
|
|
func linkHeader(name, linkname string) *tar.Header {
|
|
return &tar.Header{
|
|
Name: name,
|
|
Size: 0,
|
|
Typeflag: tar.TypeSymlink,
|
|
Linkname: linkname,
|
|
}
|
|
}
|
|
|
|
func hardlinkHeader(name, linkname string) *tar.Header {
|
|
return &tar.Header{
|
|
Name: name,
|
|
Size: 0,
|
|
Typeflag: tar.TypeLink,
|
|
Linkname: linkname,
|
|
}
|
|
}
|
|
|
|
func dirHeader(name string, mode int64) *tar.Header {
|
|
return &tar.Header{
|
|
Name: name,
|
|
Size: 0,
|
|
Typeflag: tar.TypeDir,
|
|
Mode: mode,
|
|
Uid: os.Getuid(),
|
|
Gid: os.Getgid(),
|
|
}
|
|
}
|
|
|
|
func createUncompressedTar(fileContents map[string]string, tarFileName, testDir string) error {
|
|
if err := testutil.SetupFiles(testDir, fileContents); err != nil {
|
|
return err
|
|
}
|
|
tarFile, err := os.Create(filepath.Join(testDir, tarFileName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t := NewTar(tarFile)
|
|
defer t.Close()
|
|
for file := range fileContents {
|
|
filePath := filepath.Join(testDir, file)
|
|
if err := t.AddFileToTar(filePath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Test_unTar(t *testing.T) {
|
|
tcs := []struct {
|
|
name string
|
|
setupTarContents map[string]string
|
|
tarFileName string
|
|
destination string
|
|
expectedFileList []string
|
|
errorExpected bool
|
|
}{
|
|
{
|
|
name: "multfile tar",
|
|
setupTarContents: map[string]string{
|
|
"foo/file1": "hello World",
|
|
"bar/file1": "hello World",
|
|
"bar/file2": "hello World",
|
|
"file1": "hello World",
|
|
},
|
|
tarFileName: "test.tar",
|
|
destination: "/",
|
|
expectedFileList: []string{"foo/file1", "bar/file1", "bar/file2", "file1"},
|
|
errorExpected: false,
|
|
},
|
|
{
|
|
name: "empty tar",
|
|
setupTarContents: map[string]string{},
|
|
tarFileName: "test.tar",
|
|
destination: "/",
|
|
expectedFileList: nil,
|
|
errorExpected: false,
|
|
},
|
|
}
|
|
for _, tc := range tcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
testDir, err := ioutil.TempDir("", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(testDir)
|
|
if err := createUncompressedTar(tc.setupTarContents, tc.tarFileName, testDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
file, err := os.Open(filepath.Join(testDir, tc.tarFileName))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fileList, err := unTar(file, tc.destination)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// update expectedFileList to take into factor temp directory
|
|
for i, file := range tc.expectedFileList {
|
|
tc.expectedFileList[i] = filepath.Join(testDir, file)
|
|
}
|
|
// sort both slices to ensure objects are in the same order for deep equals
|
|
sort.Strings(tc.expectedFileList)
|
|
sort.Strings(fileList)
|
|
testutil.CheckErrorAndDeepEqual(t, tc.errorExpected, err, tc.expectedFileList, fileList)
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtractFile(t *testing.T) {
|
|
type tc struct {
|
|
name string
|
|
hdrs []*tar.Header
|
|
tmpdir string
|
|
contents []byte
|
|
checkers []checker
|
|
}
|
|
|
|
defaultTestTime, err := time.Parse(time.RFC3339, "1912-06-23T00:00:00Z")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tcs := []tc{
|
|
{
|
|
name: "normal file",
|
|
contents: []byte("helloworld"),
|
|
hdrs: []*tar.Header{fileHeader("./bar", "helloworld", 0644, defaultTestTime)},
|
|
checkers: []checker{
|
|
fileExists("/bar"),
|
|
fileMatches("/bar", []byte("helloworld")),
|
|
permissionsMatch("/bar", 0644),
|
|
timesMatch("/bar", defaultTestTime),
|
|
},
|
|
},
|
|
{
|
|
name: "normal file, directory does not exist",
|
|
contents: []byte("helloworld"),
|
|
hdrs: []*tar.Header{fileHeader("./foo/bar", "helloworld", 0644, defaultTestTime)},
|
|
checkers: []checker{
|
|
fileExists("/foo/bar"),
|
|
fileMatches("/foo/bar", []byte("helloworld")),
|
|
permissionsMatch("/foo/bar", 0644),
|
|
permissionsMatch("/foo", 0755|os.ModeDir),
|
|
},
|
|
},
|
|
{
|
|
name: "normal file, directory is created after",
|
|
contents: []byte("helloworld"),
|
|
hdrs: []*tar.Header{
|
|
fileHeader("./foo/bar", "helloworld", 0644, defaultTestTime),
|
|
dirHeader("./foo", 0722),
|
|
},
|
|
checkers: []checker{
|
|
fileExists("/foo/bar"),
|
|
fileMatches("/foo/bar", []byte("helloworld")),
|
|
permissionsMatch("/foo/bar", 0644),
|
|
permissionsMatch("/foo", 0722|os.ModeDir),
|
|
},
|
|
},
|
|
{
|
|
name: "symlink",
|
|
hdrs: []*tar.Header{linkHeader("./bar", "bar/bat")},
|
|
checkers: []checker{
|
|
linkPointsTo("/bar", "bar/bat"),
|
|
},
|
|
},
|
|
{
|
|
name: "symlink relative path",
|
|
hdrs: []*tar.Header{linkHeader("./bar", "./foo/bar/baz")},
|
|
checkers: []checker{
|
|
linkPointsTo("/bar", "./foo/bar/baz"),
|
|
},
|
|
},
|
|
{
|
|
name: "symlink parent does not exist",
|
|
hdrs: []*tar.Header{linkHeader("./foo/bar/baz", "../../bat")},
|
|
checkers: []checker{
|
|
linkPointsTo("/foo/bar/baz", "../../bat"),
|
|
},
|
|
},
|
|
{
|
|
name: "symlink parent does not exist 2",
|
|
hdrs: []*tar.Header{linkHeader("./foo/bar/baz", "../../bat")},
|
|
checkers: []checker{
|
|
linkPointsTo("/foo/bar/baz", "../../bat"),
|
|
permissionsMatch("/foo", 0755|os.ModeDir),
|
|
permissionsMatch("/foo/bar", 0755|os.ModeDir),
|
|
},
|
|
},
|
|
{
|
|
name: "hardlink",
|
|
tmpdir: "/tmp/hardlink",
|
|
hdrs: []*tar.Header{
|
|
fileHeader("/bin/gzip", "gzip-binary", 0751, defaultTestTime),
|
|
hardlinkHeader("/bin/uncompress", "/bin/gzip"),
|
|
},
|
|
checkers: []checker{
|
|
fileExists("/bin/gzip"),
|
|
filesAreHardlinks("/bin/uncompress", "/bin/gzip"),
|
|
},
|
|
},
|
|
{
|
|
name: "file with setuid bit",
|
|
contents: []byte("helloworld"),
|
|
hdrs: []*tar.Header{fileHeader("./bar", "helloworld", 04644, defaultTestTime)},
|
|
checkers: []checker{
|
|
fileExists("/bar"),
|
|
fileMatches("/bar", []byte("helloworld")),
|
|
permissionsMatch("/bar", 0644|os.ModeSetuid),
|
|
},
|
|
},
|
|
{
|
|
name: "dir with sticky bit",
|
|
contents: []byte("helloworld"),
|
|
hdrs: []*tar.Header{
|
|
dirHeader("./foo", 01755),
|
|
fileHeader("./foo/bar", "helloworld", 0644, defaultTestTime),
|
|
},
|
|
checkers: []checker{
|
|
fileExists("/foo/bar"),
|
|
fileMatches("/foo/bar", []byte("helloworld")),
|
|
permissionsMatch("/foo/bar", 0644),
|
|
permissionsMatch("/foo", 0755|os.ModeDir|os.ModeSticky),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tc := tc
|
|
t.Parallel()
|
|
r := ""
|
|
var err error
|
|
|
|
if tc.tmpdir != "" {
|
|
r = tc.tmpdir
|
|
} else {
|
|
r, err = ioutil.TempDir("", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
defer os.RemoveAll(r)
|
|
|
|
for _, hdr := range tc.hdrs {
|
|
if err := ExtractFile(r, hdr, bytes.NewReader(tc.contents)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, checker := range tc.checkers {
|
|
checker(r, t)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCopySymlink(t *testing.T) {
|
|
type tc struct {
|
|
name string
|
|
linkTarget string
|
|
dest string
|
|
beforeLink func(r string) error
|
|
}
|
|
|
|
tcs := []tc{{
|
|
name: "absolute symlink",
|
|
linkTarget: "/abs/dest",
|
|
}, {
|
|
name: "relative symlink",
|
|
linkTarget: "rel",
|
|
}, {
|
|
name: "symlink copy overwrites existing file",
|
|
linkTarget: "/abs/dest",
|
|
dest: "overwrite_me",
|
|
beforeLink: func(r string) error {
|
|
return ioutil.WriteFile(filepath.Join(r, "overwrite_me"), nil, 0644)
|
|
},
|
|
}}
|
|
|
|
for _, tc := range tcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tc := tc
|
|
t.Parallel()
|
|
r, err := ioutil.TempDir("", "")
|
|
os.MkdirAll(filepath.Join(r, filepath.Dir(tc.linkTarget)), 0777)
|
|
tc.linkTarget = filepath.Join(r, tc.linkTarget)
|
|
ioutil.WriteFile(tc.linkTarget, nil, 0644)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(r)
|
|
|
|
if tc.beforeLink != nil {
|
|
if err := tc.beforeLink(r); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
link := filepath.Join(r, "link")
|
|
dest := filepath.Join(r, "copy")
|
|
if tc.dest != "" {
|
|
dest = filepath.Join(r, tc.dest)
|
|
}
|
|
if err := os.Symlink(tc.linkTarget, link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := CopySymlink(link, dest, FileContext{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Lstat(dest); err != nil {
|
|
t.Fatalf("error reading link %s: %s", link, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_childDirInSkiplist(t *testing.T) {
|
|
type args struct {
|
|
path string
|
|
ignorelist []IgnoreListEntry
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want bool
|
|
}{
|
|
{
|
|
name: "not in ignorelist",
|
|
args: args{
|
|
path: "/foo",
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "child in ignorelist",
|
|
args: args{
|
|
path: "/foo",
|
|
ignorelist: []IgnoreListEntry{
|
|
{
|
|
Path: "/foo/bar",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
}
|
|
oldIgnoreList := ignorelist
|
|
defer func() {
|
|
ignorelist = oldIgnoreList
|
|
}()
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ignorelist = tt.args.ignorelist
|
|
if got := childDirInIgnoreList(tt.args.path); got != tt.want {
|
|
t.Errorf("childDirInIgnoreList() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_correctDockerignoreFileIsUsed(t *testing.T) {
|
|
type args struct {
|
|
dockerfilepath string
|
|
buildcontext string
|
|
excluded []string
|
|
included []string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
}{
|
|
{
|
|
name: "relative dockerfile used",
|
|
args: args{
|
|
dockerfilepath: "../../integration/dockerfiles/Dockerfile_dockerignore_relative",
|
|
buildcontext: "../../integration/",
|
|
excluded: []string{"ignore_relative/bar"},
|
|
included: []string{"ignore_relative/foo", "ignore/bar"},
|
|
},
|
|
},
|
|
{
|
|
name: "context dockerfile is used",
|
|
args: args{
|
|
dockerfilepath: "../../integration/dockerfiles/Dockerfile_test_dockerignore",
|
|
buildcontext: "../../integration/",
|
|
excluded: []string{"ignore/bar"},
|
|
included: []string{"ignore/foo", "ignore_relative/bar"},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
fileContext, err := NewFileContextFromDockerfile(tt.args.dockerfilepath, tt.args.buildcontext)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, excl := range tt.args.excluded {
|
|
t.Run(tt.name+" to exclude "+excl, func(t *testing.T) {
|
|
if !fileContext.ExcludesFile(excl) {
|
|
t.Errorf("'%v' not excluded", excl)
|
|
}
|
|
})
|
|
}
|
|
for _, incl := range tt.args.included {
|
|
t.Run(tt.name+" to include "+incl, func(t *testing.T) {
|
|
if fileContext.ExcludesFile(incl) {
|
|
t.Errorf("'%v' not included", incl)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_CopyFile_skips_self(t *testing.T) {
|
|
t.Parallel()
|
|
tempDir, err := ioutil.TempDir("", "kaniko_test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tempFile := filepath.Join(tempDir, "foo")
|
|
expected := "bar"
|
|
|
|
if err := ioutil.WriteFile(
|
|
tempFile,
|
|
[]byte(expected),
|
|
0755,
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ignored, err := CopyFile(tempFile, tempFile, FileContext{}, DoNotChangeUID, DoNotChangeGID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if ignored {
|
|
t.Fatal("expected file to NOT be ignored")
|
|
}
|
|
|
|
// Ensure file has expected contents
|
|
actualData, err := ioutil.ReadFile(tempFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if actual := string(actualData); actual != expected {
|
|
t.Fatalf("expected file contents to be %q, but got %q", expected, actual)
|
|
}
|
|
}
|
|
|
|
func fakeExtract(dest string, hdr *tar.Header, tr io.Reader) error {
|
|
return nil
|
|
}
|
|
|
|
func Test_GetFSFromLayers_with_whiteouts_include_whiteout_enabled(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
|
|
root, err := ioutil.TempDir("", "layers-test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Write a whiteout path
|
|
d1 := []byte("Hello World\n")
|
|
if err := ioutil.WriteFile(filepath.Join(root, "foobar"), d1, 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer os.Remove(root)
|
|
|
|
opts := []FSOpt{
|
|
// I'd rather use the real func (util.ExtractFile)
|
|
// but you have to be root to chown
|
|
ExtractFunc(fakeExtract),
|
|
IncludeWhiteout(),
|
|
}
|
|
|
|
expectErr := false
|
|
|
|
f := func(expectedFiles []string, tw *tar.Writer) {
|
|
for _, f := range expectedFiles {
|
|
f := strings.TrimPrefix(strings.TrimPrefix(f, root), "/")
|
|
|
|
hdr := &tar.Header{
|
|
Name: f,
|
|
Mode: 0644,
|
|
Size: int64(len("Hello World\n")),
|
|
}
|
|
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := tw.Write([]byte("Hello World\n")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if err := tw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
expectedFiles := []string{
|
|
filepath.Join(root, "foobar"),
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
tw := tar.NewWriter(buf)
|
|
|
|
f(expectedFiles, tw)
|
|
|
|
mockLayer := mockv1.NewMockLayer(ctrl)
|
|
mockLayer.EXPECT().MediaType().Return(types.OCILayer, nil)
|
|
|
|
rc := ioutil.NopCloser(buf)
|
|
mockLayer.EXPECT().Uncompressed().Return(rc, nil)
|
|
|
|
secondLayerFiles := []string{
|
|
filepath.Join(root, ".wh.foobar"),
|
|
}
|
|
|
|
buf = new(bytes.Buffer)
|
|
tw = tar.NewWriter(buf)
|
|
|
|
f(secondLayerFiles, tw)
|
|
|
|
mockLayer2 := mockv1.NewMockLayer(ctrl)
|
|
mockLayer2.EXPECT().MediaType().Return(types.OCILayer, nil)
|
|
|
|
rc = ioutil.NopCloser(buf)
|
|
mockLayer2.EXPECT().Uncompressed().Return(rc, nil)
|
|
|
|
layers := []v1.Layer{
|
|
mockLayer,
|
|
mockLayer2,
|
|
}
|
|
|
|
expectedFiles = append(expectedFiles, secondLayerFiles...)
|
|
|
|
actualFiles, err := GetFSFromLayers(root, layers, opts...)
|
|
|
|
assertGetFSFromLayers(
|
|
t,
|
|
actualFiles,
|
|
expectedFiles,
|
|
err,
|
|
expectErr,
|
|
)
|
|
// Make sure whiteout files are removed form the root.
|
|
_, err = os.Lstat(filepath.Join(root, "foobar"))
|
|
if err == nil || !os.IsNotExist(err) {
|
|
t.Errorf("expected whiteout foobar file to be deleted. However found it.")
|
|
}
|
|
}
|
|
|
|
func Test_GetFSFromLayers_with_whiteouts_include_whiteout_disabled(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
|
|
root, err := ioutil.TempDir("", "layers-test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Write a whiteout path
|
|
d1 := []byte("Hello World\n")
|
|
if err := ioutil.WriteFile(filepath.Join(root, "foobar"), d1, 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Remove(root)
|
|
|
|
opts := []FSOpt{
|
|
// I'd rather use the real func (util.ExtractFile)
|
|
// but you have to be root to chown
|
|
ExtractFunc(fakeExtract),
|
|
}
|
|
|
|
expectErr := false
|
|
|
|
f := func(expectedFiles []string, tw *tar.Writer) {
|
|
for _, f := range expectedFiles {
|
|
f := strings.TrimPrefix(strings.TrimPrefix(f, root), "/")
|
|
|
|
hdr := &tar.Header{
|
|
Name: f,
|
|
Mode: 0644,
|
|
Size: int64(len("Hello world\n")),
|
|
}
|
|
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := tw.Write([]byte("Hello world\n")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if err := tw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
expectedFiles := []string{
|
|
filepath.Join(root, "foobar"),
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
tw := tar.NewWriter(buf)
|
|
|
|
f(expectedFiles, tw)
|
|
|
|
mockLayer := mockv1.NewMockLayer(ctrl)
|
|
mockLayer.EXPECT().MediaType().Return(types.OCILayer, nil)
|
|
layerFiles := []string{
|
|
filepath.Join(root, "foobar"),
|
|
}
|
|
buf = new(bytes.Buffer)
|
|
tw = tar.NewWriter(buf)
|
|
|
|
f(layerFiles, tw)
|
|
|
|
rc := ioutil.NopCloser(buf)
|
|
mockLayer.EXPECT().Uncompressed().Return(rc, nil)
|
|
|
|
secondLayerFiles := []string{
|
|
filepath.Join(root, ".wh.foobar"),
|
|
}
|
|
|
|
buf = new(bytes.Buffer)
|
|
tw = tar.NewWriter(buf)
|
|
|
|
f(secondLayerFiles, tw)
|
|
|
|
mockLayer2 := mockv1.NewMockLayer(ctrl)
|
|
mockLayer2.EXPECT().MediaType().Return(types.OCILayer, nil)
|
|
|
|
rc = ioutil.NopCloser(buf)
|
|
mockLayer2.EXPECT().Uncompressed().Return(rc, nil)
|
|
|
|
layers := []v1.Layer{
|
|
mockLayer,
|
|
mockLayer2,
|
|
}
|
|
|
|
actualFiles, err := GetFSFromLayers(root, layers, opts...)
|
|
|
|
assertGetFSFromLayers(
|
|
t,
|
|
actualFiles,
|
|
expectedFiles,
|
|
err,
|
|
expectErr,
|
|
)
|
|
// Make sure whiteout files are removed form the root.
|
|
_, err = os.Lstat(filepath.Join(root, "foobar"))
|
|
if err == nil || !os.IsNotExist(err) {
|
|
t.Errorf("expected whiteout foobar file to be deleted. However found it.")
|
|
}
|
|
}
|
|
|
|
func Test_GetFSFromLayers(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
|
|
root, err := ioutil.TempDir("", "layers-test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Remove(root)
|
|
|
|
opts := []FSOpt{
|
|
// I'd rather use the real func (util.ExtractFile)
|
|
// but you have to be root to chown
|
|
ExtractFunc(fakeExtract),
|
|
}
|
|
|
|
expectErr := false
|
|
expectedFiles := []string{
|
|
filepath.Join(root, "foobar"),
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
tw := tar.NewWriter(buf)
|
|
|
|
for _, f := range expectedFiles {
|
|
f := strings.TrimPrefix(strings.TrimPrefix(f, root), "/")
|
|
|
|
hdr := &tar.Header{
|
|
Name: f,
|
|
Mode: 0644,
|
|
Size: int64(len("Hello world\n")),
|
|
}
|
|
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := tw.Write([]byte("Hello world\n")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if err := tw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mockLayer := mockv1.NewMockLayer(ctrl)
|
|
mockLayer.EXPECT().MediaType().Return(types.OCILayer, nil)
|
|
|
|
rc := ioutil.NopCloser(buf)
|
|
mockLayer.EXPECT().Uncompressed().Return(rc, nil)
|
|
|
|
layers := []v1.Layer{
|
|
mockLayer,
|
|
}
|
|
|
|
actualFiles, err := GetFSFromLayers(root, layers, opts...)
|
|
|
|
assertGetFSFromLayers(
|
|
t,
|
|
actualFiles,
|
|
expectedFiles,
|
|
err,
|
|
expectErr,
|
|
)
|
|
}
|
|
|
|
func assertGetFSFromLayers(
|
|
t *testing.T,
|
|
actualFiles []string,
|
|
expectedFiles []string,
|
|
err error,
|
|
expectErr bool,
|
|
) {
|
|
if !expectErr && err != nil {
|
|
t.Error(err)
|
|
t.FailNow()
|
|
} else if expectErr && err == nil {
|
|
t.Error("expected err to not be nil")
|
|
t.FailNow()
|
|
}
|
|
|
|
if len(actualFiles) != len(expectedFiles) {
|
|
t.Errorf("expected %s to equal %s", actualFiles, expectedFiles)
|
|
t.FailNow()
|
|
}
|
|
|
|
for i := range expectedFiles {
|
|
if actualFiles[i] != expectedFiles[i] {
|
|
t.Errorf("expected %s to equal %s", actualFiles[i], expectedFiles[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpdateSkiplist(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
skipVarRun bool
|
|
expected []IgnoreListEntry
|
|
}{
|
|
{
|
|
name: "var/run ignored",
|
|
skipVarRun: true,
|
|
expected: []IgnoreListEntry{
|
|
{
|
|
Path: "/kaniko",
|
|
PrefixMatchOnly: false,
|
|
},
|
|
{
|
|
Path: "/etc/mtab",
|
|
PrefixMatchOnly: false,
|
|
},
|
|
{
|
|
Path: "/var/run",
|
|
PrefixMatchOnly: false,
|
|
},
|
|
{
|
|
Path: "/tmp/apt-key-gpghome",
|
|
PrefixMatchOnly: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "var/run not ignored",
|
|
expected: []IgnoreListEntry{
|
|
{
|
|
Path: "/kaniko",
|
|
PrefixMatchOnly: false,
|
|
},
|
|
{
|
|
Path: "/etc/mtab",
|
|
PrefixMatchOnly: false,
|
|
},
|
|
{
|
|
Path: "/tmp/apt-key-gpghome",
|
|
PrefixMatchOnly: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
original := initialIgnoreList
|
|
defer func() { initialIgnoreList = original }()
|
|
UpdateInitialIgnoreList(tt.skipVarRun)
|
|
sort.Slice(tt.expected, func(i, j int) bool {
|
|
return tt.expected[i].Path < tt.expected[j].Path
|
|
})
|
|
sort.Slice(initialIgnoreList, func(i, j int) bool {
|
|
return initialIgnoreList[i].Path < initialIgnoreList[j].Path
|
|
})
|
|
testutil.CheckDeepEqual(t, tt.expected, initialIgnoreList)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_setFileTimes(t *testing.T) {
|
|
testDir, err := ioutil.TempDir("", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer os.RemoveAll(testDir)
|
|
|
|
p := filepath.Join(testDir, "foo.txt")
|
|
|
|
if err := ioutil.WriteFile(p, []byte("meow"), 0777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
type testcase struct {
|
|
desc string
|
|
path string
|
|
aTime time.Time
|
|
mTime time.Time
|
|
}
|
|
|
|
testCases := []testcase{
|
|
{
|
|
desc: "zero for mod and access",
|
|
path: p,
|
|
},
|
|
{
|
|
desc: "zero for mod",
|
|
path: p,
|
|
aTime: time.Now(),
|
|
},
|
|
{
|
|
desc: "zero for access",
|
|
path: p,
|
|
mTime: time.Now(),
|
|
},
|
|
{
|
|
desc: "both non-zero",
|
|
path: p,
|
|
mTime: time.Now(),
|
|
aTime: time.Now(),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
err := setFileTimes(tc.path, tc.aTime, tc.mTime)
|
|
if err != nil {
|
|
t.Errorf("expected err to be nil not %s", err)
|
|
}
|
|
})
|
|
}
|
|
}
|