blob: 0c2105c672b730777109d8fd6766035447e20644 [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 {
101 name string
102 args *FileArgsBuilder
103 compressionLevel int
104 emulateJar bool
105 nonDeflatedFiles map[string]bool
106 dirEntries bool
107 manifest string
108
109 files []zip.FileHeader
110 err error
111 }{
112 {
113 name: "empty args",
114 args: fileArgsBuilder(),
115
116 files: []zip.FileHeader{},
117 },
118 {
119 name: "files",
120 args: fileArgsBuilder().
121 File("a/a/a").
122 File("a/a/b").
123 File("c"),
124 compressionLevel: 9,
125
126 files: []zip.FileHeader{
127 fh("a/a/a", fileA, zip.Deflate),
128 fh("a/a/b", fileB, zip.Deflate),
129 fh("c", fileC, zip.Deflate),
130 },
131 },
132 {
133 name: "stored files",
134 args: fileArgsBuilder().
135 File("a/a/a").
136 File("a/a/b").
137 File("c"),
138 compressionLevel: 0,
139
140 files: []zip.FileHeader{
141 fh("a/a/a", fileA, zip.Store),
142 fh("a/a/b", fileB, zip.Store),
143 fh("c", fileC, zip.Store),
144 },
145 },
146 {
147 name: "symlinks in zip",
148 args: fileArgsBuilder().
149 File("a/a/a").
150 File("a/a/b").
151 File("a/a/c").
152 File("a/a/d"),
153 compressionLevel: 9,
154
155 files: []zip.FileHeader{
156 fh("a/a/a", fileA, zip.Deflate),
157 fh("a/a/b", fileB, zip.Deflate),
158 fhLink("a/a/c", "../../c"),
159 fhLink("a/a/d", "b"),
160 },
161 },
162 {
163 name: "list",
164 args: fileArgsBuilder().
165 List("l"),
166 compressionLevel: 9,
167
168 files: []zip.FileHeader{
169 fh("a/a/a", fileA, zip.Deflate),
170 fh("a/a/b", fileB, zip.Deflate),
171 fh("c", fileC, zip.Deflate),
172 },
173 },
174 {
175 name: "prefix in zip",
176 args: fileArgsBuilder().
177 PathPrefixInZip("foo").
178 File("a/a/a").
179 File("a/a/b").
180 File("c"),
181 compressionLevel: 9,
182
183 files: []zip.FileHeader{
184 fh("foo/a/a/a", fileA, zip.Deflate),
185 fh("foo/a/a/b", fileB, zip.Deflate),
186 fh("foo/c", fileC, zip.Deflate),
187 },
188 },
189 {
190 name: "relative root",
191 args: fileArgsBuilder().
192 SourcePrefixToStrip("a").
193 File("a/a/a").
194 File("a/a/b"),
195 compressionLevel: 9,
196
197 files: []zip.FileHeader{
198 fh("a/a", fileA, zip.Deflate),
199 fh("a/b", fileB, zip.Deflate),
200 },
201 },
202 {
203 name: "multiple relative root",
204 args: fileArgsBuilder().
205 SourcePrefixToStrip("a").
206 File("a/a/a").
207 SourcePrefixToStrip("a/a").
208 File("a/a/b"),
209 compressionLevel: 9,
210
211 files: []zip.FileHeader{
212 fh("a/a", fileA, zip.Deflate),
213 fh("b", fileB, zip.Deflate),
214 },
215 },
216 {
217 name: "emulate jar",
218 args: fileArgsBuilder().
219 File("a/a/a").
220 File("a/a/b"),
221 compressionLevel: 9,
222 emulateJar: true,
223
224 files: []zip.FileHeader{
225 fhDir("META-INF/"),
226 fhManifest(fileManifest),
227 fhDir("a/"),
228 fhDir("a/a/"),
229 fh("a/a/a", fileA, zip.Deflate),
230 fh("a/a/b", fileB, zip.Deflate),
231 },
232 },
233 {
234 name: "emulate jar with manifest",
235 args: fileArgsBuilder().
236 File("a/a/a").
237 File("a/a/b"),
238 compressionLevel: 9,
239 emulateJar: true,
240 manifest: "manifest.txt",
241
242 files: []zip.FileHeader{
243 fhDir("META-INF/"),
244 fhManifest(customManifestAfter),
245 fhDir("a/"),
246 fhDir("a/a/"),
247 fh("a/a/a", fileA, zip.Deflate),
248 fh("a/a/b", fileB, zip.Deflate),
249 },
250 },
251 {
252 name: "dir entries",
253 args: fileArgsBuilder().
254 File("a/a/a").
255 File("a/a/b"),
256 compressionLevel: 9,
257 dirEntries: true,
258
259 files: []zip.FileHeader{
260 fhDir("a/"),
261 fhDir("a/a/"),
262 fh("a/a/a", fileA, zip.Deflate),
263 fh("a/a/b", fileB, zip.Deflate),
264 },
265 },
266 {
267 name: "junk paths",
268 args: fileArgsBuilder().
269 JunkPaths(true).
270 File("a/a/a").
271 File("a/a/b"),
272 compressionLevel: 9,
273
274 files: []zip.FileHeader{
275 fh("a", fileA, zip.Deflate),
276 fh("b", fileB, zip.Deflate),
277 },
278 },
279 {
280 name: "non deflated files",
281 args: fileArgsBuilder().
282 File("a/a/a").
283 File("a/a/b"),
284 compressionLevel: 9,
285 nonDeflatedFiles: map[string]bool{"a/a/a": true},
286
287 files: []zip.FileHeader{
288 fh("a/a/a", fileA, zip.Store),
289 fh("a/a/b", fileB, zip.Deflate),
290 },
291 },
292
293 // errors
294 {
295 name: "error missing file",
296 args: fileArgsBuilder().
297 File("missing"),
298 err: os.ErrNotExist,
299 },
300 {
301 name: "error missing file in list",
302 args: fileArgsBuilder().
303 List("l2"),
304 err: os.ErrNotExist,
305 },
306 }
307
308 for _, test := range testCases {
309 t.Run(test.name, func(t *testing.T) {
310 if test.args.Error() != nil {
311 t.Fatal(test.args.Error())
312 }
313
314 args := ZipArgs{}
315 args.FileArgs = test.args.FileArgs()
316 args.CompressionLevel = test.compressionLevel
317 args.EmulateJar = test.emulateJar
318 args.AddDirectoryEntriesToZip = test.dirEntries
319 args.NonDeflatedFiles = test.nonDeflatedFiles
320 args.ManifestSourcePath = test.manifest
321 args.Filesystem = mockFs
322
323 buf := &bytes.Buffer{}
324 err := ZipTo(args, buf)
325
326 if (err != nil) != (test.err != nil) {
327 t.Fatalf("want error %v, got %v", test.err, err)
328 } else if test.err != nil {
329 if os.IsNotExist(test.err) {
330 if !os.IsNotExist(test.err) {
331 t.Fatalf("want error %v, got %v", test.err, err)
332 }
333 } else {
334 t.Fatalf("want error %v, got %v", test.err, err)
335 }
336 return
337 }
338
339 br := bytes.NewReader(buf.Bytes())
340 zr, err := zip.NewReader(br, int64(br.Len()))
341 if err != nil {
342 t.Fatal(err)
343 }
344
345 var files []zip.FileHeader
346 for _, f := range zr.File {
347 r, err := f.Open()
348 if err != nil {
349 t.Fatalf("error when opening %s: %s", f.Name, err)
350 }
351
352 crc := crc32.NewIEEE()
353 len, err := io.Copy(crc, r)
354 r.Close()
355 if err != nil {
356 t.Fatalf("error when reading %s: %s", f.Name, err)
357 }
358
359 if uint64(len) != f.UncompressedSize64 {
360 t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
361 }
362
363 if crc.Sum32() != f.CRC32 {
364 t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
365 }
366
367 files = append(files, f.FileHeader)
368 }
369
370 if len(files) != len(test.files) {
371 t.Fatalf("want %d files, got %d", len(test.files), len(files))
372 }
373
374 for i := range files {
375 want := test.files[i]
376 got := files[i]
377
378 if want.Name != got.Name {
379 t.Errorf("incorrect file %d want %q got %q", i, want.Name, got.Name)
380 continue
381 }
382
383 if want.UncompressedSize64 != got.UncompressedSize64 {
384 t.Errorf("incorrect file %s length want %v got %v", want.Name,
385 want.UncompressedSize64, got.UncompressedSize64)
386 }
387
388 if want.ExternalAttrs != got.ExternalAttrs {
389 t.Errorf("incorrect file %s attrs want %x got %x", want.Name,
390 want.ExternalAttrs, got.ExternalAttrs)
391 }
392
393 if want.CRC32 != got.CRC32 {
394 t.Errorf("incorrect file %s crc want %v got %v", want.Name,
395 want.CRC32, got.CRC32)
396 }
397
398 if want.Method != got.Method {
399 t.Errorf("incorrect file %s method want %v got %v", want.Name,
400 want.Method, got.Method)
401 }
402 }
403 })
404 }
405}
406
Nan Zhang674dd932018-01-26 18:30:36 -0800407func TestReadRespFile(t *testing.T) {
408 testCases := []struct {
409 name, in string
410 out []string
411 }{
412 {
413 name: "single quoting test case 1",
414 in: `./cmd '"'-C`,
415 out: []string{"./cmd", `"-C`},
416 },
417 {
418 name: "single quoting test case 2",
419 in: `./cmd '-C`,
420 out: []string{"./cmd", `-C`},
421 },
422 {
423 name: "single quoting test case 3",
424 in: `./cmd '\"'-C`,
425 out: []string{"./cmd", `\"-C`},
426 },
427 {
428 name: "single quoting test case 4",
429 in: `./cmd '\\'-C`,
430 out: []string{"./cmd", `\\-C`},
431 },
432 {
433 name: "none quoting test case 1",
434 in: `./cmd \'-C`,
435 out: []string{"./cmd", `'-C`},
436 },
437 {
438 name: "none quoting test case 2",
439 in: `./cmd \\-C`,
440 out: []string{"./cmd", `\-C`},
441 },
442 {
443 name: "none quoting test case 3",
444 in: `./cmd \"-C`,
445 out: []string{"./cmd", `"-C`},
446 },
447 {
448 name: "double quoting test case 1",
449 in: `./cmd "'"-C`,
450 out: []string{"./cmd", `'-C`},
451 },
452 {
453 name: "double quoting test case 2",
454 in: `./cmd "\\"-C`,
455 out: []string{"./cmd", `\-C`},
456 },
457 {
458 name: "double quoting test case 3",
459 in: `./cmd "\""-C`,
460 out: []string{"./cmd", `"-C`},
461 },
462 }
463
464 for _, testCase := range testCases {
465 t.Run(testCase.name, func(t *testing.T) {
466 got := ReadRespFile([]byte(testCase.in))
467 if !reflect.DeepEqual(got, testCase.out) {
468 t.Errorf("expected %q got %q", testCase.out, got)
469 }
470 })
471 }
472}