Merge "Atest: Send a FindTestFinishEvent when test found in fuzzy search."
diff --git a/atest/Android.bp b/atest/Android.bp
index 47fef47..d67972b 100644
--- a/atest/Android.bp
+++ b/atest/Android.bp
@@ -148,3 +148,29 @@
canonical_path_from_root: false,
},
}
+
+python_library_host {
+ name: "asuite_proto",
+ defaults: ["asuite_default"],
+ srcs: [
+ "proto/*.proto",
+ ],
+ proto: {
+ canonical_path_from_root: false,
+ },
+}
+
+python_library_host {
+ name: "asuite_cc_client",
+ defaults: ["asuite_default"],
+ srcs: [
+ "atest_utils.py",
+ "constants.py",
+ "constants_default.py",
+ "metrics/*.py",
+ ],
+ libs: [
+ "asuite_proto",
+ "asuite_metrics",
+ ],
+}
diff --git a/atest/asuite_metrics.py b/atest/asuite_metrics.py
index c588a1b..8dcd7dc 100644
--- a/atest/asuite_metrics.py
+++ b/atest/asuite_metrics.py
@@ -17,10 +17,17 @@
import json
import logging
import os
-import urllib2
import uuid
-import constants
+try:
+ # PYTHON2
+ from urllib2 import Request
+ from urllib2 import urlopen
+except ImportError:
+ # PYTHON3
+ from urllib.request import Request
+ from urllib.request import urlopen
+
_JSON_HEADERS = {'Content-Type': 'application/json'}
_METRICS_RESPONSE = 'done'
@@ -29,6 +36,8 @@
'.config', 'asuite', '.metadata')
_ANDROID_BUILD_TOP = 'ANDROID_BUILD_TOP'
+DUMMY_UUID = '00000000-0000-4000-8000-000000000000'
+
#pylint: disable=broad-except
def log_event(metrics_url, dummy_key_fallback=True, **kwargs):
@@ -48,15 +57,15 @@
except Exception:
if not dummy_key_fallback:
return
- key = constants.DUMMY_UUID
+ key = DUMMY_UUID
data = {'grouping_key': key,
'run_id': str(uuid.uuid4())}
if kwargs:
data.update(kwargs)
data = json.dumps(data)
- request = urllib2.Request(metrics_url, data=data,
- headers=_JSON_HEADERS)
- response = urllib2.urlopen(request, timeout=_METRICS_TIMEOUT)
+ request = Request(metrics_url, data=data,
+ headers=_JSON_HEADERS)
+ response = urlopen(request, timeout=_METRICS_TIMEOUT)
content = response.read()
if content != _METRICS_RESPONSE:
raise Exception('Unexpected metrics response: %s' % content)
diff --git a/atest/atest.py b/atest/atest.py
index 9ea9707..6ae9214 100755
--- a/atest/atest.py
+++ b/atest/atest.py
@@ -44,6 +44,7 @@
import test_runner_handler
from metrics import metrics
+from metrics import metrics_base
from metrics import metrics_utils
from test_runners import regression_test_runner
@@ -598,6 +599,7 @@
RESULTS_DIR = make_test_run_dir()
with atest_execution_info.AtestExecutionInfo(sys.argv[1:],
RESULTS_DIR) as result_file:
+ metrics_base.MetricsBase.tool_name = constants.TOOL_NAME
EXIT_CODE = main(sys.argv[1:], RESULTS_DIR)
metrics_utils.send_exit_event(EXIT_CODE)
if result_file:
diff --git a/atest/atest_unittest.py b/atest/atest_unittest.py
index 0a786d0..7ffe5e1 100755
--- a/atest/atest_unittest.py
+++ b/atest/atest_unittest.py
@@ -23,6 +23,8 @@
import atest
import constants
import module_info
+
+from metrics import metrics_utils
from test_finders import test_info
if sys.version_info[0] == 2:
@@ -213,7 +215,8 @@
'\x1b[1;37m\x1b[0m\n')
self.assertEqual(capture_output.getvalue(), correct_output)
- def test_validate_exec_mode(self):
+ @mock.patch.object(metrics_utils, 'send_exit_event')
+ def test_validate_exec_mode(self, _send_exit):
"""Test _validate_exec_mode."""
args = []
parsed_args = atest._parse_args(args)
diff --git a/atest/constants_default.py b/atest/constants_default.py
index a6cc856..abc6740 100644
--- a/atest/constants_default.py
+++ b/atest/constants_default.py
@@ -141,7 +141,7 @@
}
PRIVACY_POLICY_URL = 'https://policies.google.com/privacy'
TERMS_SERVICE_URL = 'https://policies.google.com/terms'
-DUMMY_UUID = '00000000-0000-4000-8000-000000000000'
+TOOL_NAME = 'atest'
# VTS plans
VTS_STAGING_PLAN = 'vts-staging-default'
diff --git a/atest/metrics/clearcut_client.py b/atest/metrics/clearcut_client.py
index 39e2745..ecb83c3 100644
--- a/atest/metrics/clearcut_client.py
+++ b/atest/metrics/clearcut_client.py
@@ -26,7 +26,18 @@
import logging
import threading
import time
-import urllib2
+try:
+ # PYTHON2
+ from urllib2 import urlopen
+ from urllib2 import Request
+ from urllib2 import HTTPError
+ from urllib2 import URLError
+except ImportError:
+ # PYTHON3
+ from urllib.request import urlopen
+ from urllib.request import Request
+ from urllib.request import HTTPError
+ from urllib.request import URLError
from proto import clientanalytics_pb2
@@ -83,7 +94,7 @@
def _serialize_events_to_proto(self, events):
log_request = clientanalytics_pb2.LogRequest()
- log_request.request_time_ms = long(time.time() * 1000)
+ log_request.request_time_ms = int(time.time() * 1000)
# pylint: disable=no-member
log_request.client_info.client_type = _CLIENT_TYPE
log_request.log_source = self._log_source
@@ -144,9 +155,9 @@
Args:
data: The serialized proto to send to Clearcut.
"""
- request = urllib2.Request(self._clearcut_url, data=data)
+ request = Request(self._clearcut_url, data=data)
try:
- response = urllib2.urlopen(request)
+ response = urlopen(request)
msg = response.read()
logging.debug('LogRequest successfully sent to Clearcut.')
log_response = clientanalytics_pb2.LogResponse()
@@ -156,10 +167,10 @@
self._min_next_request_time = (log_response.next_request_wait_millis
/ 1000 + time.time())
logging.debug('LogResponse: %s', log_response)
- except urllib2.HTTPError as e:
+ except HTTPError as e:
logging.debug('Failed to push events to Clearcut. Error code: %d',
e.code)
- except urllib2.URLError:
+ except URLError:
logging.debug('Failed to push events to Clearcut.')
except Exception as e:
logging.debug(e)
diff --git a/atest/metrics/metrics.py b/atest/metrics/metrics.py
index 1445d84..f6446a6 100644
--- a/atest/metrics/metrics.py
+++ b/atest/metrics/metrics.py
@@ -17,7 +17,8 @@
"""
import constants
-import metrics_base
+
+from . import metrics_base
class AtestStartEvent(metrics_base.MetricsBase):
"""
@@ -132,3 +133,16 @@
"""
_EVENT_NAME = 'run_tests_finish_event'
duration = constants.EXTERNAL
+
+class LocalDetectEvent(metrics_base.MetricsBase):
+ """
+ Create local detection event and send it to clearcut.
+
+ Usage:
+ metrics.LocalDetectEvent(
+ detect_type=0,
+ result=0)
+ """
+ _EVENT_NAME = 'local_detect_event'
+ detect_type = constants.EXTERNAL
+ result = constants.EXTERNAL
diff --git a/atest/metrics/metrics_base.py b/atest/metrics/metrics_base.py
index b10b5ca..b716092 100644
--- a/atest/metrics/metrics_base.py
+++ b/atest/metrics/metrics_base.py
@@ -15,19 +15,21 @@
"""
Metrics base class.
"""
+import logging
import random
import time
import uuid
import atest_utils
import asuite_metrics
-import clearcut_client
import constants
from proto import clientanalytics_pb2
from proto import external_user_log_pb2
from proto import internal_user_log_pb2
+from . import clearcut_client
+
INTERNAL_USER = 0
EXTERNAL_USER = 1
@@ -50,11 +52,12 @@
_user_key = str(asuite_metrics._get_grouping_key())
#pylint: disable=broad-except
except Exception:
- _user_key = constants.DUMMY_UUID
+ _user_key = asuite_metrics.DUMMY_UUID
_user_type = (EXTERNAL_USER if atest_utils.is_external_run()
else INTERNAL_USER)
_log_source = ATEST_LOG_SOURCE[_user_type]
cc = clearcut_client.Clearcut(_log_source)
+ tool_name = None
def __new__(cls, **kwargs):
"""Send metric event to clearcut.
@@ -67,9 +70,12 @@
A Clearcut instance.
"""
# pylint: disable=no-member
+ if not cls.tool_name:
+ logging.debug('There is no tool_name, and metrics stops sending.')
+ return None
allowed = ({constants.EXTERNAL} if cls._user_type == EXTERNAL_USER
else {constants.EXTERNAL, constants.INTERNAL})
- fields = [k for k, v in vars(cls).iteritems()
+ fields = [k for k, v in vars(cls).items()
if not k.startswith('_') and v in allowed]
fields_and_values = {}
for field in fields:
@@ -78,6 +84,7 @@
params = {'user_key': cls._user_key,
'run_id': cls._run_id,
'user_type': cls._user_type,
+ 'tool_name': cls.tool_name,
cls._EVENT_NAME: fields_and_values}
log_event = cls._build_full_event(ATEST_EVENTS[cls._user_type](**params))
cls.cc.log(log_event)
@@ -95,6 +102,6 @@
A clientanalytics_pb2.LogEvent instance.
"""
log_event = clientanalytics_pb2.LogEvent()
- log_event.event_time_ms = long((time.time() - random.randint(1, 600)) * 1000)
+ log_event.event_time_ms = int((time.time() - random.randint(1, 600)) * 1000)
log_event.source_extension = atest_event.SerializeToString()
return log_event
diff --git a/atest/metrics/metrics_utils.py b/atest/metrics/metrics_utils.py
index 0c1f317..e951eda 100644
--- a/atest/metrics/metrics_utils.py
+++ b/atest/metrics/metrics_utils.py
@@ -18,7 +18,7 @@
import time
-import metrics
+from . import metrics
def static_var(varname, value):
@@ -56,7 +56,7 @@
Returns:
A dict of Duration.
"""
- seconds = long(diff_time_sec)
+ seconds = int(diff_time_sec)
nanos = int((diff_time_sec - seconds)*10**9)
return {'seconds': seconds, 'nanos': nanos}
@@ -74,4 +74,5 @@
stacktrace=stacktrace,
logs=logs)
# pylint: disable=no-member
- clearcut.flush_events()
+ if clearcut:
+ clearcut.flush_events()
diff --git a/atest/proto/external_user_log.proto b/atest/proto/external_user_log.proto
index 26a709b..505497e 100644
--- a/atest/proto/external_user_log.proto
+++ b/atest/proto/external_user_log.proto
@@ -43,12 +43,19 @@
optional Duration duration = 1;
}
+ // Occurs after detection of catching bug by atest have finished
+ message LocalDetectEvent {
+ optional int32 detect_type = 1;
+ optional int32 result = 2;
+ }
+
// ------------------------
// FIELDS FOR ATESTLOGEVENT
// ------------------------
optional string user_key = 1;
optional string run_id = 2;
optional UserType user_type = 3;
+ optional string tool_name = 10;
oneof event {
AtestStartEvent atest_start_event = 4;
AtestExitEvent atest_exit_event = 5;
@@ -56,5 +63,6 @@
BuildFinishEvent build_finish_event = 7;
RunnerFinishEvent runner_finish_event = 8;
RunTestsFinishEvent run_tests_finish_event = 9;
+ LocalDetectEvent local_detect_event = 11;
}
}
diff --git a/atest/proto/external_user_log_pb2.py b/atest/proto/external_user_log_pb2.py
index 56665ed..ba33fd4 100644
--- a/atest/proto/external_user_log_pb2.py
+++ b/atest/proto/external_user_log_pb2.py
@@ -21,7 +21,7 @@
name='proto/external_user_log.proto',
package='',
syntax='proto2',
- serialized_pb=_b('\n\x1dproto/external_user_log.proto\x1a\x12proto/common.proto\"\xfc\x06\n\x15\x41testLogEventExternal\x12\x10\n\x08user_key\x18\x01 \x01(\t\x12\x0e\n\x06run_id\x18\x02 \x01(\t\x12\x1c\n\tuser_type\x18\x03 \x01(\x0e\x32\t.UserType\x12\x43\n\x11\x61test_start_event\x18\x04 \x01(\x0b\x32&.AtestLogEventExternal.AtestStartEventH\x00\x12\x41\n\x10\x61test_exit_event\x18\x05 \x01(\x0b\x32%.AtestLogEventExternal.AtestExitEventH\x00\x12L\n\x16\x66ind_test_finish_event\x18\x06 \x01(\x0b\x32*.AtestLogEventExternal.FindTestFinishEventH\x00\x12\x45\n\x12\x62uild_finish_event\x18\x07 \x01(\x0b\x32\'.AtestLogEventExternal.BuildFinishEventH\x00\x12G\n\x13runner_finish_event\x18\x08 \x01(\x0b\x32(.AtestLogEventExternal.RunnerFinishEventH\x00\x12L\n\x16run_tests_finish_event\x18\t \x01(\x0b\x32*.AtestLogEventExternal.RunTestsFinishEventH\x00\x1a\x11\n\x0f\x41testStartEvent\x1a@\n\x0e\x41testExitEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x11\n\texit_code\x18\x02 \x01(\x05\x1a\x43\n\x13\x46indTestFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x0f\n\x07success\x18\x02 \x01(\x08\x1a@\n\x10\x42uildFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x0f\n\x07success\x18\x02 \x01(\x08\x1aV\n\x11RunnerFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x13\n\x0brunner_name\x18\x03 \x01(\t\x1a\x32\n\x13RunTestsFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.DurationB\x07\n\x05\x65vent')
+ serialized_pb=_b('\n\x1dproto/external_user_log.proto\x1a\x12proto/common.proto\"\x8f\x08\n\x15\x41testLogEventExternal\x12\x10\n\x08user_key\x18\x01 \x01(\t\x12\x0e\n\x06run_id\x18\x02 \x01(\t\x12\x1c\n\tuser_type\x18\x03 \x01(\x0e\x32\t.UserType\x12\x11\n\ttool_name\x18\n \x01(\t\x12\x43\n\x11\x61test_start_event\x18\x04 \x01(\x0b\x32&.AtestLogEventExternal.AtestStartEventH\x00\x12\x41\n\x10\x61test_exit_event\x18\x05 \x01(\x0b\x32%.AtestLogEventExternal.AtestExitEventH\x00\x12L\n\x16\x66ind_test_finish_event\x18\x06 \x01(\x0b\x32*.AtestLogEventExternal.FindTestFinishEventH\x00\x12\x45\n\x12\x62uild_finish_event\x18\x07 \x01(\x0b\x32\'.AtestLogEventExternal.BuildFinishEventH\x00\x12G\n\x13runner_finish_event\x18\x08 \x01(\x0b\x32(.AtestLogEventExternal.RunnerFinishEventH\x00\x12L\n\x16run_tests_finish_event\x18\t \x01(\x0b\x32*.AtestLogEventExternal.RunTestsFinishEventH\x00\x12\x45\n\x12local_detect_event\x18\x0b \x01(\x0b\x32\'.AtestLogEventExternal.LocalDetectEventH\x00\x1a\x11\n\x0f\x41testStartEvent\x1a@\n\x0e\x41testExitEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x11\n\texit_code\x18\x02 \x01(\x05\x1a\x43\n\x13\x46indTestFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x0f\n\x07success\x18\x02 \x01(\x08\x1a@\n\x10\x42uildFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x0f\n\x07success\x18\x02 \x01(\x08\x1aV\n\x11RunnerFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x13\n\x0brunner_name\x18\x03 \x01(\t\x1a\x32\n\x13RunTestsFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x1a\x37\n\x10LocalDetectEvent\x12\x13\n\x0b\x64\x65tect_type\x18\x01 \x01(\x05\x12\x0e\n\x06result\x18\x02 \x01(\x05\x42\x07\n\x05\x65vent')
,
dependencies=[proto_dot_common__pb2.DESCRIPTOR,])
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
@@ -48,8 +48,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=579,
- serialized_end=596,
+ serialized_start=669,
+ serialized_end=686,
)
_ATESTLOGEVENTEXTERNAL_ATESTEXITEVENT = _descriptor.Descriptor(
@@ -85,8 +85,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=598,
- serialized_end=662,
+ serialized_start=688,
+ serialized_end=752,
)
_ATESTLOGEVENTEXTERNAL_FINDTESTFINISHEVENT = _descriptor.Descriptor(
@@ -122,8 +122,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=664,
- serialized_end=731,
+ serialized_start=754,
+ serialized_end=821,
)
_ATESTLOGEVENTEXTERNAL_BUILDFINISHEVENT = _descriptor.Descriptor(
@@ -159,8 +159,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=733,
- serialized_end=797,
+ serialized_start=823,
+ serialized_end=887,
)
_ATESTLOGEVENTEXTERNAL_RUNNERFINISHEVENT = _descriptor.Descriptor(
@@ -203,8 +203,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=799,
- serialized_end=885,
+ serialized_start=889,
+ serialized_end=975,
)
_ATESTLOGEVENTEXTERNAL_RUNTESTSFINISHEVENT = _descriptor.Descriptor(
@@ -233,8 +233,45 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=887,
- serialized_end=937,
+ serialized_start=977,
+ serialized_end=1027,
+)
+
+_ATESTLOGEVENTEXTERNAL_LOCALDETECTEVENT = _descriptor.Descriptor(
+ name='LocalDetectEvent',
+ full_name='AtestLogEventExternal.LocalDetectEvent',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='detect_type', full_name='AtestLogEventExternal.LocalDetectEvent.detect_type', index=0,
+ number=1, type=5, 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='result', full_name='AtestLogEventExternal.LocalDetectEvent.result', index=1,
+ number=2, type=5, 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),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=1029,
+ serialized_end=1084,
)
_ATESTLOGEVENTEXTERNAL = _descriptor.Descriptor(
@@ -266,51 +303,65 @@
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
- name='atest_start_event', full_name='AtestLogEventExternal.atest_start_event', index=3,
+ name='tool_name', full_name='AtestLogEventExternal.tool_name', index=3,
+ number=10, 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='atest_start_event', full_name='AtestLogEventExternal.atest_start_event', index=4,
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),
_descriptor.FieldDescriptor(
- name='atest_exit_event', full_name='AtestLogEventExternal.atest_exit_event', index=4,
+ name='atest_exit_event', full_name='AtestLogEventExternal.atest_exit_event', index=5,
number=5, 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='find_test_finish_event', full_name='AtestLogEventExternal.find_test_finish_event', index=5,
+ name='find_test_finish_event', full_name='AtestLogEventExternal.find_test_finish_event', index=6,
number=6, 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='build_finish_event', full_name='AtestLogEventExternal.build_finish_event', index=6,
+ name='build_finish_event', full_name='AtestLogEventExternal.build_finish_event', index=7,
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='runner_finish_event', full_name='AtestLogEventExternal.runner_finish_event', index=7,
+ name='runner_finish_event', full_name='AtestLogEventExternal.runner_finish_event', index=8,
number=8, 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='run_tests_finish_event', full_name='AtestLogEventExternal.run_tests_finish_event', index=8,
+ name='run_tests_finish_event', full_name='AtestLogEventExternal.run_tests_finish_event', index=9,
number=9, 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='local_detect_event', full_name='AtestLogEventExternal.local_detect_event', index=10,
+ number=11, 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=[_ATESTLOGEVENTEXTERNAL_ATESTSTARTEVENT, _ATESTLOGEVENTEXTERNAL_ATESTEXITEVENT, _ATESTLOGEVENTEXTERNAL_FINDTESTFINISHEVENT, _ATESTLOGEVENTEXTERNAL_BUILDFINISHEVENT, _ATESTLOGEVENTEXTERNAL_RUNNERFINISHEVENT, _ATESTLOGEVENTEXTERNAL_RUNTESTSFINISHEVENT, ],
+ nested_types=[_ATESTLOGEVENTEXTERNAL_ATESTSTARTEVENT, _ATESTLOGEVENTEXTERNAL_ATESTEXITEVENT, _ATESTLOGEVENTEXTERNAL_FINDTESTFINISHEVENT, _ATESTLOGEVENTEXTERNAL_BUILDFINISHEVENT, _ATESTLOGEVENTEXTERNAL_RUNNERFINISHEVENT, _ATESTLOGEVENTEXTERNAL_RUNTESTSFINISHEVENT, _ATESTLOGEVENTEXTERNAL_LOCALDETECTEVENT, ],
enum_types=[
],
options=None,
@@ -323,7 +374,7 @@
index=0, containing_type=None, fields=[]),
],
serialized_start=54,
- serialized_end=946,
+ serialized_end=1093,
)
_ATESTLOGEVENTEXTERNAL_ATESTSTARTEVENT.containing_type = _ATESTLOGEVENTEXTERNAL
@@ -337,6 +388,7 @@
_ATESTLOGEVENTEXTERNAL_RUNNERFINISHEVENT.containing_type = _ATESTLOGEVENTEXTERNAL
_ATESTLOGEVENTEXTERNAL_RUNTESTSFINISHEVENT.fields_by_name['duration'].message_type = proto_dot_common__pb2._DURATION
_ATESTLOGEVENTEXTERNAL_RUNTESTSFINISHEVENT.containing_type = _ATESTLOGEVENTEXTERNAL
+_ATESTLOGEVENTEXTERNAL_LOCALDETECTEVENT.containing_type = _ATESTLOGEVENTEXTERNAL
_ATESTLOGEVENTEXTERNAL.fields_by_name['user_type'].enum_type = proto_dot_common__pb2._USERTYPE
_ATESTLOGEVENTEXTERNAL.fields_by_name['atest_start_event'].message_type = _ATESTLOGEVENTEXTERNAL_ATESTSTARTEVENT
_ATESTLOGEVENTEXTERNAL.fields_by_name['atest_exit_event'].message_type = _ATESTLOGEVENTEXTERNAL_ATESTEXITEVENT
@@ -344,6 +396,7 @@
_ATESTLOGEVENTEXTERNAL.fields_by_name['build_finish_event'].message_type = _ATESTLOGEVENTEXTERNAL_BUILDFINISHEVENT
_ATESTLOGEVENTEXTERNAL.fields_by_name['runner_finish_event'].message_type = _ATESTLOGEVENTEXTERNAL_RUNNERFINISHEVENT
_ATESTLOGEVENTEXTERNAL.fields_by_name['run_tests_finish_event'].message_type = _ATESTLOGEVENTEXTERNAL_RUNTESTSFINISHEVENT
+_ATESTLOGEVENTEXTERNAL.fields_by_name['local_detect_event'].message_type = _ATESTLOGEVENTEXTERNAL_LOCALDETECTEVENT
_ATESTLOGEVENTEXTERNAL.oneofs_by_name['event'].fields.append(
_ATESTLOGEVENTEXTERNAL.fields_by_name['atest_start_event'])
_ATESTLOGEVENTEXTERNAL.fields_by_name['atest_start_event'].containing_oneof = _ATESTLOGEVENTEXTERNAL.oneofs_by_name['event']
@@ -362,6 +415,9 @@
_ATESTLOGEVENTEXTERNAL.oneofs_by_name['event'].fields.append(
_ATESTLOGEVENTEXTERNAL.fields_by_name['run_tests_finish_event'])
_ATESTLOGEVENTEXTERNAL.fields_by_name['run_tests_finish_event'].containing_oneof = _ATESTLOGEVENTEXTERNAL.oneofs_by_name['event']
+_ATESTLOGEVENTEXTERNAL.oneofs_by_name['event'].fields.append(
+ _ATESTLOGEVENTEXTERNAL.fields_by_name['local_detect_event'])
+_ATESTLOGEVENTEXTERNAL.fields_by_name['local_detect_event'].containing_oneof = _ATESTLOGEVENTEXTERNAL.oneofs_by_name['event']
DESCRIPTOR.message_types_by_name['AtestLogEventExternal'] = _ATESTLOGEVENTEXTERNAL
AtestLogEventExternal = _reflection.GeneratedProtocolMessageType('AtestLogEventExternal', (_message.Message,), dict(
@@ -407,6 +463,13 @@
# @@protoc_insertion_point(class_scope:AtestLogEventExternal.RunTestsFinishEvent)
))
,
+
+ LocalDetectEvent = _reflection.GeneratedProtocolMessageType('LocalDetectEvent', (_message.Message,), dict(
+ DESCRIPTOR = _ATESTLOGEVENTEXTERNAL_LOCALDETECTEVENT,
+ __module__ = 'proto.external_user_log_pb2'
+ # @@protoc_insertion_point(class_scope:AtestLogEventExternal.LocalDetectEvent)
+ ))
+ ,
DESCRIPTOR = _ATESTLOGEVENTEXTERNAL,
__module__ = 'proto.external_user_log_pb2'
# @@protoc_insertion_point(class_scope:AtestLogEventExternal)
@@ -418,6 +481,7 @@
_sym_db.RegisterMessage(AtestLogEventExternal.BuildFinishEvent)
_sym_db.RegisterMessage(AtestLogEventExternal.RunnerFinishEvent)
_sym_db.RegisterMessage(AtestLogEventExternal.RunTestsFinishEvent)
+_sym_db.RegisterMessage(AtestLogEventExternal.LocalDetectEvent)
# @@protoc_insertion_point(module_scope)
diff --git a/atest/proto/internal_user_log.proto b/atest/proto/internal_user_log.proto
index c2be26f..8fbecc6 100644
--- a/atest/proto/internal_user_log.proto
+++ b/atest/proto/internal_user_log.proto
@@ -59,12 +59,19 @@
optional Duration duration = 1;
}
+ // Occurs after detection of catching bug by atest have finished
+ message LocalDetectEvent {
+ optional int32 detect_type = 1;
+ optional int32 result = 2;
+ }
+
// ------------------------
// FIELDS FOR ATESTLOGEVENT
// ------------------------
optional string user_key = 1;
optional string run_id = 2;
optional UserType user_type = 3;
+ optional string tool_name = 10;
oneof event {
AtestStartEvent atest_start_event = 4;
AtestExitEvent atest_exit_event = 5;
@@ -72,5 +79,6 @@
BuildFinishEvent build_finish_event = 7;
RunnerFinishEvent runner_finish_event = 8;
RunTestsFinishEvent run_tests_finish_event = 9;
+ LocalDetectEvent local_detect_event = 11;
}
}
diff --git a/atest/proto/internal_user_log_pb2.py b/atest/proto/internal_user_log_pb2.py
index 70d81f3..e8585dc 100644
--- a/atest/proto/internal_user_log_pb2.py
+++ b/atest/proto/internal_user_log_pb2.py
@@ -21,7 +21,7 @@
name='proto/internal_user_log.proto',
package='',
syntax='proto2',
- serialized_pb=_b('\n\x1dproto/internal_user_log.proto\x1a\x12proto/common.proto\"\xb1\t\n\x15\x41testLogEventInternal\x12\x10\n\x08user_key\x18\x01 \x01(\t\x12\x0e\n\x06run_id\x18\x02 \x01(\t\x12\x1c\n\tuser_type\x18\x03 \x01(\x0e\x32\t.UserType\x12\x43\n\x11\x61test_start_event\x18\x04 \x01(\x0b\x32&.AtestLogEventInternal.AtestStartEventH\x00\x12\x41\n\x10\x61test_exit_event\x18\x05 \x01(\x0b\x32%.AtestLogEventInternal.AtestExitEventH\x00\x12L\n\x16\x66ind_test_finish_event\x18\x06 \x01(\x0b\x32*.AtestLogEventInternal.FindTestFinishEventH\x00\x12\x45\n\x12\x62uild_finish_event\x18\x07 \x01(\x0b\x32\'.AtestLogEventInternal.BuildFinishEventH\x00\x12G\n\x13runner_finish_event\x18\x08 \x01(\x0b\x32(.AtestLogEventInternal.RunnerFinishEventH\x00\x12L\n\x16run_tests_finish_event\x18\t \x01(\x0b\x32*.AtestLogEventInternal.RunTestsFinishEventH\x00\x1aY\n\x0f\x41testStartEvent\x12\x14\n\x0c\x63ommand_line\x18\x01 \x01(\t\x12\x17\n\x0ftest_references\x18\x02 \x03(\t\x12\x0b\n\x03\x63wd\x18\x03 \x01(\t\x12\n\n\x02os\x18\x04 \x01(\t\x1a\x62\n\x0e\x41testExitEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x11\n\texit_code\x18\x02 \x01(\x05\x12\x12\n\nstacktrace\x18\x03 \x01(\t\x12\x0c\n\x04logs\x18\x04 \x01(\t\x1a\x84\x01\n\x13\x46indTestFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x16\n\x0etest_reference\x18\x03 \x01(\t\x12\x14\n\x0ctest_finders\x18\x04 \x03(\t\x12\x11\n\ttest_info\x18\x05 \x01(\t\x1aQ\n\x10\x42uildFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x0f\n\x07targets\x18\x03 \x03(\t\x1a\xcd\x01\n\x11RunnerFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x13\n\x0brunner_name\x18\x03 \x01(\t\x12;\n\x04test\x18\x04 \x03(\x0b\x32-.AtestLogEventInternal.RunnerFinishEvent.Test\x1a\x38\n\x04Test\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06result\x18\x02 \x01(\x05\x12\x12\n\nstacktrace\x18\x03 \x01(\t\x1a\x32\n\x13RunTestsFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.DurationB\x07\n\x05\x65vent')
+ serialized_pb=_b('\n\x1dproto/internal_user_log.proto\x1a\x12proto/common.proto\"\xc4\n\n\x15\x41testLogEventInternal\x12\x10\n\x08user_key\x18\x01 \x01(\t\x12\x0e\n\x06run_id\x18\x02 \x01(\t\x12\x1c\n\tuser_type\x18\x03 \x01(\x0e\x32\t.UserType\x12\x11\n\ttool_name\x18\n \x01(\t\x12\x43\n\x11\x61test_start_event\x18\x04 \x01(\x0b\x32&.AtestLogEventInternal.AtestStartEventH\x00\x12\x41\n\x10\x61test_exit_event\x18\x05 \x01(\x0b\x32%.AtestLogEventInternal.AtestExitEventH\x00\x12L\n\x16\x66ind_test_finish_event\x18\x06 \x01(\x0b\x32*.AtestLogEventInternal.FindTestFinishEventH\x00\x12\x45\n\x12\x62uild_finish_event\x18\x07 \x01(\x0b\x32\'.AtestLogEventInternal.BuildFinishEventH\x00\x12G\n\x13runner_finish_event\x18\x08 \x01(\x0b\x32(.AtestLogEventInternal.RunnerFinishEventH\x00\x12L\n\x16run_tests_finish_event\x18\t \x01(\x0b\x32*.AtestLogEventInternal.RunTestsFinishEventH\x00\x12\x45\n\x12local_detect_event\x18\x0b \x01(\x0b\x32\'.AtestLogEventInternal.LocalDetectEventH\x00\x1aY\n\x0f\x41testStartEvent\x12\x14\n\x0c\x63ommand_line\x18\x01 \x01(\t\x12\x17\n\x0ftest_references\x18\x02 \x03(\t\x12\x0b\n\x03\x63wd\x18\x03 \x01(\t\x12\n\n\x02os\x18\x04 \x01(\t\x1a\x62\n\x0e\x41testExitEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x11\n\texit_code\x18\x02 \x01(\x05\x12\x12\n\nstacktrace\x18\x03 \x01(\t\x12\x0c\n\x04logs\x18\x04 \x01(\t\x1a\x84\x01\n\x13\x46indTestFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x16\n\x0etest_reference\x18\x03 \x01(\t\x12\x14\n\x0ctest_finders\x18\x04 \x03(\t\x12\x11\n\ttest_info\x18\x05 \x01(\t\x1aQ\n\x10\x42uildFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x0f\n\x07targets\x18\x03 \x03(\t\x1a\xcd\x01\n\x11RunnerFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x13\n\x0brunner_name\x18\x03 \x01(\t\x12;\n\x04test\x18\x04 \x03(\x0b\x32-.AtestLogEventInternal.RunnerFinishEvent.Test\x1a\x38\n\x04Test\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06result\x18\x02 \x01(\x05\x12\x12\n\nstacktrace\x18\x03 \x01(\t\x1a\x32\n\x13RunTestsFinishEvent\x12\x1b\n\x08\x64uration\x18\x01 \x01(\x0b\x32\t.Duration\x1a\x37\n\x10LocalDetectEvent\x12\x13\n\x0b\x64\x65tect_type\x18\x01 \x01(\x05\x12\x0e\n\x06result\x18\x02 \x01(\x05\x42\x07\n\x05\x65vent')
,
dependencies=[proto_dot_common__pb2.DESCRIPTOR,])
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
@@ -76,8 +76,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=579,
- serialized_end=668,
+ serialized_start=669,
+ serialized_end=758,
)
_ATESTLOGEVENTINTERNAL_ATESTEXITEVENT = _descriptor.Descriptor(
@@ -127,8 +127,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=670,
- serialized_end=768,
+ serialized_start=760,
+ serialized_end=858,
)
_ATESTLOGEVENTINTERNAL_FINDTESTFINISHEVENT = _descriptor.Descriptor(
@@ -185,8 +185,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=771,
- serialized_end=903,
+ serialized_start=861,
+ serialized_end=993,
)
_ATESTLOGEVENTINTERNAL_BUILDFINISHEVENT = _descriptor.Descriptor(
@@ -229,8 +229,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=905,
- serialized_end=986,
+ serialized_start=995,
+ serialized_end=1076,
)
_ATESTLOGEVENTINTERNAL_RUNNERFINISHEVENT_TEST = _descriptor.Descriptor(
@@ -273,8 +273,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=1138,
- serialized_end=1194,
+ serialized_start=1228,
+ serialized_end=1284,
)
_ATESTLOGEVENTINTERNAL_RUNNERFINISHEVENT = _descriptor.Descriptor(
@@ -324,8 +324,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=989,
- serialized_end=1194,
+ serialized_start=1079,
+ serialized_end=1284,
)
_ATESTLOGEVENTINTERNAL_RUNTESTSFINISHEVENT = _descriptor.Descriptor(
@@ -354,8 +354,45 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=1196,
- serialized_end=1246,
+ serialized_start=1286,
+ serialized_end=1336,
+)
+
+_ATESTLOGEVENTINTERNAL_LOCALDETECTEVENT = _descriptor.Descriptor(
+ name='LocalDetectEvent',
+ full_name='AtestLogEventInternal.LocalDetectEvent',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='detect_type', full_name='AtestLogEventInternal.LocalDetectEvent.detect_type', index=0,
+ number=1, type=5, 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='result', full_name='AtestLogEventInternal.LocalDetectEvent.result', index=1,
+ number=2, type=5, 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),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=1338,
+ serialized_end=1393,
)
_ATESTLOGEVENTINTERNAL = _descriptor.Descriptor(
@@ -387,51 +424,65 @@
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
- name='atest_start_event', full_name='AtestLogEventInternal.atest_start_event', index=3,
+ name='tool_name', full_name='AtestLogEventInternal.tool_name', index=3,
+ number=10, 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='atest_start_event', full_name='AtestLogEventInternal.atest_start_event', index=4,
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),
_descriptor.FieldDescriptor(
- name='atest_exit_event', full_name='AtestLogEventInternal.atest_exit_event', index=4,
+ name='atest_exit_event', full_name='AtestLogEventInternal.atest_exit_event', index=5,
number=5, 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='find_test_finish_event', full_name='AtestLogEventInternal.find_test_finish_event', index=5,
+ name='find_test_finish_event', full_name='AtestLogEventInternal.find_test_finish_event', index=6,
number=6, 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='build_finish_event', full_name='AtestLogEventInternal.build_finish_event', index=6,
+ name='build_finish_event', full_name='AtestLogEventInternal.build_finish_event', index=7,
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='runner_finish_event', full_name='AtestLogEventInternal.runner_finish_event', index=7,
+ name='runner_finish_event', full_name='AtestLogEventInternal.runner_finish_event', index=8,
number=8, 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='run_tests_finish_event', full_name='AtestLogEventInternal.run_tests_finish_event', index=8,
+ name='run_tests_finish_event', full_name='AtestLogEventInternal.run_tests_finish_event', index=9,
number=9, 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='local_detect_event', full_name='AtestLogEventInternal.local_detect_event', index=10,
+ number=11, 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=[_ATESTLOGEVENTINTERNAL_ATESTSTARTEVENT, _ATESTLOGEVENTINTERNAL_ATESTEXITEVENT, _ATESTLOGEVENTINTERNAL_FINDTESTFINISHEVENT, _ATESTLOGEVENTINTERNAL_BUILDFINISHEVENT, _ATESTLOGEVENTINTERNAL_RUNNERFINISHEVENT, _ATESTLOGEVENTINTERNAL_RUNTESTSFINISHEVENT, ],
+ nested_types=[_ATESTLOGEVENTINTERNAL_ATESTSTARTEVENT, _ATESTLOGEVENTINTERNAL_ATESTEXITEVENT, _ATESTLOGEVENTINTERNAL_FINDTESTFINISHEVENT, _ATESTLOGEVENTINTERNAL_BUILDFINISHEVENT, _ATESTLOGEVENTINTERNAL_RUNNERFINISHEVENT, _ATESTLOGEVENTINTERNAL_RUNTESTSFINISHEVENT, _ATESTLOGEVENTINTERNAL_LOCALDETECTEVENT, ],
enum_types=[
],
options=None,
@@ -444,7 +495,7 @@
index=0, containing_type=None, fields=[]),
],
serialized_start=54,
- serialized_end=1255,
+ serialized_end=1402,
)
_ATESTLOGEVENTINTERNAL_ATESTSTARTEVENT.containing_type = _ATESTLOGEVENTINTERNAL
@@ -460,6 +511,7 @@
_ATESTLOGEVENTINTERNAL_RUNNERFINISHEVENT.containing_type = _ATESTLOGEVENTINTERNAL
_ATESTLOGEVENTINTERNAL_RUNTESTSFINISHEVENT.fields_by_name['duration'].message_type = proto_dot_common__pb2._DURATION
_ATESTLOGEVENTINTERNAL_RUNTESTSFINISHEVENT.containing_type = _ATESTLOGEVENTINTERNAL
+_ATESTLOGEVENTINTERNAL_LOCALDETECTEVENT.containing_type = _ATESTLOGEVENTINTERNAL
_ATESTLOGEVENTINTERNAL.fields_by_name['user_type'].enum_type = proto_dot_common__pb2._USERTYPE
_ATESTLOGEVENTINTERNAL.fields_by_name['atest_start_event'].message_type = _ATESTLOGEVENTINTERNAL_ATESTSTARTEVENT
_ATESTLOGEVENTINTERNAL.fields_by_name['atest_exit_event'].message_type = _ATESTLOGEVENTINTERNAL_ATESTEXITEVENT
@@ -467,6 +519,7 @@
_ATESTLOGEVENTINTERNAL.fields_by_name['build_finish_event'].message_type = _ATESTLOGEVENTINTERNAL_BUILDFINISHEVENT
_ATESTLOGEVENTINTERNAL.fields_by_name['runner_finish_event'].message_type = _ATESTLOGEVENTINTERNAL_RUNNERFINISHEVENT
_ATESTLOGEVENTINTERNAL.fields_by_name['run_tests_finish_event'].message_type = _ATESTLOGEVENTINTERNAL_RUNTESTSFINISHEVENT
+_ATESTLOGEVENTINTERNAL.fields_by_name['local_detect_event'].message_type = _ATESTLOGEVENTINTERNAL_LOCALDETECTEVENT
_ATESTLOGEVENTINTERNAL.oneofs_by_name['event'].fields.append(
_ATESTLOGEVENTINTERNAL.fields_by_name['atest_start_event'])
_ATESTLOGEVENTINTERNAL.fields_by_name['atest_start_event'].containing_oneof = _ATESTLOGEVENTINTERNAL.oneofs_by_name['event']
@@ -485,6 +538,9 @@
_ATESTLOGEVENTINTERNAL.oneofs_by_name['event'].fields.append(
_ATESTLOGEVENTINTERNAL.fields_by_name['run_tests_finish_event'])
_ATESTLOGEVENTINTERNAL.fields_by_name['run_tests_finish_event'].containing_oneof = _ATESTLOGEVENTINTERNAL.oneofs_by_name['event']
+_ATESTLOGEVENTINTERNAL.oneofs_by_name['event'].fields.append(
+ _ATESTLOGEVENTINTERNAL.fields_by_name['local_detect_event'])
+_ATESTLOGEVENTINTERNAL.fields_by_name['local_detect_event'].containing_oneof = _ATESTLOGEVENTINTERNAL.oneofs_by_name['event']
DESCRIPTOR.message_types_by_name['AtestLogEventInternal'] = _ATESTLOGEVENTINTERNAL
AtestLogEventInternal = _reflection.GeneratedProtocolMessageType('AtestLogEventInternal', (_message.Message,), dict(
@@ -537,6 +593,13 @@
# @@protoc_insertion_point(class_scope:AtestLogEventInternal.RunTestsFinishEvent)
))
,
+
+ LocalDetectEvent = _reflection.GeneratedProtocolMessageType('LocalDetectEvent', (_message.Message,), dict(
+ DESCRIPTOR = _ATESTLOGEVENTINTERNAL_LOCALDETECTEVENT,
+ __module__ = 'proto.internal_user_log_pb2'
+ # @@protoc_insertion_point(class_scope:AtestLogEventInternal.LocalDetectEvent)
+ ))
+ ,
DESCRIPTOR = _ATESTLOGEVENTINTERNAL,
__module__ = 'proto.internal_user_log_pb2'
# @@protoc_insertion_point(class_scope:AtestLogEventInternal)
@@ -549,6 +612,7 @@
_sym_db.RegisterMessage(AtestLogEventInternal.RunnerFinishEvent)
_sym_db.RegisterMessage(AtestLogEventInternal.RunnerFinishEvent.Test)
_sym_db.RegisterMessage(AtestLogEventInternal.RunTestsFinishEvent)
+_sym_db.RegisterMessage(AtestLogEventInternal.LocalDetectEvent)
# @@protoc_insertion_point(module_scope)
diff --git a/src/com/android/tradefed/command/CommandOptions.java b/src/com/android/tradefed/command/CommandOptions.java
index f30fc70..d558e5f 100644
--- a/src/com/android/tradefed/command/CommandOptions.java
+++ b/src/com/android/tradefed/command/CommandOptions.java
@@ -170,6 +170,13 @@
private boolean mUseRemoteSandbox = false;
@Option(
+ name = "parallel-remote-setup",
+ description =
+ "For remote sharded invocation, whether or not to attempt the setup in parallel."
+ )
+ private boolean mUseParallelRemoteSetup = false;
+
+ @Option(
name = "auto-collect",
description =
"Specify a set of collectors that will be automatically managed by the harness "
@@ -523,4 +530,10 @@
public void setHostLogSuffix(String suffix) {
mHostLogSuffix = suffix;
}
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean shouldUseParallelRemoteSetup() {
+ return mUseParallelRemoteSetup;
+ }
}
diff --git a/src/com/android/tradefed/command/ICommandOptions.java b/src/com/android/tradefed/command/ICommandOptions.java
index cb1d0ce..b2fefd2 100644
--- a/src/com/android/tradefed/command/ICommandOptions.java
+++ b/src/com/android/tradefed/command/ICommandOptions.java
@@ -196,4 +196,7 @@
/** Sets the suffix to append to Tradefed host_log. */
public void setHostLogSuffix(String suffix);
+
+ /** Whether or not to attempt parallel setup of the remote devices. */
+ public boolean shouldUseParallelRemoteSetup();
}
diff --git a/src/com/android/tradefed/config/ArgsOptionParser.java b/src/com/android/tradefed/config/ArgsOptionParser.java
index b45631a..fa11657 100644
--- a/src/com/android/tradefed/config/ArgsOptionParser.java
+++ b/src/com/android/tradefed/config/ArgsOptionParser.java
@@ -342,7 +342,7 @@
String tmp = grabNextValue(args, name, "for its key");
// only match = to escape use "\="
Pattern p = Pattern.compile("(?<!\\\\)=");
- String[] parts = p.split(tmp);
+ String[] parts = p.split(tmp, /* allow empty-string values */ -1);
// Note that we replace escaped = (\=) to =.
if (parts.length == 2) {
key = parts[0].replaceAll("\\\\=", "=");
diff --git a/src/com/android/tradefed/device/DeviceSelectionOptions.java b/src/com/android/tradefed/device/DeviceSelectionOptions.java
index 30ce3fa..4d31b87 100644
--- a/src/com/android/tradefed/device/DeviceSelectionOptions.java
+++ b/src/com/android/tradefed/device/DeviceSelectionOptions.java
@@ -344,6 +344,10 @@
mRequestedType = requestedType;
}
+ public DeviceRequestedType getDeviceTypeRequested() {
+ return mRequestedType;
+ }
+
/**
* Sets the minimum battery level
*/
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index 910e9de..c3b8fe4 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -125,8 +125,8 @@
/** the default number of command retry attempts to perform */
protected static final int MAX_RETRY_ATTEMPTS = 2;
- /** Value returned for any invalid/not found user id: UserHandle defined the -10000 value **/
- protected static final int INVALID_USER_ID = -10000;
+ /** Value returned for any invalid/not found user id: UserHandle defined the -10000 value */
+ public static final int INVALID_USER_ID = -10000;
/** regex to match input dispatch readiness line **/
static final Pattern INPUT_DISPATCH_STATE_REGEX =
diff --git a/src/com/android/tradefed/device/cloud/CommonLogRemoteFileUtil.java b/src/com/android/tradefed/device/cloud/CommonLogRemoteFileUtil.java
index 5e39ce9..4b9d492 100644
--- a/src/com/android/tradefed/device/cloud/CommonLogRemoteFileUtil.java
+++ b/src/com/android/tradefed/device/cloud/CommonLogRemoteFileUtil.java
@@ -32,9 +32,9 @@
public class CommonLogRemoteFileUtil {
/** The directory where to find debug logs for a nested remote instance. */
- public static final String NESTED_REMOTE_LOG_DIR = "${HOME}/../vsoc-01/cuttlefish_runtime/";
+ public static final String NESTED_REMOTE_LOG_DIR = "/home/%s/cuttlefish_runtime/";
/** The directory where to find debug logs for an emulator instance. */
- public static final String EMULATOR_REMOTE_LOG_DIR = "/home/vsoc-01/log/";
+ public static final String EMULATOR_REMOTE_LOG_DIR = "/home/%s/log/";
public static final MultiMap<InstanceType, KnownLogFileEntry> KNOWN_FILES_TO_FETCH =
new MultiMap<>();
@@ -72,7 +72,8 @@
new KnownLogFileEntry("/var/log/daemon.log", null, LogDataType.TEXT));
}
- private static class KnownLogFileEntry {
+ /** A representation of a known log entry for remote devices. */
+ public static class KnownLogFileEntry {
public String path;
public String logName;
public LogDataType type;
@@ -110,7 +111,8 @@
gceAvd,
options,
runUtil,
- entry.path,
+ // Default fetch rely on main user
+ String.format(entry.path, options.getInstanceUser()),
entry.type,
entry.logName);
}
diff --git a/src/com/android/tradefed/device/cloud/LaunchCvdHelper.java b/src/com/android/tradefed/device/cloud/LaunchCvdHelper.java
index 5c18cfe..d7dea2a 100644
--- a/src/com/android/tradefed/device/cloud/LaunchCvdHelper.java
+++ b/src/com/android/tradefed/device/cloud/LaunchCvdHelper.java
@@ -29,12 +29,32 @@
* @return The created command line;
*/
public static List<String> createSimpleDeviceCommand(String username, boolean daemon) {
+ return createSimpleDeviceCommand(username, daemon, true, true);
+ }
+
+ /**
+ * Create the command line to start an additional device for a user.
+ *
+ * @param username The user that will run the device.
+ * @param daemon Whether or not to start the device as a daemon.
+ * @param alwaysCreateUserData Whether or not to create the userdata partition
+ * @param blankDataImage whether or not to create the data image.
+ * @return The created command line;
+ */
+ public static List<String> createSimpleDeviceCommand(
+ String username, boolean daemon, boolean alwaysCreateUserData, boolean blankDataImage) {
List<String> command = new ArrayList<>();
command.add("sudo -u " + username);
command.add("/home/" + username + "/bin/launch_cvd");
command.add("-data_policy");
- command.add("always_create");
- command.add("-blank_data_image_mb");
+ if (alwaysCreateUserData) {
+ command.add("always_create");
+ } else {
+ command.add("create_if_missing");
+ }
+ if (blankDataImage) {
+ command.add("-blank_data_image_mb");
+ }
command.add("8000");
if (daemon) {
command.add("-daemon");
diff --git a/src/com/android/tradefed/device/cloud/MultiUserSetupUtil.java b/src/com/android/tradefed/device/cloud/MultiUserSetupUtil.java
index 3d4ccf9..9c2e577 100644
--- a/src/com/android/tradefed/device/cloud/MultiUserSetupUtil.java
+++ b/src/com/android/tradefed/device/cloud/MultiUserSetupUtil.java
@@ -58,6 +58,64 @@
return null;
}
+ /** Create the 'cvd-XX' user on the remote device if missing. */
+ public static CommandResult addExtraCvdUser(
+ int userId,
+ GceAvdInfo remoteInstance,
+ TestDeviceOptions options,
+ IRunUtil runUtil,
+ long timeoutMs) {
+ String useridString = getUserNumber(userId);
+ String username = String.format("cvd-%s", useridString);
+ String createUserCommand =
+ "sudo useradd " + username + " -G plugdev -m -s /bin/bash -p '*'";
+ CommandResult createUserRes =
+ RemoteSshUtil.remoteSshCommandExec(
+ remoteInstance, options, runUtil, timeoutMs, createUserCommand.split(" "));
+ if (!CommandStatus.SUCCESS.equals(createUserRes.getStatus())) {
+ if (createUserRes
+ .getStderr()
+ .contains(String.format("user '%s' already exists", username))) {
+ return null;
+ }
+ return createUserRes;
+ }
+ return null;
+ }
+
+ /** Setup the tuntap interface required to start the Android devices if they are missing. */
+ public static CommandResult setupNetworkInterface(
+ int userId,
+ GceAvdInfo remoteInstance,
+ TestDeviceOptions options,
+ IRunUtil runUtil,
+ long timeoutMs) {
+ if (userId < 9) {
+ // TODO: use 'tuntap show' to check if interface exists already or not.
+ return null;
+ }
+ String useridString = getUserNumber(userId);
+ String mtap = String.format("cvd-mtap-%s", useridString);
+ String wtap = String.format("cvd-wtap-%s", useridString);
+ String addNetworkInterface = "sudo ip tuntap add dev %s mode tap group cvdnetwork";
+ String mtapCommand = String.format(addNetworkInterface, mtap);
+ CommandResult addNetworkInterfaceRes =
+ RemoteSshUtil.remoteSshCommandExec(
+ remoteInstance, options, runUtil, timeoutMs, mtapCommand.split(" "));
+ if (!CommandStatus.SUCCESS.equals(addNetworkInterfaceRes.getStatus())) {
+ return addNetworkInterfaceRes;
+ }
+
+ String wtapCommand = String.format(addNetworkInterface, wtap);
+ addNetworkInterfaceRes =
+ RemoteSshUtil.remoteSshCommandExec(
+ remoteInstance, options, runUtil, timeoutMs, wtapCommand.split(" "));
+ if (!CommandStatus.SUCCESS.equals(addNetworkInterfaceRes.getStatus())) {
+ return addNetworkInterfaceRes;
+ }
+ return null;
+ }
+
/** Setup a new remote user on an existing Cuttlefish VM. */
public static CommandResult prepareRemoteHomeDir(
String mainRootUser,
@@ -107,4 +165,9 @@
public static String getChownCommand(String username) {
return "find /home/" + username + " | sudo xargs chown " + username;
}
+
+ /** Returns the user id string version that follow the remote device notation. */
+ public static String getUserNumber(int userId) {
+ return (userId > 9) ? Integer.toString(userId) : "0" + Integer.toString(userId);
+ }
}
diff --git a/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java b/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
index 3c10300..9b36eca 100644
--- a/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
+++ b/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
@@ -21,6 +21,7 @@
import com.android.tradefed.device.IDeviceMonitor;
import com.android.tradefed.device.IDeviceStateMonitor;
import com.android.tradefed.device.TestDevice;
+import com.android.tradefed.device.cloud.CommonLogRemoteFileUtil.KnownLogFileEntry;
import com.android.tradefed.invoker.RemoteInvocationExecution;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
@@ -34,6 +35,8 @@
import com.google.common.base.Joiner;
import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -55,6 +58,14 @@
IP_TO_USER.put("127.0.0.1:6524", "vsoc-05");
IP_TO_USER.put("127.0.0.1:6525", "vsoc-06");
IP_TO_USER.put("127.0.0.1:6526", "vsoc-07");
+ IP_TO_USER.put("127.0.0.1:6527", "vsoc-08");
+ IP_TO_USER.put("127.0.0.1:6528", "vsoc-09");
+ IP_TO_USER.put("127.0.0.1:6529", "vsoc-10");
+ IP_TO_USER.put("127.0.0.1:6530", "vsoc-11");
+ IP_TO_USER.put("127.0.0.1:6531", "vsoc-12");
+ IP_TO_USER.put("127.0.0.1:6532", "vsoc-13");
+ IP_TO_USER.put("127.0.0.1:6533", "vsoc-14");
+ IP_TO_USER.put("127.0.0.1:6534", "vsoc-15");
}
/** When calling launch_cvd, the launcher.log is populated. */
@@ -92,8 +103,11 @@
}
// Synchronize this so multiple reset do not occur at the same time inside one VM.
synchronized (NestedRemoteDevice.class) {
- // Restart the device
- List<String> createCommand = LaunchCvdHelper.createSimpleDeviceCommand(username, true);
+ // Log the common files before restarting otherwise they are lost
+ logDebugFiles(logger, username);
+ // Restart the device without re-creating the data partitions.
+ List<String> createCommand =
+ LaunchCvdHelper.createSimpleDeviceCommand(username, true, false, false);
CommandResult createRes =
getRunUtil()
.runTimedCmd(
@@ -114,6 +128,32 @@
}
}
+ /**
+ * Log the runtime files of the virtual device before resetting it since they will be deleted.
+ */
+ private void logDebugFiles(ITestLogger logger, String username) {
+ List<KnownLogFileEntry> toFetch =
+ CommonLogRemoteFileUtil.KNOWN_FILES_TO_FETCH.get(getOptions().getInstanceType());
+ if (toFetch != null) {
+ SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
+ for (KnownLogFileEntry entry : toFetch) {
+ File toLog = new File(String.format(entry.path, username));
+ if (!toLog.exists()) {
+ continue;
+ }
+ try (FileInputStreamSource source = new FileInputStreamSource(toLog)) {
+ logger.testLog(
+ String.format(
+ "before_reset_%s_%s_%s",
+ toLog.getName(), username, formatter.format(new Date())),
+ entry.type,
+ source);
+ }
+ }
+ }
+ logBugreport(String.format("before_reset_%s_bugreport", username), logger);
+ }
+
/** TODO: Re-run the target_preparation. */
private boolean reInitDevice(IBuildInfo info) throws DeviceNotAvailableException {
// Reset recovery since it's a new device
diff --git a/src/com/android/tradefed/invoker/InvocationExecution.java b/src/com/android/tradefed/invoker/InvocationExecution.java
index 1bba3af..abf19dc 100644
--- a/src/com/android/tradefed/invoker/InvocationExecution.java
+++ b/src/com/android/tradefed/invoker/InvocationExecution.java
@@ -438,6 +438,7 @@
UnexecutedTestReporterThread reporterThread =
new UnexecutedTestReporterThread(listener, remainingTests);
Runtime.getRuntime().addShutdownHook(reporterThread);
+ TestInvocation.printStageDelimiter(Stage.TEST, false);
try {
for (IRemoteTest test : config.getTests()) {
// For compatibility of those receivers, they are assumed to be single device alloc.
@@ -494,6 +495,7 @@
remainingTests.remove(test);
}
} finally {
+ TestInvocation.printStageDelimiter(Stage.TEST, true);
// TODO: Look if this can be improved to DeviceNotAvailableException too.
Runtime.getRuntime().removeShutdownHook(reporterThread);
}
diff --git a/src/com/android/tradefed/invoker/RemoteInvocationExecution.java b/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
index c128e87..3051db9 100644
--- a/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
+++ b/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.invoker;
+import com.android.annotations.VisibleForTesting;
import com.android.tradefed.build.BuildRetrievalError;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.StubBuildProvider;
@@ -41,6 +42,7 @@
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.proto.FileProtoResultReporter;
import com.android.tradefed.result.proto.ProtoResultParser;
+import com.android.tradefed.result.proto.SummaryProtoResultReporter;
import com.android.tradefed.targetprep.BuildError;
import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.util.CommandResult;
@@ -62,6 +64,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.concurrent.Semaphore;
/** Implementation of {@link InvocationExecution} that drives a remote execution. */
public class RemoteInvocationExecution extends InvocationExecution {
@@ -69,22 +72,24 @@
public static final long PUSH_TF_TIMEOUT = 150000L;
public static final long PULL_RESULT_TIMEOUT = 180000L;
public static final long REMOTE_PROCESS_RUNNING_WAIT = 15000L;
- public static final long LAUNCH_EXTRA_DEVICE = 7 * 60 * 1000L;
+ public static final long LAUNCH_EXTRA_DEVICE = 10 * 60 * 1000L;
public static final long NEW_USER_TIMEOUT = 5 * 60 * 1000L;
public static final String REMOTE_VM_VARIABLE = "REMOTE_VM_ENV";
public static final String REMOTE_USER_DIR = "/home/{$USER}/";
public static final String PROTO_RESULT_NAME = "output.pb";
+ public static final String PROTO_SUMMARY_NAME = "summary-output.pb";
public static final String STDOUT_FILE = "screen-VM_tradefed-stdout.txt";
public static final String STDERR_FILE = "screen-VM_tradefed-stderr.txt";
public static final String REMOTE_CONFIG = "configuration";
public static final String GLOBAL_REMOTE_CONFIG = "global-remote-configuration";
+ public static final String SHARDING_DEVICE_SETUP_TIME = "sharding-device-setup-ms";
private static final int MAX_CONNECTION_REFUSED_COUNT = 3;
private static final int MAX_PUSH_TF_ATTEMPTS = 3;
+ private static final int MAX_WORKER_THREAD = 3;
private String mRemoteTradefedDir = null;
- private String mRemoteFinalResult = null;
private String mRemoteAdbPath = null;
@Override
@@ -127,56 +132,42 @@
if (config.getCommandOptions().getShardCount() != null
&& config.getCommandOptions().getShardIndex() == null) {
if (config.getCommandOptions().getShardCount() > 1) {
+ boolean parallel = config.getCommandOptions().shouldUseParallelRemoteSetup();
+ long startTime = System.currentTimeMillis();
// For each device after the first one we need to start a new device.
- for (int i = 2; i < config.getCommandOptions().getShardCount() + 1; i++) {
- String username = String.format("vsoc-0%s", i);
- CommandResult userSetup =
- MultiUserSetupUtil.prepareRemoteUser(
- username, info, options, runUtil, NEW_USER_TIMEOUT);
- if (userSetup != null) {
- String errorMsg =
- String.format("Failed to setup user: %s", userSetup.getStderr());
- CLog.e(errorMsg);
- listener.invocationFailed(new RuntimeException(errorMsg));
- return;
+ if (!parallel) {
+ for (int i = 2; i < config.getCommandOptions().getShardCount() + 1; i++) {
+ boolean res = startDevice(listener, i, info, options, runUtil, null);
+ if (!res) {
+ return;
+ }
+ }
+ } else {
+ // Parallel setup of devices
+ Semaphore token = new Semaphore(MAX_WORKER_THREAD);
+ List<StartDeviceThread> threads = new ArrayList<>();
+ for (int i = 2; i < config.getCommandOptions().getShardCount() + 1; i++) {
+ StartDeviceThread sdt =
+ new StartDeviceThread(listener, i, info, options, runUtil, token);
+ threads.add(sdt);
+ sdt.start();
}
- CommandResult homeDirSetup =
- MultiUserSetupUtil.prepareRemoteHomeDir(
- options.getInstanceUser(),
- username,
- info,
- options,
- runUtil,
- NEW_USER_TIMEOUT);
- if (homeDirSetup != null) {
- String errorMsg =
- String.format(
- "Failed to setup home dir: %s", homeDirSetup.getStderr());
- CLog.e(errorMsg);
- listener.invocationFailed(new RuntimeException(errorMsg));
- return;
+ boolean res = true;
+ for (StartDeviceThread t : threads) {
+ t.join();
+ res = res & t.getFinalStatus();
}
-
- List<String> startCommand =
- LaunchCvdHelper.createSimpleDeviceCommand(username, true);
- CommandResult startDeviceRes =
- GceManager.remoteSshCommandExecution(
- info,
- options,
- runUtil,
- LAUNCH_EXTRA_DEVICE,
- Joiner.on(" ").join(startCommand));
- if (!CommandStatus.SUCCESS.equals(startDeviceRes.getStatus())) {
- String errorMsg =
- String.format(
- "Failed to start %s: %s",
- username, startDeviceRes.getStderr());
- CLog.e(errorMsg);
- listener.invocationFailed(new RuntimeException(errorMsg));
+ if (!res) {
return;
}
}
+
+ // Log the overhead to start the device
+ long elapsedTime = System.currentTimeMillis() - startTime;
+ context.getBuildInfos()
+ .get(0)
+ .addBuildAttribute(SHARDING_DEVICE_SETUP_TIME, Long.toString(elapsedTime));
}
}
@@ -227,28 +218,10 @@
info, options, runUtil, 120000L, "ls", "-l", mRemoteTradefedDir);
CLog.d("stdout: %s", listRemoteDir.getStdout());
CLog.d("stderr: %s", listRemoteDir.getStderr());
- mRemoteFinalResult = mRemoteTradefedDir + PROTO_RESULT_NAME;
- // Setup the remote reporting to a proto file
- FileProtoResultReporter reporter = new FileProtoResultReporter();
- reporter.setFileOutput(new File(mRemoteFinalResult));
- config.setTestInvocationListener(reporter);
-
- for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) {
- deviceConfig.getDeviceRequirements().setSerial();
- if (deviceConfig.getDeviceRequirements() instanceof DeviceSelectionOptions) {
- ((DeviceSelectionOptions) deviceConfig.getDeviceRequirements())
- .setDeviceTypeRequested(null);
- }
- }
-
- File configFile = FileUtil.createTempFile(config.getName(), ".xml");
+ File configFile = createRemoteConfig(config, listener, mRemoteTradefedDir);
File globalConfig = null;
- config.dumpXml(new PrintWriter(configFile));
try {
- try (InputStreamSource source = new FileInputStreamSource(configFile)) {
- listener.testLog(REMOTE_CONFIG, LogDataType.XML, source);
- }
CLog.d("Pushing Tradefed XML configuration to remote.");
boolean resultPush =
RemoteFileUtil.pushFileToRemote(
@@ -384,22 +357,6 @@
isStillRunning(
currentInvocationListener, configFile, info, options, runUtil, config);
- File resultFile = null;
- if (!stillRunning) {
- resultFile =
- RemoteFileUtil.fetchRemoteFile(
- info, options, runUtil, PULL_RESULT_TIMEOUT, mRemoteFinalResult);
- if (resultFile == null) {
- currentInvocationListener.invocationFailed(
- new RuntimeException(
- String.format(
- "Could not find remote result file at %s",
- mRemoteFinalResult)));
- } else {
- CLog.d("Fetched remote result file!");
- }
- }
-
// Fetch the logs
File stdoutFile =
RemoteFileUtil.fetchRemoteFile(
@@ -427,12 +384,14 @@
}
}
- if (resultFile != null) {
- // Report result to listener.
- ProtoResultParser parser =
- new ProtoResultParser(currentInvocationListener, context, false, "remote-");
- parser.processFinalizedProto(TestRecordProtoUtil.readFromFile(resultFile));
- }
+ fetchAndProcessResults(
+ stillRunning,
+ currentInvocationListener,
+ context,
+ info,
+ options,
+ runUtil,
+ mRemoteTradefedDir);
}
private boolean isStillRunning(
@@ -547,4 +506,230 @@
LogDataType.TEXT,
"full_adb.log");
}
+
+ /**
+ * Create the configuration that will run in the remote VM.
+ *
+ * @param config The main {@link IConfiguration}.
+ * @param logger A logger where to save the XML configuration for debugging.
+ * @param resultDirPath the remote result dir where results should be saved.
+ * @return A file containing the dumped remote XML configuration.
+ * @throws IOException
+ */
+ @VisibleForTesting
+ File createRemoteConfig(IConfiguration config, ITestLogger logger, String resultDirPath)
+ throws IOException {
+ // Setup the remote reporting to a proto file
+ List<ITestInvocationListener> reporters = new ArrayList<>();
+ FileProtoResultReporter protoReporter = new FileProtoResultReporter();
+ protoReporter.setFileOutput(new File(resultDirPath + PROTO_RESULT_NAME));
+ reporters.add(protoReporter);
+ // Setup a summary reporter
+ SummaryProtoResultReporter summaryReporter = new SummaryProtoResultReporter();
+ summaryReporter.setFileOutput(new File(resultDirPath + PROTO_SUMMARY_NAME));
+ reporters.add(summaryReporter);
+
+ config.setTestInvocationListeners(reporters);
+
+ for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) {
+ deviceConfig.getDeviceRequirements().setSerial();
+ if (deviceConfig.getDeviceRequirements() instanceof DeviceSelectionOptions) {
+ ((DeviceSelectionOptions) deviceConfig.getDeviceRequirements())
+ .setDeviceTypeRequested(null);
+ }
+ }
+
+ // Dump and log the configuration
+ File configFile = FileUtil.createTempFile(config.getName(), ".xml");
+ config.dumpXml(new PrintWriter(configFile));
+ try (InputStreamSource source = new FileInputStreamSource(configFile)) {
+ logger.testLog(REMOTE_CONFIG, LogDataType.XML, source);
+ }
+ return configFile;
+ }
+
+ private void fetchAndProcessResults(
+ boolean wasStillRunning,
+ ITestInvocationListener invocationListener,
+ IInvocationContext context,
+ GceAvdInfo info,
+ TestDeviceOptions options,
+ IRunUtil runUtil,
+ String resultDirPath)
+ throws InvalidProtocolBufferException, IOException {
+ File resultFile = null;
+ if (wasStillRunning) {
+ CLog.d("Remote invocation was still running. No result can be pulled.");
+ return;
+ }
+ resultFile =
+ RemoteFileUtil.fetchRemoteFile(
+ info,
+ options,
+ runUtil,
+ PULL_RESULT_TIMEOUT,
+ resultDirPath + PROTO_RESULT_NAME);
+ if (resultFile == null) {
+ invocationListener.invocationFailed(
+ new RuntimeException(
+ String.format(
+ "Could not find remote result file at %s",
+ resultDirPath + PROTO_RESULT_NAME)));
+ return;
+ }
+ CLog.d("Fetched remote result file!");
+ // Report result to listener.
+ try {
+ ProtoResultParser parser =
+ new ProtoResultParser(invocationListener, context, false, "remote-");
+ parser.processFinalizedProto(TestRecordProtoUtil.readFromFile(resultFile));
+ } finally {
+ FileUtil.deleteFile(resultFile);
+ }
+ }
+
+ /**
+ * Method that handles starting an extra Android Virtual Device inside a given remote VM.
+ *
+ * @param listener The invocation {@link ITestInvocationListener}.
+ * @param userId The username id to associate the device with.
+ * @param info The {@link GceAvdInfo} describing the remote VM.
+ * @param options The {@link TestDeviceOptions} of the virtual device.
+ * @param runUtil A {@link IRunUtil} to run host commands
+ * @return True if the device is started successfully, false otherwise.
+ */
+ private boolean startDevice(
+ ITestInvocationListener listener,
+ int userId,
+ GceAvdInfo info,
+ TestDeviceOptions options,
+ IRunUtil runUtil,
+ Semaphore token)
+ throws InterruptedException {
+ String useridString = MultiUserSetupUtil.getUserNumber(userId);
+ String username = String.format("vsoc-%s", useridString);
+ CommandResult userSetup =
+ MultiUserSetupUtil.prepareRemoteUser(
+ username, info, options, runUtil, NEW_USER_TIMEOUT);
+ if (userSetup != null) {
+ String errorMsg = String.format("Failed to setup user: %s", userSetup.getStderr());
+ CLog.e(errorMsg);
+ listener.invocationFailed(new RuntimeException(errorMsg));
+ return false;
+ }
+
+ CommandResult homeDirSetup =
+ MultiUserSetupUtil.prepareRemoteHomeDir(
+ options.getInstanceUser(),
+ username,
+ info,
+ options,
+ runUtil,
+ NEW_USER_TIMEOUT);
+ if (homeDirSetup != null) {
+ String errorMsg =
+ String.format("Failed to setup home dir: %s", homeDirSetup.getStderr());
+ CLog.e(errorMsg);
+ listener.invocationFailed(new RuntimeException(errorMsg));
+ return false;
+ }
+
+ // Create the cvd user if missing
+ CommandResult cvdSetup =
+ MultiUserSetupUtil.addExtraCvdUser(
+ userId, info, options, runUtil, NEW_USER_TIMEOUT);
+ if (cvdSetup != null) {
+ String errorMsg = String.format("Failed to setup user: %s", cvdSetup.getStderr());
+ CLog.e(errorMsg);
+ listener.invocationFailed(new RuntimeException(errorMsg));
+ return false;
+ }
+
+ // Setup the tuntap interface if needed
+ CommandResult tapSetup =
+ MultiUserSetupUtil.setupNetworkInterface(
+ userId, info, options, runUtil, NEW_USER_TIMEOUT);
+ if (tapSetup != null) {
+ String errorMsg =
+ String.format("Failed to setup network interface: %s", tapSetup.getStderr());
+ CLog.e(errorMsg);
+ listener.invocationFailed(new RuntimeException(errorMsg));
+ return false;
+ }
+
+ List<String> startCommand = LaunchCvdHelper.createSimpleDeviceCommand(username, true);
+ if (token != null) {
+ token.acquire();
+ }
+ CommandResult startDeviceRes = null;
+ try {
+ startDeviceRes =
+ GceManager.remoteSshCommandExecution(
+ info,
+ options,
+ runUtil,
+ LAUNCH_EXTRA_DEVICE,
+ Joiner.on(" ").join(startCommand));
+ } finally {
+ if (token != null) {
+ token.release();
+ }
+ }
+ if (!CommandStatus.SUCCESS.equals(startDeviceRes.getStatus())) {
+ String errorMsg =
+ String.format("Failed to start %s: %s", username, startDeviceRes.getStderr());
+ CLog.e(errorMsg);
+ listener.invocationFailed(new RuntimeException(errorMsg));
+ return false;
+ }
+ return true;
+ }
+
+ /** Thread class that allows to start a device asynchronously. */
+ private class StartDeviceThread extends Thread {
+
+ private ITestInvocationListener mListener;
+ private int mUserId;
+ private GceAvdInfo mInfo;
+ private TestDeviceOptions mOptions;
+ private IRunUtil mRunUtil;
+ private Semaphore mToken;
+
+ private boolean mFinalResult = false;
+
+ public StartDeviceThread(
+ ITestInvocationListener listener,
+ int userId,
+ GceAvdInfo info,
+ TestDeviceOptions options,
+ IRunUtil runUtil,
+ Semaphore token) {
+ super();
+ setDaemon(true);
+ setName(String.format("start-device-thread-vsoc-%s", userId));
+ mListener = listener;
+ mUserId = userId;
+ mInfo = info;
+ mOptions = options;
+ mRunUtil = runUtil;
+ mToken = token;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mFinalResult = startDevice(mListener, mUserId, mInfo, mOptions, mRunUtil, mToken);
+ } catch (InterruptedException e) {
+ CLog.e(e);
+ }
+ }
+
+ /**
+ * Returns the final status of the startDevice. Returns true if it succeeded, false
+ * otherwise.
+ */
+ boolean getFinalStatus() {
+ return mFinalResult;
+ }
+ }
}
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index 9f6a12b..6a31ff5 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -717,6 +717,7 @@
context.getSerials());
// Log the chunk of parent host_log before sharding
reportHostLog(listener, config, TRADEFED_LOG_NAME + BEFORE_SHARDING_SUFFIX);
+ config.getLogSaver().invocationEnded(0L);
return;
}
}
@@ -803,4 +804,16 @@
return new InvocationExecution();
}
}
+
+ /** Prints a delimiter for a given Stage of the invocation. */
+ public static void printStageDelimiter(Stage phase, boolean end) {
+ String startEnd = end ? "ENDING" : "STARTING";
+ String message = String.format("===== %s PHASE %s =====", phase, startEnd);
+ String limit = "";
+ for (int i = 0; i < message.length(); i++) {
+ limit += "=";
+ }
+ String finalFormat = String.format("\n%s\n%s\n%s", limit, message, limit);
+ CLog.d(finalFormat);
+ }
}
diff --git a/src/com/android/tradefed/invoker/shard/ShardHelper.java b/src/com/android/tradefed/invoker/shard/ShardHelper.java
index 64e326c..4aa1c06 100644
--- a/src/com/android/tradefed/invoker/shard/ShardHelper.java
+++ b/src/com/android/tradefed/invoker/shard/ShardHelper.java
@@ -106,6 +106,7 @@
ShardMasterResultForwarder resultCollector =
new ShardMasterResultForwarder(buildMasterShardListeners(config), expectedShard);
+ config.getLogSaver().invocationStarted(context);
resultCollector.invocationStarted(context);
synchronized (shardableTests) {
// When shardCount is available only create 1 poller per shard
diff --git a/src/com/android/tradefed/result/IResultSummaryReceiver.java b/src/com/android/tradefed/result/IResultSummaryReceiver.java
new file mode 100644
index 0000000..23644e8
--- /dev/null
+++ b/src/com/android/tradefed/result/IResultSummaryReceiver.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 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.
+ */
+package com.android.tradefed.result;
+
+import com.android.tradefed.result.proto.SummaryRecordProto.SummaryRecord;
+
+/**
+ * Interface describing an {@link ITestInvocationListener} that can receive a summary of the
+ * invocation only instead of the full results.
+ */
+public interface IResultSummaryReceiver {
+
+ /**
+ * Callback where the {@link SummaryRecord} of the results is passed. Will always be called
+ * before {@link ITestInvocationListener#invocationEnded(long)}.
+ *
+ * @param summary a {@link SummaryRecord} containing the summary of the invocation.
+ */
+ public void setResultSummary(SummaryRecord summary);
+}
diff --git a/src/com/android/tradefed/result/LogSaverResultForwarder.java b/src/com/android/tradefed/result/LogSaverResultForwarder.java
index 6e379a2..14d9f84 100644
--- a/src/com/android/tradefed/result/LogSaverResultForwarder.java
+++ b/src/com/android/tradefed/result/LogSaverResultForwarder.java
@@ -18,12 +18,14 @@
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.proto.SummaryRecordProto.SummaryRecord;
import java.io.IOException;
import java.util.List;
/** A {@link ResultForwarder} for saving logs with the global file saver. */
-public class LogSaverResultForwarder extends ResultForwarder implements ILogSaverListener {
+public class LogSaverResultForwarder extends ResultForwarder
+ implements ILogSaverListener, IResultSummaryReceiver {
ILogSaver mLogSaver;
@@ -132,4 +134,20 @@
}
}
}
+
+ /** {@inheritDoc} */
+ @Override
+ public void setResultSummary(SummaryRecord summary) {
+ for (ITestInvocationListener listener : getListeners()) {
+ try {
+ // Forward the logAssociation call
+ if (listener instanceof IResultSummaryReceiver) {
+ ((IResultSummaryReceiver) listener).setResultSummary(summary);
+ }
+ } catch (RuntimeException e) {
+ CLog.e("Failed to setResultSummary on %s", listener);
+ CLog.e(e);
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/com/android/tradefed/result/proto/ProtoResultParser.java b/src/com/android/tradefed/result/proto/ProtoResultParser.java
index 83f9216..3363053 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultParser.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultParser.java
@@ -31,6 +31,7 @@
import com.android.tradefed.result.proto.LogFileProto.LogFileInfo;
import com.android.tradefed.result.proto.TestRecordProto.ChildReference;
import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
+import com.android.tradefed.testtype.suite.ModuleDefinition;
import com.google.common.base.Strings;
import com.google.protobuf.Any;
@@ -254,6 +255,16 @@
try {
IInvocationContext moduleContext =
InvocationContext.fromProto(anyDescription.unpack(Context.class));
+ String message = "Test module started proto";
+ if (moduleContext.getAttributes().containsKey(ModuleDefinition.MODULE_ID)) {
+ message +=
+ (": "
+ + moduleContext
+ .getAttributes()
+ .getUniqueMap()
+ .get(ModuleDefinition.MODULE_ID));
+ }
+ log(message);
mListener.testModuleStarted(moduleContext);
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
@@ -281,16 +292,14 @@
private void handleTestRunStart(TestRecord runProto) {
String id = runProto.getTestRecordId();
- log("Test run started proto: %s", id);
- //if (runProto.getAttemptId() != 0) {
+ log(
+ "Test run started proto: %s. Expected tests: %s. Attempt: %s",
+ id, runProto.getNumExpectedChildren(), runProto.getAttemptId());
mListener.testRunStarted(
id,
(int) runProto.getNumExpectedChildren(),
(int) runProto.getAttemptId(),
timeStampToMillis(runProto.getStartTime()));
- /*} else {
- mListener.testRunStarted(id, (int) runProto.getNumExpectedChildren(), 0, timeStampToMillis(runProto.getStartTime()));
- }*/
}
private void handleTestRunEnd(TestRecord runProto) {
diff --git a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
index 2db6a79..1d8e981 100644
--- a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
+++ b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
@@ -35,6 +35,7 @@
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.xml.XmlEscapers;
+import com.google.gson.Gson;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -103,6 +104,9 @@
private static final String RESULT_ATTR = "result";
private static final String RESULT_TAG = "Result";
+ private static final String RUN_HISTORY = "run_history";
+ private static final String RUN_HISTORY_TAG = "RunHistory";
+ private static final String RUN_TAG = "Run";
private static final String RUNTIME_ATTR = "runtime";
private static final String SCREENSHOT_TAG = "Screenshot";
private static final String SKIPPED_ATTR = "skipped";
@@ -116,11 +120,16 @@
private static final String LOG_FILE_NAME_ATTR = "file_name";
+ /** Helper object for JSON conversion. */
+ private static final class RunHistory {
+ public long startTime;
+ public long endTime;
+ }
+
/**
* Allows to add some attributes to the <Result> tag via {@code serializer.attribute}.
*
- * @param serializer
- * @throws IOException
+ * @param serializer The object that serializes an XML suite result.
*/
public void addSuiteAttributes(XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
@@ -132,7 +141,7 @@
*
* @param parser The parser where to read the attributes from.
* @param context The {@link IInvocationContext} where to put the attributes.
- * @throws XmlPullParserException
+ * @throws XmlPullParserException When XmlPullParser fails.
*/
public void parseSuiteAttributes(XmlPullParser parser, IInvocationContext context)
throws XmlPullParserException {
@@ -142,11 +151,8 @@
/**
* Allows to add some attributes to the <Build> tag via {@code serializer.attribute}.
*
- * @param serializer
- * @param holder
- * @throws IllegalArgumentException
- * @throws IllegalStateException
- * @throws IOException
+ * @param serializer The object that serializes an XML suite result.
+ * @param holder An object that contains information to be written to the suite result.
*/
public void addBuildInfoAttributes(XmlSerializer serializer, SuiteResultHolder holder)
throws IllegalArgumentException, IllegalStateException, IOException {
@@ -158,7 +164,7 @@
*
* @param parser The parser where to read the attributes from.
* @param context The {@link IInvocationContext} where to put the attributes.
- * @throws XmlPullParserException
+ * @throws XmlPullParserException When XmlPullParser fails.
*/
public void parseBuildInfoAttributes(XmlPullParser parser, IInvocationContext context)
throws XmlPullParserException {
@@ -237,6 +243,21 @@
addBuildInfoAttributes(serializer, holder);
serializer.endTag(NS, BUILD_TAG);
+ // Run History
+ String runHistoryJson = holder.context.getAttributes().getUniqueMap().get(RUN_HISTORY);
+ if (runHistoryJson != null) {
+ serializer.startTag(NS, RUN_HISTORY_TAG);
+ Gson gson = new Gson();
+ RunHistory[] runHistories = gson.fromJson(runHistoryJson, RunHistory[].class);
+ for (RunHistory runHistory : runHistories) {
+ serializer.startTag(NS, RUN_TAG);
+ serializer.attribute(NS, START_TIME_ATTR, String.valueOf(runHistory.startTime));
+ serializer.attribute(NS, END_TIME_ATTR, String.valueOf(runHistory.endTime));
+ serializer.endTag(NS, RUN_TAG);
+ }
+ serializer.endTag(NS, RUN_HISTORY_TAG);
+ }
+
// Summary
serializer.startTag(NS, SUMMARY_TAG);
serializer.attribute(NS, PASS_ATTR, Long.toString(holder.passedTests));
@@ -460,6 +481,16 @@
parser.require(XmlPullParser.END_TAG, NS, BUILD_TAG);
parser.nextTag();
+ boolean hasRunHistoryTag = true;
+ try {
+ parser.require(XmlPullParser.START_TAG, NS, RUN_HISTORY_TAG);
+ } catch (XmlPullParserException e) {
+ hasRunHistoryTag = false;
+ }
+ if (hasRunHistoryTag) {
+ handleRunHistoryLevel(parser);
+ }
+
parser.require(XmlPullParser.START_TAG, NS, SUMMARY_TAG);
invocation.completeModules =
@@ -490,6 +521,18 @@
return invocation;
}
+ /** Handle the parsing and replay of all run history information. */
+ private void handleRunHistoryLevel(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ parser.require(XmlPullParser.START_TAG, NS, RUN_TAG);
+ parser.nextTag();
+ parser.require(XmlPullParser.END_TAG, NS, RUN_TAG);
+ }
+ parser.require(XmlPullParser.END_TAG, NS, RUN_HISTORY_TAG);
+ parser.nextTag();
+ }
+
/**
* Handle the parsing and replay of all the information inside a module (class, method,
* failures).
diff --git a/src/com/android/tradefed/sandbox/TradefedSandbox.java b/src/com/android/tradefed/sandbox/TradefedSandbox.java
index a518f95..dfa544c 100644
--- a/src/com/android/tradefed/sandbox/TradefedSandbox.java
+++ b/src/com/android/tradefed/sandbox/TradefedSandbox.java
@@ -195,7 +195,10 @@
getTradefedSandboxEnvironment(
context,
config,
- QuotationAwareTokenizer.tokenizeLine(config.getCommandLine()));
+ QuotationAwareTokenizer.tokenizeLine(
+ config.getCommandLine(),
+ /** no logging */
+ false));
} catch (ConfigurationException e) {
return e;
}
diff --git a/src/com/android/tradefed/suite/checker/UserChecker.java b/src/com/android/tradefed/suite/checker/UserChecker.java
index d23b88c..aa84de1 100644
--- a/src/com/android/tradefed/suite/checker/UserChecker.java
+++ b/src/com/android/tradefed/suite/checker/UserChecker.java
@@ -142,7 +142,7 @@
DeviceUserState(ITestDevice device) throws DeviceNotAvailableException {
mCurrentUser = device.getCurrentUser();
mUsers = device.listUsers();
- mUserRunningStates = new HashMap(mUsers.size());
+ mUserRunningStates = new HashMap<>(mUsers.size());
for (Integer userId : mUsers) {
mUserRunningStates.put(userId, device.isUserRunning(userId));
}
diff --git a/src/com/android/tradefed/targetprep/AoaTargetPreparer.java b/src/com/android/tradefed/targetprep/AoaTargetPreparer.java
index 3402882..4e331a3 100644
--- a/src/com/android/tradefed/targetprep/AoaTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/AoaTargetPreparer.java
@@ -137,6 +137,12 @@
)
private long mDeviceTimeout = 60 * 1000;
+ @Option(
+ name = "wait-for-device-online",
+ description = "Checks whether the device is online after preparation."
+ )
+ private boolean mWaitForDeviceOnline = true;
+
@Option(name = "action", description = "AOAv2 action to perform. Can be repeated.")
private List<String> mActions = new ArrayList<>();
@@ -152,11 +158,16 @@
} catch (RuntimeException e) {
throw new TargetSetupError(e.getMessage(), e, device.getDeviceDescriptor());
}
+
+ if (mWaitForDeviceOnline) {
+ // Verify that the device is online after executing AOA actions
+ device.waitForDeviceOnline();
+ }
}
// Connect to device using its serial number and perform actions
private void configure(String serialNumber) throws DeviceNotAvailableException {
- try (UsbHelper usb = new UsbHelper();
+ try (UsbHelper usb = getUsbHelper();
AoaDevice device =
usb.getAoaDevice(serialNumber, Duration.ofMillis(mDeviceTimeout))) {
if (device == null) {
@@ -167,6 +178,11 @@
}
}
+ @VisibleForTesting
+ UsbHelper getUsbHelper() {
+ return new UsbHelper();
+ }
+
// Parse and execute an action
@VisibleForTesting
void execute(AoaDevice device, String input) {
diff --git a/src/com/android/tradefed/targetprep/CrashCollector.java b/src/com/android/tradefed/targetprep/CrashCollector.java
index a1770d1..63f80e8 100644
--- a/src/com/android/tradefed/targetprep/CrashCollector.java
+++ b/src/com/android/tradefed/targetprep/CrashCollector.java
@@ -99,6 +99,10 @@
clearTestFileName();
addTestFileName(mCrashCollectorPath);
super.setUp(device, buildInfo);
+ if (getFailedToPushFiles().contains(mCrashCollectorPath)) {
+ CLog.w("Failed to push crash collector binary. Skipping the collection.");
+ return;
+ }
String crashCollectorPath = String.format("/data/%s/%s",
mCrashCollectorPath, mCrashCollectorBinary);
device.executeShellCommand("chmod 755 " + crashCollectorPath);
diff --git a/src/com/android/tradefed/targetprep/CreateUserPreparer.java b/src/com/android/tradefed/targetprep/CreateUserPreparer.java
new file mode 100644
index 0000000..c379be4
--- /dev/null
+++ b/src/com/android/tradefed/targetprep/CreateUserPreparer.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 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.
+ */
+package com.android.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.TestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/** Target preparer for creating user and cleaning it up at the end. */
+public class CreateUserPreparer extends BaseTargetPreparer implements ITargetCleaner {
+
+ private static final String TF_CREATED_USER = "tf_created_user";
+
+ private Integer mOriginalUser = null;
+ private Integer mCreatedUserId = null;
+
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo)
+ throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ mOriginalUser = device.getCurrentUser();
+ if (mOriginalUser == TestDevice.INVALID_USER_ID) {
+ mOriginalUser = null;
+ throw new TargetSetupError(
+ "Failed to get the current user.", device.getDeviceDescriptor());
+ }
+ try {
+ mCreatedUserId = device.createUser(TF_CREATED_USER);
+ } catch (IllegalStateException e) {
+ throw new TargetSetupError("Failed to create user.", e, device.getDeviceDescriptor());
+ }
+ if (!device.switchUser(mCreatedUserId)) {
+ throw new TargetSetupError(
+ String.format("Failed to switch to user '%s'", mCreatedUserId),
+ device.getDeviceDescriptor());
+ }
+ }
+
+ @Override
+ public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+ throws DeviceNotAvailableException {
+ if (mCreatedUserId == null) {
+ return;
+ }
+ if (mOriginalUser == null) {
+ return;
+ }
+ if (e instanceof DeviceNotAvailableException) {
+ CLog.d("Skipping teardown due to dnae: %s", e.getMessage());
+ return;
+ }
+ if (!device.switchUser(mOriginalUser)) {
+ CLog.e("Failed to switch back to original user '%s'", mOriginalUser);
+ }
+ if (!device.removeUser(mCreatedUserId)) {
+ CLog.e(
+ "Failed to delete user %s on device %s",
+ mCreatedUserId, device.getSerialNumber());
+ }
+ }
+}
diff --git a/src/com/android/tradefed/targetprep/DeviceCleaner.java b/src/com/android/tradefed/targetprep/DeviceCleaner.java
index 3018d7d..4d109a1 100644
--- a/src/com/android/tradefed/targetprep/DeviceCleaner.java
+++ b/src/com/android/tradefed/targetprep/DeviceCleaner.java
@@ -130,7 +130,7 @@
}
private void turnScreenOff(ITestDevice device) throws DeviceNotAvailableException {
- String output = device.executeShellCommand("dumpsys power");
+ String output = device.executeShellCommand("dumpsys power | grep 'mScreenOn|mInteractive'");
int retries = 1;
// screen on semantics have changed in newest API platform, checking for both signatures
// to detect screen on state
@@ -140,7 +140,7 @@
// due to framework initialization, device may not actually turn off screen
// after boot, recheck screen status with linear backoff
RunUtil.getDefault().sleep(SCREEN_OFF_RETRY_DELAY_MS * retries);
- output = device.executeShellCommand("dumpsys power");
+ output = device.executeShellCommand("dumpsys power | grep 'mScreenOn|mInteractive'");
retries++;
if (retries > MAX_SCREEN_OFF_RETRY) {
CLog.w(String.format("screen still on after %d retries", retries));
diff --git a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
index 07f5368..e65f9bd 100644
--- a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
@@ -143,14 +143,15 @@
CLog.e("Device %s is not available. Teardown() skipped.", device.getSerialNumber());
return;
} else {
- for (String apkPkgName : mApkInstalled) {
- super.uninstallPackage(device, apkPkgName);
- }
- device.deleteFile(APEX_DATA_DIR + "*");
- device.deleteFile(STAGING_DATA_DIR + "*");
- device.deleteFile(SESSION_DATA_DIR + "*");
- if (!mTestApexInfoList.isEmpty()) {
- device.reboot();
+ if (mTestApexInfoList.isEmpty() && getApkInstalled().isEmpty()) {
+ super.tearDown(device, buildInfo, e);
+ } else {
+ for (String apkPkgName : getApkInstalled()) {
+ super.uninstallPackage(device, apkPkgName);
+ }
+ if (!mTestApexInfoList.isEmpty()) {
+ cleanUpStagedAndActiveSession(device);
+ }
}
}
}
@@ -412,17 +413,24 @@
private void cleanUpStagedAndActiveSession(ITestDevice device)
throws DeviceNotAvailableException {
boolean reboot = false;
- if (!device.executeShellV2Command("ls " + APEX_DATA_DIR).getStdout().isEmpty()) {
+ if (!mTestApexInfoList.isEmpty()) {
device.deleteFile(APEX_DATA_DIR + "*");
- reboot = true;
- }
- if (!device.executeShellV2Command("ls " + STAGING_DATA_DIR).getStdout().isEmpty()) {
device.deleteFile(STAGING_DATA_DIR + "*");
- reboot = true;
- }
- if (!device.executeShellV2Command("ls " + SESSION_DATA_DIR).getStdout().isEmpty()) {
device.deleteFile(SESSION_DATA_DIR + "*");
reboot = true;
+ } else {
+ if (!device.executeShellV2Command("ls " + APEX_DATA_DIR).getStdout().isEmpty()) {
+ device.deleteFile(APEX_DATA_DIR + "*");
+ reboot = true;
+ }
+ if (!device.executeShellV2Command("ls " + STAGING_DATA_DIR).getStdout().isEmpty()) {
+ device.deleteFile(STAGING_DATA_DIR + "*");
+ reboot = true;
+ }
+ if (!device.executeShellV2Command("ls " + SESSION_DATA_DIR).getStdout().isEmpty()) {
+ device.deleteFile(SESSION_DATA_DIR + "*");
+ reboot = true;
+ }
}
if (reboot) {
device.reboot();
@@ -465,5 +473,10 @@
protected BundletoolUtil getBundletoolUtil() {
return mBundletoolUtil;
}
+
+ @VisibleForTesting
+ protected List<String> getApkInstalled() {
+ return mApkInstalled;
+ }
}
diff --git a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
index f67ff20..f1b46f7 100644
--- a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
+++ b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
@@ -370,6 +370,9 @@
device.getDeviceDescriptor());
}
if (mCleanup) {
+ if (mPackagesInstalled == null) {
+ mPackagesInstalled = new ArrayList<>();
+ }
mPackagesInstalled.addAll(packageNames);
}
}
diff --git a/src/com/android/tradefed/targetprep/TestFilePushSetup.java b/src/com/android/tradefed/targetprep/TestFilePushSetup.java
index 06f9b5d..8cee953 100644
--- a/src/com/android/tradefed/targetprep/TestFilePushSetup.java
+++ b/src/com/android/tradefed/targetprep/TestFilePushSetup.java
@@ -31,7 +31,9 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* A {@link ITargetPreparer} that pushes one or more files/dirs from a {@link
@@ -63,6 +65,8 @@
+ "when searching for files to push")
private AltDirBehavior mAltDirBehavior = AltDirBehavior.FALLBACK;
+ private Set<String> mFailedToPush = new HashSet<>();
+
/**
* Adds a file to the list of items to push
*
@@ -155,10 +159,11 @@
"Could not find test file %s directory in extracted tests.zip",
fileName), device.getDeviceDescriptor());
} else {
- CLog.w(String.format(
- "Could not find test file %s directory in extracted tests.zip, but" +
- "will continue test setup as throw-if-not-found is set to false",
- fileName));
+ CLog.w(
+ "Could not find test file %s directory in extracted tests.zip, but will"
+ + " continue test setup as throw-if-not-found is set to false",
+ fileName);
+ mFailedToPush.add(fileName);
continue;
}
}
@@ -197,6 +202,14 @@
mAltDirBehavior = behavior;
}
+ /**
+ * Returns the set of files that failed to be pushed. Can only be used if 'throw-if-not-found'
+ * is false otherwise the first failed push will throw an exception.
+ */
+ protected Set<String> getFailedToPushFiles() {
+ return mFailedToPush;
+ }
+
static String getDevicePathFromUserData(String path) {
return ArrayUtil.join(FileListingService.FILE_SEPARATOR,
"", FileListingService.DIRECTORY_DATA, path);
diff --git a/src/com/android/tradefed/testtype/AndroidJUnitTest.java b/src/com/android/tradefed/testtype/AndroidJUnitTest.java
index d9602b0..bc70bb6 100644
--- a/src/com/android/tradefed/testtype/AndroidJUnitTest.java
+++ b/src/com/android/tradefed/testtype/AndroidJUnitTest.java
@@ -39,9 +39,12 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
import java.util.Set;
/**
@@ -64,6 +67,8 @@
private static final String INCLUDE_PACKAGE_INST_ARGS_KEY = "package";
/** instrumentation test runner argument key used for excluding a package */
private static final String EXCLUDE_PACKAGE_INST_ARGS_KEY = "notPackage";
+ /** instrumentation test runner argument key used for including a test regex */
+ private static final String INCLUDE_REGEX_INST_ARGS_KEY = "tests_regex";
/** instrumentation test runner argument key used for adding annotation filter */
private static final String ANNOTATION_INST_ARGS_KEY = "annotation";
/** instrumentation test runner argument key used for adding notAnnotation filter */
@@ -366,14 +371,19 @@
List<String> notClassArg = new ArrayList<String>();
List<String> packageArg = new ArrayList<String>();
List<String> notPackageArg = new ArrayList<String>();
+ List<String> regexArg = new ArrayList<String>();
for (String test : mIncludeFilters) {
- if (isClassOrMethod(test)) {
+ if (isRegex(test)) {
+ regexArg.add(test);
+ } else if (isClassOrMethod(test)) {
classArg.add(test);
} else {
packageArg.add(test);
}
}
for (String test : mExcludeFilters) {
+ // tests_regex doesn't support exclude-filter. Therefore, only check if the filter is
+ // for class/method or package.
if (isClassOrMethod(test)) {
notClassArg.add(test);
} else {
@@ -396,6 +406,16 @@
runner.addInstrumentationArg(EXCLUDE_PACKAGE_INST_ARGS_KEY,
ArrayUtil.join(",", notPackageArg));
}
+ if (!regexArg.isEmpty()) {
+ String regexFilter;
+ if (regexArg.size() == 1) {
+ regexFilter = regexArg.get(0);
+ } else {
+ Collections.sort(regexArg);
+ regexFilter = "\"(" + ArrayUtil.join("|", regexArg) + ")\"";
+ }
+ runner.addInstrumentationArg(INCLUDE_REGEX_INST_ARGS_KEY, regexFilter);
+ }
if (!mIncludeAnnotation.isEmpty()) {
runner.addInstrumentationArg(ANNOTATION_INST_ARGS_KEY,
ArrayUtil.join(",", mIncludeAnnotation));
@@ -499,6 +519,24 @@
return false;
}
+ /** Return if a string is a regex for filter. */
+ @VisibleForTesting
+ public boolean isRegex(String filter) {
+ // If filter contains any special regex character, return true.
+ // Throw RuntimeException if the regex is invalid.
+ if (Pattern.matches(".*[\\?\\*\\^\\$\\(\\)\\[\\]\\{\\}\\|\\\\].*", filter)) {
+ try {
+ Pattern.compile(filter);
+ } catch (PatternSyntaxException e) {
+ CLog.e("Filter %s is not a valid regular expression string.", filter);
+ throw new RuntimeException(e);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Helper to return if the runner is one that support sharding.
*/
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index f74ea47..b44c3c8 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -29,12 +29,14 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.NullDevice;
import com.android.tradefed.device.StubDevice;
+import com.android.tradefed.device.cloud.NestedRemoteDevice;
import com.android.tradefed.device.metric.CollectorHelper;
import com.android.tradefed.device.metric.IMetricCollector;
import com.android.tradefed.device.metric.IMetricCollectorReceiver;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.shard.token.ITokenRequest;
import com.android.tradefed.invoker.shard.token.TokenProperty;
+import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ITestInvocationListener;
@@ -283,6 +285,12 @@
private boolean mIntraModuleSharding = true;
@Option(
+ name = "isolated-module",
+ description = "Whether or not to attempt the module isolation between modules"
+ )
+ private boolean mIsolatedModule = false;
+
+ @Option(
name = "reboot-before-test",
description = "Reboot the device before the test suite starts."
)
@@ -585,6 +593,8 @@
// execution
listener.testModuleEnded();
}
+ // Module isolation routine
+ moduleIsolation(mContext, listener);
}
} catch (DeviceNotAvailableException e) {
CLog.e(
@@ -605,6 +615,31 @@
}
/**
+ * Routine that attempt to reset a device between modules in order to provide isolation.
+ *
+ * @param context The invocation context.
+ * @param logger A logger where extra logs can be saved.
+ * @throws DeviceNotAvailableException
+ */
+ private void moduleIsolation(IInvocationContext context, ITestLogger logger)
+ throws DeviceNotAvailableException {
+ // TODO: we can probably make it smarter: Did any test ran for example?
+ ITestDevice device = context.getDevices().get(0);
+ if (mIsolatedModule && (device instanceof NestedRemoteDevice)) {
+ boolean res =
+ ((NestedRemoteDevice) device)
+ .resetVirtualDevice(logger, context.getBuildInfos().get(0));
+ if (!res) {
+ String serial = device.getSerialNumber();
+ throw new DeviceNotAvailableException(
+ String.format(
+ "Failed to reset the AVD '%s' during module isolation.", serial),
+ serial);
+ }
+ }
+ }
+
+ /**
* Helper method that handle running a single module logic.
*
* @param module The {@link ModuleDefinition} to be ran.
diff --git a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
index 4e94331..dbc3d9f 100644
--- a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
+++ b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
@@ -235,9 +235,7 @@
&& !config.getConfigurationDescription()
.getSuiteTags()
.contains(suiteTag)) {
- CLog.d(
- "Configuration %s does not include the suite-tag '%s'. Ignoring it.",
- configFullName, suiteTag);
+ // Do not print here, it could leave several hundred lines of logs.
continue;
}
diff --git a/src/com/android/tradefed/util/FileUtil.java b/src/com/android/tradefed/util/FileUtil.java
index ec4897c..9d919e9 100644
--- a/src/com/android/tradefed/util/FileUtil.java
+++ b/src/com/android/tradefed/util/FileUtil.java
@@ -175,8 +175,7 @@
}
public static boolean chmod(File file, String perms) {
- Log.d(LOG_TAG, String.format("Attempting to chmod %s to %s",
- file.getAbsolutePath(), perms));
+ // No need to print, runUtil already prints the command
CommandResult result =
RunUtil.getDefault().runTimedCmd(10 * 1000, sChmod, perms, file.getAbsolutePath());
return result.getStatus().equals(CommandStatus.SUCCESS);
diff --git a/src/com/android/tradefed/util/GCSCommon.java b/src/com/android/tradefed/util/GCSCommon.java
index 8b4d8e8..c1d738d 100644
--- a/src/com/android/tradefed/util/GCSCommon.java
+++ b/src/com/android/tradefed/util/GCSCommon.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.util;
+import com.android.tradefed.host.HostOptions;
import com.android.tradefed.log.LogUtil.CLog;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
@@ -32,6 +33,8 @@
* GCSFileUploader}.
*/
public abstract class GCSCommon {
+ /** This is the key for {@link HostOptions}'s service-account-json-key-file option. */
+ private static final String GCS_JSON_KEY = "gcs-json-key";
protected static final int DEFAULT_TIMEOUT = 10 * 60 * 1000; // 10minutes
@@ -49,10 +52,9 @@
try {
if (mStorage == null) {
if (mJsonKeyFile != null && mJsonKeyFile.exists()) {
- CLog.d("Using json key file %s.", mJsonKeyFile);
credential =
- GoogleApiClientUtil.createCredentialFromJsonKeyFile(
- mJsonKeyFile, scopes);
+ GoogleApiClientUtil.createCredential(
+ scopes, mJsonKeyFile, GCS_JSON_KEY);
} else {
CLog.d("Using local authentication.");
try {
diff --git a/src/com/android/tradefed/util/GoogleApiClientUtil.java b/src/com/android/tradefed/util/GoogleApiClientUtil.java
index 5108fbb..e8a720c 100644
--- a/src/com/android/tradefed/util/GoogleApiClientUtil.java
+++ b/src/com/android/tradefed/util/GoogleApiClientUtil.java
@@ -15,6 +15,8 @@
*/
package com.android.tradefed.util;
+import com.android.tradefed.config.GlobalConfiguration;
+import com.android.tradefed.host.HostOptions;
import com.android.tradefed.log.LogUtil.CLog;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
@@ -26,19 +28,31 @@
import com.google.api.client.http.HttpUnsuccessfulResponseHandler;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.ExponentialBackOff;
+import com.google.common.annotations.VisibleForTesting;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
/** Utils for create Google API client. */
public class GoogleApiClientUtil {
public static final String APP_NAME = "tradefed";
-
+ private static GoogleApiClientUtil sInstance = null;
+
+ private static GoogleApiClientUtil getInstance() {
+ if (sInstance == null) {
+ sInstance = new GoogleApiClientUtil();
+ }
+ return sInstance;
+ }
+
/**
* Create credential from json key file.
*
@@ -51,6 +65,12 @@
*/
public static GoogleCredential createCredentialFromJsonKeyFile(
File file, Collection<String> scopes) throws IOException, GeneralSecurityException {
+ return getInstance().doCreateCredentialFromJsonKeyFile(file, scopes);
+ }
+
+ @VisibleForTesting
+ GoogleCredential doCreateCredentialFromJsonKeyFile(File file, Collection<String> scopes)
+ throws IOException, GeneralSecurityException {
return GoogleCredential.fromStream(
new FileInputStream(file),
GoogleNetHttpTransport.newTrustedTransport(),
@@ -59,6 +79,87 @@
}
/**
+ * Try to create credential with different key files or from local host.
+ *
+ * <p>1. If primaryKeyFile is set, try to use it to create credential. 2. Try to get
+ * corresponding key files from {@link HostOptions}. 3. Try to use backup key files. 4. Use
+ * local default credential.
+ *
+ * @param scopes scopes for the credential.
+ * @param primaryKeyFile the primary json key file; it can be null.
+ * @param hostOptionKeyFileName {@link HostOptions}'service-account-json-key-file option's key;
+ * it can be null.
+ * @param backupKeyFiles backup key files.
+ * @return a {@link GoogleCredential}
+ * @throws IOException
+ * @throws GeneralSecurityException
+ */
+ public static GoogleCredential createCredential(
+ Collection<String> scopes,
+ File primaryKeyFile,
+ String hostOptionKeyFileName,
+ File... backupKeyFiles)
+ throws IOException, GeneralSecurityException {
+ return getInstance()
+ .doCreateCredential(scopes, primaryKeyFile, hostOptionKeyFileName, backupKeyFiles);
+ }
+
+ @VisibleForTesting
+ GoogleCredential doCreateCredential(
+ Collection<String> scopes,
+ File primaryKeyFile,
+ String hostOptionKeyFileName,
+ File... backupKeyFiles)
+ throws IOException, GeneralSecurityException {
+ List<File> keyFiles = new ArrayList<File>();
+ if (primaryKeyFile != null) {
+ keyFiles.add(primaryKeyFile);
+ }
+ File hostOptionKeyFile = null;
+ if (hostOptionKeyFileName != null) {
+ try {
+ hostOptionKeyFile =
+ GlobalConfiguration.getInstance()
+ .getHostOptions()
+ .getServiceAccountJsonKeyFiles()
+ .get(hostOptionKeyFileName);
+ if (hostOptionKeyFile != null) {
+ keyFiles.add(hostOptionKeyFile);
+ }
+ } catch (IllegalStateException e) {
+ CLog.d("Global configuration haven't been initialized.");
+ }
+ }
+ keyFiles.addAll(Arrays.asList(backupKeyFiles));
+ for (File keyFile : keyFiles) {
+ if (keyFile != null) {
+ if (keyFile.exists() && keyFile.canRead()) {
+ CLog.d("Using %s.", keyFile.getAbsolutePath());
+ return doCreateCredentialFromJsonKeyFile(keyFile, scopes);
+ } else {
+ CLog.i("No access to %s.", keyFile.getAbsolutePath());
+ }
+ }
+ }
+ return doCreateDefaultCredential(scopes);
+ }
+
+ @VisibleForTesting
+ GoogleCredential doCreateDefaultCredential(Collection<String> scopes) throws IOException {
+ try {
+ CLog.d("Using local authentication.");
+ return GoogleCredential.getApplicationDefault().createScoped(scopes);
+ } catch (IOException e) {
+ CLog.e(
+ "Try 'gcloud auth application-default login' to login for "
+ + "personal account; Or 'export "
+ + "GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json' "
+ + "for service account.");
+ throw e;
+ }
+ }
+
+ /**
* Create credential from p12 file for service account.
*
* @deprecated It's better to use json key file, since p12 is deprecated by Google App Engine.
diff --git a/src/com/android/tradefed/util/IRunUtil.java b/src/com/android/tradefed/util/IRunUtil.java
index 4113588..3a36b7d 100644
--- a/src/com/android/tradefed/util/IRunUtil.java
+++ b/src/com/android/tradefed/util/IRunUtil.java
@@ -46,6 +46,11 @@
* Cancel the operation.
*/
public void cancel();
+
+ /** Returns the command associated with the runnable. */
+ public default List<String> getCommand() {
+ return null;
+ }
}
/**
diff --git a/src/com/android/tradefed/util/RunUtil.java b/src/com/android/tradefed/util/RunUtil.java
index 593559b..fd27843 100644
--- a/src/com/android/tradefed/util/RunUtil.java
+++ b/src/com/android/tradefed/util/RunUtil.java
@@ -28,6 +28,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -168,7 +169,8 @@
createProcessBuilder(command),
stdout,
stderr,
- /* inputRedirect= */ null);
+ /* inputRedirect= */ null,
+ false);
}
/** {@inheritDoc} */
@@ -252,7 +254,8 @@
createProcessBuilder(command),
/* stdoutStream= */ null,
/* stderrStream= */ null,
- inputRedirect);
+ inputRedirect,
+ true);
CommandStatus status = runTimed(timeout, osRunnable, true);
CommandResult result = osRunnable.getResult();
result.setStatus(status);
@@ -264,7 +267,7 @@
*/
@Override
public CommandResult runTimedCmdSilently(final long timeout, final String... command) {
- RunnableResult osRunnable = new RunnableResult(null, createProcessBuilder(command));
+ RunnableResult osRunnable = new RunnableResult(null, createProcessBuilder(command), false);
CommandStatus status = runTimed(timeout, osRunnable, false);
CommandResult result = osRunnable.getResult();
result.setStatus(status);
@@ -338,9 +341,11 @@
RunnableNotifier runThread = new RunnableNotifier(runnable, logErrors);
if (logErrors) {
if (timeout > 0l) {
- CLog.d("Running command with timeout: %dms", timeout);
+ CLog.d(
+ "Running command %s with timeout: %s",
+ runnable.getCommand(), TimeUtil.formatElapsedTime(timeout));
} else {
- CLog.d("Running command without timeout.");
+ CLog.d("Running command %s without timeout.", runnable.getCommand());
}
}
CommandStatus status = CommandStatus.TIMED_OUT;
@@ -567,9 +572,14 @@
private boolean mCreatedStderrStream = false;
private final Object mLock = new Object();
private boolean mCancelled = false;
+ private boolean mLogErrors = true;
RunnableResult(final String input, final ProcessBuilder processBuilder) {
- this(input, processBuilder, null, null, null);
+ this(input, processBuilder, null, null, null, true);
+ }
+
+ RunnableResult(final String input, final ProcessBuilder processBuilder, boolean logErrors) {
+ this(input, processBuilder, null, null, null, logErrors);
}
/**
@@ -584,9 +594,11 @@
final ProcessBuilder processBuilder,
OutputStream stdoutStream,
OutputStream stderrStream,
- File inputRedirect) {
+ File inputRedirect,
+ boolean logErrors) {
mProcessBuilder = processBuilder;
mInput = input;
+ mLogErrors = logErrors;
mInputRedirect = inputRedirect;
if (mInputRedirect != null) {
@@ -606,6 +618,11 @@
mStdErr = stderrStream != null ? stderrStream : new ByteArrayOutputStream();
}
+ @Override
+ public List<String> getCommand() {
+ return new ArrayList<>(mProcessBuilder.command());
+ }
+
public CommandResult getResult() {
return mCommandResult;
}
@@ -627,7 +644,6 @@
return false;
}
mExecutionThread = Thread.currentThread();
- CLog.d("Running %s", mProcessBuilder.command());
mProcess = startProcess();
if (mInput != null) {
BufferedOutputStream processStdin =
@@ -697,7 +713,7 @@
if (rc != null && rc == 0) {
return true;
- } else {
+ } else if (mLogErrors) {
CLog.d("%s command failed. return code %d", mProcessBuilder.command(), rc);
}
return false;
diff --git a/src/com/android/tradefed/util/UserUtil.java b/src/com/android/tradefed/util/UserUtil.java
index 608b84c..cdab730 100644
--- a/src/com/android/tradefed/util/UserUtil.java
+++ b/src/com/android/tradefed/util/UserUtil.java
@@ -77,8 +77,9 @@
case SECONDARY:
switchToSecondaryUser(device);
return;
+ default:
+ throw new RuntimeException("userType case not covered: " + userType);
}
- throw new RuntimeException("userType case not covered: " + userType);
}
/**
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 18a0b8d..fe55b89 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -182,6 +182,7 @@
import com.android.tradefed.targetprep.AoaTargetPreparerTest;
import com.android.tradefed.targetprep.AppSetupTest;
import com.android.tradefed.targetprep.BuildInfoAttributePreparerTest;
+import com.android.tradefed.targetprep.CreateUserPreparerTest;
import com.android.tradefed.targetprep.DefaultTestsZipInstallerTest;
import com.android.tradefed.targetprep.DeviceFlashPreparerTest;
import com.android.tradefed.targetprep.DeviceSetupTest;
@@ -296,6 +297,7 @@
import com.android.tradefed.util.FileUtilTest;
import com.android.tradefed.util.FixedByteArrayOutputStreamTest;
import com.android.tradefed.util.GCSFileDownloaderTest;
+import com.android.tradefed.util.GoogleApiClientUtilTest;
import com.android.tradefed.util.HprofAllocSiteParserTest;
import com.android.tradefed.util.JUnitXmlParserTest;
import com.android.tradefed.util.KeyguardControllerStateTest;
@@ -567,6 +569,7 @@
AoaTargetPreparerTest.class,
AppSetupTest.class,
BuildInfoAttributePreparerTest.class,
+ CreateUserPreparerTest.class,
DefaultTestsZipInstallerTest.class,
DeviceFlashPreparerTest.class,
DeviceSetupTest.class,
@@ -728,6 +731,7 @@
FileUtilTest.class,
FixedByteArrayOutputStreamTest.class,
GCSFileDownloaderTest.class,
+ GoogleApiClientUtilTest.class,
HprofAllocSiteParserTest.class,
JUnitXmlParserTest.class,
KeyguardControllerStateTest.class,
diff --git a/tests/src/com/android/tradefed/config/ArgsOptionParserTest.java b/tests/src/com/android/tradefed/config/ArgsOptionParserTest.java
index f05c099..4013cde 100644
--- a/tests/src/com/android/tradefed/config/ArgsOptionParserTest.java
+++ b/tests/src/com/android/tradefed/config/ArgsOptionParserTest.java
@@ -373,6 +373,21 @@
}
/**
+ * Test passing an single argument for an object that has one option specified, which is the
+ * empty string.
+ */
+ public void testParse_oneMapArg_emptyString() throws ConfigurationException {
+ MapStringOptionSource object = new MapStringOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final String expectedKey = "abc";
+ final String expectedValue = "";
+ parser.parse(new String[] {"--my_option", expectedKey + "="});
+ assertNotNull(object.mMyOption);
+ assertEquals(1, object.mMyOption.size());
+ assertEquals(expectedValue, object.mMyOption.get(expectedKey));
+ }
+
+ /**
* Test passing an single argument for an object that has one option specified.
*/
public void testParseMapArg_mismatchKeyType() throws ConfigurationException {
diff --git a/tests/src/com/android/tradefed/device/cloud/NestedRemoteDeviceTest.java b/tests/src/com/android/tradefed/device/cloud/NestedRemoteDeviceTest.java
index 88c94bd..1248d98 100644
--- a/tests/src/com/android/tradefed/device/cloud/NestedRemoteDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/cloud/NestedRemoteDeviceTest.java
@@ -20,9 +20,11 @@
import com.android.ddmlib.IDevice;
import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.IDeviceMonitor;
import com.android.tradefed.device.IDeviceStateMonitor;
+import com.android.tradefed.device.TestDeviceOptions;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
@@ -46,18 +48,26 @@
private ITestLogger mMockLogger;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
mMockIDevice = Mockito.mock(IDevice.class);
mMockStateMonitor = Mockito.mock(IDeviceStateMonitor.class);
mMockMonitor = Mockito.mock(IDeviceMonitor.class);
mMockRunUtil = Mockito.mock(IRunUtil.class);
mMockLogger = Mockito.mock(ITestLogger.class);
+ TestDeviceOptions options = new TestDeviceOptions();
+ OptionSetter setter = new OptionSetter(options);
+ setter.setOptionValue(TestDeviceOptions.INSTANCE_TYPE_OPTION, "CUTTLEFISH");
mDevice =
new NestedRemoteDevice(mMockIDevice, mMockStateMonitor, mMockMonitor) {
@Override
protected IRunUtil getRunUtil() {
return mMockRunUtil;
}
+
+ @Override
+ public TestDeviceOptions getOptions() {
+ return options;
+ }
};
}
diff --git a/tests/src/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java b/tests/src/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java
index 23d02e0..7427f71 100644
--- a/tests/src/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java
+++ b/tests/src/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java
@@ -16,19 +16,35 @@
package com.android.tradefed.invoker;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
import com.android.tradefed.build.BuildRetrievalError;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.StubBuildProvider;
import com.android.tradefed.config.Configuration;
+import com.android.tradefed.config.ConfigurationFactory;
+import com.android.tradefed.config.DeviceConfigurationHolder;
import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IDeviceConfiguration;
import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.DeviceSelectionOptions;
+import com.android.tradefed.device.DeviceSelectionOptions.DeviceRequestedType;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.proto.ProtoResultReporter;
+import com.android.tradefed.result.proto.SummaryProtoResultReporter;
+import com.android.tradefed.util.FileUtil;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.util.List;
/** Unit tests for {@link RemoteInvocationExecution}. */
@RunWith(JUnit4.class)
@@ -37,12 +53,14 @@
private RemoteInvocationExecution mRemoteInvocation;
private IInvocationContext mContext;
private IConfiguration mConfiguration;
+ private ITestInvocationListener mMockListener;
@Before
public void setUp() {
mRemoteInvocation = new RemoteInvocationExecution();
mContext = new InvocationContext();
mConfiguration = new Configuration("name", "description");
+ mMockListener = Mockito.mock(ITestInvocationListener.class);
}
@Test
@@ -65,4 +83,37 @@
// The build id is carried to the remote invocation build
assertEquals("5555", info.getBuildId());
}
+
+ @Test
+ public void testCreateRemoteConfig() throws Exception {
+ IDeviceConfiguration deviceConfig = new DeviceConfigurationHolder();
+ DeviceSelectionOptions selection = new DeviceSelectionOptions();
+ selection.setDeviceTypeRequested(DeviceRequestedType.REMOTE_DEVICE);
+ deviceConfig.addSpecificConfig(selection);
+ mConfiguration.setDeviceConfig(deviceConfig);
+ File res = null;
+ try {
+ res = mRemoteInvocation.createRemoteConfig(mConfiguration, mMockListener, "path/");
+ IConfiguration reparse =
+ ConfigurationFactory.getInstance()
+ .createConfigurationFromArgs(new String[] {res.getAbsolutePath()});
+ List<ITestInvocationListener> listeners = reparse.getTestInvocationListeners();
+ assertEquals(2, listeners.size());
+ assertTrue(listeners.get(0) instanceof ProtoResultReporter);
+ assertTrue(listeners.get(1) instanceof SummaryProtoResultReporter);
+ // Ensure the requested type is reset
+ assertNull(
+ ((DeviceSelectionOptions)
+ reparse.getDeviceConfig().get(0).getDeviceRequirements())
+ .getDeviceTypeRequested());
+ } finally {
+ FileUtil.deleteFile(res);
+ }
+
+ verify(mMockListener)
+ .testLog(
+ Mockito.eq(RemoteInvocationExecution.REMOTE_CONFIG),
+ Mockito.eq(LogDataType.XML),
+ Mockito.any());
+ }
}
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
index 811f9f1..c6ea271 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
@@ -1259,6 +1259,8 @@
.andReturn(new StubKeyStoreFactory())
.times(2);
setupInvoke();
+ mMockLogSaver.invocationStarted(mStubInvocationMetadata);
+ mMockLogSaver.invocationEnded(0L);
setupNShardInvocation(shardCount, command);
// Ensure that the host_log gets logged after sharding.
EasyMock.expect(mMockLogger.getLog()).andReturn(EMPTY_STREAM_SOURCE);
diff --git a/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java b/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
index c6ed15b..ef697dd 100644
--- a/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
+++ b/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
@@ -103,6 +103,8 @@
File res = mFormatter.writeResults(mResultHolder, mResultDir);
String content = FileUtil.readStringFromFile(res);
assertXmlContainsNode(content, "Result");
+ // Verify that RunHistory tag should not exist if run history is empty.
+ assertXmlNotContainNode(content, "Result/RunHistory");
// Verify that the summary has been populated
assertXmlContainsNode(content, "Result/Summary");
assertXmlContainsAttribute(content, "Result/Summary", "pass", "2");
@@ -499,6 +501,43 @@
}
}
+ /** Check that run history is properly reported. */
+ @Test
+ public void testRunHistoryReporting() throws Exception {
+ final String RUN_HISTORY =
+ "[{\"startTime\":10000000000000,\"endTime\":10000000100000},"
+ + "{\"startTime\":10000000200000,\"endTime\":10000000300000}]";
+ mResultHolder.context = mContext;
+ mResultHolder.context.addInvocationAttribute("run_history", RUN_HISTORY);
+
+ Collection<TestRunResult> runResults = new ArrayList<>();
+ runResults.add(createFakeResult("module1", 2, 1, 0, 0));
+ mResultHolder.runResults = runResults;
+
+ Map<String, IAbi> modulesAbi = new HashMap<>();
+ modulesAbi.put("module1", new Abi("armeabi-v7a", "32"));
+ mResultHolder.modulesAbi = modulesAbi;
+
+ mResultHolder.completeModules = 1;
+ mResultHolder.totalModules = 1;
+ mResultHolder.passedTests = 1;
+ mResultHolder.failedTests = 0;
+ mResultHolder.startTime = 0L;
+ mResultHolder.endTime = 10L;
+ File res = mFormatter.writeResults(mResultHolder, mResultDir);
+ String content = FileUtil.readStringFromFile(res);
+
+ assertXmlContainsAttribute(content, "Result/Build", "run_history", RUN_HISTORY);
+ assertXmlContainsNode(content, "Result/RunHistory");
+ assertXmlContainsAttribute(content, "Result/RunHistory/Run", "start", "10000000000000");
+ assertXmlContainsAttribute(content, "Result/RunHistory/Run", "end", "10000000100000");
+ assertXmlContainsAttribute(content, "Result/RunHistory/Run", "start", "10000000200000");
+ assertXmlContainsAttribute(content, "Result/RunHistory/Run", "end", "10000000300000");
+ // Test that we can read back the information.
+ SuiteResultHolder holder = mFormatter.parseResults(mResultDir, false);
+ assertEquals(RUN_HISTORY, holder.context.getAttributes().getUniqueMap().get("run_history"));
+ }
+
private TestRunResult createResultWithLog(String runName, int count, LogDataType type) {
TestRunResult fakeRes = new TestRunResult();
fakeRes.testRunStarted(runName, count);
@@ -596,6 +635,22 @@
return nodes;
}
+ /** Assert that the XML does not contain a node matching the given xPathExpression. */
+ private void assertXmlNotContainNode(String xml, String xPathExpression)
+ throws XPathExpressionException {
+ NodeList nodes = getXmlNodes(xml, xPathExpression);
+ assertNotNull(
+ String.format("XML '%s' returned null for xpath '%s'.", xml, xPathExpression),
+ nodes);
+ assertEquals(
+ String.format(
+ "XML '%s' should have returned at least 1 node for xpath '%s', "
+ + "but returned %s nodes instead.",
+ xml, xPathExpression, nodes.getLength()),
+ 0,
+ nodes.getLength());
+ }
+
/**
* Assert that the XML contains a node matching the given xPathExpression and that the node has
* a given value.
diff --git a/tests/src/com/android/tradefed/targetprep/AoaTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/AoaTargetPreparerTest.java
index 23039df..b35d7e3 100644
--- a/tests/src/com/android/tradefed/targetprep/AoaTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/AoaTargetPreparerTest.java
@@ -15,34 +15,94 @@
*/
package com.android.tradefed.targetprep;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
import com.android.helper.aoa.AoaDevice;
+import com.android.helper.aoa.UsbHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.TestDevice;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
import java.awt.*;
import java.time.Duration;
/** Unit tests for {@link AoaTargetPreparer} */
-@RunWith(JUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
public class AoaTargetPreparerTest {
- private AoaTargetPreparer mPreparer;
+ @Spy private AoaTargetPreparer mPreparer;
+ private OptionSetter mOptionSetter;
- private AoaDevice mDevice;
+ @Mock private TestDevice mTestDevice;
+ @Mock private IBuildInfo mBuildInfo;
+ @Mock private UsbHelper mUsb;
+ @Mock private AoaDevice mDevice;
@Before
- public void setUp() {
- mDevice = mock(AoaDevice.class);
- mPreparer = new AoaTargetPreparer();
+ public void setUp() throws ConfigurationException {
+ when(mUsb.getAoaDevice(any(), any())).thenReturn(mDevice);
+ doReturn(mUsb).when(mPreparer).getUsbHelper();
+ mOptionSetter = new OptionSetter(mPreparer);
+ }
+
+ @Test
+ public void testSetUp()
+ throws TargetSetupError, DeviceNotAvailableException, ConfigurationException {
+ mOptionSetter.setOptionValue("action", "wake");
+ mOptionSetter.setOptionValue("device-timeout", "1s");
+ mOptionSetter.setOptionValue("wait-for-device-online", "true");
+
+ mPreparer.setUp(mTestDevice, mBuildInfo);
+ // fetched device, executed actions, and verified status
+ verify(mUsb, times(1)).getAoaDevice(any(), eq(Duration.ofSeconds(1L)));
+ verify(mPreparer, times(1)).execute(eq(mDevice), eq("wake"));
+ verify(mTestDevice, times(1)).waitForDeviceOnline();
+ }
+
+ @Test
+ public void testSetUp_noActions() throws TargetSetupError, DeviceNotAvailableException {
+ mPreparer.setUp(mTestDevice, mBuildInfo);
+ // no-op if no actions provided
+ verifyZeroInteractions(mUsb);
+ verify(mPreparer, never()).execute(eq(mDevice), any());
+ verifyZeroInteractions(mTestDevice);
+ }
+
+ @Test(expected = DeviceNotAvailableException.class)
+ public void testSetUp_noDevice()
+ throws TargetSetupError, DeviceNotAvailableException, ConfigurationException {
+ mOptionSetter.setOptionValue("action", "wake");
+ when(mUsb.getAoaDevice(any(), any())).thenReturn(null); // device not found or incompatible
+ mPreparer.setUp(mTestDevice, mBuildInfo);
+ }
+
+ @Test
+ public void testSetUp_skipDeviceCheck()
+ throws TargetSetupError, DeviceNotAvailableException, ConfigurationException {
+ mOptionSetter.setOptionValue("action", "wake");
+ mOptionSetter.setOptionValue("wait-for-device-online", "false"); // device check disabled
+
+ mPreparer.setUp(mTestDevice, mBuildInfo);
+ // actions executed, but status check skipped
+ verify(mPreparer, times(1)).execute(eq(mDevice), eq("wake"));
+ verify(mTestDevice, never()).waitForDeviceOnline();
}
@Test
diff --git a/tests/src/com/android/tradefed/targetprep/CreateUserPreparerTest.java b/tests/src/com/android/tradefed/targetprep/CreateUserPreparerTest.java
new file mode 100644
index 0000000..b96d6bd
--- /dev/null
+++ b/tests/src/com/android/tradefed/targetprep/CreateUserPreparerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 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.
+ */
+package com.android.tradefed.targetprep;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.NativeDevice;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+/** Unit tests for {@link CreateUserPreparer}. */
+@RunWith(JUnit4.class)
+public class CreateUserPreparerTest {
+
+ private CreateUserPreparer mPreparer;
+ private ITestDevice mMockDevice;
+
+ @Before
+ public void setUp() {
+ mMockDevice = Mockito.mock(ITestDevice.class);
+ mPreparer = new CreateUserPreparer();
+ }
+
+ @Test
+ public void testSetUp_tearDown() throws Exception {
+ doReturn(10).when(mMockDevice).getCurrentUser();
+ doReturn(5).when(mMockDevice).createUser(Mockito.any());
+ doReturn(true).when(mMockDevice).switchUser(5);
+ mPreparer.setUp(mMockDevice, null);
+
+ doReturn(true).when(mMockDevice).removeUser(5);
+ doReturn(true).when(mMockDevice).switchUser(10);
+ mPreparer.tearDown(mMockDevice, null, null);
+ }
+
+ @Test
+ public void testSetUp_tearDown_noCurrent() throws Exception {
+ doReturn(NativeDevice.INVALID_USER_ID).when(mMockDevice).getCurrentUser();
+ try {
+ mPreparer.setUp(mMockDevice, null);
+ fail("Should have thrown an exception.");
+ } catch (TargetSetupError expected) {
+ // Expected
+ }
+
+ mPreparer.tearDown(mMockDevice, null, null);
+ verify(mMockDevice, never()).removeUser(Mockito.anyInt());
+ verify(mMockDevice, never()).switchUser(Mockito.anyInt());
+ }
+
+ @Test
+ public void testSetUp_failed() throws Exception {
+ doThrow(new IllegalStateException("failed to create"))
+ .when(mMockDevice)
+ .createUser(Mockito.any());
+
+ try {
+ mPreparer.setUp(mMockDevice, null);
+ fail("Should have thrown an exception.");
+ } catch (TargetSetupError expected) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void testTearDown_only() throws Exception {
+ mPreparer.tearDown(mMockDevice, null, null);
+
+ verify(mMockDevice, never()).removeUser(Mockito.anyInt());
+ }
+}
diff --git a/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
index 4932ece..869577a 100644
--- a/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
@@ -23,6 +23,7 @@
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.command.remote.DeviceDescriptor;
+import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.ITestDevice.ApexInfo;
import com.android.tradefed.util.CommandResult;
@@ -57,10 +58,13 @@
private File mFakeApkApks;
private File mFakeApexApks;
private File mBundletoolJar;
+ private OptionSetter mSetter;
private static final String APEX_PACKAGE_NAME = "com.android.FAKE_APEX_PACKAGE_NAME";
private static final String APK_PACKAGE_NAME = "com.android.FAKE_APK_PACKAGE_NAME";
private static final String SPLIT_APEX_PACKAGE_NAME =
"com.android.SPLIT_FAKE_APEX_PACKAGE_NAME";
+ private static final String SPLIT_APK_PACKAGE_NAME =
+ "com.android.SPLIT_FAKE_APK_PACKAGE_NAME";
private static final String APEX_PACKAGE_KEYWORD = "FAKE_APEX_PACKAGE_NAME";
private static final long APEX_VERSION = 1;
private static final String APEX_NAME = "fakeApex.apex";
@@ -129,9 +133,14 @@
if (testAppFile.getName().endsWith(".apex")) {
return APEX_PACKAGE_NAME;
}
- if (testAppFile.getName().endsWith(".apk")) {
+ if (testAppFile.getName().endsWith(".apk") &&
+ !testAppFile.getName().contains("Split")) {
return APK_PACKAGE_NAME;
}
+ if (testAppFile.getName().endsWith(".apk") &&
+ testAppFile.getName().contains("Split")) {
+ return SPLIT_APK_PACKAGE_NAME;
+ }
return null;
}
@@ -147,6 +156,9 @@
return apexInfo;
}
};
+
+ mSetter = new OptionSetter(mInstallApexModuleTargetPreparer);
+ mSetter.setOptionValue("cleanup-apks", "true");
}
@After
@@ -292,6 +304,121 @@
}
@Test
+ public void testSetupAndTearDown_SingleApk() throws Exception {
+ mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME);
+ mMockDevice.deleteFile(APEX_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ CommandResult res = new CommandResult();
+ res.setStdout("test.apex");
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+ EasyMock.expect(mMockDevice.installPackage((File) EasyMock.anyObject(), EasyMock.eq(true)))
+ .andReturn(null)
+ .once();
+ EasyMock.expect(mMockDevice.uninstallPackage(APK_PACKAGE_NAME)).andReturn(null).once();
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mMockDevice, mMockBuildInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mMockDevice, mMockBuildInfo, null);
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ }
+
+ @Test
+ public void testSetupAndTearDown_ApkAndApks() throws Exception {
+ mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME);
+ mInstallApexModuleTargetPreparer.addTestFileName(SPLIT_APK__APKS_NAME);;
+ mFakeApkApks = File.createTempFile("fakeApk", ".apks");
+
+ File fakeSplitApkApks = File.createTempFile("ApkSplits", "");
+ fakeSplitApkApks.delete();
+ fakeSplitApkApks.mkdir();
+ File splitApk1 = File.createTempFile("fakeSplitApk1", ".apk", fakeSplitApkApks);
+ mBundletoolJar = File.createTempFile("bundletool", ".jar");
+ File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks);
+ try {
+ mMockDevice.deleteFile(APEX_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ CommandResult res = new CommandResult();
+ res.setStdout("test.apex");
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR))
+ .andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR))
+ .andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR))
+ .andReturn(res);
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+ when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class)))
+ .thenReturn("serial.json");
+
+ assertTrue(fakeSplitApkApks != null);
+ assertTrue(mFakeApkApks != null);
+ assertEquals(2, fakeSplitApkApks.listFiles().length);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApkApks);
+
+ mMockDevice.waitForDeviceAvailable();
+
+ List<String> trainInstallCmd = new ArrayList<>();
+ trainInstallCmd.add("install-multi-package");
+ trainInstallCmd.add(mFakeApk.getAbsolutePath());
+ String cmd = "";
+ for (File f : fakeSplitApkApks.listFiles()) {
+ if (!cmd.isEmpty()) {
+ cmd += ":" + f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ } else {
+ cmd += f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ }
+ }
+ trainInstallCmd.add(cmd);
+ EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
+ .andReturn("Success")
+ .once();
+
+ EasyMock.expect(mMockDevice.uninstallPackage(APK_PACKAGE_NAME)).andReturn(null).once();
+ EasyMock.expect(mMockDevice.uninstallPackage(SPLIT_APK_PACKAGE_NAME))
+ .andReturn(null)
+ .once();
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mMockDevice, mMockBuildInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mMockDevice, mMockBuildInfo, null);
+ Mockito.verify(mMockBundletoolUtil, times(1))
+ .generateDeviceSpecFile(Mockito.any(ITestDevice.class));
+ Mockito.verify(mMockBundletoolUtil, times(1))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ assertTrue(!mInstallApexModuleTargetPreparer.getApkInstalled().isEmpty());
+ } finally {
+ FileUtil.deleteFile(mFakeApexApks);
+ FileUtil.deleteFile(mFakeApkApks);
+ FileUtil.recursiveDelete(fakeSplitApkApks);
+ FileUtil.deleteFile(fakeSplitApkApks);
+ FileUtil.deleteFile(mBundletoolJar);
+ }
+ }
+
+ @Test
public void testSetupAndTearDown() throws Exception {
mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
mMockDevice.deleteFile(APEX_DATA_DIR + "*");
@@ -446,7 +573,9 @@
Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
activatedApex.add(new ApexInfo(SPLIT_APEX_PACKAGE_NAME, 1));
EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(activatedApex);
- EasyMock.expect(mMockDevice.uninstallPackage(APK_PACKAGE_NAME)).andReturn(null).once();
+ EasyMock.expect(mMockDevice.uninstallPackage(SPLIT_APK_PACKAGE_NAME))
+ .andReturn(null)
+ .once();
mMockDevice.reboot();
EasyMock.expectLastCall();
diff --git a/tests/src/com/android/tradefed/testtype/AndroidJUnitTestTest.java b/tests/src/com/android/tradefed/testtype/AndroidJUnitTestTest.java
index 11ea3cb..b041293 100644
--- a/tests/src/com/android/tradefed/testtype/AndroidJUnitTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/AndroidJUnitTestTest.java
@@ -195,6 +195,47 @@
EasyMock.verify(mMockRemoteRunner, mMockTestDevice);
}
+ /** Test list of tests to run is filtered by include filters using regex. */
+ @Test
+ public void testRun_includeFilterSingleTestsRegex() throws Exception {
+ // expect this call
+ mMockRemoteRunner.addInstrumentationArg("tests_regex", ".*testName$");
+ setRunTestExpectations();
+ EasyMock.replay(mMockRemoteRunner, mMockTestDevice);
+ mAndroidJUnitTest.addIncludeFilter(".*testName$");
+ mAndroidJUnitTest.run(mMockListener);
+ EasyMock.verify(mMockRemoteRunner, mMockTestDevice);
+ }
+
+ /** Test list of tests to run is filtered by include filters using multiple regex. */
+ @Test
+ public void testRun_includeFilterMultipleTestsRegex() throws Exception {
+ // expect this call
+ mMockRemoteRunner.addInstrumentationArg("tests_regex", "\"(.*test2|.*testName$)\"");
+ setRunTestExpectations();
+ EasyMock.replay(mMockRemoteRunner, mMockTestDevice);
+ mAndroidJUnitTest.addIncludeFilter(".*test2");
+ mAndroidJUnitTest.addIncludeFilter(".*testName$");
+ mAndroidJUnitTest.run(mMockListener);
+ EasyMock.verify(mMockRemoteRunner, mMockTestDevice);
+ }
+
+ /** Test list of tests to run is filtered by include filters using invalid regex. */
+ @Test
+ public void testRun_includeFilterInvalidTestsRegex() throws Exception {
+ setRunTestExpectations();
+ EasyMock.replay(mMockRemoteRunner, mMockTestDevice);
+ // regex with unbalanced parenthesis.
+ mAndroidJUnitTest.addIncludeFilter("(testName");
+ try {
+ mAndroidJUnitTest.run(mMockListener);
+ } catch (RuntimeException expected) {
+ //expected.
+ return;
+ }
+ fail("RuntimeException not raised for filter with invalid regular expression.");
+ }
+
/** Test list of tests to run is filtered by include and exclude filters. */
@Test
public void testRun_includeAndExcludeFilters() throws Exception {
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index 6d49557..39e072a 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -279,6 +279,8 @@
mTestSuite.setConfiguration(mStubMainConfiguration);
mContext = new InvocationContext();
mTestSuite.setInvocationContext(mContext);
+ mContext.addAllocatedDevice(ConfigurationDef.DEFAULT_DEVICE_NAME, mMockDevice);
+ mContext.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, mMockBuildInfo);
mListCollectors = new ArrayList<>();
mListCollectors.add(new TestMetricCollector("metric1", "value1"));
mListCollectors.add(new TestMetricCollector("metric2", "value2"));
@@ -651,6 +653,11 @@
"unresponsive"
+ TestRunResult.ERROR_DIVIDER
+ "Module test only ran 0 out of 1 expected tests.");
+ EasyMock.expect(
+ mMockDevice.logBugreport(
+ EasyMock.eq("module-test-failure-SERIAL-bugreport"),
+ EasyMock.anyObject()))
+ .andReturn(true);
mMockListener.testRunEnded(
EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
EasyMock.expectLastCall().times(1);
@@ -775,6 +782,11 @@
"runtime"
+ TestRunResult.ERROR_DIVIDER
+ "Module test only ran 0 out of 1 expected tests.");
+ EasyMock.expect(
+ mMockDevice.logBugreport(
+ EasyMock.eq("module-test-failure-SERIAL-bugreport"),
+ EasyMock.anyObject()))
+ .andReturn(true);
mMockListener.testRunEnded(
EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
EasyMock.expectLastCall().times(1);
diff --git a/tests/src/com/android/tradefed/testtype/suite/TfSuiteRunnerTest.java b/tests/src/com/android/tradefed/testtype/suite/TfSuiteRunnerTest.java
index 17018c8..749e49e 100644
--- a/tests/src/com/android/tradefed/testtype/suite/TfSuiteRunnerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/TfSuiteRunnerTest.java
@@ -24,11 +24,13 @@
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.config.Configuration;
+import com.android.tradefed.config.ConfigurationDef;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ILogSaver;
@@ -210,7 +212,9 @@
mRunner.setDevice(mock(ITestDevice.class));
mRunner.setBuild(mock(IBuildInfo.class));
mRunner.setSystemStatusChecker(new ArrayList<>());
- mRunner.setInvocationContext(new InvocationContext());
+ IInvocationContext context = new InvocationContext();
+ context.addAllocatedDevice(ConfigurationDef.DEFAULT_DEVICE_NAME, mock(ITestDevice.class));
+ mRunner.setInvocationContext(context);
// runs the expanded suite
listener.testModuleStarted(EasyMock.anyObject());
listener.testRunStarted(
diff --git a/tests/src/com/android/tradefed/util/GoogleApiClientUtilTest.java b/tests/src/com/android/tradefed/util/GoogleApiClientUtilTest.java
new file mode 100644
index 0000000..5324571
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/GoogleApiClientUtilTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.tradefed.util;
+
+import com.android.tradefed.config.GlobalConfiguration;
+import com.android.tradefed.config.OptionSetter;
+
+import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/** Unit test for {@link GoogleApiClientUtil}. */
+@RunWith(JUnit4.class)
+public class GoogleApiClientUtilTest {
+
+ private static final Collection<String> SCOPES = Collections.singleton("ascope");
+ private static final String HOST_OPTION_JSON_KEY = "host-option-json-key";
+ private File mRoot;
+ private StubGoogleApiClientUtil mUtil;
+ private File mKey;
+ private File mOldValue;
+
+ static class StubGoogleApiClientUtil extends GoogleApiClientUtil {
+
+ List<File> mKeyFiles = new ArrayList<>();
+ boolean mDefaultCredentialUsed = false;
+
+ @Override
+ GoogleCredential doCreateCredentialFromJsonKeyFile(File file, Collection<String> scopes)
+ throws IOException, GeneralSecurityException {
+ mKeyFiles.add(file);
+ return Mockito.mock(GoogleCredential.class);
+ }
+
+ @Override
+ GoogleCredential doCreateDefaultCredential(Collection<String> scopes) throws IOException {
+ mDefaultCredentialUsed = true;
+ return Mockito.mock(GoogleCredential.class);
+ }
+ }
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ try {
+ GlobalConfiguration.getInstance();
+ } catch (IllegalStateException e) {
+ GlobalConfiguration.createGlobalConfiguration(new String[] {});
+ }
+ }
+
+ @Before
+ public void setUp() throws IOException {
+ mUtil = new StubGoogleApiClientUtil();
+ mRoot = FileUtil.createTempDir(GoogleApiClientUtilTest.class.getName());
+ mKey = new File(mRoot, "key.json");
+ FileUtil.writeToFile("key", mKey);
+ mOldValue =
+ GlobalConfiguration.getInstance()
+ .getHostOptions()
+ .getServiceAccountJsonKeyFiles()
+ .get(HOST_OPTION_JSON_KEY);
+ }
+
+ @After
+ public void tearDown() {
+ FileUtil.recursiveDelete(mRoot);
+ if (mOldValue != null) {
+ GlobalConfiguration.getInstance()
+ .getHostOptions()
+ .getServiceAccountJsonKeyFiles()
+ .put(HOST_OPTION_JSON_KEY, mOldValue);
+ }
+ }
+
+ @Test
+ public void testCreateCredential() throws Exception {
+ GoogleCredential credentail = mUtil.doCreateCredential(SCOPES, mKey, null);
+ Assert.assertNotNull(credentail);
+ Assert.assertEquals(1, mUtil.mKeyFiles.size());
+ Assert.assertEquals(mKey, mUtil.mKeyFiles.get(0));
+ Assert.assertFalse(mUtil.mDefaultCredentialUsed);
+ }
+
+ @Test
+ public void testCreateCredential_useHostOptions() throws Exception {
+ OptionSetter optionSetter =
+ new OptionSetter(GlobalConfiguration.getInstance().getHostOptions());
+ optionSetter.setOptionValue(
+ "host_options:service-account-json-key-file",
+ HOST_OPTION_JSON_KEY,
+ mKey.getAbsolutePath());
+ GoogleCredential credentail = mUtil.doCreateCredential(SCOPES, null, HOST_OPTION_JSON_KEY);
+ Assert.assertNotNull(credentail);
+ Assert.assertEquals(1, mUtil.mKeyFiles.size());
+ Assert.assertEquals(mKey, mUtil.mKeyFiles.get(0));
+ Assert.assertFalse(mUtil.mDefaultCredentialUsed);
+ }
+
+ @Test
+ public void testCreateCredential_useFallbackKeyFile() throws Exception {
+ GoogleCredential credentail = mUtil.doCreateCredential(SCOPES, null, "not-exist-key", mKey);
+ Assert.assertNotNull(credentail);
+ Assert.assertEquals(1, mUtil.mKeyFiles.size());
+ Assert.assertEquals(mKey, mUtil.mKeyFiles.get(0));
+ Assert.assertFalse(mUtil.mDefaultCredentialUsed);
+ }
+
+ @Test
+ public void testCreateCredential_useDefault() throws Exception {
+ GoogleCredential credentail = mUtil.doCreateCredential(SCOPES, null, "not-exist-key");
+ Assert.assertNotNull(credentail);
+ Assert.assertEquals(0, mUtil.mKeyFiles.size());
+ Assert.assertTrue(mUtil.mDefaultCredentialUsed);
+ }
+}
diff --git a/tests/src/com/android/tradefed/util/RunUtilTest.java b/tests/src/com/android/tradefed/util/RunUtilTest.java
index ea69276..e11ebff 100644
--- a/tests/src/com/android/tradefed/util/RunUtilTest.java
+++ b/tests/src/com/android/tradefed/util/RunUtilTest.java
@@ -44,6 +44,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
/** Unit tests for {@link RunUtil} */
@@ -109,6 +110,7 @@
public void testRunTimed() throws Exception {
IRunUtil.IRunnableResult mockRunnable = EasyMock.createStrictMock(
IRunUtil.IRunnableResult.class);
+ EasyMock.expect(mockRunnable.getCommand()).andReturn(new ArrayList<>());
EasyMock.expect(mockRunnable.run()).andReturn(Boolean.TRUE);
mockRunnable.cancel(); // always ensure execution is cancelled
EasyMock.replay(mockRunnable);
@@ -121,6 +123,7 @@
public void testRunTimed_failed() throws Exception {
IRunUtil.IRunnableResult mockRunnable = EasyMock.createStrictMock(
IRunUtil.IRunnableResult.class);
+ EasyMock.expect(mockRunnable.getCommand()).andReturn(new ArrayList<>());
EasyMock.expect(mockRunnable.run()).andReturn(Boolean.FALSE);
mockRunnable.cancel(); // always ensure execution is cancelled
EasyMock.replay(mockRunnable);
@@ -133,6 +136,7 @@
public void testRunTimed_exception() throws Exception {
IRunUtil.IRunnableResult mockRunnable = EasyMock.createStrictMock(
IRunUtil.IRunnableResult.class);
+ EasyMock.expect(mockRunnable.getCommand()).andReturn(new ArrayList<>());
EasyMock.expect(mockRunnable.run()).andThrow(new RuntimeException());
mockRunnable.cancel(); // cancel due to exception
mockRunnable.cancel(); // always ensure execution is cancelled