| // 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<String> outputEntriesWithArgs(ImmutableList<String> args, File output) |
| throws IOException { |
| ZipFilterAction.run(args.toArray(new String[0])); |
| List<String> filteredEntries = new ArrayList<>(); |
| try (ZipFile zip = new ZipFile(output)) { |
| Enumeration<? extends ZipEntry> 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<Path> filters = ImmutableSet.of( |
| createZip("foo.class", "bar.java"), |
| createZip("foo.java", "bar.java", "baz.class")); |
| ImmutableSet<String> types = ImmutableSet.of(".class"); |
| Multimap<String, Long> 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<Path> filters = ImmutableSet.of(); |
| ImmutableSet<String> types = ImmutableSet.of(".class"); |
| Multimap<String, Long> filterFiles = ZipFilterAction.getEntriesToOmit(filters, types); |
| assertThat(filterFiles).isEmpty(); |
| } |
| |
| @Test public void testCreateFilter_NoTypes() throws IOException { |
| ImmutableSet<Path> filters = ImmutableSet.of( |
| createZip("foo.class", "bar.java"), |
| createZip("foo.java", "bar.java", "baz.class")); |
| ImmutableSet<String> types = ImmutableSet.of(); |
| Multimap<String, Long> 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<Path> filters = ImmutableSet.of( |
| createZip("foo.class", "bar.java"), |
| createZip("foo.java", "bar.java", "baz.class")); |
| ImmutableSet<String> types = ImmutableSet.of(".class", "bar.java"); |
| Multimap<String, Long> 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<String> 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<String> 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<String> 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<String> 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<String> 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"); |
| } |
| |
| } |