blob: 93c5f3dee9cb13000be2853b06cb086cec61ca1f [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{
43 "a/a/a": fileA,
44 "a/a/b": fileB,
45 "a/a/c -> ../../c": nil,
46 "a/a/d -> b": nil,
47 "c": fileC,
48 "l": []byte("a/a/a\na/a/b\nc\n"),
49 "l2": []byte("missing\n"),
50 "manifest.txt": fileCustomManifest,
51})
52
53func fh(name string, contents []byte, method uint16) zip.FileHeader {
54 return zip.FileHeader{
55 Name: name,
56 Method: method,
57 CRC32: crc32.ChecksumIEEE(contents),
58 UncompressedSize64: uint64(len(contents)),
59 ExternalAttrs: 0,
60 }
61}
62
63func fhManifest(contents []byte) zip.FileHeader {
64 return zip.FileHeader{
65 Name: "META-INF/MANIFEST.MF",
66 Method: zip.Store,
67 CRC32: crc32.ChecksumIEEE(contents),
68 UncompressedSize64: uint64(len(contents)),
69 ExternalAttrs: (syscall.S_IFREG | 0700) << 16,
70 }
71}
72
73func fhLink(name string, to string) zip.FileHeader {
74 return zip.FileHeader{
75 Name: name,
76 Method: zip.Store,
77 CRC32: crc32.ChecksumIEEE([]byte(to)),
78 UncompressedSize64: uint64(len(to)),
79 ExternalAttrs: (syscall.S_IFLNK | 0777) << 16,
80 }
81}
82
83func fhDir(name string) zip.FileHeader {
84 return zip.FileHeader{
85 Name: name,
86 Method: zip.Store,
87 CRC32: crc32.ChecksumIEEE(nil),
88 UncompressedSize64: 0,
89 ExternalAttrs: (syscall.S_IFDIR|0700)<<16 | 0x10,
90 }
91}
92
93func fileArgsBuilder() *FileArgsBuilder {
94 return &FileArgsBuilder{
95 fs: mockFs,
96 }
97}
98
99func TestZip(t *testing.T) {
100 testCases := []struct {
Colin Cross4be8f9e2018-09-28 15:16:48 -0700101 name string
102 args *FileArgsBuilder
103 compressionLevel int
104 emulateJar bool
105 nonDeflatedFiles map[string]bool
106 dirEntries bool
107 manifest string
108 storeSymlinks bool
109 ignoreMissingFiles bool
Colin Cross05518bc2018-09-27 15:06:19 -0700110
111 files []zip.FileHeader
112 err error
113 }{
114 {
115 name: "empty args",
116 args: fileArgsBuilder(),
117
118 files: []zip.FileHeader{},
119 },
120 {
121 name: "files",
122 args: fileArgsBuilder().
123 File("a/a/a").
124 File("a/a/b").
125 File("c"),
126 compressionLevel: 9,
127
128 files: []zip.FileHeader{
129 fh("a/a/a", fileA, zip.Deflate),
130 fh("a/a/b", fileB, zip.Deflate),
131 fh("c", fileC, zip.Deflate),
132 },
133 },
134 {
Colin Cross1d98ee22018-09-18 17:05:15 -0700135 name: "files glob",
136 args: fileArgsBuilder().
137 SourcePrefixToStrip("a").
138 File("a/**/*"),
139 compressionLevel: 9,
Colin Cross09f11052018-09-21 15:12:39 -0700140 storeSymlinks: true,
Colin Cross1d98ee22018-09-18 17:05:15 -0700141
142 files: []zip.FileHeader{
143 fh("a/a", fileA, zip.Deflate),
144 fh("a/b", fileB, zip.Deflate),
145 fhLink("a/c", "../../c"),
146 fhLink("a/d", "b"),
147 },
148 },
149 {
150 name: "dir",
151 args: fileArgsBuilder().
152 SourcePrefixToStrip("a").
153 Dir("a"),
154 compressionLevel: 9,
Colin Cross09f11052018-09-21 15:12:39 -0700155 storeSymlinks: true,
Colin Cross1d98ee22018-09-18 17:05:15 -0700156
157 files: []zip.FileHeader{
158 fh("a/a", fileA, zip.Deflate),
159 fh("a/b", fileB, zip.Deflate),
160 fhLink("a/c", "../../c"),
161 fhLink("a/d", "b"),
162 },
163 },
164 {
Colin Cross05518bc2018-09-27 15:06:19 -0700165 name: "stored files",
166 args: fileArgsBuilder().
167 File("a/a/a").
168 File("a/a/b").
169 File("c"),
170 compressionLevel: 0,
171
172 files: []zip.FileHeader{
173 fh("a/a/a", fileA, zip.Store),
174 fh("a/a/b", fileB, zip.Store),
175 fh("c", fileC, zip.Store),
176 },
177 },
178 {
179 name: "symlinks in zip",
180 args: fileArgsBuilder().
181 File("a/a/a").
182 File("a/a/b").
183 File("a/a/c").
184 File("a/a/d"),
185 compressionLevel: 9,
Colin Cross09f11052018-09-21 15:12:39 -0700186 storeSymlinks: true,
Colin Cross05518bc2018-09-27 15:06:19 -0700187
188 files: []zip.FileHeader{
189 fh("a/a/a", fileA, zip.Deflate),
190 fh("a/a/b", fileB, zip.Deflate),
191 fhLink("a/a/c", "../../c"),
192 fhLink("a/a/d", "b"),
193 },
194 },
195 {
Colin Cross09f11052018-09-21 15:12:39 -0700196 name: "follow symlinks",
197 args: fileArgsBuilder().
198 File("a/a/a").
199 File("a/a/b").
200 File("a/a/c").
201 File("a/a/d"),
202 compressionLevel: 9,
203 storeSymlinks: false,
204
205 files: []zip.FileHeader{
206 fh("a/a/a", fileA, zip.Deflate),
207 fh("a/a/b", fileB, zip.Deflate),
208 fh("a/a/c", fileC, zip.Deflate),
209 fh("a/a/d", fileB, zip.Deflate),
210 },
211 },
212 {
Colin Cross05518bc2018-09-27 15:06:19 -0700213 name: "list",
214 args: fileArgsBuilder().
215 List("l"),
216 compressionLevel: 9,
217
218 files: []zip.FileHeader{
219 fh("a/a/a", fileA, zip.Deflate),
220 fh("a/a/b", fileB, zip.Deflate),
221 fh("c", fileC, zip.Deflate),
222 },
223 },
224 {
225 name: "prefix in zip",
226 args: fileArgsBuilder().
227 PathPrefixInZip("foo").
228 File("a/a/a").
229 File("a/a/b").
230 File("c"),
231 compressionLevel: 9,
232
233 files: []zip.FileHeader{
234 fh("foo/a/a/a", fileA, zip.Deflate),
235 fh("foo/a/a/b", fileB, zip.Deflate),
236 fh("foo/c", fileC, zip.Deflate),
237 },
238 },
239 {
240 name: "relative root",
241 args: fileArgsBuilder().
242 SourcePrefixToStrip("a").
243 File("a/a/a").
244 File("a/a/b"),
245 compressionLevel: 9,
246
247 files: []zip.FileHeader{
248 fh("a/a", fileA, zip.Deflate),
249 fh("a/b", fileB, zip.Deflate),
250 },
251 },
252 {
253 name: "multiple relative root",
254 args: fileArgsBuilder().
255 SourcePrefixToStrip("a").
256 File("a/a/a").
257 SourcePrefixToStrip("a/a").
258 File("a/a/b"),
259 compressionLevel: 9,
260
261 files: []zip.FileHeader{
262 fh("a/a", fileA, zip.Deflate),
263 fh("b", fileB, zip.Deflate),
264 },
265 },
266 {
267 name: "emulate jar",
268 args: fileArgsBuilder().
269 File("a/a/a").
270 File("a/a/b"),
271 compressionLevel: 9,
272 emulateJar: true,
273
274 files: []zip.FileHeader{
275 fhDir("META-INF/"),
276 fhManifest(fileManifest),
277 fhDir("a/"),
278 fhDir("a/a/"),
279 fh("a/a/a", fileA, zip.Deflate),
280 fh("a/a/b", fileB, zip.Deflate),
281 },
282 },
283 {
284 name: "emulate jar with manifest",
285 args: fileArgsBuilder().
286 File("a/a/a").
287 File("a/a/b"),
288 compressionLevel: 9,
289 emulateJar: true,
290 manifest: "manifest.txt",
291
292 files: []zip.FileHeader{
293 fhDir("META-INF/"),
294 fhManifest(customManifestAfter),
295 fhDir("a/"),
296 fhDir("a/a/"),
297 fh("a/a/a", fileA, zip.Deflate),
298 fh("a/a/b", fileB, zip.Deflate),
299 },
300 },
301 {
302 name: "dir entries",
303 args: fileArgsBuilder().
304 File("a/a/a").
305 File("a/a/b"),
306 compressionLevel: 9,
307 dirEntries: true,
308
309 files: []zip.FileHeader{
310 fhDir("a/"),
311 fhDir("a/a/"),
312 fh("a/a/a", fileA, zip.Deflate),
313 fh("a/a/b", fileB, zip.Deflate),
314 },
315 },
316 {
317 name: "junk paths",
318 args: fileArgsBuilder().
319 JunkPaths(true).
320 File("a/a/a").
321 File("a/a/b"),
322 compressionLevel: 9,
323
324 files: []zip.FileHeader{
325 fh("a", fileA, zip.Deflate),
326 fh("b", fileB, zip.Deflate),
327 },
328 },
329 {
330 name: "non deflated files",
331 args: fileArgsBuilder().
332 File("a/a/a").
333 File("a/a/b"),
334 compressionLevel: 9,
335 nonDeflatedFiles: map[string]bool{"a/a/a": true},
336
337 files: []zip.FileHeader{
338 fh("a/a/a", fileA, zip.Store),
339 fh("a/a/b", fileB, zip.Deflate),
340 },
341 },
Colin Cross4be8f9e2018-09-28 15:16:48 -0700342 {
343 name: "ignore missing files",
344 args: fileArgsBuilder().
345 File("a/a/a").
346 File("a/a/b").
347 File("missing"),
348 compressionLevel: 9,
349 ignoreMissingFiles: true,
350
351 files: []zip.FileHeader{
352 fh("a/a/a", fileA, zip.Deflate),
353 fh("a/a/b", fileB, zip.Deflate),
354 },
355 },
Colin Cross05518bc2018-09-27 15:06:19 -0700356
357 // errors
358 {
359 name: "error missing file",
360 args: fileArgsBuilder().
361 File("missing"),
362 err: os.ErrNotExist,
363 },
364 {
Colin Cross1d98ee22018-09-18 17:05:15 -0700365 name: "error missing dir",
366 args: fileArgsBuilder().
367 Dir("missing"),
368 err: os.ErrNotExist,
369 },
370 {
Colin Cross05518bc2018-09-27 15:06:19 -0700371 name: "error missing file in list",
372 args: fileArgsBuilder().
373 List("l2"),
374 err: os.ErrNotExist,
375 },
Colin Cross1d98ee22018-09-18 17:05:15 -0700376 {
377 name: "error incorrect relative root",
378 args: fileArgsBuilder().
379 SourcePrefixToStrip("b").
380 File("a/a/a"),
381 err: IncorrectRelativeRootError{},
382 },
Colin Cross05518bc2018-09-27 15:06:19 -0700383 }
384
385 for _, test := range testCases {
386 t.Run(test.name, func(t *testing.T) {
387 if test.args.Error() != nil {
388 t.Fatal(test.args.Error())
389 }
390
391 args := ZipArgs{}
392 args.FileArgs = test.args.FileArgs()
393 args.CompressionLevel = test.compressionLevel
394 args.EmulateJar = test.emulateJar
395 args.AddDirectoryEntriesToZip = test.dirEntries
396 args.NonDeflatedFiles = test.nonDeflatedFiles
397 args.ManifestSourcePath = test.manifest
Colin Cross09f11052018-09-21 15:12:39 -0700398 args.StoreSymlinks = test.storeSymlinks
Colin Cross4be8f9e2018-09-28 15:16:48 -0700399 args.IgnoreMissingFiles = test.ignoreMissingFiles
Colin Cross05518bc2018-09-27 15:06:19 -0700400 args.Filesystem = mockFs
Colin Cross4be8f9e2018-09-28 15:16:48 -0700401 args.Stderr = &bytes.Buffer{}
Colin Cross05518bc2018-09-27 15:06:19 -0700402
403 buf := &bytes.Buffer{}
404 err := ZipTo(args, buf)
405
406 if (err != nil) != (test.err != nil) {
407 t.Fatalf("want error %v, got %v", test.err, err)
408 } else if test.err != nil {
409 if os.IsNotExist(test.err) {
410 if !os.IsNotExist(test.err) {
411 t.Fatalf("want error %v, got %v", test.err, err)
412 }
Colin Cross1d98ee22018-09-18 17:05:15 -0700413 } else if _, wantRelativeRootErr := test.err.(IncorrectRelativeRootError); wantRelativeRootErr {
414 if _, gotRelativeRootErr := err.(IncorrectRelativeRootError); !gotRelativeRootErr {
415 t.Fatalf("want error %v, got %v", test.err, err)
416 }
Colin Cross05518bc2018-09-27 15:06:19 -0700417 } else {
418 t.Fatalf("want error %v, got %v", test.err, err)
419 }
420 return
421 }
422
423 br := bytes.NewReader(buf.Bytes())
424 zr, err := zip.NewReader(br, int64(br.Len()))
425 if err != nil {
426 t.Fatal(err)
427 }
428
429 var files []zip.FileHeader
430 for _, f := range zr.File {
431 r, err := f.Open()
432 if err != nil {
433 t.Fatalf("error when opening %s: %s", f.Name, err)
434 }
435
436 crc := crc32.NewIEEE()
437 len, err := io.Copy(crc, r)
438 r.Close()
439 if err != nil {
440 t.Fatalf("error when reading %s: %s", f.Name, err)
441 }
442
443 if uint64(len) != f.UncompressedSize64 {
444 t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
445 }
446
447 if crc.Sum32() != f.CRC32 {
448 t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
449 }
450
451 files = append(files, f.FileHeader)
452 }
453
454 if len(files) != len(test.files) {
455 t.Fatalf("want %d files, got %d", len(test.files), len(files))
456 }
457
458 for i := range files {
459 want := test.files[i]
460 got := files[i]
461
462 if want.Name != got.Name {
463 t.Errorf("incorrect file %d want %q got %q", i, want.Name, got.Name)
464 continue
465 }
466
467 if want.UncompressedSize64 != got.UncompressedSize64 {
468 t.Errorf("incorrect file %s length want %v got %v", want.Name,
469 want.UncompressedSize64, got.UncompressedSize64)
470 }
471
472 if want.ExternalAttrs != got.ExternalAttrs {
473 t.Errorf("incorrect file %s attrs want %x got %x", want.Name,
474 want.ExternalAttrs, got.ExternalAttrs)
475 }
476
477 if want.CRC32 != got.CRC32 {
478 t.Errorf("incorrect file %s crc want %v got %v", want.Name,
479 want.CRC32, got.CRC32)
480 }
481
482 if want.Method != got.Method {
483 t.Errorf("incorrect file %s method want %v got %v", want.Name,
484 want.Method, got.Method)
485 }
486 }
487 })
488 }
489}
490
Nan Zhang674dd932018-01-26 18:30:36 -0800491func TestReadRespFile(t *testing.T) {
492 testCases := []struct {
493 name, in string
494 out []string
495 }{
496 {
497 name: "single quoting test case 1",
498 in: `./cmd '"'-C`,
499 out: []string{"./cmd", `"-C`},
500 },
501 {
502 name: "single quoting test case 2",
503 in: `./cmd '-C`,
504 out: []string{"./cmd", `-C`},
505 },
506 {
507 name: "single quoting test case 3",
508 in: `./cmd '\"'-C`,
509 out: []string{"./cmd", `\"-C`},
510 },
511 {
512 name: "single quoting test case 4",
513 in: `./cmd '\\'-C`,
514 out: []string{"./cmd", `\\-C`},
515 },
516 {
517 name: "none quoting test case 1",
518 in: `./cmd \'-C`,
519 out: []string{"./cmd", `'-C`},
520 },
521 {
522 name: "none quoting test case 2",
523 in: `./cmd \\-C`,
524 out: []string{"./cmd", `\-C`},
525 },
526 {
527 name: "none quoting test case 3",
528 in: `./cmd \"-C`,
529 out: []string{"./cmd", `"-C`},
530 },
531 {
532 name: "double quoting test case 1",
533 in: `./cmd "'"-C`,
534 out: []string{"./cmd", `'-C`},
535 },
536 {
537 name: "double quoting test case 2",
538 in: `./cmd "\\"-C`,
539 out: []string{"./cmd", `\-C`},
540 },
541 {
542 name: "double quoting test case 3",
543 in: `./cmd "\""-C`,
544 out: []string{"./cmd", `"-C`},
545 },
546 }
547
548 for _, testCase := range testCases {
549 t.Run(testCase.name, func(t *testing.T) {
550 got := ReadRespFile([]byte(testCase.in))
551 if !reflect.DeepEqual(got, testCase.out) {
552 t.Errorf("expected %q got %q", testCase.out, got)
553 }
554 })
555 }
556}