Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright (C) 2017 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 package android.longevity.core;
     17 
     18 import android.longevity.core.listener.ErrorTerminator;
     19 import android.longevity.core.listener.TimeoutTerminator;
     20 import android.longevity.core.scheduler.Iterate;
     21 import android.longevity.core.scheduler.Shuffle;
     22 
     23 import org.junit.runner.Runner;
     24 import org.junit.runner.notification.RunNotifier;
     25 import org.junit.runners.Suite;
     26 import org.junit.runners.model.InitializationError;
     27 import org.junit.runners.model.RunnerBuilder;
     28 
     29 import java.util.List;
     30 import java.util.Map;
     31 import java.util.function.BiFunction;
     32 import java.util.stream.Collectors;
     33 
     34 /**
     35  * Using the {@code LongevitySuite} as a runner allows you to run test sequences repeatedly and with
     36  * shuffling in order to simulate longevity conditions and repeated stress or exercise. For examples
     37  * look at the bundled samples package.
     38  */
     39 public class LongevitySuite extends Suite {
     40     private static final String QUITTER_OPTION = "quitter";
     41     private static final boolean QUITTER_DEFAULT = false; // don't quit
     42 
     43     protected Map<String, String> mArguments;
     44 
     45     /**
     46      * Called reflectively on classes annotated with {@code @RunWith(LongevitySuite.class)}
     47      */
     48     public LongevitySuite(Class<?> klass, RunnerBuilder builder)
     49             throws InitializationError {
     50         this(klass, builder,
     51             System.getProperties().entrySet()
     52                 .stream()
     53                 .collect(Collectors.toMap(
     54                         i -> String.valueOf(i.getKey()),
     55                         i -> String.valueOf(i.getValue()))));
     56     }
     57 
     58     /**
     59      * Called explicitly to pass in configurable arguments without affecting expected formats.
     60      */
     61     public LongevitySuite(Class<?> klass, RunnerBuilder builder, Map<String, String> args)
     62             throws InitializationError {
     63         this(klass, constructClassRunners(klass, builder, args), args);
     64     }
     65 
     66     /**
     67      * Called by this class once the suite class and runners have been determined.
     68      */
     69     protected LongevitySuite(Class<?> klass, List<Runner> runners, Map<String, String> args)
     70             throws InitializationError {
     71         super(klass, runners);
     72         mArguments = args;
     73     }
     74 
     75     /**
     76      * Constructs the sequence of {@link Runner}s that produce the full longevity test.
     77      */
     78     private static List<Runner> constructClassRunners(
     79                 Class<?> suite, RunnerBuilder builder, Map<String, String> args)
     80             throws InitializationError {
     81         // Retrieve annotated suite classes.
     82         SuiteClasses annotation = suite.getAnnotation(SuiteClasses.class);
     83         if (annotation == null) {
     84             throw new InitializationError(String.format(
     85                     "Longevity suite, '%s', must have a SuiteClasses annotation", suite.getName()));
     86         }
     87         // Construct and store custom runners for the full suite.
     88         BiFunction<Map<String, String>, List<Runner>, List<Runner>> modifier =
     89                 new Iterate().andThen(new Shuffle());
     90         return modifier.apply(args, builder.runners(suite, annotation.value()));
     91     }
     92 
     93     @Override
     94     public void run(final RunNotifier notifier) {
     95         // Add action terminators for custom runner logic.
     96         if (mArguments.containsKey(QUITTER_OPTION) ?
     97                 Boolean.parseBoolean(mArguments.get(QUITTER_OPTION)) : QUITTER_DEFAULT) {
     98             notifier.addListener(getErrorTerminator(notifier));
     99         }
    100         notifier.addListener(getTimeoutTerminator(notifier));
    101         // Invoke tests to run through super call.
    102         super.run(notifier);
    103     }
    104 
    105     /**
    106      * Returns the {@link ErrorTerminator} to register with the {@link RunNotifier}.
    107      * <p>
    108      * Note: exposed for overriding with a platform-specific {@link ErrorTerminator}.
    109      */
    110     public ErrorTerminator getErrorTerminator(final RunNotifier notifier) {
    111         return new ErrorTerminator(notifier);
    112     }
    113 
    114     /**
    115      * Returns the {@link TimeoutTerminator} to register with the {@link RunNotifier}.
    116      * <p>
    117      * Note: exposed for overriding with a platform-specific {@link TimeoutTerminator}.
    118      */
    119     public TimeoutTerminator getTimeoutTerminator(final RunNotifier notifier) {
    120         return new TimeoutTerminator(notifier, mArguments);
    121     }
    122 
    123     /**
    124      * Returns the {@link List} of {@link Runner}s children for explicit modification by another
    125      * class.
    126      * <p>
    127      * Note: using this method is highly discouraged unless explicitly needed.
    128      */
    129     public List<Runner> getRunners() {
    130         return getChildren();
    131     }
    132 }
    133