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