Merge "Handle large protobuf transfer with vts shell driver"
diff --git a/harnesses/host_controller/invocation_thread.py b/harnesses/host_controller/invocation_thread.py
new file mode 100644
index 0000000..9673862
--- /dev/null
+++ b/harnesses/host_controller/invocation_thread.py
@@ -0,0 +1,169 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import logging
+import socket
+import threading
+
+import httplib2
+from apiclient import errors
+
+from vts.harnesses.host_controller.tfc import command_attempt
+from vts.harnesses.host_controller.tradefed import remote_operation
+
+
+class InvocationThread(threading.Thread):
+ """The thread that remotely executes a command task.
+
+ Attributes:
+ _remote_client: The RemoteClient which executes the command.
+ _tfc_client: The TfcClient to which the command events are sent.
+ _attempt: The CommandAttempt whose events are sent to TFC.
+ _command: A list of strings, the command and arguments.
+ device_serials: A list of strings, the serial numbers of the devices
+ which need to be allocated to the task.
+ _allocated_serials: A list of strings, the serial numbers of the devices
+ which are successfully allocated.
+ _tfc_heartbeat_interval: The interval of TestRunInProgress events in
+ seconds.
+ """
+
+ def __init__(self,
+ remote_client,
+ tfc_client,
+ attempt,
+ command,
+ device_serials,
+ tfc_heartbeat_interval=5 * 60):
+ """Initializes the attributes."""
+ super(InvocationThread, self).__init__()
+ self._remote_client = remote_client
+ self._tfc_client = tfc_client
+ self._attempt = attempt
+ self._command = command
+ self.device_serials = device_serials
+ self._allocated_serials = None
+ # The value in Java implementation is 5 minutes.
+ self._tfc_heartbeat_interval = tfc_heartbeat_interval
+
+ def _AllocateDevices(self):
+ """Allocates all of device_serial."""
+ for serial in self.device_serials:
+ self._remote_client.SendOperation(
+ remote_operation.AllocateDevice(serial))
+ self._allocated_serials.append(serial)
+
+ def _StartInvocation(self):
+ """Starts executing command and sends the event to TFC."""
+ self._remote_client.SendOperation(
+ remote_operation.ExecuteCommand(self.device_serials[0],
+ *self._command))
+ event = self._attempt.CreateCommandEvent(
+ command_attempt.EventType.INVOCATION_STARTED)
+ self._tfc_client.SubmitCommandEvents([event])
+
+ def _WaitForCommandResult(self):
+ """Waits for command result and keeps sending heartbeat to TFC
+
+ Returns:
+ A JSON object returned from TradeFed remote manager.
+ """
+ while True:
+ result = self._remote_client.WaitForCommandResult(
+ self.device_serials[0], self._tfc_heartbeat_interval)
+ if result:
+ return result
+ event = self._attempt.CreateCommandEvent(
+ command_attempt.EventType.TEST_RUN_IN_PROGRESS)
+ self._tfc_client.SubmitCommandEvents([event])
+
+ def _CompleteInvocation(self, result):
+ """Sends InvocationCompleted event according to the result.
+
+ Args:
+ result: A JSON object returned from TradeFed remote manager.
+ """
+ if result["status"] == "INVOCATION_SUCCESS":
+ event = self._attempt.CreateInvocationCompletedEvent(
+ str(result), 1, 0)
+ else:
+ event = self._attempt.CreateInvocationCompletedEvent(
+ str(result), 1, 1, error=str(result))
+ self._tfc_client.SubmitCommandEvents([event])
+
+ def _FreeAllocatedDevices(self):
+ """Frees allocated devices and tolerates RemoteOperationException."""
+ for serial in self._allocated_serials:
+ try:
+ self._remote_client.SendOperation(
+ remote_operation.FreeDevice(serial))
+ except remote_operation.RemoteOperationException as e:
+ logging.exception(e)
+ except socket.error as e:
+ logging.exception(e)
+ break
+ self._allocated_serials = []
+
+ def _SubmitErrorEvent(self, event_type, error_msg):
+ """Submits an error event and tolerates http exceptions.
+
+ Args:
+ event_type: A string, the type of the command event.
+ error_msg: A string, the error message.
+ """
+ try:
+ self._tfc_client.SubmitCommandEvents(
+ [self._attempt.CreateCommandEvent(event_type, error_msg)])
+ except (httplib2.HttpLib2Error, errors.HttpError) as e:
+ logging.exception(e)
+
+ # @Override
+ def run(self):
+ """Executes a command task with exception handling."""
+ self._allocated_serials = []
+ last_error = None
+ error_event = command_attempt.EventType.ALLOCATION_FAILED
+ try:
+ self._AllocateDevices()
+ error_event = command_attempt.EventType.EXECUTE_FAILED
+ self._StartInvocation()
+ result = self._WaitForCommandResult()
+ self._CompleteInvocation(result)
+ error_event = None
+ except errors.HttpError as e:
+ logging.exception(e)
+ last_error = e
+ except remote_operation.RemoteOperationException as e:
+ logging.exception(e)
+ last_error = e
+ # ConfigurationException on TradeFed remote manager.
+ if str(e).startswith("Config error: "):
+ error_event = command_attempt.EventType.CONFIGURATION_ERROR
+ except httplib2.HttpLib2Error as e:
+ logging.exception("Cannot communicate with TradeFed cluster: %s\n"
+ "Skip submitting event %s.", e, error_event)
+ last_error = e
+ error_event = None
+ except socket.error as e:
+ logging.exception("Cannot communicate with TradeFed remote "
+ "manager: %s\nSkip freeing devices %s.",
+ e, self._allocated_serials)
+ last_error = e
+ self._allocated_serials = []
+ finally:
+ if error_event:
+ self._SubmitErrorEvent(error_event, str(last_error))
+ self._FreeAllocatedDevices()
diff --git a/harnesses/host_controller/invocation_thread_test.py b/harnesses/host_controller/invocation_thread_test.py
new file mode 100644
index 0000000..c940329
--- /dev/null
+++ b/harnesses/host_controller/invocation_thread_test.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import unittest
+
+try:
+ from unittest import mock
+except ImportError:
+ import mock
+
+from vts.harnesses.host_controller import invocation_thread
+from vts.harnesses.host_controller.tfc import command_attempt
+from vts.harnesses.host_controller.tradefed import remote_operation
+
+
+class InvocationThreadTest(unittest.TestCase):
+ """A test for invocation_thread.InvocationThread.
+
+ Attributes:
+ _remote_client: A mock remote_client.RemoteClient.
+ _tfc_client: A mock tfc_client.TfcClient.
+ _inv_thread: The InvocationThread being tested.
+ """
+
+ def setUp(self):
+ """Creates the InvocationThread."""
+ self._remote_client = mock.Mock()
+ self._tfc_client = mock.Mock()
+ attempt = command_attempt.CommandAttempt(
+ task_id="321-0",
+ attempt_id="abcd-1234",
+ hostname="host0",
+ device_serial="ABCDEF")
+ command = ["vts", "-m", "SampleShellTest"]
+ serials = ["serial123", "serial456"]
+ self._inv_thread = invocation_thread.InvocationThread(
+ self._remote_client, self._tfc_client,
+ attempt, command, serials)
+
+ def _GetSubmittedEventTypes(self):
+ """Gets the types of the events submitted by the mock TfcClient.
+
+ Returns:
+ A list of strings, the event types.
+ """
+ event_types = []
+ for args, kwargs in self._tfc_client.SubmitCommandEvents.call_args_list:
+ event_types.extend(event["type"] for event in args[0])
+ return event_types
+
+ def _GetSentOperationTypes(self):
+ """Gets the types of the operations sent by the mock RemoteClient.
+
+ Returns:
+ A list of strings, the operation types.
+ """
+ operation_types = [args[0].type for args, kwargs in
+ self._remote_client.SendOperation.call_args_list]
+ return operation_types
+
+ def testAllocationFailed(self):
+ """Tests AllocationFailed event."""
+ self._remote_client.SendOperation.side_effect = (
+ lambda op: _RaiseExceptionForOperation(op, "ALLOCATE_DEVICE"))
+ self._inv_thread.run()
+ self.assertEqual([command_attempt.EventType.ALLOCATION_FAILED],
+ self._GetSubmittedEventTypes())
+ self.assertEqual(["ALLOCATE_DEVICE"],
+ self._GetSentOperationTypes())
+
+ def testExecuteFailed(self):
+ """Tests ExecuteFailed event."""
+ self._remote_client.SendOperation.side_effect = (
+ lambda op: _RaiseExceptionForOperation(op, "EXEC_COMMAND"))
+ self._inv_thread.run()
+ self.assertEqual([command_attempt.EventType.EXECUTE_FAILED],
+ self._GetSubmittedEventTypes())
+ self.assertEqual(["ALLOCATE_DEVICE",
+ "ALLOCATE_DEVICE",
+ "EXEC_COMMAND",
+ "FREE_DEVICE",
+ "FREE_DEVICE"],
+ self._GetSentOperationTypes())
+
+ def testConfigurationError(self):
+ """Tests ConfigurationError event."""
+ self._remote_client.SendOperation.side_effect = (
+ lambda op: _RaiseExceptionForOperation(op, "EXEC_COMMAND",
+ "Config error: test"))
+ self._inv_thread.run()
+ self.assertEqual([command_attempt.EventType.CONFIGURATION_ERROR],
+ self._GetSubmittedEventTypes())
+ self.assertEqual(["ALLOCATE_DEVICE",
+ "ALLOCATE_DEVICE",
+ "EXEC_COMMAND",
+ "FREE_DEVICE",
+ "FREE_DEVICE"],
+ self._GetSentOperationTypes())
+
+ def testInvocationCompleted(self):
+ """Tests InvocationCompleted event."""
+ self._remote_client.WaitForCommandResult.side_effect = (
+ None, {"status": "INVOCATION_SUCCESS"})
+ self._inv_thread.run()
+ self.assertEqual([command_attempt.EventType.INVOCATION_STARTED,
+ command_attempt.EventType.TEST_RUN_IN_PROGRESS,
+ command_attempt.EventType.INVOCATION_COMPLETED],
+ self._GetSubmittedEventTypes())
+ # GET_LAST_COMMAND_RESULT isn't called in mock WaitForCommandResult.
+ self.assertEqual(["ALLOCATE_DEVICE",
+ "ALLOCATE_DEVICE",
+ "EXEC_COMMAND",
+ "FREE_DEVICE",
+ "FREE_DEVICE"],
+ self._GetSentOperationTypes())
+
+
+def _RaiseExceptionForOperation(operation, op_type, error_msg="unit test"):
+ """Raises exception for specific operation type.
+
+ Args:
+ operation: A remote_operation.RemoteOperation object.
+ op_type: A string, the expected type.
+ error_msg: The message in the exception.
+
+ Raises:
+ RemoteOperationException if the operation's type matches op_type.
+ """
+ if operation.type == op_type:
+ raise remote_operation.RemoteOperationException(error_msg)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/harnesses/host_controller/tfc/command_attempt.py b/harnesses/host_controller/tfc/command_attempt.py
new file mode 100644
index 0000000..91b7551
--- /dev/null
+++ b/harnesses/host_controller/tfc/command_attempt.py
@@ -0,0 +1,137 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import time
+
+from vts.harnesses.host_controller.tfc import api_message
+
+
+class EventType(object):
+ """The types of command events."""
+ ALLOCATION_FAILED = "AllocationFailed"
+ CONFIGURATION_ERROR = "ConfigurationError"
+ EXECUTE_FAILED = "ExecuteFailed"
+ FETCH_FAILED = "FetchFailed"
+ INVOCATION_COMPLETED = "InvocationCompleted"
+ INVOCATION_STARTED = "InvocationStarted"
+ TEST_RUN_IN_PROGRESS = "TestRunInProgress"
+
+
+class CommandAttempt(api_message.ApiMessage):
+ """The command attempt defined by TFC API.
+
+ Attributes:
+ _COMMAND_EVENT: The parameters of command_events.submit.
+ _COMMAND_EVENT_DATA: The fields in "data" parameter of command_events.
+ _LIST_ATTEMPT: The fields returned by commandAttempts.list.
+ """
+ _COMMAND_EVENT = {
+ "attempt_id",
+ "data",
+ "device_serial",
+ "hostname",
+ "task_id",
+ "time",
+ "type"}
+ _COMMAND_EVENT_DATA = {
+ "error",
+ "failed_test_count",
+ "summary",
+ "test_run_name",
+ "total_test_count"}
+ _LIST_ATTEMPT = {
+ "attempt_id",
+ "command_id",
+ "create_time",
+ "end_time",
+ "error",
+ "device_serial",
+ "failed_test_count",
+ "hostname",
+ "request_id",
+ "start_time",
+ "state",
+ "status",
+ "summary",
+ "task_id",
+ "total_test_count",
+ "update_time"}
+
+ def __init__(self, task_id, attempt_id, hostname, device_serial, **kwargs):
+ """Initializes the attributes.
+
+ Args:
+ task_id: A string, the task id assigned by the server.
+ attempt_id: A string or UUID, the attempt id generated by the host.
+ hostname: The name of the TradeFed host.
+ device_serial: The serial number of the device.
+ **kwargs: The optional attributes.
+ """
+ super(CommandAttempt, self).__init__(self._LIST_ATTEMPT,
+ task_id=task_id,
+ attempt_id=str(attempt_id),
+ hostname=hostname,
+ device_serial=device_serial,
+ **kwargs)
+
+ def CreateCommandEvent(self, event_type, error=None, event_time=None):
+ """Creates an event defined by command_events.submit.
+
+ Args:
+ event_type: A string in EventType.
+ error: A string, the error message for *Failed, *Error, and
+ *Completed events.
+ event_time: A float, Unix timestamp of the event in seconds.
+
+ Returns:
+ A JSON object.
+ """
+ obj = self.ToJson(self._COMMAND_EVENT)
+ obj["type"] = event_type
+ obj["time"] = int(event_time if event_time is not None else time.time())
+ data_obj = self.ToJson(self._COMMAND_EVENT_DATA)
+ if error is not None:
+ data_obj["error"] = error
+ if data_obj:
+ obj["data"] = data_obj
+ return obj
+
+ def CreateInvocationCompletedEvent(self,
+ summary,
+ total_test_count,
+ failed_test_count,
+ error=None,
+ event_time=None):
+ """Creates an InvocationCompleted event.
+
+ Args:
+ summary: A string, the result of the command.
+ total_test_count: Number of test cases.
+ failed_test_count: Number of failed test cases.
+ error: A string, the error message.
+ event_time: A float, Unix timestamp of the event in seconds.
+
+ Returns:
+ A JSON object.
+ """
+ obj = self.CreateCommandEvent(EventType.INVOCATION_COMPLETED,
+ error, event_time)
+ if "data" not in obj:
+ obj["data"] = dict()
+ obj["data"].update({"summary": summary,
+ "total_test_count": total_test_count,
+ "failed_test_count": failed_test_count})
+ return obj
diff --git a/harnesses/host_controller/tfc/tfc_client.py b/harnesses/host_controller/tfc/tfc_client.py
index 0ccf276..fe42bf1 100644
--- a/harnesses/host_controller/tfc/tfc_client.py
+++ b/harnesses/host_controller/tfc/tfc_client.py
@@ -98,6 +98,16 @@
logging.info("host_events.submit body=%s", json_obj)
self._service.host_events().submit(body=json_obj).execute()
+ def SubmitCommandEvents(self, command_events):
+ """Calls command_events.submit.
+
+ Args:
+ command_events: A list of JSON objects converted from CommandAttempt.
+ """
+ json_obj = {"command_events": command_events}
+ logging.info("command_events.submit body=%s", json_obj)
+ self._service.command_events().submit(body=json_obj).execute()
+
def NewRequest(self, request):
"""Calls requests.new.
diff --git a/harnesses/host_controller/tfc/tfc_client_test.py b/harnesses/host_controller/tfc/tfc_client_test.py
index fd87df7..411d039 100644
--- a/harnesses/host_controller/tfc/tfc_client_test.py
+++ b/harnesses/host_controller/tfc/tfc_client_test.py
@@ -23,6 +23,7 @@
import mock
from vts.harnesses.host_controller.tfc import tfc_client
+from vts.harnesses.host_controller.tfc import command_attempt
from vts.harnesses.host_controller.tfc import device_info
from vts.harnesses.host_controller.tfc import request
@@ -77,6 +78,48 @@
self._service.assert_has_calls([
mock.call.host_events().submit().execute()])
+ def testCommandEvents(self):
+ """Tests command_events.submit."""
+ cmd = command_attempt.CommandAttempt(
+ task_id="321-0",
+ attempt_id="abcd-1234",
+ hostname="host0",
+ device_serial="ABCDEF")
+ expected_event = {
+ "task_id": "321-0",
+ "attempt_id": "abcd-1234",
+ "hostname": "host0",
+ "device_serial": "ABCDEF",
+ "time": 1}
+
+ normal_event = cmd.CreateCommandEvent(
+ command_attempt.EventType.INVOCATION_STARTED,
+ event_time=1)
+ expected_event["type"] = command_attempt.EventType.INVOCATION_STARTED
+ self.assertDictEqual(expected_event, normal_event)
+
+ error_event = cmd.CreateCommandEvent(
+ command_attempt.EventType.EXECUTE_FAILED,
+ error="unit test", event_time=1.1)
+ expected_event["type"] = command_attempt.EventType.EXECUTE_FAILED
+ expected_event["data"] = {"error":"unit test"}
+ self.assertDictEqual(expected_event, error_event)
+
+ complete_event = cmd.CreateInvocationCompletedEvent(
+ summary="complete", total_test_count=2, failed_test_count=1,
+ error="unit test")
+ expected_event["type"] = command_attempt.EventType.INVOCATION_COMPLETED
+ expected_event["data"] = {"summary": "complete", "error": "unit test",
+ "total_test_count": 2, "failed_test_count": 1}
+ del expected_event["time"]
+ self.assertDictContainsSubset(expected_event, complete_event)
+ self.assertIn("time", complete_event)
+
+ self._client.SubmitCommandEvents([
+ normal_event, error_event, complete_event])
+ self._service.assert_has_calls([
+ mock.call.command_events().submit().execute()])
+
def testWrongParameter(self):
"""Tests raising exception for wrong parameter name."""
self.assertRaisesRegexp(KeyError, "sdk", device_info.DeviceInfo,
diff --git a/harnesses/host_controller/tradefed/remote_client.py b/harnesses/host_controller/tradefed/remote_client.py
index ffa4b52..6427676 100644
--- a/harnesses/host_controller/tradefed/remote_client.py
+++ b/harnesses/host_controller/tradefed/remote_client.py
@@ -105,21 +105,9 @@
json_obj = self.SendOperation(remote_operation.ListDevices())
return remote_operation.ParseListDevicesResponse(json_obj)
- def RunCommand(self, serial, *command):
- """Sends a series of operations to run a command.
-
- Args:
- serial: The serial number of the device.
- *command: A list of strings which is the command to execute.
- """
- self.SendOperation(remote_operation.AllocateDevice(serial))
- self.SendOperation(remote_operation.ExecuteCommand(serial, *command))
-
def WaitForCommandResult(self, serial, timeout, poll_interval=5):
"""Sends a series of operations to wait until a command finishes.
- This method frees the device if the command finishes before timeout.
-
Args:
serial: The serial number of the device.
timeout: A float, the timeout in seconds.
@@ -129,13 +117,16 @@
Returns:
A JSON object which is the result of the command.
None if timeout.
+
+ Sample
+ {'status': 'INVOCATION_SUCCESS',
+ 'free_device_state': 'AVAILABLE'}
"""
deadline = time.time() + timeout
get_result_op = remote_operation.GetLastCommandResult(serial)
while True:
result = self.SendOperation(get_result_op)
if result["status"] != "EXECUTING":
- self.SendOperation(remote_operation.FreeDevice(serial))
return result
if time.time() > deadline:
return None
diff --git a/harnesses/host_controller/tradefed/remote_client_test.py b/harnesses/host_controller/tradefed/remote_client_test.py
index 5e1bbbc..c4d2e5d 100644
--- a/harnesses/host_controller/tradefed/remote_client_test.py
+++ b/harnesses/host_controller/tradefed/remote_client_test.py
@@ -144,8 +144,10 @@
def testExecuteCommand(self):
"""Tests executing a command and waiting for result."""
self._remote_mgr_thread.AddResponse('{}')
+ self._client.SendOperation(remote_operation.AllocateDevice("serial123"))
self._remote_mgr_thread.AddResponse('{}')
- self._client.RunCommand("serial123", "vts", "-m", "SampleShellTest")
+ self._client.SendOperation(remote_operation.ExecuteCommand(
+ "serial123", "vts", "-m", "SampleShellTest"))
self._remote_mgr_thread.AddResponse('{"status": "EXECUTING"}')
result = self._client.WaitForCommandResult("serial123",
@@ -154,9 +156,10 @@
self._remote_mgr_thread.AddResponse('{"status": "EXECUTING"}')
self._remote_mgr_thread.AddResponse('{"status": "INVOCATION_SUCCESS"}')
- self._remote_mgr_thread.AddResponse('{}')
result = self._client.WaitForCommandResult("serial123",
timeout=5, poll_interval=1)
+ self._remote_mgr_thread.AddResponse('{}')
+ self._client.SendOperation(remote_operation.FreeDevice("serial123"))
self.assertIsNotNone(result, "Client doesn't return command result.")
def testSocketError(self):
diff --git a/harnesses/host_controller/tradefed/remote_operation.py b/harnesses/host_controller/tradefed/remote_operation.py
index 3c077bd..a398954 100644
--- a/harnesses/host_controller/tradefed/remote_operation.py
+++ b/harnesses/host_controller/tradefed/remote_operation.py
@@ -27,7 +27,8 @@
class RemoteOperation(object):
"""The operation sent to TradeFed remote manager.
- An operation is a JSON object with 2 common entries "type" and "version".
+ Args:
+ _obj: A JSON object with at least 2 entries, "type" and "version".
"""
CURRENT_PROTOCOL_VERSION = 8
@@ -38,10 +39,10 @@
type: A string, the type of the operation.
**kwargs: The arguments which are specific to the operation type.
"""
- self.obj = kwargs
- self.obj["type"] = type
- if "version" not in self.obj:
- self.obj["version"] = self.CURRENT_PROTOCOL_VERSION
+ self._obj = kwargs
+ self._obj["type"] = type
+ if "version" not in self._obj:
+ self._obj["version"] = self.CURRENT_PROTOCOL_VERSION
def ParseResponse(self, response_str):
"""Parses the response to the operation.
@@ -60,9 +61,14 @@
raise RemoteOperationException(response["error"])
return response
+ @property
+ def type(self):
+ """Returns the type of this operation."""
+ return self._obj["type"]
+
def __str__(self):
"""Converts the JSON object to string."""
- return json.dumps(self.obj)
+ return json.dumps(self._obj)
def ListDevices():
diff --git a/tools/build/tasks/vts_package.mk b/tools/build/tasks/vts_package.mk
index 525c8f3..887a1dd 100644
--- a/tools/build/tasks/vts_package.mk
+++ b/tools/build/tasks/vts_package.mk
@@ -186,6 +186,20 @@
$(foreach f,$(kernel_rootdir_test_rc_files),\
system/core/rootdir/$(f):$(VTS_TESTCASES_OUT)/vts/testcases/kernel/api/rootdir/init_rc_files/$(f)) \
+acts_framework_files := \
+ $(call find-files-in-subdirs,tools/test/connectivity/acts/framework/acts,"*.py" -and -type f,.)
+
+acts_framework_copy_pairs := \
+ $(foreach f,$(acts_framework_files),\
+ tools/test/connectivity/acts/framework/acts/$(f):$(VTS_TESTCASES_OUT)/acts/$(f))
+
+acts_testcases_files := \
+ $(call find-files-in-subdirs,tools/test/connectivity/acts/tests/google,"*.py" -and -type f,.)
+
+acts_testcases_copy_pairs := \
+ $(foreach f,$(acts_testcases_files),\
+ tools/test/connectivity/acts/tests/google/$(f):$(VTS_TESTCASES_OUT)/vts/testcases/acts/$(f))
+
$(compatibility_zip): \
$(call copy-many-files,$(target_native_copy_pairs)) \
$(call copy-many-files,$(target_spec_copy_pairs)) \
@@ -202,5 +216,7 @@
$(call copy-many-files,$(performance_test_res_copy_pairs)) \
$(call copy-many-files,$(audio_test_res_copy_pairs)) \
$(call copy-many-files,$(kernel_rootdir_test_rc_copy_pairs)) \
+ $(call copy-many-files,$(acts_framework_copy_pairs)) \
+ $(call copy-many-files,$(acts_testcases_copy_pairs)) \
-include vendor/google_vts/tools/build/vts_package_vendor.mk
diff --git a/tools/vts-tradefed/res/push_groups/VtsAgent.push b/tools/vts-tradefed/res/push_groups/VtsAgent.push
index a0321e6..a0c0830 100644
--- a/tools/vts-tradefed/res/push_groups/VtsAgent.push
+++ b/tools/vts-tradefed/res/push_groups/VtsAgent.push
@@ -16,6 +16,7 @@
DATA/bin/vts_hal_agent32->/data/local/tmp/32/vts_hal_agent32
DATA/bin/vts_hal_agent64->/data/local/tmp/64/vts_hal_agent64
-DATA/bin/vts_testability_checker->/data/local/tmp/vts_testability_checker
+DATA/bin/vts_testability_checker32->/data/local/tmp/vts_testability_checker32
+DATA/bin/vts_testability_checker64->/data/local/tmp/vts_testability_checker64
DATA/app/VtsAgentApp/VtsAgentApp.apk->/data/local/tmp/VtsAgentApp.apk
diff --git a/utils/native/testability_checker/Android.bp b/utils/native/testability_checker/Android.bp
index d66c5be..ed24285 100644
--- a/utils/native/testability_checker/Android.bp
+++ b/utils/native/testability_checker/Android.bp
@@ -56,7 +56,15 @@
name: "vts_testability_checker",
defaults : ["VtsTestabilityCheckerDefaults"],
srcs: ["VtsTestabilityCheckerMain.cpp"],
-
+ multilib: {
+ lib64: {
+ suffix: "64",
+ },
+ lib32: {
+ suffix: "32",
+ },
+ },
+ compile_multilib: "both",
static_libs: [
"libhidl-gen-utils",
"libjsoncpp",
diff --git a/utils/python/hal/hal_service_name_utils.py b/utils/python/hal/hal_service_name_utils.py
index c53e46d..0651a9b 100644
--- a/utils/python/hal/hal_service_name_utils.py
+++ b/utils/python/hal/hal_service_name_utils.py
@@ -19,9 +19,10 @@
from vts.runners.host import asserts
from vts.runners.host import const
-VTS_TESTABILITY_CHECKER = "/data/local/tmp/vts_testability_checker"
+VTS_TESTABILITY_CHECKER_32 = "/data/local/tmp/vts_testability_checker32"
+VTS_TESTABILITY_CHECKER_64 = "/data/local/tmp/vts_testability_checker64"
-def GetHalServiceName(shell, hal, bitness="32", run_as_compliance_test=False):
+def GetHalServiceName(shell, hal, bitness="64", run_as_compliance_test=False):
"""Determine whether to run a VTS test against a HAL and get the service
names of the given hal if determine to run.
@@ -37,7 +38,9 @@
a set containing all service names for the given HAL.
"""
- cmd = VTS_TESTABILITY_CHECKER
+ cmd = VTS_TESTABILITY_CHECKER_64
+ if bitness == "32":
+ cmd = VTS_TESTABILITY_CHECKER_32
if run_as_compliance_test:
cmd += " -c "
cmd += " -b " + bitness + " " + hal