1// 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 main
16
17import (
18	"bytes"
19	"fmt"
20	"os"
21	"strconv"
22	"strings"
23	"testing"
24
25	"android/soong/jar"
26	"android/soong/third_party/zip"
27)
28
29type testZipEntry struct {
30	name string
31	mode os.FileMode
32	data []byte
33}
34
35var (
36	A     = testZipEntry{"A", 0755, []byte("foo")}
37	a     = testZipEntry{"a", 0755, []byte("foo")}
38	a2    = testZipEntry{"a", 0755, []byte("FOO2")}
39	a3    = testZipEntry{"a", 0755, []byte("Foo3")}
40	bDir  = testZipEntry{"b/", os.ModeDir | 0755, nil}
41	bbDir = testZipEntry{"b/b/", os.ModeDir | 0755, nil}
42	bbb   = testZipEntry{"b/b/b", 0755, nil}
43	ba    = testZipEntry{"b/a", 0755, []byte("foob")}
44	bc    = testZipEntry{"b/c", 0755, []byte("bar")}
45	bd    = testZipEntry{"b/d", 0700, []byte("baz")}
46	be    = testZipEntry{"b/e", 0700, []byte("")}
47
48	metainfDir     = testZipEntry{jar.MetaDir, os.ModeDir | 0755, nil}
49	manifestFile   = testZipEntry{jar.ManifestFile, 0755, []byte("manifest")}
50	manifestFile2  = testZipEntry{jar.ManifestFile, 0755, []byte("manifest2")}
51	moduleInfoFile = testZipEntry{jar.ModuleInfoClass, 0755, []byte("module-info")}
52)
53
54type testInputZip struct {
55	name    string
56	entries []testZipEntry
57	reader  *zip.Reader
58}
59
60func (tiz *testInputZip) Name() string {
61	return tiz.name
62}
63
64func (tiz *testInputZip) Open() error {
65	if tiz.reader == nil {
66		tiz.reader = testZipEntriesToZipReader(tiz.entries)
67	}
68	return nil
69}
70
71func (tiz *testInputZip) Close() error {
72	tiz.reader = nil
73	return nil
74}
75
76func (tiz *testInputZip) Entries() []*zip.File {
77	if tiz.reader == nil {
78		panic(fmt.Errorf("%s: should be open to get entries", tiz.Name()))
79	}
80	return tiz.reader.File
81}
82
83func (tiz *testInputZip) IsOpen() bool {
84	return tiz.reader != nil
85}
86
87func TestMergeZips(t *testing.T) {
88	testCases := []struct {
89		name             string
90		in               [][]testZipEntry
91		stripFiles       []string
92		stripDirs        []string
93		jar              bool
94		sort             bool
95		ignoreDuplicates bool
96		stripDirEntries  bool
97		zipsToNotStrip   map[string]bool
98
99		out []testZipEntry
100		err string
101	}{
102		{
103			name: "duplicates error",
104			in: [][]testZipEntry{
105				{a},
106				{a2},
107				{a3},
108			},
109			out: []testZipEntry{a},
110			err: "duplicate",
111		},
112		{
113			name: "duplicates take first",
114			in: [][]testZipEntry{
115				{a},
116				{a2},
117				{a3},
118			},
119			out: []testZipEntry{a},
120
121			ignoreDuplicates: true,
122		},
123		{
124			name: "duplicates identical",
125			in: [][]testZipEntry{
126				{a},
127				{a},
128			},
129			out: []testZipEntry{a},
130		},
131		{
132			name: "sort",
133			in: [][]testZipEntry{
134				{be, bc, bDir, bbDir, bbb, A, metainfDir, manifestFile},
135			},
136			out: []testZipEntry{A, metainfDir, manifestFile, bDir, bbDir, bbb, bc, be},
137
138			sort: true,
139		},
140		{
141			name: "jar sort",
142			in: [][]testZipEntry{
143				{be, bc, bDir, A, metainfDir, manifestFile},
144			},
145			out: []testZipEntry{metainfDir, manifestFile, A, bDir, bc, be},
146
147			jar: true,
148		},
149		{
150			name: "jar merge",
151			in: [][]testZipEntry{
152				{metainfDir, manifestFile, bDir, be},
153				{metainfDir, manifestFile2, bDir, bc},
154				{metainfDir, manifestFile2, A},
155			},
156			out: []testZipEntry{metainfDir, manifestFile, A, bDir, bc, be},
157
158			jar: true,
159		},
160		{
161			name: "merge",
162			in: [][]testZipEntry{
163				{bDir, be},
164				{bDir, bc},
165				{A},
166			},
167			out: []testZipEntry{bDir, be, bc, A},
168		},
169		{
170			name: "strip dir entries",
171			in: [][]testZipEntry{
172				{a, bDir, bbDir, bbb, bc, bd, be},
173			},
174			out: []testZipEntry{a, bbb, bc, bd, be},
175
176			stripDirEntries: true,
177		},
178		{
179			name: "strip files",
180			in: [][]testZipEntry{
181				{a, bDir, bbDir, bbb, bc, bd, be},
182			},
183			out: []testZipEntry{a, bDir, bbDir, bbb, bc},
184
185			stripFiles: []string{"b/d", "b/e"},
186		},
187		{
188			// merge_zips used to treat -stripFile a as stripping any file named a, it now only strips a in the
189			// root of the zip.
190			name: "strip file name",
191			in: [][]testZipEntry{
192				{a, bDir, ba},
193			},
194			out: []testZipEntry{bDir, ba},
195
196			stripFiles: []string{"a"},
197		},
198		{
199			name: "strip files glob",
200			in: [][]testZipEntry{
201				{a, bDir, ba},
202			},
203			out: []testZipEntry{bDir},
204
205			stripFiles: []string{"**/a"},
206		},
207		{
208			name: "strip dirs",
209			in: [][]testZipEntry{
210				{a, bDir, bbDir, bbb, bc, bd, be},
211			},
212			out: []testZipEntry{a},
213
214			stripDirs: []string{"b"},
215		},
216		{
217			name: "strip dirs glob",
218			in: [][]testZipEntry{
219				{a, bDir, bbDir, bbb, bc, bd, be},
220			},
221			out: []testZipEntry{a, bDir, bc, bd, be},
222
223			stripDirs: []string{"b/*"},
224		},
225		{
226			name: "zips to not strip",
227			in: [][]testZipEntry{
228				{a, bDir, bc},
229				{bDir, bd},
230				{bDir, be},
231			},
232			out: []testZipEntry{a, bDir, bd},
233
234			stripDirs: []string{"b"},
235			zipsToNotStrip: map[string]bool{
236				"in1": true,
237			},
238		},
239	}
240
241	for _, test := range testCases {
242		t.Run(test.name, func(t *testing.T) {
243			inputZips := make([]InputZip, len(test.in))
244			for i, in := range test.in {
245				inputZips[i] = &testInputZip{name: "in" + strconv.Itoa(i), entries: in}
246			}
247
248			want := testZipEntriesToBuf(test.out)
249
250			out := &bytes.Buffer{}
251			writer := zip.NewWriter(out)
252
253			err := mergeZips(inputZips, writer, "", "",
254				test.sort, test.jar, false, test.stripDirEntries, test.ignoreDuplicates,
255				test.stripFiles, test.stripDirs, test.zipsToNotStrip)
256
257			closeErr := writer.Close()
258			if closeErr != nil {
259				t.Fatal(err)
260			}
261
262			if test.err != "" {
263				if err == nil {
264					t.Fatal("missing err, expected: ", test.err)
265				} else if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(test.err)) {
266					t.Fatal("incorrect err, want:", test.err, "got:", err)
267				}
268				return
269			}
270
271			if !bytes.Equal(want, out.Bytes()) {
272				t.Error("incorrect zip output")
273				t.Errorf("want:\n%s", dumpZip(want))
274				t.Errorf("got:\n%s", dumpZip(out.Bytes()))
275			}
276		})
277	}
278}
279
280func testZipEntriesToBuf(entries []testZipEntry) []byte {
281	b := &bytes.Buffer{}
282	zw := zip.NewWriter(b)
283
284	for _, e := range entries {
285		fh := zip.FileHeader{
286			Name: e.name,
287		}
288		fh.SetMode(e.mode)
289
290		w, err := zw.CreateHeader(&fh)
291		if err != nil {
292			panic(err)
293		}
294
295		_, err = w.Write(e.data)
296		if err != nil {
297			panic(err)
298		}
299	}
300
301	err := zw.Close()
302	if err != nil {
303		panic(err)
304	}
305
306	return b.Bytes()
307}
308
309func testZipEntriesToZipReader(entries []testZipEntry) *zip.Reader {
310	b := testZipEntriesToBuf(entries)
311	r := bytes.NewReader(b)
312
313	zr, err := zip.NewReader(r, int64(len(b)))
314	if err != nil {
315		panic(err)
316	}
317
318	return zr
319}
320
321func dumpZip(buf []byte) string {
322	r := bytes.NewReader(buf)
323	zr, err := zip.NewReader(r, int64(len(buf)))
324	if err != nil {
325		panic(err)
326	}
327
328	var ret string
329
330	for _, f := range zr.File {
331		ret += fmt.Sprintf("%v: %v %v %08x\n", f.Name, f.Mode(), f.UncompressedSize64, f.CRC32)
332	}
333
334	return ret
335}
336
337type DummyInpuZip struct {
338	isOpen bool
339}
340
341func (diz *DummyInpuZip) Name() string {
342	return "dummy"
343}
344
345func (diz *DummyInpuZip) Open() error {
346	diz.isOpen = true
347	return nil
348}
349
350func (diz *DummyInpuZip) Close() error {
351	diz.isOpen = false
352	return nil
353}
354
355func (DummyInpuZip) Entries() []*zip.File {
356	panic("implement me")
357}
358
359func (diz *DummyInpuZip) IsOpen() bool {
360	return diz.isOpen
361}
362
363func TestInputZipsManager(t *testing.T) {
364	const nInputZips = 20
365	const nMaxOpenZips = 10
366	izm := NewInputZipsManager(20, 10)
367	managedZips := make([]InputZip, nInputZips)
368	for i := 0; i < nInputZips; i++ {
369		managedZips[i] = izm.Manage(&DummyInpuZip{})
370	}
371
372	t.Run("InputZipsManager", func(t *testing.T) {
373		for i, iz := range managedZips {
374			if err := iz.Open(); err != nil {
375				t.Fatalf("Step %d: open failed: %s", i, err)
376				return
377			}
378			if izm.nOpenZips > nMaxOpenZips {
379				t.Errorf("Step %d: should be <=%d open zips", i, nMaxOpenZips)
380			}
381		}
382		if !managedZips[nInputZips-1].IsOpen() {
383			t.Error("The last input should stay open")
384		}
385		for _, iz := range managedZips {
386			iz.Close()
387		}
388		if izm.nOpenZips > 0 {
389			t.Error("Some input zips are still open")
390		}
391	})
392}
393