// 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 com.google.devtools.build.android.DensitySpecificManifestProcessor.PLAY_STORE_SUPPORTED_DENSITIES; import static com.google.devtools.build.android.DensitySpecificManifestProcessor.SCREEN_SIZES; import static org.junit.Assert.fail; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.jimfs.Jimfs; import java.io.BufferedWriter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** Tests for {@link DensitySpecificManifestProcessor}. */ @RunWith(JUnit4.class) public class DensitySpecificManifestProcessorTest { private FileSystem fs; private Path tmp; @Test public void testNoDensities() throws Exception { Path manifest = createManifest("", "", ""); Path modified = new DensitySpecificManifestProcessor(ImmutableList.of(), tmp.resolve("manifest-filtered/AndroidManifest.xml")).process(manifest); assertThat((Object) modified).isEqualTo(manifest); } @Test public void testSingleDensity() throws Exception { ImmutableList densities = ImmutableList.of("xhdpi"); Path manifest = createManifest("", "", ""); Path modified = new DensitySpecificManifestProcessor(densities, tmp.resolve("manifest-filtered/AndroidManifest.xml")).process(manifest); assertThat((Object) modified).isNotNull(); checkModification(modified, densities); } @Test public void test280Density() throws Exception { ImmutableList densities = ImmutableList.of("280dpi"); Path manifest = createManifest("", "", ""); Path modified = new DensitySpecificManifestProcessor(densities, tmp.resolve("manifest-filtered/AndroidManifest.xml")).process(manifest); assertThat((Object) modified).isNotNull(); checkModification(modified, densities); } @Test public void testMultipleDensities() throws Exception { ImmutableList densities = ImmutableList.of("xhdpi", "xxhdpi", "560dpi", "xxxhdpi"); Path manifest = createManifest("", "", ""); Path modified = new DensitySpecificManifestProcessor(densities, tmp.resolve("manifest-filtered/AndroidManifest.xml")).process(manifest); assertThat((Object) modified).isNotNull(); checkModification(modified, densities); } @Test public void omitCompatibleScreensIfDensityUnsupported() throws Exception { ImmutableList densities = ImmutableList.of("xhdpi", "340dpi", "xxhdpi"); Path manifest = createManifest("", "", ""); Path modified = new DensitySpecificManifestProcessor(densities, tmp.resolve("manifest-filtered/AndroidManifest.xml")).process(manifest); assertThat((Object) modified).isNotNull(); checkCompatibleScreensOmitted(modified); } @Test public void testExistingCompatibleScreens() throws Exception { ImmutableList densities = ImmutableList.of("xhdpi"); Path manifest = createManifest("", "", "", "", ""); Path modified = new DensitySpecificManifestProcessor(densities, tmp.resolve("manifest-filtered/AndroidManifest.xml")).process(manifest); assertThat((Object) modified).isNotNull(); checkModification(modified, densities); } @Test public void testExistingSupersetCompatibleScreens() throws Exception { ImmutableList densities = ImmutableList.of("ldpi"); Path manifest = createManifest("", "", "", " ", " ", " ", " ", " ", " ", " ", " ", "", ""); Path modified = new DensitySpecificManifestProcessor(densities, tmp.resolve("manifest-filtered/AndroidManifest.xml")).process(manifest); assertThat((Object) modified).isNotNull(); checkModification(modified, ImmutableList.of("ldpi", "xxhdpi")); } @Test public void testMalformedManifest() throws Exception { Path manifest = createManifest("", "", "", "", ""); try { new DensitySpecificManifestProcessor(ImmutableList.of("xhdpi"), tmp.resolve("manifest-filtered/AndroidManifest.xml")).process(manifest); fail(); } catch (AndroidManifestProcessor.ManifestProcessingException e) { assertThat(e).hasMessageThat().contains("must be well-formed"); } } @Test public void testNoManifest() throws Exception { Path manifest = createManifest(""); try { new DensitySpecificManifestProcessor(ImmutableList.of("xhdpi"), tmp.resolve("manifest-filtered/AndroidManifest.xml")).process(manifest); fail(); } catch (AndroidManifestProcessor.ManifestProcessingException e) { assertThat(e).hasMessageThat().contains("Premature end of file."); } } @Test public void testNestedManifest() throws Exception { Path manifest = createManifest("", "", " ", " ", ""); try { new DensitySpecificManifestProcessor(ImmutableList.of("xhdpi"), tmp.resolve("manifest-filtered/AndroidManifest.xml")).process(manifest); fail(); } catch (AndroidManifestProcessor.ManifestProcessingException e) { assertThat(e).hasMessageThat().contains("does not contain exactly one "); } } @Before public void setUpEnvironment() throws Exception { fs = Jimfs.newFileSystem(); tmp = Files.createTempDirectory(this.getClass().getSimpleName()); } @After public void cleanUpEnvironment() throws Exception { fs.close(); } private Path createManifest(String... lines) throws IOException { final Path path = tmp.resolve("AndroidManifest.xml"); Files.createDirectories(path.getParent()); Files.deleteIfExists(path); try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) { writer.write(Joiner.on("\n").join(lines)); } return path; } private void checkModification(Path manifest, List densities) throws Exception { Set sizeDensities = new HashSet<>(); for (String density : densities) { for (String screenSize : SCREEN_SIZES) { sizeDensities.add(screenSize + PLAY_STORE_SUPPORTED_DENSITIES.get(density)); } } DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = db.parse(Files.newInputStream(manifest)); NodeList compatibleScreensNodes = doc.getElementsByTagName("compatible-screens"); assertThat(compatibleScreensNodes.getLength()).isEqualTo(1); Node compatibleScreens = compatibleScreensNodes.item(0); NodeList screens = doc.getElementsByTagName("screen"); assertThat(screens.getLength()).isEqualTo(densities.size() * SCREEN_SIZES.size()); for (int i = 0; i < screens.getLength(); i++) { Node s = screens.item(i); assertThat(s.getParentNode().isSameNode(compatibleScreens)).isTrue(); if (s.getNodeType() == Node.ELEMENT_NODE) { Element screen = (Element) s; assertThat( sizeDensities.remove( screen.getAttribute("android:screenSize") + screen.getAttribute("android:screenDensity"))) .isTrue(); } } assertThat(sizeDensities).isEmpty(); } private void checkCompatibleScreensOmitted(Path manifest) throws Exception { DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = db.parse(Files.newInputStream(manifest)); NodeList compatibleScreensNodes = doc.getElementsByTagName("compatible-screens"); assertThat(compatibleScreensNodes.getLength()).isEqualTo(0); NodeList screens = doc.getElementsByTagName("screen"); assertThat(screens.getLength()).isEqualTo(0); } }