1// Copyright 2017 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 "reflect" 21 "testing" 22 23 "android/soong/third_party/zip" 24) 25 26var testCases = []struct { 27 name string 28 29 inputFiles []string 30 sortGlobs bool 31 sortJava bool 32 args []string 33 excludes []string 34 includes []string 35 uncompresses []string 36 37 outputFiles []string 38 storedFiles []string 39 err error 40}{ 41 { // This is modelled after the update package build rules in build/make/core/Makefile 42 name: "filter globs", 43 44 inputFiles: []string{ 45 "RADIO/a", 46 "IMAGES/system.img", 47 "IMAGES/b.txt", 48 "IMAGES/recovery.img", 49 "IMAGES/vendor.img", 50 "OTA/android-info.txt", 51 "OTA/b", 52 }, 53 args: []string{"OTA/android-info.txt:android-info.txt", "IMAGES/*.img:."}, 54 55 outputFiles: []string{ 56 "android-info.txt", 57 "system.img", 58 "recovery.img", 59 "vendor.img", 60 }, 61 }, 62 { 63 name: "sorted filter globs", 64 65 inputFiles: []string{ 66 "RADIO/a", 67 "IMAGES/system.img", 68 "IMAGES/b.txt", 69 "IMAGES/recovery.img", 70 "IMAGES/vendor.img", 71 "OTA/android-info.txt", 72 "OTA/b", 73 }, 74 sortGlobs: true, 75 args: []string{"IMAGES/*.img:.", "OTA/android-info.txt:android-info.txt"}, 76 77 outputFiles: []string{ 78 "recovery.img", 79 "system.img", 80 "vendor.img", 81 "android-info.txt", 82 }, 83 }, 84 { 85 name: "sort all", 86 87 inputFiles: []string{ 88 "RADIO/", 89 "RADIO/a", 90 "IMAGES/", 91 "IMAGES/system.img", 92 "IMAGES/b.txt", 93 "IMAGES/recovery.img", 94 "IMAGES/vendor.img", 95 "OTA/", 96 "OTA/b", 97 "OTA/android-info.txt", 98 }, 99 sortGlobs: true, 100 args: []string{"**/*"}, 101 102 outputFiles: []string{ 103 "IMAGES/b.txt", 104 "IMAGES/recovery.img", 105 "IMAGES/system.img", 106 "IMAGES/vendor.img", 107 "OTA/android-info.txt", 108 "OTA/b", 109 "RADIO/a", 110 }, 111 }, 112 { 113 name: "sort all implicit", 114 115 inputFiles: []string{ 116 "RADIO/", 117 "RADIO/a", 118 "IMAGES/", 119 "IMAGES/system.img", 120 "IMAGES/b.txt", 121 "IMAGES/recovery.img", 122 "IMAGES/vendor.img", 123 "OTA/", 124 "OTA/b", 125 "OTA/android-info.txt", 126 }, 127 sortGlobs: true, 128 args: nil, 129 130 outputFiles: []string{ 131 "IMAGES/", 132 "IMAGES/b.txt", 133 "IMAGES/recovery.img", 134 "IMAGES/system.img", 135 "IMAGES/vendor.img", 136 "OTA/", 137 "OTA/android-info.txt", 138 "OTA/b", 139 "RADIO/", 140 "RADIO/a", 141 }, 142 }, 143 { 144 name: "sort jar", 145 146 inputFiles: []string{ 147 "MANIFEST.MF", 148 "META-INF/MANIFEST.MF", 149 "META-INF/aaa/", 150 "META-INF/aaa/aaa", 151 "META-INF/AAA", 152 "META-INF.txt", 153 "META-INF/", 154 "AAA", 155 "aaa", 156 }, 157 sortJava: true, 158 args: nil, 159 160 outputFiles: []string{ 161 "META-INF/", 162 "META-INF/MANIFEST.MF", 163 "META-INF/AAA", 164 "META-INF/aaa/", 165 "META-INF/aaa/aaa", 166 "AAA", 167 "MANIFEST.MF", 168 "META-INF.txt", 169 "aaa", 170 }, 171 }, 172 { 173 name: "double input", 174 175 inputFiles: []string{ 176 "b", 177 "a", 178 }, 179 args: []string{"a:a2", "**/*"}, 180 181 outputFiles: []string{ 182 "a2", 183 "b", 184 "a", 185 }, 186 }, 187 { 188 name: "multiple matches", 189 190 inputFiles: []string{ 191 "a/a", 192 }, 193 args: []string{"a/a", "a/*"}, 194 195 outputFiles: []string{ 196 "a/a", 197 }, 198 }, 199 { 200 name: "multiple conflicting matches", 201 202 inputFiles: []string{ 203 "a/a", 204 "a/b", 205 }, 206 args: []string{"a/b:a/a", "a/*"}, 207 208 err: fmt.Errorf(`multiple entries for "a/a" with different contents`), 209 }, 210 { 211 name: "excludes", 212 213 inputFiles: []string{ 214 "a/a", 215 "a/b", 216 }, 217 args: nil, 218 excludes: []string{"a/a"}, 219 220 outputFiles: []string{ 221 "a/b", 222 }, 223 }, 224 { 225 name: "excludes with args", 226 227 inputFiles: []string{ 228 "a/a", 229 "a/b", 230 }, 231 args: []string{"a/*"}, 232 excludes: []string{"a/a"}, 233 234 outputFiles: []string{ 235 "a/b", 236 }, 237 }, 238 { 239 name: "excludes over args", 240 241 inputFiles: []string{ 242 "a/a", 243 "a/b", 244 }, 245 args: []string{"a/a"}, 246 excludes: []string{"a/*"}, 247 248 outputFiles: nil, 249 }, 250 { 251 name: "excludes with includes", 252 253 inputFiles: []string{ 254 "a/a", 255 "a/b", 256 }, 257 args: nil, 258 excludes: []string{"a/*"}, 259 includes: []string{"a/b"}, 260 261 outputFiles: []string{"a/b"}, 262 }, 263 { 264 name: "excludes with glob", 265 266 inputFiles: []string{ 267 "a/a", 268 "a/b", 269 }, 270 args: []string{"a/*"}, 271 excludes: []string{"a/*"}, 272 273 outputFiles: nil, 274 }, 275 { 276 name: "uncompress one", 277 278 inputFiles: []string{ 279 "a/a", 280 "a/b", 281 }, 282 uncompresses: []string{"a/a"}, 283 284 outputFiles: []string{ 285 "a/a", 286 "a/b", 287 }, 288 storedFiles: []string{ 289 "a/a", 290 }, 291 }, 292 { 293 name: "uncompress two", 294 295 inputFiles: []string{ 296 "a/a", 297 "a/b", 298 }, 299 uncompresses: []string{"a/a", "a/b"}, 300 301 outputFiles: []string{ 302 "a/a", 303 "a/b", 304 }, 305 storedFiles: []string{ 306 "a/a", 307 "a/b", 308 }, 309 }, 310 { 311 name: "uncompress glob", 312 313 inputFiles: []string{ 314 "a/a", 315 "a/b", 316 "a/c.so", 317 "a/d.so", 318 }, 319 uncompresses: []string{"a/*.so"}, 320 321 outputFiles: []string{ 322 "a/a", 323 "a/b", 324 "a/c.so", 325 "a/d.so", 326 }, 327 storedFiles: []string{ 328 "a/c.so", 329 "a/d.so", 330 }, 331 }, 332 { 333 name: "uncompress rename", 334 335 inputFiles: []string{ 336 "a/a", 337 }, 338 args: []string{"a/a:a/b"}, 339 uncompresses: []string{"a/b"}, 340 341 outputFiles: []string{ 342 "a/b", 343 }, 344 storedFiles: []string{ 345 "a/b", 346 }, 347 }, 348 { 349 name: "recursive glob", 350 351 inputFiles: []string{ 352 "a/a/a", 353 "a/a/b", 354 }, 355 args: []string{"a/**/*:b"}, 356 outputFiles: []string{ 357 "b/a/a", 358 "b/a/b", 359 }, 360 }, 361 { 362 name: "glob", 363 364 inputFiles: []string{ 365 "a/a/a", 366 "a/a/b", 367 "a/b", 368 "a/c", 369 }, 370 args: []string{"a/*:b"}, 371 outputFiles: []string{ 372 "b/b", 373 "b/c", 374 }, 375 }, 376 { 377 name: "top level glob", 378 379 inputFiles: []string{ 380 "a", 381 "b", 382 }, 383 args: []string{"*:b"}, 384 outputFiles: []string{ 385 "b/a", 386 "b/b", 387 }, 388 }, 389 { 390 name: "multilple glob", 391 392 inputFiles: []string{ 393 "a/a/a", 394 "a/a/b", 395 }, 396 args: []string{"a/*/*:b"}, 397 outputFiles: []string{ 398 "b/a/a", 399 "b/a/b", 400 }, 401 }, 402 { 403 name: "escaping", 404 405 inputFiles: []string{"a"}, 406 args: []string{"\\a"}, 407 outputFiles: []string{"a"}, 408 }, 409} 410 411func errorString(e error) string { 412 if e == nil { 413 return "" 414 } 415 return e.Error() 416} 417 418func TestZip2Zip(t *testing.T) { 419 for _, testCase := range testCases { 420 t.Run(testCase.name, func(t *testing.T) { 421 inputBuf := &bytes.Buffer{} 422 outputBuf := &bytes.Buffer{} 423 424 inputWriter := zip.NewWriter(inputBuf) 425 for _, file := range testCase.inputFiles { 426 w, err := inputWriter.Create(file) 427 if err != nil { 428 t.Fatal(err) 429 } 430 fmt.Fprintln(w, "test") 431 } 432 inputWriter.Close() 433 inputBytes := inputBuf.Bytes() 434 inputReader, err := zip.NewReader(bytes.NewReader(inputBytes), int64(len(inputBytes))) 435 if err != nil { 436 t.Fatal(err) 437 } 438 439 outputWriter := zip.NewWriter(outputBuf) 440 err = zip2zip(inputReader, outputWriter, testCase.sortGlobs, testCase.sortJava, false, 441 testCase.args, testCase.excludes, testCase.includes, testCase.uncompresses) 442 if errorString(testCase.err) != errorString(err) { 443 t.Fatalf("Unexpected error:\n got: %q\nwant: %q", errorString(err), errorString(testCase.err)) 444 } 445 446 outputWriter.Close() 447 outputBytes := outputBuf.Bytes() 448 outputReader, err := zip.NewReader(bytes.NewReader(outputBytes), int64(len(outputBytes))) 449 if err != nil { 450 t.Fatal(err) 451 } 452 var outputFiles []string 453 var storedFiles []string 454 if len(outputReader.File) > 0 { 455 outputFiles = make([]string, len(outputReader.File)) 456 for i, file := range outputReader.File { 457 outputFiles[i] = file.Name 458 if file.Method == zip.Store { 459 storedFiles = append(storedFiles, file.Name) 460 } 461 } 462 } 463 464 if !reflect.DeepEqual(testCase.outputFiles, outputFiles) { 465 t.Fatalf("Output file list does not match:\nwant: %v\n got: %v", testCase.outputFiles, outputFiles) 466 } 467 if !reflect.DeepEqual(testCase.storedFiles, storedFiles) { 468 t.Fatalf("Stored file list does not match:\nwant: %v\n got: %v", testCase.storedFiles, storedFiles) 469 } 470 }) 471 } 472} 473 474// TestZip2Zip64 tests that zip2zip on zip file larger than 4GB produces a valid zip file. 475func TestZip2Zip64(t *testing.T) { 476 if testing.Short() { 477 t.Skip("skipping slow test in short mode") 478 } 479 inputBuf := &bytes.Buffer{} 480 outputBuf := &bytes.Buffer{} 481 482 inputWriter := zip.NewWriter(inputBuf) 483 w, err := inputWriter.CreateHeaderAndroid(&zip.FileHeader{ 484 Name: "a", 485 Method: zip.Store, 486 }) 487 if err != nil { 488 t.Fatal(err) 489 } 490 buf := make([]byte, 4*1024*1024) 491 for i := 0; i < 1025; i++ { 492 w.Write(buf) 493 } 494 w, err = inputWriter.CreateHeaderAndroid(&zip.FileHeader{ 495 Name: "b", 496 Method: zip.Store, 497 }) 498 for i := 0; i < 1025; i++ { 499 w.Write(buf) 500 } 501 inputWriter.Close() 502 inputBytes := inputBuf.Bytes() 503 504 inputReader, err := zip.NewReader(bytes.NewReader(inputBytes), int64(len(inputBytes))) 505 if err != nil { 506 t.Fatal(err) 507 } 508 509 outputWriter := zip.NewWriter(outputBuf) 510 err = zip2zip(inputReader, outputWriter, false, false, false, 511 nil, nil, nil, nil) 512 if err != nil { 513 t.Fatal(err) 514 } 515 516 outputWriter.Close() 517 outputBytes := outputBuf.Bytes() 518 _, err = zip.NewReader(bytes.NewReader(outputBytes), int64(len(outputBytes))) 519 if err != nil { 520 t.Fatal(err) 521 } 522} 523 524func TestConstantPartOfPattern(t *testing.T) { 525 testCases := []struct{ in, out string }{ 526 { 527 in: "", 528 out: "", 529 }, 530 { 531 in: "a", 532 out: "a", 533 }, 534 { 535 in: "*", 536 out: "", 537 }, 538 { 539 in: "a/a", 540 out: "a/a", 541 }, 542 { 543 in: "a/*", 544 out: "a", 545 }, 546 { 547 in: "a/*/a", 548 out: "a", 549 }, 550 { 551 in: "a/**/*", 552 out: "a", 553 }, 554 } 555 556 for _, test := range testCases { 557 t.Run(test.in, func(t *testing.T) { 558 got := constantPartOfPattern(test.in) 559 if got != test.out { 560 t.Errorf("want %q, got %q", test.out, got) 561 } 562 }) 563 } 564} 565