# # Copyright (C) 2023 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import copy import json import textwrap import unittest from typing import Any import gdbclient class LaunchConfigMergeTest(unittest.TestCase): def merge_compare(self, base: dict[str, Any], to_add: dict[str, Any] | None, expected: dict[str, Any]) -> None: actual = copy.deepcopy(base) gdbclient.merge_launch_dict(actual, to_add) self.assertEqual(actual, expected, f'base={base}, to_add={to_add}') def test_add_none(self) -> None: base = { 'foo' : 1 } to_add = None expected = { 'foo' : 1 } self.merge_compare(base, to_add, expected) def test_add_val(self) -> None: base = { 'foo' : 1 } to_add = { 'bar' : 2} expected = { 'foo' : 1, 'bar' : 2 } self.merge_compare(base, to_add, expected) def test_overwrite_val(self) -> None: base = { 'foo' : 1 } to_add = { 'foo' : 2} expected = { 'foo' : 2 } self.merge_compare(base, to_add, expected) def test_lists_get_appended(self) -> None: base = { 'foo' : [1, 2] } to_add = { 'foo' : [3, 4]} expected = { 'foo' : [1, 2, 3, 4] } self.merge_compare(base, to_add, expected) def test_add_elem_to_dict(self) -> None: base = { 'foo' : { 'bar' : 1 } } to_add = { 'foo' : { 'baz' : 2 } } expected = { 'foo' : { 'bar' : 1, 'baz' : 2 } } self.merge_compare(base, to_add, expected) def test_overwrite_elem_in_dict(self) -> None: base = { 'foo' : { 'bar' : 1 } } to_add = { 'foo' : { 'bar' : 2 } } expected = { 'foo' : { 'bar' : 2 } } self.merge_compare(base, to_add, expected) def test_merging_dict_and_value_raises(self) -> None: base = { 'foo' : { 'bar' : 1 } } to_add = { 'foo' : 2 } with self.assertRaises(ValueError): gdbclient.merge_launch_dict(base, to_add) def test_merging_value_and_dict_raises(self) -> None: base = { 'foo' : 2 } to_add = { 'foo' : { 'bar' : 1 } } with self.assertRaises(ValueError): gdbclient.merge_launch_dict(base, to_add) def test_merging_dict_and_list_raises(self) -> None: base = { 'foo' : { 'bar' : 1 } } to_add = { 'foo' : [1] } with self.assertRaises(ValueError): gdbclient.merge_launch_dict(base, to_add) def test_merging_list_and_dict_raises(self) -> None: base = { 'foo' : [1] } to_add = { 'foo' : { 'bar' : 1 } } with self.assertRaises(ValueError): gdbclient.merge_launch_dict(base, to_add) def test_adding_elem_to_list_raises(self) -> None: base = { 'foo' : [1] } to_add = { 'foo' : 2} with self.assertRaises(ValueError): gdbclient.merge_launch_dict(base, to_add) def test_adding_list_to_elem_raises(self) -> None: base = { 'foo' : 1 } to_add = { 'foo' : [2]} with self.assertRaises(ValueError): gdbclient.merge_launch_dict(base, to_add) class VsCodeLaunchGeneratorTest(unittest.TestCase): def setUp(self) -> None: # These tests can generate long diffs, so we remove the limit self.maxDiff = None def test_generate_script(self) -> None: self.assertEqual(json.loads(gdbclient.generate_vscode_lldb_script(root='/root', sysroot='/sysroot', binary_name='test', port=123, solib_search_path=['/path1', '/path2'], extra_props=None)), { 'name': '(lldbclient.py) Attach test (port: 123)', 'type': 'lldb', 'request': 'custom', 'relativePathBase': '/root', 'sourceMap': { '/b/f/w' : '/root', '': '/root', '.': '/root' }, 'initCommands': ['settings append target.exec-search-paths /path1 /path2'], 'targetCreateCommands': ['target create test', 'target modules search-paths add / /sysroot/'], 'processCreateCommands': ['gdb-remote 123'] }) def test_generate_script_with_extra_props(self) -> None: extra_props = { 'initCommands' : ['settings append target.exec-search-paths /path3'], 'processCreateCommands' : ['break main', 'continue'], 'sourceMap' : { '/test/' : '/root/test'}, 'preLaunchTask' : 'Build' } self.assertEqual(json.loads(gdbclient.generate_vscode_lldb_script(root='/root', sysroot='/sysroot', binary_name='test', port=123, solib_search_path=['/path1', '/path2'], extra_props=extra_props)), { 'name': '(lldbclient.py) Attach test (port: 123)', 'type': 'lldb', 'request': 'custom', 'relativePathBase': '/root', 'sourceMap': { '/b/f/w' : '/root', '': '/root', '.': '/root', '/test/' : '/root/test' }, 'initCommands': [ 'settings append target.exec-search-paths /path1 /path2', 'settings append target.exec-search-paths /path3', ], 'targetCreateCommands': ['target create test', 'target modules search-paths add / /sysroot/'], 'processCreateCommands': ['gdb-remote 123', 'break main', 'continue'], 'preLaunchTask' : 'Build' }) class LaunchConfigInsertTest(unittest.TestCase): def setUp(self) -> None: # These tests can generate long diffs, so we remove the limit self.maxDiff = None def test_insert_config(self) -> None: dst = textwrap.dedent("""\ // #lldbclient-generated-begin // #lldbclient-generated-end""") to_insert = textwrap.dedent("""\ foo bar""") self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, to_insert), textwrap.dedent("""\ // #lldbclient-generated-begin foo bar // #lldbclient-generated-end""")) def test_insert_into_start(self) -> None: dst = textwrap.dedent("""\ // #lldbclient-generated-begin // #lldbclient-generated-end more content""") to_insert = textwrap.dedent("""\ foo bar""") self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, to_insert), textwrap.dedent("""\ // #lldbclient-generated-begin foo bar // #lldbclient-generated-end more content""")) def test_insert_into_mid(self) -> None: dst = textwrap.dedent("""\ start content // #lldbclient-generated-begin // #lldbclient-generated-end more content""") to_insert = textwrap.dedent("""\ foo bar""") self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, to_insert), textwrap.dedent("""\ start content // #lldbclient-generated-begin foo bar // #lldbclient-generated-end more content""")) def test_insert_into_end(self) -> None: dst = textwrap.dedent("""\ start content // #lldbclient-generated-begin // #lldbclient-generated-end""") to_insert = textwrap.dedent("""\ foo bar""") self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, to_insert), textwrap.dedent("""\ start content // #lldbclient-generated-begin foo bar // #lldbclient-generated-end""")) def test_insert_twice(self) -> None: dst = textwrap.dedent("""\ // #lldbclient-generated-begin // #lldbclient-generated-end // #lldbclient-generated-begin // #lldbclient-generated-end """) to_insert = 'foo' self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, to_insert), textwrap.dedent("""\ // #lldbclient-generated-begin foo // #lldbclient-generated-end // #lldbclient-generated-begin foo // #lldbclient-generated-end """)) def test_preserve_space_indent(self) -> None: dst = textwrap.dedent("""\ { "version": "0.2.0", "configurations": [ // #lldbclient-generated-begin // #lldbclient-generated-end ] } """) to_insert = textwrap.dedent("""\ { "name": "(lldbclient.py) Attach test", "type": "lldb", "processCreateCommands": [ "gdb-remote 123", "test" ] }""") self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, to_insert), textwrap.dedent("""\ { "version": "0.2.0", "configurations": [ // #lldbclient-generated-begin { "name": "(lldbclient.py) Attach test", "type": "lldb", "processCreateCommands": [ "gdb-remote 123", "test" ] } // #lldbclient-generated-end ] } """)) def test_preserve_tab_indent(self) -> None: dst = textwrap.dedent("""\ { \t"version": "0.2.0", \t"configurations": [ \t\t// #lldbclient-generated-begin \t\t// #lldbclient-generated-end \t] } """) to_insert = textwrap.dedent("""\ { \t"name": "(lldbclient.py) Attach test", \t"type": "lldb", \t"processCreateCommands": [ \t\t"gdb-remote 123", \t\t"test" \t] }""") self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, to_insert), textwrap.dedent("""\ { \t"version": "0.2.0", \t"configurations": [ \t\t// #lldbclient-generated-begin \t\t{ \t\t\t"name": "(lldbclient.py) Attach test", \t\t\t"type": "lldb", \t\t\t"processCreateCommands": [ \t\t\t\t"gdb-remote 123", \t\t\t\t"test" \t\t\t] \t\t} \t\t// #lldbclient-generated-end \t] } """)) def test_preserve_trailing_whitespace(self) -> None: dst = textwrap.dedent("""\ // #lldbclient-generated-begin \t // #lldbclient-generated-end\t """) to_insert = 'foo' self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst, to_insert), textwrap.dedent("""\ // #lldbclient-generated-begin \t foo // #lldbclient-generated-end\t """)) def test_fail_if_no_begin(self) -> None: dst = textwrap.dedent("""\ // #lldbclient-generated-end""") with self.assertRaisesRegex(ValueError, 'Did not find begin marker line'): gdbclient.insert_commands_into_vscode_config(dst, 'foo') def test_fail_if_no_end(self) -> None: dst = textwrap.dedent("""\ // #lldbclient-generated-begin""") with self.assertRaisesRegex(ValueError, 'Unterminated begin marker at line 1'): gdbclient.insert_commands_into_vscode_config(dst, 'foo') def test_fail_if_begin_has_extra_text(self) -> None: dst = textwrap.dedent("""\ // #lldbclient-generated-begin text // #lldbclient-generated-end""") with self.assertRaisesRegex(ValueError, 'Did not find begin marker line'): gdbclient.insert_commands_into_vscode_config(dst, 'foo') def test_fail_if_end_has_extra_text(self) -> None: dst = textwrap.dedent("""\ // #lldbclient-generated-begin // #lldbclient-generated-end text""") with self.assertRaisesRegex(ValueError, 'Unterminated begin marker at line 1'): gdbclient.insert_commands_into_vscode_config(dst, 'foo') def test_fail_if_begin_end_swapped(self) -> None: dst = textwrap.dedent("""\ // #lldbclient-generated-end // #lldbclient-generated-begin""") with self.assertRaisesRegex(ValueError, 'Unterminated begin marker at line 2'): gdbclient.insert_commands_into_vscode_config(dst, 'foo') if __name__ == '__main__': unittest.main(verbosity=2)