// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.android; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimap; import com.google.devtools.build.android.ZipFilterAction.HashMismatchCheckMode; import com.google.devtools.build.singlejar.ZipEntryFilter.CustomMergeStrategy; import com.google.devtools.build.singlejar.ZipEntryFilter.StrategyCallback; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for {@link ZipFilterAction}. */ @RunWith(JUnit4.class) public class ZipFilterActionTest { private static final class Entry { private final String name; private final String contents; public Entry(String name, String contents) { this.name = name; this.contents = contents; } public String getName() { return name; } public String getContents() { return contents; } } private enum FilterOperation { SKIP, RENAME, CUSTOM_MERGE, COPY } private static final class TestingStrategyCallback implements StrategyCallback { private FilterOperation operation; public void assertOp(FilterOperation operation) { assertThat(this.operation).isEqualTo(operation); } @Override public void skip() throws IOException { operation = FilterOperation.SKIP; } @Override public void rename(String filename, Date date) throws IOException { operation = FilterOperation.RENAME; } @Override public void customMerge(Date date, CustomMergeStrategy strategy) throws IOException { operation = FilterOperation.CUSTOM_MERGE; } @Override public void copy(Date date) throws IOException { operation = FilterOperation.COPY; } } @Rule public ExpectedException thrown = ExpectedException.none(); @Rule public TemporaryFolder tmp = new TemporaryFolder(); private int fileCount; private TestingStrategyCallback callback; private Path createZip(String... filenames) throws IOException { Entry[] entries = new Entry[filenames.length]; int i = 0; for (String filename : filenames) { entries[i++] = new Entry(filename, "" + fileCount++); } return createZip(entries); } private Path createZip(Entry... entries) throws IOException { File zip = tmp.newFile(); try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(zip))) { for (Entry entry : entries) { ZipEntry e = new ZipEntry(entry.getName()); zout.putNextEntry(e); zout.write(entry.getContents().getBytes(UTF_8)); zout.closeEntry(); } } return zip.toPath(); } private List outputEntriesWithArgs(ImmutableList args, File output) throws IOException { ZipFilterAction.run(args.toArray(new String[0])); List filteredEntries = new ArrayList<>(); try (ZipFile zip = new ZipFile(output)) { Enumeration entries = zip.entries(); while (entries.hasMoreElements()) { filteredEntries.add(entries.nextElement().getName()); } } return filteredEntries; } @Before public void setup() { callback = new TestingStrategyCallback(); } @Test public void testCreateFilter() throws IOException { ImmutableSet filters = ImmutableSet.of( createZip("foo.class", "bar.java"), createZip("foo.java", "bar.java", "baz.class")); ImmutableSet types = ImmutableSet.of(".class"); Multimap filterFiles = ZipFilterAction.getEntriesToOmit(filters, types); assertThat(filterFiles.keySet()).containsExactly("foo.class", "baz.class"); assertThat(filterFiles).valuesForKey("foo.class").hasSize(1); assertThat(filterFiles).valuesForKey("baz.class").hasSize(1); } @Test public void testCreateFilter_NoZips() throws IOException { ImmutableSet filters = ImmutableSet.of(); ImmutableSet types = ImmutableSet.of(".class"); Multimap filterFiles = ZipFilterAction.getEntriesToOmit(filters, types); assertThat(filterFiles).isEmpty(); } @Test public void testCreateFilter_NoTypes() throws IOException { ImmutableSet filters = ImmutableSet.of( createZip("foo.class", "bar.java"), createZip("foo.java", "bar.java", "baz.class")); ImmutableSet types = ImmutableSet.of(); Multimap filterFiles = ZipFilterAction.getEntriesToOmit(filters, types); assertThat(filterFiles.keySet()) .containsExactly("foo.class", "bar.java", "foo.java", "baz.class"); } @Test public void testCreateFilter_MultipleTypes() throws IOException { ImmutableSet filters = ImmutableSet.of( createZip("foo.class", "bar.java"), createZip("foo.java", "bar.java", "baz.class")); ImmutableSet types = ImmutableSet.of(".class", "bar.java"); Multimap filterFiles = ZipFilterAction.getEntriesToOmit(filters, types); assertThat(filterFiles.keySet()).containsExactly("foo.class", "baz.class", "bar.java"); assertThat(filterFiles).valuesForKey("foo.class").hasSize(1); assertThat(filterFiles).valuesForKey("bar.java").hasSize(2); } @Test public void testZipEntryFilter() throws Exception { ZipFilterEntryFilter filter = new ZipFilterEntryFilter( ".*R.class.*", ImmutableSetMultimap.of("foo.class", 1L, "baz.class", 2L), ImmutableMap.of("foo.class", 1L, "bar.class", 2L, "baz.class", 3L, "res/R.class", 4L), HashMismatchCheckMode.WARN); filter.accept("foo.class", callback); callback.assertOp(FilterOperation.SKIP); filter.accept("bar.class", callback); callback.assertOp(FilterOperation.COPY); filter.accept("baz.class", callback); callback.assertOp(FilterOperation.COPY); filter.accept("res/R.class", callback); callback.assertOp(FilterOperation.SKIP); } @Test public void testZipEntryFilter_ErrorOnMismatch() throws Exception { ZipFilterEntryFilter filter = new ZipFilterEntryFilter( ".*R.class.*", ImmutableSetMultimap.of("foo.class", 1L, "baz.class", 2L), ImmutableMap.of("foo.class", 1L, "bar.class", 2L, "baz.class", 3L, "res/R.class", 4L), HashMismatchCheckMode.ERROR); filter.accept("foo.class", callback); callback.assertOp(FilterOperation.SKIP); filter.accept("bar.class", callback); callback.assertOp(FilterOperation.COPY); filter.accept("res/R.class", callback); callback.assertOp(FilterOperation.SKIP); filter.accept("baz.class", callback); assertThat(filter.sawErrors()).isTrue(); } @Test public void testFlags() throws Exception { File input = tmp.newFile("input"); File output = tmp.newFile("output"); output.delete(); File filter1 = tmp.newFile("filter1"); File filter2 = tmp.newFile("filter2"); ImmutableList args = ImmutableList.of( "--inputZip", input.getPath(), "--outputZip", output.getPath(), "--filterZips", Joiner.on(",").join(filter1.getPath(), filter2.getPath(), filter1.getPath()), "--filterTypes", Joiner.on(",").join(".class", ".class", ".java"), "--explicitFilters", Joiner.on(",").join("R\\.class", "R\\$.*\\.class"), "--outputMode", "DONT_CARE", "--checkHashMismatch", "IGNORE"); thrown.expect(ZipException.class); thrown.expectMessage("Zip file 'filter1' is malformed"); ZipFilterAction.run(args.toArray(new String[0])); } @Test public void testFullIntegration() throws IOException { Path input = createZip(new Entry("foo.java", "foo"), new Entry("bar.class", "bar"), new Entry("baz.class", "baz"), new Entry("1.class", "1"), new Entry("2.class", "2"), new Entry("R.class", "r"), new Entry("Read.class", "read")); File output = tmp.newFile(); output.delete(); Path filter1 = createZip(new Entry("1.class", "1"), new Entry("b.class", "b")); Path filter2 = createZip(new Entry("1.class", "2"), new Entry("2.class", "1"), new Entry("foo.java", "foo"), new Entry("bar.class", "bar")); ImmutableList args = ImmutableList.of( "--inputZip", input.toFile().getPath(), "--outputZip", output.getPath(), "--filterZips", Joiner.on(",").join(filter1.toFile().getPath(), filter2.toFile().getPath(), filter1.toFile().getPath()), "--filterTypes", ".class", "--explicitFilters", Joiner.on(",").join("R\\.class", "R\\$.*\\.class"), "--outputMode", "DONT_CARE"); assertThat(outputEntriesWithArgs(args, output)) .containsExactly("foo.java", "baz.class", "2.class", "Read.class"); } @Test public void testFullIntegrationErrorsOnHash() throws IOException { Path input = createZip("foo.java", "bar.class", "baz.class"); File output = tmp.newFile(); output.delete(); Path filter = createZip("foo.java", "bar.class"); ImmutableList args = ImmutableList.of( "--inputZip", input.toFile().getPath(), "--outputZip", output.getPath(), "--filterZips", filter.toFile().getPath(), "--filterTypes", ".class", "--checkHashMismatch", "ERROR", "--outputMode", "DONT_CARE"); int exitCode = ZipFilterAction.run(args.toArray(new String[0])); assertThat(exitCode).isEqualTo(1); } @Test public void testSkipHashMismatchCheck() throws IOException { Path input = createZip( new Entry("foo.java", "foo"), new Entry("bar.class", "bar1"), new Entry("baz.class", "baz")); File output = tmp.newFile(); output.delete(); Path filter = createZip(new Entry("foo.java", "foo"), new Entry("bar.class", "bar2")); ImmutableList args = ImmutableList.of( "--inputZip", input.toFile().getPath(), "--outputZip", output.getPath(), "--filterZips", filter.toFile().getPath(), "--filterTypes", ".class", "--checkHashMismatch", "IGNORE", "--outputMode", "DONT_CARE"); assertThat(outputEntriesWithArgs(args, output)).containsExactly("foo.java", "baz.class"); } @Test public void testFullIntegrationErrorsOnHash_WithExplicitOverride() throws IOException { Path input = createZip("foo.java", "bar.class", "baz.class"); File output = tmp.newFile(); output.delete(); Path filter = createZip("foo.java", "bar.class"); ImmutableList args = ImmutableList.of( "--inputZip", input.toFile().getPath(), "--outputZip", output.getPath(), "--filterZips", filter.toFile().getPath(), "--filterTypes", ".class", "--explicitFilters", "bar\\.class", "--outputMode", "DONT_CARE", "--errorOnHashMismatch"); assertThat(outputEntriesWithArgs(args, output)).containsExactly("foo.java", "baz.class"); } }