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