1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.layoutlib.bridge.intensive;
18 
19 import com.android.ide.common.rendering.api.ILayoutLog;
20 import com.android.ide.common.rendering.api.RenderSession;
21 import com.android.ide.common.rendering.api.Result;
22 import com.android.ide.common.rendering.api.SessionParams;
23 import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
24 import com.android.ide.common.resources.deprecated.FrameworkResources;
25 import com.android.ide.common.resources.deprecated.ResourceItem;
26 import com.android.ide.common.resources.deprecated.ResourceRepository;
27 import com.android.ide.common.resources.deprecated.TestFolderWrapper;
28 import com.android.internal.lang.System_Delegate;
29 import com.android.layoutlib.bridge.Bridge;
30 import com.android.layoutlib.bridge.android.RenderParamsFlags;
31 import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
32 import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
33 import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
34 import com.android.layoutlib.bridge.intensive.util.ImageUtils;
35 import com.android.layoutlib.bridge.intensive.util.ModuleClassLoader;
36 import com.android.layoutlib.bridge.intensive.util.SessionParamsBuilder;
37 import com.android.layoutlib.bridge.intensive.util.TestAssetRepository;
38 import com.android.utils.ILogger;
39 
40 import org.junit.AfterClass;
41 import org.junit.Before;
42 import org.junit.BeforeClass;
43 import org.junit.Rule;
44 import org.junit.rules.TestWatcher;
45 import org.junit.runner.Description;
46 
47 import android.annotation.NonNull;
48 import android.annotation.Nullable;
49 import android.view.Choreographer;
50 
51 import java.awt.image.BufferedImage;
52 import java.io.File;
53 import java.io.FileNotFoundException;
54 import java.io.IOException;
55 import java.net.URL;
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.Locale;
59 import java.util.concurrent.TimeUnit;
60 
61 import com.google.android.collect.Lists;
62 
63 import static org.junit.Assert.assertNotNull;
64 import static org.junit.Assert.fail;
65 
66 /**
67  * Base class for render tests. The render tests load all the framework resources and a project
68  * checked in this test's resources. The main dependencies
69  * are:
70  * 1. Fonts directory.
71  * 2. Framework Resources.
72  * 3. App resources.
73  * 4. build.prop file
74  * <p>
75  * These are configured by two variables set in the system properties.
76  * <p>
77  * 1. platform.dir: This is the directory for the current platform in the built SDK
78  * (.../sdk/platforms/android-<version>).
79  * <p>
80  * The fonts are platform.dir/data/fonts.
81  * The Framework resources are platform.dir/data/res.
82  * build.prop is at platform.dir/build.prop.
83  * <p>
84  * 2. test_res.dir: This is the directory for the resources of the test. If not specified, this
85  * falls back to getClass().getProtectionDomain().getCodeSource().getLocation()
86  * <p>
87  * The app resources are at: test_res.dir/testApp/MyApplication/app/src/main/res
88  */
89 public class RenderTestBase {
90 
91     /**
92      * Listener for render process.
93      */
94     public interface RenderSessionListener {
95 
96         /**
97          * Called before session is disposed after rendering.
98          */
beforeDisposed(RenderSession session)99         void beforeDisposed(RenderSession session);
100     }
101 
102     private static final String NATIVE_LIB_PATH_PROPERTY = "native.lib.path";
103     private static final String FONT_DIR_PROPERTY = "font.dir";
104     private static final String ICU_DATA_PATH_PROPERTY = "icu.data.path";
105     private static final String KEYBOARD_DIR_PROPERTY = "keyboard.dir";
106     private static final String PLATFORM_DIR_PROPERTY = "platform.dir";
107     private static final String RESOURCE_DIR_PROPERTY = "test_res.dir";
108 
109     private static final String NATIVE_LIB_DIR_PATH;
110     private static final String FONT_DIR;
111     private static final String ICU_DATA_PATH;
112     private static final String KEYBOARD_DIR;
113     protected static final String PLATFORM_DIR;
114     private static final String TEST_RES_DIR;
115     /** Location of the app to test inside {@link #TEST_RES_DIR} */
116     protected static final String APP_TEST_DIR = "testApp/MyApplication";
117     /** Location of the app's res dir inside {@link #TEST_RES_DIR} */
118     private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
119     /** Location of the app's asset dir inside {@link #TEST_RES_DIR} */
120     private static final String APP_TEST_ASSET = APP_TEST_DIR + "/src/main/assets/";
121     private static final String APP_CLASSES_LOCATION =
122             APP_TEST_DIR + "/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/";
123     protected static Bridge sBridge;
124     /** List of log messages generated by a render call. It can be used to find specific errors */
125     protected static ArrayList<String> sRenderMessages = Lists.newArrayList();
126     private static ILayoutLog sLayoutLibLog;
127     private static FrameworkResources sFrameworkRepo;
128     private static ResourceRepository sProjectResources;
129     private static ILogger sLogger;
130 
131     static {
132         // Test that System Properties are properly set.
133         PLATFORM_DIR = getPlatformDir();
134         if (PLATFORM_DIR == null) {
135             fail(String.format("System Property %1$s not properly set. The value is %2$s",
136                     PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY)));
137         }
138 
139         NATIVE_LIB_DIR_PATH = getNativeLibDirPath();
140         FONT_DIR = getFontDir();
141         ICU_DATA_PATH = getIcuDataPath();
142         KEYBOARD_DIR = getKeyboardDir();
143 
144         TEST_RES_DIR = getTestResDir();
145         if (TEST_RES_DIR == null) {
146             fail(String.format("System property %1$s.dir not properly set. The value is %2$s",
147                     RESOURCE_DIR_PROPERTY, System.getProperty(RESOURCE_DIR_PROPERTY)));
148         }
149     }
150 
151     @Rule
152     public TestWatcher sRenderMessageWatcher = new TestWatcher() {
153         @Override
154         protected void succeeded(Description description) {
155             // We only check error messages if the rest of the test case was successful.
156             if (!sRenderMessages.isEmpty()) {
157                 fail(description.getMethodName() + " render error message: " +
158                         sRenderMessages.get(0));
159             }
160         }
161     };
162 
163     @Rule
164     public TestWatcher sMemoryLeakChecker = new TestWatcher() {
165         @Override
166         protected void succeeded(Description description) {
167             for (int i = Choreographer.CALLBACK_INPUT; i <= Choreographer.CALLBACK_COMMIT; ++i) {
168                 if (Choreographer.getInstance().mCallbackQueues[i].mHead != null) {
169                     fail("Memory leak: leftover frame callbacks are detected in Choreographer");
170                 }
171             }
172         }
173     };
174 
175     protected ClassLoader mDefaultClassLoader;
176 
getNativeLibDirPath()177     private static String getNativeLibDirPath() {
178         String nativeLibDirPath = System.getProperty(NATIVE_LIB_PATH_PROPERTY);
179         if (nativeLibDirPath != null) {
180             File nativeLibDir = new File(nativeLibDirPath);
181             if (nativeLibDir.isDirectory()) {
182                 nativeLibDirPath = nativeLibDir.getAbsolutePath();
183             } else {
184                 nativeLibDirPath = null;
185             }
186         }
187         if (nativeLibDirPath == null) {
188             nativeLibDirPath = PLATFORM_DIR + "/../../../../../lib64/";
189         }
190         return nativeLibDirPath;
191     }
192 
getFontDir()193     private static String getFontDir() {
194         String fontDir = System.getProperty(FONT_DIR_PROPERTY);
195         if (fontDir == null) {
196             // The fonts are built into out/host/common/obj/PACKAGING/fonts_intermediates
197             // as specified in build/make/core/layoutlib_data.mk, and PLATFORM_DIR is
198             // out/host/[arch]/sdk/sdk*/android-sdk*/platforms/android*
199             fontDir = PLATFORM_DIR +
200                     "/../../../../../../common/obj/PACKAGING/fonts_intermediates";
201         }
202         return fontDir;
203     }
204 
getIcuDataPath()205     private static String getIcuDataPath() {
206         String icuDataPath = System.getProperty(ICU_DATA_PATH_PROPERTY);
207         if (icuDataPath == null) {
208             icuDataPath = PLATFORM_DIR + "/../../../../../com.android.i18n/etc/icu/icudt71l.dat";
209         }
210         return icuDataPath;
211     }
212 
getKeyboardDir()213     private static String getKeyboardDir() {
214         String keyboardDir = System.getProperty(KEYBOARD_DIR_PROPERTY);
215         if (keyboardDir == null) {
216             // The keyboard files are built into
217             // out/host/common/obj/PACKAGING/keyboards_intermediates
218             // as specified in build/make/core/layoutlib_data.mk, and PLATFORM_DIR is
219             // out/host/[arch]/sdk/sdk*/android-sdk*/platforms/android*
220             keyboardDir = PLATFORM_DIR +
221                     "/../../../../../../common/obj/PACKAGING/keyboards_intermediates";
222         }
223         return keyboardDir;
224     }
225 
getPlatformDir()226     private static String getPlatformDir() {
227         String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY);
228         if (platformDir != null && !platformDir.isEmpty() && new File(platformDir).isDirectory()) {
229             return platformDir;
230         }
231         // System Property not set. Try to find the directory in the build directory.
232         String androidHostOut = System.getenv("ANDROID_HOST_OUT");
233         if (androidHostOut != null) {
234             platformDir = getPlatformDirFromHostOut(new File(androidHostOut));
235             if (platformDir != null) {
236                 return platformDir;
237             }
238         }
239         String workingDirString = System.getProperty("user.dir");
240         File workingDir = new File(workingDirString);
241         // Test if workingDir is android checkout root.
242         platformDir = getPlatformDirFromRoot(workingDir);
243         if (platformDir != null) {
244             return platformDir;
245         }
246 
247         // Test if workingDir is platform/frameworks/base/tools/layoutlib/bridge.
248         File currentDir = workingDir;
249         if (currentDir.getName().equalsIgnoreCase("bridge")) {
250             currentDir = currentDir.getParentFile();
251         }
252 
253         // Find frameworks/layoutlib
254         while (currentDir != null && !"layoutlib".equals(currentDir.getName())) {
255             currentDir = currentDir.getParentFile();
256         }
257 
258         if (currentDir == null ||
259                 currentDir.getParentFile() == null ||
260                 !"frameworks".equals(currentDir.getParentFile().getName())) {
261             return null;
262         }
263 
264         // Test if currentDir is  platform/frameworks/layoutlib. That is, root should be
265         // workingDir/../../ (2 levels up)
266         for (int i = 0; i < 2; i++) {
267             if (currentDir != null) {
268                 currentDir = currentDir.getParentFile();
269             }
270         }
271         return currentDir == null ? null : getPlatformDirFromRoot(currentDir);
272     }
273 
getPlatformDirFromRoot(File root)274     private static String getPlatformDirFromRoot(File root) {
275         if (!root.isDirectory()) {
276             return null;
277         }
278         File out = new File(root, "out");
279         if (!out.isDirectory()) {
280             return null;
281         }
282         File host = new File(out, "host");
283         if (!host.isDirectory()) {
284             return null;
285         }
286         File[] hosts = host.listFiles(path -> path.isDirectory() &&
287                 (path.getName().startsWith("linux-") ||
288                         path.getName().startsWith("darwin-")));
289         assert hosts != null;
290         for (File hostOut : hosts) {
291             String platformDir = getPlatformDirFromHostOut(hostOut);
292             if (platformDir != null) {
293                 return platformDir;
294             }
295         }
296 
297         return null;
298     }
299 
getPlatformDirFromHostOut(File out)300     private static String getPlatformDirFromHostOut(File out) {
301         if (!out.isDirectory()) {
302             return null;
303         }
304         File sdkDir = new File(out, "sdk");
305         if (!sdkDir.isDirectory()) {
306             return null;
307         }
308         File[] sdkDirs = sdkDir.listFiles(path -> {
309             // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7)
310             return path.isDirectory() && path.getName().startsWith("sdk");
311         });
312         assert sdkDirs != null;
313         for (File dir : sdkDirs) {
314             String platformDir = getPlatformDirFromHostOutSdkSdk(dir);
315             if (platformDir != null) {
316                 return platformDir;
317             }
318         }
319         return null;
320     }
321 
getPlatformDirFromHostOutSdkSdk(File sdkDir)322     private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) {
323         File[] possibleSdks = sdkDir.listFiles(
324                 path -> path.isDirectory() && path.getName().contains("android-sdk"));
325         assert possibleSdks != null;
326         for (File possibleSdk : possibleSdks) {
327             File platformsDir = new File(possibleSdk, "platforms");
328             File[] platforms = platformsDir.listFiles(
329                     path -> path.isDirectory() && path.getName().startsWith("android-"));
330             if (platforms == null || platforms.length == 0) {
331                 continue;
332             }
333             Arrays.sort(platforms, (o1, o2) -> {
334                 final int MAX_VALUE = 1000;
335                 String suffix1 = o1.getName().substring("android-".length());
336                 String suffix2 = o2.getName().substring("android-".length());
337                 int suff1, suff2;
338                 try {
339                     suff1 = Integer.parseInt(suffix1);
340                 } catch (NumberFormatException e) {
341                     suff1 = MAX_VALUE;
342                 }
343                 try {
344                     suff2 = Integer.parseInt(suffix2);
345                 } catch (NumberFormatException e) {
346                     suff2 = MAX_VALUE;
347                 }
348                 if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) {
349                     return suff2 - suff1;
350                 }
351                 return suffix2.compareTo(suffix1);
352             });
353             return platforms[0].getAbsolutePath();
354         }
355         return null;
356     }
357 
getTestResDir()358     private static String getTestResDir() {
359         String resourceDir = System.getProperty(RESOURCE_DIR_PROPERTY);
360         if (resourceDir != null && !resourceDir.isEmpty() && new File(resourceDir).isDirectory()) {
361             return resourceDir;
362         }
363         // TEST_RES_DIR not explicitly set. Fallback to the class's source location.
364         try {
365             URL location = RenderTestBase.class.getProtectionDomain().getCodeSource().getLocation();
366             return new File(location.getPath()).exists() ? location.getPath() : null;
367         } catch (NullPointerException e) {
368             // Prevent a lot of null checks by just catching the exception.
369             return null;
370         }
371     }
372 
373     /**
374      * Initialize the bridge and the resource maps.
375      */
376     @BeforeClass
beforeClass()377     public static void beforeClass() {
378         File data_dir = new File(PLATFORM_DIR, "data");
379         File res = new File(data_dir, "res");
380         sFrameworkRepo = new FrameworkResources(new TestFolderWrapper(res));
381         sFrameworkRepo.loadResources();
382         sFrameworkRepo.loadPublicResources(getLogger());
383 
384         sProjectResources =
385                 new ResourceRepository(new TestFolderWrapper(TEST_RES_DIR + "/" + APP_TEST_RES),
386                         false) {
387                     @NonNull
388                     @Override
389                     protected ResourceItem createResourceItem(@NonNull String name) {
390                         return new ResourceItem(name);
391                     }
392                 };
393         sProjectResources.loadResources();
394 
395         File fontLocation = new File(FONT_DIR);
396         File buildProp = new File(PLATFORM_DIR, "build.prop");
397         File attrs = new File(res, "values" + File.separator + "attrs.xml");
398 
399         String[] keyboardPaths = new String[] { KEYBOARD_DIR + "/Generic.kcm" };
400         sBridge = new Bridge();
401         sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation, NATIVE_LIB_DIR_PATH,
402                 ICU_DATA_PATH, keyboardPaths, ConfigGenerator.getEnumMap(attrs), getLayoutLog());
403         Bridge.getLock().lock();
404         try {
405             Bridge.setLog(getLayoutLog());
406         } finally {
407             Bridge.getLock().unlock();
408         }
409     }
410 
411     @AfterClass
tearDown()412     public static void tearDown() {
413         sLayoutLibLog = null;
414         sFrameworkRepo = null;
415         sProjectResources = null;
416         sLogger = null;
417         sBridge = null;
418     }
419 
420     @NonNull
render(com.android.ide.common.rendering.api.Bridge bridge, SessionParams params, long frameTimeNanos)421     protected static RenderResult render(com.android.ide.common.rendering.api.Bridge bridge,
422             SessionParams params,
423             long frameTimeNanos) {
424         return render(bridge, params, frameTimeNanos, null);
425     }
426 
427     @NonNull
render(com.android.ide.common.rendering.api.Bridge bridge, SessionParams params, long frameTimeNanos, @Nullable RenderSessionListener listener)428     protected static RenderResult render(com.android.ide.common.rendering.api.Bridge bridge,
429             SessionParams params,
430             long frameTimeNanos,
431             @Nullable RenderSessionListener listener) {
432         // TODO: Set up action bar handler properly to test menu rendering.
433         // Create session params.
434         System_Delegate.setBootTimeNanos(TimeUnit.MILLISECONDS.toNanos(871732800000L));
435         System_Delegate.setNanosTime(TimeUnit.MILLISECONDS.toNanos(871732800000L));
436         RenderSession session = bridge.createSession(params);
437 
438         try {
439             if (frameTimeNanos != -1) {
440                 session.setElapsedFrameTimeNanos(frameTimeNanos);
441             }
442 
443             if (!session.getResult().isSuccess()) {
444                 getLogger().error(session.getResult().getException(),
445                         session.getResult().getErrorMessage());
446             }
447             else {
448                 // Render the session with a timeout of 50s.
449                 Result renderResult = session.render(50000);
450                 if (!renderResult.isSuccess()) {
451                     getLogger().error(session.getResult().getException(),
452                             session.getResult().getErrorMessage());
453                 }
454             }
455             if (listener != null) {
456                 listener.beforeDisposed(session);
457             }
458 
459             return RenderResult.getFromSession(session);
460         } finally {
461             session.dispose();
462         }
463     }
464 
465     /**
466      * Compares the golden image with the passed image
467      */
verify(@onNull String goldenImageName, @NonNull BufferedImage image)468     protected static void verify(@NonNull String goldenImageName, @NonNull BufferedImage image) {
469         try {
470             String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenImageName;
471             ImageUtils.requireSimilar(goldenImagePath, image);
472         } catch (IOException e) {
473             getLogger().error(e, e.getMessage());
474         }
475     }
476 
477     /**
478      * Create a new rendering session and test that rendering the given layout doesn't throw any
479      * exceptions and matches the provided image.
480      * <p>
481      * If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates
482      * how far in the future is.
483      */
484     @Nullable
renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos)485     protected static RenderResult renderAndVerify(SessionParams params, String goldenFileName,
486             long frameTimeNanos) throws ClassNotFoundException {
487         RenderResult result = RenderTestBase.render(sBridge, params, frameTimeNanos);
488         assertNotNull(result.getImage());
489         verify(goldenFileName, result.getImage());
490 
491         return result;
492     }
493 
494     /**
495      * Create a new rendering session and test that rendering the given layout doesn't throw any
496      * exceptions and matches the provided image.
497      */
498     @Nullable
renderAndVerify(SessionParams params, String goldenFileName)499     protected static RenderResult renderAndVerify(SessionParams params, String goldenFileName)
500             throws ClassNotFoundException {
501         return RenderTestBase.renderAndVerify(params, goldenFileName, TimeUnit.SECONDS.toNanos(2));
502     }
503 
getLayoutLog()504     protected static ILayoutLog getLayoutLog() {
505         if (sLayoutLibLog == null) {
506             sLayoutLibLog = new ILayoutLog() {
507                 @Override
508                 public void warning(@Nullable String tag, @NonNull String message, @Nullable Object viewCookie,
509                         @Nullable Object data) {
510                     System.out.println("Warning " + tag + ": " + message);
511                     failWithMsg(message);
512                 }
513 
514                 @Override
515                 public void fidelityWarning(@Nullable String tag, String message,
516                         Throwable throwable, Object cookie, Object data) {
517 
518                     System.out.println("FidelityWarning " + tag + ": " + message);
519                     if (throwable != null) {
520                         throwable.printStackTrace();
521                     }
522                     failWithMsg(message == null ? "" : message);
523                 }
524 
525                 @Override
526                 public void error(@Nullable String tag, @NonNull String message, @Nullable Object viewCookie,
527                         @Nullable Object data) {
528                     System.out.println("Error " + tag + ": " + message);
529                     failWithMsg(message);
530                 }
531 
532                 @Override
533                 public void error(@Nullable String tag, @NonNull String message, @Nullable Throwable throwable,
534                         @Nullable Object viewCookie, @Nullable Object data) {
535                     System.out.println("Error " + tag + ": " + message);
536                     if (throwable != null) {
537                         throwable.printStackTrace();
538                     }
539                     failWithMsg(message);
540                 }
541 
542                 @Override
543                 public void logAndroidFramework(int priority, String tag, String message) {
544                     System.out.println("Android framework message " + tag + ": " + message);
545                 }
546             };
547         }
548         return sLayoutLibLog;
549     }
550 
ignoreAllLogging()551     protected static void ignoreAllLogging() {
552         sLayoutLibLog = new ILayoutLog() {};
553         sLogger = new ILogger() {
554             @Override
555             public void error(Throwable t, String msgFormat, Object... args) {
556             }
557 
558             @Override
559             public void warning(String msgFormat, Object... args) {
560             }
561 
562             @Override
563             public void info(String msgFormat, Object... args) {
564             }
565 
566             @Override
567             public void verbose(String msgFormat, Object... args) {
568             }
569         };
570     }
571 
getLogger()572     protected static ILogger getLogger() {
573         if (sLogger == null) {
574             sLogger = new ILogger() {
575                 @Override
576                 public void error(Throwable t, @Nullable String msgFormat, Object... args) {
577                     if (t != null) {
578                         t.printStackTrace();
579                     }
580                     failWithMsg(msgFormat == null ? "" : msgFormat, args);
581                 }
582 
583                 @Override
584                 public void warning(@NonNull String msgFormat, Object... args) {
585                     failWithMsg(msgFormat, args);
586                 }
587 
588                 @Override
589                 public void info(@NonNull String msgFormat, Object... args) {
590                     // pass.
591                 }
592 
593                 @Override
594                 public void verbose(@NonNull String msgFormat, Object... args) {
595                     // pass.
596                 }
597             };
598         }
599         return sLogger;
600     }
601 
failWithMsg(@onNull String msgFormat, Object... args)602     private static void failWithMsg(@NonNull String msgFormat, Object... args) {
603         sRenderMessages.add(args == null ? msgFormat : String.format(msgFormat, args));
604     }
605 
606     @Before
beforeTestCase()607     public void beforeTestCase() {
608         // Default class loader with access to the app classes
609         mDefaultClassLoader = new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
610         sRenderMessages.clear();
611     }
612 
613     @NonNull
createParserFromPath(String layoutPath)614     protected LayoutPullParser createParserFromPath(String layoutPath)
615             throws FileNotFoundException {
616         return LayoutPullParser.createFromPath(APP_TEST_RES + "/layout/" + layoutPath);
617     }
618 
619     /**
620      * Create a new rendering session and test that rendering the given layout on nexus 5
621      * doesn't throw any exceptions and matches the provided image.
622      */
623     @Nullable
renderAndVerify(String layoutFileName, String goldenFileName, boolean decoration)624     protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName,
625             boolean decoration)
626             throws ClassNotFoundException, FileNotFoundException {
627         return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5, decoration);
628     }
629 
630     /**
631      * Create a new rendering session and test that rendering the given layout on given device
632      * doesn't throw any exceptions and matches the provided image.
633      */
634     @Nullable
renderAndVerify(String layoutFileName, String goldenFileName, ConfigGenerator deviceConfig, boolean decoration)635     protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName,
636             ConfigGenerator deviceConfig, boolean decoration) throws ClassNotFoundException,
637             FileNotFoundException {
638         SessionParams params = createSessionParams(layoutFileName, deviceConfig);
639         if (!decoration) {
640             params.setForceNoDecor();
641         }
642         return renderAndVerify(params, goldenFileName);
643     }
644 
createSessionParams(String layoutFileName, ConfigGenerator deviceConfig)645     protected SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig)
646             throws ClassNotFoundException, FileNotFoundException {
647         // Create the layout pull parser.
648         LayoutPullParser parser = createParserFromPath(layoutFileName);
649         // Create LayoutLibCallback.
650         LayoutLibTestCallback layoutLibCallback =
651                 new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
652         layoutLibCallback.initResources();
653         // TODO: Set up action bar handler properly to test menu rendering.
654         // Create session params.
655         return getSessionParamsBuilder()
656                 .setParser(parser)
657                 .setConfigGenerator(deviceConfig)
658                 .setCallback(layoutLibCallback)
659                 .build();
660     }
661 
662     /**
663      * Returns a pre-configured {@link SessionParamsBuilder} for target API 22, Normal rendering
664      * mode, AppTheme as theme and Nexus 5.
665      */
666     @NonNull
getSessionParamsBuilder()667     protected SessionParamsBuilder getSessionParamsBuilder() {
668         return new SessionParamsBuilder()
669                 .setLayoutLog(getLayoutLog())
670                 .setFrameworkResources(sFrameworkRepo)
671                 .setConfigGenerator(ConfigGenerator.NEXUS_5)
672                 .setProjectResources(sProjectResources)
673                 .setTheme("AppTheme", true)
674                 .setRenderingMode(RenderingMode.NORMAL)
675                 .setTargetSdk(28)
676                 .setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true)
677                 .setAssetRepository(new TestAssetRepository(TEST_RES_DIR + "/" + APP_TEST_ASSET));
678     }
679 }
680