blob: 302a749a3d374be22320a461ba5bead88f8202e6 [file] [log] [blame]
// Copyright 2018 Google Inc. All rights reserved.
//
// 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 zip
import (
"bytes"
"hash/crc32"
"io"
"os"
"reflect"
"syscall"
"testing"
"android/soong/third_party/zip"
"github.com/google/blueprint/pathtools"
)
var (
fileA = []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
fileB = []byte("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB")
fileC = []byte("CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC")
fileEmpty = []byte("")
fileManifest = []byte("Manifest-Version: 1.0\nCreated-By: soong_zip\n\n")
fileCustomManifest = []byte("Custom manifest: true\n")
customManifestAfter = []byte("Manifest-Version: 1.0\nCreated-By: soong_zip\nCustom manifest: true\n\n")
)
var mockFs = pathtools.MockFs(map[string][]byte{
"a/a/a": fileA,
"a/a/b": fileB,
"a/a/c -> ../../c": nil,
"dangling -> missing": nil,
"a/a/d -> b": nil,
"c": fileC,
"l_nl": []byte("a/a/a\na/a/b\nc\n"),
"l_sp": []byte("a/a/a a/a/b c"),
"l2": []byte("missing\n"),
"rsp": []byte("'a/a/a'\na/a/b\n'@'\n'foo'\\''bar'"),
"@ -> c": nil,
"foo'bar -> c": nil,
"manifest.txt": fileCustomManifest,
})
func fh(name string, contents []byte, method uint16) zip.FileHeader {
return zip.FileHeader{
Name: name,
Method: method,
CRC32: crc32.ChecksumIEEE(contents),
UncompressedSize64: uint64(len(contents)),
ExternalAttrs: 0,
}
}
func fhManifest(contents []byte) zip.FileHeader {
return zip.FileHeader{
Name: "META-INF/MANIFEST.MF",
Method: zip.Store,
CRC32: crc32.ChecksumIEEE(contents),
UncompressedSize64: uint64(len(contents)),
ExternalAttrs: (syscall.S_IFREG | 0700) << 16,
}
}
func fhLink(name string, to string) zip.FileHeader {
return zip.FileHeader{
Name: name,
Method: zip.Store,
CRC32: crc32.ChecksumIEEE([]byte(to)),
UncompressedSize64: uint64(len(to)),
ExternalAttrs: (syscall.S_IFLNK | 0777) << 16,
}
}
func fhDir(name string) zip.FileHeader {
return zip.FileHeader{
Name: name,
Method: zip.Store,
CRC32: crc32.ChecksumIEEE(nil),
UncompressedSize64: 0,
ExternalAttrs: (syscall.S_IFDIR|0700)<<16 | 0x10,
}
}
func fileArgsBuilder() *FileArgsBuilder {
return &FileArgsBuilder{
fs: mockFs,
}
}
func TestZip(t *testing.T) {
testCases := []struct {
name string
args *FileArgsBuilder
compressionLevel int
emulateJar bool
nonDeflatedFiles map[string]bool
dirEntries bool
manifest string
storeSymlinks bool
ignoreMissingFiles bool
files []zip.FileHeader
err error
}{
{
name: "empty args",
args: fileArgsBuilder(),
files: []zip.FileHeader{},
},
{
name: "files",
args: fileArgsBuilder().
File("a/a/a").
File("a/a/b").
File("c"),
compressionLevel: 9,
files: []zip.FileHeader{
fh("a/a/a", fileA, zip.Deflate),
fh("a/a/b", fileB, zip.Deflate),
fh("c", fileC, zip.Deflate),
},
},
{
name: "files glob",
args: fileArgsBuilder().
SourcePrefixToStrip("a").
File("a/**/*"),
compressionLevel: 9,
storeSymlinks: true,
files: []zip.FileHeader{
fh("a/a", fileA, zip.Deflate),
fh("a/b", fileB, zip.Deflate),
fhLink("a/c", "../../c"),
fhLink("a/d", "b"),
},
},
{
name: "dir",
args: fileArgsBuilder().
SourcePrefixToStrip("a").
Dir("a"),
compressionLevel: 9,
storeSymlinks: true,
files: []zip.FileHeader{
fh("a/a", fileA, zip.Deflate),
fh("a/b", fileB, zip.Deflate),
fhLink("a/c", "../../c"),
fhLink("a/d", "b"),
},
},
{
name: "stored files",
args: fileArgsBuilder().
File("a/a/a").
File("a/a/b").
File("c"),
compressionLevel: 0,
files: []zip.FileHeader{
fh("a/a/a", fileA, zip.Store),
fh("a/a/b", fileB, zip.Store),
fh("c", fileC, zip.Store),
},
},
{
name: "symlinks in zip",
args: fileArgsBuilder().
File("a/a/a").
File("a/a/b").
File("a/a/c").
File("a/a/d"),
compressionLevel: 9,
storeSymlinks: true,
files: []zip.FileHeader{
fh("a/a/a", fileA, zip.Deflate),
fh("a/a/b", fileB, zip.Deflate),
fhLink("a/a/c", "../../c"),
fhLink("a/a/d", "b"),
},
},
{
name: "follow symlinks",
args: fileArgsBuilder().
File("a/a/a").
File("a/a/b").
File("a/a/c").
File("a/a/d"),
compressionLevel: 9,
storeSymlinks: false,
files: []zip.FileHeader{
fh("a/a/a", fileA, zip.Deflate),
fh("a/a/b", fileB, zip.Deflate),
fh("a/a/c", fileC, zip.Deflate),
fh("a/a/d", fileB, zip.Deflate),
},
},
{
name: "dangling symlinks",
args: fileArgsBuilder().
File("dangling"),
compressionLevel: 9,
storeSymlinks: true,
files: []zip.FileHeader{
fhLink("dangling", "missing"),
},
},
{
name: "list",
args: fileArgsBuilder().
List("l_nl"),
compressionLevel: 9,
files: []zip.FileHeader{
fh("a/a/a", fileA, zip.Deflate),
fh("a/a/b", fileB, zip.Deflate),
fh("c", fileC, zip.Deflate),
},
},
{
name: "list",
args: fileArgsBuilder().
List("l_sp"),
compressionLevel: 9,
files: []zip.FileHeader{
fh("a/a/a", fileA, zip.Deflate),
fh("a/a/b", fileB, zip.Deflate),
fh("c", fileC, zip.Deflate),
},
},
{
name: "rsp",
args: fileArgsBuilder().
RspFile("rsp"),
compressionLevel: 9,
files: []zip.FileHeader{
fh("a/a/a", fileA, zip.Deflate),
fh("a/a/b", fileB, zip.Deflate),
fh("@", fileC, zip.Deflate),
fh("foo'bar", fileC, zip.Deflate),
},
},
{
name: "prefix in zip",
args: fileArgsBuilder().
PathPrefixInZip("foo").
File("a/a/a").
File("a/a/b").
File("c"),
compressionLevel: 9,
files: []zip.FileHeader{
fh("foo/a/a/a", fileA, zip.Deflate),
fh("foo/a/a/b", fileB, zip.Deflate),
fh("foo/c", fileC, zip.Deflate),
},
},
{
name: "relative root",
args: fileArgsBuilder().
SourcePrefixToStrip("a").
File("a/a/a").
File("a/a/b"),
compressionLevel: 9,
files: []zip.FileHeader{
fh("a/a", fileA, zip.Deflate),
fh("a/b", fileB, zip.Deflate),
},
},
{
name: "multiple relative root",
args: fileArgsBuilder().
SourcePrefixToStrip("a").
File("a/a/a").
SourcePrefixToStrip("a/a").
File("a/a/b"),
compressionLevel: 9,
files: []zip.FileHeader{
fh("a/a", fileA, zip.Deflate),
fh("b", fileB, zip.Deflate),
},
},
{
name: "emulate jar",
args: fileArgsBuilder().
File("a/a/a").
File("a/a/b"),
compressionLevel: 9,
emulateJar: true,
files: []zip.FileHeader{
fhDir("META-INF/"),
fhManifest(fileManifest),
fhDir("a/"),
fhDir("a/a/"),
fh("a/a/a", fileA, zip.Deflate),
fh("a/a/b", fileB, zip.Deflate),
},
},
{
name: "emulate jar with manifest",
args: fileArgsBuilder().
File("a/a/a").
File("a/a/b"),
compressionLevel: 9,
emulateJar: true,
manifest: "manifest.txt",
files: []zip.FileHeader{
fhDir("META-INF/"),
fhManifest(customManifestAfter),
fhDir("a/"),
fhDir("a/a/"),
fh("a/a/a", fileA, zip.Deflate),
fh("a/a/b", fileB, zip.Deflate),
},
},
{
name: "dir entries",
args: fileArgsBuilder().
File("a/a/a").
File("a/a/b"),
compressionLevel: 9,
dirEntries: true,
files: []zip.FileHeader{
fhDir("a/"),
fhDir("a/a/"),
fh("a/a/a", fileA, zip.Deflate),
fh("a/a/b", fileB, zip.Deflate),
},
},
{
name: "junk paths",
args: fileArgsBuilder().
JunkPaths(true).
File("a/a/a").
File("a/a/b"),
compressionLevel: 9,
files: []zip.FileHeader{
fh("a", fileA, zip.Deflate),
fh("b", fileB, zip.Deflate),
},
},
{
name: "non deflated files",
args: fileArgsBuilder().
File("a/a/a").
File("a/a/b"),
compressionLevel: 9,
nonDeflatedFiles: map[string]bool{"a/a/a": true},
files: []zip.FileHeader{
fh("a/a/a", fileA, zip.Store),
fh("a/a/b", fileB, zip.Deflate),
},
},
{
name: "ignore missing files",
args: fileArgsBuilder().
File("a/a/a").
File("a/a/b").
File("missing"),
compressionLevel: 9,
ignoreMissingFiles: true,
files: []zip.FileHeader{
fh("a/a/a", fileA, zip.Deflate),
fh("a/a/b", fileB, zip.Deflate),
},
},
// errors
{
name: "error missing file",
args: fileArgsBuilder().
File("missing"),
err: os.ErrNotExist,
},
{
name: "error missing dir",
args: fileArgsBuilder().
Dir("missing"),
err: os.ErrNotExist,
},
{
name: "error missing file in list",
args: fileArgsBuilder().
List("l2"),
err: os.ErrNotExist,
},
{
name: "error incorrect relative root",
args: fileArgsBuilder().
SourcePrefixToStrip("b").
File("a/a/a"),
err: IncorrectRelativeRootError{},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
if test.args.Error() != nil {
t.Fatal(test.args.Error())
}
args := ZipArgs{}
args.FileArgs = test.args.FileArgs()
args.CompressionLevel = test.compressionLevel
args.EmulateJar = test.emulateJar
args.AddDirectoryEntriesToZip = test.dirEntries
args.NonDeflatedFiles = test.nonDeflatedFiles
args.ManifestSourcePath = test.manifest
args.StoreSymlinks = test.storeSymlinks
args.IgnoreMissingFiles = test.ignoreMissingFiles
args.Filesystem = mockFs
args.Stderr = &bytes.Buffer{}
buf := &bytes.Buffer{}
err := ZipTo(args, buf)
if (err != nil) != (test.err != nil) {
t.Fatalf("want error %v, got %v", test.err, err)
} else if test.err != nil {
if os.IsNotExist(test.err) {
if !os.IsNotExist(test.err) {
t.Fatalf("want error %v, got %v", test.err, err)
}
} else if _, wantRelativeRootErr := test.err.(IncorrectRelativeRootError); wantRelativeRootErr {
if _, gotRelativeRootErr := err.(IncorrectRelativeRootError); !gotRelativeRootErr {
t.Fatalf("want error %v, got %v", test.err, err)
}
} else {
t.Fatalf("want error %v, got %v", test.err, err)
}
return
}
br := bytes.NewReader(buf.Bytes())
zr, err := zip.NewReader(br, int64(br.Len()))
if err != nil {
t.Fatal(err)
}
var files []zip.FileHeader
for _, f := range zr.File {
r, err := f.Open()
if err != nil {
t.Fatalf("error when opening %s: %s", f.Name, err)
}
crc := crc32.NewIEEE()
len, err := io.Copy(crc, r)
r.Close()
if err != nil {
t.Fatalf("error when reading %s: %s", f.Name, err)
}
if uint64(len) != f.UncompressedSize64 {
t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
}
if crc.Sum32() != f.CRC32 {
t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
}
files = append(files, f.FileHeader)
}
if len(files) != len(test.files) {
t.Fatalf("want %d files, got %d", len(test.files), len(files))
}
for i := range files {
want := test.files[i]
got := files[i]
if want.Name != got.Name {
t.Errorf("incorrect file %d want %q got %q", i, want.Name, got.Name)
continue
}
if want.UncompressedSize64 != got.UncompressedSize64 {
t.Errorf("incorrect file %s length want %v got %v", want.Name,
want.UncompressedSize64, got.UncompressedSize64)
}
if want.ExternalAttrs != got.ExternalAttrs {
t.Errorf("incorrect file %s attrs want %x got %x", want.Name,
want.ExternalAttrs, got.ExternalAttrs)
}
if want.CRC32 != got.CRC32 {
t.Errorf("incorrect file %s crc want %v got %v", want.Name,
want.CRC32, got.CRC32)
}
if want.Method != got.Method {
t.Errorf("incorrect file %s method want %v got %v", want.Name,
want.Method, got.Method)
}
}
})
}
}
func TestReadRespFile(t *testing.T) {
testCases := []struct {
name, in string
out []string
}{
{
name: "single quoting test case 1",
in: `./cmd '"'-C`,
out: []string{"./cmd", `"-C`},
},
{
name: "single quoting test case 2",
in: `./cmd '-C`,
out: []string{"./cmd", `-C`},
},
{
name: "single quoting test case 3",
in: `./cmd '\"'-C`,
out: []string{"./cmd", `\"-C`},
},
{
name: "single quoting test case 4",
in: `./cmd '\\'-C`,
out: []string{"./cmd", `\\-C`},
},
{
name: "none quoting test case 1",
in: `./cmd \'-C`,
out: []string{"./cmd", `'-C`},
},
{
name: "none quoting test case 2",
in: `./cmd \\-C`,
out: []string{"./cmd", `\-C`},
},
{
name: "none quoting test case 3",
in: `./cmd \"-C`,
out: []string{"./cmd", `"-C`},
},
{
name: "double quoting test case 1",
in: `./cmd "'"-C`,
out: []string{"./cmd", `'-C`},
},
{
name: "double quoting test case 2",
in: `./cmd "\\"-C`,
out: []string{"./cmd", `\-C`},
},
{
name: "double quoting test case 3",
in: `./cmd "\""-C`,
out: []string{"./cmd", `"-C`},
},
{
name: "ninja rsp file",
in: "'a'\nb\n'@'\n'foo'\\''bar'\n'foo\"bar'",
out: []string{"a", "b", "@", "foo'bar", `foo"bar`},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
got := ReadRespFile([]byte(testCase.in))
if !reflect.DeepEqual(got, testCase.out) {
t.Errorf("expected %q got %q", testCase.out, got)
}
})
}
}
func TestSrcJar(t *testing.T) {
mockFs := pathtools.MockFs(map[string][]byte{
"wrong_package.java": []byte("package foo;"),
"foo/correct_package.java": []byte("package foo;"),
"src/no_package.java": nil,
"src2/parse_error.java": []byte("error"),
})
want := []string{
"foo/",
"foo/wrong_package.java",
"foo/correct_package.java",
"no_package.java",
"src2/",
"src2/parse_error.java",
}
args := ZipArgs{}
args.FileArgs = NewFileArgsBuilder().File("**/*.java").FileArgs()
args.SrcJar = true
args.AddDirectoryEntriesToZip = true
args.Filesystem = mockFs
args.Stderr = &bytes.Buffer{}
buf := &bytes.Buffer{}
err := ZipTo(args, buf)
if err != nil {
t.Fatalf("got error %v", err)
}
br := bytes.NewReader(buf.Bytes())
zr, err := zip.NewReader(br, int64(br.Len()))
if err != nil {
t.Fatal(err)
}
var got []string
for _, f := range zr.File {
r, err := f.Open()
if err != nil {
t.Fatalf("error when opening %s: %s", f.Name, err)
}
crc := crc32.NewIEEE()
len, err := io.Copy(crc, r)
r.Close()
if err != nil {
t.Fatalf("error when reading %s: %s", f.Name, err)
}
if uint64(len) != f.UncompressedSize64 {
t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
}
if crc.Sum32() != f.CRC32 {
t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
}
got = append(got, f.Name)
}
if !reflect.DeepEqual(want, got) {
t.Errorf("want files %q, got %q", want, got)
}
}