1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3"""
4Copyright (c) 2024 Huawei Device Co., Ltd.
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9    http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16
17Description: Scenario test case.
18"""
19
20import json
21import logging
22import os
23import time
24
25import pytest
26
27from aw import Application
28from aw import Utils
29from aw import debugger
30from aw.api import debugger_api, runtime_api
31
32
33@pytest.mark.debug
34@pytest.mark.timeout(30)
35class TestDebug02:
36    """
37    测试用例:多实例 attach 调试
38    测试步骤:
39        1.  拉起应用,attach 主线程
40        2.  连接 connect server 和主线程 debugger server
41        3.  创建两个子线程,连接子线程 debugger server
42        4.  所有线程使能 Runtime 和 Debugger
43        5.  主线程 Index.ts 文件设置断点(Debugger.getPossibleAndSetBreakpointByUrl)
44        6.  触发点击事件,主线程命中断点
45        7.  销毁其中一个子线程
46        8.  子线程 Worker.ts 文件设置断点(Debugger.getPossibleAndSetBreakpointByUrl)
47        9.  主线程 resume,暂停在下一断点(Debugger.resume)
48        10. 重新创建一个子线程,使能并设置断点
49        11. 主线程 resume,发送消息给子线程,主线程暂停在下一断点(Debugger.resume)
50        12. 子线程命中断点后 stepOut,发消息给主线程(Debugger.stepOut)
51        13. 主线程 stepOver,发送消息给另一子线程,主线程暂停在下一行(Debugger.stepOver)
52        14. 子线程命中断点后 resume,发消息给主线程(Debugger.resume)
53        15. 销毁所有子线程,对应的 debugger server 连接断开
54        16. 关闭主线程 debugger server 和 connect server 连接
55    """
56
57    def setup_method(self):
58        logging.info('Start running TestDebug02: setup')
59
60        self.log_path = rf'{os.path.dirname(__file__)}\..\log'
61        self.hilog_file_name = 'test_debug_02.hilog.txt'
62        self.id_generator = Utils.message_id_generator()
63
64        # receive the hilog before the test start
65        Utils.clear_fault_log()
66        self.hilog_process, self.write_thread = Utils.save_hilog(log_path=self.log_path,
67                                                                 file_name=self.hilog_file_name,
68                                                                 debug_on=True)
69
70    def teardown_method(self):
71        Application.uninstall(self.config['bundle_name'])
72
73        # terminate the hilog receive process after the test done
74        time.sleep(3)
75        self.hilog_process.stdout.close()
76        self.hilog_process.terminate()
77        self.hilog_process.wait()
78        self.write_thread.join()
79
80        Utils.save_fault_log(log_path=self.log_path)
81        logging.info('TestDebug02 done')
82
83    def test(self, test_suite_worker_01):
84        logging.info('Start running TestDebug02: test')
85        self.config = test_suite_worker_01
86        websocket = self.config['websocket']
87        taskpool = self.config['taskpool']
88        pid = self.config['pid']
89        self.debugger_impl = debugger_api.DebuggerImpl(self.id_generator, websocket)
90        self.runtime_impl = runtime_api.RuntimeImpl(self.id_generator, websocket)
91
92        Application.attach(self.config['bundle_name'])
93        taskpool.submit(websocket.main_task(taskpool, self.procedure, pid))
94        taskpool.await_taskpool()
95        taskpool.task_join()
96        if taskpool.task_exception:
97            raise taskpool.task_exception
98
99    async def procedure(self, websocket):
100        ################################################################################################################
101        # main thread: connect the debugger server
102        ################################################################################################################
103        main_thread = await self.debugger_impl.connect_to_debugger_server(self.config['pid'], True)
104        logging.info(f'Connect to the debugger server of instance: {main_thread.instance_id}')
105        ################################################################################################################
106        # worker thread: connect the debugger server
107        ################################################################################################################
108        worker_thread_1 = await self.debugger_impl.connect_to_debugger_server(self.config['pid'], False)
109        logging.info(f'Connect to the debugger server of instance: {worker_thread_1.instance_id}')
110        worker_thread_2 = await self.debugger_impl.connect_to_debugger_server(self.config['pid'], False)
111        logging.info(f'Connect to the debugger server of instance: {worker_thread_2.instance_id}')
112        ################################################################################################################
113        # worker thread: Runtime.enable
114        ################################################################################################################
115        await self.runtime_impl.send("Runtime.enable", worker_thread_1)
116        await self.runtime_impl.send("Runtime.enable", worker_thread_2)
117        ################################################################################################################
118        # worker thread: Debugger.enable
119        ################################################################################################################
120        await self.debugger_impl.send("Debugger.enable", worker_thread_1)
121        await self.debugger_impl.send("Debugger.enable", worker_thread_2)
122        ################################################################################################################
123        # worker thread: Runtime.runIfWaitingForDebugger
124        ################################################################################################################
125        await self.runtime_impl.send("Runtime.runIfWaitingForDebugger", worker_thread_1)
126        await self.runtime_impl.send("Runtime.runIfWaitingForDebugger", worker_thread_2)
127        ################################################################################################################
128        # main thread: Runtime.enable
129        ################################################################################################################
130        await self.runtime_impl.send("Runtime.enable", main_thread)
131        ################################################################################################################
132        # main thread: Debugger.enable
133        ################################################################################################################
134        await self.debugger_impl.send("Debugger.enable", main_thread)
135        ################################################################################################################
136        # main thread: Runtime.runIfWaitingForDebugger
137        ################################################################################################################
138        await self.runtime_impl.send("Runtime.runIfWaitingForDebugger", main_thread)
139        ################################################################################################################
140        # main thread: Debugger.removeBreakpointsByUrl
141        ################################################################################################################
142        params = debugger.RemoveBreakpointsUrl(self.config['file_path']['index'])
143        await self.debugger_impl.send("Debugger.removeBreakpointsByUrl", main_thread, params)
144        ################################################################################################################
145        # main thread: Debugger.getPossibleAndSetBreakpointByUrl
146        ################################################################################################################
147        locations = [debugger.BreakLocationUrl(url=self.config['file_path']['index'], line_number=53),
148                     debugger.BreakLocationUrl(url=self.config['file_path']['index'], line_number=57)]
149        params = debugger.SetBreakpointsLocations(locations)
150        response = await self.debugger_impl.send("Debugger.getPossibleAndSetBreakpointsByUrl",
151                                                 main_thread, params)
152        assert response['result']['locations'][0]['id'] == 'id:53:0:' + self.config['file_path']['index']
153        assert response['result']['locations'][1]['id'] == 'id:57:0:' + self.config['file_path']['index']
154        ################################################################################################################
155        # main thread: click on the screen
156        ################################################################################################################
157        Application.click_on_middle()
158        ################################################################################################################
159        # main thread: Debugger.paused, hit breakpoint
160        ################################################################################################################
161        response = await self.debugger_impl.recv("Debugger.paused", main_thread)
162        assert response['params']['callFrames'][0]['url'] == self.config['file_path']['index']
163        assert response['params']['hitBreakpoints'] == ['id:53:16:' + self.config['file_path']['index']]
164        ################################################################################################################
165        # worker thread: destroy instance
166        ################################################################################################################
167        response = await self.debugger_impl.destroy_instance()
168        if response['instanceId'] == worker_thread_1.instance_id:
169            worker_thread_1 = worker_thread_2
170        ################################################################################################################
171        # worker thread: Debugger.removeBreakpointsByUrl
172        ################################################################################################################
173        params = debugger.RemoveBreakpointsUrl(self.config['file_path']['worker'])
174        await self.debugger_impl.send("Debugger.removeBreakpointsByUrl", worker_thread_1, params)
175        ################################################################################################################
176        # worker thread: Debugger.getPossibleAndSetBreakpointByUrl
177        ################################################################################################################
178        locations = [debugger.BreakLocationUrl(url=self.config['file_path']['worker'], line_number=11)]
179        params = debugger.SetBreakpointsLocations(locations)
180        response = await self.debugger_impl.send("Debugger.getPossibleAndSetBreakpointsByUrl",
181                                                 worker_thread_1, params)
182        assert response['result']['locations'][0]['id'] == 'id:11:0:' + self.config['file_path']['worker']
183        ################################################################################################################
184        # main thread: Debugger.resume
185        ################################################################################################################
186        await self.debugger_impl.send("Debugger.resume", main_thread)
187        # main thread: Debugger.paused, hit breakpoint
188        response = await self.debugger_impl.recv("Debugger.paused", main_thread)
189        assert response['params']['callFrames'][0]['url'] == self.config['file_path']['index']
190        assert response['params']['hitBreakpoints'] == ['id:57:20:' + self.config['file_path']['index']]
191        ################################################################################################################
192        # worker thread: connect the debugger server
193        ################################################################################################################
194        worker_thread_2 = await self.debugger_impl.connect_to_debugger_server(self.config['pid'], False)
195        logging.info(f'Connect to the debugger server of instance: {worker_thread_2.instance_id}')
196        ################################################################################################################
197        # worker thread: Runtime.enable
198        ################################################################################################################
199        await self.runtime_impl.send("Runtime.enable", worker_thread_2)
200        ################################################################################################################
201        # worker thread: Debugger.enable
202        ################################################################################################################
203        await self.debugger_impl.send("Debugger.enable", worker_thread_2)
204        ################################################################################################################
205        # worker thread: Runtime.runIfWaitingForDebugger
206        ################################################################################################################
207        await self.runtime_impl.send("Runtime.runIfWaitingForDebugger", worker_thread_2)
208        # worker thread: Debugger.scriptParsed
209        response = await self.debugger_impl.recv("Debugger.scriptParsed", worker_thread_2)
210        assert response['params']['url'] == self.config['file_path']['worker']
211        assert response['params']['endLine'] == 0
212        # worker thread: Debugger.paused
213        response = await self.debugger_impl.recv("Debugger.paused", worker_thread_2)
214        assert response['params']['callFrames'][0]['url'] == self.config['file_path']['worker']
215        assert response['params']['reason'] == 'Break on start'
216        ################################################################################################################
217        # worker thread: Debugger.removeBreakpointsByUrl
218        ################################################################################################################
219        params = debugger.RemoveBreakpointsUrl(self.config['file_path']['worker'])
220        await self.debugger_impl.send("Debugger.removeBreakpointsByUrl", worker_thread_2, params)
221        ################################################################################################################
222        # worker thread: Debugger.getPossibleAndSetBreakpointByUrl
223        ################################################################################################################
224        locations = [debugger.BreakLocationUrl(url=self.config['file_path']['worker'], line_number=11)]
225        params = debugger.SetBreakpointsLocations(locations)
226        response = await self.debugger_impl.send("Debugger.getPossibleAndSetBreakpointsByUrl",
227                                                 worker_thread_2, params)
228        assert response['result']['locations'][0]['id'] == 'id:11:0:' + self.config['file_path']['worker']
229        ################################################################################################################
230        # worker thread: Debugger.resume
231        ################################################################################################################
232        await self.debugger_impl.send("Debugger.resume", worker_thread_2)
233        ################################################################################################################
234        # main thread: Debugger.resume
235        ################################################################################################################
236        await self.debugger_impl.send("Debugger.resume", main_thread)
237        # main thread: Debugger.paused
238        response = await self.debugger_impl.recv("Debugger.paused", main_thread)
239        assert response['params']['callFrames'][0]['url'] == self.config['file_path']['index']
240        assert response['params']['reason'] == 'other'
241        assert response['params']['hitBreakpoints'] == ['id:57:20:' + self.config['file_path']['index']]
242        # worker thread: Debugger.paused
243        response = await self.debugger_impl.recv("Debugger.paused", worker_thread_1)
244        assert response['params']['callFrames'][0]['url'] == self.config['file_path']['worker']
245        assert response['params']['reason'] == 'Break on start'
246        assert response['params']['hitBreakpoints'] == []
247        ################################################################################################################
248        # worker thread: Debugger.resume
249        ################################################################################################################
250        await self.debugger_impl.send("Debugger.resume", worker_thread_1)
251        # worker thread: Debugger.paused
252        response = await self.debugger_impl.recv("Debugger.paused", worker_thread_1)
253        assert response['params']['callFrames'][0]['url'] == self.config['file_path']['worker']
254        assert response['params']['reason'] == 'other'
255        assert response['params']['hitBreakpoints'] == ['id:11:4:' + self.config['file_path']['worker']]
256        ################################################################################################################
257        # worker thread: Debugger.stepOut
258        ################################################################################################################
259        await self.debugger_impl.send("Debugger.stepOut", worker_thread_1)
260        ################################################################################################################
261        # worker thread: Debugger.disable
262        ################################################################################################################
263        await self.debugger_impl.send("Debugger.disable", worker_thread_1)
264        ################################################################################################################
265        # main thread: Debugger.stepOver
266        ################################################################################################################
267        await self.debugger_impl.send("Debugger.stepOver", main_thread)
268        # main thread: Debugger.paused
269        response = await self.debugger_impl.recv("Debugger.paused", main_thread)
270        assert response['params']['callFrames'][0]['url'] == self.config['file_path']['index']
271        assert response['params']['reason'] == 'other'
272        assert response['params']['hitBreakpoints'] == []
273        # worker thread: Debugger.paused
274        response = await self.debugger_impl.recv("Debugger.paused", worker_thread_2)
275        assert response['params']['callFrames'][0]['url'] == self.config['file_path']['worker']
276        assert response['params']['reason'] == 'other'
277        assert response['params']['hitBreakpoints'] == ['id:11:4:' + self.config['file_path']['worker']]
278        ################################################################################################################
279        # worker thread: Debugger.resume
280        ################################################################################################################
281        await self.debugger_impl.send("Debugger.resume", worker_thread_2)
282        ################################################################################################################
283        # worker thread: Debugger.disable
284        ################################################################################################################
285        await self.debugger_impl.send("Debugger.disable", worker_thread_2)
286        ################################################################################################################
287        # main thread: Debugger.resume
288        ################################################################################################################
289        await self.debugger_impl.send("Debugger.resume", main_thread)
290        ################################################################################################################
291        # worker thread: destroy instance
292        ################################################################################################################
293        response = await self.debugger_impl.destroy_instance()
294        assert response['instanceId'] != self.config['pid']
295        response = await self.debugger_impl.destroy_instance()
296        assert response['instanceId'] != self.config['pid']
297        ################################################################################################################
298        # main thread: Debugger.disable
299        ################################################################################################################
300        await self.debugger_impl.send("Debugger.disable", main_thread)
301        ################################################################################################################
302        # close the websocket connections
303        ################################################################################################################
304        await websocket.send_msg_to_debugger_server(main_thread.instance_id, main_thread.send_msg_queue, 'close')
305        await websocket.send_msg_to_connect_server('close')
306        ################################################################################################################