• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright 2020 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import datetime
18import os
19
20from acts.libs.proto.proto_utils import parse_proto_to_ascii
21from acts.libs.testtracker.protos.gen.testtracker_result_pb2 import Result
22from acts.records import TestResultEnums
23from acts.records import TestResultRecord
24
25from acts import signals
26
27KEY_DETAILS = 'details'
28KEY_EFFORT_NAME = 'effort_name'
29KEY_PROJECT_ID = 'project_id'
30KEY_TESTTRACKER_UUID = 'test_tracker_uuid'
31KEY_USER = 'user'
32KEY_UUID = 'uuid'
33
34TESTTRACKER_PATH = 'test_tracker_results/test_effort_name=%s/test_case_uuid=%s'
35RESULT_FILE_NAME = 'result.pb.txt'
36
37_TEST_RESULT_TO_STATUS_MAP = {
38    TestResultEnums.TEST_RESULT_PASS: Result.PASSED,
39    TestResultEnums.TEST_RESULT_FAIL: Result.FAILED,
40    TestResultEnums.TEST_RESULT_SKIP: Result.SKIPPED,
41    TestResultEnums.TEST_RESULT_ERROR: Result.ERROR
42}
43
44
45class TestTrackerError(Exception):
46    """Exception class for errors raised within TestTrackerResultsWriter"""
47
48
49class TestTrackerResultsWriter(object):
50    """Takes a test record, converts it to a TestTracker result proto, and
51    writes it to the log directory. In automation, these protos will
52    automatically be read from Sponge and uploaded to TestTracker.
53    """
54
55    def __init__(self, log_path, properties):
56        """Creates a TestTrackerResultsWriter
57
58        Args:
59            log_path: Base log path to store TestTracker results. Must be within
60                the ACTS directory.
61            properties: dict representing key-value pairs to be uploaded as
62                TestTracker properties.
63        """
64        self._log_path = log_path
65        self._properties = properties
66        self._validate_properties()
67
68    def write_results(self, record):
69        """Create a Result proto from test record, then write it to a file.
70
71        Args:
72            record: An acts.records.TestResultRecord object
73        """
74        proto = self._create_result_proto(record)
75        proto_dir = self._create_results_dir(proto.uuid)
76        with open(os.path.join(proto_dir, RESULT_FILE_NAME), mode='w') as f:
77            f.write(parse_proto_to_ascii(proto))
78
79    def write_results_from_test_signal(self, signal, begin_time=None):
80        """Create a Result proto from a test signal, then write it to a file.
81
82        Args:
83            signal: An acts.signals.TestSignal object
84            begin_time: Optional. Sets the begin_time of the test record.
85        """
86        record = TestResultRecord('')
87        record.begin_time = begin_time
88        if not record.begin_time:
89            record.test_begin()
90        if isinstance(signal, signals.TestPass):
91            record.test_pass(signal)
92        elif isinstance(signal, signals.TestFail):
93            record.test_fail(signal)
94        elif isinstance(signal, signals.TestSkip):
95            record.test_skip(signal)
96        else:
97            record.test_error(signal)
98        self.write_results(record)
99
100    def _validate_properties(self):
101        """Checks that the required properties are set
102
103        Raises:
104            TestTrackerError if one or more required properties is absent
105        """
106        required_props = [KEY_USER, KEY_PROJECT_ID, KEY_EFFORT_NAME]
107        missing_props = [
108            p for p in required_props if p not in self._properties
109        ]
110        if missing_props:
111            raise TestTrackerError(
112                'Missing the following required properties for TestTracker: %s'
113                % missing_props)
114
115    @staticmethod
116    def _add_property(result_proto, name, value):
117        """Adds a Property to a given Result proto
118
119        Args:
120            result_proto: Result proto to modify
121            name: Property name
122            value: Property value
123        """
124        new_prop = result_proto.property.add()
125        new_prop.name = name
126        if isinstance(value, bool):
127            new_prop.bool_value = value
128        elif isinstance(value, int):
129            new_prop.int_value = value
130        elif isinstance(value, float):
131            new_prop.double_value = value
132        else:
133            new_prop.string_value = str(value)
134
135    def _create_result_proto(self, record):
136        """Create a Result proto object from test record. Fills in uuid, status,
137        and properties with info gathered from the test record.
138
139        Args:
140            record: An acts.records.TestResultRecord object
141
142        Returns: Result proto, or None if record is invalid
143        """
144        uuid = record.extras[KEY_TESTTRACKER_UUID]
145        result = Result()
146        result.uuid = uuid
147        result.status = _TEST_RESULT_TO_STATUS_MAP[record.result]
148        result.timestamp = (datetime.datetime.fromtimestamp(
149            record.begin_time / 1000,
150            datetime.timezone.utc).isoformat(timespec='milliseconds').replace(
151                '+00:00', 'Z'))
152
153        self._add_property(result, KEY_UUID, uuid)
154        if record.details:
155            self._add_property(result, KEY_DETAILS, record.details)
156
157        for key, value in self._properties.items():
158            self._add_property(result, key, value)
159
160        return result
161
162    def _create_results_dir(self, uuid):
163        """Creates the TestTracker directory given the test uuid
164
165        Args:
166            uuid: The TestTracker uuid of the test
167
168        Returns: Path to the created directory.
169        """
170        dir_path = os.path.join(
171            self._log_path,
172            TESTTRACKER_PATH % (self._properties[KEY_EFFORT_NAME], uuid))
173        os.makedirs(dir_path, exist_ok=True)
174        return dir_path
175