1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# 4# Copyright (c) 2024-2025 Huawei Device Co., Ltd. 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# 17 18import logging 19import warnings 20from signal import SIGTERM 21 22import trio 23from cdp import runtime 24from pytest import fixture 25 26from arkdb.compiler import StringCodeCompiler 27from arkdb.debug_client import DebuggerClient, DebugLocator 28from arkdb.logs import RichLogger 29from arkdb.runnable_module import ScriptFile 30from arkdb.runtime import Runtime, RuntimeProcess 31from arkdb.source_meta import parse_source_meta 32 33 34class RuntimeExitStatusWarning(UserWarning): 35 pass 36 37 38@fixture 39def script_file( 40 code_compiler: StringCodeCompiler, 41) -> ScriptFile: 42 code = """\ 43class A { 44 public i: int; 45 public d: double; 46 public s: string; 47 public ss: String; 48 constructor() { 49 this.i = 1; 50 this.d = 2; 51 this.s = 's'; 52 this.ss = 'ss'; 53 } 54} 55 56function foo(arg: int): int { 57 let c: int = 2; 58 return arg * c 59} 60 61function main(): int { 62 let a: int = 100; 63 let b: int = a * foo(a); 64 let obj = new A(); 65 console.log(a / b) 66 return a / b; 67} 68""" 69 return code_compiler.compile(code) 70 71 72def check_exit_status(process: RuntimeProcess, log: RichLogger): 73 status = process.returncode 74 if status != 0: 75 msg = f"Runtime exit status is {status}" 76 log.warning(msg) 77 warnings.warn(msg, RuntimeExitStatusWarning) 78 79 80async def test_run( 81 nursery: trio.Nursery, 82 ark_runtime: Runtime, 83 script_file: ScriptFile, 84 log: RichLogger, 85): 86 script_file.log(log, logging.INFO) 87 async with ark_runtime.run(nursery, module=script_file, debug=False) as process: 88 await process.wait() 89 check_exit_status(process, log) 90 91 92async def test_debug( 93 nursery: trio.Nursery, 94 ark_runtime: Runtime, 95 script_file: ScriptFile, 96 log: RichLogger, 97 debug_locator: DebugLocator, 98): 99 script_file.log(log, logging.INFO) 100 async with ark_runtime.run(nursery, module=script_file) as process: 101 async with debug_locator.connect(nursery) as client: 102 await client.configure(nursery) 103 paused = await client.run_if_waiting_for_debugger() 104 log.info("%s", paused) 105 await client.resume() 106 check_exit_status(process, log) 107 108 109async def _pause_and_get_vars(client: DebuggerClient, log: RichLogger, script_file: ScriptFile, line_number: int): 110 paused = await client.continue_to_location(script_id=runtime.ScriptId("0"), line_number=line_number) 111 script_file.log(log, highlight_lines=[paused.call_frames[0].location.line_number + 1]) 112 log.info("paused: %s", paused) 113 object_ids = [ 114 scope.object_.object_id 115 for frame in paused.call_frames 116 for scope in frame.scope_chain 117 if scope.object_.object_id is not None 118 ] 119 # fmt: off 120 props = [ 121 prop 122 for props in [(await client.get_properties(obj_id))[0] 123 for obj_id in object_ids] 124 for prop in props 125 ] 126 # fmt: on 127 variables = {prop.name: prop.value.value if prop.value is not None else None for prop in props} 128 log.info("Properties: \n%s", repr(props)) 129 log.info("All variables: %r", variables) 130 return variables 131 132 133async def test_code_compiler( 134 nursery: trio.Nursery, 135 ark_runtime: Runtime, 136 code_compiler: StringCodeCompiler, 137 debug_locator: DebugLocator, 138 log: RichLogger, 139): 140 code = """ 141 function main(): int { 142 let a: int = 100; 143 let b: int = a + 10; 144 console.log(a + 1) 145 return a; 146 } 147 """ 148 149 script_file = code_compiler.compile(code) 150 async with ark_runtime.run(nursery, module=script_file) as process: 151 async with debug_locator.connect(nursery) as client: 152 153 await client.configure(nursery) 154 await client.run_if_waiting_for_debugger() 155 156 variables = await _pause_and_get_vars(client, log, script_file, 3) 157 assert variables == {"a": 100} 158 159 variables = await _pause_and_get_vars(client, log, script_file, 4) 160 assert variables == {"a": 100, "b": 110} 161 162 await client.resume() 163 check_exit_status(process, log) 164 165 166async def test_inline_breakpoints( 167 nursery: trio.Nursery, 168 ark_runtime: Runtime, 169 code_compiler: StringCodeCompiler, 170 debug_locator: DebugLocator, 171 log: RichLogger, 172): 173 code = """\ 174 function main(): int { 175 let a: int = 100; // # BP {} 176 let b: int = a + 10; // #BP{1} 177 console.log(a + 1) // # BP 178 return a; 179 }""" 180 181 script_file = code_compiler.compile(code) 182 meta = parse_source_meta(code) # noqa F841 183 log.info("Parsed breakpoints %s", meta.breakpoints) 184 script_file.log(log, highlight_lines=[b.line_number for b in meta.breakpoints]) 185 186 async def _check_breakpoints(): 187 await trio.lowlevel.checkpoint() 188 for br in meta.breakpoints: 189 with trio.fail_after(2): 190 paused = await client.resume_and_wait_for_paused() 191 paused_ln = paused.call_frames[0].location.line_number 192 script_file.log(log, highlight_lines=[paused_ln]) 193 assert paused_ln == br.line_number 194 195 async with ark_runtime.run(nursery, module=script_file) as process: 196 async with debug_locator.connect(nursery) as client: 197 await client.configure(nursery) 198 _ = await client.run_if_waiting_for_debugger() 199 for br in meta.breakpoints: 200 await client.set_breakpoint_by_url( 201 url=script_file.source_file.name, 202 line_number=br.line_number, 203 ) 204 205 await _check_breakpoints() 206 process.terminate() 207 assert process.returncode == -SIGTERM 208