1#!/usr/bin/env python3
2#
3#   Copyright 2019 - 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 time
18
19from acts import base_test
20from acts import asserts
21from acts.controllers.rohdeschwarz_lib import contest
22from acts_contrib.test_utils.tel import tel_test_utils
23from acts.metrics.loggers import blackbox
24
25import json
26
27
28class AGNSSPerformanceTest(base_test.BaseTestClass):
29
30    # User parameters defined in the ACTS config file
31
32    TESTPLAN_KEY = '{}_testplan'
33    CONTEST_IP_KEY = 'contest_ip'
34    REMOTE_SERVER_PORT_KEY = 'remote_server_port'
35    AUTOMATION_PORT_KEY = 'automation_port'
36    CUSTOM_FILES_KEY = 'custom_files'
37    AUTOMATION_LISTEN_IP = 'automation_listen_ip'
38    FTP_USER_KEY = 'ftp_user'
39    FTP_PASSWORD_KEY = 'ftp_password'
40
41    def __init__(self, controllers):
42        """ Initializes class attributes. """
43
44        super().__init__(controllers)
45
46        self.dut = None
47        self.contest = None
48        self.testplan = None
49        self.thresholds_file = None
50
51        self.ttf_metric = blackbox.BlackboxMetricLogger.for_test_case(
52            metric_name='ttf')
53        self.pos_error_metric = blackbox.BlackboxMetricLogger.for_test_case(
54            metric_name='pos_error')
55        self.sensitivity_metric = blackbox.BlackboxMetricLogger.for_test_case(
56            metric_name='sensitivity')
57
58    def setup_class(self):
59        """ Executed before any test case is started. Initializes the Contest
60        controller and prepares the DUT for testing. """
61
62        req_params = [
63            self.CONTEST_IP_KEY, self.REMOTE_SERVER_PORT_KEY,
64            self.AUTOMATION_PORT_KEY, self.AUTOMATION_LISTEN_IP,
65            self.FTP_USER_KEY, self.FTP_PASSWORD_KEY
66        ]
67
68        for param in req_params:
69            if param not in self.user_params:
70                self.log.error('Required parameter {} is missing in config '
71                               'file.'.format(param))
72                return False
73
74        contest_ip = self.user_params[self.CONTEST_IP_KEY]
75        remote_port = self.user_params[self.REMOTE_SERVER_PORT_KEY]
76        automation_port = self.user_params[self.AUTOMATION_PORT_KEY]
77        listen_ip = self.user_params[self.AUTOMATION_LISTEN_IP]
78        ftp_user = self.user_params[self.FTP_USER_KEY]
79        ftp_password = self.user_params[self.FTP_PASSWORD_KEY]
80        custom_files = self.user_params.get(self.CUSTOM_FILES_KEY, [])
81
82        self.dut = self.android_devices[0]
83
84        self.contest = contest.Contest(logger=self.log,
85                                       remote_ip=contest_ip,
86                                       remote_port=remote_port,
87                                       automation_listen_ip=listen_ip,
88                                       automation_port=automation_port,
89                                       dut_on_func=self.set_apm_off,
90                                       dut_off_func=self.set_apm_on,
91                                       ftp_usr=ftp_user,
92                                       ftp_pwd=ftp_password)
93
94        # Look for the threshold files
95        for file in custom_files:
96            if 'pass_fail_threshold_' + self.dut.model in file:
97                self.thresholds_file = file
98                self.log.debug('Threshold file loaded: ' + file)
99                break
100        else:
101            self.log.warning('No threshold files found in custom files.')
102
103    def teardown_class(self):
104        """ Executed after completing all selected test cases."""
105        if self.contest:
106            self.contest.destroy()
107
108    def setup_test(self):
109        """ Executed before every test case.
110
111        Returns:
112            False if the setup failed.
113        """
114
115        testplan_formatted_key = self.TESTPLAN_KEY.format(self.test_name)
116
117        if testplan_formatted_key not in self.user_params:
118            self.log.error('Test plan not indicated in the config file. Use '
119                           'the {} key to set the testplan filename.'.format(
120                               testplan_formatted_key))
121            return False
122
123        self.testplan = self.user_params[testplan_formatted_key]
124
125    def agnss_performance_test(self):
126        """ Executes the aGNSS performance test and verifies that the results
127        are within the expected values if a thresholds file is available.
128
129        The thresholds file is in json format and contains the metrics keys
130        defined in the Contest object with 'min' and 'max' values. """
131
132        results = self.contest.execute_testplan(self.testplan)
133
134        asserts.assert_true(
135            results, 'No results were obtained from the test execution.')
136
137        if not self.thresholds_file:
138            self.log.info('Skipping pass / fail check because no thresholds '
139                          'file was provided.')
140            return
141
142        passed = True
143
144        with open(self.thresholds_file, 'r') as file:
145
146            thresholds = json.load(file)
147
148            for key, val in results.items():
149
150                asserts.assert_true(
151                    key in thresholds, 'Key {} is missing in '
152                    'the thresholds file.'.format(key))
153
154                # If the result is provided as a dictionary, obtain the value
155                # from the 'avg' key.
156                if isinstance(val, dict):
157                    metric = val['avg']
158                else:
159                    metric = val
160
161                if thresholds[key]['min'] < metric < thresholds[key]['max']:
162                    self.log.info('Metric {} = {} is within the expected '
163                                  'values.'.format(key, metric))
164                else:
165                    self.log.error('Metric {} = {} is not within ({}, '
166                                   '{}).'.format(key, metric,
167                                                 thresholds[key]['min'],
168                                                 thresholds[key]['max']))
169                    passed = False
170
171                # Save metric to Blackbox logger
172                if key == contest.Contest.TTFF_KEY:
173                    self.ttf_metric.metric_value = metric
174                elif key == contest.Contest.POS_ERROR_KEY:
175                    self.pos_error_metric.metric_value = metric
176                elif key == contest.Contest.SENSITIVITY_KEY:
177                    self.sensitivity_metric.metric_value = metric
178
179        asserts.assert_true(
180            passed, 'At least one of the metrics was not '
181            'within the expected values.')
182
183    def set_apm_on(self):
184        """ Wrapper method to turn airplane mode on.
185
186        This is passed to the Contest object so it can be executed when the
187        automation system requires the DUT to be set to 'off' state.
188        """
189
190        tel_test_utils.toggle_airplane_mode(self.log, self.dut, True)
191
192    def set_apm_off(self):
193        """ Wrapper method to turn airplane mode off.
194
195        This is passed to the Contest object so it can be executed when the
196        automation system requires the DUT to be set to 'on' state.
197        """
198        # Wait for the Contest system to initialize the base stations before
199        # actually setting APM off.
200        time.sleep(5)
201
202        tel_test_utils.toggle_airplane_mode(self.log, self.dut, False)
203
204    def test_agnss_performance(self):
205        self.agnss_performance_test()
206