1 package android.platform.longevity; 2 3 import android.os.Bundle; 4 import android.platform.longevity.listeners.BatteryTerminator; 5 import android.platform.longevity.listeners.ErrorTerminator; 6 import android.platform.longevity.listeners.TimeoutTerminator; 7 import android.platform.longevity.scheduler.Iterate; 8 import android.platform.longevity.scheduler.Shuffle; 9 import android.support.annotation.VisibleForTesting; 10 import android.support.test.InstrumentationRegistry; 11 12 import java.util.List; 13 import java.util.function.BiFunction; 14 15 import org.junit.runner.Runner; 16 import org.junit.runners.Suite; 17 import org.junit.runners.model.InitializationError; 18 import org.junit.runners.model.RunnerBuilder; 19 import org.junit.runner.notification.RunNotifier; 20 21 /** 22 * Using the {@code LongevitySuite} as a runner allows you to run test sequences repeatedly and with 23 * shuffling in order to simulate longevity conditions and repeated stress or exercise. For examples 24 * look at the bundled sample package. 25 * 26 * TODO(b/62445871): Provide external documentation. 27 */ 28 public final class LongevitySuite<T> extends Suite { 29 private static final String QUITTER_OPTION = "quitter"; 30 private static final String QUITTER_DEFAULT = "false"; // don't quit 31 32 private Bundle mArguments; 33 34 /** 35 * Called reflectively on classes annotated with {@code @RunWith(LongevitySuite.class)} 36 */ LongevitySuite(Class<T> klass, RunnerBuilder builder)37 public LongevitySuite(Class<T> klass, RunnerBuilder builder) throws InitializationError { 38 this(klass, builder, InstrumentationRegistry.getArguments()); 39 } 40 41 /** 42 * Called by tests in order to pass in configurable arguments without affecting the registry. 43 */ 44 @VisibleForTesting LongevitySuite(Class<T> klass, RunnerBuilder builder, Bundle args)45 LongevitySuite(Class<T> klass, RunnerBuilder builder, Bundle args) 46 throws InitializationError { 47 this(klass, constructClassRunners(klass, builder, args), args); 48 } 49 50 /** 51 * Called by this class once the suite class and runners have been determined. 52 */ LongevitySuite(Class<T> klass, List<Runner> runners, Bundle args)53 private LongevitySuite(Class<T> klass, List<Runner> runners, Bundle args) 54 throws InitializationError { 55 super(klass, runners); 56 mArguments = args; 57 } 58 59 /** 60 * Constructs the sequence of {@link Runner}s that produce the full longevity test. 61 */ constructClassRunners( Class<?> suite, RunnerBuilder builder, Bundle args)62 private static List<Runner> constructClassRunners( 63 Class<?> suite, RunnerBuilder builder, Bundle args) throws InitializationError { 64 // Retrieve annotated suite classes. 65 SuiteClasses annotation = suite.getAnnotation(SuiteClasses.class); 66 if (annotation == null) { 67 throw new InitializationError(String.format( 68 "Longevity suite, '%s', must have a SuiteClasses annotation", suite.getName())); 69 } 70 // Construct and store custom runners for the full suite. 71 BiFunction<Bundle, List<Runner>, List<Runner>> modifier = 72 new Iterate().andThen(new Shuffle()); 73 return modifier.apply(args, builder.runners(suite, annotation.value())); 74 } 75 76 @Override run(final RunNotifier notifier)77 public void run(final RunNotifier notifier) { 78 // Add action terminators for custom runner logic. 79 notifier.addListener( 80 new BatteryTerminator(notifier, mArguments, InstrumentationRegistry.getContext())); 81 notifier.addListener( 82 new TimeoutTerminator(notifier, mArguments)); 83 if (Boolean.parseBoolean( 84 mArguments.getString(QUITTER_OPTION, String.valueOf(QUITTER_DEFAULT)))) { 85 notifier.addListener(new ErrorTerminator(notifier)); 86 } 87 // Invoke tests to run through super call. 88 super.run(notifier); 89 } 90 } 91