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