309 lines
9.0 KiB
Go
309 lines
9.0 KiB
Go
package buildah
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"io"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
digest "github.com/opencontainers/go-digest"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func (c *CompositeDigester) isOpen() bool {
|
|
for _, digester := range c.digesters {
|
|
if tarDigester, ok := digester.(*tarDigester); ok {
|
|
if tarDigester.isOpen {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func TestCompositeDigester(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
itemTypes []string
|
|
resultType string
|
|
}{
|
|
{
|
|
name: "download",
|
|
itemTypes: []string{""},
|
|
resultType: "",
|
|
},
|
|
{
|
|
name: "file",
|
|
itemTypes: []string{"file"},
|
|
resultType: "file",
|
|
},
|
|
{
|
|
name: "dir",
|
|
itemTypes: []string{"dir"},
|
|
resultType: "dir",
|
|
},
|
|
{
|
|
name: "multiple-1",
|
|
itemTypes: []string{"file", "dir"},
|
|
resultType: "multi",
|
|
},
|
|
{
|
|
name: "multiple-2",
|
|
itemTypes: []string{"dir", "file"},
|
|
resultType: "multi",
|
|
},
|
|
{
|
|
name: "multiple-3",
|
|
itemTypes: []string{"", "dir"},
|
|
resultType: "multi",
|
|
},
|
|
{
|
|
name: "multiple-4",
|
|
itemTypes: []string{"", "file"},
|
|
resultType: "multi",
|
|
},
|
|
{
|
|
name: "multiple-5",
|
|
itemTypes: []string{"dir", ""},
|
|
resultType: "multi",
|
|
},
|
|
{
|
|
name: "multiple-6",
|
|
itemTypes: []string{"file", ""},
|
|
resultType: "multi",
|
|
},
|
|
}
|
|
var digester CompositeDigester
|
|
var i int
|
|
var buf bytes.Buffer
|
|
zero := time.Unix(0, 0)
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
for _, filtered := range []bool{false, true} {
|
|
desc := "unfiltered"
|
|
if filtered {
|
|
desc = "filter"
|
|
}
|
|
t.Run(desc, func(t *testing.T) {
|
|
if i > 0 {
|
|
// restart only after it's been used some, to make sure it's not necessary otherwise
|
|
digester.Restart()
|
|
}
|
|
i++
|
|
size := int64(i * 32) // items for this archive will be bigger than the last one
|
|
for _, itemType := range test.itemTypes {
|
|
for int64(buf.Len()) < size {
|
|
err := buf.WriteByte(byte(buf.Len() % 256))
|
|
require.Nil(t, err, "error padding content buffer: %v", err)
|
|
}
|
|
// feed it content that it will treat either as raw data ("") or expect to
|
|
// look like a tarball ("file"/"dir")
|
|
digester.Start(itemType)
|
|
hasher := digester.Hash() // returns an io.WriteCloser
|
|
require.NotNil(t, hasher, "digester returned a null hasher?")
|
|
if itemType == "" {
|
|
// write something that isn't an archive
|
|
n, err := io.Copy(hasher, &buf)
|
|
require.Nil(t, err, "error writing tar content to digester: %v", err)
|
|
require.Equal(t, size, n, "short write writing tar content to digester")
|
|
continue
|
|
}
|
|
// write an archive
|
|
var written bytes.Buffer // a copy of the archive we're generating and digesting
|
|
hasher = &struct {
|
|
io.Writer
|
|
io.Closer
|
|
}{
|
|
Writer: io.MultiWriter(hasher, &written), // splice into the writer
|
|
Closer: hasher,
|
|
}
|
|
if filtered {
|
|
// wrap the WriteCloser in another WriteCloser
|
|
hasher = newTarFilterer(hasher, func(hdr *tar.Header) (bool, bool, io.Reader) {
|
|
hdr.ModTime = zero
|
|
return false, false, nil
|
|
})
|
|
require.NotNil(t, hasher, "newTarFilterer returned a null WriteCloser?")
|
|
}
|
|
// write this item as an archive
|
|
tw := tar.NewWriter(hasher)
|
|
hdr := &tar.Header{
|
|
Name: "content",
|
|
Size: size,
|
|
Mode: 0o640,
|
|
ModTime: time.Now(),
|
|
Typeflag: tar.TypeReg,
|
|
}
|
|
err := tw.WriteHeader(hdr)
|
|
require.Nil(t, err, "error writing tar header to digester: %v", err)
|
|
n, err := io.Copy(tw, &buf)
|
|
require.Nil(t, err, "error writing tar content to digester: %v", err)
|
|
require.Equal(t, size, n, "short write writing tar content to digester")
|
|
err = tw.Flush()
|
|
require.Nil(t, err, "error flushing tar content to digester: %v", err)
|
|
err = tw.Close()
|
|
require.Nil(t, err, "error closing tar archive being written digester: %v", err)
|
|
if filtered {
|
|
// the ContentDigester can close its own if we don't explicitly ask it to,
|
|
// but if we wrapped it in a filter, we have to close the filter to clean
|
|
// up the filter, so we can't skip it to exercise that logic; we have to
|
|
// leave that for the corresponding unfiltered case to try
|
|
hasher.Close()
|
|
}
|
|
// now read the archive back
|
|
tr := tar.NewReader(&written)
|
|
require.NotNil(t, tr, "unable to read byte buffer?")
|
|
hdr, err = tr.Next()
|
|
for err == nil {
|
|
var n int64
|
|
if filtered {
|
|
// the filter should have set the modtime to unix 0
|
|
require.Equal(t, zero, hdr.ModTime, "timestamp for entry should have been zero")
|
|
} else {
|
|
// the filter should have left modtime to "roughly now"
|
|
require.NotEqual(t, zero, hdr.ModTime, "timestamp for entry should not have been zero")
|
|
}
|
|
n, err = io.Copy(io.Discard, tr)
|
|
require.Nil(t, err, "error reading tar content from buffer: %v", err)
|
|
require.Equal(t, hdr.Size, n, "short read reading tar content")
|
|
hdr, err = tr.Next()
|
|
}
|
|
require.Equal(t, io.EOF, err, "finished reading archive with %v, not EOF", err)
|
|
}
|
|
// check the composite digest type matches expectations and the value is not just the
|
|
// digest of zero-length data, which is absolutely not what we wrote
|
|
digestType, digestValue := digester.Digest()
|
|
require.Equal(t, test.resultType, digestType, "expected to get a %q digest back for %v, got %q", test.resultType, test.itemTypes, digestType)
|
|
require.NotEqual(t, digest.Canonical.FromBytes([]byte{}), digestValue, "digester wasn't fed any data")
|
|
require.False(t, digester.isOpen(), "expected digester to have been closed with this usage pattern")
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTarFilterer(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
input, output map[string]string
|
|
breakAfter int
|
|
filter func(*tar.Header) (bool, bool, io.Reader)
|
|
}{
|
|
{
|
|
name: "none",
|
|
input: map[string]string{
|
|
"file a": "content a",
|
|
"file b": "content b",
|
|
},
|
|
output: map[string]string{
|
|
"file a": "content a",
|
|
"file b": "content b",
|
|
},
|
|
filter: nil,
|
|
},
|
|
{
|
|
name: "plain",
|
|
input: map[string]string{
|
|
"file a": "content a",
|
|
"file b": "content b",
|
|
},
|
|
output: map[string]string{
|
|
"file a": "content a",
|
|
"file b": "content b",
|
|
},
|
|
filter: func(*tar.Header) (bool, bool, io.Reader) { return false, false, nil },
|
|
},
|
|
{
|
|
name: "skip",
|
|
input: map[string]string{
|
|
"file a": "content a",
|
|
"file b": "content b",
|
|
},
|
|
output: map[string]string{
|
|
"file a": "content a",
|
|
},
|
|
filter: func(hdr *tar.Header) (bool, bool, io.Reader) { return hdr.Name == "file b", false, nil },
|
|
},
|
|
{
|
|
name: "replace",
|
|
input: map[string]string{
|
|
"file a": "content a",
|
|
"file b": "content b",
|
|
"file c": "content c",
|
|
},
|
|
output: map[string]string{
|
|
"file a": "content a",
|
|
"file b": "content b+c",
|
|
"file c": "content c",
|
|
},
|
|
breakAfter: 2,
|
|
filter: func(hdr *tar.Header) (bool, bool, io.Reader) {
|
|
if hdr.Name == "file b" {
|
|
content := "content b+c"
|
|
hdr.Size = int64(len(content))
|
|
return false, true, strings.NewReader(content)
|
|
}
|
|
return false, false, nil
|
|
},
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var buffer bytes.Buffer
|
|
tw := tar.NewWriter(&buffer)
|
|
files := 0
|
|
for filename, contents := range test.input {
|
|
hdr := tar.Header{
|
|
Name: filename,
|
|
Size: int64(len(contents)),
|
|
Typeflag: tar.TypeReg,
|
|
}
|
|
err := tw.WriteHeader(&hdr)
|
|
require.Nil(t, err, "unexpected error from TarWriter.WriteHeader")
|
|
n, err := io.CopyN(tw, strings.NewReader(contents), int64(len(contents)))
|
|
require.Nil(t, err, "unexpected error copying to tar writer")
|
|
require.Equal(t, int64(len(contents)), n, "unexpected write length")
|
|
files++
|
|
if test.breakAfter != 0 && files%test.breakAfter == 0 {
|
|
// this test may have us writing multiple archives to the buffer
|
|
// they should still read back as a single archive
|
|
tw.Close()
|
|
tw = tar.NewWriter(&buffer)
|
|
}
|
|
}
|
|
tw.Close()
|
|
output := make(map[string]string)
|
|
pipeReader, pipeWriter := io.Pipe()
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
tr := tar.NewReader(pipeReader)
|
|
hdr, err := tr.Next()
|
|
for err == nil {
|
|
var buffer bytes.Buffer
|
|
var n int64
|
|
n, err = io.Copy(&buffer, tr)
|
|
require.Nil(t, err, "unexpected error copying from tar reader")
|
|
require.Equal(t, hdr.Size, n, "unexpected read length")
|
|
output[hdr.Name] = buffer.String()
|
|
hdr, err = tr.Next()
|
|
}
|
|
require.Equal(t, io.EOF, err, "unexpected error ended our tarstream read")
|
|
pipeReader.Close()
|
|
wg.Done()
|
|
}()
|
|
filterer := newTarFilterer(pipeWriter, test.filter)
|
|
_, err := io.Copy(filterer, &buffer)
|
|
require.Nil(t, err, "unexpected error copying archive through filter to reader")
|
|
filterer.Close()
|
|
wg.Wait()
|
|
require.Equal(t, test.output, output, "got unexpected results")
|
|
})
|
|
}
|
|
}
|