1 package com.xtremelabs.robolectric; 2 3 import com.xtremelabs.robolectric.RobolectricTestRunner.InstrumentDetector; 4 import com.xtremelabs.robolectric.bytecode.ClassHandler; 5 import com.xtremelabs.robolectric.bytecode.RobolectricClassLoader; 6 import com.xtremelabs.robolectric.bytecode.ShadowWrangler; 7 import org.junit.Test; 8 import org.junit.runner.JUnitCore; 9 import org.junit.runner.Result; 10 import org.junit.runner.RunWith; 11 import org.junit.runners.model.InitializationError; 12 13 import java.io.PrintWriter; 14 import java.io.StringWriter; 15 16 import static com.xtremelabs.robolectric.util.TestUtil.resourceFile; 17 import static org.hamcrest.CoreMatchers.equalTo; 18 import static org.junit.Assert.assertThat; 19 import static org.junit.Assert.assertTrue; 20 21 /** 22 * Tests related to custom instrument detection strategy 23 * that is used for introducing custom class loaders to test runners. 24 */ 25 public class InstrumentDetectorTest { 26 27 // ==================== CLASSLOADER ==================== 28 29 /** Custom class loader. It must be singleton. */ 30 public static class ClassLoaderForTesting extends RobolectricClassLoader { 31 32 /** Instance. */ 33 private static ClassLoaderForTesting instance; 34 35 /** Usage flag. */ 36 static boolean usageFlag = false; 37 38 public static ClassLoaderForTesting getInstance() { 39 if (instance == null) { 40 instance = new ClassLoaderForTesting(ShadowWrangler.getInstance()); 41 } 42 return instance; 43 } 44 45 protected ClassLoaderForTesting(final ClassHandler classHandler) { 46 super(classHandler); 47 } 48 49 @Override 50 public Class<?> loadClass(final String name) throws ClassNotFoundException { 51 if (!usageFlag) { 52 System.setProperty("robolectric.cusomClassLoader", "yes"); 53 } 54 usageFlag = true; 55 return super.loadClass(name); 56 } 57 58 } 59 60 // ==================== RUNNERS ==================== 61 62 /** Custom runner that uses custom class loader but does not modify instrument detection. */ 63 public static class RunnerForTesting extends RobolectricTestRunner { 64 65 public RunnerForTesting(final Class<?> testClass) throws InitializationError { 66 super( 67 testClass, 68 isInstrumented() ? null : ShadowWrangler.getInstance(), 69 isInstrumented() ? null : ClassLoaderForTesting.getInstance(), 70 new RobolectricConfig(resourceFile("TestAndroidManifest.xml"), resourceFile("res"), resourceFile("assets")) 71 ); 72 } 73 74 } 75 76 /** Custom runner that uses custom class loader. */ 77 public static class RunnerForTestingThatWillPass extends RobolectricTestRunner { 78 79 static { 80 RunnerForTestingThatWillPass.setInstrumentDetector(CUSTOM_DETECTOR); 81 } 82 83 public RunnerForTestingThatWillPass(final Class<?> testClass) throws InitializationError { 84 super( 85 testClass, 86 isInstrumented() ? null : ShadowWrangler.getInstance(), 87 isInstrumented() ? null : ClassLoaderForTesting.getInstance(), 88 new RobolectricConfig(resourceFile("TestAndroidManifest.xml"), resourceFile("res"), resourceFile("assets")) 89 ); 90 } 91 92 } 93 94 /** Custom instrument detector. */ 95 private static final InstrumentDetector CUSTOM_DETECTOR = new InstrumentDetector() { 96 @Override 97 public boolean isInstrumented() { 98 final String currentLoader = RunnerForTestingThatWillPass.class.getClassLoader().getClass().getName(); 99 return currentLoader.contains(ClassLoaderForTesting.class.getName()); 100 } 101 }; 102 103 104 // ==================== TEST CLASSES ==================== 105 106 /** 107 * Default behavior test. 108 */ 109 public static class DefaultBehaviorTest { 110 @Test 111 public void fakeTest() { 112 assertThat(true, equalTo(true)); 113 } 114 } 115 116 /** 117 * Test that is launched with a custom test runner and will fail. 118 */ 119 @RunWith(RunnerForTesting.class) 120 public static class TestWithCustomRunner { 121 @Test 122 public void loadedWithCustomClassLoader_usageFlagShoudBeTrue() { 123 assertThat(System.getProperty("robolectric.cusomClassLoader"), equalTo("yes")); 124 } 125 } 126 127 /** 128 * Test that is launched with a custom test runner and will pass. 129 */ 130 @RunWith(RunnerForTestingThatWillPass.class) 131 public static class TestWithCustomRunnerThatWillPass extends TestWithCustomRunner { 132 // nothing here, we just need another annotation 133 } 134 135 136 // ==================== RUN METHODS ==================== 137 138 /** 139 * This is default behavior. 140 */ 141 @Test 142 public void withDefaultDetectorAndDefaultRunner_shouldPass() { 143 final Result result = JUnitCore.runClasses(DefaultBehaviorTest.class); 144 assertThat(result.getRunCount(), equalTo(1)); 145 assertThat(result.getFailureCount(), equalTo(0)); 146 } 147 148 /** 149 * This test simulates wrong instrument detection when custom class loader is used. 150 */ 151 @Test 152 public void wrongInstrumentDetection_shouldRaiseLinkageError() { 153 final Result result = JUnitCore.runClasses(TestWithCustomRunner.class); 154 assertThat(result.getRunCount(), equalTo(1)); 155 assertThat(result.getFailureCount(), equalTo(1)); 156 157 // check whether it's a linkage error 158 final StringWriter buffer = new StringWriter(); 159 result.getFailures().get(0).getException().printStackTrace(new PrintWriter(buffer)); 160 final int linkageErrorNameIndex = buffer.toString().indexOf(LinkageError.class.getName()); 161 assertTrue(linkageErrorNameIndex >= 0); 162 } 163 164 /** 165 * Propper behavior test. 166 */ 167 @Test 168 public void customizeInstumentDetection_shouldPass() { 169 final Result result = JUnitCore.runClasses(TestWithCustomRunnerThatWillPass.class); 170 assertThat(result.getRunCount(), equalTo(1)); 171 assertThat(result.getFailureCount(), equalTo(0)); 172 } 173 174 } 175