219 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
   Copyright The containerd 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 compression
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"compress/gzip"
 | 
						|
	"context"
 | 
						|
	"io"
 | 
						|
	"math/rand"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"runtime"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	exec "golang.org/x/sys/execabs"
 | 
						|
)
 | 
						|
 | 
						|
func TestMain(m *testing.M) {
 | 
						|
	// Force initPigz to be called, so tests start with the same initial state
 | 
						|
	gzipDecompress(context.Background(), strings.NewReader(""))
 | 
						|
	os.Exit(m.Run())
 | 
						|
}
 | 
						|
 | 
						|
// generateData generates data that composed of 2 random parts
 | 
						|
// and single zero-filled part within them.
 | 
						|
// Typically, the compression ratio would be about 67%.
 | 
						|
func generateData(t *testing.T, size int) []byte {
 | 
						|
	part0 := size / 3             // random
 | 
						|
	part2 := size / 3             // random
 | 
						|
	part1 := size - part0 - part2 // zero-filled
 | 
						|
	part0Data := make([]byte, part0)
 | 
						|
	if _, err := rand.Read(part0Data); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	part1Data := make([]byte, part1)
 | 
						|
	part2Data := make([]byte, part2)
 | 
						|
	if _, err := rand.Read(part2Data); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	return append(part0Data, append(part1Data, part2Data...)...)
 | 
						|
}
 | 
						|
 | 
						|
func testCompressDecompress(t *testing.T, size int, compression Compression) DecompressReadCloser {
 | 
						|
	orig := generateData(t, size)
 | 
						|
	var b bytes.Buffer
 | 
						|
	compressor, err := CompressStream(&b, compression)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if n, err := compressor.Write(orig); err != nil || n != size {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	compressor.Close()
 | 
						|
	compressed := b.Bytes()
 | 
						|
	t.Logf("compressed %d bytes to %d bytes (%.2f%%)",
 | 
						|
		len(orig), len(compressed), 100.0*float32(len(compressed))/float32(len(orig)))
 | 
						|
	if compared := bytes.Compare(orig, compressed); (compression == Uncompressed && compared != 0) ||
 | 
						|
		(compression != Uncompressed && compared == 0) {
 | 
						|
		t.Fatal("strange compressed data")
 | 
						|
	}
 | 
						|
 | 
						|
	decompressor, err := DecompressStream(bytes.NewReader(compressed))
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	decompressed, err := io.ReadAll(decompressor)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if !bytes.Equal(orig, decompressed) {
 | 
						|
		t.Fatal("strange decompressed data")
 | 
						|
	}
 | 
						|
 | 
						|
	return decompressor
 | 
						|
}
 | 
						|
 | 
						|
func TestCompressDecompressGzip(t *testing.T) {
 | 
						|
	oldUnpigzPath := unpigzPath
 | 
						|
	unpigzPath = ""
 | 
						|
	defer func() { unpigzPath = oldUnpigzPath }()
 | 
						|
 | 
						|
	decompressor := testCompressDecompress(t, 1024*1024, Gzip)
 | 
						|
	wrapper := decompressor.(*readCloserWrapper)
 | 
						|
	_, ok := wrapper.Reader.(*gzip.Reader)
 | 
						|
	if !ok {
 | 
						|
		t.Fatalf("unexpected compressor type: %T", wrapper.Reader)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCompressDecompressPigz(t *testing.T) {
 | 
						|
	if _, err := exec.LookPath("unpigz"); err != nil {
 | 
						|
		t.Skip("pigz not installed")
 | 
						|
	}
 | 
						|
 | 
						|
	decompressor := testCompressDecompress(t, 1024*1024, Gzip)
 | 
						|
	wrapper := decompressor.(*readCloserWrapper)
 | 
						|
	_, ok := wrapper.Reader.(*io.PipeReader)
 | 
						|
	if !ok {
 | 
						|
		t.Fatalf("unexpected compressor type: %T", wrapper.Reader)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCompressDecompressUncompressed(t *testing.T) {
 | 
						|
	testCompressDecompress(t, 1024*1024, Uncompressed)
 | 
						|
}
 | 
						|
 | 
						|
func TestDetectPigz(t *testing.T) {
 | 
						|
	// Create fake PATH with unpigz executable, make sure detectPigz can find it
 | 
						|
	tempPath := t.TempDir()
 | 
						|
 | 
						|
	filename := "unpigz"
 | 
						|
	if runtime.GOOS == "windows" {
 | 
						|
		filename = "unpigz.exe"
 | 
						|
	}
 | 
						|
 | 
						|
	fullPath := filepath.Join(tempPath, filename)
 | 
						|
 | 
						|
	if err := os.WriteFile(fullPath, []byte(""), 0111); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	t.Setenv("PATH", tempPath)
 | 
						|
 | 
						|
	if pigzPath := detectPigz(); pigzPath == "" {
 | 
						|
		t.Fatal("failed to detect pigz path")
 | 
						|
	} else if pigzPath != fullPath {
 | 
						|
		t.Fatalf("wrong pigz found: %s != %s", pigzPath, fullPath)
 | 
						|
	}
 | 
						|
 | 
						|
	t.Setenv(disablePigzEnv, "1")
 | 
						|
 | 
						|
	if pigzPath := detectPigz(); pigzPath != "" {
 | 
						|
		t.Fatalf("disable via %s doesn't work", disablePigzEnv)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCmdStream(t *testing.T) {
 | 
						|
	out, err := cmdStream(exec.Command("sh", "-c", "echo hello; exit 0"), nil)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	buf, err := io.ReadAll(out)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("failed to read from stdout: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if string(buf) != "hello\n" {
 | 
						|
		t.Fatalf("unexpected command output ('%s' != '%s')", string(buf), "hello\n")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCmdStreamBad(t *testing.T) {
 | 
						|
	out, err := cmdStream(exec.Command("sh", "-c", "echo hello; echo >&2 bad result; exit 1"), nil)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("failed to start command: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if buf, err := io.ReadAll(out); err == nil {
 | 
						|
		t.Fatal("command should have failed")
 | 
						|
	} else if err.Error() != "exit status 1: bad result\n" {
 | 
						|
		t.Fatalf("wrong error: %s", err.Error())
 | 
						|
	} else if string(buf) != "hello\n" {
 | 
						|
		t.Fatalf("wrong output: %s", string(buf))
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestDetectCompressionZstd(t *testing.T) {
 | 
						|
	for _, tc := range []struct {
 | 
						|
		source   []byte
 | 
						|
		expected Compression
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			// test zstd compression without skippable frames.
 | 
						|
			source: []byte{
 | 
						|
				0x28, 0xb5, 0x2f, 0xfd, // magic number of Zstandard frame: 0xFD2FB528
 | 
						|
				0x04, 0x00, 0x31, 0x00, 0x00, // frame header
 | 
						|
				0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, // data block "docker"
 | 
						|
				0x16, 0x0e, 0x21, 0xc3, // content checksum
 | 
						|
			},
 | 
						|
			expected: Zstd,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			// test zstd compression with skippable frames.
 | 
						|
			source: []byte{
 | 
						|
				0x50, 0x2a, 0x4d, 0x18, // magic number of skippable frame: 0x184D2A50 to 0x184D2A5F
 | 
						|
				0x04, 0x00, 0x00, 0x00, // frame size
 | 
						|
				0x5d, 0x00, 0x00, 0x00, // user data
 | 
						|
				0x28, 0xb5, 0x2f, 0xfd, // magic number of Zstandard frame: 0xFD2FB528
 | 
						|
				0x04, 0x00, 0x31, 0x00, 0x00, // frame header
 | 
						|
				0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, // data block "docker"
 | 
						|
				0x16, 0x0e, 0x21, 0xc3, // content checksum
 | 
						|
			},
 | 
						|
			expected: Zstd,
 | 
						|
		},
 | 
						|
	} {
 | 
						|
		compression := DetectCompression(tc.source)
 | 
						|
		if compression != tc.expected {
 | 
						|
			t.Fatalf("Unexpected compression %v, expected %v", compression, tc.expected)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |