robotcode-debugger 2.8.0.dev548__tar.gz → 2.8.0.dev631__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/PKG-INFO +3 -3
  2. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/pyproject.toml +2 -2
  3. robotcode_debugger-2.8.0.dev631/src/robotcode/debugger/__version__.py +1 -0
  4. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/launcher/server.py +40 -5
  5. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/listeners.py +24 -13
  6. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/server.py +5 -0
  7. robotcode_debugger-2.8.0.dev548/src/robotcode/debugger/__version__.py +0 -1
  8. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/.gitignore +0 -0
  9. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/README.md +0 -0
  10. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/__init__.py +0 -0
  11. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/cli.py +0 -0
  12. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/dap_types.py +0 -0
  13. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/debugger.py +0 -0
  14. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/default_capabilities.py +0 -0
  15. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/hooks.py +0 -0
  16. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/id_manager.py +0 -0
  17. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/launcher/__init__.py +0 -0
  18. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/launcher/cli.py +0 -0
  19. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/launcher/client.py +0 -0
  20. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/launcher/run.py +0 -0
  21. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/mixins.py +0 -0
  22. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/protocol.py +0 -0
  23. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/py.typed +0 -0
  24. {robotcode_debugger-2.8.0.dev548 → robotcode_debugger-2.8.0.dev631}/src/robotcode/debugger/run.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: robotcode-debugger
3
- Version: 2.8.0-dev.548
3
+ Version: 2.8.0-dev.631
4
4
  Summary: RobotCode Debugger for Robot Framework
5
5
  Project-URL: Homepage, https://robotcode.io
6
6
  Project-URL: Donate, https://opencollective.com/robotcode
@@ -26,8 +26,8 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
26
26
  Classifier: Topic :: Utilities
27
27
  Classifier: Typing :: Typed
28
28
  Requires-Python: >=3.10
29
- Requires-Dist: robotcode-jsonrpc2==2.8.0-dev.548
30
- Requires-Dist: robotcode-runner==2.8.0-dev.548
29
+ Requires-Dist: robotcode-jsonrpc2==2.8.0-dev.631
30
+ Requires-Dist: robotcode-runner==2.8.0-dev.631
31
31
  Requires-Dist: robotframework>=5.0.0
32
32
  Provides-Extra: debugpy
33
33
  Requires-Dist: debugpy; extra == 'debugpy'
@@ -30,8 +30,8 @@ classifiers = [
30
30
  dynamic = ["version"]
31
31
  dependencies = [
32
32
  "robotframework>=5.0.0",
33
- "robotcode-jsonrpc2==2.8.0-dev.548",
34
- "robotcode-runner==2.8.0-dev.548",
33
+ "robotcode-jsonrpc2==2.8.0-dev.631",
34
+ "robotcode-runner==2.8.0-dev.631",
35
35
  ]
36
36
 
37
37
  [project.optional-dependencies]
@@ -0,0 +1 @@
1
+ __version__ = "2.8.0-dev.631"
@@ -6,7 +6,7 @@ import os
6
6
  import shlex
7
7
  import sys
8
8
  from pathlib import Path
9
- from typing import Any, Dict, List, Literal, Optional
9
+ from typing import Any, Dict, List, Literal, Mapping, Optional
10
10
 
11
11
  from robotcode.core.types import ServerMode, TcpParams
12
12
  from robotcode.core.utils.logging import LoggingDescriptor
@@ -70,6 +70,30 @@ def _build_robotcode_run_args(
70
70
  ]
71
71
 
72
72
 
73
+ Console = Literal["robotcodeTerminal", "internalConsole", "integratedTerminal", "externalTerminal"]
74
+
75
+
76
+ def _console_for_client(console: Optional[Console], client_id: Optional[str]) -> Optional[Console]:
77
+ """The console a run uses: RobotCode's own run terminal is part of its VS Code extension, so any other client
78
+ gets the integrated terminal instead."""
79
+ if console == "robotcodeTerminal" and client_id != "vscode":
80
+ return "integratedTerminal"
81
+ return console
82
+
83
+
84
+ def _merged_environment(
85
+ base: Mapping[str, str], env: Mapping[str, Optional[str]], windows: bool
86
+ ) -> Dict[str, Optional[str]]:
87
+ """The environment of a run: ``base`` with the variables of ``env``. On Windows, where a name means the same
88
+ variable in any case, a variable of ``env`` replaces the one of ``base`` whose name differs only in case, and of
89
+ two names in ``env`` that differ only in case, the last one stays."""
90
+ if windows:
91
+ last = {name.upper(): name for name in env}
92
+ env = {name: value for name, value in env.items() if last[name.upper()] == name}
93
+ base = {name: value for name, value in base.items() if name.upper() not in last}
94
+ return {**base, **env}
95
+
96
+
73
97
  class OutputProtocol(asyncio.SubprocessProtocol):
74
98
  def __init__(self, parent: LauncherDebugAdapterProtocol) -> None:
75
99
  super().__init__()
@@ -136,7 +160,7 @@ class LauncherDebugAdapterProtocol(DebugAdapterProtocol):
136
160
  paths: Optional[List[str]] = None,
137
161
  args: Optional[List[str]] = None,
138
162
  env: Optional[Dict[str, Optional[Any]]] = None,
139
- console: Optional[Literal["internalConsole", "integratedTerminal", "externalTerminal"]] = "integratedTerminal",
163
+ console: Optional[Console] = "integratedTerminal",
140
164
  name: Optional[str] = None,
141
165
  no_debug: Optional[bool] = None,
142
166
  robotPythonPath: Optional[List[str]] = None, # noqa: N803
@@ -263,7 +287,19 @@ class LauncherDebugAdapterProtocol(DebugAdapterProtocol):
263
287
 
264
288
  env = {k: ("" if v is None else str(v)) for k, v in env.items()} if env else {}
265
289
 
266
- if console in ["integratedTerminal", "externalTerminal"]:
290
+ console = _console_for_client(
291
+ console, self._initialize_arguments.client_id if self._initialize_arguments is not None else None
292
+ )
293
+
294
+ if console == "robotcodeTerminal":
295
+ # RobotCode's VS Code extension runs it as the process of a terminal of its own, without a shell
296
+ await self.send_event_async(
297
+ Event(
298
+ event="runInRobotCodeTerminal",
299
+ body={"cwd": cwd, "args": [*robotcode_run_args, *run_args], "env": env, "title": name},
300
+ )
301
+ )
302
+ elif console in ["integratedTerminal", "externalTerminal"]:
267
303
  await self.send_request_async(
268
304
  RunInTerminalRequest(
269
305
  arguments=RunInTerminalRequestArguments(
@@ -284,8 +320,7 @@ class LauncherDebugAdapterProtocol(DebugAdapterProtocol):
284
320
  )
285
321
  # time.sleep(1) # Give some time for the terminal to start
286
322
  elif console is None or console == "internalConsole":
287
- run_env: Dict[str, Optional[str]] = dict(os.environ)
288
- run_env.update(env)
323
+ run_env = _merged_environment(os.environ, env, sys.platform == "win32")
289
324
 
290
325
  await asyncio.get_event_loop().subprocess_exec(
291
326
  lambda: OutputProtocol(self), *robotcode_run_args, *run_args, cwd=cwd, env=run_env
@@ -66,6 +66,15 @@ def source_from_attributes(attributes: Dict[str, Any]) -> str:
66
66
  return s or ""
67
67
 
68
68
 
69
+ def without_empty_lineno(attributes: Dict[str, Any]) -> Dict[str, Any]:
70
+ """A copy of listener attributes with an empty line number as None. Up to Robot Framework 6.1, listeners get ""
71
+ for a test or keyword without one, as a custom parser or a pre-run modifier creates them."""
72
+ result = dict(attributes)
73
+ if result.get("lineno") == "":
74
+ result["lineno"] = None
75
+ return result
76
+
77
+
69
78
  class ListenerV2:
70
79
  ROBOT_LISTENER_API_VERSION = "2"
71
80
 
@@ -146,11 +155,11 @@ class ListenerV2:
146
155
  type="test",
147
156
  name=name,
148
157
  id=f"{source_from_attributes(attributes)};{attributes.get('longname', '')};"
149
- f"{attributes.get('lineno', 0)}",
158
+ f"{attributes.get('lineno') or 0}",
150
159
  parent_id=self.suite_id_stack[-1] if self.suite_id_stack else None,
151
- attributes=dict(attributes),
160
+ attributes=without_empty_lineno(attributes),
152
161
  source=source_from_attributes(attributes) or None,
153
- lineno=attributes.get("lineno", None),
162
+ lineno=attributes.get("lineno") or None,
154
163
  ),
155
164
  ),
156
165
  )
@@ -172,13 +181,13 @@ class ListenerV2:
172
181
  type="test",
173
182
  name=name,
174
183
  id=f"{source_from_attributes(attributes)};{attributes.get('longname', '')};"
175
- f"{attributes.get('lineno', 0)}",
184
+ f"{attributes.get('lineno') or 0}",
176
185
  parent_id=self.suite_id_stack[-1] if self.suite_id_stack else None,
177
- attributes=dict(attributes),
186
+ attributes=without_empty_lineno(attributes),
178
187
  failed_keywords=self.failed_keywords,
179
188
  failures=self._failures(),
180
189
  source=source_from_attributes(attributes) or None,
181
- lineno=attributes.get("lineno", None),
190
+ lineno=attributes.get("lineno") or None,
182
191
  ),
183
192
  ),
184
193
  )
@@ -310,7 +319,7 @@ class ListenerV2:
310
319
  if self.failed_keywords is None:
311
320
  self.failed_keywords = []
312
321
 
313
- keyword = {"message": self.last_fail_message, **attributes}
322
+ keyword = {"message": self.last_fail_message, **without_empty_lineno(attributes)}
314
323
  # it is the nearest failed keyword around those that ended since it started and have none yet
315
324
  for inner in self.failed_keywords[: max(0, len(self.failed_keywords) - mark)]:
316
325
  self.failed_keyword_parents.setdefault(id(inner), keyword)
@@ -356,7 +365,7 @@ class ListenerV2:
356
365
  self.failure_logged[-1] = True
357
366
 
358
367
  source = current_frame.source if current_frame else None
359
- line = current_frame.line if current_frame else None
368
+ line = (current_frame.line or None) if current_frame else None
360
369
  column = current_frame.column if current_frame else None
361
370
 
362
371
  item_id = next(
@@ -365,7 +374,7 @@ class ListenerV2:
365
374
  f"{normalized_path(Path(item.source)) if item.source is not None else ''};{item.longname}"
366
375
  if item.type == "SUITE"
367
376
  else f"{normalized_path(Path(item.source)) if item.source is not None else ''};"
368
- f"{item.longname};{item.line}"
377
+ f"{item.longname};{item.line or 0}"
369
378
  )
370
379
  for item in Debugger.instance.full_stack_frames
371
380
  if item.type in ["SUITE", "TEST"]
@@ -405,7 +414,7 @@ class ListenerV2:
405
414
  current_frame = Debugger.instance.full_stack_frames[0] if Debugger.instance.full_stack_frames else None
406
415
 
407
416
  source = current_frame.source if current_frame else None
408
- line = current_frame.line if current_frame else None
417
+ line = (current_frame.line or None) if current_frame else None
409
418
  column = current_frame.column if current_frame else None
410
419
 
411
420
  item_id = next(
@@ -414,7 +423,7 @@ class ListenerV2:
414
423
  f"{normalized_path(Path(item.source)) if item.source is not None else ''};{item.longname}"
415
424
  if item.type == "SUITE"
416
425
  else f"{normalized_path(Path(item.source)) if item.source is not None else ''};"
417
- f"{item.longname};{item.line}"
426
+ f"{item.longname};{item.line or 0}"
418
427
  )
419
428
  for item in Debugger.instance.full_stack_frames
420
429
  if item.type in ["SUITE", "TEST"]
@@ -498,7 +507,9 @@ class ListenerV3:
498
507
  yield from enqueue(s)
499
508
  return
500
509
 
501
- yield (f"{normalized_path(item.source) if item.source is not None else ''};{item.longname};{item.lineno}")
510
+ yield (
511
+ f"{normalized_path(item.source) if item.source is not None else ''};{item.longname};{item.lineno or 0}"
512
+ )
502
513
 
503
514
  if self._event_sended:
504
515
  return
@@ -534,7 +545,7 @@ class ListenerV3:
534
545
  id=f"{normalized_path(Path(result_item.source)) if result_item.source else ''};"
535
546
  f"{result_item.longname or ''}"
536
547
  + (
537
- f";{data_item.lineno if data_item is not None else 0}"
548
+ f";{(data_item.lineno if data_item is not None else None) or 0}"
538
549
  if isinstance(result_item, result.TestCase)
539
550
  else ""
540
551
  ),
@@ -210,6 +210,11 @@ class DebugAdapterServerProtocol(DebugAdapterProtocol):
210
210
  *args: Any,
211
211
  **kwargs: Any,
212
212
  ) -> None:
213
+ if self.exited:
214
+ # The run has ended and the process only waits for the client to disconnect. A signal would end it with
215
+ # the exit code of an interrupt and skip its exit handlers, such as those that close a library's browsers.
216
+ return
217
+
213
218
  Debugger.instance.terminate()
214
219
 
215
220
  if not restart and not self._sigint_signaled:
@@ -1 +0,0 @@
1
- __version__ = "2.8.0-dev.548"