blob: aa88961db37731f979a02de7e220393ce09a202a [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.longevity.platform;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.longevity.platform.listener.BatteryTerminator;
import android.longevity.platform.listener.ErrorTerminator;
import android.longevity.platform.listener.TimeoutTerminator;
import android.os.BatteryManager;
import android.os.Bundle;
import android.platform.test.composer.Iterate;
import android.platform.test.composer.Shuffle;
import android.platform.test.composer.Profile;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.test.InstrumentationRegistry;
import java.util.function.BiFunction;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
/**
* {@inheritDoc}
*
* This class is used for constructing longevity suites that run on an Android device.
*/
public final class LongevitySuite extends android.longevity.core.LongevitySuite {
private static final String LOG_TAG = LongevitySuite.class.getSimpleName();
private Instrumentation mInstrumentation;
private Context mContext;
/**
* Takes a {@link Bundle} and maps all String K/V pairs into a {@link Map<String, String>}.
*
* @param bundle the input arguments to return in a {@link Map}
* @return Map<String, String> all String-to-String key, value pairs in the {@link Bundle}
*/
private static final Map<String, String> toMap(Bundle bundle) {
Map<String, String> result = new HashMap<>();
for (String key : bundle.keySet()) {
if (!bundle.containsKey(key)) {
Log.w(LOG_TAG, String.format("Couldn't find value for option: %s", key));
} else {
// Arguments are assumed String <-> String
result.put(key, bundle.getString(key));
}
}
return result;
}
/**
* Called reflectively on classes annotated with {@code @RunWith(LongevitySuite.class)}
*/
public LongevitySuite(Class<?> klass, RunnerBuilder builder)
throws InitializationError {
this(klass, builder, InstrumentationRegistry.getInstrumentation(),
InstrumentationRegistry.getContext(), InstrumentationRegistry.getArguments());
}
/**
* Used to pass in mock-able Android features for testing.
*/
@VisibleForTesting
public LongevitySuite(Class<?> klass, RunnerBuilder builder,
Instrumentation instrumentation, Context context, Bundle arguments)
throws InitializationError {
super(klass, constructClassRunners(klass, builder, arguments), toMap(arguments));
mInstrumentation = instrumentation;
mContext = context;
}
/**
* Constructs the sequence of {@link Runner}s using platform composers.
*/
private static List<Runner> constructClassRunners(
Class<?> suite, RunnerBuilder builder, Bundle args)
throws InitializationError {
// TODO(b/118340229): Refactor to share logic with base class. In the meanwhile, keep the
// logic here in sync with the base class.
// Retrieve annotated suite classes.
SuiteClasses annotation = suite.getAnnotation(SuiteClasses.class);
if (annotation == null) {
throw new InitializationError(String.format(
"Longevity suite, '%s', must have a SuiteClasses annotation", suite.getName()));
}
// Construct and store custom runners for the full suite.
BiFunction<Bundle, List<Runner>, List<Runner>> modifier =
new Iterate<Runner>().andThen(new Shuffle<Runner>()).andThen(new Profile());
return modifier.apply(args, builder.runners(suite, annotation.value()));
}
@Override
public void run(final RunNotifier notifier) {
// Register the battery terminator available only on the platform library, if present.
if (hasBattery()) {
notifier.addListener(new BatteryTerminator(notifier, mArguments, mContext));
}
// Register other listeners and continue with standard longevity run.
super.run(notifier);
}
/**
* Returns the platform-specific {@link TimeoutTerminator} for Android devices.
*/
@Override
public android.longevity.core.listener.ErrorTerminator getErrorTerminator(
final RunNotifier notifier) {
return new ErrorTerminator(notifier);
}
/**
* Returns the platform-specific {@link TimeoutTerminator} for Android devices.
*/
@Override
public android.longevity.core.listener.TimeoutTerminator getTimeoutTerminator(
final RunNotifier notifier) {
return new TimeoutTerminator(notifier, mArguments);
}
/**
* Determines if the device has a battery attached.
*/
private boolean hasBattery () {
final Intent batteryInfo =
mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
return batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
}
}