upload android base code part4

This commit is contained in:
August 2018-08-08 17:00:29 +08:00
parent b9e30e05b1
commit 78ea2404cd
23455 changed files with 5250148 additions and 0 deletions

View file

@ -0,0 +1,19 @@
# Copyright (C) 2009 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.
LOCAL_PATH:= $(call my-dir)
BUILD_CTSCORE_PACKAGE:=$(LOCAL_PATH)/ctscore.mk
include $(call all-makefiles-under,$(LOCAL_PATH))

View file

@ -0,0 +1,35 @@
# Copyright (C) 2009 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.
LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle conscrypt
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_DEX_PREOPT := false
# don't include these packages in any target
LOCAL_MODULE_TAGS := optional
# and when installed explicitly put them in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
# Don't delete META-INF from the core-tests jar
LOCAL_DONT_DELETE_JAR_META_INF := true
# TODO: Clean up this mess. (b/26483949). libnativehelper_compat_libc++ pulls in its own
# static copy of libc++ and the libc++ we're bundling here is the platform libc++. This is
# bround to break but is being submitted as a workaround for failing CTS tests.
LOCAL_JNI_SHARED_LIBRARIES := libjavacoretests libsqlite_jni libnativehelper_compat_libc++ libc++
# Include both the 32 and 64 bit versions of libjavacoretests,
# where applicable.
LOCAL_MULTILIB := both
include $(BUILD_PACKAGE)

View file

@ -0,0 +1,65 @@
# Copyright (C) 2009 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.
LOCAL_PATH:= $(call my-dir)
ifeq ($(BUILD_CTSCORE_PACKAGE),)
$(error BUILD_CTSCORE_PACKAGE must be defined)
endif
include $(CLEAR_VARS)
# include this package in the tests target for continuous testing
LOCAL_MODULE_TAGS := tests
LOCAL_PACKAGE_NAME := android.core.tests.runner
LOCAL_STATIC_JAVA_LIBRARIES := cts-test-runner
include $(BUILD_CTSCORE_PACKAGE)
#==========================================================
# Build the core runner.
#==========================================================
# Build library
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_MODULE := cts-core-test-runner
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
android-support-test \
vogarexpect \
testng
LOCAL_JAVA_LIBRARIES := android.test.runner
include $(BUILD_STATIC_JAVA_LIBRARY)
#==========================================================
# Build the run listener
#==========================================================
# Build library
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under,src/com/android/cts/runner)
LOCAL_MODULE := cts-test-runner
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_SDK_VERSION := current
include $(BUILD_STATIC_JAVA_LIBRARY)

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (C) 2007 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.core.tests.runner">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
android:label="cts framework tests"/>
</manifest>

View file

@ -0,0 +1,258 @@
/*
* Copyright (C) 2016 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.
*/
/**
* This file is a copy of https://cs.corp.google.com/android/frameworks/testing/runner/src/main/java/android/support/test/internal/runner/TestLoader.java
* The only changes that have been made starts with // Libcore-specific
*/
package com.android.cts.core.internal.runner;
import android.util.Log;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A class for loading JUnit3 and JUnit4 test classes given a set of potential class names.
*/
public final class TestLoader {
private static final String LOG_TAG = "TestLoader";
// Libcore-specific change: Fully qualified name of TestNG annotation class.
private static final String TESTNG_TEST = "org.testng.annotations.Test";
private Map<String, Class<?>> mLoadedClassesMap = new LinkedHashMap<String, Class<?>>();
private Map<String, Failure> mLoadFailuresMap = new LinkedHashMap<String, Failure>();
private ClassLoader mClassLoader;
/**
* Set the {@link ClassLoader} to be used to load test cases.
*
* @param loader {@link ClassLoader} to load test cases with.
*/
public void setClassLoader(ClassLoader loader) {
mClassLoader = loader;
}
/**
* Loads the test class from a given class name if its not already loaded.
* <p/>
* Will store the result internally. Successfully loaded classes can be retrieved via
* {@link #getLoadedClasses()}, failures via {@link #getLoadFailures()}.
*
* @param className the class name to attempt to load
* @return the loaded class or null.
*/
public Class<?> loadClass(String className) {
Class<?> loadedClass = doLoadClass(className);
if (loadedClass != null) {
mLoadedClassesMap.put(className, loadedClass);
}
return loadedClass;
}
protected ClassLoader getClassLoader() {
if (mClassLoader != null) {
return mClassLoader;
}
// TODO: InstrumentationTestRunner uses
// Class.forName(className, false, getTargetContext().getClassLoader());
// Evaluate if that is needed. Initial testing indicates
// getTargetContext().getClassLoader() == this.getClass().getClassLoader()
return this.getClass().getClassLoader();
}
private Class<?> doLoadClass(String className) {
if (mLoadFailuresMap.containsKey(className)) {
// Don't load classes that already failed to load
return null;
} else if (mLoadedClassesMap.containsKey(className)) {
// Class with the same name was already loaded, return it
return mLoadedClassesMap.get(className);
}
try {
ClassLoader myClassLoader = getClassLoader();
return Class.forName(className, false, myClassLoader);
} catch (ClassNotFoundException e) {
String errMsg = String.format("Could not find class: %s", className);
Log.e(LOG_TAG, errMsg);
Description description = Description.createSuiteDescription(className);
Failure failure = new Failure(description, e);
mLoadFailuresMap.put(className, failure);
}
return null;
}
/**
* Loads the test class from the given class name.
* <p/>
* Similar to {@link #loadClass(String)}, but will ignore classes that are
* not tests.
*
* @param className the class name to attempt to load
* @return the loaded class or null.
*/
public Class<?> loadIfTest(String className) {
Class<?> loadedClass = doLoadClass(className);
if (loadedClass != null && isTestClass(loadedClass)) {
mLoadedClassesMap.put(className, loadedClass);
return loadedClass;
}
return null;
}
/**
* @return whether this {@link TestLoader} contains any loaded classes or load failures.
*/
public boolean isEmpty() {
return mLoadedClassesMap.isEmpty() && mLoadFailuresMap.isEmpty();
}
/**
* Get the {@link Collection) of classes successfully loaded via
* {@link #loadIfTest(String)} calls.
*/
public Collection<Class<?>> getLoadedClasses() {
return mLoadedClassesMap.values();
}
/**
* Get the {@link List) of {@link Failure} that occurred during
* {@link #loadIfTest(String)} calls.
*/
public Collection<Failure> getLoadFailures() {
return mLoadFailuresMap.values();
}
/**
* Determines if given class is a valid test class.
*
* @param loadedClass
* @return <code>true</code> if loadedClass is a test
*/
private boolean isTestClass(Class<?> loadedClass) {
try {
if (Modifier.isAbstract(loadedClass.getModifiers())) {
logDebug(String.format("Skipping abstract class %s: not a test",
loadedClass.getName()));
return false;
}
// Libcore-specific change: Also consider TestNG annotated classes.
if (isTestNgTestClass(loadedClass)) {
return true;
}
// TODO: try to find upstream junit calls to replace these checks
if (junit.framework.Test.class.isAssignableFrom(loadedClass)) {
// ensure that if a TestCase, it has at least one test method otherwise
// TestSuite will throw error
if (junit.framework.TestCase.class.isAssignableFrom(loadedClass)) {
return hasJUnit3TestMethod(loadedClass);
}
return true;
}
// TODO: look for a 'suite' method?
if (loadedClass.isAnnotationPresent(org.junit.runner.RunWith.class)) {
return true;
}
for (Method testMethod : loadedClass.getMethods()) {
if (testMethod.isAnnotationPresent(org.junit.Test.class)) {
return true;
}
}
logDebug(String.format("Skipping class %s: not a test", loadedClass.getName()));
return false;
} catch (Exception e) {
// Defensively catch exceptions - Will throw runtime exception if it cannot load methods.
// For earlier versions of Android (Pre-ICS), Dalvik might try to initialize a class
// during getMethods(), fail to do so, hide the error and throw a NoSuchMethodException.
// Since the java.lang.Class.getMethods does not declare such an exception, resort to a
// generic catch all.
// For ICS+, Dalvik will throw a NoClassDefFoundException.
Log.w(LOG_TAG, String.format("%s in isTestClass for %s", e.toString(),
loadedClass.getName()));
return false;
} catch (Error e) {
// defensively catch Errors too
Log.w(LOG_TAG, String.format("%s in isTestClass for %s", e.toString(),
loadedClass.getName()));
return false;
}
}
private boolean hasJUnit3TestMethod(Class<?> loadedClass) {
for (Method testMethod : loadedClass.getMethods()) {
if (isPublicTestMethod(testMethod)) {
return true;
}
}
return false;
}
// copied from junit.framework.TestSuite
private boolean isPublicTestMethod(Method m) {
return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
}
// copied from junit.framework.TestSuite
private boolean isTestMethod(Method m) {
return m.getParameterTypes().length == 0 && m.getName().startsWith("test")
&& m.getReturnType().equals(Void.TYPE);
}
// Libcore-specific change: Add method for checking TestNG-annotated classes.
private static boolean isTestNgTestClass(Class<?> cls) {
// TestNG test is either marked @Test at the class
for (Annotation a : cls.getAnnotations()) {
if (a.annotationType().getName().equals(TESTNG_TEST)) {
return true;
}
}
// Or It's marked @Test at the method level
for (Method m : cls.getDeclaredMethods()) {
for (Annotation a : m.getAnnotations()) {
if (a.annotationType().getName().equals(TESTNG_TEST)) {
return true;
}
}
}
return false;
}
/**
* Utility method for logging debug messages. Only actually logs a message if LOG_TAG is marked
* as loggable to limit log spam during normal use.
*/
private void logDebug(String msg) {
if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
Log.d(LOG_TAG, msg);
}
}
}

View file

@ -0,0 +1,125 @@
/*
* Copyright (C) 2016 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 com.android.cts.core.runner;
import android.support.test.runner.AndroidJUnitRunner;
/**
* Constants used to communicate to and from {@link AndroidJUnitRunner}.
*/
public interface AndroidJUnitRunnerConstants {
/**
* The name of the file containing the names of the tests to run.
*
* <p>This is an internal constant used within
* {@code android.support.test.internal.runner.RunnerArgs}, which is used on both the server
* and
* client side. The constant is used when there are too many test names to pass on the command
* line, in which case they are stored in a file that is pushed to the device and then the
* location of that file is passed in this argument. The {@code RunnerArgs} on the client will
* read the contents of that file in order to retrieve the list of names and then return that
* to
* its client without the client being aware of how that was done.
*/
String ARGUMENT_TEST_FILE = "testFile";
/**
* The name of the file containing the names of the tests not to run.
*
* <p>This is an internal constant used within
* {@code android.support.test.internal.runner.RunnerArgs}, which is used on both the server
* and
* client side. The constant is used when there are too many test names to pass on the command
* line, in which case they are stored in a file that is pushed to the device and then the
* location of that file is passed in this argument. The {@code RunnerArgs} on the client will
* read the contents of that file in order to retrieve the list of names and then return that
* to
* its client without the client being aware of how that was done.
*/
String ARGUMENT_NOT_TEST_FILE = "notTestFile";
/**
* A comma separated list of the names of test classes to run.
*
* <p>The equivalent constant in {@code InstrumentationTestRunner} is hidden and so not
* available
* through the public API.
*/
String ARGUMENT_TEST_CLASS = "class";
/**
* A comma separated list of the names of test classes not to run
*/
String ARGUMENT_NOT_TEST_CLASS = "notClass";
/**
* A comma separated list of the names of test packages to run.
*
* <p>The equivalent constant in {@code InstrumentationTestRunner} is hidden and so not
* available
* through the public API.
*/
String ARGUMENT_TEST_PACKAGE = "package";
/**
* A comma separated list of the names of test packages not to run.
*/
String ARGUMENT_NOT_TEST_PACKAGE = "notPackage";
/**
* Log the results as if the tests were executed but don't actually run the tests.
*
* <p>The equivalent constant in {@code InstrumentationTestRunner} is private.
*/
String ARGUMENT_LOG_ONLY = "log";
/**
* Wait for the debugger before starting.
*
* <p>There is no equivalent constant in {@code InstrumentationTestRunner} but the string is
* used
* within that class.
*/
String ARGUMENT_DEBUG = "debug";
/**
* Only count the number of tests to run.
*
* <p>There is no equivalent constant in {@code InstrumentationTestRunner} but the string is
* used
* within that class.
*/
String ARGUMENT_COUNT = "count";
/**
* The per test timeout value.
*/
String ARGUMENT_TIMEOUT = "timeout_msec";
/**
* Token representing how long (in seconds) the current test took to execute.
*
* <p>The equivalent constant in {@code InstrumentationTestRunner} is private.
*/
String REPORT_KEY_RUNTIME = "runtime";
/**
* An identifier for tests run using this class.
*/
String REPORT_VALUE_ID = "CoreTestRunner";
}

View file

@ -0,0 +1,355 @@
/*
* Copyright (C) 2015 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 com.android.cts.core.runner;
import android.app.Activity;
import android.app.Instrumentation;
import android.os.Bundle;
import android.os.Debug;
import android.support.test.internal.runner.listener.InstrumentationResultPrinter;
import android.support.test.internal.runner.listener.InstrumentationRunListener;
import android.support.test.internal.util.AndroidRunnerParams;
import android.util.Log;
import com.android.cts.core.runner.support.ExtendedAndroidRunnerBuilder;
import com.google.common.base.Splitter;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.runner.Computer;
import org.junit.runner.JUnitCore;
import org.junit.runner.Request;
import org.junit.runner.Result;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.Filterable;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.notification.RunListener;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_COUNT;
import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_DEBUG;
import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_LOG_ONLY;
import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_CLASS;
import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_FILE;
import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_PACKAGE;
import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_CLASS;
import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_FILE;
import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_PACKAGE;
import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TIMEOUT;
/**
* A drop-in replacement for AndroidJUnitTestRunner, which understands the same arguments, and has
* similar functionality, but can filter by expectations and allows a custom runner-builder to be
* provided.
*/
public class CoreTestRunner extends Instrumentation {
static final String TAG = "LibcoreTestRunner";
private static final java.lang.String ARGUMENT_ROOT_CLASSES = "core-root-classes";
private static final String ARGUMENT_CORE_LISTENER = "core-listener";
private static final Splitter CLASS_LIST_SPLITTER = Splitter.on(',').trimResults();
/** The args for the runner. */
private Bundle args;
/** Only log the number and names of tests, and not run them. */
private boolean logOnly;
/** The amount of time in millis to wait for a single test to complete. */
private long testTimeout;
/**
* The list of tests to run.
*/
private TestList testList;
/**
* The list of {@link RunListener} classes to create.
*/
private List<Class<? extends RunListener>> listenerClasses;
private Filter expectationFilter;
@Override
public void onCreate(final Bundle args) {
super.onCreate(args);
this.args = args;
boolean debug = "true".equalsIgnoreCase(args.getString(ARGUMENT_DEBUG));
if (debug) {
Log.i(TAG, "Waiting for debugger to connect...");
Debug.waitForDebugger();
Log.i(TAG, "Debugger connected.");
}
// Log the message only after getting a value from the args so that the args are
// unparceled.
Log.d(TAG, "In OnCreate: " + args);
// Treat logOnly and count as the same. This is not quite true as count should only send
// the host the number of tests but logOnly should send the name and number. However,
// this is how this has always behaved and it does not appear to have caused any problems.
// Changing it seems unnecessary given that count is CTSv1 only and CTSv1 will be removed
// soon now that CTSv2 is ready.
boolean testCountOnly = args.getBoolean(ARGUMENT_COUNT);
this.logOnly = "true".equalsIgnoreCase(args.getString(ARGUMENT_LOG_ONLY)) || testCountOnly;
this.testTimeout = parseUnsignedLong(args.getString(ARGUMENT_TIMEOUT), ARGUMENT_TIMEOUT);
expectationFilter = new ExpectationBasedFilter(args);
// The test can be run specifying a list of tests to run, or as cts-tradefed does it,
// by passing a fileName with a test to run on each line.
Set<String> testNameSet = new HashSet<>();
String arg;
if ((arg = args.getString(ARGUMENT_TEST_FILE)) != null) {
// The tests are specified in a file.
try {
testNameSet.addAll(readTestsFromFile(arg));
} catch (IOException err) {
finish(Activity.RESULT_CANCELED, new Bundle());
return;
}
} else if ((arg = args.getString(ARGUMENT_TEST_CLASS)) != null) {
// The tests are specified in a String passed in the bundle.
String[] tests = arg.split(",");
testNameSet.addAll(Arrays.asList(tests));
}
// Tests may be excluded from the run by passing a list of tests not to run,
// or by passing a fileName with a test not to run on each line.
Set<String> notTestNameSet = new HashSet<>();
if ((arg = args.getString(ARGUMENT_NOT_TEST_FILE)) != null) {
// The tests are specified in a file.
try {
notTestNameSet.addAll(readTestsFromFile(arg));
} catch (IOException err) {
finish(Activity.RESULT_CANCELED, new Bundle());
return;
}
} else if ((arg = args.getString(ARGUMENT_NOT_TEST_CLASS)) != null) {
// The classes are specified in a String passed in the bundle
String[] tests = arg.split(",");
notTestNameSet.addAll(Arrays.asList(tests));
}
Set<String> packageNameSet = new HashSet<>();
if ((arg = args.getString(ARGUMENT_TEST_PACKAGE)) != null) {
// The packages are specified in a String passed in the bundle
String[] packages = arg.split(",");
packageNameSet.addAll(Arrays.asList(packages));
}
Set<String> notPackageNameSet = new HashSet<>();
if ((arg = args.getString(ARGUMENT_NOT_TEST_PACKAGE)) != null) {
// The packages are specified in a String passed in the bundle
String[] packages = arg.split(",");
notPackageNameSet.addAll(Arrays.asList(packages));
}
List<String> roots = getRootClassNames(args);
if (roots == null) {
// Find all test classes
Collection<Class<?>> classes = TestClassFinder.getClasses(
Collections.singletonList(getContext().getPackageCodePath()),
getClass().getClassLoader());
testList = new TestList(classes);
} else {
testList = TestList.rootList(roots);
}
testList.addIncludeTestPackages(packageNameSet);
testList.addExcludeTestPackages(notPackageNameSet);
testList.addIncludeTests(testNameSet);
testList.addExcludeTests(notTestNameSet);
listenerClasses = new ArrayList<>();
String listenerArg = args.getString(ARGUMENT_CORE_LISTENER);
if (listenerArg != null) {
List<String> listenerClassNames = CLASS_LIST_SPLITTER.splitToList(listenerArg);
for (String listenerClassName : listenerClassNames) {
try {
Class<? extends RunListener> listenerClass = Class.forName(listenerClassName)
.asSubclass(RunListener.class);
listenerClasses.add(listenerClass);
} catch (ClassNotFoundException e) {
Log.e(TAG, "Could not load listener class: " + listenerClassName, e);
}
}
}
start();
}
private List<String> getRootClassNames(Bundle args) {
String rootClasses = args.getString(ARGUMENT_ROOT_CLASSES);
List<String> roots;
if (rootClasses == null) {
roots = null;
} else {
roots = CLASS_LIST_SPLITTER.splitToList(rootClasses);
}
return roots;
}
@Override
public void onStart() {
if (logOnly) {
Log.d(TAG, "Counting/logging tests only");
} else {
Log.d(TAG, "Running tests");
}
AndroidRunnerParams runnerParams = new AndroidRunnerParams(this, args,
false, testTimeout, false /*ignoreSuiteMethods*/);
Runner runner;
try {
RunnerBuilder runnerBuilder = new ExtendedAndroidRunnerBuilder(runnerParams);
Class[] classes = testList.getClassesToRun();
for (Class cls : classes) {
Log.d(TAG, "Found class to run: " + cls.getName());
}
runner = new Computer().getSuite(runnerBuilder, classes);
if (runner instanceof Filterable) {
Log.d(TAG, "Applying filters");
Filterable filterable = (Filterable) runner;
// Filter out all the tests that are expected to fail.
try {
filterable.filter(expectationFilter);
} catch (NoTestsRemainException e) {
// Sometimes filtering will remove all tests but we do not care about that.
}
Log.d(TAG, "Applied filters");
}
// If the tests are only supposed to be logged and not actually run then replace the
// runner with a runner that will fire notifications for all the tests that would have
// been run. This is needed because CTSv2 does a log only run through a CTS module in
// order to generate a list of tests that will be run so that it can monitor them.
// Encapsulating that in a Runner implementation makes it easier to leverage the
// existing code for running tests.
if (logOnly) {
runner = new DescriptionHierarchyNotifier(runner.getDescription());
}
} catch (InitializationError e) {
throw new RuntimeException("Could not create a suite", e);
}
InstrumentationResultPrinter instrumentationResultPrinter =
new InstrumentationResultPrinter();
instrumentationResultPrinter.setInstrumentation(this);
JUnitCore core = new JUnitCore();
core.addListener(instrumentationResultPrinter);
// If not logging the list of tests then add any additional configured listeners. These
// must be added before firing any events.
if (!logOnly) {
// Add additional configured listeners.
for (Class<? extends RunListener> listenerClass : listenerClasses) {
try {
RunListener runListener = listenerClass.newInstance();
if (runListener instanceof InstrumentationRunListener) {
((InstrumentationRunListener) runListener).setInstrumentation(this);
}
core.addListener(runListener);
} catch (InstantiationException | IllegalAccessException e) {
Log.e(TAG,
"Could not create instance of listener: " + listenerClass, e);
}
}
}
Log.d(TAG, "Finished preparations, running/listing tests");
Bundle results = new Bundle();
Result junitResults = new Result();
try {
junitResults = core.run(Request.runner(runner));
} catch (RuntimeException e) {
final String msg = "Fatal exception when running tests";
Log.e(TAG, msg, e);
// report the exception to instrumentation out
results.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
msg + "\n" + Log.getStackTraceString(e));
} finally {
ByteArrayOutputStream summaryStream = new ByteArrayOutputStream();
// create the stream used to output summary data to the user
PrintStream summaryWriter = new PrintStream(summaryStream);
instrumentationResultPrinter.instrumentationRunFinished(summaryWriter,
results, junitResults);
summaryWriter.close();
results.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
String.format("\n%s", summaryStream.toString()));
}
Log.d(TAG, "Finished");
finish(Activity.RESULT_OK, results);
}
/**
* Read tests from a specified file.
*
* @return class names of tests. If there was an error reading the file, null is returned.
*/
private static List<String> readTestsFromFile(String fileName) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
List<String> tests = new ArrayList<>();
String line;
while ((line = br.readLine()) != null) {
tests.add(line);
}
return tests;
} catch (IOException err) {
Log.e(TAG, "There was an error reading the test class list: " + err.getMessage());
throw err;
}
}
/**
* Parse long from given value - except either Long or String.
*
* @return the value, -1 if not found
* @throws NumberFormatException if value is negative or not a number
*/
private static long parseUnsignedLong(Object value, String name) {
if (value != null) {
long longValue = Long.parseLong(value.toString());
if (longValue < 0) {
throw new NumberFormatException(name + " can not be negative");
}
return longValue;
}
return -1;
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (C) 2016 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 com.android.cts.core.runner;
import java.util.List;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
/**
* A {@link Runner} that does not actually run any tests but simply fires events for all leaf
* {@link Description} instances in the supplied {@link Description} hierarchy.
*/
class DescriptionHierarchyNotifier extends Runner {
private final Description description;
DescriptionHierarchyNotifier(Description description) {
this.description = description;
}
@Override
public Description getDescription() {
return description;
}
@Override
public void run(RunNotifier notifier) {
generateListOfTests(notifier, description);
}
/**
* Generates a list of tests to run by recursing over the {@link Description} hierarchy and
* firing events to simulate the tests being run successfully.
* @param runNotifier the notifier to which the events are sent.
* @param description the description to traverse.
*/
private void generateListOfTests(RunNotifier runNotifier, Description description) {
List<Description> children = description.getChildren();
if (children.isEmpty()) {
runNotifier.fireTestStarted(description);
runNotifier.fireTestFinished(description);
} else {
for (Description child : children) {
generateListOfTests(runNotifier, child);
}
}
}
}

View file

@ -0,0 +1,145 @@
/*
* Copyright (C) 2016 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 com.android.cts.core.runner;
import android.os.Bundle;
import android.util.Log;
import com.google.common.base.Splitter;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;
import org.junit.runners.ParentRunner;
import org.junit.runners.Suite;
import vogar.Expectation;
import vogar.ExpectationStore;
import vogar.ModeId;
import vogar.Result;
/**
* Filter out tests/classes that are not requested or which are expected to fail.
*
* <p>This filter has to handle both a hierarchy of {@code Description descriptions} that looks
* something like this:
* <pre>
* Suite
* Suite
* Suite
* ParentRunner
* Test
* ...
* ...
* ParentRunner
* Test
* ...
* ...
* Suite
* ParentRunner
* Test
* ...
* ...
* ...
* </pre>
*
* <p>It cannot filter out the non-leaf nodes in the hierarchy, i.e. {@link Suite} and
* {@link ParentRunner}, as that would prevent it from traversing the hierarchy and finding
* the leaf nodes.
*/
class ExpectationBasedFilter extends Filter {
static final String TAG = "ExpectationBasedFilter";
private static final String ARGUMENT_EXPECTATIONS = "core-expectations";
private static final Splitter CLASS_LIST_SPLITTER = Splitter.on(',').trimResults();
private final ExpectationStore expectationStore;
private static List<String> getExpectationResourcePaths(Bundle args) {
return CLASS_LIST_SPLITTER.splitToList(args.getString(ARGUMENT_EXPECTATIONS));
}
public ExpectationBasedFilter(Bundle args) {
ExpectationStore expectationStore = null;
try {
// Get the set of resource names containing the expectations.
Set<String> expectationResources = new LinkedHashSet<>(
getExpectationResourcePaths(args));
Log.i(TAG, "Loading expectations from: " + expectationResources);
expectationStore = ExpectationStore.parseResources(
getClass(), expectationResources, ModeId.DEVICE);
} catch (IOException e) {
Log.e(TAG, "Could not initialize ExpectationStore: ", e);
}
this.expectationStore = expectationStore;
}
@Override
public boolean shouldRun(Description description) {
// Only filter leaf nodes. The description is for a test if and only if it is a leaf node.
// Non-leaf nodes must not be filtered out as that would prevent leaf nodes from being
// visited in the case when we are traversing the hierarchy of classes.
Description testDescription = getTestDescription(description);
if (testDescription != null) {
String className = testDescription.getClassName();
String methodName = testDescription.getMethodName();
String testName = className + "#" + methodName;
if (expectationStore != null) {
Expectation expectation = expectationStore.get(testName);
if (expectation.getResult() != Result.SUCCESS) {
Log.d(CoreTestRunner.TAG, "Excluding test " + testDescription
+ " as it matches expectation: " + expectation);
return false;
}
}
}
return true;
}
private Description getTestDescription(Description description) {
List<Description> children = description.getChildren();
// An empty description is by definition a test.
if (children.isEmpty()) {
return description;
}
// Handle initialization errors that were wrapped in an ErrorReportingRunner as a special
// case. This is needed because ErrorReportingRunner is treated as a suite of Throwables,
// (where each Throwable corresponds to a test called initializationError) and so its
// description contains children, one for each Throwable, and so is not treated as a test
// to filter. Unfortunately, it does not support Filterable so this filter is never applied
// to its children.
// See https://github.com/junit-team/junit/issues/1253
Description child = children.get(0);
String methodName = child.getMethodName();
if ("initializationError".equals(methodName)) {
return child;
}
return null;
}
@Override
public String describe() {
return "TestFilter";
}
}

View file

@ -0,0 +1,151 @@
/*
* Copyright (C) 2016 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 com.android.cts.core.runner;
import android.support.test.internal.runner.ClassPathScanner;
import android.support.test.internal.runner.ClassPathScanner.ClassNameFilter;
import android.util.Log;
import com.android.cts.core.internal.runner.TestLoader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import dalvik.system.DexFile;
/**
* Find tests in the current APK.
*/
public class TestClassFinder {
private static final String TAG = "TestClassFinder";
private static final boolean DEBUG = false;
// Excluded test packages
private static final String[] DEFAULT_EXCLUDED_PACKAGES = {
"junit",
"org.junit",
"org.hamcrest",
"org.mockito",// exclude Mockito for performance and to prevent JVM related errors
"android.support.test.internal.runner.junit3",// always skip AndroidTestSuite
};
static Collection<Class<?>> getClasses(List<String> apks, ClassLoader loader) {
if (DEBUG) {
Log.d(TAG, "getClasses: =======================================");
for (String apkPath : apks) {
Log.d(TAG, "getClasses: -------------------------------");
Log.d(TAG, "getClasses: APK " + apkPath);
DexFile dexFile = null;
try {
dexFile = new DexFile(apkPath);
Enumeration<String> apkClassNames = dexFile.entries();
while (apkClassNames.hasMoreElements()) {
String apkClassName = apkClassNames.nextElement();
Log.d(TAG, "getClasses: DexClass element " + apkClassName);
}
} catch (IOException e) {
throw new AssertionError(e);
} finally {
if (dexFile != null) {
try {
dexFile.close();
} catch (IOException e) {
throw new AssertionError(e);
}
}
}
Log.d(TAG, "getClasses: -------------------------------");
}
} // if DEBUG
List<Class<?>> classes = new ArrayList<>();
ClassPathScanner scanner = new ClassPathScanner(apks);
ClassPathScanner.ChainedClassNameFilter filter =
new ClassPathScanner.ChainedClassNameFilter();
// exclude inner classes
filter.add(new ClassPathScanner.ExternalClassNameFilter());
// exclude default classes
for (String defaultExcludedPackage : DEFAULT_EXCLUDED_PACKAGES) {
filter.add(new ExcludePackageNameFilter(defaultExcludedPackage));
}
// exclude any classes that aren't a "test class" (see #loadIfTest)
TestLoader testLoader = new TestLoader();
testLoader.setClassLoader(loader);
try {
Set<String> classNames = scanner.getClassPathEntries(filter);
for (String className : classNames) {
// Important: This further acts as an additional filter;
// classes that aren't a "test class" are never loaded.
Class<?> cls = testLoader.loadIfTest(className);
if (cls != null) {
classes.add(cls);
if (DEBUG) {
Log.d(TAG, "getClasses: Loaded " + className);
}
} else if (DEBUG) {
Log.d(TAG, "getClasses: Failed to load class " + className);
}
}
return classes;
} catch (IOException e) {
Log.e(CoreTestRunner.TAG, "Failed to scan classes", e);
}
if (DEBUG) {
Log.d(TAG, "getClasses: =======================================");
}
return testLoader.getLoadedClasses();
}
/**
* A {@link ClassNameFilter} that only rejects a given package names within the given namespace.
*/
public static class ExcludePackageNameFilter implements ClassNameFilter {
private final String mPkgName;
ExcludePackageNameFilter(String pkgName) {
if (!pkgName.endsWith(".")) {
mPkgName = String.format("%s.", pkgName);
} else {
mPkgName = pkgName;
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean accept(String pathName) {
return !pathName.startsWith(mPkgName);
}
}
}

View file

@ -0,0 +1,137 @@
/*
* Copyright (C) 2016 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 com.android.cts.core.runner;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
/**
* A list of the tests to run.
*/
class TestList {
/** The set of test pacakges to run */
private final Set<String> mIncludedPackages = new HashSet<>();
/** The set of test packages not to run */
private final Set<String> mExcludedPackages = new HashSet<>();
/** The set of tests (classes or methods) to run */
private final Set<String> mIncludedTests = new HashSet<>();
/** The set of tests (classes or methods) not to run */
private final Set<String> mExcludedTests = new HashSet<>();
/** The list of all test classes to run (without filtering applied)*/
private final Collection<Class<?>> classesToRun;
public static TestList rootList(List<String> rootList) {
// Run from the root test class.
Set<String> classNamesToRun = new LinkedHashSet<>(rootList);
Log.d(CoreTestRunner.TAG, "Running all tests rooted at " + classNamesToRun);
List<Class<?>> classesToRun1 = getClasses(classNamesToRun);
return new TestList(classesToRun1);
}
private static List<Class<?>> getClasses(Set<String> classNames) {
// Populate the list of classes to run.
List<Class<?>> classesToRun = new ArrayList<>();
for (String className : classNames) {
try {
classesToRun.add(Class.forName(className));
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Could not load class '" + className, e);
}
}
return classesToRun;
}
/**
* @param classes The list of classes to run.
*/
public TestList(Collection<Class<?>> classes) {
this.classesToRun = classes;
}
public void addIncludeTestPackages(Set<String> packageNameSet) {
mIncludedPackages.addAll(packageNameSet);
}
public void addExcludeTestPackages(Set<String> packageNameSet) {
mExcludedPackages.addAll(packageNameSet);
}
public void addIncludeTests(Set<String> testNameSet) {
mIncludedTests.addAll(testNameSet);
}
public void addExcludeTests(Set<String> testNameSet) {
mExcludedTests.addAll(testNameSet);
}
/**
* Return all the classes to run.
*/
public Class[] getClassesToRun() {
return classesToRun.toArray(new Class[classesToRun.size()]);
}
/**
* Return true if the test with the specified name should be run, false otherwise.
*/
public boolean shouldRunTest(String testName) {
int index = testName.indexOf('#');
String className;
if (index == -1) {
className = testName;
} else {
className = testName.substring(0, index);
}
try {
Class<?> testClass = Class.forName(className);
Package testPackage = testClass.getPackage();
String testPackageName = "";
if (testPackage != null) {
testPackageName = testPackage.getName();
}
boolean include =
(mIncludedPackages.isEmpty() || mIncludedPackages.contains(testPackageName)) &&
(mIncludedTests.isEmpty() || mIncludedTests.contains(className) ||
mIncludedTests.contains(testName));
boolean exclude =
mExcludedPackages.contains(testPackageName) ||
mExcludedTests.contains(className) ||
mExcludedTests.contains(testName);
return include && !exclude;
} catch (ClassNotFoundException e) {
Log.w("Could not load class '" + className, e);
return false;
}
}
}

View file

@ -0,0 +1,82 @@
/*
* Copyright (C) 2016 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 com.android.cts.core.runner.support;
import android.support.test.internal.runner.junit3.AndroidJUnit3Builder;
import android.support.test.internal.runner.junit3.AndroidSuiteBuilder;
import android.support.test.internal.runner.junit4.AndroidAnnotatedBuilder;
import android.support.test.internal.runner.junit4.AndroidJUnit4Builder;
import android.support.test.internal.util.AndroidRunnerParams;
import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
import org.junit.internal.builders.AnnotatedBuilder;
import org.junit.internal.builders.IgnoredBuilder;
import org.junit.internal.builders.JUnit3Builder;
import org.junit.internal.builders.JUnit4Builder;
import org.junit.runners.model.RunnerBuilder;
/**
* A {@link RunnerBuilder} that can handle all types of tests.
*/
// A copy of package private class android.support.test.internal.runner.AndroidRunnerBuilder.
// Copied here so that it can be extended.
class AndroidRunnerBuilder extends AllDefaultPossibilitiesBuilder {
private final AndroidJUnit3Builder mAndroidJUnit3Builder;
private final AndroidJUnit4Builder mAndroidJUnit4Builder;
private final AndroidSuiteBuilder mAndroidSuiteBuilder;
private final AndroidAnnotatedBuilder mAndroidAnnotatedBuilder;
// TODO: customize for Android ?
private final IgnoredBuilder mIgnoredBuilder;
/**
* @param runnerParams {@link AndroidRunnerParams} that stores common runner parameters
*/
// Added canUseSuiteMethod parameter.
AndroidRunnerBuilder(AndroidRunnerParams runnerParams, boolean canUseSuiteMethod) {
super(canUseSuiteMethod);
mAndroidJUnit3Builder = new AndroidJUnit3Builder(runnerParams);
mAndroidJUnit4Builder = new AndroidJUnit4Builder(runnerParams);
mAndroidSuiteBuilder = new AndroidSuiteBuilder(runnerParams);
mAndroidAnnotatedBuilder = new AndroidAnnotatedBuilder(this, runnerParams);
mIgnoredBuilder = new IgnoredBuilder();
}
@Override
protected JUnit4Builder junit4Builder() {
return mAndroidJUnit4Builder;
}
@Override
protected JUnit3Builder junit3Builder() {
return mAndroidJUnit3Builder;
}
@Override
protected AnnotatedBuilder annotatedBuilder() {
return mAndroidAnnotatedBuilder;
}
@Override
protected IgnoredBuilder ignoredBuilder() {
return mIgnoredBuilder;
}
@Override
protected RunnerBuilder suiteMethodBuilder() {
return mAndroidSuiteBuilder;
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (C) 2016 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 com.android.cts.core.runner.support;
import android.support.test.internal.util.AndroidRunnerParams;
import android.util.Log;
import org.junit.runners.model.RunnerBuilder;
import org.junit.runner.Runner;
/**
* Extends {@link AndroidRunnerBuilder} in order to provide alternate {@link RunnerBuilder}
* implementations.
*/
public class ExtendedAndroidRunnerBuilder extends AndroidRunnerBuilder {
private static final boolean DEBUG = false;
private final TestNgRunnerBuilder mTestNgBuilder;
/**
* @param runnerParams {@link AndroidRunnerParams} that stores common runner parameters
*/
public ExtendedAndroidRunnerBuilder(AndroidRunnerParams runnerParams) {
super(runnerParams, false /* CTSv1 filtered out Test suite() classes. */);
mTestNgBuilder = new TestNgRunnerBuilder();
}
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
if (DEBUG) {
Log.d("ExAndRunBuild", "runnerForClass: Searching runner for class " + testClass.getName());
}
// Give TestNG tests a chance to participate in the Runner search first.
// (Note that the TestNG runner handles log-only runs by itself)
Runner runner = mTestNgBuilder.runnerForClass(testClass);
if (runner == null) {
// Use the normal Runner search mechanism (for Junit tests).
runner = super.runnerForClass(testClass);
}
logFoundRunner(runner);
return runner;
}
private static void logFoundRunner(Runner runner) {
if (DEBUG) {
Log.d("ExAndRunBuild", "runnerForClass: Found runner of type " +
((runner == null) ? "<null>" : runner.getClass().getName()));
}
}
}

View file

@ -0,0 +1,124 @@
/*
* Copyright (C) 2016 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 com.android.cts.core.runner.support;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Listener for TestNG runs that provides gtest-like console output.
*
* Prints a message like [RUN], [OK], [ERROR], [SKIP] to stdout
* as tests are being executed with their status.
*
* This output is also saved as the device logs (logcat) when the test is run through
* cts-tradefed.
*/
public class SingleTestNGTestRunListener implements org.testng.ITestListener {
private int mTestStarted = 0;
private Map<String, Throwable> failures = new LinkedHashMap<>();
private static class Prefixes {
@SuppressWarnings("unused")
private static final String INFORMATIONAL_MARKER = "[----------]";
private static final String START_TEST_MARKER = "[ RUN ]";
private static final String OK_TEST_MARKER = "[ OK ]";
private static final String ERROR_TEST_RUN_MARKER = "[ ERROR ]";
private static final String SKIPPED_TEST_MARKER = "[ SKIP ]";
private static final String TEST_RUN_MARKER = "[==========]";
}
// How many tests did TestNG *actually* try to run?
public int getNumTestStarted() {
return mTestStarted;
}
public Map<String, Throwable> getFailures() {
return Collections.unmodifiableMap(failures);
}
@Override
public void onFinish(org.testng.ITestContext context) {
System.out.println(String.format("%s", Prefixes.TEST_RUN_MARKER));
}
@Override
public void onStart(org.testng.ITestContext context) {
System.out.println(String.format("%s", Prefixes.INFORMATIONAL_MARKER));
}
@Override
public void onTestFailedButWithinSuccessPercentage(org.testng.ITestResult result) {
onTestFailure(result);
}
@Override
public void onTestFailure(org.testng.ITestResult result) {
// All failures are coalesced into one '[ FAILED ]' message at the end
// This is because a single test method can run multiple times with different parameters.
// Since we only test a single method, it's safe to combine all failures into one
// failure at the end.
//
// The big pass/fail is printed from SingleTestNGTestRunner, not from the listener.
String id = getId(result);
Throwable throwable = result.getThrowable();
System.out.println(String.format("%s %s ::: %s", Prefixes.ERROR_TEST_RUN_MARKER,
id, stringify(throwable)));
failures.put(id, throwable);
}
@Override
public void onTestSkipped(org.testng.ITestResult result) {
System.out.println(String.format("%s %s", Prefixes.SKIPPED_TEST_MARKER,
getId(result)));
}
@Override
public void onTestStart(org.testng.ITestResult result) {
mTestStarted++;
System.out.println(String.format("%s %s", Prefixes.START_TEST_MARKER,
getId(result)));
}
@Override
public void onTestSuccess(org.testng.ITestResult result) {
System.out.println(String.format("%s", Prefixes.OK_TEST_MARKER));
}
private String getId(org.testng.ITestResult test) {
// TestNG is quite complicated since tests can have arbitrary parameters.
// Use its code to stringify a result name instead of doing it ourselves.
org.testng.remote.strprotocol.TestResultMessage msg =
new org.testng.remote.strprotocol.TestResultMessage(
null, /*suite name*/
null, /*test name -- display the test method name instead */
test);
String className = test.getTestClass().getName();
//String name = test.getMethod().getMethodName();
return String.format("%s#%s", className, msg.toDisplayString());
}
private String stringify(Throwable error) {
return Arrays.toString(error.getStackTrace()).replaceAll("\n", " ");
}
}

View file

@ -0,0 +1,132 @@
/*
* Copyright (C) 2016 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 com.android.cts.core.runner.support;
import android.util.Log;
import org.testng.TestNG;
import org.testng.xml.XmlClass;
import org.testng.xml.XmlInclude;
import org.testng.xml.XmlSuite;
import org.testng.xml.XmlTest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Test executor to run a single TestNG test method.
*/
public class SingleTestNgTestExecutor {
// Execute any method which is in the class klass.
// The klass is passed in separately to handle inherited methods only.
// Returns true if all tests pass, false otherwise.
public static Result execute(Class<?> klass, String methodName) {
if (klass == null) {
throw new NullPointerException("klass must not be null");
}
if (methodName == null) {
throw new NullPointerException("methodName must not be null");
}
//if (!method.getDeclaringClass().isAssignableFrom(klass)) {
// throw new IllegalArgumentException("klass must match method's declaring class");
//}
SingleTestNGTestRunListener listener = new SingleTestNGTestRunListener();
// Although creating a new testng "core" every time might seem heavyweight, in practice
// it seems to take a mere few milliseconds at most.
// Since we're running all the parameteric combinations of a test,
// this ends up being neglible relative to that.
TestNG testng = createTestNG(klass.getName(), methodName, listener);
testng.run();
if (listener.getNumTestStarted() <= 0) {
// It's possible to be invoked here with an arbitrary method name
// so print out a warning incase TestNG actually had a no-op.
Log.w("TestNgExec", "execute class " + klass.getName() + ", method " + methodName +
" had 0 tests executed. Not a test method?");
}
return new Result(testng.hasFailure(), listener.getFailures());
}
private static org.testng.TestNG createTestNG(String klass, String method,
SingleTestNGTestRunListener listener) {
org.testng.TestNG testng = new org.testng.TestNG();
testng.setUseDefaultListeners(false); // Don't create the testng-specific HTML/XML reports.
// It still prints the X/Y tests succeeded/failed summary to stdout.
// We don't strictly need this listener for CTS, but having it print SUCCESS/FAIL
// makes it easier to diagnose which particular combination of a test method had failed
// from looking at device logcat.
testng.addListener(listener);
/* Construct the following equivalent XML configuration:
*
* <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
* <suite>
* <test>
* <classes>
* <class name="$klass">
* <include name="$method" />
* </class>
* </classes>
* </test>
* </suite>
*
* This will ensure that only a single klass/method is being run by testng.
* (It can still be run multiple times due to @DataProvider, with different parameters
* each time)
*/
List<XmlSuite> suites = new ArrayList<>();
XmlSuite the_suite = new XmlSuite();
XmlTest the_test = new XmlTest(the_suite);
XmlClass the_class = new XmlClass(klass);
XmlInclude the_include = new XmlInclude(method);
the_class.getIncludedMethods().add(the_include);
the_test.getXmlClasses().add(the_class);
suites.add(the_suite);
testng.setXmlSuites(suites);
return testng;
}
public static class Result {
private final boolean hasFailure;
private final Map<String,Throwable> failures;
Result(boolean hasFailure, Map<String, Throwable> failures) {
this.hasFailure = hasFailure;
this.failures = Collections.unmodifiableMap(new LinkedHashMap<>(failures));
}
public boolean hasFailure() {
return hasFailure;
}
public Map<String, Throwable> getFailures() {
return failures;
}
}
}

View file

@ -0,0 +1,235 @@
/*
* Copyright (C) 2016 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 com.android.cts.core.runner.support;
import android.util.Log;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.Filterable;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Map;
/**
* A {@link Runner} that can TestNG tests.
*
* <p>Implementation note: Avoid extending ParentRunner since that also has
* logic to handle BeforeClass/AfterClass and other junit-specific functionality
* that would be invalid for TestNG.</p>
*/
class TestNgRunner extends Runner implements Filterable {
private static final boolean DEBUG = false;
private Description mDescription;
/** Class name for debugging. */
private String mClassName;
/** Don't include the same method names twice. */
private HashSet<String> mMethodSet = new HashSet<>();
/**
* @param testClass the test class to run
*/
TestNgRunner(Class<?> testClass) {
mDescription = generateTestNgDescription(testClass);
mClassName = testClass.getName();
}
// Runner implementation
@Override
public Description getDescription() {
return mDescription;
}
// Runner implementation
@Override
public int testCount() {
if (!descriptionHasChildren(getDescription())) { // Avoid NPE when description is null.
return 0;
}
// We always follow a flat Parent->Leaf hierarchy, so no recursion necessary.
return getDescription().testCount();
}
// Filterable implementation
@Override
public void filter(Filter filter) throws NoTestsRemainException {
mDescription = filterDescription(mDescription, filter);
if (!descriptionHasChildren(getDescription())) { // Avoid NPE when description is null.
if (DEBUG) {
Log.d("TestNgRunner",
"Filtering has removed all tests :( for class " + mClassName);
}
throw new NoTestsRemainException();
}
if (DEBUG) {
Log.d("TestNgRunner",
"Filtering has retained " + testCount() + " tests for class " + mClassName);
}
}
// Filterable implementation
@Override
public void run(RunNotifier notifier) {
if (!descriptionHasChildren(getDescription())) { // Avoid NPE when description is null.
// Nothing to do.
return;
}
for (Description child : getDescription().getChildren()) {
String className = child.getClassName();
String methodName = child.getMethodName();
Class<?> klass;
try {
klass = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new AssertionError(e);
}
notifier.fireTestStarted(child);
// Avoid looking at all the methods by just using the string method name.
SingleTestNgTestExecutor.Result result = SingleTestNgTestExecutor.execute(klass, methodName);
if (result.hasFailure()) {
// TODO: get the error messages from testng somehow.
notifier.fireTestFailure(new Failure(child, extractException(result.getFailures())));
}
notifier.fireTestFinished(child);
// TODO: Check @Test(enabled=false) and invoke #fireTestIgnored instead.
}
}
private Throwable extractException(Map<String, Throwable> failures) {
if (failures.isEmpty()) {
return new AssertionError();
}
if (failures.size() == 1) {
return failures.values().iterator().next();
}
StringBuilder errorMessage = new StringBuilder("========== Multiple Failures ==========");
for (Map.Entry<String, Throwable> failureEntry : failures.entrySet()) {
errorMessage.append("\n\n=== "). append(failureEntry.getKey()).append(" ===\n");
Throwable throwable = failureEntry.getValue();
errorMessage
.append(throwable.getClass()).append(": ")
.append(throwable.getMessage());
for (StackTraceElement e : throwable.getStackTrace()) {
if (e.getClassName().equals(getClass().getName())) {
break;
}
errorMessage.append("\n at ").append(e);
}
}
errorMessage.append("\n=======================================\n\n");
return new AssertionError(errorMessage.toString());
}
/**
* Recursively (preorder traversal) apply the filter to all the descriptions.
*
* @return null if the filter rejects the whole tree.
*/
private static Description filterDescription(Description desc, Filter filter) {
if (!filter.shouldRun(desc)) { // XX: Does the filter itself do the recursion?
return null;
}
Description newDesc = desc.childlessCopy();
// Return leafs.
if (!descriptionHasChildren(desc)) {
return newDesc;
}
// Filter all subtrees, only copying them if the filter accepts them.
for (Description child : desc.getChildren()) {
Description filteredChild = filterDescription(child, filter);
if (filteredChild != null) {
newDesc.addChild(filteredChild);
}
}
return newDesc;
}
private Description generateTestNgDescription(Class<?> cls) {
// Add the overall class description as the parent.
Description parent = Description.createSuiteDescription(cls);
if (DEBUG) {
Log.d("TestNgRunner", "Generating TestNg Description for class " + cls.getName());
}
// Add each test method as a child.
for (Method m : cls.getDeclaredMethods()) {
// Filter to only 'public void' signatures.
if ((m.getModifiers() & Modifier.PUBLIC) == 0) {
continue;
}
if (!m.getReturnType().equals(Void.TYPE)) {
continue;
}
// Note that TestNG methods may actually have parameters
// (e.g. with @DataProvider) which TestNG will populate itself.
// Add [Class, MethodName] as a Description leaf node.
String name = m.getName();
if (!mMethodSet.add(name)) {
// Overloaded methods have the same name, don't add them twice.
if (DEBUG) {
Log.d("TestNgRunner", "Already added child " + cls.getName() + "#" + name);
}
continue;
}
Description child = Description.createTestDescription(cls, name);
parent.addChild(child);
if (DEBUG) {
Log.d("TestNgRunner", "Add child " + cls.getName() + "#" + name);
}
}
return parent;
}
private static boolean descriptionHasChildren(Description desc) {
// Note: Although "desc.isTest()" is equivalent to "!desc.getChildren().isEmpty()"
// we add the pre-requisite 2 extra null checks to avoid throwing NPEs.
return desc != null && desc.getChildren() != null && !desc.getChildren().isEmpty();
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (C) 2016 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 com.android.cts.core.runner.support;
import java.lang.reflect.Method;
import org.junit.runner.Runner;
import org.junit.runners.model.RunnerBuilder;
import org.testng.annotations.Test;
/**
* A {@link RunnerBuilder} that can handle TestNG tests.
*/
public class TestNgRunnerBuilder extends RunnerBuilder {
// Returns a TestNG runner for this class, only if it is a class
// annotated with testng's @Test or has any methods with @Test in it.
@Override
public Runner runnerForClass(Class<?> testClass) {
if (isTestNgTestClass(testClass)) {
return new TestNgRunner(testClass);
}
return null;
}
private static boolean isTestNgTestClass(Class<?> cls) {
// TestNG test is either marked @Test at the class
if (cls.getAnnotation(Test.class) != null) {
return true;
}
// Or It's marked @Test at the method level
for (Method m : cls.getDeclaredMethods()) {
if (m.getAnnotation(Test.class) != null) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (C) 2016 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.
*/
/**
* Contains all the changes needed to the {@code android.support.test.internal.runner} classes.
*
* <p>As its name suggests {@code android.support.test.internal.runner} are internal classes that
* are not designed to be extended from outside those packages. This package encapsulates all the
* workarounds needed to overcome that limitation, from duplicating classes to using reflection.
* The intention is that these changes are temporary and they (or a better equivalent) will be
* quickly integrated into the internal classes.
*/
package com.android.cts.core.runner.support;

View file

@ -0,0 +1,262 @@
/*
* Copyright (C) 2014 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 com.android.cts.runner;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.support.test.internal.runner.listener.InstrumentationRunListener;
import android.text.TextUtils;
import android.util.Log;
import junit.framework.TestCase;
import org.junit.runner.Description;
import org.junit.runner.notification.RunListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.Class;
import java.lang.ReflectiveOperationException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.Authenticator;
import java.net.CookieHandler;
import java.net.ResponseCache;
import java.text.DateFormat;
import java.util.Locale;
import java.util.Properties;
import java.util.TimeZone;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
/**
* A {@link RunListener} for CTS. Sets the system properties necessary for many
* core tests to run. This is needed because there are some core tests that need
* writing access to the file system.
* Finally, we add a means to free memory allocated by a TestCase after its
* execution.
*/
public class CtsTestRunListener extends InstrumentationRunListener {
private static final String TAG = "CtsTestRunListener";
private TestEnvironment mEnvironment;
private Class<?> lastClass;
@Override
public void testRunStarted(Description description) throws Exception {
mEnvironment = new TestEnvironment(getInstrumentation().getTargetContext());
// We might want to move this to /sdcard, if is is mounted/writable.
File cacheDir = getInstrumentation().getTargetContext().getCacheDir();
System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
// attempt to disable keyguard, if current test has permission to do so
// TODO: move this to a better place, such as InstrumentationTestRunner
// ?
if (getInstrumentation().getContext().checkCallingOrSelfPermission(
android.Manifest.permission.DISABLE_KEYGUARD)
== PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "Disabling keyguard");
KeyguardManager keyguardManager =
(KeyguardManager) getInstrumentation().getContext().getSystemService(
Context.KEYGUARD_SERVICE);
keyguardManager.newKeyguardLock("cts").disableKeyguard();
} else {
Log.i(TAG, "Test lacks permission to disable keyguard. " +
"UI based tests may fail if keyguard is up");
}
}
@Override
public void testStarted(Description description) throws Exception {
if (description.getTestClass() != lastClass) {
lastClass = description.getTestClass();
printMemory(description.getTestClass());
}
mEnvironment.reset();
}
@Override
public void testFinished(Description description) {
// no way to implement this in JUnit4...
// offending test cases that need this logic should probably be cleaned
// up individually
// if (test instanceof TestCase) {
// cleanup((TestCase) test);
// }
}
/**
* Dumps some memory info.
*/
private void printMemory(Class<?> testClass) {
Runtime runtime = Runtime.getRuntime();
long total = runtime.totalMemory();
long free = runtime.freeMemory();
long used = total - free;
Log.d(TAG, "Total memory : " + total);
Log.d(TAG, "Used memory : " + used);
Log.d(TAG, "Free memory : " + free);
String tempdir = System.getProperty("java.io.tmpdir", "");
// TODO: Remove these extra Logs added to debug a specific timeout problem.
Log.d(TAG, "java.io.tmpdir is:" + tempdir);
if (!TextUtils.isEmpty(tempdir)) {
String[] commands = {"df", tempdir};
BufferedReader in = null;
try {
Log.d(TAG, "About to .exec df");
Process proc = runtime.exec(commands);
Log.d(TAG, ".exec returned");
in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
Log.d(TAG, "Stream reader created");
String line;
while ((line = in.readLine()) != null) {
Log.d(TAG, line);
}
} catch (IOException e) {
Log.d(TAG, "Exception: " + e.toString());
// Well, we tried
} finally {
Log.d(TAG, "In finally");
if (in != null) {
try {
in.close();
} catch (IOException e) {
// Meh
}
}
}
}
Log.d(TAG, "Now executing : " + testClass.getName());
}
/**
* Nulls all non-static reference fields in the given test class. This
* method helps us with those test classes that don't have an explicit
* tearDown() method. Normally the garbage collector should take care of
* everything, but since JUnit keeps references to all test cases, a little
* help might be a good idea.
*/
private void cleanup(TestCase test) {
Class<?> clazz = test.getClass();
while (clazz != TestCase.class) {
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field f = fields[i];
if (!f.getType().isPrimitive() &&
!Modifier.isStatic(f.getModifiers())) {
try {
f.setAccessible(true);
f.set(test, null);
} catch (Exception ignored) {
// Nothing we can do about it.
}
}
}
clazz = clazz.getSuperclass();
}
}
// http://code.google.com/p/vogar/source/browse/trunk/src/vogar/target/TestEnvironment.java
static class TestEnvironment {
private static final Field sDateFormatIs24HourField;
static {
try {
Class<?> dateFormatClass = Class.forName("java.text.DateFormat");
sDateFormatIs24HourField = dateFormatClass.getDeclaredField("is24Hour");
} catch (ReflectiveOperationException e) {
throw new AssertionError("Missing DateFormat.is24Hour", e);
}
}
private final Locale mDefaultLocale;
private final TimeZone mDefaultTimeZone;
private final HostnameVerifier mHostnameVerifier;
private final SSLSocketFactory mSslSocketFactory;
private final Properties mProperties = new Properties();
private final Boolean mDefaultIs24Hour;
TestEnvironment(Context context) {
mDefaultLocale = Locale.getDefault();
mDefaultTimeZone = TimeZone.getDefault();
mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
mSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
mProperties.setProperty("user.home", "");
mProperties.setProperty("java.io.tmpdir", context.getCacheDir().getAbsolutePath());
// The CDD mandates that devices that support WiFi are the only ones that will have
// multicast.
PackageManager pm = context.getPackageManager();
mProperties.setProperty("android.cts.device.multicast",
Boolean.toString(pm.hasSystemFeature(PackageManager.FEATURE_WIFI)));
mDefaultIs24Hour = getDateFormatIs24Hour();
// There are tests in libcore that should be disabled for low ram devices. They can't
// access ActivityManager to call isLowRamDevice, but can read system properties.
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
mProperties.setProperty("android.cts.device.lowram",
Boolean.toString(activityManager.isLowRamDevice()));
}
void reset() {
System.setProperties(null);
System.setProperties(mProperties);
Locale.setDefault(mDefaultLocale);
TimeZone.setDefault(mDefaultTimeZone);
Authenticator.setDefault(null);
CookieHandler.setDefault(null);
ResponseCache.setDefault(null);
HttpsURLConnection.setDefaultHostnameVerifier(mHostnameVerifier);
HttpsURLConnection.setDefaultSSLSocketFactory(mSslSocketFactory);
setDateFormatIs24Hour(mDefaultIs24Hour);
}
private static Boolean getDateFormatIs24Hour() {
try {
return (Boolean) sDateFormatIs24HourField.get(null);
} catch (ReflectiveOperationException e) {
throw new AssertionError("Unable to get java.text.DateFormat.is24Hour", e);
}
}
private static void setDateFormatIs24Hour(Boolean value) {
try {
sDateFormatIs24HourField.set(null, value);
} catch (ReflectiveOperationException e) {
throw new AssertionError("Unable to set java.text.DateFormat.is24Hour", e);
}
}
}
}