OSDN Git Service

Refactor tests structure
authorDiego Perez <diegoperez@google.com>
Tue, 13 Dec 2016 18:10:03 +0000 (18:10 +0000)
committerDiego Perez <diegoperez@google.com>
Mon, 16 Jan 2017 18:13:44 +0000 (18:13 +0000)
Move all the render test utility methods to a separate class and leave
only tests in RenderTests class (old Main).
The idea is to improve the organization to allow to create a set of
performance tests and also to allow the tests to grow more structured.

Test: Just moved tests around
Change-Id: I3b773d0745700dd2a52f937b9b668a2d374b8686

tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java [deleted file]
tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java [new file with mode: 0644]
tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java [new file with mode: 0644]
tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java [moved from tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java with 99% similarity]
tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ModuleClassLoader.java [moved from tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java with 97% similarity]

diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
deleted file mode 100644 (file)
index d0c04d7..0000000
+++ /dev/null
@@ -1,898 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.intensive;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderSession;
-import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.SessionParams;
-import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
-import com.android.ide.common.rendering.api.ViewInfo;
-import com.android.ide.common.resources.FrameworkResources;
-import com.android.ide.common.resources.ResourceItem;
-import com.android.ide.common.resources.ResourceRepository;
-import com.android.ide.common.resources.ResourceResolver;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.io.FolderWrapper;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.impl.RenderAction;
-import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
-import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
-import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
-import com.android.resources.Density;
-import com.android.resources.Navigation;
-import com.android.resources.ResourceType;
-import com.android.resources.ScreenOrientation;
-import com.android.tools.layoutlib.java.System_Delegate;
-import com.android.utils.ILogger;
-
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.res.AssetManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.util.DisplayMetrics;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Field;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
-
-import com.google.android.collect.Lists;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-/**
- * This is a set of tests that loads all the framework resources and a project checked in this
- * test's resources. The main dependencies
- * are:
- * 1. Fonts directory.
- * 2. Framework Resources.
- * 3. App resources.
- * 4. build.prop file
- *
- * These are configured by two variables set in the system properties.
- *
- * 1. platform.dir: This is the directory for the current platform in the built SDK
- *     (.../sdk/platforms/android-<version>).
- *
- *     The fonts are platform.dir/data/fonts.
- *     The Framework resources are platform.dir/data/res.
- *     build.prop is at platform.dir/build.prop.
- *
- * 2. test_res.dir: This is the directory for the resources of the test. If not specified, this
- *     falls back to getClass().getProtectionDomain().getCodeSource().getLocation()
- *
- *     The app resources are at: test_res.dir/testApp/MyApplication/app/src/main/res
- */
-public class Main {
-
-    private static final String PLATFORM_DIR_PROPERTY = "platform.dir";
-    private static final String RESOURCE_DIR_PROPERTY = "test_res.dir";
-
-    private static final String PLATFORM_DIR;
-    private static final String TEST_RES_DIR;
-    /** Location of the app to test inside {@link #TEST_RES_DIR}*/
-    private static final String APP_TEST_DIR = "testApp/MyApplication";
-    /** Location of the app's res dir inside {@link #TEST_RES_DIR}*/
-    private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
-    private static final String APP_CLASSES_LOCATION =
-            APP_TEST_DIR + "/build/intermediates/classes/debug/";
-
-    private static LayoutLog sLayoutLibLog;
-    private static FrameworkResources sFrameworkRepo;
-    private static ResourceRepository sProjectResources;
-    private static  ILogger sLogger;
-    private static Bridge sBridge;
-
-    /** List of log messages generated by a render call. It can be used to find specific errors */
-    private static ArrayList<String> sRenderMessages = Lists.newArrayList();
-
-    // Default class loader with access to the app classes
-    private ClassLoader mDefaultClassLoader =
-            new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
-
-    @Rule
-    public TestWatcher sRenderMessageWatcher = new TestWatcher() {
-        @Override
-        protected void succeeded(Description description) {
-            // We only check error messages if the rest of the test case was successful.
-            if (!sRenderMessages.isEmpty()) {
-                fail(description.getMethodName() + " render error message: " + sRenderMessages.get
-                        (0));
-            }
-        }
-    };
-
-    static {
-        // Test that System Properties are properly set.
-        PLATFORM_DIR = getPlatformDir();
-        if (PLATFORM_DIR == null) {
-            fail(String.format("System Property %1$s not properly set. The value is %2$s",
-                    PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY)));
-        }
-
-        TEST_RES_DIR = getTestResDir();
-        if (TEST_RES_DIR == null) {
-            fail(String.format("System property %1$s.dir not properly set. The value is %2$s",
-                    RESOURCE_DIR_PROPERTY, System.getProperty(RESOURCE_DIR_PROPERTY)));
-        }
-    }
-
-    private static String getPlatformDir() {
-        String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY);
-        if (platformDir != null && !platformDir.isEmpty() && new File(platformDir).isDirectory()) {
-            return platformDir;
-        }
-        // System Property not set. Try to find the directory in the build directory.
-        String androidHostOut = System.getenv("ANDROID_HOST_OUT");
-        if (androidHostOut != null) {
-            platformDir = getPlatformDirFromHostOut(new File(androidHostOut));
-            if (platformDir != null) {
-                return platformDir;
-            }
-        }
-        String workingDirString = System.getProperty("user.dir");
-        File workingDir = new File(workingDirString);
-        // Test if workingDir is android checkout root.
-        platformDir = getPlatformDirFromRoot(workingDir);
-        if (platformDir != null) {
-            return platformDir;
-        }
-
-        // Test if workingDir is platform/frameworks/base/tools/layoutlib/bridge.
-        File currentDir = workingDir;
-        if (currentDir.getName().equalsIgnoreCase("bridge")) {
-            currentDir = currentDir.getParentFile();
-        }
-        // Test if currentDir is  platform/frameworks/base/tools/layoutlib. That is, root should be
-        // workingDir/../../../../  (4 levels up)
-        for (int i = 0; i < 4; i++) {
-            if (currentDir != null) {
-                currentDir = currentDir.getParentFile();
-            }
-        }
-        return currentDir == null ? null : getPlatformDirFromRoot(currentDir);
-    }
-
-    private static String getPlatformDirFromRoot(File root) {
-        if (!root.isDirectory()) {
-            return null;
-        }
-        File out = new File(root, "out");
-        if (!out.isDirectory()) {
-            return null;
-        }
-        File host = new File(out, "host");
-        if (!host.isDirectory()) {
-            return null;
-        }
-        File[] hosts = host.listFiles(path -> path.isDirectory() &&
-                (path.getName().startsWith("linux-") || path.getName().startsWith("darwin-")));
-        assert hosts != null;
-        for (File hostOut : hosts) {
-            String platformDir = getPlatformDirFromHostOut(hostOut);
-            if (platformDir != null) {
-                return platformDir;
-            }
-        }
-        return null;
-    }
-
-    private static String getPlatformDirFromHostOut(File out) {
-        if (!out.isDirectory()) {
-            return null;
-        }
-        File sdkDir = new File(out, "sdk");
-        if (!sdkDir.isDirectory()) {
-            return null;
-        }
-        File[] sdkDirs = sdkDir.listFiles(path -> {
-            // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7)
-            return path.isDirectory() && path.getName().startsWith("sdk");
-        });
-        assert sdkDirs != null;
-        for (File dir : sdkDirs) {
-            String platformDir = getPlatformDirFromHostOutSdkSdk(dir);
-            if (platformDir != null) {
-                return platformDir;
-            }
-        }
-        return null;
-    }
-
-    private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) {
-        File[] possibleSdks = sdkDir.listFiles(
-                path -> path.isDirectory() && path.getName().contains("android-sdk"));
-        assert possibleSdks != null;
-        for (File possibleSdk : possibleSdks) {
-            File platformsDir = new File(possibleSdk, "platforms");
-            File[] platforms = platformsDir.listFiles(
-                    path -> path.isDirectory() && path.getName().startsWith("android-"));
-            if (platforms == null || platforms.length == 0) {
-                continue;
-            }
-            Arrays.sort(platforms, (o1, o2) -> {
-                final int MAX_VALUE = 1000;
-                String suffix1 = o1.getName().substring("android-".length());
-                String suffix2 = o2.getName().substring("android-".length());
-                int suff1, suff2;
-                try {
-                    suff1 = Integer.parseInt(suffix1);
-                } catch (NumberFormatException e) {
-                    suff1 = MAX_VALUE;
-                }
-                try {
-                    suff2 = Integer.parseInt(suffix2);
-                } catch (NumberFormatException e) {
-                    suff2 = MAX_VALUE;
-                }
-                if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) {
-                    return suff2 - suff1;
-                }
-                return suffix2.compareTo(suffix1);
-            });
-            return platforms[0].getAbsolutePath();
-        }
-        return null;
-    }
-
-    private static String getTestResDir() {
-        String resourceDir = System.getProperty(RESOURCE_DIR_PROPERTY);
-        if (resourceDir != null && !resourceDir.isEmpty() && new File(resourceDir).isDirectory()) {
-            return resourceDir;
-        }
-        // TEST_RES_DIR not explicitly set. Fallback to the class's source location.
-        try {
-            URL location = Main.class.getProtectionDomain().getCodeSource().getLocation();
-            return new File(location.getPath()).exists() ? location.getPath() : null;
-        } catch (NullPointerException e) {
-            // Prevent a lot of null checks by just catching the exception.
-            return null;
-        }
-    }
-
-    /**
-     * Initialize the bridge and the resource maps.
-     */
-    @BeforeClass
-    public static void setUp() {
-        File data_dir = new File(PLATFORM_DIR, "data");
-        File res = new File(data_dir, "res");
-        sFrameworkRepo = new FrameworkResources(new FolderWrapper(res));
-        sFrameworkRepo.loadResources();
-        sFrameworkRepo.loadPublicResources(getLogger());
-
-        sProjectResources =
-                new ResourceRepository(new FolderWrapper(TEST_RES_DIR + "/" + APP_TEST_RES),
-                        false) {
-            @NonNull
-            @Override
-            protected ResourceItem createResourceItem(@NonNull String name) {
-                return new ResourceItem(name);
-            }
-        };
-        sProjectResources.loadResources();
-
-        File fontLocation = new File(data_dir, "fonts");
-        File buildProp = new File(PLATFORM_DIR, "build.prop");
-        File attrs = new File(res, "values" + File.separator + "attrs.xml");
-        sBridge = new Bridge();
-        sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
-                ConfigGenerator.getEnumMap(attrs), getLayoutLog());
-        Bridge.getLock().lock();
-        try {
-            Bridge.setLog(getLayoutLog());
-        } finally {
-            Bridge.getLock().unlock();
-        }
-    }
-
-    @Before
-    public void beforeTestCase() {
-        sRenderMessages.clear();
-    }
-
-    /** Test activity.xml */
-    @Test
-    public void testActivity() throws ClassNotFoundException {
-        renderAndVerify("activity.xml", "activity.png");
-    }
-
-    @Test
-    public void testActivityOnOldTheme() throws ClassNotFoundException {
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.NoTitleBar", false,
-                RenderingMode.NORMAL, 22);
-
-        renderAndVerify(params, "simple_activity-old-theme.png");
-    }
-
-    @Test
-    public void testTranslucentBars() throws ClassNotFoundException {
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        LayoutPullParser parser = createLayoutPullParser("four_corners.xml");
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
-                RenderingMode.NORMAL, 22);
-        renderAndVerify(params, "four_corners_translucent.png");
-
-        parser = createLayoutPullParser("four_corners.xml");
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5_LAND,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
-                RenderingMode.NORMAL, 22);
-        renderAndVerify(params, "four_corners_translucent_land.png");
-
-        parser = createLayoutPullParser("four_corners.xml");
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
-                RenderingMode.NORMAL, 22);
-        renderAndVerify(params, "four_corners.png");
-    }
-
-    private static void gc() {
-        // See RuntimeUtil#gc in jlibs (http://jlibs.in/)
-        Object obj = new Object();
-        WeakReference ref = new WeakReference<>(obj);
-        //noinspection UnusedAssignment
-        obj = null;
-        while(ref.get() != null) {
-            System.gc();
-            System.runFinalization();
-        }
-
-        System.gc();
-        System.runFinalization();
-    }
-
-    /** Test allwidgets.xml */
-    @Test
-    public void testAllWidgets() throws ClassNotFoundException {
-        renderAndVerify("allwidgets.xml", "allwidgets.png");
-
-        // We expect fidelity warnings for Path.isConvex. Fail for anything else.
-        sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
-    }
-
-    @Test
-    public void testArrayCheck() throws ClassNotFoundException {
-        renderAndVerify("array_check.xml", "array_check.png");
-    }
-
-    @Test
-    public void testAllWidgetsTablet() throws ClassNotFoundException {
-        renderAndVerify("allwidgets.xml", "allwidgets_tab.png", ConfigGenerator.NEXUS_7_2012);
-
-        // We expect fidelity warnings for Path.isConvex. Fail for anything else.
-        sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
-    }
-
-    @Test
-    public void testActivityActionBar() throws ClassNotFoundException {
-        LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
-                RenderingMode.V_SCROLL, 22);
-
-        renderAndVerify(params, "simple_activity_noactionbar.png");
-
-        parser = createLayoutPullParser("simple_activity.xml");
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light", false,
-                RenderingMode.V_SCROLL, 22);
-
-        renderAndVerify(params, "simple_activity.png");
-
-        // This also tests that a theme with "NoActionBar" DOES HAVE an action bar when we are
-        // displaying menus.
-        parser = createLayoutPullParser("simple_activity.xml");
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
-                RenderingMode.V_SCROLL, 22);
-        params.setFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG, "menu");
-        renderAndVerify(params, "simple_activity.png");
-    }
-
-    @Test
-    public void testOnApplyInsetsCall()
-            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
-        // We get the widget via reflection to avoid IntelliJ complaining about the class being
-        // located in the wrong package. (From the Bridge tests point of view, it is)
-        Class insetsWidgetClass = Class.forName("com.android.layoutlib.test.myapplication.widgets" +
-                ".InsetsWidget");
-        Field field = insetsWidgetClass.getDeclaredField("sApplyInsetsCalled");
-        assertFalse((Boolean)field.get(null));
-
-        LayoutPullParser parser = createLayoutPullParser("insets.xml");
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
-                RenderingMode.NORMAL, 22);
-
-        render(params, -1);
-
-        assertTrue((Boolean)field.get(null));
-        field.set(null, false);
-    }
-
-    @AfterClass
-    public static void tearDown() {
-        sLayoutLibLog = null;
-        sFrameworkRepo = null;
-        sProjectResources = null;
-        sLogger = null;
-        sBridge = null;
-
-        gc();
-
-        System.out.println("Objects still linked from the DelegateManager:");
-        DelegateManager.dump(System.out);
-    }
-
-    /** Test expand_layout.xml */
-    @Test
-    public void testExpand() throws ClassNotFoundException {
-        // Create the layout pull parser.
-        LayoutPullParser parser = createLayoutPullParser("expand_vert_layout.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        ConfigGenerator customConfigGenerator = new ConfigGenerator()
-                .setScreenWidth(300)
-                .setScreenHeight(20)
-                .setDensity(Density.XHIGH)
-                .setNavigation(Navigation.NONAV);
-
-        SessionParams params = getSessionParams(parser, customConfigGenerator,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-
-        renderAndVerify(params, "expand_vert_layout.png");
-
-        customConfigGenerator = new ConfigGenerator()
-                .setScreenWidth(20)
-                .setScreenHeight(300)
-                .setDensity(Density.XHIGH)
-                .setNavigation(Navigation.NONAV);
-        parser = createLayoutPullParser("expand_horz_layout.xml");
-        params = getSessionParams(parser, customConfigGenerator,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
-                RenderingMode.H_SCROLL, 22);
-
-        renderAndVerify(params, "expand_horz_layout.png");
-    }
-
-    /** Test indeterminate_progressbar.xml */
-    @Test
-    public void testVectorAnimation() throws ClassNotFoundException {
-        // Create the layout pull parser.
-        LayoutPullParser parser = createLayoutPullParser("indeterminate_progressbar.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-
-        renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
-
-        parser = createLayoutPullParser("indeterminate_progressbar.xml");
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-        renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3));
-    }
-
-    /**
-     * Test a vector drawable that uses trimStart and trimEnd. It also tests all the primitives
-     * for vector drawables (lines, moves and cubic and quadratic curves).
-     */
-    @Test
-    public void testVectorDrawable() throws ClassNotFoundException {
-        // Create the layout pull parser.
-        LayoutPullParser parser = createLayoutPullParser("vector_drawable.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-
-        renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2));
-    }
-
-    /**
-     * Regression test for http://b.android.com/91383 and http://b.android.com/203797
-     */
-    @Test
-    public void testVectorDrawable91383() throws ClassNotFoundException {
-        // Create the layout pull parser.
-        LayoutPullParser parser = createLayoutPullParser("vector_drawable_android.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-
-        renderAndVerify(params, "vector_drawable_91383.png", TimeUnit.SECONDS.toNanos(2));
-    }
-
-    /** Test activity.xml */
-    @Test
-    public void testScrollingAndMeasure() throws ClassNotFoundException {
-        // Create the layout pull parser.
-        LayoutPullParser parser = createLayoutPullParser("scrolled.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-        params.setForceNoDecor();
-        params.setExtendedViewInfoMode(true);
-
-        // Do an only-measure pass
-        RenderSession session = sBridge.createSession(params);
-        session.measure();
-        RenderResult result = RenderResult.getFromSession(session);
-        assertNotNull(result);
-        assertNotNull(result.getResult());
-        assertTrue(result.getResult().isSuccess());
-
-        ViewInfo rootLayout = result.getRootViews().get(0);
-        // Check the first box in the main LinearLayout
-        assertEquals(-90, rootLayout.getChildren().get(0).getTop());
-        assertEquals(-30, rootLayout.getChildren().get(0).getLeft());
-        assertEquals(90, rootLayout.getChildren().get(0).getBottom());
-        assertEquals(150, rootLayout.getChildren().get(0).getRight());
-
-        // Check the first box within the nested LinearLayout
-        assertEquals(-450, rootLayout.getChildren().get(5).getChildren().get(0).getTop());
-        assertEquals(90, rootLayout.getChildren().get(5).getChildren().get(0).getLeft());
-        assertEquals(-270, rootLayout.getChildren().get(5).getChildren().get(0).getBottom());
-        assertEquals(690, rootLayout.getChildren().get(5).getChildren().get(0).getRight());
-
-        // Do a full render pass
-        parser = createLayoutPullParser("scrolled.xml");
-
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-        params.setForceNoDecor();
-        params.setExtendedViewInfoMode(true);
-
-        result = renderAndVerify(params, "scrolled.png");
-        assertNotNull(result);
-        assertNotNull(result.getResult());
-        assertTrue(result.getResult().isSuccess());
-    }
-
-    @Test
-    public void testGetResourceNameVariants() throws Exception {
-        // Setup
-        // Create the layout pull parser for our resources (empty.xml can not be part of the test
-        // app as it won't compile).
-        LayoutPullParser parser = new LayoutPullParser("/empty.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
-                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
-        AssetManager assetManager = AssetManager.getSystem();
-        DisplayMetrics metrics = new DisplayMetrics();
-        Configuration configuration = RenderAction.getConfiguration(params);
-        Resources resources = new Resources(assetManager, metrics, configuration);
-        resources.mLayoutlibCallback = params.getLayoutlibCallback();
-        resources.mContext =
-                new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
-                        params.getAssets(), params.getLayoutlibCallback(), configuration,
-                        params.getTargetSdkVersion(), params.isRtlSupported());
-        // Test
-        assertEquals("android:style/ButtonBar",
-                resources.getResourceName(android.R.style.ButtonBar));
-        assertEquals("android", resources.getResourcePackageName(android.R.style.ButtonBar));
-        assertEquals("ButtonBar", resources.getResourceEntryName(android.R.style.ButtonBar));
-        assertEquals("style", resources.getResourceTypeName(android.R.style.ButtonBar));
-        int id = resources.mLayoutlibCallback.getResourceId(ResourceType.STRING, "app_name");
-        assertEquals("com.android.layoutlib.test.myapplication:string/app_name",
-                resources.getResourceName(id));
-        assertEquals("com.android.layoutlib.test.myapplication",
-                resources.getResourcePackageName(id));
-        assertEquals("string", resources.getResourceTypeName(id));
-        assertEquals("app_name", resources.getResourceEntryName(id));
-    }
-
-    @Test
-    public void testStringEscaping() throws Exception {
-        // Setup
-        // Create the layout pull parser for our resources (empty.xml can not be part of the test
-        // app as it won't compile).
-        LayoutPullParser parser = new LayoutPullParser("/empty.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
-                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
-        AssetManager assetManager = AssetManager.getSystem();
-        DisplayMetrics metrics = new DisplayMetrics();
-        Configuration configuration = RenderAction.getConfiguration(params);
-        Resources resources = new Resources(assetManager, metrics, configuration);
-        resources.mLayoutlibCallback = params.getLayoutlibCallback();
-        resources.mContext =
-                new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
-                        params.getAssets(), params.getLayoutlibCallback(), configuration,
-                        params.getTargetSdkVersion(), params.isRtlSupported());
-
-        int id = resources.mLayoutlibCallback.getResourceId(ResourceType.ARRAY, "string_array");
-        String[] strings = resources.getStringArray(id);
-        assertArrayEquals(
-                new String[]{"mystring", "Hello world!", "candidates", "Unknown", "?EC"},
-                strings);
-        assertTrue(sRenderMessages.isEmpty());
-    }
-
-    @NonNull
-    private LayoutPullParser createLayoutPullParser(String layoutPath) {
-        return new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutPath);
-    }
-
-    @NonNull
-    private static RenderResult render(SessionParams params, long frameTimeNanos) {
-        // TODO: Set up action bar handler properly to test menu rendering.
-        // Create session params.
-        System_Delegate.setBootTimeNanos(TimeUnit.MILLISECONDS.toNanos(871732800000L));
-        System_Delegate.setNanosTime(TimeUnit.MILLISECONDS.toNanos(871732800000L));
-        RenderSession session = sBridge.createSession(params);
-
-        try {
-
-            if (frameTimeNanos != -1) {
-                session.setElapsedFrameTimeNanos(frameTimeNanos);
-            }
-
-            if (!session.getResult().isSuccess()) {
-                getLogger().error(session.getResult().getException(),
-                        session.getResult().getErrorMessage());
-            }
-            // Render the session with a timeout of 50s.
-            Result renderResult = session.render(50000);
-            if (!renderResult.isSuccess()) {
-                getLogger().error(session.getResult().getException(),
-                        session.getResult().getErrorMessage());
-            }
-
-            return RenderResult.getFromSession(session);
-        } finally {
-            session.dispose();
-        }
-    }
-
-    /**
-     * Create a new rendering session and test that rendering the given layout doesn't throw any
-     * exceptions and matches the provided image.
-     * <p>
-     * If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates
-     * how far in the future is.
-     */
-    @Nullable
-    private static RenderResult renderAndVerify(SessionParams params, String goldenFileName, long
-            frameTimeNanos)
-            throws ClassNotFoundException {
-        RenderResult result = Main.render(params, frameTimeNanos);
-        try {
-            String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
-            assertNotNull(result.getImage());
-            ImageUtils.requireSimilar(goldenImagePath, result.getImage());
-        } catch (IOException e) {
-            getLogger().error(e, e.getMessage());
-        }
-
-        return result;
-    }
-
-    /**
-     * Create a new rendering session and test that rendering the given layout doesn't throw any
-     * exceptions and matches the provided image.
-     */
-    @Nullable
-    private static RenderResult renderAndVerify(SessionParams params, String goldenFileName)
-            throws ClassNotFoundException {
-        return Main.renderAndVerify(params, goldenFileName, -1);
-    }
-
-    /**
-     * Create a new rendering session and test that rendering the given layout on nexus 5
-     * doesn't throw any exceptions and matches the provided image.
-     */
-    @Nullable
-    private RenderResult renderAndVerify(String layoutFileName, String goldenFileName)
-            throws ClassNotFoundException {
-        return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5);
-    }
-
-    /**
-     * Create a new rendering session and test that rendering the given layout on given device
-     * doesn't throw any exceptions and matches the provided image.
-     */
-    @Nullable
-    private RenderResult renderAndVerify(String layoutFileName, String goldenFileName,
-            ConfigGenerator deviceConfig)
-            throws ClassNotFoundException {
-        SessionParams params = createSessionParams(layoutFileName, deviceConfig);
-        return renderAndVerify(params, goldenFileName);
-    }
-
-    private SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig)
-            throws ClassNotFoundException {
-        // Create the layout pull parser.
-        LayoutPullParser parser = createLayoutPullParser(layoutFileName);
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-        // TODO: Set up action bar handler properly to test menu rendering.
-        // Create session params.
-        return getSessionParams(parser, deviceConfig,
-                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
-    }
-
-    /**
-     * Uses Theme.Material and Target sdk version as 22.
-     */
-    private SessionParams getSessionParams(LayoutPullParser layoutParser,
-            ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback,
-            String themeName, boolean isProjectTheme, RenderingMode renderingMode, int targetSdk) {
-        FolderConfiguration config = configGenerator.getFolderConfig();
-        ResourceResolver resourceResolver =
-                ResourceResolver.create(sProjectResources.getConfiguredResources(config),
-                        sFrameworkRepo.getConfiguredResources(config),
-                        themeName, isProjectTheme);
-
-        SessionParams sessionParams = new SessionParams(
-                layoutParser,
-                renderingMode,
-                null /*used for caching*/,
-                configGenerator.getHardwareConfig(),
-                resourceResolver,
-                layoutLibCallback,
-                0,
-                targetSdk,
-                getLayoutLog());
-        sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true);
-        return sessionParams;
-    }
-
-    private static LayoutLog getLayoutLog() {
-        if (sLayoutLibLog == null) {
-            sLayoutLibLog = new LayoutLog() {
-                @Override
-                public void warning(String tag, String message, Object data) {
-                    System.out.println("Warning " + tag + ": " + message);
-                    failWithMsg(message);
-                }
-
-                @Override
-                public void fidelityWarning(@Nullable String tag, String message,
-                        Throwable throwable, Object data) {
-
-                    System.out.println("FidelityWarning " + tag + ": " + message);
-                    if (throwable != null) {
-                        throwable.printStackTrace();
-                    }
-                    failWithMsg(message == null ? "" : message);
-                }
-
-                @Override
-                public void error(String tag, String message, Object data) {
-                    System.out.println("Error " + tag + ": " + message);
-                    failWithMsg(message);
-                }
-
-                @Override
-                public void error(String tag, String message, Throwable throwable, Object data) {
-                    System.out.println("Error " + tag + ": " + message);
-                    if (throwable != null) {
-                        throwable.printStackTrace();
-                    }
-                    failWithMsg(message);
-                }
-            };
-        }
-        return sLayoutLibLog;
-    }
-
-    private static ILogger getLogger() {
-        if (sLogger == null) {
-            sLogger = new ILogger() {
-                @Override
-                public void error(Throwable t, @Nullable String msgFormat, Object... args) {
-                    if (t != null) {
-                        t.printStackTrace();
-                    }
-                    failWithMsg(msgFormat == null ? "" : msgFormat, args);
-                }
-
-                @Override
-                public void warning(@NonNull String msgFormat, Object... args) {
-                    failWithMsg(msgFormat, args);
-                }
-
-                @Override
-                public void info(@NonNull String msgFormat, Object... args) {
-                    // pass.
-                }
-
-                @Override
-                public void verbose(@NonNull String msgFormat, Object... args) {
-                    // pass.
-                }
-            };
-        }
-        return sLogger;
-    }
-
-    private static void failWithMsg(@NonNull String msgFormat, Object... args) {
-        sRenderMessages.add(args == null ? msgFormat : String.format(msgFormat, args));
-    }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
new file mode 100644 (file)
index 0000000..f622c21
--- /dev/null
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) 2016 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.layoutlib.bridge.intensive;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.ide.common.resources.FrameworkResources;
+import com.android.ide.common.resources.ResourceItem;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.ResourceResolver;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.io.FolderWrapper;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
+import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
+import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
+import com.android.layoutlib.bridge.intensive.util.ImageUtils;
+import com.android.layoutlib.bridge.intensive.util.ModuleClassLoader;
+import com.android.tools.layoutlib.java.System_Delegate;
+import com.android.utils.ILogger;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import com.google.android.collect.Lists;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+/**
+ * Base class for render tests. The render tests load all the framework resources and a project
+ * checked in this test's resources. The main dependencies
+ * are:
+ * 1. Fonts directory.
+ * 2. Framework Resources.
+ * 3. App resources.
+ * 4. build.prop file
+ * <p>
+ * These are configured by two variables set in the system properties.
+ * <p>
+ * 1. platform.dir: This is the directory for the current platform in the built SDK
+ * (.../sdk/platforms/android-<version>).
+ * <p>
+ * The fonts are platform.dir/data/fonts.
+ * The Framework resources are platform.dir/data/res.
+ * build.prop is at platform.dir/build.prop.
+ * <p>
+ * 2. test_res.dir: This is the directory for the resources of the test. If not specified, this
+ * falls back to getClass().getProtectionDomain().getCodeSource().getLocation()
+ * <p>
+ * The app resources are at: test_res.dir/testApp/MyApplication/app/src/main/res
+ */
+public class RenderTestBase {
+
+    private static final String PLATFORM_DIR_PROPERTY = "platform.dir";
+    private static final String RESOURCE_DIR_PROPERTY = "test_res.dir";
+
+    private static final String PLATFORM_DIR;
+    private static final String TEST_RES_DIR;
+    /** Location of the app to test inside {@link #TEST_RES_DIR} */
+    private static final String APP_TEST_DIR = "testApp/MyApplication";
+    /** Location of the app's res dir inside {@link #TEST_RES_DIR} */
+    private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
+    private static final String APP_CLASSES_LOCATION =
+            APP_TEST_DIR + "/build/intermediates/classes/debug/";
+    protected static Bridge sBridge;
+    /** List of log messages generated by a render call. It can be used to find specific errors */
+    protected static ArrayList<String> sRenderMessages = Lists.newArrayList();
+    private static LayoutLog sLayoutLibLog;
+    private static FrameworkResources sFrameworkRepo;
+    private static ResourceRepository sProjectResources;
+    private static ILogger sLogger;
+
+    static {
+        // Test that System Properties are properly set.
+        PLATFORM_DIR = getPlatformDir();
+        if (PLATFORM_DIR == null) {
+            fail(String.format("System Property %1$s not properly set. The value is %2$s",
+                    PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY)));
+        }
+
+        TEST_RES_DIR = getTestResDir();
+        if (TEST_RES_DIR == null) {
+            fail(String.format("System property %1$s.dir not properly set. The value is %2$s",
+                    RESOURCE_DIR_PROPERTY, System.getProperty(RESOURCE_DIR_PROPERTY)));
+        }
+    }
+
+    @Rule
+    public TestWatcher sRenderMessageWatcher = new TestWatcher() {
+        @Override
+        protected void succeeded(Description description) {
+            // We only check error messages if the rest of the test case was successful.
+            if (!sRenderMessages.isEmpty()) {
+                fail(description.getMethodName() + " render error message: " +
+                        sRenderMessages.get(0));
+            }
+        }
+    };
+    // Default class loader with access to the app classes
+    protected ClassLoader mDefaultClassLoader =
+            new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
+
+    private static String getPlatformDir() {
+        String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY);
+        if (platformDir != null && !platformDir.isEmpty() && new File(platformDir).isDirectory()) {
+            return platformDir;
+        }
+        // System Property not set. Try to find the directory in the build directory.
+        String androidHostOut = System.getenv("ANDROID_HOST_OUT");
+        if (androidHostOut != null) {
+            platformDir = getPlatformDirFromHostOut(new File(androidHostOut));
+            if (platformDir != null) {
+                return platformDir;
+            }
+        }
+        String workingDirString = System.getProperty("user.dir");
+        File workingDir = new File(workingDirString);
+        // Test if workingDir is android checkout root.
+        platformDir = getPlatformDirFromRoot(workingDir);
+        if (platformDir != null) {
+            return platformDir;
+        }
+
+        // Test if workingDir is platform/frameworks/base/tools/layoutlib/bridge.
+        File currentDir = workingDir;
+        if (currentDir.getName().equalsIgnoreCase("bridge")) {
+            currentDir = currentDir.getParentFile();
+        }
+        // Test if currentDir is  platform/frameworks/base/tools/layoutlib. That is, root should be
+        // workingDir/../../../../  (4 levels up)
+        for (int i = 0; i < 4; i++) {
+            if (currentDir != null) {
+                currentDir = currentDir.getParentFile();
+            }
+        }
+        return currentDir == null ? null : getPlatformDirFromRoot(currentDir);
+    }
+
+    private static String getPlatformDirFromRoot(File root) {
+        if (!root.isDirectory()) {
+            return null;
+        }
+        File out = new File(root, "out");
+        if (!out.isDirectory()) {
+            return null;
+        }
+        File host = new File(out, "host");
+        if (!host.isDirectory()) {
+            return null;
+        }
+        File[] hosts = host.listFiles(path -> path.isDirectory() &&
+                (path.getName().startsWith("linux-") || path.getName().startsWith("darwin-")));
+        assert hosts != null;
+        for (File hostOut : hosts) {
+            String platformDir = getPlatformDirFromHostOut(hostOut);
+            if (platformDir != null) {
+                return platformDir;
+            }
+        }
+        return null;
+    }
+
+    private static String getPlatformDirFromHostOut(File out) {
+        if (!out.isDirectory()) {
+            return null;
+        }
+        File sdkDir = new File(out, "sdk");
+        if (!sdkDir.isDirectory()) {
+            return null;
+        }
+        File[] sdkDirs = sdkDir.listFiles(path -> {
+            // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7)
+            return path.isDirectory() && path.getName().startsWith("sdk");
+        });
+        assert sdkDirs != null;
+        for (File dir : sdkDirs) {
+            String platformDir = getPlatformDirFromHostOutSdkSdk(dir);
+            if (platformDir != null) {
+                return platformDir;
+            }
+        }
+        return null;
+    }
+
+    private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) {
+        File[] possibleSdks = sdkDir.listFiles(
+                path -> path.isDirectory() && path.getName().contains("android-sdk"));
+        assert possibleSdks != null;
+        for (File possibleSdk : possibleSdks) {
+            File platformsDir = new File(possibleSdk, "platforms");
+            File[] platforms = platformsDir.listFiles(
+                    path -> path.isDirectory() && path.getName().startsWith("android-"));
+            if (platforms == null || platforms.length == 0) {
+                continue;
+            }
+            Arrays.sort(platforms, (o1, o2) -> {
+                final int MAX_VALUE = 1000;
+                String suffix1 = o1.getName().substring("android-".length());
+                String suffix2 = o2.getName().substring("android-".length());
+                int suff1, suff2;
+                try {
+                    suff1 = Integer.parseInt(suffix1);
+                } catch (NumberFormatException e) {
+                    suff1 = MAX_VALUE;
+                }
+                try {
+                    suff2 = Integer.parseInt(suffix2);
+                } catch (NumberFormatException e) {
+                    suff2 = MAX_VALUE;
+                }
+                if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) {
+                    return suff2 - suff1;
+                }
+                return suffix2.compareTo(suffix1);
+            });
+            return platforms[0].getAbsolutePath();
+        }
+        return null;
+    }
+
+    private static String getTestResDir() {
+        String resourceDir = System.getProperty(RESOURCE_DIR_PROPERTY);
+        if (resourceDir != null && !resourceDir.isEmpty() && new File(resourceDir).isDirectory()) {
+            return resourceDir;
+        }
+        // TEST_RES_DIR not explicitly set. Fallback to the class's source location.
+        try {
+            URL location = RenderTestBase.class.getProtectionDomain().getCodeSource().getLocation();
+            return new File(location.getPath()).exists() ? location.getPath() : null;
+        } catch (NullPointerException e) {
+            // Prevent a lot of null checks by just catching the exception.
+            return null;
+        }
+    }
+
+    /**
+     * Initialize the bridge and the resource maps.
+     */
+    @BeforeClass
+    public static void setUp() {
+        File data_dir = new File(PLATFORM_DIR, "data");
+        File res = new File(data_dir, "res");
+        sFrameworkRepo = new FrameworkResources(new FolderWrapper(res));
+        sFrameworkRepo.loadResources();
+        sFrameworkRepo.loadPublicResources(getLogger());
+
+        sProjectResources =
+                new ResourceRepository(new FolderWrapper(TEST_RES_DIR + "/" + APP_TEST_RES),
+                        false) {
+                    @NonNull
+                    @Override
+                    protected ResourceItem createResourceItem(@NonNull String name) {
+                        return new ResourceItem(name);
+                    }
+                };
+        sProjectResources.loadResources();
+
+        File fontLocation = new File(data_dir, "fonts");
+        File buildProp = new File(PLATFORM_DIR, "build.prop");
+        File attrs = new File(res, "values" + File.separator + "attrs.xml");
+        sBridge = new Bridge();
+        sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
+                ConfigGenerator.getEnumMap(attrs), getLayoutLog());
+        Bridge.getLock().lock();
+        try {
+            Bridge.setLog(getLayoutLog());
+        } finally {
+            Bridge.getLock().unlock();
+        }
+    }
+
+    private static void gc() {
+        // See RuntimeUtil#gc in jlibs (http://jlibs.in/)
+        Object obj = new Object();
+        WeakReference ref = new WeakReference<>(obj);
+        //noinspection UnusedAssignment
+        obj = null;
+        while (ref.get() != null) {
+            System.gc();
+            System.runFinalization();
+        }
+
+        System.gc();
+        System.runFinalization();
+    }
+
+    @AfterClass
+    public static void tearDown() {
+        sLayoutLibLog = null;
+        sFrameworkRepo = null;
+        sProjectResources = null;
+        sLogger = null;
+        sBridge = null;
+
+        gc();
+
+        System.out.println("Objects still linked from the DelegateManager:");
+        DelegateManager.dump(System.out);
+    }
+
+    @NonNull
+    protected static RenderResult render(SessionParams params, long frameTimeNanos) {
+        // TODO: Set up action bar handler properly to test menu rendering.
+        // Create session params.
+        System_Delegate.setBootTimeNanos(TimeUnit.MILLISECONDS.toNanos(871732800000L));
+        System_Delegate.setNanosTime(TimeUnit.MILLISECONDS.toNanos(871732800000L));
+        RenderSession session = sBridge.createSession(params);
+
+        try {
+
+            if (frameTimeNanos != -1) {
+                session.setElapsedFrameTimeNanos(frameTimeNanos);
+            }
+
+            if (!session.getResult().isSuccess()) {
+                getLogger().error(session.getResult().getException(),
+                        session.getResult().getErrorMessage());
+            }
+            // Render the session with a timeout of 50s.
+            Result renderResult = session.render(50000);
+            if (!renderResult.isSuccess()) {
+                getLogger().error(session.getResult().getException(),
+                        session.getResult().getErrorMessage());
+            }
+
+            return RenderResult.getFromSession(session);
+        } finally {
+            session.dispose();
+        }
+    }
+
+    /**
+     * Create a new rendering session and test that rendering the given layout doesn't throw any
+     * exceptions and matches the provided image.
+     * <p>
+     * If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates
+     * how far in the future is.
+     */
+    @Nullable
+    protected static RenderResult renderAndVerify(SessionParams params, String goldenFileName,
+            long frameTimeNanos) throws ClassNotFoundException {
+        RenderResult result = RenderTestBase.render(params, frameTimeNanos);
+        try {
+            String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
+            assertNotNull(result.getImage());
+            ImageUtils.requireSimilar(goldenImagePath, result.getImage());
+        } catch (IOException e) {
+            getLogger().error(e, e.getMessage());
+        }
+
+        return result;
+    }
+
+    /**
+     * Create a new rendering session and test that rendering the given layout doesn't throw any
+     * exceptions and matches the provided image.
+     */
+    @Nullable
+    protected static RenderResult renderAndVerify(SessionParams params, String goldenFileName)
+            throws ClassNotFoundException {
+        return RenderTestBase.renderAndVerify(params, goldenFileName, -1);
+    }
+
+    private static LayoutLog getLayoutLog() {
+        if (sLayoutLibLog == null) {
+            sLayoutLibLog = new LayoutLog() {
+                @Override
+                public void warning(String tag, String message, Object data) {
+                    System.out.println("Warning " + tag + ": " + message);
+                    failWithMsg(message);
+                }
+
+                @Override
+                public void fidelityWarning(@Nullable String tag, String message,
+                        Throwable throwable, Object data) {
+
+                    System.out.println("FidelityWarning " + tag + ": " + message);
+                    if (throwable != null) {
+                        throwable.printStackTrace();
+                    }
+                    failWithMsg(message == null ? "" : message);
+                }
+
+                @Override
+                public void error(String tag, String message, Object data) {
+                    System.out.println("Error " + tag + ": " + message);
+                    failWithMsg(message);
+                }
+
+                @Override
+                public void error(String tag, String message, Throwable throwable, Object data) {
+                    System.out.println("Error " + tag + ": " + message);
+                    if (throwable != null) {
+                        throwable.printStackTrace();
+                    }
+                    failWithMsg(message);
+                }
+            };
+        }
+        return sLayoutLibLog;
+    }
+
+    protected static ILogger getLogger() {
+        if (sLogger == null) {
+            sLogger = new ILogger() {
+                @Override
+                public void error(Throwable t, @Nullable String msgFormat, Object... args) {
+                    if (t != null) {
+                        t.printStackTrace();
+                    }
+                    failWithMsg(msgFormat == null ? "" : msgFormat, args);
+                }
+
+                @Override
+                public void warning(@NonNull String msgFormat, Object... args) {
+                    failWithMsg(msgFormat, args);
+                }
+
+                @Override
+                public void info(@NonNull String msgFormat, Object... args) {
+                    // pass.
+                }
+
+                @Override
+                public void verbose(@NonNull String msgFormat, Object... args) {
+                    // pass.
+                }
+            };
+        }
+        return sLogger;
+    }
+
+    private static void failWithMsg(@NonNull String msgFormat, Object... args) {
+        sRenderMessages.add(args == null ? msgFormat : String.format(msgFormat, args));
+    }
+
+    @Before
+    public void beforeTestCase() {
+        sRenderMessages.clear();
+    }
+
+    @NonNull
+    protected LayoutPullParser createLayoutPullParser(String layoutPath) {
+        return new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutPath);
+    }
+
+    /**
+     * Create a new rendering session and test that rendering the given layout on nexus 5
+     * doesn't throw any exceptions and matches the provided image.
+     */
+    @Nullable
+    protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName)
+            throws ClassNotFoundException {
+        return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5);
+    }
+
+    /**
+     * Create a new rendering session and test that rendering the given layout on given device
+     * doesn't throw any exceptions and matches the provided image.
+     */
+    @Nullable
+    protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName,
+            ConfigGenerator deviceConfig) throws ClassNotFoundException {
+        SessionParams params = createSessionParams(layoutFileName, deviceConfig);
+        return renderAndVerify(params, goldenFileName);
+    }
+
+    private SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig)
+            throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = createLayoutPullParser(layoutFileName);
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+        // TODO: Set up action bar handler properly to test menu rendering.
+        // Create session params.
+        return getSessionParams(parser, deviceConfig, layoutLibCallback, "AppTheme", true,
+                RenderingMode.NORMAL, 22);
+    }
+
+    /**
+     * Uses Theme.Material and Target sdk version as 22.
+     */
+    protected SessionParams getSessionParams(LayoutPullParser layoutParser,
+            ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback,
+            String themeName, boolean isProjectTheme, RenderingMode renderingMode,
+            @SuppressWarnings("SameParameterValue") int targetSdk) {
+        FolderConfiguration config = configGenerator.getFolderConfig();
+        ResourceResolver resourceResolver =
+                ResourceResolver.create(sProjectResources.getConfiguredResources(config),
+                        sFrameworkRepo.getConfiguredResources(config), themeName, isProjectTheme);
+
+        SessionParams sessionParams =
+                new SessionParams(layoutParser, renderingMode, null /*used for caching*/,
+                        configGenerator.getHardwareConfig(), resourceResolver, layoutLibCallback, 0,
+                        targetSdk, getLayoutLog());
+        sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true);
+        return sessionParams;
+    }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
new file mode 100644 (file)
index 0000000..73e51ec
--- /dev/null
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2014 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.layoutlib.bridge.intensive;
+
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.impl.RenderAction;
+import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
+import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
+import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
+import com.android.resources.Density;
+import com.android.resources.Navigation;
+import com.android.resources.ResourceType;
+
+import org.junit.Test;
+
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Set of render tests
+ */
+public class RenderTests extends RenderTestBase {
+
+    @Test
+    public void testActivity() throws ClassNotFoundException {
+        renderAndVerify("activity.xml", "activity.png");
+    }
+
+    @Test
+    public void testActivityOnOldTheme() throws ClassNotFoundException {
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.NoTitleBar", false,
+                RenderingMode.NORMAL, 22);
+
+        renderAndVerify(params, "simple_activity-old-theme.png");
+    }
+
+    @Test
+    public void testTranslucentBars() throws ClassNotFoundException {
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        LayoutPullParser parser = createLayoutPullParser("four_corners.xml");
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
+                RenderingMode.NORMAL, 22);
+        renderAndVerify(params, "four_corners_translucent.png");
+
+        parser = createLayoutPullParser("four_corners.xml");
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5_LAND,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
+                RenderingMode.NORMAL, 22);
+        renderAndVerify(params, "four_corners_translucent_land.png");
+
+        parser = createLayoutPullParser("four_corners.xml");
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
+                RenderingMode.NORMAL, 22);
+        renderAndVerify(params, "four_corners.png");
+    }
+
+    @Test
+    public void testAllWidgets() throws ClassNotFoundException {
+        renderAndVerify("allwidgets.xml", "allwidgets.png");
+
+        // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+        sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
+    }
+
+    @Test
+    public void testArrayCheck() throws ClassNotFoundException {
+        renderAndVerify("array_check.xml", "array_check.png");
+    }
+
+    @Test
+    public void testAllWidgetsTablet() throws ClassNotFoundException {
+        renderAndVerify("allwidgets.xml", "allwidgets_tab.png", ConfigGenerator.NEXUS_7_2012);
+
+        // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+        sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
+    }
+
+    @Test
+    public void testActivityActionBar() throws ClassNotFoundException {
+        LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "simple_activity_noactionbar.png");
+
+        parser = createLayoutPullParser("simple_activity.xml");
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.Light", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "simple_activity.png");
+
+        // This also tests that a theme with "NoActionBar" DOES HAVE an action bar when we are
+        // displaying menus.
+        parser = createLayoutPullParser("simple_activity.xml");
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
+                RenderingMode.V_SCROLL, 22);
+        params.setFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG, "menu");
+        renderAndVerify(params, "simple_activity.png");
+    }
+
+    @Test
+    public void testOnApplyInsetsCall()
+            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
+        // We get the widget via reflection to avoid IntelliJ complaining about the class being
+        // located in the wrong package. (From the Bridge tests point of view, it is)
+        Class insetsWidgetClass = Class.forName("com.android.layoutlib.test.myapplication.widgets" +
+                ".InsetsWidget");
+        Field field = insetsWidgetClass.getDeclaredField("sApplyInsetsCalled");
+        assertFalse((Boolean)field.get(null));
+
+        LayoutPullParser parser = createLayoutPullParser("insets.xml");
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
+                RenderingMode.NORMAL, 22);
+
+        render(params, -1);
+
+        assertTrue((Boolean)field.get(null));
+        field.set(null, false);
+    }
+
+    /** Test expand_layout.xml */
+    @Test
+    public void testExpand() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = createLayoutPullParser("expand_vert_layout.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        ConfigGenerator customConfigGenerator = new ConfigGenerator()
+                .setScreenWidth(300)
+                .setScreenHeight(20)
+                .setDensity(Density.XHIGH)
+                .setNavigation(Navigation.NONAV);
+
+        SessionParams params = getSessionParams(parser, customConfigGenerator,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "expand_vert_layout.png");
+
+        customConfigGenerator = new ConfigGenerator()
+                .setScreenWidth(20)
+                .setScreenHeight(300)
+                .setDensity(Density.XHIGH)
+                .setNavigation(Navigation.NONAV);
+        parser = createLayoutPullParser("expand_horz_layout.xml");
+        params = getSessionParams(parser, customConfigGenerator,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
+                RenderingMode.H_SCROLL, 22);
+
+        renderAndVerify(params, "expand_horz_layout.png");
+    }
+
+    /** Test indeterminate_progressbar.xml */
+    @Test
+    public void testVectorAnimation() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = createLayoutPullParser("indeterminate_progressbar.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
+
+        parser = createLayoutPullParser("indeterminate_progressbar.xml");
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+        renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3));
+    }
+
+    /**
+     * Test a vector drawable that uses trimStart and trimEnd. It also tests all the primitives
+     * for vector drawables (lines, moves and cubic and quadratic curves).
+     */
+    @Test
+    public void testVectorDrawable() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = createLayoutPullParser("vector_drawable.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2));
+    }
+
+    /**
+     * Regression test for http://b.android.com/91383 and http://b.android.com/203797
+     */
+    @Test
+    public void testVectorDrawable91383() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = createLayoutPullParser("vector_drawable_android.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "vector_drawable_91383.png", TimeUnit.SECONDS.toNanos(2));
+    }
+
+    /** Test activity.xml */
+    @Test
+    public void testScrollingAndMeasure() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = createLayoutPullParser("scrolled.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+        params.setForceNoDecor();
+        params.setExtendedViewInfoMode(true);
+
+        // Do an only-measure pass
+        RenderSession session = sBridge.createSession(params);
+        session.measure();
+        RenderResult result = RenderResult.getFromSession(session);
+        assertNotNull(result);
+        assertNotNull(result.getResult());
+        assertTrue(result.getResult().isSuccess());
+
+        ViewInfo rootLayout = result.getRootViews().get(0);
+        // Check the first box in the main LinearLayout
+        assertEquals(-90, rootLayout.getChildren().get(0).getTop());
+        assertEquals(-30, rootLayout.getChildren().get(0).getLeft());
+        assertEquals(90, rootLayout.getChildren().get(0).getBottom());
+        assertEquals(150, rootLayout.getChildren().get(0).getRight());
+
+        // Check the first box within the nested LinearLayout
+        assertEquals(-450, rootLayout.getChildren().get(5).getChildren().get(0).getTop());
+        assertEquals(90, rootLayout.getChildren().get(5).getChildren().get(0).getLeft());
+        assertEquals(-270, rootLayout.getChildren().get(5).getChildren().get(0).getBottom());
+        assertEquals(690, rootLayout.getChildren().get(5).getChildren().get(0).getRight());
+
+        // Do a full render pass
+        parser = createLayoutPullParser("scrolled.xml");
+
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+        params.setForceNoDecor();
+        params.setExtendedViewInfoMode(true);
+
+        result = renderAndVerify(params, "scrolled.png");
+        assertNotNull(result);
+        assertNotNull(result.getResult());
+        assertTrue(result.getResult().isSuccess());
+    }
+
+    @Test
+    public void testGetResourceNameVariants() throws Exception {
+        // Setup
+        // Create the layout pull parser for our resources (empty.xml can not be part of the test
+        // app as it won't compile).
+        LayoutPullParser parser = new LayoutPullParser("/empty.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
+                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+        AssetManager assetManager = AssetManager.getSystem();
+        DisplayMetrics metrics = new DisplayMetrics();
+        Configuration configuration = RenderAction.getConfiguration(params);
+        Resources resources = new Resources(assetManager, metrics, configuration);
+        resources.mLayoutlibCallback = params.getLayoutlibCallback();
+        resources.mContext =
+                new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
+                        params.getAssets(), params.getLayoutlibCallback(), configuration,
+                        params.getTargetSdkVersion(), params.isRtlSupported());
+        // Test
+        assertEquals("android:style/ButtonBar",
+                resources.getResourceName(android.R.style.ButtonBar));
+        assertEquals("android", resources.getResourcePackageName(android.R.style.ButtonBar));
+        assertEquals("ButtonBar", resources.getResourceEntryName(android.R.style.ButtonBar));
+        assertEquals("style", resources.getResourceTypeName(android.R.style.ButtonBar));
+        int id = resources.mLayoutlibCallback.getResourceId(ResourceType.STRING, "app_name");
+        assertEquals("com.android.layoutlib.test.myapplication:string/app_name",
+                resources.getResourceName(id));
+        assertEquals("com.android.layoutlib.test.myapplication",
+                resources.getResourcePackageName(id));
+        assertEquals("string", resources.getResourceTypeName(id));
+        assertEquals("app_name", resources.getResourceEntryName(id));
+    }
+
+    @Test
+    public void testStringEscaping() throws Exception {
+        // Setup
+        // Create the layout pull parser for our resources (empty.xml can not be part of the test
+        // app as it won't compile).
+        LayoutPullParser parser = new LayoutPullParser("/empty.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
+                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+        AssetManager assetManager = AssetManager.getSystem();
+        DisplayMetrics metrics = new DisplayMetrics();
+        Configuration configuration = RenderAction.getConfiguration(params);
+        Resources resources = new Resources(assetManager, metrics, configuration);
+        resources.mLayoutlibCallback = params.getLayoutlibCallback();
+        resources.mContext =
+                new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
+                        params.getAssets(), params.getLayoutlibCallback(), configuration,
+                        params.getTargetSdkVersion(), params.isRtlSupported());
+
+        int id = resources.mLayoutlibCallback.getResourceId(ResourceType.ARRAY, "string_array");
+        String[] strings = resources.getStringArray(id);
+        assertArrayEquals(
+                new String[]{"mystring", "Hello world!", "candidates", "Unknown", "?EC"},
+                strings);
+        assertTrue(sRenderMessages.isEmpty());
+    }
+}
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.layoutlib.bridge.intensive;
+package com.android.layoutlib.bridge.intensive.util;
 
 import android.annotation.NonNull;