resource/unarchive/unarchive_test.go
// Copyright © 2017 Asteris, 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 unarchive
import (
"archive/zip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/asteris-llc/converge/helpers/fakerenderer"
"github.com/asteris-llc/converge/resource"
"github.com/asteris-llc/converge/resource/file/fetch"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
// TestUnarchiveInterface tests that Unarchive is properly implemented
func TestUnarchiveInterface(t *testing.T) {
t.Parallel()
assert.Implements(t, (*resource.Task)(nil), new(Unarchive))
}
// TestCheck tests the cases Check handles
func TestCheck(t *testing.T) {
t.Parallel()
srcFile, err := ioutil.TempFile("", "unarchive_test.txt")
require.NoError(t, err)
defer os.Remove(srcFile.Name())
destInvalid, err := ioutil.TempFile("", "unarchive_test.txt")
require.NoError(t, err)
defer os.Remove(destInvalid.Name())
srcDir, err := ioutil.TempDir("", "unarchive_srcDir")
require.NoError(t, err)
defer os.RemoveAll(srcDir)
destDir, err := ioutil.TempDir("", "unarchive_destDir")
require.NoError(t, err)
defer os.RemoveAll(destDir)
tmpFetchDir, err := ioutil.TempDir("", "tmpFetchDir")
require.NoError(t, err)
defer os.RemoveAll(tmpFetchDir)
t.Run("error", func(t *testing.T) {
t.Run("diff error", func(t *testing.T) {
u := &Unarchive{
Source: srcFile.Name(),
Destination: destInvalid.Name(),
}
status, err := u.Check(context.Background(), fakerenderer.New())
assert.EqualError(t, err, fmt.Sprintf("invalid destination %q, must be directory", u.Destination))
assert.Equal(t, resource.StatusCantChange, status.StatusCode())
assert.True(t, status.HasChanges())
})
t.Run("fetch error", func(t *testing.T) {
u := &Unarchive{
Source: srcFile.Name(),
Destination: destDir,
HashType: string(HashMD5),
Hash: "notarealhashbutstringnonetheless",
Force: false,
fetchLoc: destInvalid.Name(),
}
u.fetch = fetch.Fetch{
Source: u.Source,
Destination: u.fetchLoc,
HashType: u.HashType,
Hash: u.Hash,
Unarchive: true,
}
status, err := u.Check(context.Background(), fakerenderer.New())
assert.EqualError(t, err, fmt.Sprintf("cannot attempt unarchive: fetch error: invalid destination %q for unarchiving, must be directory", u.fetch.Destination))
assert.Equal(t, resource.StatusCantChange, status.StatusCode())
assert.True(t, status.HasChanges())
})
})
t.Run("unarchive", func(t *testing.T) {
u := &Unarchive{
Source: srcFile.Name(),
Destination: "/tmp",
}
status, err := u.Check(context.Background(), fakerenderer.New())
assert.NoError(t, err)
assert.Equal(t, fmt.Sprintf("fetch and unarchive %q", u.Source), status.Messages()[0])
assert.Equal(t, u.Source, status.Diffs()["unarchive"].Original())
assert.Equal(t, u.Destination, status.Diffs()["unarchive"].Current())
assert.Equal(t, resource.StatusWillChange, status.StatusCode())
assert.True(t, status.HasChanges())
})
t.Run("context", func(t *testing.T) {
u := &Unarchive{}
ctx, cancel := context.WithCancel(context.Background())
cancel()
status, err := u.Check(ctx, fakerenderer.New())
assert.EqualError(t, err, "context canceled")
assert.Nil(t, status)
})
}
// TestApply tests the cases Apply handles
func TestApply(t *testing.T) {
t.Parallel()
srcDir, err := ioutil.TempDir("", "unarchive_srcDir")
require.NoError(t, err)
defer os.RemoveAll(srcDir)
srcFile, err := ioutil.TempFile("", "unarchive_file.txt")
require.NoError(t, err)
defer os.Remove(srcFile.Name())
destDir, err := ioutil.TempDir("", "unarchive_destDir")
require.NoError(t, err)
defer os.RemoveAll(destDir)
tmpFetchDir, err := ioutil.TempDir("", "tmpFetchDir")
require.NoError(t, err)
defer os.RemoveAll(tmpFetchDir)
t.Run("error", func(t *testing.T) {
t.Run("diff error", func(t *testing.T) {
u := &Unarchive{}
status, err := u.Apply(context.Background())
assert.EqualError(t, err, fmt.Sprintf("cannot unarchive: stat %s: no such file or directory", u.Source))
assert.Equal(t, resource.StatusCantChange, status.StatusCode())
assert.True(t, status.HasChanges())
})
t.Run("fetch error", func(t *testing.T) {
u := &Unarchive{
Source: srcFile.Name(),
Destination: destDir,
HashType: string(HashMD5),
Hash: "notarealhashbutstringnonetheless",
Force: false,
fetchLoc: tmpFetchDir,
}
u.fetch = fetch.Fetch{
Source: u.Source,
Destination: u.fetchLoc,
HashType: u.HashType,
Hash: u.Hash,
Unarchive: true,
}
status, err := u.Apply(context.Background())
assert.EqualError(t, err, "failed to fetch: invalid checksum: encoding/hex: invalid byte: U+006E 'n'")
assert.Equal(t, resource.StatusFatal, status.StatusCode())
assert.False(t, status.HasChanges())
})
t.Run("evaluateDuplicates error", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "destDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(destDir)
fileBDest, err := os.Create(destDir + "/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBDest.Name())
fileBFetch, err := os.Create("/tmp/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBFetch.Name())
_, err = fileBFetch.Write([]byte{1})
require.NoError(t, err)
// zip fetchDir to use as our unarchive source
zipFile := "/tmp/unarchive_test_zip.zip"
err = zipFiles(fileBFetch.Name(), zipFile)
require.NoError(t, err)
u := &Unarchive{
Source: zipFile,
Destination: destDir,
fetchLoc: tmpFetchDir,
}
u.fetch = fetch.Fetch{
Source: u.Source,
Destination: u.fetchLoc,
HashType: u.HashType,
Hash: u.Hash,
Unarchive: true,
}
defer os.Remove(u.Source)
defer os.RemoveAll(u.Destination)
checksumDest, err := u.getChecksum(destDir + "/fileB.txt")
require.NoError(t, err)
checksumFetch, err := u.getChecksum("/tmp/fileB.txt")
require.NoError(t, err)
require.NotEqual(t, checksumDest, checksumFetch)
status, err := u.Apply(context.Background())
assert.EqualError(t, err, fmt.Sprintf("will not replace, \"/fileB.txt\" exists at %q: checksum mismatch", u.Destination))
assert.Equal(t, "use the \"force\" option to replace all files with checksum mismatch", status.Messages()[0])
assert.Equal(t, resource.StatusFatal, status.StatusCode())
assert.False(t, status.HasChanges())
})
})
t.Run("success", func(t *testing.T) {
t.Run("empty dest", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "destDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(destDir)
fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(fetchDir)
fileBFetch, err := os.Create(fetchDir + "/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBFetch.Name())
// zip fetchDir to use as our unarchive source
zipFile := "/tmp/unarchive_test_zip.zip"
err = zipFiles(fetchDir, zipFile)
require.NoError(t, err)
u := &Unarchive{
Source: zipFile,
Destination: destDir,
fetchLoc: tmpFetchDir,
}
u.fetch = fetch.Fetch{
Source: u.Source,
Destination: u.fetchLoc,
HashType: u.HashType,
Hash: u.Hash,
Unarchive: true,
}
defer os.Remove(u.Source)
defer os.RemoveAll(u.Destination)
status, err := u.Apply(context.Background())
assert.NoError(t, err)
assert.Equal(t, fmt.Sprintf("completed fetch and unarchive %q", u.Source), status.Messages()[0])
assert.Equal(t, u.Source, status.Diffs()["unarchive"].Original())
assert.Equal(t, u.Destination, status.Diffs()["unarchive"].Current())
})
t.Run("checksum match", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "destDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(destDir)
fileBDest, err := os.Create(destDir + "/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBDest.Name())
fileBFetch, err := os.Create("/tmp/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBFetch.Name())
// zip fetchDir to use as our unarchive source
zipFile := "/tmp/unarchive_test_zip.zip"
err = zipFiles(fileBFetch.Name(), zipFile)
require.NoError(t, err)
u := &Unarchive{
Source: zipFile,
Destination: destDir,
fetchLoc: tmpFetchDir,
}
u.fetch = fetch.Fetch{
Source: u.Source,
Destination: u.fetchLoc,
HashType: u.HashType,
Hash: u.Hash,
Unarchive: true,
}
defer os.Remove(u.Source)
defer os.RemoveAll(u.Destination)
checksumDest, err := u.getChecksum(destDir + "/fileB.txt")
require.NoError(t, err)
checksumFetch, err := u.getChecksum("/tmp/fileB.txt")
require.NoError(t, err)
require.Equal(t, checksumDest, checksumFetch)
status, err := u.Apply(context.Background())
assert.NoError(t, err)
assert.Equal(t, fmt.Sprintf("completed fetch and unarchive %q", u.Source), status.Messages()[0])
assert.Equal(t, u.Source, status.Diffs()["unarchive"].Original())
assert.Equal(t, u.Destination, status.Diffs()["unarchive"].Current())
})
t.Run("checksum mismatch", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "destDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(destDir)
fileBDest, err := os.Create(destDir + "/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBDest.Name())
fileBFetch, err := os.Create("/tmp/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBFetch.Name())
_, err = fileBFetch.Write([]byte{1})
require.NoError(t, err)
// zip fetchDir to use as our unarchive source
zipFile := "/tmp/unarchive_test_zip.zip"
err = zipFiles(fileBFetch.Name(), zipFile)
require.NoError(t, err)
u := &Unarchive{
Source: zipFile,
Destination: destDir,
Force: true,
fetchLoc: tmpFetchDir,
}
u.fetch = fetch.Fetch{
Source: u.Source,
Destination: u.fetchLoc,
HashType: u.HashType,
Hash: u.Hash,
Unarchive: true,
}
defer os.Remove(u.Source)
defer os.RemoveAll(u.Destination)
checksumDest, err := u.getChecksum(destDir + "/fileB.txt")
require.NoError(t, err)
checksumFetch, err := u.getChecksum("/tmp/fileB.txt")
require.NoError(t, err)
require.NotEqual(t, checksumDest, checksumFetch)
status, testErr := u.Apply(context.Background())
checksumDest, err = u.getChecksum(destDir + "/fileB.txt")
require.NoError(t, err)
checksumFetch, err = u.getChecksum("/tmp/fileB.txt")
require.NoError(t, err)
assert.NoError(t, testErr)
assert.Equal(t, fmt.Sprintf("completed fetch and unarchive %q", u.Source), status.Messages()[0])
assert.Equal(t, u.Source, status.Diffs()["unarchive"].Original())
assert.Equal(t, u.Destination, status.Diffs()["unarchive"].Current())
assert.Equal(t, checksumDest, checksumFetch)
})
t.Run("fetch with checksum", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "destDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(destDir)
fileBDest, err := os.Create(destDir + "/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBDest.Name())
fileBFetch, err := os.Create("/tmp/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBFetch.Name())
// zip fetchDir to use as our unarchive source
zipFile := "/tmp/unarchive_test_zip.zip"
err = zipFiles(fileBFetch.Name(), zipFile)
require.NoError(t, err)
u := &Unarchive{
Source: zipFile,
Destination: destDir,
fetchLoc: tmpFetchDir,
}
srcChecksum, err := u.getChecksum(zipFile)
require.NoError(t, err)
u.HashType = "sha256"
u.Hash = srcChecksum
u.fetch = fetch.Fetch{
Source: u.Source,
Destination: u.fetchLoc,
HashType: u.HashType,
Hash: u.Hash,
Unarchive: true,
}
defer os.Remove(u.Source)
defer os.RemoveAll(u.Destination)
status, err := u.Apply(context.Background())
assert.NoError(t, err)
assert.Equal(t, fmt.Sprintf("completed fetch and unarchive %q", u.Source), status.Messages()[0])
assert.Equal(t, u.Source, status.Diffs()["unarchive"].Original())
assert.Equal(t, u.Destination, status.Diffs()["unarchive"].Current())
})
})
t.Run("context", func(t *testing.T) {
u := &Unarchive{}
ctx, cancel := context.WithCancel(context.Background())
cancel()
status, err := u.Apply(ctx)
assert.EqualError(t, err, "context canceled")
assert.Nil(t, status)
})
}
// TestDiff tests diff for Unarchive
func TestDiff(t *testing.T) {
t.Parallel()
src, err := ioutil.TempFile("", "unarchive_test.txt")
require.NoError(t, err)
defer os.Remove(src.Name())
destInvalid, err := ioutil.TempFile("", "unarchive_test.txt")
require.NoError(t, err)
defer os.Remove(destInvalid.Name())
t.Run("source does not exist", func(t *testing.T) {
u := &Unarchive{
Source: "",
Destination: "/tmp",
}
status := resource.NewStatus()
err := u.diff(status)
assert.EqualError(t, err, "cannot unarchive: stat : no such file or directory")
assert.Equal(t, resource.StatusCantChange, status.StatusCode())
assert.True(t, status.HasChanges())
})
t.Run("destination is not directory", func(t *testing.T) {
u := &Unarchive{
Source: src.Name(),
Destination: destInvalid.Name(),
}
status := resource.NewStatus()
err := u.diff(status)
assert.EqualError(t, err, fmt.Sprintf("invalid destination \"%s\", must be directory", u.Destination))
assert.Equal(t, resource.StatusCantChange, status.StatusCode())
assert.True(t, status.HasChanges())
})
t.Run("destination does not exist", func(t *testing.T) {
u := &Unarchive{
Source: src.Name(),
Destination: "",
}
status := resource.NewStatus()
err := u.diff(status)
assert.EqualError(t, err, fmt.Sprintf("destination \"%s\" does not exist", u.Destination))
assert.Equal(t, resource.StatusCantChange, status.StatusCode())
assert.True(t, status.HasChanges())
})
t.Run("unarchive", func(t *testing.T) {
u := &Unarchive{
Source: src.Name(),
Destination: "/tmp",
}
status := resource.NewStatus()
err := u.diff(status)
assert.NoError(t, err)
assert.Equal(t, u.Source, status.Diffs()["unarchive"].Original())
assert.Equal(t, u.Destination, status.Diffs()["unarchive"].Current())
assert.Equal(t, resource.StatusWillChange, status.StatusCode())
assert.True(t, status.HasChanges())
})
}
// BenchmarkDiff benchmarks diff for Unarchive
func BenchmarkDiff(b *testing.B) {
src, err := ioutil.TempFile("", "unarchive_test.txt")
require.NoError(b, err)
defer os.Remove(src.Name())
destInvalid, err := ioutil.TempFile("", "unarchive_test.txt")
require.NoError(b, err)
defer os.Remove(destInvalid.Name())
status := resource.NewStatus()
benchmarks := []struct {
name string
Unarchive
}{
{"source does not exist",
Unarchive{
Source: "",
Destination: "/tmp",
},
},
{"destination is not directory",
Unarchive{
Source: src.Name(),
Destination: destInvalid.Name(),
},
},
{"destination does not exist",
Unarchive{
Source: src.Name(),
Destination: "",
},
},
{"unarchive",
Unarchive{
Source: src.Name(),
Destination: "/tmp",
},
},
}
for _, bm := range benchmarks {
b.Run(bm.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
bm.Unarchive.diff(status)
}
})
}
}
// TestSetDirsAndContents tests setDirsAndContents for Unarchive
func TestSetDirsAndContents(t *testing.T) {
t.Parallel()
srcFile, err := ioutil.TempFile("", "unarchive_test.zip")
require.NoError(t, err)
defer os.Remove(srcFile.Name())
t.Run("dest not exist", func(t *testing.T) {
notExistDir := "/tmp/unarchive_test12345678"
_, err := os.Stat(notExistDir)
require.True(t, os.IsNotExist(err))
u := &Unarchive{
Source: srcFile.Name(),
Destination: notExistDir,
}
defer os.RemoveAll(u.Destination)
evalDups, err := u.setDirsAndContents()
_, exists := os.Stat(notExistDir)
assert.EqualError(t, err, fmt.Sprintf("open %s: no such file or directory", u.Destination))
assert.False(t, evalDups)
assert.True(t, os.IsNotExist(exists))
})
t.Run("empty dest", func(t *testing.T) {
emptyDir, err := ioutil.TempDir("", "unarchive_empty")
require.NoError(t, err)
defer os.RemoveAll(emptyDir)
u := &Unarchive{
Source: srcFile.Name(),
Destination: emptyDir,
}
defer os.RemoveAll(u.Destination)
err = setupSetDirsAndContents(u, false)
require.NoError(t, err)
defer os.RemoveAll(u.fetchLoc)
expF := [2]string{u.fetchLoc, "fetchFileA.txt"}
expD := [1]string{emptyDir}
evalDups, err := u.setDirsAndContents()
assert.NoError(t, err)
assert.False(t, evalDups)
assert.Equal(t, 2, len(u.fetchContents))
assert.Equal(t, 1, len(u.destContents))
assert.True(t, strings.Contains(u.fetchContents[0], expF[0]) || strings.Contains(u.fetchContents[1], expF[0]))
assert.True(t, strings.Contains(u.fetchContents[0], expF[1]) || strings.Contains(u.fetchContents[1], expF[1]))
assert.True(t, strings.Contains(u.destContents[0], expD[0]))
})
t.Run("fetch dir", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "unarchive_dir")
require.NoError(t, err)
defer os.RemoveAll(destDir)
nestedDir, err := ioutil.TempDir(destDir, "unarchive_nest_dir")
require.NoError(t, err)
defer os.RemoveAll(nestedDir)
nestedFile, err := ioutil.TempFile(destDir, "unarchive_nest_file")
require.NoError(t, err)
defer os.Remove(nestedFile.Name())
u := &Unarchive{
Source: srcFile.Name(),
Destination: destDir,
}
defer os.RemoveAll(u.Destination)
err = setupSetDirsAndContents(u, false)
require.NoError(t, err)
defer os.RemoveAll(u.fetchLoc)
expF := [2]string{u.fetchLoc, "fetchFileA.txt"}
expD := [3]string{destDir, nestedDir, nestedFile.Name()}
evalDups, err := u.setDirsAndContents()
assert.NoError(t, err)
assert.True(t, evalDups)
assert.Equal(t, 2, len(u.fetchContents))
assert.Equal(t, 3, len(u.destContents))
assert.True(t, strings.Contains(u.fetchContents[0], expF[0]) || strings.Contains(u.fetchContents[1], expF[0]))
assert.True(t, strings.Contains(u.fetchContents[0], expF[1]) || strings.Contains(u.fetchContents[1], expF[1]))
assert.True(t, strings.Contains(u.destContents[0], expD[0]) || strings.Contains(u.destContents[1], expD[0]) || strings.Contains(u.destContents[2], expD[0]))
assert.True(t, strings.Contains(u.destContents[0], expD[1]) || strings.Contains(u.destContents[1], expD[1]) || strings.Contains(u.destContents[2], expD[1]))
assert.True(t, strings.Contains(u.destContents[0], expD[2]) || strings.Contains(u.destContents[1], expD[2]) || strings.Contains(u.destContents[2], expD[2]))
})
t.Run("nested fetch dir", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "unarchive_dir")
require.NoError(t, err)
defer os.RemoveAll(destDir)
nestedDir, err := ioutil.TempDir(destDir, "unarchive_nest_dir")
require.NoError(t, err)
defer os.RemoveAll(nestedDir)
nestedFile, err := ioutil.TempFile(destDir, "unarchive_nest_file")
require.NoError(t, err)
defer os.Remove(nestedFile.Name())
u := &Unarchive{
Source: srcFile.Name(),
Destination: destDir,
}
defer os.RemoveAll(u.Destination)
err = setupSetDirsAndContents(u, true)
require.NoError(t, err)
defer os.RemoveAll(u.fetchLoc)
expF := [4]string{u.fetchLoc, "/unarchive_fetch_nest", "fetchFileB.txt", "fetchFileC.txt"}
expD := [3]string{destDir, nestedDir, nestedFile.Name()}
evalDups, err := u.setDirsAndContents()
assert.NoError(t, err)
assert.True(t, evalDups)
assert.Equal(t, 4, len(u.fetchContents))
assert.Equal(t, 3, len(u.destContents))
assert.True(t, strings.Contains(u.fetchContents[0], expF[0]) || strings.Contains(u.fetchContents[1], expF[0]) || strings.Contains(u.fetchContents[2], expF[0]) || strings.Contains(u.fetchContents[3], expF[0]))
assert.True(t, strings.Contains(u.fetchContents[0], expF[1]) || strings.Contains(u.fetchContents[1], expF[1]) || strings.Contains(u.fetchContents[2], expF[1]) || strings.Contains(u.fetchContents[3], expF[1]))
assert.True(t, strings.Contains(u.fetchContents[0], expF[2]) || strings.Contains(u.fetchContents[1], expF[2]) || strings.Contains(u.fetchContents[2], expF[2]) || strings.Contains(u.fetchContents[3], expF[2]))
assert.True(t, strings.Contains(u.fetchContents[0], expF[3]) || strings.Contains(u.fetchContents[1], expF[3]) || strings.Contains(u.fetchContents[2], expF[3]) || strings.Contains(u.fetchContents[3], expF[3]))
assert.True(t, strings.Contains(u.destContents[0], expD[0]) || strings.Contains(u.destContents[1], expD[0]) || strings.Contains(u.destContents[2], expD[0]))
assert.True(t, strings.Contains(u.destContents[0], expD[1]) || strings.Contains(u.destContents[1], expD[1]) || strings.Contains(u.destContents[2], expD[1]))
assert.True(t, strings.Contains(u.destContents[0], expD[2]) || strings.Contains(u.destContents[1], expD[2]) || strings.Contains(u.destContents[2], expD[2]))
})
}
// TestEvaluateDuplicates tests evaluateDuplicates for Unarchive
func TestEvaluateDuplicates(t *testing.T) {
t.Parallel()
t.Run("no duplicates", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "destDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(destDir)
fileA, err := ioutil.TempFile(destDir, "fileA.txt")
require.NoError(t, err)
defer os.Remove(fileA.Name())
fileB, err := ioutil.TempFile(destDir, "fileB.txt")
require.NoError(t, err)
defer os.Remove(fileB.Name())
ddInfo, err := os.Open(destDir)
require.NoError(t, err)
defer ddInfo.Close()
fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(fetchDir)
fileC, err := ioutil.TempFile(fetchDir, "fileC.txt")
require.NoError(t, err)
defer os.Remove(fileC.Name())
fileD, err := ioutil.TempFile(fetchDir, "fileD.txt")
require.NoError(t, err)
defer os.Remove(fileD.Name())
fdInfo, err := os.Open(fetchDir)
require.NoError(t, err)
defer fdInfo.Close()
u := &Unarchive{
destDir: ddInfo,
fetchDir: fdInfo,
}
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
u.destContents = append(u.destContents, path)
return nil
})
require.NoError(t, err)
err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
u.fetchContents = append(u.fetchContents, path)
return nil
})
require.NoError(t, err)
err = u.evaluateDuplicates()
assert.NoError(t, err)
})
t.Run("duplicates", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "destDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(destDir)
fileADest, err := os.Create(destDir + "/fileA.txt")
require.NoError(t, err)
defer os.Remove(fileADest.Name())
ddInfo, err := os.Open(destDir)
require.NoError(t, err)
defer ddInfo.Close()
fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(fetchDir)
fileAFetch, err := os.Create(fetchDir + "/fileA.txt")
require.NoError(t, err)
defer os.Remove(fileAFetch.Name())
fdInfo, err := os.Open(fetchDir)
require.NoError(t, err)
defer fdInfo.Close()
t.Run("checksum match", func(t *testing.T) {
u := &Unarchive{
destDir: ddInfo,
fetchDir: fdInfo,
}
checksumDest, err := u.getChecksum(destDir + "/fileA.txt")
require.NoError(t, err)
checksumFetch, err := u.getChecksum(fetchDir + "/fileA.txt")
require.NoError(t, err)
require.Equal(t, checksumDest, checksumFetch)
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
u.destContents = append(u.destContents, path)
return nil
})
require.NoError(t, err)
err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
u.fetchContents = append(u.fetchContents, path)
return nil
})
require.NoError(t, err)
err = u.evaluateDuplicates()
assert.NoError(t, err)
})
t.Run("checksum mismatch", func(t *testing.T) {
t.Run("different size", func(t *testing.T) {
fileBDest, err := os.Create(destDir + "/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBDest.Name())
fileBFetch, err := os.Create(fetchDir + "/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBFetch.Name())
_, err = fileBFetch.Write([]byte{1})
require.NoError(t, err)
dStat, _ := os.Stat(destDir + "/fileB.txt")
fStat, _ := os.Stat(fetchDir + "/fileB.txt")
require.NotEqual(t, dStat.Size(), fStat.Size())
u := &Unarchive{
Destination: destDir,
destDir: ddInfo,
fetchDir: fdInfo,
}
checksumDest, err := u.getChecksum(destDir + "/fileB.txt")
require.NoError(t, err)
checksumFetch, err := u.getChecksum(fetchDir + "/fileB.txt")
require.NoError(t, err)
require.NotEqual(t, checksumDest, checksumFetch)
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
u.destContents = append(u.destContents, path)
return nil
})
require.NoError(t, err)
err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
u.fetchContents = append(u.fetchContents, path)
return nil
})
require.NoError(t, err)
err = u.evaluateDuplicates()
assert.EqualError(t, err, fmt.Sprintf("will not replace, \"/fileB.txt\" exists at %q: checksum mismatch", u.Destination))
})
t.Run("same size", func(t *testing.T) {
fileBDest, err := os.Create(destDir + "/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBDest.Name())
_, err = fileBDest.WriteString("a")
require.NoError(t, err)
fileBFetch, err := os.Create(fetchDir + "/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBFetch.Name())
_, err = fileBFetch.WriteString("b")
require.NoError(t, err)
dStat, _ := os.Stat(destDir + "/fileB.txt")
fStat, _ := os.Stat(fetchDir + "/fileB.txt")
require.Equal(t, dStat.Size(), fStat.Size())
u := &Unarchive{
Destination: destDir,
destDir: ddInfo,
fetchDir: fdInfo,
}
checksumDest, err := u.getChecksum(destDir + "/fileB.txt")
require.NoError(t, err)
checksumFetch, err := u.getChecksum(fetchDir + "/fileB.txt")
require.NoError(t, err)
require.NotEqual(t, checksumDest, checksumFetch)
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
u.destContents = append(u.destContents, path)
return nil
})
require.NoError(t, err)
err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
u.fetchContents = append(u.fetchContents, path)
return nil
})
require.NoError(t, err)
err = u.evaluateDuplicates()
assert.EqualError(t, err, fmt.Sprintf("will not replace, \"/fileB.txt\" exists at %q: checksum mismatch", u.Destination))
})
})
})
t.Run("recurse", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "destDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(destDir)
stat, err := os.Stat(destDir)
require.NoError(t, err)
err = os.Mkdir(destDir+"/dirA", stat.Mode().Perm())
require.NoError(t, err)
err = os.Mkdir(destDir+"/dirA/dirB", stat.Mode().Perm())
require.NoError(t, err)
fileBDest, err := os.Create(destDir + "/dirA/dirB/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBDest.Name())
ddInfo, err := os.Open(destDir)
require.NoError(t, err)
defer ddInfo.Close()
fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(fetchDir)
stat, err = os.Stat(fetchDir)
require.NoError(t, err)
err = os.Mkdir(fetchDir+"/dirA", stat.Mode().Perm())
require.NoError(t, err)
err = os.Mkdir(fetchDir+"/dirA/dirB", stat.Mode().Perm())
require.NoError(t, err)
fileBFetch, err := os.Create(fetchDir + "/dirA/dirB/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBFetch.Name())
fdInfo, err := os.Open(fetchDir)
require.NoError(t, err)
defer fdInfo.Close()
t.Run("checksum match", func(t *testing.T) {
u := &Unarchive{
Destination: destDir,
destDir: ddInfo,
fetchDir: fdInfo,
}
checksumDest, err := u.getChecksum(destDir + "/dirA/dirB/fileB.txt")
require.NoError(t, err)
checksumFetch, err := u.getChecksum(fetchDir + "/dirA/dirB/fileB.txt")
require.NoError(t, err)
require.Equal(t, checksumDest, checksumFetch)
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
u.destContents = append(u.destContents, path)
return nil
})
require.NoError(t, err)
err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
u.fetchContents = append(u.fetchContents, path)
return nil
})
require.NoError(t, err)
err = u.evaluateDuplicates()
assert.NoError(t, err)
})
t.Run("checksum mismatch", func(t *testing.T) {
fileCDest, err := os.Create(destDir + "/dirA/dirB/fileC.txt")
require.NoError(t, err)
defer os.Remove(fileCDest.Name())
fileCFetch, err := os.Create(fetchDir + "/dirA/dirB/fileC.txt")
require.NoError(t, err)
defer os.Remove(fileCFetch.Name())
_, err = fileCFetch.Write([]byte{2})
require.NoError(t, err)
u := &Unarchive{
Destination: destDir,
destDir: ddInfo,
fetchDir: fdInfo,
}
checksumDest, err := u.getChecksum(destDir + "/dirA/dirB/fileC.txt")
require.NoError(t, err)
checksumFetch, err := u.getChecksum(fetchDir + "/dirA/dirB/fileC.txt")
require.NoError(t, err)
require.NotEqual(t, checksumDest, checksumFetch)
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
u.destContents = append(u.destContents, path)
return nil
})
require.NoError(t, err)
err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
u.fetchContents = append(u.fetchContents, path)
return nil
})
require.NoError(t, err)
err = u.evaluateDuplicates()
assert.EqualError(t, err, fmt.Sprintf("will not replace, \"/dirA/dirB/fileC.txt\" exists at %q: checksum mismatch", u.Destination))
})
})
}
// TestCopyToFinalDest tests copyToFinalDest for Unarchive
func TestCopyToFinalDest(t *testing.T) {
t.Parallel()
t.Run("no duplicates", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "destDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(destDir)
fileA, err := ioutil.TempFile(destDir, "fileA.txt")
require.NoError(t, err)
defer os.Remove(fileA.Name())
fileB, err := ioutil.TempFile(destDir, "fileB.txt")
require.NoError(t, err)
defer os.Remove(fileB.Name())
ddInfo, err := os.Open(destDir)
require.NoError(t, err)
defer ddInfo.Close()
fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(fetchDir)
fileC, err := ioutil.TempFile(fetchDir, "fileC.txt")
require.NoError(t, err)
defer os.Remove(fileC.Name())
fileD, err := ioutil.TempFile(fetchDir, "fileD.txt")
require.NoError(t, err)
defer os.Remove(fileD.Name())
fdInfo, err := os.Open(fetchDir)
require.NoError(t, err)
defer fdInfo.Close()
u := &Unarchive{
destDir: ddInfo,
fetchDir: fdInfo,
}
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
u.destContents = append(u.destContents, path)
return nil
})
require.NoError(t, err)
err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
u.fetchContents = append(u.fetchContents, path)
return nil
})
require.NoError(t, err)
exp := []string{destDir, "fileA.txt", "fileB.txt", "fileC.txt", "fileD.txt"}
testErr := u.copyToFinalDest()
act := []string{}
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
act = append(act, path)
return nil
})
require.NoError(t, err)
assert.NoError(t, testErr)
assert.Equal(t, 5, len(act))
assert.True(t, strings.Contains(act[0], exp[0]) || strings.Contains(act[1], exp[0]) || strings.Contains(act[2], exp[0]) || strings.Contains(act[3], exp[0]) || strings.Contains(act[4], exp[0]))
assert.True(t, strings.Contains(act[0], exp[1]) || strings.Contains(act[1], exp[1]) || strings.Contains(act[2], exp[1]) || strings.Contains(act[3], exp[1]) || strings.Contains(act[4], exp[1]))
assert.True(t, strings.Contains(act[0], exp[2]) || strings.Contains(act[1], exp[2]) || strings.Contains(act[2], exp[2]) || strings.Contains(act[3], exp[2]) || strings.Contains(act[4], exp[2]))
assert.True(t, strings.Contains(act[0], exp[3]) || strings.Contains(act[1], exp[3]) || strings.Contains(act[2], exp[3]) || strings.Contains(act[3], exp[3]) || strings.Contains(act[4], exp[3]))
assert.True(t, strings.Contains(act[0], exp[4]) || strings.Contains(act[1], exp[4]) || strings.Contains(act[2], exp[4]) || strings.Contains(act[3], exp[4]) || strings.Contains(act[4], exp[4]))
})
t.Run("duplicates", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "destDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(destDir)
fileADest, err := os.Create(destDir + "/fileA.txt")
require.NoError(t, err)
defer os.Remove(fileADest.Name())
ddInfo, err := os.Open(destDir)
require.NoError(t, err)
defer ddInfo.Close()
fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(fetchDir)
fileAFetch, err := os.Create(fetchDir + "/fileA.txt")
require.NoError(t, err)
defer os.Remove(fileAFetch.Name())
fdInfo, err := os.Open(fetchDir)
require.NoError(t, err)
defer fdInfo.Close()
t.Run("checksum match", func(t *testing.T) {
u := &Unarchive{
destDir: ddInfo,
fetchDir: fdInfo,
}
checksumDest, err := u.getChecksum(destDir + "/fileA.txt")
require.NoError(t, err)
checksumFetch, err := u.getChecksum(fetchDir + "/fileA.txt")
require.NoError(t, err)
require.Equal(t, checksumDest, checksumFetch)
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
u.destContents = append(u.destContents, path)
return nil
})
require.NoError(t, err)
err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
u.fetchContents = append(u.fetchContents, path)
return nil
})
require.NoError(t, err)
exp := []string{destDir, "fileA.txt"}
testErr := u.copyToFinalDest()
act := []string{}
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
act = append(act, path)
return nil
})
require.NoError(t, err)
assert.NoError(t, testErr)
assert.Equal(t, 2, len(act))
assert.True(t, strings.Contains(act[0], exp[0]) || strings.Contains(act[1], exp[0]))
assert.True(t, strings.Contains(act[0], exp[1]) || strings.Contains(act[1], exp[1]))
})
t.Run("checksum mismatch", func(t *testing.T) {
fileBDest, err := os.Create(destDir + "/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBDest.Name())
fileBFetch, err := os.Create(fetchDir + "/fileB.txt")
require.NoError(t, err)
defer os.Remove(fileBFetch.Name())
_, err = fileBFetch.Write([]byte{1})
require.NoError(t, err)
u := &Unarchive{
Destination: destDir,
destDir: ddInfo,
fetchDir: fdInfo,
}
checksumDest, err := u.getChecksum(destDir + "/fileB.txt")
require.NoError(t, err)
checksumFetch, err := u.getChecksum(fetchDir + "/fileB.txt")
require.NoError(t, err)
require.NotEqual(t, checksumDest, checksumFetch)
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
u.destContents = append(u.destContents, path)
return nil
})
require.NoError(t, err)
err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
u.fetchContents = append(u.fetchContents, path)
return nil
})
require.NoError(t, err)
exp := []string{destDir, "fileA.txt", "fileB.txt"}
testErr := u.copyToFinalDest()
act := []string{}
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
act = append(act, path)
return nil
})
require.NoError(t, err)
checksumDest, err = u.getChecksum(destDir + "/fileB.txt")
require.NoError(t, err)
checksumFetch, err = u.getChecksum(fetchDir + "/fileB.txt")
require.NoError(t, err)
assert.NoError(t, testErr)
assert.Equal(t, 3, len(act))
assert.True(t, strings.Contains(act[0], exp[0]) || strings.Contains(act[1], exp[0]) || strings.Contains(act[2], exp[0]))
assert.True(t, strings.Contains(act[0], exp[1]) || strings.Contains(act[1], exp[1]) || strings.Contains(act[2], exp[1]))
assert.True(t, strings.Contains(act[0], exp[2]) || strings.Contains(act[1], exp[2]) || strings.Contains(act[2], exp[2]))
assert.Equal(t, checksumDest, checksumFetch)
})
})
t.Run("recurse", func(t *testing.T) {
destDir, err := ioutil.TempDir("", "destDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(destDir)
ddInfo, err := os.Open(destDir)
require.NoError(t, err)
defer ddInfo.Close()
fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
require.NoError(t, err)
defer os.RemoveAll(fetchDir)
dirA, err := ioutil.TempDir(fetchDir, "dirA")
require.NoError(t, err)
defer os.RemoveAll(dirA)
dirB, err := ioutil.TempDir(dirA, "dirB")
require.NoError(t, err)
defer os.RemoveAll(dirB)
dirC, err := ioutil.TempDir(dirB, "dirC")
require.NoError(t, err)
defer os.RemoveAll(dirC)
fileC, err := os.Create(dirC + "/fileC.txt")
require.NoError(t, err)
defer os.Remove(fileC.Name())
fdInfo, err := os.Open(fetchDir)
require.NoError(t, err)
defer fdInfo.Close()
u := &Unarchive{
Destination: destDir,
destDir: ddInfo,
fetchDir: fdInfo,
}
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
u.destContents = append(u.destContents, path)
return nil
})
require.NoError(t, err)
err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
u.fetchContents = append(u.fetchContents, path)
return nil
})
require.NoError(t, err)
exp := []string{destDir, "dirA", "dirB", "dirC", "fileC.txt"}
testErr := u.copyToFinalDest()
act := []string{}
err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
act = append(act, path)
return nil
})
require.NoError(t, err)
assert.NoError(t, testErr)
assert.Equal(t, 5, len(act))
assert.True(t, strings.Contains(act[0], exp[0]) || strings.Contains(act[1], exp[0]) || strings.Contains(act[2], exp[0]) || strings.Contains(act[3], exp[0]) || strings.Contains(act[4], exp[0]))
assert.True(t, strings.Contains(act[0], exp[1]) || strings.Contains(act[1], exp[1]) || strings.Contains(act[2], exp[1]) || strings.Contains(act[3], exp[1]) || strings.Contains(act[4], exp[1]))
assert.True(t, strings.Contains(act[0], exp[2]) || strings.Contains(act[1], exp[2]) || strings.Contains(act[2], exp[2]) || strings.Contains(act[3], exp[2]) || strings.Contains(act[4], exp[2]))
assert.True(t, strings.Contains(act[0], exp[3]) || strings.Contains(act[1], exp[3]) || strings.Contains(act[2], exp[3]) || strings.Contains(act[3], exp[3]) || strings.Contains(act[4], exp[3]))
assert.True(t, strings.Contains(act[0], exp[4]) || strings.Contains(act[1], exp[4]) || strings.Contains(act[2], exp[4]) || strings.Contains(act[3], exp[4]) || strings.Contains(act[4], exp[4]))
})
}
// TestSetFetchLoc tests setFetchLoc for Unarchive
func TestSetFetchLoc(t *testing.T) {
t.Parallel()
srcFile, err := ioutil.TempFile("", "unarchive_test.txt")
require.NoError(t, err)
defer os.Remove(srcFile.Name())
u := &Unarchive{
Source: srcFile.Name(),
}
tmp := os.TempDir()
s := tmp[len(tmp)-1:]
expected := tmp + "tmpDirFetch"
if s != "/" {
expected = tmp + "/tmpDirFetch"
}
err = u.setFetchLoc()
defer os.RemoveAll(u.fetchLoc)
assert.NoError(t, err)
assert.Contains(t, u.fetchLoc, expected)
}
// setupSetDirsAndContents performs some setup required to test
// SetDirsAndContents
func setupSetDirsAndContents(u *Unarchive, nested bool) error {
err := u.setFetchLoc()
if err != nil {
return err
}
err = fetchApply(u.fetchLoc, nested)
return err
}
// fetchApply sets up a temporary fetch location with file(s) based on the
// nested flag
func fetchApply(fetchLoc string, nested bool) error {
if !nested {
err := createTempFile(fetchLoc)
if err != nil {
return err
}
} else {
_, err := createTempDirAndFiles(fetchLoc)
if err != nil {
return err
}
}
return nil
}
// createTempFile will create a temp file in the provided location
// the caller is responsible for removing this file
func createTempFile(location string) error {
_, err := ioutil.TempFile(location, "fetchFileA.txt")
return err
}
// createTempDirAndFiles will create a temp directory and two temp files within
// it in the provided location
// the caller is responsible for removing the directory and files
func createTempDirAndFiles(location string) (string, error) {
nestedFetchDir, err := ioutil.TempDir(location, "unarchive_fetch_nest")
if err != nil {
return nestedFetchDir, err
}
_, err = ioutil.TempFile(nestedFetchDir, "fetchFileB.txt")
if err != nil {
return nestedFetchDir, err
}
_, err = ioutil.TempFile(nestedFetchDir, "fetchFileC.txt")
if err != nil {
return nestedFetchDir, err
}
return nestedFetchDir, nil
}
// zipFiles zips the files in source and places them in destination
func zipFiles(source, destination string) error {
base := ""
zipFile, err := os.Create(destination)
if err != nil {
return err
}
defer zipFile.Close()
w := zip.NewWriter(zipFile)
defer w.Close()
f, err := os.Stat(source)
if err != nil {
return err
}
if f.IsDir() {
base = filepath.Base(source)
}
err = filepath.Walk(source, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
header, err := zip.FileInfoHeader(f)
if err != nil {
return err
}
if base != "" {
header.Name = filepath.Join(base, strings.TrimPrefix(path, source))
}
if f.IsDir() {
header.Name += "/"
} else {
header.Method = zip.Deflate
}
writer, err := w.CreateHeader(header)
if err != nil {
return err
}
if f.IsDir() {
return nil
}
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(writer, file)
return err
})
if err != nil {
return err
}
return nil
}