/* * Copyright (C) 2011 The Android Open Source Project * * 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.android.tools.lint.detector.api; import static com.android.tools.lint.detector.api.LintUtils.computeResourceName; import static com.android.tools.lint.detector.api.LintUtils.convertVersion; import static com.android.tools.lint.detector.api.LintUtils.escapePropertyValue; import static com.android.tools.lint.detector.api.LintUtils.findSubstring; import static com.android.tools.lint.detector.api.LintUtils.getFormattedParameters; import static com.android.tools.lint.detector.api.LintUtils.getLocaleAndRegion; import static com.android.tools.lint.detector.api.LintUtils.isImported; import static com.android.tools.lint.detector.api.LintUtils.splitPath; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.builder.model.AndroidProject; import com.android.builder.model.ApiVersion; import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget; import com.android.tools.lint.EcjParser; import com.android.tools.lint.LintCliClient; import com.android.tools.lint.checks.BuiltinIssueRegistry; import com.android.tools.lint.client.api.JavaParser; import com.android.tools.lint.client.api.LintDriver; import com.google.common.collect.Iterables; import junit.framework.TestCase; import org.intellij.lang.annotations.Language; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.util.Arrays; import java.util.Collections; import java.util.Locale; import lombok.ast.Node; @SuppressWarnings("javadoc") public class LintUtilsTest extends TestCase { public void testPrintList() throws Exception { assertEquals("foo, bar, baz", LintUtils.formatList(Arrays.asList("foo", "bar", "baz"), 3)); assertEquals("foo, bar, baz", LintUtils.formatList(Arrays.asList("foo", "bar", "baz"), 5)); assertEquals("foo, bar, baz... (3 more)", LintUtils.formatList( Arrays.asList("foo", "bar", "baz", "4", "5", "6"), 3)); assertEquals("foo... (5 more)", LintUtils.formatList( Arrays.asList("foo", "bar", "baz", "4", "5", "6"), 1)); assertEquals("foo, bar, baz", LintUtils.formatList(Arrays.asList("foo", "bar", "baz"), 0)); } public void testEndsWith() throws Exception { assertTrue(LintUtils.endsWith("Foo", "")); assertTrue(LintUtils.endsWith("Foo", "o")); assertTrue(LintUtils.endsWith("Foo", "oo")); assertTrue(LintUtils.endsWith("Foo", "Foo")); assertTrue(LintUtils.endsWith("Foo", "FOO")); assertTrue(LintUtils.endsWith("Foo", "fOO")); assertFalse(LintUtils.endsWith("Foo", "f")); } public void testStartsWith() throws Exception { assertTrue(LintUtils.startsWith("FooBar", "Bar", 3)); assertTrue(LintUtils.startsWith("FooBar", "BAR", 3)); assertTrue(LintUtils.startsWith("FooBar", "Foo", 0)); assertFalse(LintUtils.startsWith("FooBar", "Foo", 2)); } public void testIsXmlFile() throws Exception { assertTrue(LintUtils.isXmlFile(new File("foo.xml"))); assertTrue(LintUtils.isXmlFile(new File("foo.Xml"))); assertTrue(LintUtils.isXmlFile(new File("foo.XML"))); assertFalse(LintUtils.isXmlFile(new File("foo.png"))); assertFalse(LintUtils.isXmlFile(new File("xml"))); assertFalse(LintUtils.isXmlFile(new File("xml.png"))); } public void testGetBasename() throws Exception { assertEquals("foo", LintUtils.getBaseName("foo.png")); assertEquals("foo", LintUtils.getBaseName("foo.9.png")); assertEquals(".foo", LintUtils.getBaseName(".foo")); } public void testEditDistance() { assertEquals(0, LintUtils.editDistance("kitten", "kitten")); // editing kitten to sitting has edit distance 3: // replace k with s // replace e with i // append g assertEquals(3, LintUtils.editDistance("kitten", "sitting")); assertEquals(3, LintUtils.editDistance("saturday", "sunday")); assertEquals(1, LintUtils.editDistance("button", "bitton")); assertEquals(6, LintUtils.editDistance("radiobutton", "bitton")); } public void testSplitPath() throws Exception { assertTrue(Arrays.equals(new String[] { "/foo", "/bar", "/baz" }, Iterables.toArray(splitPath("/foo:/bar:/baz"), String.class))); assertTrue(Arrays.equals(new String[] { "/foo", "/bar" }, Iterables.toArray(splitPath("/foo;/bar"), String.class))); assertTrue(Arrays.equals(new String[] { "/foo", "/bar:baz" }, Iterables.toArray(splitPath("/foo;/bar:baz"), String.class))); assertTrue(Arrays.equals(new String[] { "\\foo\\bar", "\\bar\\foo" }, Iterables.toArray(splitPath("\\foo\\bar;\\bar\\foo"), String.class))); assertTrue(Arrays.equals(new String[] { "${sdk.dir}\\foo\\bar", "\\bar\\foo" }, Iterables.toArray(splitPath("${sdk.dir}\\foo\\bar;\\bar\\foo"), String.class))); assertTrue(Arrays.equals(new String[] { "${sdk.dir}/foo/bar", "/bar/foo" }, Iterables.toArray(splitPath("${sdk.dir}/foo/bar:/bar/foo"), String.class))); assertTrue(Arrays.equals(new String[] { "C:\\foo", "/bar" }, Iterables.toArray(splitPath("C:\\foo:/bar"), String.class))); } public void testCommonParen1() { assertEquals(new File("/a"), (LintUtils.getCommonParent( new File("/a/b/c/d/e"), new File("/a/c")))); assertEquals(new File("/a"), (LintUtils.getCommonParent( new File("/a/c"), new File("/a/b/c/d/e")))); assertEquals(new File("/"), LintUtils.getCommonParent( new File("/foo/bar"), new File("/bar/baz"))); assertEquals(new File("/"), LintUtils.getCommonParent( new File("/foo/bar"), new File("/"))); assertNull(LintUtils.getCommonParent( new File("C:\\Program Files"), new File("F:\\"))); assertNull(LintUtils.getCommonParent( new File("C:/Program Files"), new File("F:/"))); assertEquals(new File("/foo/bar/baz"), LintUtils.getCommonParent( new File("/foo/bar/baz"), new File("/foo/bar/baz"))); assertEquals(new File("/foo/bar"), LintUtils.getCommonParent( new File("/foo/bar/baz"), new File("/foo/bar"))); assertEquals(new File("/foo/bar"), LintUtils.getCommonParent( new File("/foo/bar/baz"), new File("/foo/bar/foo"))); assertEquals(new File("/foo"), LintUtils.getCommonParent( new File("/foo/bar"), new File("/foo/baz"))); assertEquals(new File("/foo"), LintUtils.getCommonParent( new File("/foo/bar"), new File("/foo/baz"))); assertEquals(new File("/foo/bar"), LintUtils.getCommonParent( new File("/foo/bar"), new File("/foo/bar/baz"))); } public void testCommonParent2() { assertEquals(new File("/"), LintUtils.getCommonParent( Arrays.asList(new File("/foo/bar"), new File("/bar/baz")))); assertEquals(new File("/"), LintUtils.getCommonParent( Arrays.asList(new File("/foo/bar"), new File("/")))); assertNull(LintUtils.getCommonParent( Arrays.asList(new File("C:\\Program Files"), new File("F:\\")))); assertNull(LintUtils.getCommonParent( Arrays.asList(new File("C:/Program Files"), new File("F:/")))); assertEquals(new File("/foo"), LintUtils.getCommonParent( Arrays.asList(new File("/foo/bar"), new File("/foo/baz")))); assertEquals(new File("/foo"), LintUtils.getCommonParent( Arrays.asList(new File("/foo/bar"), new File("/foo/baz"), new File("/foo/baz/f")))); assertEquals(new File("/foo/bar"), LintUtils.getCommonParent( Arrays.asList(new File("/foo/bar"), new File("/foo/bar/baz"), new File("/foo/bar/foo2/foo3")))); } public void testStripIdPrefix() throws Exception { assertEquals("foo", LintUtils.stripIdPrefix("@+id/foo")); assertEquals("foo", LintUtils.stripIdPrefix("@id/foo")); assertEquals("foo", LintUtils.stripIdPrefix("foo")); } public void testIdReferencesMatch() throws Exception { assertTrue(LintUtils.idReferencesMatch("@+id/foo", "@+id/foo")); assertTrue(LintUtils.idReferencesMatch("@id/foo", "@id/foo")); assertTrue(LintUtils.idReferencesMatch("@id/foo", "@+id/foo")); assertTrue(LintUtils.idReferencesMatch("@+id/foo", "@id/foo")); assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@+id/bar")); assertFalse(LintUtils.idReferencesMatch("@id/foo", "@+id/bar")); assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@id/bar")); assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@+id/bar")); assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@+id/foo1")); assertFalse(LintUtils.idReferencesMatch("@id/foo", "@id/foo1")); assertFalse(LintUtils.idReferencesMatch("@id/foo", "@+id/foo1")); assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@id/foo1")); assertFalse(LintUtils.idReferencesMatch("@+id/foo1", "@+id/foo")); assertFalse(LintUtils.idReferencesMatch("@id/foo1", "@id/foo")); assertFalse(LintUtils.idReferencesMatch("@id/foo1", "@+id/foo")); assertFalse(LintUtils.idReferencesMatch("@+id/foo1", "@id/foo")); } private static void checkEncoding(String encoding, boolean writeBom, String lineEnding) throws Exception { @SuppressWarnings("StringBufferReplaceableByString") StringBuilder sb = new StringBuilder(); // Norwegian extra vowel characters such as "latin small letter a with ring above" String value = "\u00e6\u00d8\u00e5"; String expected = "First line." + lineEnding + "Second line." + lineEnding + "Third line." + lineEnding + value + lineEnding; sb.append(expected); File file = File.createTempFile("getEncodingTest" + encoding + writeBom, ".txt"); file.deleteOnExit(); BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file)); OutputStreamWriter writer = new OutputStreamWriter(stream, encoding); if (writeBom) { String normalized = encoding.toLowerCase(Locale.US).replace("-", "_"); if (normalized.equals("utf_8")) { stream.write(0xef); stream.write(0xbb); stream.write(0xbf); } else if (normalized.equals("utf_16")) { stream.write(0xfe); stream.write(0xff); } else if (normalized.equals("utf_16le")) { stream.write(0xff); stream.write(0xfe); } else if (normalized.equals("utf_32")) { stream.write(0x0); stream.write(0x0); stream.write(0xfe); stream.write(0xff); } else if (normalized.equals("utf_32le")) { stream.write(0xff); stream.write(0xfe); stream.write(0x0); stream.write(0x0); } else { fail("Can't write BOM for encoding " + encoding); } } writer.write(sb.toString()); writer.close(); String s = LintUtils.getEncodedString(new LintCliClient(), file); assertEquals(expected, s); } public void testGetEncodedString() throws Exception { checkEncoding("utf-8", false /*bom*/, "\n"); checkEncoding("UTF-8", false /*bom*/, "\n"); checkEncoding("UTF_16", false /*bom*/, "\n"); checkEncoding("UTF-16", false /*bom*/, "\n"); checkEncoding("UTF_16LE", false /*bom*/, "\n"); // Try BOM's checkEncoding("utf-8", true /*bom*/, "\n"); checkEncoding("UTF-8", true /*bom*/, "\n"); checkEncoding("UTF_16", true /*bom*/, "\n"); checkEncoding("UTF-16", true /*bom*/, "\n"); checkEncoding("UTF_16LE", true /*bom*/, "\n"); checkEncoding("UTF_32", true /*bom*/, "\n"); checkEncoding("UTF_32LE", true /*bom*/, "\n"); // Make sure this works for \r and \r\n as well checkEncoding("UTF-16", false /*bom*/, "\r"); checkEncoding("UTF_16LE", false /*bom*/, "\r"); checkEncoding("UTF-16", false /*bom*/, "\r\n"); checkEncoding("UTF_16LE", false /*bom*/, "\r\n"); checkEncoding("UTF-16", true /*bom*/, "\r"); checkEncoding("UTF_16LE", true /*bom*/, "\r"); checkEncoding("UTF_32", true /*bom*/, "\r"); checkEncoding("UTF_32LE", true /*bom*/, "\r"); checkEncoding("UTF-16", true /*bom*/, "\r\n"); checkEncoding("UTF_16LE", true /*bom*/, "\r\n"); checkEncoding("UTF_32", true /*bom*/, "\r\n"); checkEncoding("UTF_32LE", true /*bom*/, "\r\n"); } public void testGetLocaleAndRegion() throws Exception { assertNull(getLocaleAndRegion("")); assertNull(getLocaleAndRegion("values")); assertNull(getLocaleAndRegion("values-xlarge-port")); assertEquals("en", getLocaleAndRegion("values-en")); assertEquals("pt-rPT", getLocaleAndRegion("values-pt-rPT-nokeys")); assertEquals("b+pt+PT", getLocaleAndRegion("values-b+pt+PT-nokeys")); assertEquals("zh-rCN", getLocaleAndRegion("values-zh-rCN-keyshidden")); assertEquals("ms", getLocaleAndRegion("values-ms-keyshidden")); } public void testIsImported() throws Exception { assertFalse(isImported(getCompilationUnit( "package foo.bar;\n" + "class Foo {\n" + "}\n"), "android.app.Activity")); assertTrue(isImported(getCompilationUnit( "package foo.bar;\n" + "import foo.bar.*;\n" + "import android.app.Activity;\n" + "import foo.bar.Baz;\n" + "class Foo {\n" + "}\n"), "android.app.Activity")); assertTrue(isImported(getCompilationUnit( "package foo.bar;\n" + "import android.app.Activity;\n" + "class Foo {\n" + "}\n"), "android.app.Activity")); assertTrue(isImported(getCompilationUnit( "package foo.bar;\n" + "import android.app.*;\n" + "class Foo {\n" + "}\n"), "android.app.Activity")); assertFalse(isImported(getCompilationUnit( "package foo.bar;\n" + "import android.app.*;\n" + "import foo.bar.Activity;\n" + "class Foo {\n" + "}\n"), "android.app.Activity")); } public void testComputeResourceName() { assertEquals("", computeResourceName("", "")); assertEquals("foo", computeResourceName("", "foo")); assertEquals("foo", computeResourceName("foo", "")); assertEquals("prefix_name", computeResourceName("prefix_", "name")); assertEquals("prefixName", computeResourceName("prefix", "name")); } public static Node getCompilationUnit(@Language("JAVA") String javaSource) { return getCompilationUnit(javaSource, new File("test")); } public static Node getCompilationUnit(@Language("JAVA") String javaSource, File relativePath) { JavaContext context = parse(javaSource, relativePath); return context.getCompilationUnit(); } public static JavaContext parse(@Language("JAVA") final String javaSource, final File relativePath) { File dir = new File("projectDir"); final File fullPath = new File(dir, relativePath.getPath()); LintCliClient client = new LintCliClient() { @NonNull @Override public String readFile(@NonNull File file) { if (file.getPath().equals(fullPath.getPath())) { return javaSource; } return super.readFile(file); } @Nullable @Override public IAndroidTarget getCompileTarget(@NonNull Project project) { IAndroidTarget[] targets = getTargets(); for (int i = targets.length - 1; i >= 0; i--) { IAndroidTarget target = targets[i]; if (target.isPlatform()) { return target; } } return super.getCompileTarget(project); } }; Project project = client.getProject(dir, dir); LintDriver driver = new LintDriver(new BuiltinIssueRegistry(), new LintCliClient()); driver.setScope(Scope.JAVA_FILE_SCOPE); TestContext context = new TestContext(driver, client, project, javaSource, fullPath); JavaParser parser = new EcjParser(client, project); parser.prepareJavaParse(Collections.<JavaContext>singletonList(context)); Node compilationUnit = parser.parseJava(context); assertNotNull(javaSource, compilationUnit); context.setCompilationUnit(compilationUnit); return context; } public void testConvertVersion() { assertEquals(new AndroidVersion(5, null), convertVersion(new DefaultApiVersion(5, null), null)); assertEquals(new AndroidVersion(19, null), convertVersion(new DefaultApiVersion(19, null), null)); //noinspection SpellCheckingInspection assertEquals(new AndroidVersion(18, "KITKAT"), // a preview platform API level is not final convertVersion(new DefaultApiVersion(0, "KITKAT"), null)); } public void testIsModelOlderThan() throws Exception { AndroidProject project = mock(AndroidProject.class); when(project.getModelVersion()).thenReturn("0.10.4"); assertTrue(LintUtils.isModelOlderThan(project, 0, 10, 5)); assertTrue(LintUtils.isModelOlderThan(project, 0, 11, 0)); assertTrue(LintUtils.isModelOlderThan(project, 0, 11, 4)); assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0)); project = mock(AndroidProject.class); when(project.getModelVersion()).thenReturn("0.11.0"); assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0)); assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0)); assertFalse(LintUtils.isModelOlderThan(project, 0, 10, 4)); project = mock(AndroidProject.class); when(project.getModelVersion()).thenReturn("0.11.5"); assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0)); assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0)); project = mock(AndroidProject.class); when(project.getModelVersion()).thenReturn("1.0.0"); assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 1)); assertFalse(LintUtils.isModelOlderThan(project, 1, 0, 0)); assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0)); } private static final class DefaultApiVersion implements ApiVersion { private final int mApiLevel; private final String mCodename; public DefaultApiVersion(int apiLevel, @Nullable String codename) { mApiLevel = apiLevel; mCodename = codename; } @Override public int getApiLevel() { return mApiLevel; } @Nullable @Override public String getCodename() { return mCodename; } @NonNull @Override public String getApiString() { fail("Not needed in this test"); return "<invalid>"; } } public void testFindSubstring() { assertEquals("foo", findSubstring("foo", null, null)); assertEquals("foo", findSubstring("foo ", null, " ")); assertEquals("foo", findSubstring(" foo", " ", null)); assertEquals("foo", findSubstring("[foo]", "[", "]")); } public void testGetFormattedParameters() { assertEquals(Arrays.asList("foo","bar"), getFormattedParameters("Prefix %1$s Divider %2$s Suffix", "Prefix foo Divider bar Suffix")); } public void testEscapePropertyValue() throws Exception { assertEquals("foo", escapePropertyValue("foo")); assertEquals("\\ foo ", escapePropertyValue(" foo ")); assertEquals("c\\:/foo/bar", escapePropertyValue("c:/foo/bar")); assertEquals("\\!\\#\\:\\\\a\\\\b\\\\c", escapePropertyValue("!#:\\a\\b\\c")); assertEquals( "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo\\#foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo", escapePropertyValue("foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo#foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo")); } private static class TestContext extends JavaContext { private final String mJavaSource; public TestContext(LintDriver driver, LintCliClient client, Project project, String javaSource, File file) { //noinspection ConstantConditions super(driver, project, null, file, client.getJavaParser(null)); mJavaSource = javaSource; } @Override @Nullable public String getContents() { return mJavaSource; } } }