1# Copyright (C) 2016 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15'''Module that contains the base class TestBaseRemote'''
16
17from __future__ import absolute_import
18
19import os
20import re
21
22from .test_base import TestBase
23from . import util_log
24
25
26class TestBaseRemote(TestBase):
27    '''Base class for all tests that connect to a remote device.
28
29    Provides common functionality to set up the connection and tear it down.
30    '''
31
32    def __init__(self, device_port, device, timer, *args, **kwargs):
33        super(TestBaseRemote, self).__init__(device_port, device, timer, *args, **kwargs)
34        # port used by lldb-server on the device.
35        self._device_port = device_port
36        self._platform = None
37        # id of the device that adb will communicate with.
38        self._device = device
39
40    def set_src_map(self, file_name, new_src_path):
41        '''Call lldb to set the source mapping of a given file.
42
43        Set lldb's source mapping of a given file to a given path. This can be
44        used to make the test suite independent of where an APK was compiled.
45
46        Args:
47            file_name: String, which is the name of the file whose mapping is
48                to be changed
49            new_src_path: String which is the new absolute path to the source
50                file.
51        '''
52        line_table = self.do_command('target modules dump line-table '
53                                     + file_name)
54
55        lines = line_table.split('\n')
56        if 'Line table for' not in lines[0]:
57            raise self.TestFail('Could not determine source path of '
58                                + file_name)
59
60        # Expecting output like:
61        # (lldb) target modules dump line-table scalars.rs
62        # Line table for /home/jenkins/workspace/grd-aosp-parameterised-build/
63        # merge_151216/frameworks/rs/tests/lldb/java/BranchingFunCalls/src/rs/
64        # frameworks/rs/tests/lldb/java/BranchingFunCalls/src/rs/scalars.rs in
65        # `librs.scalars.so
66        # 0xb30f2374: /home/jenkins/workspace/grd-aosp-parameterised-build/
67        # merge_151216/frameworks/rs/tests/lldb/java/BranchingFunCalls/src/rs/
68        # scalars.rs:46
69        # ...
70        # For some reason the first line contains a mangled path?
71        old_path = re.findall(r"[^ :]+", lines[1])[1]
72        old_dir = os.path.dirname(old_path)
73
74        self.try_command('settings set target.source-map %s %s'
75                         % (old_dir, new_src_path), [''])
76
77    def post_run(self):
78        '''Clean up after execution.'''
79        if self._platform:
80            self._platform.DisconnectRemote()
81
82    def _connect_to_platform(self, lldb_module, dbg, remote_pid):
83        '''Connect to an lldb platform that has been started elsewhere.
84
85        Args:
86            lldb_module: A handle to the lldb module.
87            dbg: The instance of the SBDebugger that should connect to the
88                 server.
89            remote_pid: The integer that is the process id of the binary that
90                        the debugger should attach to.
91
92        Returns:
93            True if the debugger successfully attached to the server and
94            process.
95        '''
96        # pylint: disable=too-many-return-statements
97        remote_pid = str(remote_pid)
98
99        log = util_log.get_logger()
100
101        err1 = dbg.SetCurrentPlatform('remote-android')
102        if err1.Fail():
103            log.fatal(err1.GetCString())
104            return False
105
106        self._platform = dbg.GetSelectedPlatform()
107        if not self._platform:
108            return False
109
110        connect_string = \
111            'adb://{0}:{1}'.format(self._device, self._device_port)
112        opts = lldb_module.SBPlatformConnectOptions(connect_string)
113
114        for _ in range(2):
115            err2 = self._platform.ConnectRemote(opts)
116            if err2.Fail():
117                log.error(err2.GetCString())
118
119                if 'Connection refused' in err2.GetCString():
120                    log.warning('Connection to lldb server was refused. '
121                                'Trying again.')
122                else:
123                    # Unknown error. Don't try again.
124                    return False
125            else:
126                # Success
127                break
128        else:
129            log.fatal('Not trying again, maximum retries exceeded.')
130            return False
131
132        target = dbg.CreateTarget(None)
133        if not target:
134            return False
135
136        dbg.SetSelectedTarget(target)
137        listener = lldb_module.SBListener()
138        err3 = lldb_module.SBError()
139        process = target.AttachToProcessWithID(listener, int(remote_pid), err3)
140        if err3.Fail() or not process:
141            log.fatal(err3.GetCString())
142            return False
143
144        return True
145
146    def run(self, dbg, remote_pid, lldb):
147        '''Execute the actual testsuite.
148
149        Args:
150            dbg: The instance of the SBDebugger that is used to test commands.
151            remote_pid: The integer that is the process id of the binary that
152                        the debugger is attached to.
153            lldb: A handle to the lldb module.
154
155        Returns: list of (test, failure) tuples.
156
157        '''
158        assert dbg
159        assert remote_pid
160        assert lldb
161
162        self._lldb = lldb
163
164        self.assert_true(self._connect_to_platform(lldb, dbg, remote_pid))
165        self._ci = dbg.GetCommandInterpreter()
166        assert self._ci
167
168        self.assert_true(self._ci.IsValid())
169        self.assert_true(self._ci.HasCommands())
170
171        return super(TestBaseRemote, self).run(dbg, remote_pid, lldb)
172
173