Merge "Add instrumentation_data.proto parsing support for ACTS instrumentation tests"
diff --git a/acts/framework/acts/test_utils/instrumentation/instrumentation_command_builder.py b/acts/framework/acts/test_utils/instrumentation/instrumentation_command_builder.py
index 8947dc3..9d11e81 100644
--- a/acts/framework/acts/test_utils/instrumentation/instrumentation_command_builder.py
+++ b/acts/framework/acts/test_utils/instrumentation/instrumentation_command_builder.py
@@ -23,6 +23,7 @@
self._flags = []
self._key_value_params = {}
self._runner = None
+ self._proto_path = None
def set_manifest_package(self, test_package):
self._manifest_package_name = test_package
@@ -36,6 +37,9 @@
def add_key_value_param(self, key, value):
self._key_value_params[key] = value
+ def set_proto_path(self, path):
+ self._proto_path = path
+
def build(self):
call = self._instrument_call_with_arguments()
call.append('{}/{}'.format(self._manifest_package_name, self._runner))
@@ -53,6 +57,9 @@
call = ['am instrument']
for flag in self._flags:
call.append(flag)
+ call.append('-f')
+ if self._proto_path:
+ call.append(self._proto_path)
for key, value in self._key_value_params.items():
call.append('-e')
call.append(key)
diff --git a/acts/framework/acts/test_utils/instrumentation/instrumentation_proto_parser.py b/acts/framework/acts/test_utils/instrumentation/instrumentation_proto_parser.py
new file mode 100644
index 0000000..f03bccf
--- /dev/null
+++ b/acts/framework/acts/test_utils/instrumentation/instrumentation_proto_parser.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - 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 os
+import tempfile
+import time
+
+from acts.test_utils.instrumentation.proto.gen import instrumentation_data_pb2
+
+DEFAULT_INST_PROTO_DIR = '/sdcard/instrument-logs'
+
+
+def pull_proto(ad, dest_dir, source_path=None):
+ """Pull latest instrumentation result proto from device.
+
+ Args:
+ ad: AndroidDevice object
+ dest_dir: Directory on the host where the proto will be sent
+ source_path: Path on the device where the proto is generated. If None,
+ pull the latest proto from DEFAULT_INST_PROTO_DIR.
+
+ Returns: Path to the retrieved proto file
+ """
+ if source_path:
+ filename = os.path.basename(source_path)
+ else:
+ filename = ad.adb.shell('ls %s -t | head -n1' % DEFAULT_INST_PROTO_DIR)
+ if not filename:
+ ad.log.warning('No instrumentation result protos found at default '
+ 'location.')
+ return ''
+ source_path = os.path.join(DEFAULT_INST_PROTO_DIR, filename)
+ ad.pull_files(source_path, dest_dir)
+ dest_path = os.path.join(dest_dir, filename)
+ if not os.path.exists(dest_path):
+ ad.log.warning('Failed to pull instrumentation result proto: %s -> %s'
+ % (source_path, dest_path))
+ return ''
+ return os.path.join(dest_dir, filename)
+
+
+def get_session_from_local_file(proto_file):
+ """Get a instrumentation_data.Session object from a proto file on the host.
+
+ Args:
+ proto_file: Path to the proto file (on host)
+
+ Returns: A instrumentation_data_pb2.Session
+ """
+ with open(proto_file, 'rb') as f:
+ return instrumentation_data_pb2.Session.FromString(f.read())
+
+
+def get_session_from_device(ad, proto_file=None):
+ """Get a instrumentation_data.Session object from a proto file on device.
+
+ Args:
+ ad: AndroidDevice object
+ proto_file: Path to the proto file (on device). If None, defaults to
+ latest proto from DEFAULT_INST_PROTO_DIR.
+
+ Returns: A instrumentation_data_pb2.Session
+ """
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ pulled_proto = pull_proto(ad, tmp_dir, proto_file)
+ return get_session_from_local_file(pulled_proto)
diff --git a/acts/framework/acts/test_utils/instrumentation/proto/gen/instrumentation_data_pb2.py b/acts/framework/acts/test_utils/instrumentation/proto/gen/instrumentation_data_pb2.py
new file mode 100644
index 0000000..783cd22
--- /dev/null
+++ b/acts/framework/acts/test_utils/instrumentation/proto/gen/instrumentation_data_pb2.py
@@ -0,0 +1,345 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: instrumentation_data.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='instrumentation_data.proto',
+ package='android.am',
+ syntax='proto2',
+ serialized_pb=_b('\n\x1ainstrumentation_data.proto\x12\nandroid.am\"\xcf\x01\n\x12ResultsBundleEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x14\n\x0cvalue_string\x18\x02 \x01(\t\x12\x11\n\tvalue_int\x18\x03 \x01(\x11\x12\x13\n\x0bvalue_float\x18\x04 \x01(\x02\x12\x14\n\x0cvalue_double\x18\x05 \x01(\x01\x12\x12\n\nvalue_long\x18\x06 \x01(\x12\x12/\n\x0cvalue_bundle\x18\x07 \x01(\x0b\x32\x19.android.am.ResultsBundle\x12\x13\n\x0bvalue_bytes\x18\x08 \x01(\x0c\"@\n\rResultsBundle\x12/\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x1e.android.am.ResultsBundleEntry\"M\n\nTestStatus\x12\x13\n\x0bresult_code\x18\x03 \x01(\x11\x12*\n\x07results\x18\x04 \x01(\x0b\x32\x19.android.am.ResultsBundle\"\x98\x01\n\rSessionStatus\x12\x32\n\x0bstatus_code\x18\x01 \x01(\x0e\x32\x1d.android.am.SessionStatusCode\x12\x12\n\nerror_text\x18\x02 \x01(\t\x12\x13\n\x0bresult_code\x18\x03 \x01(\x11\x12*\n\x07results\x18\x04 \x01(\x0b\x32\x19.android.am.ResultsBundle\"i\n\x07Session\x12+\n\x0btest_status\x18\x01 \x03(\x0b\x32\x16.android.am.TestStatus\x12\x31\n\x0esession_status\x18\x02 \x01(\x0b\x32\x19.android.am.SessionStatus*>\n\x11SessionStatusCode\x12\x14\n\x10SESSION_FINISHED\x10\x00\x12\x13\n\x0fSESSION_ABORTED\x10\x01\x42\x19\n\x17\x63om.android.commands.am')
+)
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+_SESSIONSTATUSCODE = _descriptor.EnumDescriptor(
+ name='SessionStatusCode',
+ full_name='android.am.SessionStatusCode',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='SESSION_FINISHED', index=0, number=0,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SESSION_ABORTED', index=1, number=1,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=659,
+ serialized_end=721,
+)
+_sym_db.RegisterEnumDescriptor(_SESSIONSTATUSCODE)
+
+SessionStatusCode = enum_type_wrapper.EnumTypeWrapper(_SESSIONSTATUSCODE)
+SESSION_FINISHED = 0
+SESSION_ABORTED = 1
+
+
+
+_RESULTSBUNDLEENTRY = _descriptor.Descriptor(
+ name='ResultsBundleEntry',
+ full_name='android.am.ResultsBundleEntry',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='key', full_name='android.am.ResultsBundleEntry.key', index=0,
+ number=1, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='value_string', full_name='android.am.ResultsBundleEntry.value_string', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='value_int', full_name='android.am.ResultsBundleEntry.value_int', index=2,
+ number=3, type=17, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='value_float', full_name='android.am.ResultsBundleEntry.value_float', index=3,
+ number=4, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='value_double', full_name='android.am.ResultsBundleEntry.value_double', index=4,
+ number=5, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='value_long', full_name='android.am.ResultsBundleEntry.value_long', index=5,
+ number=6, type=18, cpp_type=2, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='value_bundle', full_name='android.am.ResultsBundleEntry.value_bundle', index=6,
+ number=7, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='value_bytes', full_name='android.am.ResultsBundleEntry.value_bytes', index=7,
+ number=8, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=43,
+ serialized_end=250,
+)
+
+
+_RESULTSBUNDLE = _descriptor.Descriptor(
+ name='ResultsBundle',
+ full_name='android.am.ResultsBundle',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='entries', full_name='android.am.ResultsBundle.entries', index=0,
+ number=1, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=252,
+ serialized_end=316,
+)
+
+
+_TESTSTATUS = _descriptor.Descriptor(
+ name='TestStatus',
+ full_name='android.am.TestStatus',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='result_code', full_name='android.am.TestStatus.result_code', index=0,
+ number=3, type=17, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='results', full_name='android.am.TestStatus.results', index=1,
+ number=4, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=318,
+ serialized_end=395,
+)
+
+
+_SESSIONSTATUS = _descriptor.Descriptor(
+ name='SessionStatus',
+ full_name='android.am.SessionStatus',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='status_code', full_name='android.am.SessionStatus.status_code', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='error_text', full_name='android.am.SessionStatus.error_text', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='result_code', full_name='android.am.SessionStatus.result_code', index=2,
+ number=3, type=17, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='results', full_name='android.am.SessionStatus.results', index=3,
+ number=4, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=398,
+ serialized_end=550,
+)
+
+
+_SESSION = _descriptor.Descriptor(
+ name='Session',
+ full_name='android.am.Session',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='test_status', full_name='android.am.Session.test_status', index=0,
+ number=1, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='session_status', full_name='android.am.Session.session_status', index=1,
+ number=2, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=552,
+ serialized_end=657,
+)
+
+_RESULTSBUNDLEENTRY.fields_by_name['value_bundle'].message_type = _RESULTSBUNDLE
+_RESULTSBUNDLE.fields_by_name['entries'].message_type = _RESULTSBUNDLEENTRY
+_TESTSTATUS.fields_by_name['results'].message_type = _RESULTSBUNDLE
+_SESSIONSTATUS.fields_by_name['status_code'].enum_type = _SESSIONSTATUSCODE
+_SESSIONSTATUS.fields_by_name['results'].message_type = _RESULTSBUNDLE
+_SESSION.fields_by_name['test_status'].message_type = _TESTSTATUS
+_SESSION.fields_by_name['session_status'].message_type = _SESSIONSTATUS
+DESCRIPTOR.message_types_by_name['ResultsBundleEntry'] = _RESULTSBUNDLEENTRY
+DESCRIPTOR.message_types_by_name['ResultsBundle'] = _RESULTSBUNDLE
+DESCRIPTOR.message_types_by_name['TestStatus'] = _TESTSTATUS
+DESCRIPTOR.message_types_by_name['SessionStatus'] = _SESSIONSTATUS
+DESCRIPTOR.message_types_by_name['Session'] = _SESSION
+DESCRIPTOR.enum_types_by_name['SessionStatusCode'] = _SESSIONSTATUSCODE
+
+ResultsBundleEntry = _reflection.GeneratedProtocolMessageType('ResultsBundleEntry', (_message.Message,), dict(
+ DESCRIPTOR = _RESULTSBUNDLEENTRY,
+ __module__ = 'instrumentation_data_pb2'
+ # @@protoc_insertion_point(class_scope:android.am.ResultsBundleEntry)
+ ))
+_sym_db.RegisterMessage(ResultsBundleEntry)
+
+ResultsBundle = _reflection.GeneratedProtocolMessageType('ResultsBundle', (_message.Message,), dict(
+ DESCRIPTOR = _RESULTSBUNDLE,
+ __module__ = 'instrumentation_data_pb2'
+ # @@protoc_insertion_point(class_scope:android.am.ResultsBundle)
+ ))
+_sym_db.RegisterMessage(ResultsBundle)
+
+TestStatus = _reflection.GeneratedProtocolMessageType('TestStatus', (_message.Message,), dict(
+ DESCRIPTOR = _TESTSTATUS,
+ __module__ = 'instrumentation_data_pb2'
+ # @@protoc_insertion_point(class_scope:android.am.TestStatus)
+ ))
+_sym_db.RegisterMessage(TestStatus)
+
+SessionStatus = _reflection.GeneratedProtocolMessageType('SessionStatus', (_message.Message,), dict(
+ DESCRIPTOR = _SESSIONSTATUS,
+ __module__ = 'instrumentation_data_pb2'
+ # @@protoc_insertion_point(class_scope:android.am.SessionStatus)
+ ))
+_sym_db.RegisterMessage(SessionStatus)
+
+Session = _reflection.GeneratedProtocolMessageType('Session', (_message.Message,), dict(
+ DESCRIPTOR = _SESSION,
+ __module__ = 'instrumentation_data_pb2'
+ # @@protoc_insertion_point(class_scope:android.am.Session)
+ ))
+_sym_db.RegisterMessage(Session)
+
+
+DESCRIPTOR.has_options = True
+DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\027com.android.commands.am'))
+# @@protoc_insertion_point(module_scope)
diff --git a/acts/framework/acts/test_utils/instrumentation/proto/instrumentation_data.proto b/acts/framework/acts/test_utils/instrumentation/proto/instrumentation_data.proto
new file mode 100644
index 0000000..8e29f96
--- /dev/null
+++ b/acts/framework/acts/test_utils/instrumentation/proto/instrumentation_data.proto
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.am;
+
+option java_package = "com.android.commands.am";
+
+message ResultsBundleEntry {
+ optional string key = 1;
+
+ optional string value_string = 2;
+ optional sint32 value_int = 3;
+ optional float value_float = 4;
+ optional double value_double = 5;
+ optional sint64 value_long = 6;
+ optional ResultsBundle value_bundle = 7;
+ optional bytes value_bytes = 8;
+}
+
+message ResultsBundle {
+ repeated ResultsBundleEntry entries = 1;
+}
+
+message TestStatus {
+ optional sint32 result_code = 3;
+ optional ResultsBundle results = 4;
+}
+
+enum SessionStatusCode {
+ /**
+ * The command ran successfully. This does not imply that the tests passed.
+ */
+ SESSION_FINISHED = 0;
+
+ /**
+ * There was an unrecoverable error running the tests.
+ */
+ SESSION_ABORTED = 1;
+}
+
+message SessionStatus {
+ optional SessionStatusCode status_code = 1;
+ optional string error_text = 2;
+ optional sint32 result_code = 3;
+ optional ResultsBundle results = 4;
+}
+
+message Session {
+ repeated TestStatus test_status = 1;
+ optional SessionStatus session_status = 2;
+}
+
+
diff --git a/acts/framework/tests/test_utils/instrumentation/intent_builder_test.py b/acts/framework/tests/test_utils/instrumentation/intent_builder_test.py
index deb4d68..f86fac3 100644
--- a/acts/framework/tests/test_utils/instrumentation/intent_builder_test.py
+++ b/acts/framework/tests/test_utils/instrumentation/intent_builder_test.py
@@ -77,7 +77,7 @@
builder.add_key_value_param('float_param', 12.1)
self.assertEqual(
builder.build(),
- 'am start --ez bool_param False --es string_param enabled '
+ 'am start --ez bool_param false --es string_param enabled '
'--ei int_param 5 --ef float_param 12.1')
def test_full_intent_command(self):
@@ -94,7 +94,7 @@
builder.build(),
'am broadcast -a android.intent.action.TEST_ACTION '
'-n package.name/.ComponentName -d file://path/to/file --unit-test '
- '--esn empty --ef numeric_param 11.6 --ez bool_param True')
+ '--esn empty --ef numeric_param 11.6 --ez bool_param true')
if __name__ == '__main__':