Merge "Make vm-tests-tf use ITestDevice.pushDir" into ics-mr0
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 29164bc..81758f6 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -28,6 +28,8 @@
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
+ <meta-data android:name="android.accessibilityservice"
+ android:resource="@xml/accessibilityservice" />
</service>
<service android:name=".DelegatingAccessibilityService$DelegatingConnectionService"
diff --git a/tests/accessibilityservice/res/xml/accessibilityservice.xml b/tests/accessibilityservice/res/xml/accessibilityservice.xml
new file mode 100644
index 0000000..31a3f71
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/accessibilityservice.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:accessibilityFlags="flagDefault"
+ android:canRetrieveWindowContent="true" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl b/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
index b5ebf19..868bd72 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
+++ b/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
@@ -20,7 +20,7 @@
/**
* Interface for interacting with the accessibility service mock.
*/
-interface IAccessibilityServiceDelegate {
+oneway interface IAccessibilityServiceDelegate {
/**
* Delegate an {@link android.view.accessibility.AccessibilityEvent}.
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java
index 37a9c54..5680b7c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java
@@ -17,7 +17,6 @@
package android.accessibilityservice.delegate;
import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceDelegate;
import android.accessibilityservice.IAccessibilityServiceDelegateConnection;
import android.app.Service;
@@ -64,14 +63,6 @@
@Override
protected void onServiceConnected() {
- AccessibilityServiceInfo info = new AccessibilityServiceInfo();
- info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
- info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
- info.packageNames = new String[] {
- "com.android.cts.accessibilityservice"
- };
- setServiceInfo(info);
-
// the service is ready to be used only
// after the system has bound to it
sServiceDelegate = this;
diff --git a/tests/tests/accessibilityservice/AndroidManifest.xml b/tests/tests/accessibilityservice/AndroidManifest.xml
index 70e1092..c75ffb2 100644
--- a/tests/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/tests/accessibilityservice/AndroidManifest.xml
@@ -26,6 +26,9 @@
<activity android:label="@string/accessibility_end_to_end_test_activity"
android:name="android.accessibilityservice.cts.AccessibilityEndToEndTestActivity"/>
+ <activity android:label="@string/accessibility_query_window_test_activity"
+ android:name="android.accessibilityservice.cts.AccessibilityWindowQueryActivity"/>
+
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/tests/tests/accessibilityservice/res/layout/query_window_test.xml b/tests/tests/accessibilityservice/res/layout/query_window_test.xml
new file mode 100644
index 0000000..4b32eb1
--- /dev/null
+++ b/tests/tests/accessibilityservice/res/layout/query_window_test.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2011, 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ >
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <Button
+ android:id="@+id/button1"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button1"
+ />
+ <Button
+ android:id="@+id/button2"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button2"
+ />
+ <Button
+ android:id="@+id/button3"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button3"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <Button
+ android:id="@+id/button4"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button4"
+ />
+ <Button
+ android:id="@+id/button5"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button5"
+ />
+ <Button
+ android:id="@+id/button6"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button6"
+ android:contentDescription="@string/contentDescription"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <Button
+ android:id="@+id/button7"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button7"
+ />
+ <Button
+ android:id="@+id/button8"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button8"
+ />
+ <Button
+ android:id="@+id/button9"
+ android:layout_width="160px"
+ android:layout_height="100px"
+ android:text="@string/button9"
+ />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/tests/accessibilityservice/res/values/strings.xml b/tests/tests/accessibilityservice/res/values/strings.xml
index e86d3cc..7b3cd0f 100644
--- a/tests/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/tests/accessibilityservice/res/values/strings.xml
@@ -44,4 +44,38 @@
<!-- String notification message -->
<string name="notification_message">Notification message</string>
+
+ <!-- String title of the accessibility query window test activity -->
+ <string name="accessibility_query_window_test_activity">Query window test</string>
+
+ <!-- String Button1 text -->
+ <string name="button1">Button1</string>
+
+ <!-- String Button2 text -->
+ <string name="button2">Button2</string>
+
+ <!-- String Button3 text -->
+ <string name="button3">Button3</string>
+
+ <!-- String Button4 text -->
+ <string name="button4">Button4</string>
+
+ <!-- String Button5 text -->
+ <string name="button5">Button5</string>
+
+ <!-- String Button6 text -->
+ <string name="button6">Button6</string>
+
+ <!-- String with content description for Button6 -->
+ <string name="contentDescription">contentDescription</string>
+
+ <!-- String Button7 text -->
+ <string name="button7">Button7</string>
+
+ <!-- String Button8 text -->
+ <string name="button8">Button8</string>
+
+ <!-- String Button9 text -->
+ <string name="button9">Button9</string>
+
</resources>
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl b/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
index b5ebf19..868bd72 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
@@ -20,7 +20,7 @@
/**
* Interface for interacting with the accessibility service mock.
*/
-interface IAccessibilityServiceDelegate {
+oneway interface IAccessibilityServiceDelegate {
/**
* Delegate an {@link android.view.accessibility.AccessibilityEvent}.
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityDelegateHelper.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityDelegateHelper.java
new file mode 100644
index 0000000..1e7c625
--- /dev/null
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityDelegateHelper.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2011 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.accessibilityservice.cts;
+
+import static junit.framework.Assert.fail;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceDelegate;
+import android.accessibilityservice.IAccessibilityServiceDelegateConnection;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import java.util.List;
+
+/**
+ * This class is a helper for implementing delegation of accessibility events.
+ * This is required because changing accessibility settings can be performed
+ * either via writing to the secure settings, which a CTS test cannot do, or
+ * by manual interaction from the settings application. However, manually
+ * enabling the testing accessibility service does not work because the test
+ * runner restarts the package before running the tests, thus breaking the
+ * bond between the system and the manually enabled testing service. So,
+ * we have a delegating service that is manually enabled which services as a
+ * proxy to deliver accessibility events to the testing service.
+ */
+class AccessibilityDelegateHelper implements ServiceConnection {
+
+ /**
+ * Timeout required for pending Binder calls or event processing to
+ * complete.
+ */
+ public static final long TIMEOUT_ASYNC_PROCESSING = 500;
+
+ /**
+ * The package of the accessibility service mock interface.
+ */
+ private static final String DELEGATING_SERVICE_PACKAGE =
+ "android.accessibilityservice.delegate";
+
+ /**
+ * The package of the delegating accessibility service interface.
+ */
+ private static final String DELEGATING_SERVICE_CLASS_NAME =
+ "android.accessibilityservice.delegate.DelegatingAccessibilityService";
+
+ /**
+ * The package of the delegating accessibility service connection interface.
+ */
+ private static final String DELEGATING_SERVICE_CONNECTION_CLASS_NAME =
+ "android.accessibilityservice.delegate."
+ + "DelegatingAccessibilityService$DelegatingConnectionService";
+
+ /**
+ * The client accessibility service to which to delegate.
+ */
+ private final AccessibilityService mAccessibilityService;
+
+ /**
+ * Lock for synchronization.
+ */
+ private final Object mLock = new Object();
+
+ /**
+ * Whether this delegate is initialized.
+ */
+ private boolean mInitialized;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param service The service to which to delegate.
+ */
+ public AccessibilityDelegateHelper(AccessibilityService service) {
+ mAccessibilityService = service;
+ }
+
+ /**
+ * Ensures the required setup for the test performed and that it is bound to the
+ * DelegatingAccessibilityService which runs in another process. The setup is
+ * enabling accessibility and installing and enabling the delegating accessibility
+ * service this test binds to.
+ * </p>
+ * Note: Please look at the class description for information why such an
+ * approach is taken.
+ */
+ public void bindToDelegatingAccessibilityService(Context context) {
+ // check if accessibility is enabled
+ AccessibilityManager accessibilityManager = (AccessibilityManager) context
+ .getSystemService(Service.ACCESSIBILITY_SERVICE);
+
+ if (!accessibilityManager.isEnabled()) {
+ throw new IllegalStateException("Delegating service not enabled. "
+ + "(Settings -> Accessibility -> Delegating Accessibility Service)");
+ }
+
+ // check if the delegating service is running
+ List<AccessibilityServiceInfo> enabledServices =
+ accessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ boolean delegatingServiceRunning = false;
+ for (AccessibilityServiceInfo enabledService : enabledServices) {
+ ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo;
+ if (DELEGATING_SERVICE_PACKAGE.equals(serviceInfo.packageName)
+ && DELEGATING_SERVICE_CLASS_NAME.equals(serviceInfo.name)) {
+ delegatingServiceRunning = true;
+ break;
+ }
+ }
+
+ if (!delegatingServiceRunning) {
+ // delegating service not running, so check if it is installed at all
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ packageManager.getServiceInfo(new ComponentName(DELEGATING_SERVICE_PACKAGE,
+ DELEGATING_SERVICE_CLASS_NAME), 0);
+ } catch (NameNotFoundException nnfe) {
+ throw new IllegalStateException("CtsDelegatingAccessibilityService.apk" +
+ " not installed.");
+ }
+
+ throw new IllegalStateException("Delegating Accessibility Service not running."
+ + "(Settings -> Accessibility -> Delegating Accessibility Service)");
+ }
+
+ Intent intent = new Intent().setClassName(DELEGATING_SERVICE_PACKAGE,
+ DELEGATING_SERVICE_CONNECTION_CLASS_NAME);
+ context.bindService(intent, this, Context.BIND_AUTO_CREATE);
+
+ final long beginTime = SystemClock.uptimeMillis();
+ synchronized (mLock) {
+ while (true) {
+ if (mInitialized) {
+ return;
+ }
+ final long elapsedTime = (SystemClock.uptimeMillis() - beginTime);
+ final long remainingTime = TIMEOUT_ASYNC_PROCESSING - elapsedTime;
+ if (remainingTime <= 0) {
+ if (!mInitialized) {
+ throw new IllegalStateException("Cound not connect to the delegating"
+ + " accessibility service");
+ }
+ return;
+ }
+ try {
+ mLock.wait(remainingTime);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc ServiceConnection#onServiceConnected(ComponentName,IBinder)}
+ */
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ IAccessibilityServiceDelegateConnection connection =
+ IAccessibilityServiceDelegateConnection.Stub.asInterface(service);
+ try {
+ connection.setAccessibilityServiceDelegate(new IAccessibilityServiceDelegate.Stub() {
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ mAccessibilityService.onAccessibilityEvent(event);
+ }
+ @Override
+ public void onInterrupt() {
+ mAccessibilityService.onInterrupt();
+ }
+ });
+ mInitialized = true;
+ synchronized (mLock) {
+ mLock.notifyAll();
+ }
+ } catch (RemoteException re) {
+ fail("Could not set delegate to the delegating service.");
+ }
+ }
+
+ /**
+ * {@inheritDoc ServiceConnection#onServiceDisconnected(ComponentName)}
+ */
+ public void onServiceDisconnected(ComponentName name) {
+ mInitialized = false;
+ /* do nothing */
+ }
+}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index af6d37d..5a565ec 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -16,40 +16,25 @@
package android.accessibilityservice.cts;
-import com.android.cts.accessibilityservice.R;
-
import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.IAccessibilityServiceDelegate;
-import android.accessibilityservice.IAccessibilityServiceDelegateConnection;
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
-import android.app.ActivityManager.RunningServiceInfo;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
+import com.android.cts.accessibilityservice.R;
+
import junit.framework.TestCase;
import java.util.Iterator;
@@ -62,18 +47,22 @@
* creating an {@link Activity} and poking around so {@link AccessibilityEvent}s
* are generated and their correct dispatch verified.
* <p>
- * Note: The end-to-end test is composed of two APKs, one with a mock accessibility
- * service, another with the instrumented activity and test cases. The motivation for
- * two APKs design is that CTS tests cannot access the secure settings which is
- * required for enabling accessibility and accessibility services. Therefore, manual
- * installation of the <strong>CtsAccessibilityServiceTestMockService.apk</strong>
+ * Note: The accessibility CTS tests are composed of two APKs, one with delegating
+ * accessibility service and another with the instrumented activity and test cases.
+ * The motivation for two APKs design is that CTS tests cannot access the secure
+ * settings which is required for enabling accessibility services, hence there is
+ * no way to manipulate accessibility settings programmaticaly. Further, manually
+ * enabling an accessibility service in the tests APK will not work either because
+ * the instrumentation restarts the process under test which would break the binding
+ * between the accessibility service and the system.
+ * <p>
+ * Therefore, manual installation of the
+ * <strong>CtsAccessibilityServiceTestMockService.apk</strong>
* whose source is located at <strong>cts/tests/accessibility</strong> is required.
- * Once the former package has been installed accessibility must be enabled (Settings ->
- * Accessibility), the mock service must be enabled (Settings -> Accessibility
- * -> Mock Accessibility Service), and then the CTS tests in this package can be
- * successfully run. Further, the mock and tests run in separate processes since
- * the instrumentation restarts the process in which it is running and this
- * breaks the binding between the mock accessibility service and the system.
+ * Once the former package has been installed the service must be enabled
+ * (Settings -> Accessibility -> Delegating Accessibility Service), and then the CTS tests
+ * in this package can be successfully run.
+ * </p>
*/
public class AccessibilityEndToEndTest extends
ActivityInstrumentationTestCase2<AccessibilityEndToEndTestActivity> {
@@ -82,35 +71,10 @@
* Timeout required for pending Binder calls or event processing to
* complete.
*/
- private static final long MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING = 500;
+ private static final long TIMEOUT_ASYNC_PROCESSING = 500;
/**
- * The count of the polling attempts during {@link #MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING}
- */
- private static final long COUNT_POLLING_ATTEMPTS = 10;
-
- /**
- * The package of the accessibility service mock interface.
- */
- private static final String DELEGATING_SERVICE_PACKAGE =
- "android.accessibilityservice.delegate";
-
- /**
- * The package of the delegating accessibility service interface.
- */
- private static final String DELEGATING_SERVICE_CLASS_NAME =
- "android.accessibilityservice.delegate.DelegatingAccessibilityService";
-
- /**
- * The package of the delegating accessibility service connection interface.
- */
- private static final String DELEGATING_SERVICE_CONNECTION_CLASS_NAME =
- "android.accessibilityservice.delegate."
- + "DelegatingAccessibilityService$DelegatingConnectionService";
-
- /**
- * Creates a new instance for testing
- * {@link AccessibilityEndToEndTestActivity}.
+ * Creates a new instance for testing {@link AccessibilityEndToEndTestActivity}.
*
* @throws Exception If any error occurs.
*/
@@ -124,7 +88,7 @@
// Wait for accessibility events to settle i.e. for all events generated
// while bringing the activity up to be delivered so they do not interfere.
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
// create and populate the expected event
AccessibilityEvent selectedEvent = AccessibilityEvent.obtain();
@@ -135,7 +99,7 @@
selectedEvent.setItemCount(2);
selectedEvent.setCurrentItemIndex(1);
selectedEvent.setEnabled(true);
- selectedEvent.setScrollable(true);
+ selectedEvent.setScrollable(false);
selectedEvent.setFromIndex(0);
selectedEvent.setToIndex(1);
@@ -153,7 +117,7 @@
});
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
}
@LargeTest
@@ -162,7 +126,7 @@
// Wait for accessibility events to settle i.e. for all events generated
// while bringing the activity up to be delivered so they do not interfere.
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
// create and populate the expected event
AccessibilityEvent clickedEvent = AccessibilityEvent.obtain();
@@ -186,7 +150,7 @@
});
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
}
@LargeTest
@@ -195,7 +159,7 @@
// Wait for accessibility events to settle i.e. for all events generated
// while bringing the activity up to be delivered so they do not interfere.
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
// create and populate the expected event
AccessibilityEvent longClickedEvent = AccessibilityEvent.obtain();
@@ -219,7 +183,7 @@
});
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
}
@LargeTest
@@ -228,7 +192,7 @@
// Wait for accessibility events to settle i.e. for all events generated
// while bringing the activity up to be delivered so they do not interfere.
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
// create and populate the expected event
AccessibilityEvent focusedEvent = AccessibilityEvent.obtain();
@@ -254,7 +218,7 @@
});
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
}
@LargeTest
@@ -270,7 +234,7 @@
});
// wait for the generated focus event to be dispatched
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
final String beforeText = activity.getString(R.string.text_input_blah);
@@ -302,7 +266,7 @@
});
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
}
@LargeTest
@@ -311,7 +275,7 @@
// Wait for accessibility events to settle i.e. for all events generated
// while bringing the activity up to be delivered so they do not interfere.
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
String title = activity.getString(R.string.alert_title);
String message = activity.getString(R.string.alert_message);
@@ -342,7 +306,7 @@
});
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
}
@LargeTest
@@ -351,7 +315,7 @@
// Wait for accessibility events to settle i.e. for all events generated
// while bringing the activity up to be delivered so they do not interfere.
- SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+ SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
String message = activity.getString(R.string.notification_message);
@@ -383,61 +347,18 @@
notificationManager.notify(notificationId, notification);
// verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
+ service.verify();
// remove the notification
notificationManager.cancel(notificationId);
}
- /**
- * Asserts the the mock accessibility service has been successfully verified
- * (which is it has received the expected method calls with expected
- * arguments) within the {@link #MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING}. The
- * verified state is checked by polling upon small intervals.
- *
- * @param service The service to verify.
- * @throws Exception If the verification has failed with exception after the
- * {@link #MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING}.
- */
- private void assertMockServiceVerifiedWithinTimeout(MockAccessibilityService service)
- throws Throwable {
- Throwable lastVerifyThrowable = null;
- long beginTime = SystemClock.uptimeMillis();
- long pollTmeout = MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING / COUNT_POLLING_ATTEMPTS;
+ static class MockAccessibilityService extends AccessibilityService {
- // poll until the timeout has elapsed
- while (SystemClock.uptimeMillis() - beginTime < MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING) {
- // sleep first since immediate call will always fail
- try {
- Thread.sleep(pollTmeout);
- } catch (InterruptedException ie) {
- /* ignore */
- }
-
- try {
- service.verify();
- // success - reset so it is not accept more events
- service.reset();
- return;
- } catch (IllegalStateException ise) {
- // this exception is thrown if the expected event is not
- // received yet, so we will keep trying within the timeout
- lastVerifyThrowable = ise;
- continue;
- } catch (Throwable t) {
- // we have just failed
- lastVerifyThrowable = t;
- break;
- }
- }
-
- // failure - reset so it is not accept more events
- service.reset();
- throw lastVerifyThrowable;
- }
-
- static class MockAccessibilityService extends AccessibilityService implements
- ServiceConnection {
+ /**
+ * Helper for connecting to the delegating accessibility service.
+ */
+ private final AccessibilityDelegateHelper mAccessibilityDelegateHelper;
/**
* The singleton instance.
@@ -451,6 +372,11 @@
new LinkedList<AccessibilityEvent>();
/**
+ * Reusable temporary builder.
+ */
+ private final StringBuilder mTempBuilder = new StringBuilder();
+
+ /**
* Interruption call this service expects to receive.
*/
private boolean mExpectedInterrupt;
@@ -461,14 +387,9 @@
private boolean mReplaying;
/**
- * Flag indicating if this mock is initialized.
+ * Lock for synchronization.
*/
- private boolean mInitialized;
-
- /**
- * The {@link Context} whose services to utilize.
- */
- private Context mContext;
+ private final Object mLock = new Object();
/**
* Gets the {@link MockAccessibilityService} singleton.
@@ -491,100 +412,57 @@
* Creates a new instance.
*/
private MockAccessibilityService(Context context) {
- mContext = context;
- ensureSetupAndBoundToDelegatingAccessibilityService();
- }
-
- /**
- * Ensures the required setup for the test performed and that it is bound to the
- * DelegatingAccessibilityService which runs in another process. The setup is
- * enabling accessibility and installing and enabling the delegating accessibility
- * service this test binds to.
- * </p>
- * Note: Please look at the class description for information why such an
- * approach is taken.
- */
- public void ensureSetupAndBoundToDelegatingAccessibilityService() {
- // check if accessibility is enabled
- AccessibilityManager accessibilityManager = (AccessibilityManager) mContext
- .getSystemService(Service.ACCESSIBILITY_SERVICE);
-
- if (!accessibilityManager.isEnabled()) {
- throw new IllegalStateException("Accessibility not enabled. "
- + "(Settings -> Accessibility)");
- }
-
- // check if the delegating service is running
- ComponentName delegatingServiceName = new ComponentName(
- DELEGATING_SERVICE_PACKAGE, DELEGATING_SERVICE_CLASS_NAME);
- ActivityManager activityManager = (ActivityManager) mContext
- .getSystemService(Service.ACTIVITY_SERVICE);
- boolean delegatingServiceRunning = false;
-
- for (RunningServiceInfo runningServiceInfo : activityManager.getRunningServices(100)) {
- if (delegatingServiceName.equals(runningServiceInfo.service)) {
- delegatingServiceRunning = true;
- break;
- }
- }
-
- if (!delegatingServiceRunning) {
- // delegating service not running, so check if it is installed at all
- try {
- PackageManager packageManager = mContext.getPackageManager();
- packageManager.getServiceInfo(delegatingServiceName, 0);
- } catch (NameNotFoundException nnfe) {
- throw new IllegalStateException("CtsDelegatingAccessibilityService.apk" +
- " not installed.");
- }
-
- throw new IllegalStateException("Delegating Accessibility Service not running."
- + "(Settings -> Accessibility -> Delegating Accessibility Service)");
- }
-
- Intent intent = new Intent().setClassName(DELEGATING_SERVICE_PACKAGE,
- DELEGATING_SERVICE_CONNECTION_CLASS_NAME);
- mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
-
- long beginTime = SystemClock.uptimeMillis();
- long pollTmeout = MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING / COUNT_POLLING_ATTEMPTS;
-
- // bind to the delegating service which runs in another process by
- // polling until the binder connection is established
- while (SystemClock.uptimeMillis() - beginTime < MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING) {
- if (mInitialized) {
- // success
- return;
- }
- try {
- Thread.sleep(pollTmeout);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
+ mAccessibilityDelegateHelper = new AccessibilityDelegateHelper(this);
+ mAccessibilityDelegateHelper.bindToDelegatingAccessibilityService(
+ context);
}
/**
* Starts replaying the mock.
*/
- private void replay() {
+ public void replay() {
mReplaying = true;
}
/**
- * Verifies if all expected service methods have been called.
+ * Verifies the mock service.
+ *
+ * @throws IllegalStateException If the verification has failed.
*/
- private void verify() {
- synchronized (this) {
- if (!mReplaying) {
- throw new IllegalStateException("Did you forget to call replay()");
- }
- if (mExpectedInterrupt) {
- throw new IllegalStateException("Expected call to #interrupt() not received");
- }
- if (!mExpectedEvents.isEmpty()) {
- throw new IllegalStateException("Expected a call to onAccessibilityEvent() for "
- + "events \"" + mExpectedEvents + "\" not received");
+ public void verify() throws IllegalStateException {
+ StringBuilder problems = mTempBuilder;
+ final long startTime = SystemClock.uptimeMillis();
+ synchronized (mLock) {
+ while (true) {
+ if (!mReplaying) {
+ throw new IllegalStateException("Did you forget to call replay()?");
+ }
+ if (!mExpectedInterrupt && mExpectedEvents.isEmpty()) {
+ reset();
+ return; // success
+ }
+ problems.setLength(0);
+ if (mExpectedInterrupt) {
+ problems.append("Expected call to #interrupt() not received.");
+ }
+ if (!mExpectedEvents.isEmpty()) {
+ problems.append("Expected a call to onAccessibilityEvent() for events \""
+ + mExpectedEvents + "\" not received.");
+ }
+ final long elapsedTime = SystemClock.uptimeMillis() - startTime;
+ final long remainingTime = TIMEOUT_ASYNC_PROCESSING - elapsedTime;
+ if (remainingTime <= 0) {
+ reset();
+ if (problems.length() > 0) {
+ throw new IllegalStateException(problems.toString());
+ }
+ return;
+ }
+ try {
+ mLock.wait(remainingTime);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
}
}
}
@@ -593,10 +471,11 @@
* Resets this instance so it can be reused.
*/
private void reset() {
- synchronized (this) {
+ synchronized (mLock) {
mExpectedEvents.clear();
mExpectedInterrupt = false;
mReplaying = false;
+ mLock.notifyAll();
}
}
@@ -620,7 +499,7 @@
@Override
public void onAccessibilityEvent(AccessibilityEvent receivedEvent) {
- synchronized (this) {
+ synchronized (mLock) {
if (!mReplaying) {
return;
}
@@ -629,48 +508,25 @@
}
AccessibilityEvent expectedEvent = mExpectedEvents.poll();
assertEqualsAccessiblityEvent(expectedEvent, receivedEvent);
+ mLock.notifyAll();
}
}
@Override
public void onInterrupt() {
- synchronized (this) {
+ synchronized (mLock) {
if (!mReplaying) {
return;
}
-
if (!mExpectedInterrupt) {
throw new IllegalStateException("Unexpected call to onInterrupt()");
}
-
mExpectedInterrupt = false;
+ mLock.notifyAll();
}
}
/**
- * {@inheritDoc ServiceConnection#onServiceConnected(ComponentName,IBinder)}
- */
- public void onServiceConnected(ComponentName name, IBinder service) {
- IAccessibilityServiceDelegateConnection connection =
- IAccessibilityServiceDelegateConnection.Stub
- .asInterface(service);
- try {
- connection.setAccessibilityServiceDelegate(new AccessibilityServiceDelegate(this));
- mInitialized = true;
- } catch (RemoteException re) {
- fail("Could not set delegate to the delegating service.");
- }
- }
-
- /**
- * {@inheritDoc ServiceConnection#onServiceDisconnected(ComponentName)}
- */
- public void onServiceDisconnected(ComponentName name) {
- mInitialized = false;
- /* do nothing */
- }
-
- /**
* Compares all properties of the <code>expectedEvent</code> and the
* <code>receviedEvent</code> to verify that the received event is the
* one that is expected.
@@ -764,84 +620,5 @@
receivedTextIterator.next().toString());
}
}
-
- /**
- * This class is the delegate called by the DelegatingAccessibilityService.
- */
- private class AccessibilityServiceDelegate extends
- IAccessibilityServiceDelegate.Stub implements Handler.Callback {
-
- /**
- * Tag for logging.
- */
- private static final String LOG_TAG = "AccessibilityServiceDelegate";
-
- /**
- * Message type for calling {@link #onInterrupt()}
- */
- private static final int DO_ON_INTERRUPT = 10;
-
- /**
- * Message type for calling {@link #onAccessibilityEvent(AccessibilityEvent)}
- */
- private static final int DO_ON_ACCESSIBILITY_EVENT = 20;
-
- /**
- * Caller for handling {@link Message}s
- */
- private final Handler mHandler;
-
- /**
- * The {@link MockAccessibilityService} to which to delegate;
- */
- private MockAccessibilityService mMockAccessibilityService;
-
- /**
- * Creates a new instance.
- *
- * @param mockAccessibilityService The service to whcih to delegate.
- */
- public AccessibilityServiceDelegate(MockAccessibilityService mockAccessibilityService) {
- mMockAccessibilityService = mockAccessibilityService;
- mHandler = new Handler(this);
- }
-
- /**
- * {@inheritDoc IAccessibilityServiceDelegate#onAccessibilityEvent(AccessibilityEvent)}
- */
- public void onAccessibilityEvent(AccessibilityEvent event) {
- Message message = Message.obtain(mHandler, DO_ON_ACCESSIBILITY_EVENT, event);
- mHandler.sendMessage(message);
- }
-
- /**
- * {@inheritDoc IAccessibilityServiceDelegate#onInterrupt()}
- */
- public void onInterrupt() {
- Message message = mHandler.obtainMessage(DO_ON_INTERRUPT);
- mHandler.sendMessage(message);
- }
-
- /**
- * {@inheritDoc Handler.Callback#handleMessage(Message)}
- */
- public boolean handleMessage(Message message) {
- switch (message.what) {
- case DO_ON_ACCESSIBILITY_EVENT:
- AccessibilityEvent event = (AccessibilityEvent) message.obj;
- if (event != null) {
- mMockAccessibilityService.onAccessibilityEvent(event);
- event.recycle();
- }
- return true;
- case DO_ON_INTERRUPT:
- mMockAccessibilityService.onInterrupt();
- return true;
- default:
- Log.w(LOG_TAG, "Unknown message type " + message.what);
- return false;
- }
- }
- }
}
}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
new file mode 100644
index 0000000..6f72a75
--- /dev/null
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2011 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.accessibilityservice.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.cts.accessibilityservice.R;
+
+/**
+ * Activity for testing the accessibility APIs for querying of
+ * the screen content. These APIs allow exploring the screen and
+ * requesting an action to be performed on a given view from an
+ * AccessiiblityService.
+ */
+public class AccessibilityWindowQueryActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.query_window_test);
+
+ findViewById(R.id.button5).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ /* do nothing */
+ }
+ });
+ findViewById(R.id.button5).setOnLongClickListener(new View.OnLongClickListener() {
+ public boolean onLongClick(View v) {
+ return true;
+ }
+ });
+ }
+}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java
new file mode 100644
index 0000000..d798052
--- /dev/null
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java
@@ -0,0 +1,497 @@
+/**
+ * Copyright (C) 2011 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.accessibilityservice.cts;
+
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT;
+
+import android.accessibilityservice.AccessibilityService;
+import android.content.Context;
+import android.graphics.Rect;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.cts.accessibilityservice.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Activity for testing the accessibility APIs for querying of
+ * the screen content. These APIs allow exploring the screen and
+ * requesting an action to be performed on a given view from an
+ * AccessiiblityService.
+ * <p>
+ * Note: The accessibility CTS tests are composed of two APKs, one with delegating
+ * accessibility service and another with the instrumented activity and test cases.
+ * The motivation for two APKs design is that CTS tests cannot access the secure
+ * settings which is required for enabling accessibility services, hence there is
+ * no way to manipulate accessibility settings programmaticaly. Further, manually
+ * enabling an accessibility service in the tests APK will not work either because
+ * the instrumentation restarts the process under test which would break the binding
+ * between the accessibility service and the system.
+ * <p>
+ * Therefore, manual installation of the
+ * <strong>CtsAccessibilityServiceTestMockService.apk</strong>
+ * whose source is located at <strong>cts/tests/accessibility</strong> is required.
+ * Once the former package has been installed the service must be enabled
+ * (Settings -> Accessibility -> Delegating Accessibility Service), and then the CTS tests
+ * in this package can be successfully run.
+ * </p>
+ */
+public class AccessibilityWindowQueryActivityTest
+ extends ActivityInstrumentationTestCase2<AccessibilityWindowQueryActivity> {
+
+ private interface AccessibilityEventFilter {
+ public boolean accept(AccessibilityEvent event);
+ }
+
+ public AccessibilityWindowQueryActivityTest() {
+ super(AccessibilityWindowQueryActivity.class);
+ }
+
+ @Override
+ public void setUp() {
+ // start the activity and wait for a handle to its window root.
+ startActivityAndWaitForFirstEvent();
+ }
+
+ @LargeTest
+ public void testFindByText() throws Exception {
+ // find a view by text
+ List<AccessibilityNodeInfo> buttons = findAccessibilityNodeInfosByText(
+ getAwaitedAccessibilityEventSource(), "butto");
+ assertEquals(9, buttons.size());
+ }
+
+ @LargeTest
+ public void testFindByContentDescription() throws Exception {
+ // find a view by text
+ AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.contentDescription);
+ assertNotNull(button);
+ }
+
+ @LargeTest
+ public void testTraverseWindow() throws Exception {
+ // make list of expected nodes
+ List<String> classNameAndTextList = new ArrayList<String>();
+ classNameAndTextList.add("com.android.internal.policy.impl.PhoneWindow$DecorView");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.FrameLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.ButtonButton1");
+ classNameAndTextList.add("android.widget.ButtonButton2");
+ classNameAndTextList.add("android.widget.ButtonButton3");
+ classNameAndTextList.add("android.widget.ButtonButton4");
+ classNameAndTextList.add("android.widget.ButtonButton5");
+ classNameAndTextList.add("android.widget.ButtonButton6");
+ classNameAndTextList.add("android.widget.ButtonButton7");
+ classNameAndTextList.add("android.widget.ButtonButton8");
+ classNameAndTextList.add("android.widget.ButtonButton9");
+
+ Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
+ fringe.add(getAwaitedAccessibilityEventSource());
+
+ // do a BFS traversal and check nodes
+ while (!fringe.isEmpty()) {
+ AccessibilityNodeInfo current = fringe.poll();
+
+ CharSequence text = current.getText();
+ String receivedClassNameAndText = current.getClassName().toString()
+ + ((text != null) ? text.toString() : "");
+ String expectedClassNameAndText = classNameAndTextList.remove(0);
+
+ assertEquals("Did not get the expected node info",
+ expectedClassNameAndText, receivedClassNameAndText);
+
+ final int childCount = current.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ AccessibilityNodeInfo child = current.getChild(i);
+ fringe.add(child);
+ }
+ }
+ }
+
+ @LargeTest
+ public void testPerformActionFocus() throws Exception {
+ // find a view and make sure it is not focused
+ AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.button5);
+ assertFalse(button.isFocused());
+
+ // focus the view
+ assertTrue(button.performAction(ACTION_FOCUS));
+
+ // find the view again and make sure it is focused
+ button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+ R.string.button5);
+ assertTrue(button.isFocused());
+ }
+
+ @LargeTest
+ public void testPerformActionClearFocus() throws Exception {
+ // find a view and make sure it is not focused
+ AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.button5);
+ assertFalse(button.isFocused());
+
+ // focus the view
+ assertTrue(button.performAction(ACTION_FOCUS));
+
+ // find the view again and make sure it is focused
+ button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+ R.string.button5);
+ assertTrue(button.isFocused());
+
+ // unfocus the view
+ assertTrue(button.performAction(ACTION_CLEAR_FOCUS));
+
+ // find the view again and make sure it is not focused
+ button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+ R.string.button5);
+ assertFalse(button.isFocused());
+ }
+
+ @LargeTest
+ public void testPerformActionSelect() throws Exception {
+ // find a view and make sure it is not selected
+ AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.button5);
+ assertFalse(button.isSelected());
+
+ // select the view
+ assertTrue(button.performAction(ACTION_SELECT));
+
+ // find the view again and make sure it is selected
+ button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+ R.string.button5);
+ assertTrue(button.isSelected());
+ }
+
+ @LargeTest
+ public void testPerformActionClearSelection() throws Exception {
+ // find a view and make sure it is not selected
+ AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.button5);
+ assertFalse(button.isSelected());
+
+ // select the view
+ assertTrue(button.performAction(ACTION_SELECT));
+
+ // find the view again and make sure it is selected
+ button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),R
+ .string.button5);
+
+ assertTrue(button.isSelected());
+
+ // unselect the view
+ assertTrue(button.performAction(ACTION_CLEAR_SELECTION));
+
+ // find the view again and make sure it is not selected
+ button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+ R.string.button5);
+ assertFalse(button.isSelected());
+ }
+
+ @LargeTest
+ public void testGetEventSource() throws Exception {
+ // find a view and make sure it is not focused
+ final AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.button5);
+ assertFalse(button.isSelected());
+
+ // focus and wait for the event
+ AccessibilityQueryBridge bridge = AccessibilityQueryBridge.getInstance(
+ getInstrumentation().getContext());
+ bridge.perfromActionAndWaitForEvent(
+ new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(button.performAction(ACTION_FOCUS));
+ }
+ },
+ new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+ });
+
+ // check that last event source
+ AccessibilityNodeInfo source = getAwaitedAccessibilityEventSource();
+ assertNotNull(source);
+
+ // bounds
+ Rect buttonBounds = new Rect();
+ button.getBoundsInParent(buttonBounds);
+ Rect sourceBounds = new Rect();
+ source.getBoundsInParent(sourceBounds);
+
+ assertEquals(buttonBounds.left, sourceBounds.left);
+ assertEquals(buttonBounds.right, sourceBounds.right);
+ assertEquals(buttonBounds.top, sourceBounds.top);
+ assertEquals(buttonBounds.bottom, sourceBounds.bottom);
+
+ // char sequence attributes
+ assertEquals(button.getPackageName(), source.getPackageName());
+ assertEquals(button.getClassName(), source.getClassName());
+ assertEquals(button.getText(), source.getText());
+ assertSame(button.getContentDescription(), source.getContentDescription());
+
+ // boolean attributes
+ assertSame(button.isFocusable(), source.isFocusable());
+ assertSame(button.isClickable(), source.isClickable());
+ assertSame(button.isEnabled(), source.isEnabled());
+ assertNotSame(button.isFocused(), source.isFocused());
+ assertSame(button.isLongClickable(), source.isLongClickable());
+ assertSame(button.isPassword(), source.isPassword());
+ assertSame(button.isSelected(), source.isSelected());
+ assertSame(button.isCheckable(), source.isCheckable());
+ assertSame(button.isChecked(), source.isChecked());
+ }
+
+ @LargeTest
+ public void testObjectContract() throws Exception {
+ // find a view and make sure it is not focused
+ AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+ getAwaitedAccessibilityEventSource(), R.string.button5);
+ AccessibilityNodeInfo parent = button.getParent();
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ AccessibilityNodeInfo child = parent.getChild(i);
+ assertNotNull(child);
+ if (child.equals(button)) {
+ assertEquals("Equal objects must have same hasCode.", button.hashCode(),
+ child.hashCode());
+ return;
+ }
+ }
+ fail("Parent's children do not have the info whose parent is the parent.");
+ }
+
+ @Override
+ protected void scrubClass(Class<?> testCaseClass) {
+ /* intentionally do not scrub */
+ }
+
+ /**
+ * Starts the activity under tests and waits for the first accessibility
+ * event from that activity.
+ */
+ private void startActivityAndWaitForFirstEvent() {
+ AccessibilityQueryBridge bridge = AccessibilityQueryBridge.getInstance(
+ getInstrumentation().getContext());
+ bridge.perfromActionAndWaitForEvent(
+ new Runnable() {
+ @Override
+ public void run() {
+ getActivity();
+ }
+ },
+ new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ final int eventType = event.getEventType();
+ CharSequence packageName = event.getPackageName();
+ Context targetContext = getInstrumentation().getTargetContext();
+ return (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ && targetContext.getPackageName().equals(packageName));
+ }
+ });
+ }
+
+ /**
+ * @return The source of the last accessibility event.
+ */
+ private AccessibilityNodeInfo getAwaitedAccessibilityEventSource() {
+ AccessibilityQueryBridge bridge = AccessibilityQueryBridge.getInstance(
+ getInstrumentation().getContext());
+ AccessibilityEvent event = bridge.getAwaitedAccessibilityEvent();
+ if (event != null) {
+ return event.getSource();
+ }
+ return null;
+ }
+
+ private List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(AccessibilityNodeInfo root,
+ String text) {
+ if (root != null) {
+ return root.findAccessibilityNodeInfosByText(text);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Finds the first accessibility info that contains text. The search starts
+ * from the given <code>root</code>
+ *
+ * @param root Node from which to start the search.
+ * @param resId Resource id of the searched text.
+ * @return The node with this text or null.
+ */
+ private AccessibilityNodeInfo findAccessibilityNodeInfoByText(AccessibilityNodeInfo root,
+ int resId) {
+ return findAccessibilityNodeInfoByText(root,
+ getInstrumentation().getContext().getString(resId));
+ }
+
+ /**
+ * Finds the first accessibility info that contains text. The search starts
+ * from the given <code>root</code>
+ *
+ * @param root Node from which to start the search.
+ * @param text The searched text.
+ * @return The node with this text or null.
+ */
+ private AccessibilityNodeInfo findAccessibilityNodeInfoByText(AccessibilityNodeInfo root,
+ String text) {
+ List<AccessibilityNodeInfo> nodes = findAccessibilityNodeInfosByText(root, text);
+ if (nodes != null && !nodes.isEmpty()) {
+ return nodes.get(0);
+ }
+ return null;
+ }
+
+ /**
+ * This class serves as a bridge for querying the screen content.
+ * The bride is connected of a delegating accessibility service.
+ */
+ static class AccessibilityQueryBridge extends AccessibilityService {
+
+ /**
+ * The singleton instance.
+ */
+ private static AccessibilityQueryBridge sInstance;
+
+ /**
+ * Helper for connecting to the delegating accessibility service.
+ */
+ private final AccessibilityDelegateHelper mAccessibilityDelegateHelper;
+
+ /**
+ * The last received accessibility event.
+ */
+ private AccessibilityEvent mAwaitedAccessbiliyEvent;
+
+ /**
+ * Barrier for synchronizing waiting client and this bridge.
+ */
+ private CyclicBarrier mBarrier = new CyclicBarrier(2);
+
+ /**
+ * Filter for the currently waited event.
+ */
+ private AccessibilityEventFilter mWaitedFilter;
+
+ /**
+ * Gets the {@link AccessibilityQueryBridge} singleton.
+ *
+ * @param context A context handle.
+ * @return The mock service.
+ */
+ public static AccessibilityQueryBridge getInstance(Context context) {
+ if (sInstance == null) {
+ // since we do bind once and do not unbind from the delegating
+ // service and JUnit3 does not support @BeforeTest and @AfterTest,
+ // we will leak a service connection after the test but this
+ // does not affect the test results and the test is twice as fast
+ sInstance = new AccessibilityQueryBridge(context);
+ }
+ return sInstance;
+ }
+
+ private AccessibilityQueryBridge(Context context) {
+ mAccessibilityDelegateHelper = new AccessibilityDelegateHelper(this);
+ mAccessibilityDelegateHelper.bindToDelegatingAccessibilityService(context);
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ if (mWaitedFilter != null && mWaitedFilter.accept(event)) {
+ mAwaitedAccessbiliyEvent = AccessibilityEvent.obtain(event);
+ awaitOnBarrier();
+ }
+ }
+
+ @Override
+ public void onInterrupt() {
+ /* do nothing */
+ }
+
+ /**
+ * @return The event that was waited for.
+ */
+ public AccessibilityEvent getAwaitedAccessibilityEvent() {
+ return mAwaitedAccessbiliyEvent;
+ }
+
+ /**
+ * Performs an action and waits for the resulting event.
+ *
+ * @param action The action to perform.
+ * @param filter Filter for recognizing the waited event.
+ */
+ public void perfromActionAndWaitForEvent(Runnable action,
+ AccessibilityEventFilter filter) {
+ reset();
+ mWaitedFilter = filter;
+ action.run();
+ awaitOnBarrier();
+ }
+
+ /**
+ * Rests the internal state.
+ */
+ private void reset() {
+ if (mAwaitedAccessbiliyEvent != null) {
+ mAwaitedAccessbiliyEvent.recycle();
+ mAwaitedAccessbiliyEvent = null;
+ }
+ mBarrier.reset();
+ mWaitedFilter = null;
+ }
+
+ /**
+ * Calls await of the barrier taking care of the exceptions.
+ */
+ private void awaitOnBarrier() {
+ try {
+ mBarrier.await(AccessibilityDelegateHelper.TIMEOUT_ASYNC_PROCESSING,
+ TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ } catch (BrokenBarrierException bbe) {
+ /* ignore */
+ } catch (TimeoutException te) {
+ /* ignore */
+ }
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
index fb06d14..309bfc7 100644
--- a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
@@ -1986,30 +1986,26 @@
for (int i = 0; i < settings.length; i++) {
if (Parameters.SCENE_MODE_AUTO.equals(settings[i].mScene)) continue;
- // If the scene mode overrides the flash mode, it should also override
- // the supported flash modes.
+ // Both the setting and the supported settings may change. It is
+ // allowed to have more than one supported settings in scene
+ // modes. For example, in night scene mode, supported flash
+ // modes can have on and off.
if (autoSceneMode.mSupportedFlash != null) {
- if (!autoSceneMode.mFlash.equals(settings[i].mFlash)) {
- assertEquals(1, settings[i].mSupportedFlash.size());
- assertTrue(settings[i].mSupportedFlash.contains(settings[i].mFlash));
+ assertTrue(settings[i].mSupportedFlash.contains(settings[i].mFlash));
+ for (String mode: settings[i].mSupportedFlash) {
+ assertTrue(autoSceneMode.mSupportedFlash.contains(mode));
}
}
-
- // If the scene mode overrides the focus mode, it should also override
- // the supported focus modes.
if (autoSceneMode.mSupportedFocus != null) {
- if (!autoSceneMode.mFocus.equals(settings[i].mFocus)) {
- assertEquals(1, settings[i].mSupportedFocus.size());
- assertTrue(settings[i].mSupportedFocus.contains(settings[i].mFocus));
+ assertTrue(settings[i].mSupportedFocus.contains(settings[i].mFocus));
+ for (String mode: settings[i].mSupportedFocus) {
+ assertTrue(autoSceneMode.mSupportedFocus.contains(mode));
}
}
-
- // If the scene mode overrides the white balance, it should also override
- // the supported white balance.
if (autoSceneMode.mSupportedWhiteBalance != null) {
- if (!autoSceneMode.mWhiteBalance.equals(settings[i].mWhiteBalance)) {
- assertEquals(1, settings[i].mSupportedWhiteBalance.size());
- assertTrue(settings[i].mSupportedWhiteBalance.contains(settings[i].mWhiteBalance));
+ assertTrue(settings[i].mSupportedWhiteBalance.contains(settings[i].mWhiteBalance));
+ for (String mode: settings[i].mSupportedWhiteBalance) {
+ assertTrue(autoSceneMode.mSupportedWhiteBalance.contains(mode));
}
}
}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerStreamingTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerStreamingTest.java
index a4db981..652c5bd 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerStreamingTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerStreamingTest.java
@@ -108,8 +108,10 @@
// Streaming HLS video from YouTube
public void testHLS() throws Exception {
// Play stream for 60 seconds
- playLiveVideoTest("http://www.youtube.com/api/manifest/hls/ns/test/"
- + "id/1?user=android-device-test&m3u8=1", 60 * 1000);
+ playLiveVideoTest("http://www.youtube.com/api/manifest/hls/ns/yt-live/id/UeHRu5LFHaU"
+ + "?ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire&signature"
+ + "=313BE90526F2D815EB207156E1460C7E8EEC2503.799EE7B8B7CE3F2957060DB27C216077"
+ + "0303EBD2&key=test_key1&user=android-device-test&m3u8=1", 60 * 1000);
}
// Streaming audio from local HTTP server
diff --git a/tests/tests/os/src/android/os/cts/FileObserverTest.java b/tests/tests/os/src/android/os/cts/FileObserverTest.java
index d44e22b..b51f804 100644
--- a/tests/tests/os/src/android/os/cts/FileObserverTest.java
+++ b/tests/tests/os/src/android/os/cts/FileObserverTest.java
@@ -16,20 +16,15 @@
package android.os.cts;
+import android.os.FileObserver;
+import android.test.AndroidTestCase;
+
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
-import android.os.FileObserver;
-import android.test.AndroidTestCase;
-import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetClass;
-import dalvik.annotation.TestTargetNew;
-import dalvik.annotation.TestTargets;
-import dalvik.annotation.ToBeFixed;
-
-@TestTargetClass(FileObserver.class)
public class FileObserverTest extends AndroidTestCase {
private File mTestFile;
@@ -73,18 +68,6 @@
}
}
- @TestTargets({
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "FileObserver",
- args = {java.lang.String.class}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "FileObserver",
- args = {java.lang.String.class, int.class}
- )
- })
public void testConstructor() {
// new the instance
new MockFileObserver(PATH);
@@ -104,31 +87,6 @@
* moved from dir observer should get moved-from event,
* moved to dir observer should get moved-to event.
*/
- @TestTargets({
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "startWatching",
- args = {}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "onEvent",
- args = {int.class, java.lang.String.class}
- ),
- @TestTargetNew(
- level = TestLevel.PARTIAL,
- method = "stopWatching",
- args = {}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "finalize",
- args = {}
- )
- })
- @ToBeFixed(bug = "1725406", explanation =
- "android.os.FileObserver#onEvent(int event, String path) still got event "
- + "after called FileObserver#stopWatching()")
public void testFileObserver() throws Exception {
MockFileObserver fileObserver = null;
int[] expected = null;
@@ -172,7 +130,7 @@
mTestDir.delete();
expected = new int[] {FileObserver.CREATE,
- FileObserver.OPEN, FileObserver.CLOSE_NOWRITE,
+ FileObserver.OPEN, FileObserver.CLOSE_WRITE,
FileObserver.DELETE, FileObserver.DELETE_SELF, UNDEFINED};
moveEvents = waitForEvent(fileObserver);
assertEventsEquals(expected, moveEvents);
@@ -217,9 +175,15 @@
}
private void assertEventsEquals(final int[] expected, final FileEvent[] moveEvents) {
- assertEquals(expected.length, moveEvents.length);
+ List<Integer> expectedEvents = new ArrayList<Integer>();
for (int i = 0; i < expected.length; i++) {
- assertEquals(expected[i], moveEvents[i].event);
+ expectedEvents.add(expected[i]);
+ }
+ List<FileEvent> actualEvents = Arrays.asList(moveEvents);
+ String message = "Expected: " + expectedEvents + " Actual: " + actualEvents;
+ assertEquals(message, expected.length, moveEvents.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(message, expected[i], moveEvents[i].event);
}
}
@@ -239,6 +203,11 @@
this.event = event;
this.path = path;
}
+
+ @Override
+ public String toString() {
+ return Integer.toString(event);
+ }
}
/*
diff --git a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
index 4dddd5d..3822035 100644
--- a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
+++ b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
@@ -80,7 +80,10 @@
"android.deviceadmin.cts",
// APK for an activity that collects information printed in the CTS report header
- "android.tests.devicesetup"
+ "android.tests.devicesetup",
+
+ // APK for the Android core tests runner used only during CTS
+ "android.core.tests.runner"
));
private boolean isWhitelistedPackage(String packageName) {
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index f0e7d07..b06109a 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -2267,13 +2267,14 @@
public void run() {
mWebView.setWebViewClient(webViewClient);
mWebView.setWebChromeClient(new LoadCompleteWebChromeClient());
+ mWebView.clearSslPreferences();
mWebView.loadUrl(errorUrl);
}
});
waitForUiThreadDone();
- assertEquals(webViewClient.webView(), mWebView);
- assertEquals(webViewClient.errorUrl(), errorUrl);
+ assertEquals(mWebView, webViewClient.webView());
+ assertEquals(errorUrl, webViewClient.errorUrl());
}
@TestTargetNew(
@@ -2301,7 +2302,7 @@
waitForUiThreadDone();
runTestOnUiThread(new Runnable() {
public void run() {
- assertEquals(mWebView.getTitle(), TestHtmlConstants.HELLO_WORLD_TITLE);
+ assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mWebView.getTitle());
}
});
}
@@ -2325,6 +2326,7 @@
public void run() {
mWebView.setWebViewClient(new MockWebViewClient());
mWebView.setWebChromeClient(new LoadCompleteWebChromeClient());
+ mWebView.clearSslPreferences();
mWebView.loadUrl(url);
}
});
@@ -2351,6 +2353,7 @@
public void run() {
mWebView.setWebViewClient(webViewClient);
mWebView.setWebChromeClient(new LoadCompleteWebChromeClient());
+ mWebView.clearSslPreferences();
mWebView.loadUrl(firstUrl);
}
});
@@ -2370,7 +2373,7 @@
assertFalse(webViewClient.wasOnReceivedSslErrorCalled());
runTestOnUiThread(new Runnable() {
public void run() {
- assertEquals(mWebView.getTitle(), "Second page");
+ assertEquals("Second page", mWebView.getTitle());
}
});
}
@@ -2390,6 +2393,7 @@
public void run() {
mWebView.setWebViewClient(webViewClient);
mWebView.setWebChromeClient(new LoadCompleteWebChromeClient());
+ mWebView.clearSslPreferences();
mWebView.loadUrl(firstUrl);
}
});
@@ -2412,7 +2416,7 @@
assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
runTestOnUiThread(new Runnable() {
public void run() {
- assertEquals(mWebView.getTitle(), "Second page");
+ assertEquals("Second page", mWebView.getTitle());
}
});
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
index 3ae1f06..62827d5 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
@@ -16,6 +16,8 @@
package com.android.cts.tradefed.result;
+import android.tests.getinfo.DeviceInfoConstants;
+
import com.android.cts.tradefed.build.CtsBuildHelper;
import com.android.cts.tradefed.build.CtsBuildProvider;
import com.android.cts.tradefed.device.DeviceInfoCollector;
@@ -27,18 +29,15 @@
import com.android.tradefed.build.IFolderBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.result.TestSummary;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.StreamUtil;
import org.kxml2.io.KXmlSerializer;
-import android.tests.getinfo.DeviceInfoConstants;
-
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -57,7 +56,7 @@
* <p/>
* Outputs xml in format governed by the cts_result.xsd
*/
-public class CtsXmlResultReporter extends CollectingTestListener {
+public class CtsXmlResultReporter implements ITestInvocationListener {
private static final String LOG_TAG = "CtsXmlResultReporter";
@@ -88,14 +87,18 @@
@Option(name = CtsTest.PLAN_OPTION, description = "the test plan to run.")
private String mPlanName = "NA";
+ // listen in on the continue-session option provided to CtsTest
+ @Option(name = CtsTest.CONTINUE_OPTION, description = "the test result session to continue.")
+ private Integer mContinueSessionId = null;
+
@Option(name = "quiet-output", description = "Mute display of test results.")
private boolean mQuietOutput = false;
protected IBuildInfo mBuildInfo;
-
private String mStartTime;
-
private String mDeviceSerial;
+ private TestResults mResults = new TestResults();
+ private TestPackageResult mCurrentPkgResult = null;
public void setReportDir(File reportDir) {
mReportDir = reportDir;
@@ -106,29 +109,51 @@
*/
@Override
public void invocationStarted(IBuildInfo buildInfo) {
- super.invocationStarted(buildInfo);
- if (mReportDir == null) {
- if (!(buildInfo instanceof IFolderBuildInfo)) {
- throw new IllegalArgumentException("build info is not a IFolderBuildInfo");
- }
-
- IFolderBuildInfo ctsBuild = (IFolderBuildInfo)buildInfo;
- try {
- CtsBuildHelper buildHelper = new CtsBuildHelper(ctsBuild.getRootDir());
- buildHelper.validateStructure();
- mReportDir = buildHelper.getResultsDir();
- } catch (FileNotFoundException e) {
- throw new IllegalArgumentException("Invalid CTS build", e);
- }
+ mBuildInfo = buildInfo;
+ if (!(buildInfo instanceof IFolderBuildInfo)) {
+ throw new IllegalArgumentException("build info is not a IFolderBuildInfo");
}
- // create a unique directory for saving results, using old cts host convention
- // TODO: in future, consider using LogFileSaver to create build-specific directories
- mReportDir = new File(mReportDir, TimeUtil.getResultTimestamp());
- mReportDir.mkdirs();
- mStartTime = getTimestamp();
+ IFolderBuildInfo ctsBuild = (IFolderBuildInfo)buildInfo;
mDeviceSerial = buildInfo.getDeviceSerial() == null ? "unknown_device" :
- buildInfo.getDeviceSerial();
- logResult("Created result dir %s", mReportDir.getName());
+ buildInfo.getDeviceSerial();
+ if (mContinueSessionId != null) {
+ CLog.d("Continuing session %d", mContinueSessionId);
+ // reuse existing directory
+ TestResultRepo resultRepo = new TestResultRepo(getBuildHelper(ctsBuild).getResultsDir());
+ mResults = resultRepo.getResult(mContinueSessionId);
+ if (mResults == null) {
+ throw new IllegalArgumentException(String.format("Could not find session %d",
+ mContinueSessionId));
+ }
+ mPlanName = resultRepo.getSummaries().get(mContinueSessionId).getTestPlan();
+ mStartTime = resultRepo.getSummaries().get(mContinueSessionId).getTimestamp();
+ mReportDir = resultRepo.getReportDir(mContinueSessionId);
+ } else {
+ if (mReportDir == null) {
+ mReportDir = getBuildHelper(ctsBuild).getResultsDir();
+ }
+ // create a unique directory for saving results, using old cts host convention
+ // TODO: in future, consider using LogFileSaver to create build-specific directories
+ mReportDir = new File(mReportDir, TimeUtil.getResultTimestamp());
+ mReportDir.mkdirs();
+ mStartTime = getTimestamp();
+ logResult("Created result dir %s", mReportDir.getName());
+ }
+ }
+
+ /**
+ * Helper method to retrieve the {@link CtsBuildHelper}.
+ * @param ctsBuild
+ * @return
+ */
+ CtsBuildHelper getBuildHelper(IFolderBuildInfo ctsBuild) {
+ CtsBuildHelper buildHelper = new CtsBuildHelper(ctsBuild.getRootDir());
+ try {
+ buildHelper.validateStructure();
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("Invalid CTS build", e);
+ }
+ return buildHelper;
}
/**
@@ -149,30 +174,51 @@
}
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void testRunStarted(String name, int numTests) {
+ if (mCurrentPkgResult != null && !name.equals(mCurrentPkgResult.getAppPackageName())) {
+ // display results from previous run
+ logCompleteRun(mCurrentPkgResult);
+ }
if (name.equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
logResult("Collecting device info");
- } else if (!name.equals(getCurrentRunResults().getName())) {
- // this is a new run
- if (getCurrentRunResults().isRunComplete()) {
- // display results from previous run
- logCompleteRun(getCurrentRunResults());
- }
+ } else if (mCurrentPkgResult == null || !name.equals(
+ mCurrentPkgResult.getAppPackageName())) {
logResult("-----------------------------------------");
logResult("Test package %s started", name);
logResult("-----------------------------------------");
}
- super.testRunStarted(name, numTests);
+ mCurrentPkgResult = mResults.getOrCreatePackage(name);
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testStarted(TestIdentifier test) {
+ mCurrentPkgResult.insertTest(test);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+ mCurrentPkgResult.reportTestFailure(test, CtsTestStatus.FAIL, trace);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
@Override
public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
- super.testEnded(test, testMetrics);
- TestRunResult results = getCurrentRunResults();
- TestResult result = results.getTestResults().get(test);
+ mCurrentPkgResult.reportTestEnded(test);
+ Test result = mCurrentPkgResult.findTest(test);
String stack = result.getStackTrace() == null ? "" : "\n" + result.getStackTrace();
- logResult("%s#%s %s %s", test.getClassName(), test.getTestName(), result.getStatus(),
+ logResult("%s#%s %s %s", test.getClassName(), test.getTestName(), result.getResult(),
stack);
}
@@ -180,12 +226,19 @@
* {@inheritDoc}
*/
@Override
+ public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
+ mCurrentPkgResult.populateMetrics(runMetrics);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public void invocationEnded(long elapsedTime) {
// display the results of the last completed run
- if (getCurrentRunResults().isRunComplete()) {
- logCompleteRun(getCurrentRunResults());
+ if (mCurrentPkgResult != null) {
+ logCompleteRun(mCurrentPkgResult);
}
- super.invocationEnded(elapsedTime);
createXmlResult(mReportDir, mStartTime, elapsedTime);
copyFormattingFiles(mReportDir);
zipResults(mReportDir);
@@ -199,16 +252,15 @@
}
}
- private void logCompleteRun(TestRunResult runResults) {
- if (runResults.getName().equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
+ private void logCompleteRun(TestPackageResult pkgResult) {
+ if (pkgResult.getAppPackageName().equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
logResult("Device info collection complete");
return;
}
logResult("%s package complete: Passed %d, Failed %d, Not Executed %d",
- runResults.getName(), runResults.getNumPassedTests(),
- runResults.getNumFailedTests() +
- runResults.getNumErrorTests(),
- runResults.getNumIncompleteTests());
+ pkgResult.getAppPackageName(), pkgResult.countTests(CtsTestStatus.PASS),
+ pkgResult.countTests(CtsTestStatus.FAIL),
+ pkgResult.countTests(CtsTestStatus.NOT_EXECUTED));
}
/**
@@ -230,8 +282,10 @@
serializeResultsDoc(serializer, startTimestamp, endTime);
serializer.endDocument();
String msg = String.format("XML test result file generated at %s. Passed %d, " +
- "Failed %d, Not Executed %d", mReportDir.getName(), getNumPassedTests(),
- getNumFailedTests() + getNumErrorTests(), getNumIncompleteTests());
+ "Failed %d, Not Executed %d", mReportDir.getName(),
+ mResults.countTests(CtsTestStatus.PASS),
+ mResults.countTests(CtsTestStatus.FAIL),
+ mResults.countTests(CtsTestStatus.NOT_EXECUTED));
logResult(msg);
logResult("Time: %s", TimeUtil.formatElapsedTime(elapsedTime));
} catch (IOException e) {
@@ -260,7 +314,7 @@
serializeDeviceInfo(serializer);
serializeHostInfo(serializer);
serializeTestSummary(serializer);
- serializeTestResults(serializer);
+ mResults.serialize(serializer);
// TODO: not sure why, but the serializer doesn't like this statement
//serializer.endTag(ns, RESULT_TAG);
}
@@ -273,15 +327,16 @@
private void serializeDeviceInfo(KXmlSerializer serializer) throws IOException {
serializer.startTag(ns, "DeviceInfo");
- TestRunResult deviceInfoResult = findRunResult(DeviceInfoCollector.APP_PACKAGE_NAME);
- if (deviceInfoResult == null) {
- Log.w(LOG_TAG, String.format("Could not find device info run %s",
- DeviceInfoCollector.APP_PACKAGE_NAME));
+ Map<String, String> deviceInfoMetrics = mResults.getDeviceInfoMetrics();
+ if (deviceInfoMetrics == null || deviceInfoMetrics.isEmpty()) {
+ // this might be expected, if device info collection was turned off
+ CLog.d("Could not find device info");
return;
}
+
// Extract metrics that need extra handling, and then dump the remainder into BuildInfo
Map<String, String> metricsCopy = new HashMap<String, String>(
- deviceInfoResult.getRunMetrics());
+ deviceInfoMetrics);
serializer.startTag(ns, "Screen");
String screenWidth = metricsCopy.remove(DeviceInfoConstants.SCREEN_WIDTH);
String screenHeight = metricsCopy.remove(DeviceInfoConstants.SCREEN_HEIGHT);
@@ -390,21 +445,6 @@
}
/**
- * Finds the {@link TestRunResult} with the given name.
- *
- * @param runName
- * @return the {@link TestRunResult}
- */
- private TestRunResult findRunResult(String runName) {
- for (TestRunResult runResult : getRunResults()) {
- if (runResult.getName().equals(runName)) {
- return runResult;
- }
- }
- return null;
- }
-
- /**
* Output the host info XML.
*
* @param serializer
@@ -451,69 +491,18 @@
*/
private void serializeTestSummary(KXmlSerializer serializer) throws IOException {
serializer.startTag(ns, SUMMARY_TAG);
- serializer.attribute(ns, FAILED_ATTR, Integer.toString(getNumErrorTests() +
- getNumFailedTests()));
- serializer.attribute(ns, NOT_EXECUTED_ATTR, Integer.toString(getNumIncompleteTests()));
+ serializer.attribute(ns, FAILED_ATTR, Integer.toString(mResults.countTests(
+ CtsTestStatus.FAIL)));
+ serializer.attribute(ns, NOT_EXECUTED_ATTR, Integer.toString(mResults.countTests(
+ CtsTestStatus.NOT_EXECUTED)));
// ignore timeouts - these are reported as errors
serializer.attribute(ns, TIMEOUT_ATTR, "0");
- serializer.attribute(ns, PASS_ATTR, Integer.toString(getNumPassedTests()));
+ serializer.attribute(ns, PASS_ATTR, Integer.toString(mResults.countTests(
+ CtsTestStatus.PASS)));
serializer.endTag(ns, SUMMARY_TAG);
}
/**
- * Output the detailed test results XML.
- *
- * @param serializer
- * @throws IOException
- */
- private void serializeTestResults(KXmlSerializer serializer) throws IOException {
- for (TestRunResult runResult : getRunResults()) {
- serializeTestRunResult(serializer, runResult);
- }
- }
-
- /**
- * Output the XML for one test run aka test package.
- *
- * @param serializer
- * @param runResult the {@link TestRunResult}
- * @throws IOException
- */
- private void serializeTestRunResult(KXmlSerializer serializer, TestRunResult runResult)
- throws IOException {
- if (runResult.getName().equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
- // ignore run results for the info collecting packages
- return;
- }
- TestPackageResult packageResult = new TestPackageResult();
- packageResult.setName(getMetric(runResult, CtsTest.PACKAGE_NAME_METRIC));
- packageResult.setAppPackageName(runResult.getName());
- packageResult.setDigest(getMetric(runResult, CtsTest.PACKAGE_DIGEST_METRIC));
- // organize the tests into data structures that mirror the expected xml output.
- for (Map.Entry<TestIdentifier, TestResult> testEntry : runResult.getTestResults()
- .entrySet()) {
- packageResult.insertTest(testEntry.getKey(), testEntry.getValue());
- }
- // dump the results
- packageResult.serialize(serializer);
- }
-
- /**
- * Helper method to retrieve the metric value with given name, or blank if not found
- *
- * @param runResult
- * @param string
- * @return
- */
- private String getMetric(TestRunResult runResult, String keyName) {
- String value = runResult.getRunMetrics().get(keyName);
- if (value == null) {
- return "";
- }
- return value;
- }
-
- /**
* Creates the output stream to use for test results. Exposed for mocking.
* @param mReportPath
*/
@@ -570,4 +559,36 @@
String getTimestamp() {
return TimeUtil.getTimestamp();
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testRunFailed(String errorMessage) {
+ // ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testRunStopped(long elapsedTime) {
+ // ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void invocationFailed(Throwable cause) {
+ // ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public TestSummary getSummary() {
+ return null;
+ }
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/ITestResultRepo.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/ITestResultRepo.java
index bed2719..d672f41 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/ITestResultRepo.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/ITestResultRepo.java
@@ -15,6 +15,7 @@
*/
package com.android.cts.tradefed.result;
+import java.io.File;
import java.util.List;
/**
@@ -37,4 +38,11 @@
*/
public TestResults getResult(int sessionId);
+ /**
+ * Get the report directory for given result
+ * @param sessionId
+ * @return
+ */
+ public File getReportDir(int sessionId);
+
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
index 184b8ab..917ccbe 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
@@ -15,9 +15,7 @@
*/
package com.android.cts.tradefed.result;
-import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestResult.TestStatus;
import org.kxml2.io.KXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
@@ -40,7 +38,7 @@
private static final String STACK_TAG = "StackTrace";
private String mName;
- private String mResult;
+ private CtsTestStatus mResult;
private String mStartTime;
private String mEndTime;
private String mMessage;
@@ -56,18 +54,12 @@
* Create a {@link Test} from a {@link TestResult}.
*
* @param name
- * @param result
*/
- public Test(String name, TestResult result) {
+ public Test(String name) {
mName = name;
- mResult = convertStatus(result.getStatus());
- mStartTime = TimeUtil.getTimestamp(result.getStartTime());
- mEndTime = TimeUtil.getTimestamp(result.getEndTime());
- if (result.getStackTrace() != null) {
- String sanitizedStack = sanitizeStackTrace(result.getStackTrace());
- mMessage = getFailureMessageFromStackTrace(sanitizedStack);
- mStackTrace = sanitizedStack;
- }
+ mResult = CtsTestStatus.NOT_EXECUTED;
+ mStartTime = TimeUtil.getTimestamp();
+ updateEndTime();
}
/**
@@ -84,7 +76,7 @@
return mName;
}
- public String getResult() {
+ public CtsTestStatus getResult() {
return mResult;
}
@@ -104,6 +96,20 @@
return mStackTrace;
}
+ public void setStackTrace(String stackTrace) {
+
+ mStackTrace = sanitizeStackTrace(stackTrace);
+ mMessage = getFailureMessageFromStackTrace(mStackTrace);
+ }
+
+ public void updateEndTime() {
+ mEndTime = TimeUtil.getTimestamp();
+ }
+
+ public void setResultStatus(CtsTestStatus status) {
+ mResult = status;
+ }
+
/**
* Serialize this object and all its contents to XML.
*
@@ -114,7 +120,7 @@
throws IOException {
serializer.startTag(CtsXmlResultReporter.ns, TAG);
serializer.attribute(CtsXmlResultReporter.ns, NAME_ATTR, getName());
- serializer.attribute(CtsXmlResultReporter.ns, RESULT_ATTR, mResult);
+ serializer.attribute(CtsXmlResultReporter.ns, RESULT_ATTR, mResult.getValue());
serializer.attribute(CtsXmlResultReporter.ns, STARTTIME_ATTR, mStartTime);
serializer.attribute(CtsXmlResultReporter.ns, ENDTIME_ATTR, mEndTime);
@@ -132,27 +138,6 @@
}
/**
- * Convert a {@link TestStatus} to the result text to output in XML
- *
- * @param status the {@link TestStatus}
- * @return
- */
- private String convertStatus(TestStatus status) {
- switch (status) {
- case ERROR:
- return CtsTestStatus.FAIL.getValue();
- case FAILURE:
- return CtsTestStatus.FAIL.getValue();
- case PASSED:
- return CtsTestStatus.PASS.getValue();
- case INCOMPLETE:
- return CtsTestStatus.NOT_EXECUTED.getValue();
- }
- CLog.w("Unrecognized status %s", status);
- return CtsTestStatus.FAIL.getValue();
- }
-
- /**
* Strip out any invalid XML characters that might cause the report to be unviewable.
* http://www.w3.org/TR/REC-xml/#dt-character
*/
@@ -187,7 +172,7 @@
"invalid XML: Expected %s tag but received %s", TAG, parser.getName()));
}
setName(getAttribute(parser, NAME_ATTR));
- mResult = getAttribute(parser, RESULT_ATTR);
+ mResult = CtsTestStatus.getStatus(getAttribute(parser, RESULT_ATTR));
mStartTime = getAttribute(parser, STARTTIME_ATTR);
mEndTime = getAttribute(parser, ENDTIME_ATTR);
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestCase.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestCase.java
index 138fe7c..fdb8b3b 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestCase.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestCase.java
@@ -16,7 +16,6 @@
package com.android.cts.tradefed.result;
import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.tradefed.result.TestResult;
import com.android.tradefed.util.ArrayUtil;
import org.kxml2.io.KXmlSerializer;
@@ -67,24 +66,17 @@
}
/**
- * Inserts given test result
- *
* @param testName
- * @param testResult
+ * @param insertIfMissing
+ * @return
*/
- public void insertTest(String testName, TestResult testResult) {
- Test t = new Test(testName, testResult);
- insertTest(t);
- }
-
- /**
- * Inserts given test result
- *
- * @param testName
- * @param testResult
- */
- private void insertTest(Test test) {
- mChildTestMap.put(test.getName(), test);
+ public Test findTest(String testName, boolean insertIfMissing) {
+ Test t = mChildTestMap.get(testName);
+ if (t == null && insertIfMissing) {
+ t = new Test(testName);
+ mChildTestMap.put(t.getName(), t);
+ }
+ return t;
}
/**
@@ -122,7 +114,7 @@
if (eventType == XmlPullParser.START_TAG && parser.getName().equals(Test.TAG)) {
Test test = new Test();
test.parse(parser);
- insertTest(test);
+ mChildTestMap.put(test.getName(), test);
} else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG)) {
return;
}
@@ -145,7 +137,7 @@
}
String fullClassName = ArrayUtil.join(".", parentSuiteNames);
for (Test test : mChildTestMap.values()) {
- if (resultFilter.getValue().equals(test.getResult())) {
+ if (resultFilter.equals(test.getResult())) {
tests.add(new TestIdentifier(fullClassName, test.getName()));
}
}
@@ -153,4 +145,20 @@
parentSuiteNames.removeLast();
}
}
+
+ /**
+ * Count the number of tests in this {@link TestCase} with given status.
+ *
+ * @param status
+ * @return the test count
+ */
+ public int countTests(CtsTestStatus status) {
+ int total = 0;
+ for (Test test : mChildTestMap.values()) {
+ if (test.getResult().equals(status)) {
+ total++;
+ }
+ }
+ return total;
+ }
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
index 8480803..7d5db75 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
@@ -15,9 +15,9 @@
*/
package com.android.cts.tradefed.result;
+import com.android.cts.tradefed.testtype.CtsTest;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.TestResult;
import org.kxml2.io.KXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
@@ -27,8 +27,10 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
/**
* Data structure for a CTS test package result.
@@ -48,6 +50,8 @@
private String mName;
private String mDigest;
+ private Map<String, String> mMetrics = new HashMap<String, String>();
+
private TestSuite mSuiteRoot = new TestSuite(null);
public void setAppPackageName(String appPackageName) {
@@ -87,17 +91,31 @@
* @param testId
* @param testResult
*/
- public void insertTest(TestIdentifier testId, TestResult testResult) {
+ public Test insertTest(TestIdentifier testId) {
+ return findTest(testId, true);
+ }
+
+ private Test findTest(TestIdentifier testId, boolean insertIfMissing) {
List<String> classNameSegments = new LinkedList<String>();
Collections.addAll(classNameSegments, testId.getClassName().split("\\."));
if (classNameSegments.size() <= 0) {
CLog.e("Unrecognized package name format for test class '%s'",
testId.getClassName());
- } else {
- String testCaseName = classNameSegments.remove(classNameSegments.size()-1);
- mSuiteRoot.insertTest(classNameSegments, testCaseName, testId.getTestName(),
- testResult);
+ // should never happen
+ classNameSegments.add("UnknownTestClass");
}
+ String testCaseName = classNameSegments.remove(classNameSegments.size()-1);
+ return mSuiteRoot.findTest(classNameSegments, testCaseName, testId.getTestName(), insertIfMissing);
+ }
+
+
+ /**
+ * Find the test result for given {@link TestIdentifier}.
+ * @param testId
+ * @return the {@link Test} or <code>null</code>
+ */
+ public Test findTest(TestIdentifier testId) {
+ return findTest(testId, false);
}
/**
@@ -108,10 +126,10 @@
*/
public void serialize(KXmlSerializer serializer) throws IOException {
serializer.startTag(ns, TAG);
- serializer.attribute(ns, NAME_ATTR, mName);
- serializer.attribute(ns, APP_PACKAGE_NAME_ATTR, mAppPackageName);
- serializer.attribute(ns, DIGEST_ATTR, getDigest());
- if (mName.equals(SIGNATURE_TEST_PKG)) {
+ serializeAttribute(serializer, NAME_ATTR, mName);
+ serializeAttribute(serializer, APP_PACKAGE_NAME_ATTR, mAppPackageName);
+ serializeAttribute(serializer, DIGEST_ATTR, getDigest());
+ if (SIGNATURE_TEST_PKG.equals(mName)) {
serializer.attribute(ns, "signatureCheck", "true");
}
mSuiteRoot.serialize(serializer);
@@ -119,6 +137,21 @@
}
/**
+ * Helper method to serialize attributes.
+ * Can handle null values. Useful for cases where test package has not been fully populated
+ * such as when unit testing.
+ *
+ * @param attrName
+ * @param attrValue
+ * @throws IOException
+ */
+ private void serializeAttribute(KXmlSerializer serializer, String attrName, String attrValue)
+ throws IOException {
+ attrValue = attrValue == null ? "" : attrValue;
+ serializer.attribute(ns, attrName, attrValue);
+ }
+
+ /**
* Populates this class with package result data parsed from XML.
*
* @param parser the {@link XmlPullParser}. Expected to be pointing at start
@@ -159,4 +192,63 @@
mSuiteRoot.addTestsWithStatus(tests, suiteNames, resultFilter);
return tests;
}
+
+ /**
+ * Populate values in this package result from run metrics
+ * @param runResult
+ */
+ public void populateMetrics(Map<String, String> metrics) {
+ String name = metrics.get(CtsTest.PACKAGE_NAME_METRIC);
+ if (name != null) {
+ setName(name);
+ }
+ String digest = metrics.get(CtsTest.PACKAGE_DIGEST_METRIC);
+ if (digest != null) {
+ setDigest(digest);
+ }
+ mMetrics.putAll(metrics);
+ }
+
+ /**
+ * Report the given test as a failure.
+ *
+ * @param test
+ * @param status
+ * @param trace
+ */
+ public void reportTestFailure(TestIdentifier test, CtsTestStatus status, String trace) {
+ Test result = findTest(test);
+ result.setResultStatus(status);
+ result.setStackTrace(trace);
+ }
+
+ /**
+ * Report that the given test has completed.
+ *
+ * @param test
+ */
+ public void reportTestEnded(TestIdentifier test) {
+ Test result = findTest(test);
+ if (!result.getResult().equals(CtsTestStatus.FAIL)) {
+ result.setResultStatus(CtsTestStatus.PASS);
+ }
+ result.updateEndTime();
+ }
+
+ /**
+ * Return the number of tests with given status
+ *
+ * @param status
+ * @return the total number of tests with given status
+ */
+ public int countTests(CtsTestStatus status) {
+ return mSuiteRoot.countTests(status);
+ }
+
+ /**
+ * @return
+ */
+ public Map<String, String> getMetrics() {
+ return mMetrics;
+ }
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResultRepo.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResultRepo.java
index 9178e91..fd42892 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResultRepo.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResultRepo.java
@@ -54,12 +54,17 @@
File resultFile = new File(resultList.get(i),
CtsXmlResultReporter.TEST_RESULT_FILE_NAME);
if (resultFile.exists()) {
- mResultDirs.add(i, resultList.get(i));
+ mResultDirs.add(resultList.get(i));
}
}
}
}
+ @Override
+ public File getReportDir(int sessionId) {
+ return mResultDirs.get(sessionId);
+ }
+
private ITestSummary parseSummary(int id, File resultDir) {
TestSummaryXml result = new TestSummaryXml(id, resultDir.getName());
try {
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java
index 6900e58..a874227 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java
@@ -15,12 +15,21 @@
*/
package com.android.cts.tradefed.result;
+import com.android.cts.tradefed.device.DeviceInfoCollector;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import org.kxml2.io.KXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
/**
* Data structure for the detailed CTS test results.
@@ -29,7 +38,8 @@
*/
class TestResults extends AbstractXmlPullParser {
- private List<TestPackageResult> mPackages = new ArrayList<TestPackageResult>();
+ private Map<String, TestPackageResult> mPackageMap = new LinkedHashMap<String, TestPackageResult>();
+ private TestPackageResult mDeviceInfoPkg = new TestPackageResult();
/**
* {@inheritDoc}
@@ -42,16 +52,91 @@
TestPackageResult.TAG)) {
TestPackageResult pkg = new TestPackageResult();
pkg.parse(parser);
- mPackages.add(pkg);
+ if (pkg.getAppPackageName() != null) {
+ mPackageMap.put(pkg.getAppPackageName(), pkg);
+ } else {
+ CLog.w("Found package with no app package name");
+ }
}
eventType = parser.next();
}
}
/**
- * @return the list of parsed {@link TestPackageResult}.
+ * @return the list of {@link TestPackageResult}.
*/
- public List<TestPackageResult> getPackages() {
- return mPackages;
+ public Collection<TestPackageResult> getPackages() {
+ return mPackageMap.values();
+ }
+
+ /**
+ * Count the number of tests with given status
+ * @param pass
+ * @return
+ */
+ public int countTests(CtsTestStatus status) {
+ int total = 0;
+ for (TestPackageResult result : mPackageMap.values()) {
+ total += result.countTests(status);
+ }
+ return total;
+ }
+
+ /**
+ * @return
+ */
+ public Map<String, String> getDeviceInfoMetrics() {
+ return mDeviceInfoPkg.getMetrics();
+ }
+
+ /**
+ * @param mCurrentPkgResult
+ */
+ public void addPackageResult(TestPackageResult pkgResult) {
+ mPackageMap.put(pkgResult.getName(), pkgResult);
+ }
+
+ /**
+ * @param serializer
+ * @throws IOException
+ */
+ public void serialize(KXmlSerializer serializer) throws IOException {
+ // sort before serializing
+ List<TestPackageResult> pkgs = new ArrayList<TestPackageResult>(mPackageMap.values());
+ Collections.sort(pkgs, new PkgComparator());
+ for (TestPackageResult r : pkgs) {
+ r.serialize(serializer);
+ }
+ }
+
+ private static class PkgComparator implements Comparator<TestPackageResult> {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int compare(TestPackageResult o1, TestPackageResult o2) {
+ return o1.getAppPackageName().compareTo(o2.getAppPackageName());
+ }
+
+ }
+
+ /**
+ * Return existing package with given app package name. If not found, create a new one.
+ * @param name
+ * @return
+ */
+ public TestPackageResult getOrCreatePackage(String appPackageName) {
+ if (appPackageName.equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
+ mDeviceInfoPkg.setAppPackageName(DeviceInfoCollector.APP_PACKAGE_NAME);
+ return mDeviceInfoPkg ;
+ }
+ TestPackageResult pkgResult = mPackageMap.get(appPackageName);
+ if (pkgResult == null) {
+ pkgResult = new TestPackageResult();
+ pkgResult.setAppPackageName(appPackageName);
+ mPackageMap.put(appPackageName, pkgResult);
+ }
+ return pkgResult;
}
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestSuite.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestSuite.java
index 426ff2d..df1dceb 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestSuite.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestSuite.java
@@ -74,16 +74,16 @@
* @param testName the test method name
* @param testResult the {@link TestResult}
*/
- public void insertTest(List<String> suiteNames, String testClassName, String testName,
- TestResult testResult) {
+ public Test findTest(List<String> suiteNames, String testClassName, String testName,
+ boolean insertIfMissing) {
if (suiteNames.size() <= 0) {
// no more package segments
TestCase testCase = getTestCase(testClassName);
- testCase.insertTest(testName, testResult);
+ return testCase.findTest(testName, insertIfMissing);
} else {
String rootName = suiteNames.remove(0);
TestSuite suite = getTestSuite(rootName);
- suite.insertTest(suiteNames, testClassName, testName, testResult);
+ return suite.findTest(suiteNames, testClassName, testName, insertIfMissing);
}
}
@@ -221,4 +221,21 @@
parentSuiteNames.removeLast();
}
}
+
+ /**
+ * Count the number of tests in this {@link TestSuite} with given status.
+ *
+ * @param status
+ * @return the test count
+ */
+ public int countTests(CtsTestStatus status) {
+ int total = 0;
+ for (TestSuite suite : mChildSuiteMap.values()) {
+ total += suite.countTests(status);
+ }
+ for (TestCase testCase : mChildTestCaseMap.values()) {
+ total += testCase.countTests(status);
+ }
+ return total;
+ }
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ResultFilter.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ResultFilter.java
index 68bb62e..e8fdad8 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ResultFilter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ResultFilter.java
@@ -130,14 +130,16 @@
*/
public void reportUnexecutedTests() {
for (Map.Entry<String, Collection<TestIdentifier>> entry : mRemainingTestsMap.entrySet()) {
- super.testRunStarted(entry.getKey(), entry.getValue().size());
- for (TestIdentifier test : entry.getValue()) {
- // an unexecuted test is currently reported as a 'testStarted' event without a
- // 'testEnded'. TODO: consider adding an explict API for reporting an unexecuted
- // test
- super.testStarted(test);
+ if (!entry.getValue().isEmpty()) {
+ super.testRunStarted(entry.getKey(), entry.getValue().size());
+ for (TestIdentifier test : entry.getValue()) {
+ // an unexecuted test is currently reported as a 'testStarted' event without a
+ // 'testEnded'. TODO: consider adding an explict API for reporting an unexecuted
+ // test
+ super.testStarted(test);
+ }
+ super.testRunEnded(0, new HashMap<String,String>());
}
- super.testRunEnded(0, new HashMap<String,String>());
}
}
}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
index 4e6b914..7566fce 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
@@ -15,13 +15,17 @@
*/
package com.android.cts.tradefed.result;
-import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.ddmlib.testrunner.ITestRunListener.TestFailure;
-import com.android.tradefed.build.BuildInfo;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IFolderBuildInfo;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.XmlResultReporter;
import com.android.tradefed.util.FileUtil;
+import junit.framework.TestCase;
+
+import org.easymock.EasyMock;
+
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
@@ -29,8 +33,6 @@
import java.util.Collections;
import java.util.Map;
-import junit.framework.TestCase;
-
/**
* Unit tests for {@link XmlResultReporter}.
*/
@@ -39,6 +41,7 @@
private CtsXmlResultReporter mResultReporter;
private ByteArrayOutputStream mOutputStream;
private File mReportDir;
+ private IFolderBuildInfo mMockBuild;
/**
* {@inheritDoc}
@@ -62,6 +65,7 @@
// TODO: use mock file dir instead
mReportDir = FileUtil.createTempDir("foo");
mResultReporter.setReportDir(mReportDir);
+ mMockBuild = EasyMock.createNiceMock(IFolderBuildInfo.class);
}
@Override
@@ -83,7 +87,7 @@
final String expectedSummaryOutput =
"<Summary failed=\"0\" notExecuted=\"0\" timeout=\"0\" pass=\"0\" />";
final String expectedEndTag = "</TestResult>";
- mResultReporter.invocationStarted(new BuildInfo("1", "test", "test"));
+ mResultReporter.invocationStarted(mMockBuild);
mResultReporter.invocationEnded(1);
String actualOutput = getOutput();
assertTrue(actualOutput.startsWith(expectedHeaderOutput));
@@ -101,7 +105,7 @@
public void testSinglePass() {
Map<String, String> emptyMap = Collections.emptyMap();
final TestIdentifier testId = new TestIdentifier("com.foo.FooTest", "testFoo");
- mResultReporter.invocationStarted(new BuildInfo());
+ mResultReporter.invocationStarted(mMockBuild);
mResultReporter.testRunStarted("run", 1);
mResultReporter.testStarted(testId);
mResultReporter.testEnded(testId, emptyMap);
@@ -128,7 +132,7 @@
Map<String, String> emptyMap = Collections.emptyMap();
final TestIdentifier testId = new TestIdentifier("FooTest", "testFoo");
final String trace = "this is a trace\nmore trace";
- mResultReporter.invocationStarted(new BuildInfo());
+ mResultReporter.invocationStarted(mMockBuild);
mResultReporter.testRunStarted("run", 1);
mResultReporter.testStarted(testId);
mResultReporter.testFailed(TestFailure.FAILURE, testId, trace);
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestPackageResultTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestPackageResultTest.java
index b3c903b..df80dbb 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestPackageResultTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestPackageResultTest.java
@@ -16,8 +16,6 @@
package com.android.cts.tradefed.result;
import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestResult.TestStatus;
import junit.framework.TestCase;
@@ -34,14 +32,11 @@
public void testGetTestsWithStatus() {
TestPackageResult pkgResult = new TestPackageResult();
TestIdentifier excludedTest = new TestIdentifier("com.example.ExampleTest", "testPass");
- TestResult passed = new TestResult();
- passed.setStatus(TestStatus.PASSED);
- pkgResult.insertTest(excludedTest, passed);
+ pkgResult.insertTest(excludedTest);
+ pkgResult.reportTestEnded(excludedTest);
TestIdentifier includedTest = new TestIdentifier("com.example.ExampleTest",
"testNotExecuted");
- TestResult notExecuted = new TestResult();
- notExecuted.setStatus(TestStatus.INCOMPLETE);
- pkgResult.insertTest(includedTest, notExecuted);
+ pkgResult.insertTest(includedTest);
Collection<TestIdentifier> tests = pkgResult.getTestsWithStatus(
CtsTestStatus.NOT_EXECUTED);
assertEquals(1, tests.size());
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestResultsTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestResultsTest.java
index a0360cd..75f545e 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestResultsTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestResultsTest.java
@@ -64,7 +64,7 @@
TestResults parser = new TestResults();
parser.parse(new StringReader(TEST_PACKAGE_FULL));
assertEquals(1, parser.getPackages().size());
- TestPackageResult pkg = parser.getPackages().get(0);
+ TestPackageResult pkg = parser.getPackages().iterator().next();
assertEquals("pkgName", pkg.getName());
assertEquals("appPkgName", pkg.getAppPackageName());
assertEquals("digValue", pkg.getDigest());
@@ -91,7 +91,7 @@
TestResults parser = new TestResults();
parser.parse(new StringReader(TEST_FULL));
assertEquals(1, parser.getPackages().size());
- TestPackageResult pkg = parser.getPackages().get(0);
+ TestPackageResult pkg = parser.getPackages().iterator().next();
TestSuite comSuite = pkg.getTestSuites().iterator().next();
assertEquals("com", comSuite.getName());
TestSuite exampleSuite = comSuite.getTestSuites().iterator().next();