The new argument makes the generator write the VSCode launch.json config
into a file instead of stdout.
The generator uses marker lines to insert the config. This way the user
can control where in the file the launch config is written.
Test: atest gdbclient_test
Test: lldbclient.py --setup-forwarding vscode-lldb \
--vscode-launch-props= \
'{"internalConsoleOptions" : "openOnSessionStart"}' \
--vscode-launch-file=.vscode/launch.json -r test
Change-Id: I92b3f479b5ebcb722933938f52d0f23ff098beac
384 lines
16 KiB
Python
384 lines
16 KiB
Python
#
|
|
# 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)
|