/*
* Copyright (C) 2012 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.checks;
import static com.android.SdkConstants.FN_PUBLIC_TXT;
import static com.android.SdkConstants.FN_RESOURCE_TEXT;
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.AndroidArtifact;
import com.android.builder.model.AndroidLibrary;
import com.android.builder.model.AndroidProject;
import com.android.builder.model.Dependencies;
import com.android.builder.model.Variant;
import com.android.testutils.TestUtils;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Project;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import org.mockito.stubbing.OngoingStubbing;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
@SuppressWarnings("javadoc")
public class PrivateResourceDetectorTest extends AbstractCheckTest {
@Override
protected Detector getDetector() {
return new PrivateResourceDetector();
}
public void testPrivateInXml() throws Exception {
assertEquals(""
+ "res/layout/private.xml:11: Warning: The resource @string/my_private_string is marked as private in the library [PrivateResource]\n"
+ " android:text=\"@string/my_private_string\" />\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "0 errors, 1 warnings\n",
lintProject(xml("res/layout/private.xml", ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " android:id=\"@+id/newlinear\"\n"
+ " android:orientation=\"vertical\"\n"
+ " android:layout_width=\"match_parent\"\n"
+ " android:layout_height=\"match_parent\">\n"
+ "\n"
+ " <TextView\n"
+ " android:layout_width=\"wrap_content\"\n"
+ " android:layout_height=\"wrap_content\"\n"
+ " android:text=\"@string/my_private_string\" />\n"
+ "\n"
+ " <TextView\n"
+ " android:layout_width=\"wrap_content\"\n"
+ " android:layout_height=\"wrap_content\"\n"
+ " android:text=\"@string/my_public_string\" />\n"
+ "</LinearLayout>\n")));
}
public void testPrivateInJava() throws Exception {
assertEquals(""
+ ""
+ "src/test/pkg/Private.java:3: Warning: The resource @string/my_private_string is marked as private in the library [PrivateResource]\n"
+ " int x = R.string.my_private_string; // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "0 errors, 1 warnings\n",
lintProject(java("src/test/pkg/Private.java", ""
+ "public class PrivateResourceDetectorTest {\n"
+ " void test() {\n"
+ " int x = R.string.my_private_string; // ERROR\n"
+ " int y = R.string.my_public_string; // OK\n"
+ " int y = android.R.string.my_private_string; // OK (not in project namespace)\n"
+ " }\n"
+ "}\n")));
}
public void testOverride() throws Exception {
assertEquals(""
+ "res/layout/my_private_layout.xml: Warning: Overriding @layout/my_private_layout which is marked as private in the library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
+ "res/values/strings.xml:5: Warning: Overriding @string/my_private_string which is marked as private in the library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
+ " <string name=\"my_private_string\">String 1</string>\n"
+ " ~~~~~~~~~~~~~~~~~\n"
+ "res/values/strings.xml:9: Warning: Overriding @string/my_private_string which is marked as private in the library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
+ " <item type=\"string\" name=\"my_private_string\">String 1</item>\n"
+ " ~~~~~~~~~~~~~~~~~\n"
+ "res/values/strings.xml:12: Warning: Overriding @string/my_private_string which is marked as private in the library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
+ " <string tools:override=\"false\" name=\"my_private_string\">String 2</string>\n"
+ " ~~~~~~~~~~~~~~~~~\n"
+ "0 errors, 4 warnings\n",
lintProject(xml("res/values/strings.xml", ""
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ "\n"
+ " <string name=\"app_name\">LibraryProject</string>\n"
+ " <string name=\"my_private_string\">String 1</string>\n"
+ " <string name=\"my_public_string\">String 2</string>\n"
+ " <string name=\"string3\"> @my_private_string </string>\n"
+ " <string name=\"string4\"> @my_public_string </string>\n"
+ " <item type=\"string\" name=\"my_private_string\">String 1</item>\n"
+ " <dimen name=\"my_private_string\">String 1</dimen>\n" // unrelated
+ " <string tools:ignore=\"PrivateResource\" name=\"my_private_string\">String 2</string>\n"
+ " <string tools:override=\"false\" name=\"my_private_string\">String 2</string>\n"
+ " <string tools:override=\"true\" name=\"my_private_string\">String 2</string>\n"
+ "\n"
+ "</resources>\n"),
xml("res/layout/my_private_layout.xml", "<LinearLayout/>"),
xml("res/layout/my_public_layout.xml", "<LinearLayout/>")));
}
@Override
protected TestLintClient createClient() {
return new TestLintClient() {
@NonNull
@Override
protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
return new Project(this, dir, referenceDir) {
@Override
public boolean isGradleProject() {
return true;
}
@Nullable
@Override
public AndroidProject getGradleProjectModel() {
// First version which supported private resources; this does not
// need to track later versions we release
return createMockProject("1.3.0-alpha2", 3);
}
@Nullable
@Override
public Variant getCurrentVariant() {
try {
AndroidLibrary library = createMockLibrary(
""
+ "int string my_private_string 0x7f040000\n"
+ "int string my_public_string 0x7f040001\n"
+ "int layout my_private_layout 0x7f040002\n",
""
+ ""
+ "string my_public_string\n",
Collections.<AndroidLibrary>emptyList()
);
AndroidArtifact artifact = createMockArtifact(
Collections.singletonList(library));
Variant variant = mock(Variant.class);
when(variant.getMainArtifact()).thenReturn(artifact);
return variant;
} catch (Exception e) {
fail(e.toString());
return null;
}
}
};
}
};
}
public static AndroidProject createMockProject(String modelVersion, int apiVersion) {
AndroidProject project = mock(AndroidProject.class);
when(project.getApiVersion()).thenReturn(apiVersion);
when(project.getModelVersion()).thenReturn(modelVersion);
return project;
}
public static AndroidArtifact createMockArtifact(List<AndroidLibrary> libraries) {
Dependencies dependencies = mock(Dependencies.class);
when(dependencies.getLibraries()).thenReturn(libraries);
AndroidArtifact artifact = mock(AndroidArtifact.class);
when(artifact.getDependencies()).thenReturn(dependencies);
return artifact;
}
public static AndroidLibrary createMockLibrary(String allResources, String publicResources,
List<AndroidLibrary> dependencies)
throws IOException {
final File tempDir = TestUtils.createTempDirDeletedOnExit();
Files.write(allResources, new File(tempDir, FN_RESOURCE_TEXT), Charsets.UTF_8);
File publicTxtFile = new File(tempDir, FN_PUBLIC_TXT);
if (publicResources != null) {
Files.write(publicResources, publicTxtFile, Charsets.UTF_8);
}
AndroidLibrary library = mock(AndroidLibrary.class);
when(library.getPublicResources()).thenReturn(publicTxtFile);
// Work around wildcard capture
//when(mock.getLibraryDependencies()).thenReturn(dependencies);
List libraryDependencies = library.getLibraryDependencies();
OngoingStubbing<List> setter = when(libraryDependencies);
setter.thenReturn(dependencies);
return library;
}
}