blob: 767d4e61f26286460591698972cd112d496e028f [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"
25
26 "android/soong/jar"
27 "android/soong/third_party/zip"
28)
29
30type testZipEntry struct {
Colin Cross7592d5a2023-07-18 15:57:09 -070031 name string
32 mode os.FileMode
33 data []byte
34 method uint16
Colin Cross24860652018-07-14 22:19:14 -070035}
36
37var (
Colin Cross7592d5a2023-07-18 15:57:09 -070038 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}
Colin Cross24860652018-07-14 22:19:14 -070049
Colin Cross7592d5a2023-07-18 15:57:09 -070050 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}
Colin Cross24860652018-07-14 22:19:14 -070059)
60
Sasha Smundak1459a922019-07-16 18:45:24 -070061type 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
Colin Cross24860652018-07-14 22:19:14 -070094func 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 {
Colin Crossdc1e8292018-10-17 15:05:56 -0700131 name: "duplicates identical",
132 in: [][]testZipEntry{
133 {a},
134 {a},
135 },
136 out: []testZipEntry{a},
137 },
138 {
Colin Cross24860652018-07-14 22:19:14 -0700139 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 {
Colin Cross4c03f682018-07-15 08:16:31 -0700186 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.
Colin Cross24860652018-07-14 22:19:14 -0700197 name: "strip file name",
198 in: [][]testZipEntry{
199 {a, bDir, ba},
200 },
Colin Cross4c03f682018-07-15 08:16:31 -0700201 out: []testZipEntry{bDir, ba},
202
203 stripFiles: []string{"a"},
204 },
205 {
206 name: "strip files glob",
207 in: [][]testZipEntry{
208 {a, bDir, ba},
209 },
Colin Cross24860652018-07-14 22:19:14 -0700210 out: []testZipEntry{bDir},
211
Colin Cross4c03f682018-07-15 08:16:31 -0700212 stripFiles: []string{"**/a"},
Colin Cross24860652018-07-14 22:19:14 -0700213 },
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 {
Colin Cross4c03f682018-07-15 08:16:31 -0700224 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 {
Colin Cross24860652018-07-14 22:19:14 -0700233 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 },
Colin Cross7592d5a2023-07-18 15:57:09 -0700246 {
247 name: "services",
248 in: [][]testZipEntry{
249 {service1a, service2},
250 {service1b},
251 },
252 jar: true,
253 out: []testZipEntry{service1combined, service2},
254 },
Colin Cross24860652018-07-14 22:19:14 -0700255 }
256
257 for _, test := range testCases {
258 t.Run(test.name, func(t *testing.T) {
Sasha Smundak1459a922019-07-16 18:45:24 -0700259 inputZips := make([]InputZip, len(test.in))
Colin Cross24860652018-07-14 22:19:14 -0700260 for i, in := range test.in {
Sasha Smundak1459a922019-07-16 18:45:24 -0700261 inputZips[i] = &testInputZip{name: "in" + strconv.Itoa(i), entries: in}
Colin Cross24860652018-07-14 22:19:14 -0700262 }
263
264 want := testZipEntriesToBuf(test.out)
265
266 out := &bytes.Buffer{}
267 writer := zip.NewWriter(out)
268
Sasha Smundak1459a922019-07-16 18:45:24 -0700269 err := mergeZips(inputZips, writer, "", "",
Colin Cross24860652018-07-14 22:19:14 -0700270 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 {
Colin Cross7592d5a2023-07-18 15:57:09 -0700275 t.Fatal(closeErr)
Colin Cross24860652018-07-14 22:19:14 -0700276 }
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
Colin Cross7592d5a2023-07-18 15:57:09 -0700285 } else if err != nil {
286 t.Fatal("unexpected err: ", err)
Colin Cross24860652018-07-14 22:19:14 -0700287 }
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()))
Colin Cross7592d5a2023-07-18 15:57:09 -0700293 os.WriteFile("/tmp/got.zip", out.Bytes(), 0755)
294 os.WriteFile("/tmp/want.zip", want, 0755)
Colin Cross24860652018-07-14 22:19:14 -0700295 }
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)
Colin Cross7592d5a2023-07-18 15:57:09 -0700309 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 }
Colin Cross24860652018-07-14 22:19:14 -0700315
Colin Cross7592d5a2023-07-18 15:57:09 -0700316 w, err := zw.CreateHeaderAndroid(&fh)
Colin Cross24860652018-07-14 22:19:14 -0700317 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}
Sasha Smundak1459a922019-07-16 18:45:24 -0700362
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}