1// Copyright 2017 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 main
16
17import (
18	"bytes"
19	"fmt"
20	"reflect"
21	"testing"
22
23	"android/soong/third_party/zip"
24)
25
26var testCases = []struct {
27	name string
28
29	inputFiles   []string
30	sortGlobs    bool
31	sortJava     bool
32	args         []string
33	excludes     []string
34	includes     []string
35	uncompresses []string
36
37	outputFiles []string
38	storedFiles []string
39	err         error
40}{
41	{ // This is modelled after the update package build rules in build/make/core/Makefile
42		name: "filter globs",
43
44		inputFiles: []string{
45			"RADIO/a",
46			"IMAGES/system.img",
47			"IMAGES/b.txt",
48			"IMAGES/recovery.img",
49			"IMAGES/vendor.img",
50			"OTA/android-info.txt",
51			"OTA/b",
52		},
53		args: []string{"OTA/android-info.txt:android-info.txt", "IMAGES/*.img:."},
54
55		outputFiles: []string{
56			"android-info.txt",
57			"system.img",
58			"recovery.img",
59			"vendor.img",
60		},
61	},
62	{
63		name: "sorted filter globs",
64
65		inputFiles: []string{
66			"RADIO/a",
67			"IMAGES/system.img",
68			"IMAGES/b.txt",
69			"IMAGES/recovery.img",
70			"IMAGES/vendor.img",
71			"OTA/android-info.txt",
72			"OTA/b",
73		},
74		sortGlobs: true,
75		args:      []string{"IMAGES/*.img:.", "OTA/android-info.txt:android-info.txt"},
76
77		outputFiles: []string{
78			"recovery.img",
79			"system.img",
80			"vendor.img",
81			"android-info.txt",
82		},
83	},
84	{
85		name: "sort all",
86
87		inputFiles: []string{
88			"RADIO/",
89			"RADIO/a",
90			"IMAGES/",
91			"IMAGES/system.img",
92			"IMAGES/b.txt",
93			"IMAGES/recovery.img",
94			"IMAGES/vendor.img",
95			"OTA/",
96			"OTA/b",
97			"OTA/android-info.txt",
98		},
99		sortGlobs: true,
100		args:      []string{"**/*"},
101
102		outputFiles: []string{
103			"IMAGES/b.txt",
104			"IMAGES/recovery.img",
105			"IMAGES/system.img",
106			"IMAGES/vendor.img",
107			"OTA/android-info.txt",
108			"OTA/b",
109			"RADIO/a",
110		},
111	},
112	{
113		name: "sort all implicit",
114
115		inputFiles: []string{
116			"RADIO/",
117			"RADIO/a",
118			"IMAGES/",
119			"IMAGES/system.img",
120			"IMAGES/b.txt",
121			"IMAGES/recovery.img",
122			"IMAGES/vendor.img",
123			"OTA/",
124			"OTA/b",
125			"OTA/android-info.txt",
126		},
127		sortGlobs: true,
128		args:      nil,
129
130		outputFiles: []string{
131			"IMAGES/",
132			"IMAGES/b.txt",
133			"IMAGES/recovery.img",
134			"IMAGES/system.img",
135			"IMAGES/vendor.img",
136			"OTA/",
137			"OTA/android-info.txt",
138			"OTA/b",
139			"RADIO/",
140			"RADIO/a",
141		},
142	},
143	{
144		name: "sort jar",
145
146		inputFiles: []string{
147			"MANIFEST.MF",
148			"META-INF/MANIFEST.MF",
149			"META-INF/aaa/",
150			"META-INF/aaa/aaa",
151			"META-INF/AAA",
152			"META-INF.txt",
153			"META-INF/",
154			"AAA",
155			"aaa",
156		},
157		sortJava: true,
158		args:     nil,
159
160		outputFiles: []string{
161			"META-INF/",
162			"META-INF/MANIFEST.MF",
163			"META-INF/AAA",
164			"META-INF/aaa/",
165			"META-INF/aaa/aaa",
166			"AAA",
167			"MANIFEST.MF",
168			"META-INF.txt",
169			"aaa",
170		},
171	},
172	{
173		name: "double input",
174
175		inputFiles: []string{
176			"b",
177			"a",
178		},
179		args: []string{"a:a2", "**/*"},
180
181		outputFiles: []string{
182			"a2",
183			"b",
184			"a",
185		},
186	},
187	{
188		name: "multiple matches",
189
190		inputFiles: []string{
191			"a/a",
192		},
193		args: []string{"a/a", "a/*"},
194
195		outputFiles: []string{
196			"a/a",
197		},
198	},
199	{
200		name: "multiple conflicting matches",
201
202		inputFiles: []string{
203			"a/a",
204			"a/b",
205		},
206		args: []string{"a/b:a/a", "a/*"},
207
208		err: fmt.Errorf(`multiple entries for "a/a" with different contents`),
209	},
210	{
211		name: "excludes",
212
213		inputFiles: []string{
214			"a/a",
215			"a/b",
216		},
217		args:     nil,
218		excludes: []string{"a/a"},
219
220		outputFiles: []string{
221			"a/b",
222		},
223	},
224	{
225		name: "excludes with args",
226
227		inputFiles: []string{
228			"a/a",
229			"a/b",
230		},
231		args:     []string{"a/*"},
232		excludes: []string{"a/a"},
233
234		outputFiles: []string{
235			"a/b",
236		},
237	},
238	{
239		name: "excludes over args",
240
241		inputFiles: []string{
242			"a/a",
243			"a/b",
244		},
245		args:     []string{"a/a"},
246		excludes: []string{"a/*"},
247
248		outputFiles: nil,
249	},
250	{
251		name: "excludes with includes",
252
253		inputFiles: []string{
254			"a/a",
255			"a/b",
256		},
257		args:     nil,
258		excludes: []string{"a/*"},
259		includes: []string{"a/b"},
260
261		outputFiles: []string{"a/b"},
262	},
263	{
264		name: "excludes with glob",
265
266		inputFiles: []string{
267			"a/a",
268			"a/b",
269		},
270		args:     []string{"a/*"},
271		excludes: []string{"a/*"},
272
273		outputFiles: nil,
274	},
275	{
276		name: "uncompress one",
277
278		inputFiles: []string{
279			"a/a",
280			"a/b",
281		},
282		uncompresses: []string{"a/a"},
283
284		outputFiles: []string{
285			"a/a",
286			"a/b",
287		},
288		storedFiles: []string{
289			"a/a",
290		},
291	},
292	{
293		name: "uncompress two",
294
295		inputFiles: []string{
296			"a/a",
297			"a/b",
298		},
299		uncompresses: []string{"a/a", "a/b"},
300
301		outputFiles: []string{
302			"a/a",
303			"a/b",
304		},
305		storedFiles: []string{
306			"a/a",
307			"a/b",
308		},
309	},
310	{
311		name: "uncompress glob",
312
313		inputFiles: []string{
314			"a/a",
315			"a/b",
316			"a/c.so",
317			"a/d.so",
318		},
319		uncompresses: []string{"a/*.so"},
320
321		outputFiles: []string{
322			"a/a",
323			"a/b",
324			"a/c.so",
325			"a/d.so",
326		},
327		storedFiles: []string{
328			"a/c.so",
329			"a/d.so",
330		},
331	},
332	{
333		name: "uncompress rename",
334
335		inputFiles: []string{
336			"a/a",
337		},
338		args:         []string{"a/a:a/b"},
339		uncompresses: []string{"a/b"},
340
341		outputFiles: []string{
342			"a/b",
343		},
344		storedFiles: []string{
345			"a/b",
346		},
347	},
348	{
349		name: "recursive glob",
350
351		inputFiles: []string{
352			"a/a/a",
353			"a/a/b",
354		},
355		args: []string{"a/**/*:b"},
356		outputFiles: []string{
357			"b/a/a",
358			"b/a/b",
359		},
360	},
361	{
362		name: "glob",
363
364		inputFiles: []string{
365			"a/a/a",
366			"a/a/b",
367			"a/b",
368			"a/c",
369		},
370		args: []string{"a/*:b"},
371		outputFiles: []string{
372			"b/b",
373			"b/c",
374		},
375	},
376	{
377		name: "top level glob",
378
379		inputFiles: []string{
380			"a",
381			"b",
382		},
383		args: []string{"*:b"},
384		outputFiles: []string{
385			"b/a",
386			"b/b",
387		},
388	},
389	{
390		name: "multilple glob",
391
392		inputFiles: []string{
393			"a/a/a",
394			"a/a/b",
395		},
396		args: []string{"a/*/*:b"},
397		outputFiles: []string{
398			"b/a/a",
399			"b/a/b",
400		},
401	},
402	{
403		name: "escaping",
404
405		inputFiles:  []string{"a"},
406		args:        []string{"\\a"},
407		outputFiles: []string{"a"},
408	},
409}
410
411func errorString(e error) string {
412	if e == nil {
413		return ""
414	}
415	return e.Error()
416}
417
418func TestZip2Zip(t *testing.T) {
419	for _, testCase := range testCases {
420		t.Run(testCase.name, func(t *testing.T) {
421			inputBuf := &bytes.Buffer{}
422			outputBuf := &bytes.Buffer{}
423
424			inputWriter := zip.NewWriter(inputBuf)
425			for _, file := range testCase.inputFiles {
426				w, err := inputWriter.Create(file)
427				if err != nil {
428					t.Fatal(err)
429				}
430				fmt.Fprintln(w, "test")
431			}
432			inputWriter.Close()
433			inputBytes := inputBuf.Bytes()
434			inputReader, err := zip.NewReader(bytes.NewReader(inputBytes), int64(len(inputBytes)))
435			if err != nil {
436				t.Fatal(err)
437			}
438
439			outputWriter := zip.NewWriter(outputBuf)
440			err = zip2zip(inputReader, outputWriter, testCase.sortGlobs, testCase.sortJava, false,
441				testCase.args, testCase.excludes, testCase.includes, testCase.uncompresses)
442			if errorString(testCase.err) != errorString(err) {
443				t.Fatalf("Unexpected error:\n got: %q\nwant: %q", errorString(err), errorString(testCase.err))
444			}
445
446			outputWriter.Close()
447			outputBytes := outputBuf.Bytes()
448			outputReader, err := zip.NewReader(bytes.NewReader(outputBytes), int64(len(outputBytes)))
449			if err != nil {
450				t.Fatal(err)
451			}
452			var outputFiles []string
453			var storedFiles []string
454			if len(outputReader.File) > 0 {
455				outputFiles = make([]string, len(outputReader.File))
456				for i, file := range outputReader.File {
457					outputFiles[i] = file.Name
458					if file.Method == zip.Store {
459						storedFiles = append(storedFiles, file.Name)
460					}
461				}
462			}
463
464			if !reflect.DeepEqual(testCase.outputFiles, outputFiles) {
465				t.Fatalf("Output file list does not match:\nwant: %v\n got: %v", testCase.outputFiles, outputFiles)
466			}
467			if !reflect.DeepEqual(testCase.storedFiles, storedFiles) {
468				t.Fatalf("Stored file list does not match:\nwant: %v\n got: %v", testCase.storedFiles, storedFiles)
469			}
470		})
471	}
472}
473
474// TestZip2Zip64 tests that zip2zip on zip file larger than 4GB produces a valid zip file.
475func TestZip2Zip64(t *testing.T) {
476	if testing.Short() {
477		t.Skip("skipping slow test in short mode")
478	}
479	inputBuf := &bytes.Buffer{}
480	outputBuf := &bytes.Buffer{}
481
482	inputWriter := zip.NewWriter(inputBuf)
483	w, err := inputWriter.CreateHeaderAndroid(&zip.FileHeader{
484		Name:   "a",
485		Method: zip.Store,
486	})
487	if err != nil {
488		t.Fatal(err)
489	}
490	buf := make([]byte, 4*1024*1024)
491	for i := 0; i < 1025; i++ {
492		w.Write(buf)
493	}
494	w, err = inputWriter.CreateHeaderAndroid(&zip.FileHeader{
495		Name:   "b",
496		Method: zip.Store,
497	})
498	for i := 0; i < 1025; i++ {
499		w.Write(buf)
500	}
501	inputWriter.Close()
502	inputBytes := inputBuf.Bytes()
503
504	inputReader, err := zip.NewReader(bytes.NewReader(inputBytes), int64(len(inputBytes)))
505	if err != nil {
506		t.Fatal(err)
507	}
508
509	outputWriter := zip.NewWriter(outputBuf)
510	err = zip2zip(inputReader, outputWriter, false, false, false,
511		nil, nil, nil, nil)
512	if err != nil {
513		t.Fatal(err)
514	}
515
516	outputWriter.Close()
517	outputBytes := outputBuf.Bytes()
518	_, err = zip.NewReader(bytes.NewReader(outputBytes), int64(len(outputBytes)))
519	if err != nil {
520		t.Fatal(err)
521	}
522}
523
524func TestConstantPartOfPattern(t *testing.T) {
525	testCases := []struct{ in, out string }{
526		{
527			in:  "",
528			out: "",
529		},
530		{
531			in:  "a",
532			out: "a",
533		},
534		{
535			in:  "*",
536			out: "",
537		},
538		{
539			in:  "a/a",
540			out: "a/a",
541		},
542		{
543			in:  "a/*",
544			out: "a",
545		},
546		{
547			in:  "a/*/a",
548			out: "a",
549		},
550		{
551			in:  "a/**/*",
552			out: "a",
553		},
554	}
555
556	for _, test := range testCases {
557		t.Run(test.in, func(t *testing.T) {
558			got := constantPartOfPattern(test.in)
559			if got != test.out {
560				t.Errorf("want %q, got %q", test.out, got)
561			}
562		})
563	}
564}
565