1# 2# Copyright (C) 2023 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17import copy 18import json 19import textwrap 20import unittest 21from typing import Any 22 23import gdbclient 24 25 26class LaunchConfigMergeTest(unittest.TestCase): 27 def merge_compare(self, base: dict[str, Any], to_add: dict[str, Any] | None, expected: dict[str, Any]) -> None: 28 actual = copy.deepcopy(base) 29 gdbclient.merge_launch_dict(actual, to_add) 30 self.assertEqual(actual, expected, f'base={base}, to_add={to_add}') 31 32 def test_add_none(self) -> None: 33 base = { 'foo' : 1 } 34 to_add = None 35 expected = { 'foo' : 1 } 36 self.merge_compare(base, to_add, expected) 37 38 def test_add_val(self) -> None: 39 base = { 'foo' : 1 } 40 to_add = { 'bar' : 2} 41 expected = { 'foo' : 1, 'bar' : 2 } 42 self.merge_compare(base, to_add, expected) 43 44 def test_overwrite_val(self) -> None: 45 base = { 'foo' : 1 } 46 to_add = { 'foo' : 2} 47 expected = { 'foo' : 2 } 48 self.merge_compare(base, to_add, expected) 49 50 def test_lists_get_appended(self) -> None: 51 base = { 'foo' : [1, 2] } 52 to_add = { 'foo' : [3, 4]} 53 expected = { 'foo' : [1, 2, 3, 4] } 54 self.merge_compare(base, to_add, expected) 55 56 def test_add_elem_to_dict(self) -> None: 57 base = { 'foo' : { 'bar' : 1 } } 58 to_add = { 'foo' : { 'baz' : 2 } } 59 expected = { 'foo' : { 'bar' : 1, 'baz' : 2 } } 60 self.merge_compare(base, to_add, expected) 61 62 def test_overwrite_elem_in_dict(self) -> None: 63 base = { 'foo' : { 'bar' : 1 } } 64 to_add = { 'foo' : { 'bar' : 2 } } 65 expected = { 'foo' : { 'bar' : 2 } } 66 self.merge_compare(base, to_add, expected) 67 68 def test_merging_dict_and_value_raises(self) -> None: 69 base = { 'foo' : { 'bar' : 1 } } 70 to_add = { 'foo' : 2 } 71 with self.assertRaises(ValueError): 72 gdbclient.merge_launch_dict(base, to_add) 73 74 def test_merging_value_and_dict_raises(self) -> None: 75 base = { 'foo' : 2 } 76 to_add = { 'foo' : { 'bar' : 1 } } 77 with self.assertRaises(ValueError): 78 gdbclient.merge_launch_dict(base, to_add) 79 80 def test_merging_dict_and_list_raises(self) -> None: 81 base = { 'foo' : { 'bar' : 1 } } 82 to_add = { 'foo' : [1] } 83 with self.assertRaises(ValueError): 84 gdbclient.merge_launch_dict(base, to_add) 85 86 def test_merging_list_and_dict_raises(self) -> None: 87 base = { 'foo' : [1] } 88 to_add = { 'foo' : { 'bar' : 1 } } 89 with self.assertRaises(ValueError): 90 gdbclient.merge_launch_dict(base, to_add) 91 92 def test_adding_elem_to_list_raises(self) -> None: 93 base = { 'foo' : [1] } 94 to_add = { 'foo' : 2} 95 with self.assertRaises(ValueError): 96 gdbclient.merge_launch_dict(base, to_add) 97 98 def test_adding_list_to_elem_raises(self) -> None: 99 base = { 'foo' : 1 } 100 to_add = { 'foo' : [2]} 101 with self.assertRaises(ValueError): 102 gdbclient.merge_launch_dict(base, to_add) 103 104 105class VsCodeLaunchGeneratorTest(unittest.TestCase): 106 def setUp(self) -> None: 107 # These tests can generate long diffs, so we remove the limit 108 self.maxDiff = None 109 110 def test_generate_script(self) -> None: 111 self.assertEqual(json.loads(gdbclient.generate_vscode_lldb_script(root='/root', 112 sysroot='/sysroot', 113 binary_name='test', 114 port=123, 115 solib_search_path=['/path1', 116 '/path2'], 117 extra_props=None)), 118 { 119 'name': '(lldbclient.py) Attach test (port: 123)', 120 'type': 'lldb', 121 'request': 'custom', 122 'relativePathBase': '/root', 123 'sourceMap': { '/b/f/w' : '/root', '': '/root', '.': '/root' }, 124 'initCommands': ['settings append target.exec-search-paths /path1 /path2'], 125 'targetCreateCommands': ['target create test', 126 'target modules search-paths add / /sysroot/'], 127 'processCreateCommands': ['gdb-remote 123'] 128 }) 129 130 def test_generate_script_with_extra_props(self) -> None: 131 extra_props = { 132 'initCommands' : ['settings append target.exec-search-paths /path3'], 133 'processCreateCommands' : ['break main', 'continue'], 134 'sourceMap' : { '/test/' : '/root/test'}, 135 'preLaunchTask' : 'Build' 136 } 137 self.assertEqual(json.loads(gdbclient.generate_vscode_lldb_script(root='/root', 138 sysroot='/sysroot', 139 binary_name='test', 140 port=123, 141 solib_search_path=['/path1', 142 '/path2'], 143 extra_props=extra_props)), 144 { 145 'name': '(lldbclient.py) Attach test (port: 123)', 146 'type': 'lldb', 147 'request': 'custom', 148 'relativePathBase': '/root', 149 'sourceMap': { '/b/f/w' : '/root', 150 '': '/root', 151 '.': '/root', 152 '/test/' : '/root/test' }, 153 'initCommands': [ 154 'settings append target.exec-search-paths /path1 /path2', 155 'settings append target.exec-search-paths /path3', 156 ], 157 'targetCreateCommands': ['target create test', 158 'target modules search-paths add / /sysroot/'], 159 'processCreateCommands': ['gdb-remote 123', 160 'break main', 161 'continue'], 162 'preLaunchTask' : 'Build' 163 }) 164 165 166class LaunchConfigInsertTest(unittest.TestCase): 167 def setUp(self) -> None: 168 # These tests can generate long diffs, so we remove the limit 169 self.maxDiff = None 170 171 def test_insert_config(self) -> None: 172 dst = textwrap.dedent("""\ 173 // #lldbclient-generated-begin 174 // #lldbclient-generated-end""") 175 to_insert = textwrap.dedent("""\ 176 foo 177 bar""") 178 self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, 179 to_insert), 180 textwrap.dedent("""\ 181 // #lldbclient-generated-begin 182 foo 183 bar 184 // #lldbclient-generated-end""")) 185 186 def test_insert_into_start(self) -> None: 187 dst = textwrap.dedent("""\ 188 // #lldbclient-generated-begin 189 // #lldbclient-generated-end 190 more content""") 191 to_insert = textwrap.dedent("""\ 192 foo 193 bar""") 194 self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, 195 to_insert), 196 textwrap.dedent("""\ 197 // #lldbclient-generated-begin 198 foo 199 bar 200 // #lldbclient-generated-end 201 more content""")) 202 203 def test_insert_into_mid(self) -> None: 204 dst = textwrap.dedent("""\ 205 start content 206 // #lldbclient-generated-begin 207 // #lldbclient-generated-end 208 more content""") 209 to_insert = textwrap.dedent("""\ 210 foo 211 bar""") 212 self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, 213 to_insert), 214 textwrap.dedent("""\ 215 start content 216 // #lldbclient-generated-begin 217 foo 218 bar 219 // #lldbclient-generated-end 220 more content""")) 221 222 def test_insert_into_end(self) -> None: 223 dst = textwrap.dedent("""\ 224 start content 225 // #lldbclient-generated-begin 226 // #lldbclient-generated-end""") 227 to_insert = textwrap.dedent("""\ 228 foo 229 bar""") 230 self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, 231 to_insert), 232 textwrap.dedent("""\ 233 start content 234 // #lldbclient-generated-begin 235 foo 236 bar 237 // #lldbclient-generated-end""")) 238 239 def test_insert_twice(self) -> None: 240 dst = textwrap.dedent("""\ 241 // #lldbclient-generated-begin 242 // #lldbclient-generated-end 243 // #lldbclient-generated-begin 244 // #lldbclient-generated-end 245 """) 246 to_insert = 'foo' 247 self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, 248 to_insert), 249 textwrap.dedent("""\ 250 // #lldbclient-generated-begin 251 foo 252 // #lldbclient-generated-end 253 // #lldbclient-generated-begin 254 foo 255 // #lldbclient-generated-end 256 """)) 257 258 def test_preserve_space_indent(self) -> None: 259 dst = textwrap.dedent("""\ 260 { 261 "version": "0.2.0", 262 "configurations": [ 263 // #lldbclient-generated-begin 264 // #lldbclient-generated-end 265 ] 266 } 267 """) 268 to_insert = textwrap.dedent("""\ 269 { 270 "name": "(lldbclient.py) Attach test", 271 "type": "lldb", 272 "processCreateCommands": [ 273 "gdb-remote 123", 274 "test" 275 ] 276 }""") 277 self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, 278 to_insert), 279 textwrap.dedent("""\ 280 { 281 "version": "0.2.0", 282 "configurations": [ 283 // #lldbclient-generated-begin 284 { 285 "name": "(lldbclient.py) Attach test", 286 "type": "lldb", 287 "processCreateCommands": [ 288 "gdb-remote 123", 289 "test" 290 ] 291 } 292 // #lldbclient-generated-end 293 ] 294 } 295 """)) 296 297 def test_preserve_tab_indent(self) -> None: 298 dst = textwrap.dedent("""\ 299 { 300 \t"version": "0.2.0", 301 \t"configurations": [ 302 \t\t// #lldbclient-generated-begin 303 \t\t// #lldbclient-generated-end 304 \t] 305 } 306 """) 307 to_insert = textwrap.dedent("""\ 308 { 309 \t"name": "(lldbclient.py) Attach test", 310 \t"type": "lldb", 311 \t"processCreateCommands": [ 312 \t\t"gdb-remote 123", 313 \t\t"test" 314 \t] 315 }""") 316 self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, 317 to_insert), 318 textwrap.dedent("""\ 319 { 320 \t"version": "0.2.0", 321 \t"configurations": [ 322 \t\t// #lldbclient-generated-begin 323 \t\t{ 324 \t\t\t"name": "(lldbclient.py) Attach test", 325 \t\t\t"type": "lldb", 326 \t\t\t"processCreateCommands": [ 327 \t\t\t\t"gdb-remote 123", 328 \t\t\t\t"test" 329 \t\t\t] 330 \t\t} 331 \t\t// #lldbclient-generated-end 332 \t] 333 } 334 """)) 335 336 def test_preserve_trailing_whitespace(self) -> None: 337 dst = textwrap.dedent("""\ 338 // #lldbclient-generated-begin \t 339 // #lldbclient-generated-end\t """) 340 to_insert = 'foo' 341 self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, 342 to_insert), 343 textwrap.dedent("""\ 344 // #lldbclient-generated-begin \t 345 foo 346 // #lldbclient-generated-end\t """)) 347 348 def test_fail_if_no_begin(self) -> None: 349 dst = textwrap.dedent("""\ 350 // #lldbclient-generated-end""") 351 with self.assertRaisesRegex(ValueError, 'Did not find begin marker line'): 352 gdbclient.insert_commands_into_vscode_config(dst, 'foo') 353 354 def test_fail_if_no_end(self) -> None: 355 dst = textwrap.dedent("""\ 356 // #lldbclient-generated-begin""") 357 with self.assertRaisesRegex(ValueError, 'Unterminated begin marker at line 1'): 358 gdbclient.insert_commands_into_vscode_config(dst, 'foo') 359 360 def test_fail_if_begin_has_extra_text(self) -> None: 361 dst = textwrap.dedent("""\ 362 // #lldbclient-generated-begin text 363 // #lldbclient-generated-end""") 364 with self.assertRaisesRegex(ValueError, 'Did not find begin marker line'): 365 gdbclient.insert_commands_into_vscode_config(dst, 'foo') 366 367 def test_fail_if_end_has_extra_text(self) -> None: 368 dst = textwrap.dedent("""\ 369 // #lldbclient-generated-begin 370 // #lldbclient-generated-end text""") 371 with self.assertRaisesRegex(ValueError, 'Unterminated begin marker at line 1'): 372 gdbclient.insert_commands_into_vscode_config(dst, 'foo') 373 374 def test_fail_if_begin_end_swapped(self) -> None: 375 dst = textwrap.dedent("""\ 376 // #lldbclient-generated-end 377 // #lldbclient-generated-begin""") 378 with self.assertRaisesRegex(ValueError, 'Unterminated begin marker at line 2'): 379 gdbclient.insert_commands_into_vscode_config(dst, 'foo') 380 381 382if __name__ == '__main__': 383 unittest.main(verbosity=2) 384