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
diff --git a/zip/zip.go b/zip/zip.go
index 96f4535..e7de6f8 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -22,7 +22,6 @@
 	"hash/crc32"
 	"io"
 	"io/ioutil"
-	"log"
 	"os"
 	"path/filepath"
 	"sort"
@@ -178,6 +177,8 @@
 
 	compressorPool sync.Pool
 	compLevel      int
+
+	fs pathtools.FileSystem
 }
 
 type zipEntry struct {
@@ -201,6 +202,7 @@
 	NumParallelJobs          int
 	NonDeflatedFiles         map[string]bool
 	WriteIfChanged           bool
+	Filesystem               pathtools.FileSystem
 }
 
 const NOQUOTE = '\x00'
@@ -246,22 +248,24 @@
 	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 @@
 		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 @@
 		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 @@
 	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 @@
 	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 @@
 		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 @@
 		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 @@
 	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 @@
 	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
 	}