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	{
42		name: "unsupported \\",
43
44		args: []string{"a\\b:b"},
45
46		err: fmt.Errorf("\\ characters are not currently supported"),
47	},
48	{ // This is modelled after the update package build rules in build/make/core/Makefile
49		name: "filter globs",
50
51		inputFiles: []string{
52			"RADIO/a",
53			"IMAGES/system.img",
54			"IMAGES/b.txt",
55			"IMAGES/recovery.img",
56			"IMAGES/vendor.img",
57			"OTA/android-info.txt",
58			"OTA/b",
59		},
60		args: []string{"OTA/android-info.txt:android-info.txt", "IMAGES/*.img:."},
61
62		outputFiles: []string{
63			"android-info.txt",
64			"system.img",
65			"recovery.img",
66			"vendor.img",
67		},
68	},
69	{
70		name: "sorted filter globs",
71
72		inputFiles: []string{
73			"RADIO/a",
74			"IMAGES/system.img",
75			"IMAGES/b.txt",
76			"IMAGES/recovery.img",
77			"IMAGES/vendor.img",
78			"OTA/android-info.txt",
79			"OTA/b",
80		},
81		sortGlobs: true,
82		args:      []string{"IMAGES/*.img:.", "OTA/android-info.txt:android-info.txt"},
83
84		outputFiles: []string{
85			"recovery.img",
86			"system.img",
87			"vendor.img",
88			"android-info.txt",
89		},
90	},
91	{
92		name: "sort all",
93
94		inputFiles: []string{
95			"RADIO/",
96			"RADIO/a",
97			"IMAGES/",
98			"IMAGES/system.img",
99			"IMAGES/b.txt",
100			"IMAGES/recovery.img",
101			"IMAGES/vendor.img",
102			"OTA/",
103			"OTA/b",
104			"OTA/android-info.txt",
105		},
106		sortGlobs: true,
107		args:      []string{"**/*"},
108
109		outputFiles: []string{
110			"IMAGES/b.txt",
111			"IMAGES/recovery.img",
112			"IMAGES/system.img",
113			"IMAGES/vendor.img",
114			"OTA/android-info.txt",
115			"OTA/b",
116			"RADIO/a",
117		},
118	},
119	{
120		name: "sort all implicit",
121
122		inputFiles: []string{
123			"RADIO/",
124			"RADIO/a",
125			"IMAGES/",
126			"IMAGES/system.img",
127			"IMAGES/b.txt",
128			"IMAGES/recovery.img",
129			"IMAGES/vendor.img",
130			"OTA/",
131			"OTA/b",
132			"OTA/android-info.txt",
133		},
134		sortGlobs: true,
135		args:      nil,
136
137		outputFiles: []string{
138			"IMAGES/",
139			"IMAGES/b.txt",
140			"IMAGES/recovery.img",
141			"IMAGES/system.img",
142			"IMAGES/vendor.img",
143			"OTA/",
144			"OTA/android-info.txt",
145			"OTA/b",
146			"RADIO/",
147			"RADIO/a",
148		},
149	},
150	{
151		name: "sort jar",
152
153		inputFiles: []string{
154			"MANIFEST.MF",
155			"META-INF/MANIFEST.MF",
156			"META-INF/aaa/",
157			"META-INF/aaa/aaa",
158			"META-INF/AAA",
159			"META-INF.txt",
160			"META-INF/",
161			"AAA",
162			"aaa",
163		},
164		sortJava: true,
165		args:     nil,
166
167		outputFiles: []string{
168			"META-INF/",
169			"META-INF/MANIFEST.MF",
170			"META-INF/AAA",
171			"META-INF/aaa/",
172			"META-INF/aaa/aaa",
173			"AAA",
174			"MANIFEST.MF",
175			"META-INF.txt",
176			"aaa",
177		},
178	},
179	{
180		name: "double input",
181
182		inputFiles: []string{
183			"b",
184			"a",
185		},
186		args: []string{"a:a2", "**/*"},
187
188		outputFiles: []string{
189			"a2",
190			"b",
191			"a",
192		},
193	},
194	{
195		name: "multiple matches",
196
197		inputFiles: []string{
198			"a/a",
199		},
200		args: []string{"a/a", "a/*"},
201
202		outputFiles: []string{
203			"a/a",
204		},
205	},
206	{
207		name: "multiple conflicting matches",
208
209		inputFiles: []string{
210			"a/a",
211			"a/b",
212		},
213		args: []string{"a/b:a/a", "a/*"},
214
215		err: fmt.Errorf(`multiple entries for "a/a" with different contents`),
216	},
217	{
218		name: "excludes",
219
220		inputFiles: []string{
221			"a/a",
222			"a/b",
223		},
224		args:     nil,
225		excludes: []string{"a/a"},
226
227		outputFiles: []string{
228			"a/b",
229		},
230	},
231	{
232		name: "excludes with args",
233
234		inputFiles: []string{
235			"a/a",
236			"a/b",
237		},
238		args:     []string{"a/*"},
239		excludes: []string{"a/a"},
240
241		outputFiles: []string{
242			"a/b",
243		},
244	},
245	{
246		name: "excludes over args",
247
248		inputFiles: []string{
249			"a/a",
250			"a/b",
251		},
252		args:     []string{"a/a"},
253		excludes: []string{"a/*"},
254
255		outputFiles: nil,
256	},
257	{
258		name: "excludes with includes",
259
260		inputFiles: []string{
261			"a/a",
262			"a/b",
263		},
264		args:     nil,
265		excludes: []string{"a/*"},
266		includes: []string{"a/b"},
267
268		outputFiles: []string{"a/b"},
269	},
270	{
271		name: "excludes with glob",
272
273		inputFiles: []string{
274			"a/a",
275			"a/b",
276		},
277		args:     []string{"a/*"},
278		excludes: []string{"a/*"},
279
280		outputFiles: nil,
281	},
282	{
283		name: "uncompress one",
284
285		inputFiles: []string{
286			"a/a",
287			"a/b",
288		},
289		uncompresses: []string{"a/a"},
290
291		outputFiles: []string{
292			"a/a",
293			"a/b",
294		},
295		storedFiles: []string{
296			"a/a",
297		},
298	},
299	{
300		name: "uncompress two",
301
302		inputFiles: []string{
303			"a/a",
304			"a/b",
305		},
306		uncompresses: []string{"a/a", "a/b"},
307
308		outputFiles: []string{
309			"a/a",
310			"a/b",
311		},
312		storedFiles: []string{
313			"a/a",
314			"a/b",
315		},
316	},
317	{
318		name: "uncompress glob",
319
320		inputFiles: []string{
321			"a/a",
322			"a/b",
323			"a/c.so",
324			"a/d.so",
325		},
326		uncompresses: []string{"a/*.so"},
327
328		outputFiles: []string{
329			"a/a",
330			"a/b",
331			"a/c.so",
332			"a/d.so",
333		},
334		storedFiles: []string{
335			"a/c.so",
336			"a/d.so",
337		},
338	},
339	{
340		name: "uncompress rename",
341
342		inputFiles: []string{
343			"a/a",
344		},
345		args:         []string{"a/a:a/b"},
346		uncompresses: []string{"a/b"},
347
348		outputFiles: []string{
349			"a/b",
350		},
351		storedFiles: []string{
352			"a/b",
353		},
354	},
355	{
356		name: "recursive glob",
357
358		inputFiles: []string{
359			"a/a/a",
360			"a/a/b",
361		},
362		args: []string{"a/**/*:b"},
363		outputFiles: []string{
364			"b/a/a",
365			"b/a/b",
366		},
367	},
368	{
369		name: "glob",
370
371		inputFiles: []string{
372			"a/a/a",
373			"a/a/b",
374			"a/b",
375			"a/c",
376		},
377		args: []string{"a/*:b"},
378		outputFiles: []string{
379			"b/b",
380			"b/c",
381		},
382	},
383	{
384		name: "top level glob",
385
386		inputFiles: []string{
387			"a",
388			"b",
389		},
390		args: []string{"*:b"},
391		outputFiles: []string{
392			"b/a",
393			"b/b",
394		},
395	},
396	{
397		name: "multilple glob",
398
399		inputFiles: []string{
400			"a/a/a",
401			"a/a/b",
402		},
403		args: []string{"a/*/*:b"},
404		outputFiles: []string{
405			"b/a/a",
406			"b/a/b",
407		},
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
474func TestConstantPartOfPattern(t *testing.T) {
475	testCases := []struct{ in, out string }{
476		{
477			in:  "",
478			out: "",
479		},
480		{
481			in:  "a",
482			out: "a",
483		},
484		{
485			in:  "*",
486			out: "",
487		},
488		{
489			in:  "a/a",
490			out: "a/a",
491		},
492		{
493			in:  "a/*",
494			out: "a",
495		},
496		{
497			in:  "a/*/a",
498			out: "a",
499		},
500		{
501			in:  "a/**/*",
502			out: "a",
503		},
504	}
505
506	for _, test := range testCases {
507		t.Run(test.in, func(t *testing.T) {
508			got := constantPartOfPattern(test.in)
509			if got != test.out {
510				t.Errorf("want %q, got %q", test.out, got)
511			}
512		})
513	}
514}
515