Home | History | Annotate | Download | only in target
      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;
     18 
     19 import com.google.common.base.Function;
     20 import com.google.common.base.Functions;
     21 import java.util.List;
     22 import java.util.Properties;
     23 import java.util.concurrent.atomic.AtomicInteger;
     24 import org.junit.After;
     25 import org.junit.Before;
     26 import org.junit.Rule;
     27 import vogar.Result;
     28 import vogar.target.junit.JUnitUtils;
     29 import vogar.testing.InterceptOutputStreams;
     30 import vogar.testing.InterceptOutputStreams.Stream;
     31 
     32 import static org.junit.Assert.assertEquals;
     33 import static org.junit.Assert.assertTrue;
     34 
     35 /**
     36  * Provides support for testing {@link TestRunner} class.
     37  *
     38  * <p>Subclasses provide the individual test methods, each test method has the following structure.
     39  *
     40  * <p>It is annotated with {@link TestRunnerProperties @TestRunnerProperties} that specifies the
     41  * properties that the main Vogar process supplies to the client process that actually runs the
     42  * tests. It must specify either a {@link TestRunnerProperties#testClass()} or
     43  * {@link TestRunnerProperties#testClassOrPackage()}, all the remaining properties are optional.
     44  *
     45  * <p>It calls {@code testRunnerRule.createTestRunner(...)} to create a {@link TestRunner}; passing
     46  * in any additional command line arguments that {@link TestRunner#TestRunner(Properties, List)}
     47  * accepts.
     48  *
     49  * <p>It calls {@link TestRunner#run()} to actually run the tests.
     50  *
     51  * <p>It calls {@link #expectedResults()} to obtain a {@link ExpectedResults} instance that it uses
     52  * to specify the expected results for the test. Once it has specified the expected results then it
     53  * must call either {@link ExpectedResults#completedNormally()} or
     54  * {@link ExpectedResults#aborted()}. They indicate whether the test process completed normally or
     55  * would abort (due to a test timing out) and cause the actual results to be checked against the
     56  * expected results.
     57  */
     58 public abstract class AbstractTestRunnerTest {
     59 
     60     @Rule
     61     public InterceptOutputStreams ios = new InterceptOutputStreams(Stream.OUT);
     62 
     63     @Rule
     64     public TestRunnerRule testRunnerRule = new TestRunnerRule();
     65 
     66     /**
     67      * Keeps track of number of times {@link #expectedResults()} has been called without
     68      * {@link ExpectedResults#checkFilteredOutput(String)}
     69      * also being called. If it is {@code > 0} then the test is in error.
     70      */
     71     private AtomicInteger checkCount;
     72 
     73     @Before
     74     public void beforeTest() {
     75         checkCount = new AtomicInteger();
     76     }
     77 
     78     @After
     79     public void afterTest() {
     80         if (checkCount.get() != 0) {
     81             throw new IllegalStateException("Test called expectedResults() but failed to call"
     82                     + "either aborted() or completedNormally()");
     83         }
     84     }
     85 
     86     protected ExpectedResults expectedResults() {
     87         checkCount.incrementAndGet();
     88         return new ExpectedResults(testRunnerRule.testClass(), ios,
     89                 checkCount);
     90     }
     91 
     92     protected static class ExpectedResults {
     93 
     94         private final StringBuilder builder = new StringBuilder();
     95         private final InterceptOutputStreams ios;
     96         private final AtomicInteger checkCount;
     97         private String testClassName;
     98         private Function<String, String> filter;
     99 
    100         private ExpectedResults(
    101                 Class<?> testClass, InterceptOutputStreams ios, AtomicInteger checkCount) {
    102             this.testClassName = testClass.getName();
    103             this.checkCount = checkCount;
    104             // Automatically strip out methods from a stack trace to avoid making tests dependent
    105             // on either the call hierarchy or on source line numbers which would make the tests
    106             // incredibly fragile. If a test fails then the unfiltered output containing the full
    107             // stack trace will be output so this will not lose information needed to debug errors.
    108             filter = new Function<String, String>() {
    109                 @Override
    110                 public String apply(String input) {
    111                     // Remove stack trace from output.
    112                     return input.replaceAll("\\t(at[^\\n]+|\\.\\.\\. [0-9]+ more)\\n", "");
    113                 }
    114             };
    115             this.ios = ios;
    116         }
    117 
    118         public ExpectedResults text(String message) {
    119             builder.append(message);
    120             return this;
    121         }
    122 
    123         private ExpectedResults addFilter(Function<String, String> function) {
    124             filter = Functions.compose(filter, function);
    125             return this;
    126         }
    127 
    128         public ExpectedResults forTestClass(Class<?> testClass) {
    129             this.testClassName = testClass.getName();
    130             return this;
    131         }
    132 
    133         public ExpectedResults forTestClass(String testClassName) {
    134             this.testClassName = testClassName;
    135             return this;
    136         }
    137 
    138         public ExpectedResults failure(String methodName, String message) {
    139             String output = outcome(testClassName, methodName, message, Result.EXEC_FAILED);
    140             return text(output);
    141         }
    142 
    143         public ExpectedResults success(String methodName) {
    144             String output = outcome(testClassName, methodName, null, Result.SUCCESS);
    145             return text(output);
    146         }
    147 
    148         public ExpectedResults success(String methodName, String message) {
    149             String output = outcome(testClassName, methodName, message, Result.SUCCESS);
    150             return text(output);
    151         }
    152 
    153 
    154         public ExpectedResults unsupported() {
    155             String output = outcome(
    156                     testClassName, null,
    157                     "Skipping " + testClassName + ": no associated runner class\n",
    158                     Result.UNSUPPORTED);
    159             return text(output);
    160         }
    161 
    162         public ExpectedResults noRunner() {
    163             String message =
    164                     String.format("Skipping %s: no associated runner class\n", testClassName);
    165             String output = outcome(testClassName, null, message, Result.UNSUPPORTED);
    166             return text(output);
    167         }
    168 
    169         public void completedNormally() {
    170             text("//00xx{\"completedNormally\":true}\n");
    171             checkFilteredOutput(builder.toString());
    172         }
    173 
    174         public void aborted() {
    175             checkFilteredOutput(builder.toString());
    176         }
    177 
    178 
    179         private static String outcome(
    180                 String testClassName, String methodName, String message, Result result) {
    181             String testName = JUnitUtils.getTestName(testClassName, methodName);
    182 
    183             return String.format("//00xx{\"outcome\":\"%s\"}\n"
    184                             + "%s"
    185                             + "//00xx{\"result\":\"%s\"}\n",
    186                     testName, message == null ? "" : message, result);
    187         }
    188 
    189         private void checkFilteredOutput(String expected) {
    190             checkCount.decrementAndGet();
    191             String output = ios.contents(Stream.OUT);
    192             String filtered = filter.apply(output);
    193             if (!expected.equals(filtered)) {
    194                 assertEquals(expected, output);
    195             }
    196         }
    197     }
    198 }
    199