1"""Make sure the user build configuration is working as expected.
2
3Although we can assume the features should be the same between user and user_debug build,
4the configuration difference between this two build are not tested.
5
6In this test suite, we modify the gps configuration to be the same as user build
7and check if the setting is working.
8For more details, please refer to : go/p22_user_build_verification
9"""
10import os
11import re
12import shutil
13import tempfile
14import time
15
16from acts import asserts
17from acts import signals
18from acts.base_test import BaseTestClass
19from acts_contrib.test_utils.gnss.testtracker_util import log_testtracker_uuid
20from acts.controllers.adb_lib.error import AdbCommandError
21from acts.libs.proc.job import TimeoutError
22from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
23from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
24
25
26class GpsConfig:
27    def __init__(self, ad, name) -> None:
28        self.ad = ad
29        self.name = name
30        self.folder = "/vendor/etc/gnss"
31        self.full_path = os.path.join(self.folder, self.name)
32        self.logenabled = "LogEnabled"
33        self._log_enable = "true"
34        self._log_disable = "false"
35
36    def _change_file_content(self, pattern, target):
37        """Modify file via sed command
38
39        command will be sed -i 's/<pattern>/<target>/g' <file_path>
40        Args:
41            pattern: a string will be used as search pattern
42            target: string that will overwrite the matched result
43        """
44        self.ad.adb.remount()
45        command = f"sed -i s/{pattern}/{target}/g {self.full_path}"
46        self.ad.adb.shell(command)
47
48    def _get_setting_value(self, key):
49        """Get setting value from config file
50
51        command is grep <key> self.full_path
52        Args:
53            key: a string will be used as search pattern
54        Returns:
55            string: grep result ("" for no grep result)
56        """
57        command = f"grep {key} {self.full_path}"
58        result = self.ad.adb.shell(command)
59        return result
60
61    def _adjust_log_enable_setting(self, key, enable):
62        """Enable / Disable in self.full_path by setting key = true / false
63        Args:
64            key: The target will be changed
65            enable: True to enable / False to disable
66        """
67        src = self._log_disable if enable else self._log_enable
68        target = self._log_enable if enable else self._log_disable
69        pattern = f"{key}={src}"
70        target = f"{key}={target}"
71        self._change_file_content(pattern, target)
72        result = self._get_setting_value(key)
73        self.ad.log.debug("%s setting: %s", self.name, result)
74
75    def _check_file_exist(self, file_pattern):
76        """use command ls to check if file/dir exists
77        command ls <file_pattern>
78        Args:
79            file_pattern: A string represents the file or dir
80        Returns:
81            bool: True -> file exists / False -> file doesn't exist
82        """
83        command = f"ls {file_pattern}"
84        try:
85            self.ad.adb.shell(command)
86            result = True
87        except AdbCommandError as e:
88            result = False
89        return result
90
91    def enable_diagnostic_log(self):
92        """Set LogEnabled=true in config file
93        In gps.xml it will be LogEnabled=\"true\"
94        """
95        self.ad.log.info("Enable diagnostic log in %s", self.name)
96        self._adjust_log_enable_setting(key=self.logenabled, enable=True)
97
98    def disable_diagnostic_log(self):
99        """Set LogEnabled=false in config file
100        In gps.xml it will be LogEnabled=\"false\"
101        """
102        self.ad.log.info("Disable diagnostic log in %s", self.name)
103        self._adjust_log_enable_setting(key=self.logenabled, enable=False)
104
105
106class ScdConf(GpsConfig):
107    def __init__(self, ad) -> None:
108        super().__init__(ad, "scd.conf")
109
110
111class GpsXml(GpsConfig):
112    def __init__(self, ad) -> None:
113        super().__init__(ad, "gps.xml")
114        self.supllogenable = "SuplLogEnable"
115        self.supl_log = "/data/vendor/gps/suplflow.txt"
116        self._log_enable = "\\\"true\\\""
117        self._log_disable = "\\\"false\\\""
118
119    def enable_supl_log(self):
120        """Set SuplLogEnable=\"true\" in gps.xml"""
121        self.ad.log.info("Enable SUPL logs")
122        self._adjust_log_enable_setting(key=self.supllogenable, enable=True)
123
124    def disable_supl_log(self):
125        """Set SuplLogEnable=\"false\" in gps.xml"""
126        self.ad.log.info("Disable SUPL log")
127        self._adjust_log_enable_setting(key=self.supllogenable, enable=False)
128
129    def remove_supl_logs(self):
130        """Remove /data/vendor/gps/suplflow.txt"""
131        self.ad.log.info("Remove SUPL logs")
132        command = f"rm -f {self.supl_log}"
133        self.ad.adb.shell(command)
134
135    def is_supl_log_file_exist(self):
136        """Check if /data/vendor/gps/suplflow.txt exist
137        Returns:
138            bool: True -> supl log exists / False -> supl log doesn't exist
139        """
140        result = self._check_file_exist(self.supl_log)
141        self.ad.log.debug("Supl file exists?: %s", result)
142        return result
143
144
145class LhdConf(GpsConfig):
146    def __init__(self, ad) -> None:
147        super().__init__(ad, "lhd.conf")
148        self.lhefailsafe = "LheFailSafe"
149        self.lheconsole = "LheConsole"
150        self.lheconsole_hub = self.get_lheconsole_value()
151        self.esw_crash_dump_pattern = self.get_esw_crash_dump_pattern()
152
153    def _adjust_lhe_setting(self, key, enable):
154        """Set lhe setting.
155        Enable - uncomment out the setting
156        Dissable - comment out the setting
157        Args:
158            key: A string will be used as search pattern
159            enable: bool True to enable / False to disable
160        """
161        pattern = f"#\ {key}" if enable else key
162        target = key if enable else f"#\ {key}"
163        self._change_file_content(pattern, target)
164
165    def enable_lhefailsafe(self):
166        """Uncomment out LheFailSafe"""
167        self.ad.log.info("Enable %s", self.lhefailsafe)
168        self._adjust_lhe_setting(key=self.lhefailsafe, enable=True)
169
170    def disable_lhefailsafe(self):
171        """Comment out LheFailSafe"""
172        self.ad.log.info("Disable %s", self.lhefailsafe)
173        self._adjust_lhe_setting(key=self.lhefailsafe, enable=False)
174
175    def enable_lheconsole(self):
176        """Uncomment out LheConsole"""
177        self.ad.log.info("Enable %s", self.lheconsole)
178        self._adjust_lhe_setting(key=self.lheconsole, enable=True)
179
180    def disable_lheconsole(self):
181        """Comment out LheConsole"""
182        self.ad.log.info("Disable %s", self.lheconsole)
183        self._adjust_lhe_setting(key=self.lheconsole, enable=False)
184
185    def get_lhefailsafe_value(self):
186        """Get the LheFailSafe value
187
188        Returns:
189            string: the LheFailSafe value in config
190        Raises:
191            ValueError: No LheFailSafe value
192        """
193        result = self._get_setting_value(self.lhefailsafe)
194        if not result:
195            raise ValueError(("%s should exists in %s", self.lhefailsafe, self.name))
196        result = result.split("=")[1]
197        self.ad.log.debug("%s is %s", self.lhefailsafe, result)
198        return result
199
200    def get_lheconsole_value(self):
201        """Get the LheConsole value
202
203        Returns:
204            string: the LheConsole value in config
205        Raises:
206            ValueError: No LheConsole value
207        """
208        result = self._get_setting_value(self.lheconsole)
209        if not result:
210            raise ValueError(("%s should exists in %s", self.lheconsole, self.name))
211        result = result.split("=")[1]
212        self.ad.log.debug("%s is %s", self.lheconsole, result)
213        return result
214
215    def get_esw_crash_dump_pattern(self):
216        """Get the esw crash dump file pattern
217        The value is set in LheFailSafe, but we need to add wildcard.
218        Returns:
219            string: esw crash dump pattern
220        Raises:
221            ValueError: No LheFailSafe value
222        """
223        value = self.get_lhefailsafe_value()
224        value = value.replace(".txt", "*.txt")
225        self.ad.log.debug("Dump file pattern is %s", value)
226        return value
227
228    def remove_esw_crash_dump_file(self):
229        """Remove crash dump file"""
230        self.ad.log.info("Remove esw crash file")
231        command = f"rm -f {self.esw_crash_dump_pattern}"
232        self.ad.adb.shell(command)
233
234    def trigger_firmware_crash(self):
235        """Send command to LheConsole to trigger firmware crash"""
236        self.ad.log.info("Trigger firmware crash")
237        command = f"echo Lhe:write=0xFFFFFFFF,4 > {self.lheconsole_hub}.toAsic"
238        self.ad.adb.shell(command, timeout=10)
239
240    def is_esw_crash_dump_file_exist(self):
241        """Check if esw_crash_dump_pattern exists
242        Will try 3 times, 1 second interval for each attempt
243        Returns:
244            bool: True -> file exists / False -> file doesn't exist
245        """
246        for attempt in range(1, 4):
247            result = self._check_file_exist(self.esw_crash_dump_pattern)
248            self.ad.log.debug("(Attempt %s)esw dump file exists?: %s", attempt, result)
249            if result:
250                return result
251            time.sleep(1)
252        return False
253
254
255class GnssBroadcomConfigurationTest(BaseTestClass):
256    """ GNSS configuration Tests on Broadcom device."""
257    def setup_class(self):
258        super().setup_class()
259        self.ad = self.android_devices[0]
260        req_params = ["standalone_cs_criteria"]
261        self.unpack_userparams(req_param_names=req_params)
262
263        if not gutils.check_chipset_vendor_by_qualcomm(self.ad):
264            self.init_device()
265            self.gps_config_path = tempfile.mkdtemp()
266            self.gps_xml = GpsXml(self.ad)
267            self.lhd_conf = LhdConf(self.ad)
268            self.scd_conf = ScdConf(self.ad)
269            self.enable_testing_setting()
270            self.backup_gps_config()
271
272    def init_device(self):
273        gutils._init_device(self.ad)
274        gutils.enable_supl_mode(self.ad)
275        gutils.enable_vendor_orbit_assistance_data(self.ad)
276        wutils.wifi_toggle_state(self.ad, True)
277        gutils.set_mobile_data(self.ad, state=True)
278
279
280    def teardown_class(self):
281        if hasattr(self, "gps_config_path") and os.path.isdir(self.gps_config_path):
282            shutil.rmtree(self.gps_config_path)
283
284    def setup_test(self):
285        gutils.log_current_epoch_time(self.ad, "test_start_time")
286        log_testtracker_uuid(self.ad, self.current_test_name)
287        if gutils.check_chipset_vendor_by_qualcomm(self.ad):
288            raise signals.TestSkip("Device is Qualcomm, skip the test")
289        gutils.get_baseband_and_gms_version(self.ad)
290        gutils.clear_logd_gnss_qxdm_log(self.ad)
291
292    def teardown_test(self):
293        if not gutils.check_chipset_vendor_by_qualcomm(self.ad):
294            self.revert_gps_config()
295            self.ad.reboot()
296        gutils.log_current_epoch_time(self.ad, "test_end_time")
297
298    def on_fail(self, test_name, begin_time):
299        self.ad.take_bug_report(test_name, begin_time)
300        gutils.get_gnss_qxdm_log(self.ad)
301
302    def enable_testing_setting(self):
303        """Enable setting to the testing target
304        Before backing up config, enable all the testing target
305        To ensure the teardown_test can bring the device back to the desired state
306        """
307        self.set_gps_logenabled(enable=True)
308        self.gps_xml.enable_supl_log()
309        self.lhd_conf.enable_lheconsole()
310        self.lhd_conf.enable_lhefailsafe()
311
312    def backup_gps_config(self):
313        """Copy the gps config
314
315        config file will be copied: gps.xml / lhd.conf / scd.conf
316        """
317        for conf in [self.gps_xml, self.scd_conf, self.lhd_conf]:
318            self.ad.log.debug("Backup %s", conf.full_path)
319            self.ad.adb.pull(conf.full_path, self.gps_config_path)
320
321    def revert_gps_config(self):
322        """Revert the gps config from the one we backup in the setup_class
323
324        config file will be reverted: gps.xml / lhd.conf / scd.conf
325        """
326        self.ad.adb.remount()
327        for conf in [self.gps_xml, self.scd_conf, self.lhd_conf]:
328            file_path = os.path.join(self.gps_config_path, conf.name)
329            self.ad.log.debug("Revert %s", conf.full_path)
330            self.ad.adb.push(file_path, conf.full_path)
331
332    def run_gps_and_capture_log(self):
333        """Enable GPS via gps tool for 15s and capture pixel log"""
334        gutils.start_pixel_logger(self.ad)
335        gutils.gnss_tracking_via_gtw_gpstool(self.ad, self.standalone_cs_criteria, testtime=1)
336
337    def set_gps_logenabled(self, enable):
338        """Set LogEnabled in gps.xml / lhd.conf / scd.conf
339
340        Args:
341            enable: True to enable / False to disable
342        """
343        if enable:
344            self.gps_xml.enable_diagnostic_log()
345            self.scd_conf.enable_diagnostic_log()
346            self.lhd_conf.enable_diagnostic_log()
347        else:
348            self.gps_xml.disable_diagnostic_log()
349            self.scd_conf.disable_diagnostic_log()
350            self.lhd_conf.disable_diagnostic_log()
351
352    def test_gps_logenabled_setting(self):
353        """Verify the LogEnabled setting in gps.xml / scd.conf / lhd.conf
354        Steps:
355            1. default setting is on in user_debug build
356            2. run gps tracking for 1 min
357            3. should find slog in pixel logger log files
358            4. disable LogEnabled in all the gps conf
359            5. run gps tracking for 1 min
360            6. should not find slog in pixel logger log files
361        """
362        self.run_gps_and_capture_log()
363        pattern = re.compile(f".*slog\s+:.*")
364        result, _ = gutils.parse_brcm_nmea_log(self.ad, pattern, [])
365        asserts.assert_true(bool(result), "LogEnabled is set to true, but no gps log was found")
366
367        self.set_gps_logenabled(enable=False)
368        gutils.clear_logd_gnss_qxdm_log(self.ad)
369        # Removes pixel logger path again in case pixel logger still writes log unexpectedly.
370        gutils.remove_pixel_logger_folder(self.ad)
371
372        self.run_gps_and_capture_log()
373        try:
374            result, _ = gutils.parse_brcm_nmea_log(self.ad, pattern, [])
375            asserts.assert_false(
376                bool(result),
377                ("LogEnabled is set to False but still found %d slog" % len(result)))
378        except FileNotFoundError:
379            self.ad.log.info("Test pass because no BRCM log files/folders was found")
380
381    def test_gps_supllogenable_setting(self):
382        """Verify SuplLogEnable in gps.xml
383        Steps:
384            1. default setting is on in user_debug build
385            2. remove existing supl log
386            3. run gps tracking for 1 min
387            4. supl log should exist
388            5. disable SuplLogEnable in gps.xml
389            6. remove existing supl log
390            7. run gps tracking for 1 min
391            8. supl log should not exist
392        """
393        def is_supl_log_exist_after_supl_request():
394            self.gps_xml.remove_supl_logs()
395            self.ad.reboot()
396            self.run_gps_and_capture_log()
397            return self.gps_xml.is_supl_log_file_exist()
398
399        result = is_supl_log_exist_after_supl_request()
400        asserts.assert_true(result, "SuplLogEnable is enable, should find supl log file")
401
402        self.gps_xml.disable_supl_log()
403
404        result = is_supl_log_exist_after_supl_request()
405        asserts.assert_false(result, "SuplLogEnable is disable, should not find supl log file")
406
407    def test_lhe_setting(self):
408        """Verify lhefailsafe / lheconsole setting in lhd.conf
409        Steps:
410            1. both setting is enabled
411            2. trigger firmware crash and check if dump file exist
412            3. disable lhefailsafe
413            4. trigger firmware crash and check if dump file exist
414            5. disable lheconsle
415            6. trigger firmware crash and check if command timeout
416        """
417        def is_dump_file_exist_after_firmware_crash():
418            self.lhd_conf.remove_esw_crash_dump_file()
419            self.lhd_conf.trigger_firmware_crash()
420            return self.lhd_conf.is_esw_crash_dump_file_exist()
421
422        result = is_dump_file_exist_after_firmware_crash()
423        asserts.assert_true(result, "LheFailSafe is enabled, but no crash file was found")
424
425        self.lhd_conf.disable_lhefailsafe()
426        self.ad.reboot()
427
428        result = is_dump_file_exist_after_firmware_crash()
429        asserts.assert_false(result, "LheFailSafe is disabled, but still found crash file")
430
431        self.lhd_conf.disable_lheconsole()
432        self.ad.reboot()
433
434        with asserts.assert_raises(TimeoutError):
435            self.lhd_conf.trigger_firmware_crash()
436