1 /* 2 * Copyright (C) 2016 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 vogar.target.junit.junit3; 18 19 import java.lang.annotation.Annotation; 20 import java.lang.reflect.Constructor; 21 import java.lang.reflect.InvocationTargetException; 22 import java.util.List; 23 import junit.framework.Test; 24 import junit.framework.TestCase; 25 import junit.framework.TestResult; 26 import junit.framework.TestSuite; 27 import org.junit.internal.runners.JUnit38ClassRunner; 28 import org.junit.runner.Description; 29 import org.junit.runner.Runner; 30 import org.junit.runners.model.InitializationError; 31 import vogar.target.junit.DescribableStatement; 32 import vogar.target.junit.ErrorRunner; 33 import vogar.target.junit.ParentStatementRunner; 34 import vogar.target.junit.RunnerParams; 35 36 /** 37 * Constructs a {@link ParentStatementRunner} from a {@link TestCase} derived class. 38 * 39 * <p>This does a similar job to the {@link JUnit38ClassRunner} when it is constructed with a 40 * {@link Class Class<? extends TestCase>} but takes a very different approach. 41 * 42 * <p>The JU38CR takes a {@link Test}, runs it using the standard JUnit3 infrastructure and 43 * adapts the JUnit3 events ({@link TestResult}) to the JUnit4 event model. If it is given a Class 44 * then it will first turn it into a {@link TestSuite} which is a {@link Test} and then process it 45 * as normal. 46 * 47 * <p>When this is given a Class it converts it directly into a {@link ParentStatementRunner} where 48 * each child {@link DescribableStatement} simply runs the {@link TestCase#runBare()} method. That 49 * may cause a slight difference in behavior if the class overrides 50 * {@link TestCase#run(TestResult)} as that method is never called but if that does become a 51 * problem then it can be detected and fallback to treating it as a {@link Test} just as is done 52 * by {@link TestSuiteRunnerFactory}. 53 * 54 * <p>The JU38CR is also used when running the result from a {@code public static Test suite()} 55 * method. That functionality is provided by the {@link TestSuiteRunnerFactory}. 56 * 57 * <p>The advantage of converting JUnit3 style tests into JUnit4 structures is that it makes it 58 * easier to treat them all consistently, apply test rules, etc. 59 */ 60 public class TestCaseRunnerFactory implements TestCaseFactory<Runner, DescribableStatement> { 61 62 private final RunnerParams runnerParams; 63 64 public TestCaseRunnerFactory(RunnerParams runnerParams) { 65 this.runnerParams = runnerParams; 66 } 67 68 @Override 69 public boolean eagerClassValidation() { 70 return false; 71 } 72 73 @Override 74 public DescribableStatement createTest( 75 Class<? extends TestCase> testClass, String methodName, Annotation[] annotations) { 76 return new RunTestCaseStatement(testClass, methodName, annotations); 77 } 78 79 @Override 80 public DescribableStatement createFailingTest( 81 Class<? extends Test> testClass, String name, Throwable throwable) { 82 Description description = Description.createTestDescription( 83 testClass, name, testClass.getAnnotations()); 84 return new ThrowingStatement(description, throwable); 85 } 86 87 @Override 88 public Runner createSuite( 89 Class<? extends TestCase> testClass, List<DescribableStatement> tests) { 90 91 try { 92 return new ParentStatementRunner(testClass, tests, runnerParams); 93 } catch (InitializationError e) { 94 Description description = Description.createTestDescription( 95 testClass, "initializationError", testClass.getAnnotations()); 96 return new ErrorRunner(description, e); 97 } 98 } 99 100 /** 101 * Create the test case. 102 * 103 * <p>Called immediately prior to running the test and the returned object is discarded 104 * immediately after the test was run. 105 */ 106 private static TestCase createTestCase(Class<? extends TestCase> testClass, String name) 107 throws Throwable { 108 Constructor<? extends TestCase> constructor; 109 try { 110 constructor = testClass.getConstructor(String.class); 111 } catch (NoSuchMethodException ignored) { 112 constructor = testClass.getConstructor(); 113 } 114 115 TestCase test; 116 try { 117 if (constructor.getParameterTypes().length == 0) { 118 test = constructor.newInstance(); 119 test.setName(name); 120 } else { 121 test = constructor.newInstance(name); 122 } 123 } catch (InvocationTargetException e) { 124 throw e.getCause(); 125 } 126 127 return test; 128 } 129 130 /** 131 * Runs a method in a {@link TestCase}. 132 */ 133 private static class RunTestCaseStatement extends DescribableStatement { 134 private final Class<? extends TestCase> testClass; 135 private final String name; 136 137 public RunTestCaseStatement( 138 Class<? extends TestCase> testClass, String name, Annotation[] annotations) { 139 super(Description.createTestDescription(testClass, name, annotations)); 140 this.testClass = testClass; 141 this.name = name; 142 } 143 144 @Override 145 public void evaluate() throws Throwable { 146 // Validate the class just before running the test. 147 TestCaseTransformer.validateTestClass(testClass); 148 TestCase test = createTestCase(testClass, name); 149 test.runBare(); 150 } 151 } 152 153 /** 154 * Throws the supplied {@link Throwable}. 155 */ 156 private static class ThrowingStatement extends DescribableStatement { 157 private final Throwable throwable; 158 159 public ThrowingStatement(Description description, Throwable throwable) { 160 super(description); 161 this.throwable = throwable; 162 } 163 164 @Override 165 public void evaluate() throws Throwable { 166 throw throwable; 167 } 168 } 169 } 170