blob: 7fed0f562164b6d9522a917420d100d0f8ccc877 [file]
// Copyright 2026 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// SPDX-License-Identifier: Apache-2.0 OR MIT
package suitar
import (
"archive/tar"
"bytes"
"fmt"
"hash/crc32"
"io"
"os"
"testing"
)
type crcWriter uint32
func (c *crcWriter) Write(b []byte) (int, error) {
state := uint32(*c)
state = crc32.Update(state, crc32.IEEETable, b)
*c = crcWriter(state)
return len(b), nil
}
func testWriter(tt *testing.T, sparse bool) {
f, err := os.Open("../../test/data/archive.tar")
if err != nil {
tt.Fatalf("os.Open: %v", err)
}
defer f.Close()
// Convert from t (tar, using the standard library) to s (suitar, using
// this package).
buf := bytes.Buffer{}
sWriter := NewWriter(&buf)
for tReader := tar.NewReader(f); ; {
tHeader, err := tReader.Next()
if err == io.EOF {
break
} else if err != nil {
tt.Fatalf("Next: %v", err)
}
sHeader := &Header{
Typeflag: tHeader.Typeflag,
Name: tHeader.Name,
Size: tHeader.Size,
Mode: tHeader.Mode,
ModTime: tHeader.ModTime,
}
if sparse && (sHeader.Typeflag == TypeReg) {
sHeader.Typeflag = TypeGNUSparse
}
if err := sWriter.WriteHeader(sHeader); err != nil {
tt.Fatalf("WriteHeader: %v", err)
}
dstWriter := (io.Writer)(sWriter)
if sparse {
dstWriter = io.Discard
}
if _, err := io.Copy(dstWriter, tReader); err != nil {
tt.Fatalf("io.Copy: %v", err)
}
}
if err := sWriter.Close(); err != nil {
tt.Fatalf("Close: %v", err)
}
got := buf.Bytes()
wantFilename := "../../test/data/archive"
if sparse {
wantFilename += ".sparse.suitar"
} else {
wantFilename += ".dense.suitar"
}
want, err := os.ReadFile(wantFilename)
if err != nil {
tt.Fatalf("os.ReadFile: %v", err)
}
if !bytes.Equal(got, want) {
tt.Fatalf("did not recreate golden test file")
}
}
func testReader(tt *testing.T, sparse bool, ignore bool) {
filename, wantTypeflag := "../../test/data/archive.dense.suitar", " T:'0'"
if sparse {
filename, wantTypeflag = "../../test/data/archive.sparse.suitar", " T:'S'"
}
wantChecksums := []string(nil)
if ignore {
wantChecksums = []string{
"C:0x00000000",
"C:0x00000000",
"C:0x00000000",
"C:0x00000000",
"C:0x00000000",
"C:0x00000000",
"C:0x00000000",
"C:0x00000000",
}
} else if sparse {
wantChecksums = []string{
"C:0x00000000",
"C:0xB69F8E37",
"C:0x73FF3CAE",
"C:0xD71F022F",
"C:0xC446EAB8",
"C:0x5F228EB9",
"C:0x7BA7D011",
"C:0x48792A7F",
}
} else {
wantChecksums = []string{
"C:0x00000000",
"C:0xFEDD8F35",
"C:0x87EE5E05",
"C:0x703E9270",
"C:0xC37CB538",
"C:0x2B0B23B0",
"C:0xABE507EF",
"C:0x67FABE9C",
}
}
want := "" +
wantChecksums[0] + wantTypeflag + " S:0x0000 M:644 MT:0x5E3A5C50 N:artificial/0.bytes\n" +
wantChecksums[1] + wantTypeflag + " S:0x0355 M:644 MT:0x5F33F6E6 N:github-tags.json\n" +
wantChecksums[2] + wantTypeflag + " S:0x02B5 M:755 MT:0x608F960B N:hello.sh\n" +
wantChecksums[3] + wantTypeflag + " S:0x0068 M:644 MT:0x608F954D N:non-ascii/αβ.txt\n" +
wantChecksums[4] + wantTypeflag + " S:0x0097 M:644 MT:0x608F96C7 N:non-ascii/😻.txt\n" +
wantChecksums[5] + wantTypeflag + " S:0x00D0 M:644 MT:0x5E3A5C50 N:pjw-thumbnail.png\n" +
wantChecksums[6] + wantTypeflag + " S:0x03AE M:644 MT:0x5E3A5C50 N:romeo.txt\n" +
wantChecksums[7] + wantTypeflag + " S:0x022E M:644 MT:0x5E3A5C50 N:romeo.txt.gz\n" +
""
f, err := os.Open(filename)
if err != nil {
tt.Fatalf("os.Open: %v", err)
}
defer f.Close()
buf := bytes.Buffer{}
for r := NewReader(f); ; {
h, err := r.Next()
if err == io.EOF {
break
} else if err != nil {
tt.Fatalf("Next: %v", err)
}
checksum := crcWriter(0)
if !ignore {
if _, err := io.Copy(&checksum, r); err != nil {
tt.Fatalf("io.Copy: %v", err)
}
}
fmt.Fprintf(&buf, "C:0x%08X T:'%c' S:0x%04X M:%3o MT:0x%08X N:%s\n",
checksum, h.Typeflag, h.Size, h.Mode, h.ModTime.Unix(), h.Name)
}
if got := buf.String(); got != want {
tt.Fatalf("\ngot:\n%s\nwant:\n%s", got, want)
}
}
func TestWriterDense(tt *testing.T) { testWriter(tt, false) }
func TestWriterSparse(tt *testing.T) { testWriter(tt, true) }
func TestReaderDenseCheck(tt *testing.T) { testReader(tt, false, false) }
func TestReaderDenseIgnore(tt *testing.T) { testReader(tt, false, true) }
func TestReaderSparseCheck(tt *testing.T) { testReader(tt, true, false) }
func TestReaderSparseIgnore(tt *testing.T) { testReader(tt, true, true) }