blob: 84317d1b6a09098408e2a26d3d533d43104b1264 [file] [log] [blame]
Nan Zhang674dd932018-01-26 18:30:36 -08001// Copyright 2018 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package zip
16
17import (
Colin Cross05518bc2018-09-27 15:06:19 -070018 "bytes"
19 "hash/crc32"
20 "io"
21 "os"
Nan Zhang674dd932018-01-26 18:30:36 -080022 "reflect"
Colin Cross05518bc2018-09-27 15:06:19 -070023 "syscall"
Nan Zhang674dd932018-01-26 18:30:36 -080024 "testing"
Colin Cross05518bc2018-09-27 15:06:19 -070025
26 "android/soong/third_party/zip"
27
28 "github.com/google/blueprint/pathtools"
Nan Zhang674dd932018-01-26 18:30:36 -080029)
30
Colin Cross05518bc2018-09-27 15:06:19 -070031var (
32 fileA = []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
33 fileB = []byte("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB")
34 fileC = []byte("CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC")
35 fileEmpty = []byte("")
36 fileManifest = []byte("Manifest-Version: 1.0\nCreated-By: soong_zip\n\n")
37
38 fileCustomManifest = []byte("Custom manifest: true\n")
39 customManifestAfter = []byte("Manifest-Version: 1.0\nCreated-By: soong_zip\nCustom manifest: true\n\n")
40)
41
42var mockFs = pathtools.MockFs(map[string][]byte{
Colin Cross9cb51db2019-06-17 14:12:41 -070043 "a/a/a": fileA,
44 "a/a/b": fileB,
45 "a/a/c -> ../../c": nil,
46 "dangling -> missing": nil,
47 "a/a/d -> b": nil,
48 "c": fileC,
49 "l": []byte("a/a/a\na/a/b\nc\n"),
50 "l2": []byte("missing\n"),
51 "manifest.txt": fileCustomManifest,
Colin Cross05518bc2018-09-27 15:06:19 -070052})
53
54func fh(name string, contents []byte, method uint16) zip.FileHeader {
55 return zip.FileHeader{
56 Name: name,
57 Method: method,
58 CRC32: crc32.ChecksumIEEE(contents),
59 UncompressedSize64: uint64(len(contents)),
60 ExternalAttrs: 0,
61 }
62}
63
64func fhManifest(contents []byte) zip.FileHeader {
65 return zip.FileHeader{
66 Name: "META-INF/MANIFEST.MF",
67 Method: zip.Store,
68 CRC32: crc32.ChecksumIEEE(contents),
69 UncompressedSize64: uint64(len(contents)),
70 ExternalAttrs: (syscall.S_IFREG | 0700) << 16,
71 }
72}
73
74func fhLink(name string, to string) zip.FileHeader {
75 return zip.FileHeader{
76 Name: name,
77 Method: zip.Store,
78 CRC32: crc32.ChecksumIEEE([]byte(to)),
79 UncompressedSize64: uint64(len(to)),
80 ExternalAttrs: (syscall.S_IFLNK | 0777) << 16,
81 }
82}
83
84func fhDir(name string) zip.FileHeader {
85 return zip.FileHeader{
86 Name: name,
87 Method: zip.Store,
88 CRC32: crc32.ChecksumIEEE(nil),
89 UncompressedSize64: 0,
90 ExternalAttrs: (syscall.S_IFDIR|0700)<<16 | 0x10,
91 }
92}
93
94func fileArgsBuilder() *FileArgsBuilder {
95 return &FileArgsBuilder{
96 fs: mockFs,
97 }
98}
99
100func TestZip(t *testing.T) {
101 testCases := []struct {
Colin Cross4be8f9e2018-09-28 15:16:48 -0700102 name string
103 args *FileArgsBuilder
104 compressionLevel int
105 emulateJar bool
106 nonDeflatedFiles map[string]bool
107 dirEntries bool
108 manifest string
109 storeSymlinks bool
110 ignoreMissingFiles bool
Colin Cross05518bc2018-09-27 15:06:19 -0700111
112 files []zip.FileHeader
113 err error
114 }{
115 {
116 name: "empty args",
117 args: fileArgsBuilder(),
118
119 files: []zip.FileHeader{},
120 },
121 {
122 name: "files",
123 args: fileArgsBuilder().
124 File("a/a/a").
125 File("a/a/b").
126 File("c"),
127 compressionLevel: 9,
128
129 files: []zip.FileHeader{
130 fh("a/a/a", fileA, zip.Deflate),
131 fh("a/a/b", fileB, zip.Deflate),
132 fh("c", fileC, zip.Deflate),
133 },
134 },
135 {
Colin Cross1d98ee22018-09-18 17:05:15 -0700136 name: "files glob",
137 args: fileArgsBuilder().
138 SourcePrefixToStrip("a").
139 File("a/**/*"),
140 compressionLevel: 9,
Colin Cross09f11052018-09-21 15:12:39 -0700141 storeSymlinks: true,
Colin Cross1d98ee22018-09-18 17:05:15 -0700142
143 files: []zip.FileHeader{
144 fh("a/a", fileA, zip.Deflate),
145 fh("a/b", fileB, zip.Deflate),
146 fhLink("a/c", "../../c"),
147 fhLink("a/d", "b"),
148 },
149 },
150 {
151 name: "dir",
152 args: fileArgsBuilder().
153 SourcePrefixToStrip("a").
154 Dir("a"),
155 compressionLevel: 9,
Colin Cross09f11052018-09-21 15:12:39 -0700156 storeSymlinks: true,
Colin Cross1d98ee22018-09-18 17:05:15 -0700157
158 files: []zip.FileHeader{
159 fh("a/a", fileA, zip.Deflate),
160 fh("a/b", fileB, zip.Deflate),
161 fhLink("a/c", "../../c"),
162 fhLink("a/d", "b"),
163 },
164 },
165 {
Colin Cross05518bc2018-09-27 15:06:19 -0700166 name: "stored files",
167 args: fileArgsBuilder().
168 File("a/a/a").
169 File("a/a/b").
170 File("c"),
171 compressionLevel: 0,
172
173 files: []zip.FileHeader{
174 fh("a/a/a", fileA, zip.Store),
175 fh("a/a/b", fileB, zip.Store),
176 fh("c", fileC, zip.Store),
177 },
178 },
179 {
180 name: "symlinks in zip",
181 args: fileArgsBuilder().
182 File("a/a/a").
183 File("a/a/b").
184 File("a/a/c").
185 File("a/a/d"),
186 compressionLevel: 9,
Colin Cross09f11052018-09-21 15:12:39 -0700187 storeSymlinks: true,
Colin Cross05518bc2018-09-27 15:06:19 -0700188
189 files: []zip.FileHeader{
190 fh("a/a/a", fileA, zip.Deflate),
191 fh("a/a/b", fileB, zip.Deflate),
192 fhLink("a/a/c", "../../c"),
193 fhLink("a/a/d", "b"),
194 },
195 },
196 {
Colin Cross09f11052018-09-21 15:12:39 -0700197 name: "follow symlinks",
198 args: fileArgsBuilder().
199 File("a/a/a").
200 File("a/a/b").
201 File("a/a/c").
202 File("a/a/d"),
203 compressionLevel: 9,
204 storeSymlinks: false,
205
206 files: []zip.FileHeader{
207 fh("a/a/a", fileA, zip.Deflate),
208 fh("a/a/b", fileB, zip.Deflate),
209 fh("a/a/c", fileC, zip.Deflate),
210 fh("a/a/d", fileB, zip.Deflate),
211 },
212 },
213 {
Colin Cross9cb51db2019-06-17 14:12:41 -0700214 name: "dangling symlinks",
215 args: fileArgsBuilder().
216 File("dangling"),
217 compressionLevel: 9,
218 storeSymlinks: true,
219
220 files: []zip.FileHeader{
221 fhLink("dangling", "missing"),
222 },
223 },
224 {
Colin Cross05518bc2018-09-27 15:06:19 -0700225 name: "list",
226 args: fileArgsBuilder().
227 List("l"),
228 compressionLevel: 9,
229
230 files: []zip.FileHeader{
231 fh("a/a/a", fileA, zip.Deflate),
232 fh("a/a/b", fileB, zip.Deflate),
233 fh("c", fileC, zip.Deflate),
234 },
235 },
236 {
237 name: "prefix in zip",
238 args: fileArgsBuilder().
239 PathPrefixInZip("foo").
240 File("a/a/a").
241 File("a/a/b").
242 File("c"),
243 compressionLevel: 9,
244
245 files: []zip.FileHeader{
246 fh("foo/a/a/a", fileA, zip.Deflate),
247 fh("foo/a/a/b", fileB, zip.Deflate),
248 fh("foo/c", fileC, zip.Deflate),
249 },
250 },
251 {
252 name: "relative root",
253 args: fileArgsBuilder().
254 SourcePrefixToStrip("a").
255 File("a/a/a").
256 File("a/a/b"),
257 compressionLevel: 9,
258
259 files: []zip.FileHeader{
260 fh("a/a", fileA, zip.Deflate),
261 fh("a/b", fileB, zip.Deflate),
262 },
263 },
264 {
265 name: "multiple relative root",
266 args: fileArgsBuilder().
267 SourcePrefixToStrip("a").
268 File("a/a/a").
269 SourcePrefixToStrip("a/a").
270 File("a/a/b"),
271 compressionLevel: 9,
272
273 files: []zip.FileHeader{
274 fh("a/a", fileA, zip.Deflate),
275 fh("b", fileB, zip.Deflate),
276 },
277 },
278 {
279 name: "emulate jar",
280 args: fileArgsBuilder().
281 File("a/a/a").
282 File("a/a/b"),
283 compressionLevel: 9,
284 emulateJar: true,
285
286 files: []zip.FileHeader{
287 fhDir("META-INF/"),
288 fhManifest(fileManifest),
289 fhDir("a/"),
290 fhDir("a/a/"),
291 fh("a/a/a", fileA, zip.Deflate),
292 fh("a/a/b", fileB, zip.Deflate),
293 },
294 },
295 {
296 name: "emulate jar with manifest",
297 args: fileArgsBuilder().
298 File("a/a/a").
299 File("a/a/b"),
300 compressionLevel: 9,
301 emulateJar: true,
302 manifest: "manifest.txt",
303
304 files: []zip.FileHeader{
305 fhDir("META-INF/"),
306 fhManifest(customManifestAfter),
307 fhDir("a/"),
308 fhDir("a/a/"),
309 fh("a/a/a", fileA, zip.Deflate),
310 fh("a/a/b", fileB, zip.Deflate),
311 },
312 },
313 {
314 name: "dir entries",
315 args: fileArgsBuilder().
316 File("a/a/a").
317 File("a/a/b"),
318 compressionLevel: 9,
319 dirEntries: true,
320
321 files: []zip.FileHeader{
322 fhDir("a/"),
323 fhDir("a/a/"),
324 fh("a/a/a", fileA, zip.Deflate),
325 fh("a/a/b", fileB, zip.Deflate),
326 },
327 },
328 {
329 name: "junk paths",
330 args: fileArgsBuilder().
331 JunkPaths(true).
332 File("a/a/a").
333 File("a/a/b"),
334 compressionLevel: 9,
335
336 files: []zip.FileHeader{
337 fh("a", fileA, zip.Deflate),
338 fh("b", fileB, zip.Deflate),
339 },
340 },
341 {
342 name: "non deflated files",
343 args: fileArgsBuilder().
344 File("a/a/a").
345 File("a/a/b"),
346 compressionLevel: 9,
347 nonDeflatedFiles: map[string]bool{"a/a/a": true},
348
349 files: []zip.FileHeader{
350 fh("a/a/a", fileA, zip.Store),
351 fh("a/a/b", fileB, zip.Deflate),
352 },
353 },
Colin Cross4be8f9e2018-09-28 15:16:48 -0700354 {
355 name: "ignore missing files",
356 args: fileArgsBuilder().
357 File("a/a/a").
358 File("a/a/b").
359 File("missing"),
360 compressionLevel: 9,
361 ignoreMissingFiles: true,
362
363 files: []zip.FileHeader{
364 fh("a/a/a", fileA, zip.Deflate),
365 fh("a/a/b", fileB, zip.Deflate),
366 },
367 },
Colin Cross05518bc2018-09-27 15:06:19 -0700368
369 // errors
370 {
371 name: "error missing file",
372 args: fileArgsBuilder().
373 File("missing"),
374 err: os.ErrNotExist,
375 },
376 {
Colin Cross1d98ee22018-09-18 17:05:15 -0700377 name: "error missing dir",
378 args: fileArgsBuilder().
379 Dir("missing"),
380 err: os.ErrNotExist,
381 },
382 {
Colin Cross05518bc2018-09-27 15:06:19 -0700383 name: "error missing file in list",
384 args: fileArgsBuilder().
385 List("l2"),
386 err: os.ErrNotExist,
387 },
Colin Cross1d98ee22018-09-18 17:05:15 -0700388 {
389 name: "error incorrect relative root",
390 args: fileArgsBuilder().
391 SourcePrefixToStrip("b").
392 File("a/a/a"),
393 err: IncorrectRelativeRootError{},
394 },
Colin Cross05518bc2018-09-27 15:06:19 -0700395 }
396
397 for _, test := range testCases {
398 t.Run(test.name, func(t *testing.T) {
399 if test.args.Error() != nil {
400 t.Fatal(test.args.Error())
401 }
402
403 args := ZipArgs{}
404 args.FileArgs = test.args.FileArgs()
405 args.CompressionLevel = test.compressionLevel
406 args.EmulateJar = test.emulateJar
407 args.AddDirectoryEntriesToZip = test.dirEntries
408 args.NonDeflatedFiles = test.nonDeflatedFiles
409 args.ManifestSourcePath = test.manifest
Colin Cross09f11052018-09-21 15:12:39 -0700410 args.StoreSymlinks = test.storeSymlinks
Colin Cross4be8f9e2018-09-28 15:16:48 -0700411 args.IgnoreMissingFiles = test.ignoreMissingFiles
Colin Cross05518bc2018-09-27 15:06:19 -0700412 args.Filesystem = mockFs
Colin Cross4be8f9e2018-09-28 15:16:48 -0700413 args.Stderr = &bytes.Buffer{}
Colin Cross05518bc2018-09-27 15:06:19 -0700414
415 buf := &bytes.Buffer{}
416 err := ZipTo(args, buf)
417
418 if (err != nil) != (test.err != nil) {
419 t.Fatalf("want error %v, got %v", test.err, err)
420 } else if test.err != nil {
421 if os.IsNotExist(test.err) {
422 if !os.IsNotExist(test.err) {
423 t.Fatalf("want error %v, got %v", test.err, err)
424 }
Colin Cross1d98ee22018-09-18 17:05:15 -0700425 } else if _, wantRelativeRootErr := test.err.(IncorrectRelativeRootError); wantRelativeRootErr {
426 if _, gotRelativeRootErr := err.(IncorrectRelativeRootError); !gotRelativeRootErr {
427 t.Fatalf("want error %v, got %v", test.err, err)
428 }
Colin Cross05518bc2018-09-27 15:06:19 -0700429 } else {
430 t.Fatalf("want error %v, got %v", test.err, err)
431 }
432 return
433 }
434
435 br := bytes.NewReader(buf.Bytes())
436 zr, err := zip.NewReader(br, int64(br.Len()))
437 if err != nil {
438 t.Fatal(err)
439 }
440
441 var files []zip.FileHeader
442 for _, f := range zr.File {
443 r, err := f.Open()
444 if err != nil {
445 t.Fatalf("error when opening %s: %s", f.Name, err)
446 }
447
448 crc := crc32.NewIEEE()
449 len, err := io.Copy(crc, r)
450 r.Close()
451 if err != nil {
452 t.Fatalf("error when reading %s: %s", f.Name, err)
453 }
454
455 if uint64(len) != f.UncompressedSize64 {
456 t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
457 }
458
459 if crc.Sum32() != f.CRC32 {
460 t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
461 }
462
463 files = append(files, f.FileHeader)
464 }
465
466 if len(files) != len(test.files) {
467 t.Fatalf("want %d files, got %d", len(test.files), len(files))
468 }
469
470 for i := range files {
471 want := test.files[i]
472 got := files[i]
473
474 if want.Name != got.Name {
475 t.Errorf("incorrect file %d want %q got %q", i, want.Name, got.Name)
476 continue
477 }
478
479 if want.UncompressedSize64 != got.UncompressedSize64 {
480 t.Errorf("incorrect file %s length want %v got %v", want.Name,
481 want.UncompressedSize64, got.UncompressedSize64)
482 }
483
484 if want.ExternalAttrs != got.ExternalAttrs {
485 t.Errorf("incorrect file %s attrs want %x got %x", want.Name,
486 want.ExternalAttrs, got.ExternalAttrs)
487 }
488
489 if want.CRC32 != got.CRC32 {
490 t.Errorf("incorrect file %s crc want %v got %v", want.Name,
491 want.CRC32, got.CRC32)
492 }
493
494 if want.Method != got.Method {
495 t.Errorf("incorrect file %s method want %v got %v", want.Name,
496 want.Method, got.Method)
497 }
498 }
499 })
500 }
501}
502
Nan Zhang674dd932018-01-26 18:30:36 -0800503func TestReadRespFile(t *testing.T) {
504 testCases := []struct {
505 name, in string
506 out []string
507 }{
508 {
509 name: "single quoting test case 1",
510 in: `./cmd '"'-C`,
511 out: []string{"./cmd", `"-C`},
512 },
513 {
514 name: "single quoting test case 2",
515 in: `./cmd '-C`,
516 out: []string{"./cmd", `-C`},
517 },
518 {
519 name: "single quoting test case 3",
520 in: `./cmd '\"'-C`,
521 out: []string{"./cmd", `\"-C`},
522 },
523 {
524 name: "single quoting test case 4",
525 in: `./cmd '\\'-C`,
526 out: []string{"./cmd", `\\-C`},
527 },
528 {
529 name: "none quoting test case 1",
530 in: `./cmd \'-C`,
531 out: []string{"./cmd", `'-C`},
532 },
533 {
534 name: "none quoting test case 2",
535 in: `./cmd \\-C`,
536 out: []string{"./cmd", `\-C`},
537 },
538 {
539 name: "none quoting test case 3",
540 in: `./cmd \"-C`,
541 out: []string{"./cmd", `"-C`},
542 },
543 {
544 name: "double quoting test case 1",
545 in: `./cmd "'"-C`,
546 out: []string{"./cmd", `'-C`},
547 },
548 {
549 name: "double quoting test case 2",
550 in: `./cmd "\\"-C`,
551 out: []string{"./cmd", `\-C`},
552 },
553 {
554 name: "double quoting test case 3",
555 in: `./cmd "\""-C`,
556 out: []string{"./cmd", `"-C`},
557 },
558 }
559
560 for _, testCase := range testCases {
561 t.Run(testCase.name, func(t *testing.T) {
562 got := ReadRespFile([]byte(testCase.in))
563 if !reflect.DeepEqual(got, testCase.out) {
564 t.Errorf("expected %q got %q", testCase.out, got)
565 }
566 })
567 }
568}
Colin Cross9cb51db2019-06-17 14:12:41 -0700569
570func TestSrcJar(t *testing.T) {
571 mockFs := pathtools.MockFs(map[string][]byte{
572 "wrong_package.java": []byte("package foo;"),
573 "foo/correct_package.java": []byte("package foo;"),
574 "src/no_package.java": nil,
575 "src2/parse_error.java": []byte("error"),
576 })
577
578 want := []string{
579 "foo/",
580 "foo/wrong_package.java",
581 "foo/correct_package.java",
582 "no_package.java",
583 "src2/",
584 "src2/parse_error.java",
585 }
586
587 args := ZipArgs{}
588 args.FileArgs = NewFileArgsBuilder().File("**/*.java").FileArgs()
589
590 args.SrcJar = true
591 args.AddDirectoryEntriesToZip = true
592 args.Filesystem = mockFs
593 args.Stderr = &bytes.Buffer{}
594
595 buf := &bytes.Buffer{}
596 err := ZipTo(args, buf)
597 if err != nil {
598 t.Fatalf("got error %v", err)
599 }
600
601 br := bytes.NewReader(buf.Bytes())
602 zr, err := zip.NewReader(br, int64(br.Len()))
603 if err != nil {
604 t.Fatal(err)
605 }
606
607 var got []string
608 for _, f := range zr.File {
609 r, err := f.Open()
610 if err != nil {
611 t.Fatalf("error when opening %s: %s", f.Name, err)
612 }
613
614 crc := crc32.NewIEEE()
615 len, err := io.Copy(crc, r)
616 r.Close()
617 if err != nil {
618 t.Fatalf("error when reading %s: %s", f.Name, err)
619 }
620
621 if uint64(len) != f.UncompressedSize64 {
622 t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
623 }
624
625 if crc.Sum32() != f.CRC32 {
626 t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
627 }
628
629 got = append(got, f.Name)
630 }
631
632 if !reflect.DeepEqual(want, got) {
633 t.Errorf("want files %q, got %q", want, got)
634 }
635}