Пример #1
0
    def launch(
        self,
        target,
        debug=True,
        success=True,
        terminal="none",
        args: Optional[Iterable[str]] = None,
    ):
        """
        :param args:
            The arguments to the launch (for instance:
                ["--variable", "my_var:22"]
            )
        """
        from robocorp_ls_core.debug_adapter_core.dap.dap_schema import LaunchRequest
        from robocorp_ls_core.debug_adapter_core.dap.dap_schema import (
            LaunchRequestArguments, )
        from robocorp_ls_core.debug_adapter_core.dap.dap_schema import LaunchResponse
        from robocorp_ls_core.debug_adapter_core.dap.dap_schema import (
            RunInTerminalRequest, )
        from robocorp_ls_core.basic import as_str
        from robocorp_ls_core.debug_adapter_core.dap.dap_schema import InitializedEvent
        from robocorp_ls_core.debug_adapter_core.dap.dap_schema import Response
        from robocorp_ls_core.debug_adapter_core.dap.dap_schema import ProcessEvent

        launch_args = LaunchRequestArguments(__sessionId="some_id",
                                             noDebug=not debug,
                                             target=target,
                                             terminal=terminal)
        if args:
            launch_args.kwargs["args"] = args
        self.write(LaunchRequest(launch_args))

        if terminal == "external":
            run_in_terminal_request = self.read(RunInTerminalRequest)
            env = os.environ.copy()
            for key, val in run_in_terminal_request.arguments.env.to_dict(
            ).items():
                env[as_str(key)] = as_str(val)

            cwd = run_in_terminal_request.arguments.cwd
            popen_args = run_in_terminal_request.arguments.args

            subprocess.Popen(popen_args, cwd=cwd, env=env)

        if success:
            # Initialized is sent just before the launch response (at which
            # point it's possible to send breakpoints).
            self.read(ProcessEvent)
            event = self.read(InitializedEvent)
            assert isinstance(event, InitializedEvent)

        if success:
            launch_response = self.read(LaunchResponse)
        else:
            launch_response = self.read(Response)
        assert launch_response.success == success
Пример #2
0
def not_supported_test_launch_in_external_terminal(
    debugger_api: _DebuggerAPI, rcc_config_location: str
):
    """
    This is an integrated test of the debug adapter. It communicates with it as if it was
    VSCode.
    
    Note: we don't currently support launching in an external terminal because there's
    no easy way to get the pid (it'd be possible to do that by creating a wrapper script
    which would then really launch rcc and then it'd connect back to some port and
    provide the pid of the process which was spawned, but the value gained vs the
    effort to do so seems low, which means we can only run without a terminal for
    now so that we have an easy way of tracking the RCC process pid).
    """
    from robocorp_ls_core.debug_adapter_core.dap.dap_schema import TerminatedEvent
    from robocorp_ls_core.debug_adapter_core.dap.dap_schema import RunInTerminalRequest
    import os
    from robocorp_ls_core.basic import as_str
    from robocorp_ls_core.subprocess_wrapper import subprocess

    debugger_api.initialize(rcc_config_location=rcc_config_location)

    robot = debugger_api.get_dap_case_file("minimal/robot.yaml")
    debugger_api.launch(robot, "task2", debug=False, terminal="external")
    debugger_api.configuration_done()

    run_in_terminal_request = debugger_api.read(RunInTerminalRequest)
    env = os.environ.copy()
    for key, val in run_in_terminal_request.arguments.env.to_dict().items():
        env[as_str(key)] = as_str(val)

    cwd = run_in_terminal_request.arguments.cwd
    popen_args = run_in_terminal_request.arguments.args

    subprocess.Popen(popen_args, cwd=cwd, env=env)

    # i.e.: Big timeout because creating the environment may be slow.
    debugger_api.read(TerminatedEvent, timeout=120)
Пример #3
0
    def _run_rcc(
        self,
        args: List[str],
        timeout: float = 30,
        error_msg: str = "",
        mutex_name=None,
        cwd: Optional[str] = None,
        log_errors=True,
        stderr=Sentinel.SENTINEL,
    ) -> ActionResult[str]:
        """
        Returns an ActionResult where the result is the stdout of the executed command.
        
        :param log_errors:
            If false, errors won't be logged (i.e.: should be false when errors
            are expected).
        """
        from robocorp_ls_core.basic import build_subprocess_kwargs
        from subprocess import check_output
        from robocorp_ls_core.subprocess_wrapper import subprocess

        if stderr is Sentinel.SENTINEL:
            stderr = subprocess.PIPE

        rcc_location = self.get_rcc_location()

        env = os.environ.copy()
        env.pop("PYTHONPATH", "")
        env.pop("PYTHONHOME", "")
        env.pop("VIRTUAL_ENV", "")
        env["PYTHONIOENCODING"] = "utf-8"
        env["PYTHONUNBUFFERED"] = "1"

        robocorp_home = self._get_robocorp_home()
        if robocorp_home:
            env["ROBOCORP_HOME"] = robocorp_home

        kwargs: dict = build_subprocess_kwargs(cwd, env, stderr=stderr)
        args = [rcc_location] + args + ["--controller", "RobocorpCode"]
        cmdline = " ".join([str(x) for x in args])

        try:
            if mutex_name:
                from robocorp_ls_core.system_mutex import timed_acquire_mutex
            else:
                timed_acquire_mutex = NULL
            with timed_acquire_mutex(mutex_name, timeout=15):
                boutput: bytes = check_output(args, timeout=timeout, **kwargs)

        except CalledProcessError as e:
            stdout = as_str(e.stdout)
            stderr = as_str(e.stderr)
            msg = f"Error running: {cmdline}.\nROBOCORP_HOME: {robocorp_home}\n\nStdout: {stdout}\nStderr: {stderr}"
            if log_errors:
                log.exception(msg)
            if not error_msg:
                return ActionResult(success=False, message=msg)
            else:
                additional_info = [error_msg]
                if stdout or stderr:
                    if stdout and stderr:
                        additional_info.append("\nDetails: ")
                        additional_info.append("\nStdout")
                        additional_info.append(stdout)
                        additional_info.append("\nStderr")
                        additional_info.append(stderr)

                    elif stdout:
                        additional_info.append("\nDetails: ")
                        additional_info.append(stdout)

                    elif stderr:
                        additional_info.append("\nDetails: ")
                        additional_info.append(stderr)

                return ActionResult(success=False,
                                    message="".join(additional_info))

        except Exception:
            msg = f"Error running: {args}"
            log.exception(msg)
            return ActionResult(success=False, message=msg)

        output = boutput.decode("utf-8", "replace")

        log.debug("Output from: %s:\n%s", cmdline, output)
        return ActionResult(success=True, message=None, result=output)
Пример #4
0
    def _run_rcc(
        self,
        args: List[str],
        timeout: float = 30,
        expect_ok=True,
        error_msg: str = "",
        mutex_name=None,
        cwd: Optional[str] = None,
    ) -> ActionResult[str]:
        """
        Returns an ActionResult where the result is the stdout of the executed command.
        """
        from robocorp_ls_core.basic import build_subprocess_kwargs
        from subprocess import check_output
        from robocorp_ls_core.subprocess_wrapper import subprocess

        rcc_location = self.get_rcc_location()

        env = os.environ.copy()
        env.pop("PYTHONPATH", "")
        env.pop("PYTHONHOME", "")
        env.pop("VIRTUAL_ENV", "")
        env["PYTHONIOENCODING"] = "utf-8"
        env["PYTHONUNBUFFERED"] = "1"

        kwargs: dict = build_subprocess_kwargs(cwd, env, stderr=subprocess.PIPE)
        args = [rcc_location] + args
        cmdline = " ".join([str(x) for x in args])

        try:
            if mutex_name:
                from robocorp_ls_core.system_mutex import timed_acquire_mutex
            else:
                timed_acquire_mutex = NULL
            with timed_acquire_mutex(mutex_name, timeout=15):
                boutput: bytes = check_output(args, timeout=timeout, **kwargs)

        except CalledProcessError as e:
            stdout = as_str(e.stdout)
            stderr = as_str(e.stderr)
            msg = f"Error running: {cmdline}.\nStdout: {stdout}\nStderr: {stderr}"
            log.exception(msg)
            if not error_msg:
                return ActionResult(success=False, message=msg)
            else:
                additional_info = [error_msg]
                if stdout or stderr:
                    if stdout and stderr:
                        additional_info.append("\nDetails: ")
                        additional_info.append("\nStdout")
                        additional_info.append(stdout)
                        additional_info.append("\nStderr")
                        additional_info.append(stderr)

                    elif stdout:
                        additional_info.append("\nDetails: ")
                        additional_info.append(stdout)

                    elif stderr:
                        additional_info.append("\nDetails: ")
                        additional_info.append(stderr)

                return ActionResult(success=False, message="".join(additional_info))

        except Exception:
            msg = f"Error running: {args}"
            log.exception(msg)
            return ActionResult(success=False, message=msg)

        output = boutput.decode("utf-8", "replace")

        log.debug(f"Output from: {cmdline}:\n{output}")
        if expect_ok:
            if "OK." in output:
                return ActionResult(success=True, message=None, result=output)
        else:
            return ActionResult(success=True, message=None, result=output)

        return ActionResult(
            success=False, message="OK. not found in message", result=output
        )
Пример #5
0
    def __init__(self, request, launch_response, debug_adapter_comm):
        """
        :param LaunchRequest request:
        :param LaunchResponse launch_response:
        """
        import weakref
        from robotframework_debug_adapter.constants import VALID_TERMINAL_OPTIONS
        from robotframework_debug_adapter.constants import TERMINAL_NONE
        from robocorp_ls_core.basic import as_str
        import robocorp_ls_core

        self._weak_debug_adapter_comm = weakref.ref(debug_adapter_comm)
        self._valid = True
        self._cmdline = []
        self._popen = None
        self._launch_response = launch_response
        self._next_seq = partial(next, itertools.count(0))
        self._track_process_pid = None
        self._sent_terminated = threading.Event()

        self._debug_adapter_robot_target_comm = _DebugAdapterRobotTargetComm(
            debug_adapter_comm)

        def mark_invalid(message):
            launch_response.success = False
            launch_response.message = message
            self._valid = False

        import sys

        target = request.arguments.kwargs.get("target")
        self._cwd = request.arguments.kwargs.get("cwd")
        self._terminal = request.arguments.kwargs.get("terminal",
                                                      TERMINAL_NONE)
        args = request.arguments.kwargs.get("args") or []
        args = [str(arg) for arg in args]

        env = {}
        request_env = request.arguments.kwargs.get("env")
        if isinstance(request_env, dict) and request_env:
            env.update(request_env)

        pythonpath = env.get("PYTHONPATH", "")
        pythonpath += (
            os.pathsep + os.path.dirname(os.path.dirname(__file__)) +
            os.pathsep +
            os.path.dirname(os.path.dirname(robocorp_ls_core.__file__)))
        env["PYTHONPATH"] = pythonpath

        for key, value in os.environ.items():
            if "ROBOTFRAMEWORK" in key:
                env[key] = value

        env = dict(
            ((as_str(key), as_str(value)) for (key, value) in env.items()))

        self._env = env

        self._run_in_debug_mode = not request.arguments.noDebug

        if self._terminal not in VALID_TERMINAL_OPTIONS:
            return mark_invalid(
                "Invalid terminal option: %s (must be one of: %s)" %
                (self._terminal, VALID_TERMINAL_OPTIONS))

        try:
            if self._cwd is not None:
                if not os.path.exists(self._cwd):
                    return mark_invalid("cwd specified does not exist: %s" %
                                        (self._cwd, ))
        except:
            log.exception("Error")
            return mark_invalid("Error checking if cwd (%s) exists." %
                                (self._cwd, ))

        try:
            if target is None:
                return mark_invalid("target not provided in launch.")

            if not os.path.exists(target):
                return mark_invalid("File: %s does not exist." % (target, ))
        except:
            log.exception("Error")
            return mark_invalid("Error checking if target (%s) exists." %
                                (target, ))

        if DEBUG:
            log.debug("Run in debug mode: %s\n" % (self._run_in_debug_mode, ))

        port = self._debug_adapter_robot_target_comm.start_listening()

        try:
            run_robot_py = os.path.join(os.path.dirname(__file__),
                                        "run_robot__main__.py")
            if not os.path.exists(run_robot_py):
                return mark_invalid("File: %s does not exist." %
                                    (run_robot_py, ))
        except:
            log.exception("Error")
            return mark_invalid(
                "Error checking if run_robot__main__.py exists.")

        else:
            # Note: target must be the last parameter.
            cmdline = ([
                sys.executable,
                "-u",
                run_robot_py,
                "--port",
                str(port),
                "--debug" if self._run_in_debug_mode else "--no-debug",
            ] + args + [target])

        self._cmdline = cmdline
Пример #6
0
    def __init__(
        self,
        request,
        launch_response,
        debug_adapter_comm,
        rcc_config_location: Optional[str],
    ):
        """
        :param LaunchRequest request:
        :param LaunchResponse launch_response:
        """
        import weakref
        from robocorp_ls_core.basic import as_str
        from robocorp_code_debug_adapter.constants import VALID_TERMINAL_OPTIONS
        from robocorp_code_debug_adapter.constants import TERMINAL_NONE
        from robocorp_ls_core.robotframework_log import get_log_level
        from robocorp_code.rcc import Rcc
        from robocorp_ls_core.config import Config
        from pathlib import Path

        self._weak_debug_adapter_comm = weakref.ref(debug_adapter_comm)
        self._valid = True
        self._cmdline = []
        self._popen = None
        self._launch_response = launch_response
        self._next_seq = partial(next, itertools.count(0))
        self._track_process_pid = None
        self._sent_terminated = threading.Event()
        self._rcc_config_location = rcc_config_location

        def mark_invalid(message):
            launch_response.success = False
            launch_response.message = message
            self._valid = False

        robot_yaml = request.arguments.kwargs.get("robot")
        self._terminal = request.arguments.kwargs.get("terminal",
                                                      TERMINAL_NONE)
        if self._terminal != TERMINAL_NONE:
            # We don't currently support the integrated terminal because we don't
            # have an easy way to get the launched process pid in this way.
            return mark_invalid(
                f"Only 'terminal=none' is supported. Found terminal: {self._terminal}"
            )

        task_name = request.arguments.kwargs.get("task", "")
        args = request.arguments.kwargs.get("args") or []
        if not isinstance(args, list):
            args = [args]
        args = [str(arg) for arg in args]

        env = {}
        request_env = request.arguments.kwargs.get("env")
        if isinstance(request_env, dict) and request_env:
            env.update(request_env)

        env = dict(
            ((as_str(key), as_str(value)) for (key, value) in env.items()))

        self._env = env

        self._run_in_debug_mode = not request.arguments.noDebug

        if self._terminal not in VALID_TERMINAL_OPTIONS:
            return mark_invalid(
                "Invalid terminal option: %s (must be one of: %s)" %
                (self._terminal, VALID_TERMINAL_OPTIONS))

        try:
            if robot_yaml is None:
                return mark_invalid("robot not provided in launch.")

            if not os.path.exists(robot_yaml):
                return mark_invalid("File: %s does not exist." %
                                    (robot_yaml, ))
        except:
            log.exception("Error")
            return mark_invalid("Error checking if robot (%s) exists." %
                                (robot_yaml, ))

        self._cwd = os.path.dirname(robot_yaml)
        try:
            if self._cwd is not None:
                if not os.path.exists(self._cwd):
                    return mark_invalid("cwd specified does not exist: %s" %
                                        (self._cwd, ))
        except:
            log.exception("Error")
            return mark_invalid("Error checking if cwd (%s) exists." %
                                (self._cwd, ))

        if get_log_level() > 1:
            log.debug("Run in debug mode: %s\n" % (self._run_in_debug_mode, ))

        try:
            config = Config()
            config_provider = _DefaultConfigurationProvider(config)
            rcc = Rcc(config_provider=config_provider)
            rcc_executable = rcc.get_rcc_location()

            if not os.path.exists(rcc_executable):
                return mark_invalid(f"Expected: {rcc_executable} to exist.")
        except:
            log.exception("Error")
            return mark_invalid("Error getting rcc executable location.")

        else:
            task_args = []
            if task_name:
                task_args.append("--task")
                task_args.append(task_name)

            cmdline = ([rcc_executable, "task", "run", "--robot", robot_yaml] +
                       task_args + args)
            if self._rcc_config_location:
                cmdline.append("--config")
                cmdline.append(self._rcc_config_location)

            env_json_path = Path(robot_yaml).parent / "devdata" / "env.json"
            if env_json_path.exists():
                cmdline.append("-e")
                cmdline.append(str(env_json_path))

        self._cmdline = cmdline