Home | History | Annotate | Download | only in zip2zip
      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 
     15 package main
     16 
     17 import (
     18 	"bytes"
     19 	"fmt"
     20 	"reflect"
     21 	"testing"
     22 
     23 	"android/soong/third_party/zip"
     24 )
     25 
     26 var 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 	{
     42 		name: "unsupported \\",
     43 
     44 		args: []string{"a\\b:b"},
     45 
     46 		err: fmt.Errorf("\\ characters are not currently supported"),
     47 	},
     48 	{ // This is modelled after the update package build rules in build/make/core/Makefile
     49 		name: "filter globs",
     50 
     51 		inputFiles: []string{
     52 			"RADIO/a",
     53 			"IMAGES/system.img",
     54 			"IMAGES/b.txt",
     55 			"IMAGES/recovery.img",
     56 			"IMAGES/vendor.img",
     57 			"OTA/android-info.txt",
     58 			"OTA/b",
     59 		},
     60 		args: []string{"OTA/android-info.txt:android-info.txt", "IMAGES/*.img:."},
     61 
     62 		outputFiles: []string{
     63 			"android-info.txt",
     64 			"system.img",
     65 			"recovery.img",
     66 			"vendor.img",
     67 		},
     68 	},
     69 	{
     70 		name: "sorted filter globs",
     71 
     72 		inputFiles: []string{
     73 			"RADIO/a",
     74 			"IMAGES/system.img",
     75 			"IMAGES/b.txt",
     76 			"IMAGES/recovery.img",
     77 			"IMAGES/vendor.img",
     78 			"OTA/android-info.txt",
     79 			"OTA/b",
     80 		},
     81 		sortGlobs: true,
     82 		args:      []string{"IMAGES/*.img:.", "OTA/android-info.txt:android-info.txt"},
     83 
     84 		outputFiles: []string{
     85 			"recovery.img",
     86 			"system.img",
     87 			"vendor.img",
     88 			"android-info.txt",
     89 		},
     90 	},
     91 	{
     92 		name: "sort all",
     93 
     94 		inputFiles: []string{
     95 			"RADIO/",
     96 			"RADIO/a",
     97 			"IMAGES/",
     98 			"IMAGES/system.img",
     99 			"IMAGES/b.txt",
    100 			"IMAGES/recovery.img",
    101 			"IMAGES/vendor.img",
    102 			"OTA/",
    103 			"OTA/b",
    104 			"OTA/android-info.txt",
    105 		},
    106 		sortGlobs: true,
    107 		args:      []string{"**/*"},
    108 
    109 		outputFiles: []string{
    110 			"IMAGES/b.txt",
    111 			"IMAGES/recovery.img",
    112 			"IMAGES/system.img",
    113 			"IMAGES/vendor.img",
    114 			"OTA/android-info.txt",
    115 			"OTA/b",
    116 			"RADIO/a",
    117 		},
    118 	},
    119 	{
    120 		name: "sort all implicit",
    121 
    122 		inputFiles: []string{
    123 			"RADIO/",
    124 			"RADIO/a",
    125 			"IMAGES/",
    126 			"IMAGES/system.img",
    127 			"IMAGES/b.txt",
    128 			"IMAGES/recovery.img",
    129 			"IMAGES/vendor.img",
    130 			"OTA/",
    131 			"OTA/b",
    132 			"OTA/android-info.txt",
    133 		},
    134 		sortGlobs: true,
    135 		args:      nil,
    136 
    137 		outputFiles: []string{
    138 			"IMAGES/",
    139 			"IMAGES/b.txt",
    140 			"IMAGES/recovery.img",
    141 			"IMAGES/system.img",
    142 			"IMAGES/vendor.img",
    143 			"OTA/",
    144 			"OTA/android-info.txt",
    145 			"OTA/b",
    146 			"RADIO/",
    147 			"RADIO/a",
    148 		},
    149 	},
    150 	{
    151 		name: "sort jar",
    152 
    153 		inputFiles: []string{
    154 			"MANIFEST.MF",
    155 			"META-INF/MANIFEST.MF",
    156 			"META-INF/aaa/",
    157 			"META-INF/aaa/aaa",
    158 			"META-INF/AAA",
    159 			"META-INF.txt",
    160 			"META-INF/",
    161 			"AAA",
    162 			"aaa",
    163 		},
    164 		sortJava: true,
    165 		args:     nil,
    166 
    167 		outputFiles: []string{
    168 			"META-INF/",
    169 			"META-INF/MANIFEST.MF",
    170 			"META-INF/AAA",
    171 			"META-INF/aaa/",
    172 			"META-INF/aaa/aaa",
    173 			"AAA",
    174 			"MANIFEST.MF",
    175 			"META-INF.txt",
    176 			"aaa",
    177 		},
    178 	},
    179 	{
    180 		name: "double input",
    181 
    182 		inputFiles: []string{
    183 			"b",
    184 			"a",
    185 		},
    186 		args: []string{"a:a2", "**/*"},
    187 
    188 		outputFiles: []string{
    189 			"a2",
    190 			"b",
    191 			"a",
    192 		},
    193 	},
    194 	{
    195 		name: "multiple matches",
    196 
    197 		inputFiles: []string{
    198 			"a/a",
    199 		},
    200 		args: []string{"a/a", "a/*"},
    201 
    202 		outputFiles: []string{
    203 			"a/a",
    204 		},
    205 	},
    206 	{
    207 		name: "multiple conflicting matches",
    208 
    209 		inputFiles: []string{
    210 			"a/a",
    211 			"a/b",
    212 		},
    213 		args: []string{"a/b:a/a", "a/*"},
    214 
    215 		err: fmt.Errorf(`multiple entries for "a/a" with different contents`),
    216 	},
    217 	{
    218 		name: "excludes",
    219 
    220 		inputFiles: []string{
    221 			"a/a",
    222 			"a/b",
    223 		},
    224 		args:     nil,
    225 		excludes: []string{"a/a"},
    226 
    227 		outputFiles: []string{
    228 			"a/b",
    229 		},
    230 	},
    231 	{
    232 		name: "excludes with args",
    233 
    234 		inputFiles: []string{
    235 			"a/a",
    236 			"a/b",
    237 		},
    238 		args:     []string{"a/*"},
    239 		excludes: []string{"a/a"},
    240 
    241 		outputFiles: []string{
    242 			"a/b",
    243 		},
    244 	},
    245 	{
    246 		name: "excludes over args",
    247 
    248 		inputFiles: []string{
    249 			"a/a",
    250 			"a/b",
    251 		},
    252 		args:     []string{"a/a"},
    253 		excludes: []string{"a/*"},
    254 
    255 		outputFiles: nil,
    256 	},
    257 	{
    258 		name: "excludes with includes",
    259 
    260 		inputFiles: []string{
    261 			"a/a",
    262 			"a/b",
    263 		},
    264 		args:     nil,
    265 		excludes: []string{"a/*"},
    266 		includes: []string{"a/b"},
    267 
    268 		outputFiles: []string{"a/b"},
    269 	},
    270 	{
    271 		name: "excludes with glob",
    272 
    273 		inputFiles: []string{
    274 			"a/a",
    275 			"a/b",
    276 		},
    277 		args:     []string{"a/*"},
    278 		excludes: []string{"a/*"},
    279 
    280 		outputFiles: nil,
    281 	},
    282 	{
    283 		name: "uncompress one",
    284 
    285 		inputFiles: []string{
    286 			"a/a",
    287 			"a/b",
    288 		},
    289 		uncompresses: []string{"a/a"},
    290 
    291 		outputFiles: []string{
    292 			"a/a",
    293 			"a/b",
    294 		},
    295 		storedFiles: []string{
    296 			"a/a",
    297 		},
    298 	},
    299 	{
    300 		name: "uncompress two",
    301 
    302 		inputFiles: []string{
    303 			"a/a",
    304 			"a/b",
    305 		},
    306 		uncompresses: []string{"a/a", "a/b"},
    307 
    308 		outputFiles: []string{
    309 			"a/a",
    310 			"a/b",
    311 		},
    312 		storedFiles: []string{
    313 			"a/a",
    314 			"a/b",
    315 		},
    316 	},
    317 	{
    318 		name: "uncompress glob",
    319 
    320 		inputFiles: []string{
    321 			"a/a",
    322 			"a/b",
    323 			"a/c.so",
    324 			"a/d.so",
    325 		},
    326 		uncompresses: []string{"a/*.so"},
    327 
    328 		outputFiles: []string{
    329 			"a/a",
    330 			"a/b",
    331 			"a/c.so",
    332 			"a/d.so",
    333 		},
    334 		storedFiles: []string{
    335 			"a/c.so",
    336 			"a/d.so",
    337 		},
    338 	},
    339 	{
    340 		name: "uncompress rename",
    341 
    342 		inputFiles: []string{
    343 			"a/a",
    344 		},
    345 		args:         []string{"a/a:a/b"},
    346 		uncompresses: []string{"a/b"},
    347 
    348 		outputFiles: []string{
    349 			"a/b",
    350 		},
    351 		storedFiles: []string{
    352 			"a/b",
    353 		},
    354 	},
    355 	{
    356 		name: "recursive glob",
    357 
    358 		inputFiles: []string{
    359 			"a/a/a",
    360 			"a/a/b",
    361 		},
    362 		args: []string{"a/**/*:b"},
    363 		outputFiles: []string{
    364 			"b/a/a",
    365 			"b/a/b",
    366 		},
    367 	},
    368 	{
    369 		name: "glob",
    370 
    371 		inputFiles: []string{
    372 			"a/a/a",
    373 			"a/a/b",
    374 			"a/b",
    375 			"a/c",
    376 		},
    377 		args: []string{"a/*:b"},
    378 		outputFiles: []string{
    379 			"b/b",
    380 			"b/c",
    381 		},
    382 	},
    383 	{
    384 		name: "top level glob",
    385 
    386 		inputFiles: []string{
    387 			"a",
    388 			"b",
    389 		},
    390 		args: []string{"*:b"},
    391 		outputFiles: []string{
    392 			"b/a",
    393 			"b/b",
    394 		},
    395 	},
    396 	{
    397 		name: "multilple glob",
    398 
    399 		inputFiles: []string{
    400 			"a/a/a",
    401 			"a/a/b",
    402 		},
    403 		args: []string{"a/*/*:b"},
    404 		outputFiles: []string{
    405 			"b/a/a",
    406 			"b/a/b",
    407 		},
    408 	},
    409 }
    410 
    411 func errorString(e error) string {
    412 	if e == nil {
    413 		return ""
    414 	}
    415 	return e.Error()
    416 }
    417 
    418 func 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 func TestConstantPartOfPattern(t *testing.T) {
    475 	testCases := []struct{ in, out string }{
    476 		{
    477 			in:  "",
    478 			out: "",
    479 		},
    480 		{
    481 			in:  "a",
    482 			out: "a",
    483 		},
    484 		{
    485 			in:  "*",
    486 			out: "",
    487 		},
    488 		{
    489 			in:  "a/a",
    490 			out: "a/a",
    491 		},
    492 		{
    493 			in:  "a/*",
    494 			out: "a",
    495 		},
    496 		{
    497 			in:  "a/*/a",
    498 			out: "a",
    499 		},
    500 		{
    501 			in:  "a/**/*",
    502 			out: "a",
    503 		},
    504 	}
    505 
    506 	for _, test := range testCases {
    507 		t.Run(test.in, func(t *testing.T) {
    508 			got := constantPartOfPattern(test.in)
    509 			if got != test.out {
    510 				t.Errorf("want %q, got %q", test.out, got)
    511 			}
    512 		})
    513 	}
    514 }
    515