Home | History | Annotate | Download | only in target
      1 /*
      2  * Copyright (C) 2009 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.annotations.VisibleForTesting;
     20 import java.io.File;
     21 import java.io.IOException;
     22 import java.io.InputStream;
     23 import java.io.PrintStream;
     24 import java.util.ArrayList;
     25 import java.util.Arrays;
     26 import java.util.HashSet;
     27 import java.util.Iterator;
     28 import java.util.List;
     29 import java.util.Properties;
     30 import java.util.Set;
     31 import java.util.concurrent.atomic.AtomicReference;
     32 import javax.annotation.Nullable;
     33 import vogar.Result;
     34 import vogar.TestProperties;
     35 import vogar.monitor.TargetMonitor;
     36 import vogar.target.junit.JUnitRunnerFactory;
     37 
     38 /**
     39  * Runs an action, in process on the target.
     40  */
     41 public final class TestRunner {
     42 
     43     private final String qualifiedName;
     44     private final String qualifiedClassOrPackageName;
     45 
     46     /** the monitor port if a monitor is expected, or null for no monitor */
     47     @VisibleForTesting final Integer monitorPort;
     48 
     49     /** use an atomic reference so the runner can null it out when it is encountered. */
     50     @VisibleForTesting final AtomicReference<String> skipPastReference;
     51     private final int timeoutSeconds;
     52 
     53     private final RunnerFactory runnerFactory;
     54     private final boolean profile;
     55     private final int profileDepth;
     56     private final int profileInterval;
     57     private final File profileFile;
     58     private final boolean profileThreadGroup;
     59     private final String[] args;
     60     private boolean useSocketMonitor;
     61 
     62     public TestRunner(Properties properties, List<String> argsList) {
     63         qualifiedName = properties.getProperty(TestProperties.QUALIFIED_NAME);
     64         qualifiedClassOrPackageName = properties.getProperty(TestProperties.TEST_CLASS_OR_PACKAGE);
     65         timeoutSeconds = Integer.parseInt(properties.getProperty(TestProperties.TIMEOUT));
     66 
     67         int monitorPort = Integer.parseInt(properties.getProperty(TestProperties.MONITOR_PORT));
     68         String skipPast = null;
     69         boolean profile = Boolean.parseBoolean(properties.getProperty(TestProperties.PROFILE));
     70         int profileDepth = Integer.parseInt(properties.getProperty(TestProperties.PROFILE_DEPTH));
     71         int profileInterval
     72                 = Integer.parseInt(properties.getProperty(TestProperties.PROFILE_INTERVAL));
     73         File profileFile = new File(properties.getProperty(TestProperties.PROFILE_FILE));
     74         boolean profileThreadGroup
     75                 = Boolean.parseBoolean(properties.getProperty(TestProperties.PROFILE_THREAD_GROUP));
     76 
     77         for (Iterator<String> i = argsList.iterator(); i.hasNext(); ) {
     78             String arg = i.next();
     79             if (arg.equals("--monitorPort")) {
     80                 i.remove();
     81                 monitorPort = Integer.parseInt(i.next());
     82                 i.remove();
     83             }
     84             if (arg.equals("--skipPast")) {
     85                 i.remove();
     86                 skipPast = i.next();
     87                 i.remove();
     88             }
     89         }
     90 
     91         boolean testOnly = Boolean.parseBoolean(properties.getProperty(TestProperties.TEST_ONLY));
     92         if (testOnly) {
     93             runnerFactory = new CompositeRunnerFactory(new JUnitRunnerFactory());
     94         } else {
     95             runnerFactory = new CompositeRunnerFactory(
     96                     new JUnitRunnerFactory(),
     97                     new CaliperRunnerFactory(argsList),
     98                     new MainRunnerFactory());
     99         }
    100 
    101         this.monitorPort = monitorPort;
    102         this.skipPastReference = new AtomicReference<>(skipPast);
    103         this.profile = profile;
    104         this.profileDepth = profileDepth;
    105         this.profileInterval = profileInterval;
    106         this.profileFile = profileFile;
    107         this.profileThreadGroup = profileThreadGroup;
    108         this.args = argsList.toArray(new String[argsList.size()]);
    109     }
    110 
    111     /**
    112      * Load the properties that were either encapsulated in the APK (if using
    113      * {@link vogar.android.ActivityMode}), or encapsulated in the JAR compiled by Vogar (in other
    114      * modes).
    115      *
    116      * @return The {@link Properties} that were loaded.
    117      */
    118     public static Properties loadProperties() {
    119         try {
    120             InputStream in = getPropertiesStream();
    121             Properties properties = new Properties();
    122             properties.load(in);
    123             in.close();
    124             return properties;
    125         } catch (IOException e) {
    126             throw new RuntimeException(e);
    127         }
    128     }
    129 
    130     /**
    131      * Configure this test runner to await an incoming socket connection when
    132      * writing test results. Otherwise all communication happens over
    133      * System.out.
    134      */
    135     public void useSocketMonitor() {
    136         this.useSocketMonitor = true;
    137     }
    138 
    139     /**
    140      * Attempt to load the test properties file from both the application and system classloader.
    141      * This is necessary because sometimes we run tests from the boot classpath.
    142      */
    143     private static InputStream getPropertiesStream() throws IOException {
    144         for (Class<?> classToLoadFrom : new Class<?>[] { TestRunner.class, Object.class }) {
    145             InputStream propertiesStream = classToLoadFrom.getResourceAsStream(
    146                     "/" + TestProperties.FILE);
    147             if (propertiesStream != null) {
    148                 return propertiesStream;
    149             }
    150         }
    151         throw new IOException(TestProperties.FILE + " missing!");
    152     }
    153 
    154     public void run() throws IOException {
    155         final TargetMonitor monitor = useSocketMonitor
    156                 ? TargetMonitor.await(monitorPort)
    157                 : TargetMonitor.forPrintStream(System.out);
    158 
    159         PrintStream monitorPrintStream = new PrintStreamDecorator(System.out) {
    160             @Override public void print(String str) {
    161                 monitor.output(str != null ? str : "null");
    162             }
    163         };
    164         System.setOut(monitorPrintStream);
    165         System.setErr(monitorPrintStream);
    166 
    167         try {
    168             run(monitor);
    169         } catch (Throwable internalError) {
    170             internalError.printStackTrace(monitorPrintStream);
    171         } finally {
    172             monitor.close();
    173         }
    174     }
    175 
    176     private void run(final TargetMonitor monitor) {
    177         TestEnvironment testEnvironment = new TestEnvironment();
    178         testEnvironment.reset();
    179 
    180         String classOrPackageName;
    181         String qualification;
    182 
    183         // Check whether the class or package is qualified and, if so, strip it off and pass it
    184         // separately to the runners. For instance, may qualify a junit class by appending
    185         // #method_name, where method_name is the name of a single test of the class to run.
    186         int hash_position = qualifiedClassOrPackageName.indexOf("#");
    187         if (hash_position != -1) {
    188             classOrPackageName = qualifiedClassOrPackageName.substring(0, hash_position);
    189             qualification = qualifiedClassOrPackageName.substring(hash_position + 1);
    190         } else {
    191             classOrPackageName = qualifiedClassOrPackageName;
    192             qualification = null;
    193         }
    194 
    195         Set<Class<?>> classes = new ClassFinder().find(classOrPackageName);
    196 
    197         // if there is more than one class in the set, this must be a package. Since we're
    198         // running everything in the package already, remove any class called AllTests.
    199         if (classes.size() > 1) {
    200             Set<Class<?>> toRemove = new HashSet<>();
    201             for (Class<?> klass : classes) {
    202                 if (klass.getName().endsWith(".AllTests")) {
    203                     toRemove.add(klass);
    204                 }
    205             }
    206             classes.removeAll(toRemove);
    207         }
    208 
    209 
    210         Profiler profiler = null;
    211         if (profile) {
    212             try {
    213                 profiler = Profiler.getInstance();
    214             } catch (Exception e) {
    215                 System.out.println("Profiling is disabled: " + e);
    216             }
    217         }
    218         if (profiler != null) {
    219             profiler.setup(profileThreadGroup, profileDepth, profileInterval);
    220         }
    221         for (Class<?> klass : classes) {
    222             Runner runner;
    223             try {
    224                 runner = runnerFactory.newRunner(monitor, qualification, klass,
    225                         skipPastReference, testEnvironment, timeoutSeconds, profile, args);
    226             } catch (RuntimeException e) {
    227                 monitor.outcomeStarted(null, qualifiedName);
    228                 e.printStackTrace();
    229                 monitor.outcomeFinished(Result.ERROR);
    230                 return;
    231             }
    232 
    233             if (runner == null) {
    234                 monitor.outcomeStarted(null, klass.getName());
    235                 System.out.println("Skipping " + klass.getName()
    236                         + ": no associated runner class");
    237                 monitor.outcomeFinished(Result.UNSUPPORTED);
    238                 continue;
    239             }
    240 
    241             boolean completedNormally = runner.run(profiler);
    242             if (!completedNormally) {
    243                 return; // let the caller start another process
    244             }
    245         }
    246         if (profiler != null) {
    247             profiler.shutdown(profileFile);
    248         }
    249 
    250         monitor.completedNormally(true);
    251     }
    252 
    253     public static void main(String[] args) throws IOException {
    254         new TestRunner(loadProperties(), new ArrayList<>(Arrays.asList(args))).run();
    255         System.exit(0);
    256     }
    257 
    258     /**
    259      * A {@link RunnerFactory} that will traverse a list of {@link RunnerFactory} instances to find
    260      * one that can be used to run the code.
    261      */
    262     private static class CompositeRunnerFactory implements RunnerFactory {
    263 
    264         private final List<? extends RunnerFactory> runnerFactories;
    265 
    266         private CompositeRunnerFactory(RunnerFactory... runnerFactories) {
    267             this.runnerFactories = Arrays.asList(runnerFactories);
    268         }
    269 
    270         @Override @Nullable
    271         public Runner newRunner(TargetMonitor monitor, String qualification,
    272                 Class<?> klass, AtomicReference<String> skipPastReference,
    273                 TestEnvironment testEnvironment, int timeoutSeconds, boolean profile, String[] args) {
    274             for (RunnerFactory runnerFactory : runnerFactories) {
    275                 Runner runner = runnerFactory.newRunner(monitor, qualification, klass,
    276                         skipPastReference, testEnvironment, timeoutSeconds, profile, args);
    277                 if (runner != null) {
    278                     return runner;
    279                 }
    280             }
    281 
    282             return null;
    283         }
    284     }
    285 }
    286