diff options
author | 2018-09-27 15:06:19 -0700 | |
---|---|---|
committer | 2018-09-28 13:56:06 -0700 | |
commit | 05518bc13b3cd31e6cb7158a41aa124b224c3bc4 (patch) | |
tree | 65dbf6c09827df7f23d973ef6419fea543580d52 /zip | |
parent | b051ab5cb54f8802807680a8fb24092b445a3ddb (diff) |
soong_zip: Add tests
Add test that cover basic command line usage of soong_zip. -D
is not covered yet as the implementation will be replaced with
one that is also more easily testable in the next patch.
Bug: 116751500
Test: zip_test.go
Change-Id: I5a1bcee74ebc9cb3cf332c36f89bc12c0e807ad2
Diffstat (limited to 'zip')
-rw-r--r-- | zip/cmd/main.go | 2 | ||||
-rw-r--r-- | zip/zip.go | 61 | ||||
-rw-r--r-- | zip/zip_test.go | 385 |
3 files changed, 426 insertions, 22 deletions
diff --git a/zip/cmd/main.go b/zip/cmd/main.go index a2fbf4113..1125602d0 100644 --- a/zip/cmd/main.go +++ b/zip/cmd/main.go @@ -187,7 +187,7 @@ func main() { os.Exit(1) } - err := zip.Run(zip.ZipArgs{ + err := zip.Zip(zip.ZipArgs{ FileArgs: fileArgsBuilder.FileArgs(), OutputFilePath: *out, EmulateJar: *emulateJar, diff --git a/zip/zip.go b/zip/zip.go index 96f4535ae..e7de6f8cc 100644 --- a/zip/zip.go +++ b/zip/zip.go @@ -22,7 +22,6 @@ import ( "hash/crc32" "io" "io/ioutil" - "log" "os" "path/filepath" "sort" @@ -178,6 +177,8 @@ type ZipWriter struct { compressorPool sync.Pool compLevel int + + fs pathtools.FileSystem } type zipEntry struct { @@ -201,6 +202,7 @@ type ZipArgs struct { NumParallelJobs int NonDeflatedFiles map[string]bool WriteIfChanged bool + Filesystem pathtools.FileSystem } const NOQUOTE = '\x00' @@ -246,22 +248,24 @@ func ReadRespFile(bytes []byte) []string { return args } -func Run(args ZipArgs) (err error) { - if args.OutputFilePath == "" { - return fmt.Errorf("output file path must be nonempty") - } - +func ZipTo(args ZipArgs, w io.Writer) error { if args.EmulateJar { args.AddDirectoryEntriesToZip = true } - w := &ZipWriter{ + z := &ZipWriter{ time: jar.DefaultTime, createdDirs: make(map[string]string), createdFiles: make(map[string]string), directories: args.AddDirectoryEntriesToZip, compLevel: args.CompressionLevel, + fs: args.Filesystem, + } + + if z.fs == nil { + z.fs = pathtools.OsFs } + pathMappings := []pathMapping{} noCompression := args.CompressionLevel == 0 @@ -274,11 +278,19 @@ func Run(args ZipArgs) (err error) { for _, src := range srcs { err := fillPathPairs(fa, src, &pathMappings, args.NonDeflatedFiles, noCompression) if err != nil { - log.Fatal(err) + return err } } } + return z.write(w, pathMappings, args.ManifestSourcePath, args.EmulateJar, args.NumParallelJobs) +} + +func Zip(args ZipArgs) error { + if args.OutputFilePath == "" { + return fmt.Errorf("output file path must be nonempty") + } + buf := &bytes.Buffer{} var out io.Writer = buf @@ -298,7 +310,7 @@ func Run(args ZipArgs) (err error) { out = f } - err = w.write(out, pathMappings, args.ManifestSourcePath, args.EmulateJar, args.NumParallelJobs) + err := ZipTo(args, out) if err != nil { return err } @@ -351,13 +363,6 @@ func jarSort(mappings []pathMapping) { sort.SliceStable(mappings, less) } -type readerSeekerCloser interface { - io.Reader - io.ReaderAt - io.Closer - io.Seeker -} - func (z *ZipWriter) write(f io.Writer, pathMappings []pathMapping, manifest string, emulateJar bool, parallelJobs int) error { z.errors = make(chan error) defer close(z.errors) @@ -504,7 +509,7 @@ func (z *ZipWriter) addFile(dest, src string, method uint16, emulateJar bool) er var fileSize int64 var executable bool - if s, err := os.Lstat(src); err != nil { + if s, err := z.fs.Lstat(src); err != nil { return err } else if s.IsDir() { if z.directories { @@ -535,7 +540,7 @@ func (z *ZipWriter) addFile(dest, src string, method uint16, emulateJar bool) er executable = s.Mode()&0100 != 0 } - r, err := os.Open(src) + r, err := z.fs.Open(src) if err != nil { return err } @@ -565,7 +570,21 @@ func (z *ZipWriter) addManifest(dest string, src string, method uint16) error { return err } - fh, buf, err := jar.ManifestFileContents(src) + var contents []byte + if src != "" { + f, err := z.fs.Open(src) + if err != nil { + return err + } + + contents, err = ioutil.ReadAll(f) + f.Close() + if err != nil { + return err + } + } + + fh, buf, err := jar.ManifestFileContents(contents) if err != nil { return err } @@ -575,7 +594,7 @@ func (z *ZipWriter) addManifest(dest string, src string, method uint16) error { return z.writeFileContents(fh, reader) } -func (z *ZipWriter) writeFileContents(header *zip.FileHeader, r readerSeekerCloser) (err error) { +func (z *ZipWriter) writeFileContents(header *zip.FileHeader, r pathtools.ReaderAtSeekerCloser) (err error) { header.SetModTime(z.time) @@ -845,7 +864,7 @@ func (z *ZipWriter) writeSymlink(rel, file string) error { fileHeader.SetModTime(z.time) fileHeader.SetMode(0777 | os.ModeSymlink) - dest, err := os.Readlink(file) + dest, err := z.fs.Readlink(file) if err != nil { return err } diff --git a/zip/zip_test.go b/zip/zip_test.go index 03e7958fb..0c2105c67 100644 --- a/zip/zip_test.go +++ b/zip/zip_test.go @@ -15,10 +15,395 @@ 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, + "a/a/d -> b": nil, + "c": fileC, + "l": []byte("a/a/a\na/a/b\nc\n"), + "l2": []byte("missing\n"), + "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 + + 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: "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, + + 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: "list", + args: fileArgsBuilder(). + List("l"), + 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: "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), + }, + }, + + // errors + { + name: "error missing file", + args: fileArgsBuilder(). + File("missing"), + err: os.ErrNotExist, + }, + { + name: "error missing file in list", + args: fileArgsBuilder(). + List("l2"), + err: os.ErrNotExist, + }, + } + + 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.Filesystem = mockFs + + 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 { + 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 |