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