Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
testing.py480 linesDownload Raw Back to click
1import contextlib2import io3import os4import shlex5import shutil6import sys7import tempfile8import typing as t9from types import TracebackType10 11from . import formatting12from . import termui13from . import utils14from ._compat import _find_binary_reader15 16if t.TYPE_CHECKING:17    from .core import BaseCommand18 19 20class EchoingStdin:21    def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None:22        self._input = input23        self._output = output24        self._paused = False25 26    def __getattr__(self, x: str) -> t.Any:27        return getattr(self._input, x)28 29    def _echo(self, rv: bytes) -> bytes:30        if not self._paused:31            self._output.write(rv)32 33        return rv34 35    def read(self, n: int = -1) -> bytes:36        return self._echo(self._input.read(n))37 38    def read1(self, n: int = -1) -> bytes:39        return self._echo(self._input.read1(n))  # type: ignore40 41    def readline(self, n: int = -1) -> bytes:42        return self._echo(self._input.readline(n))43 44    def readlines(self) -> t.List[bytes]:45        return [self._echo(x) for x in self._input.readlines()]46 47    def __iter__(self) -> t.Iterator[bytes]:48        return iter(self._echo(x) for x in self._input)49 50    def __repr__(self) -> str:51        return repr(self._input)52 53 54@contextlib.contextmanager55def _pause_echo(stream: t.Optional[EchoingStdin]) -> t.Iterator[None]:56    if stream is None:57        yield58    else:59        stream._paused = True60        yield61        stream._paused = False62 63 64class _NamedTextIOWrapper(io.TextIOWrapper):65    def __init__(66        self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any67    ) -> None:68        super().__init__(buffer, **kwargs)69        self._name = name70        self._mode = mode71 72    @property73    def name(self) -> str:74        return self._name75 76    @property77    def mode(self) -> str:78        return self._mode79 80 81def make_input_stream(82    input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]], charset: str83) -> t.BinaryIO:84    # Is already an input stream.85    if hasattr(input, "read"):86        rv = _find_binary_reader(t.cast(t.IO[t.Any], input))87 88        if rv is not None:89            return rv90 91        raise TypeError("Could not find binary reader for input stream.")92 93    if input is None:94        input = b""95    elif isinstance(input, str):96        input = input.encode(charset)97 98    return io.BytesIO(input)99 100 101class Result:102    """Holds the captured result of an invoked CLI script."""103 104    def __init__(105        self,106        runner: "CliRunner",107        stdout_bytes: bytes,108        stderr_bytes: t.Optional[bytes],109        return_value: t.Any,110        exit_code: int,111        exception: t.Optional[BaseException],112        exc_info: t.Optional[113            t.Tuple[t.Type[BaseException], BaseException, TracebackType]114        ] = None,115    ):116        #: The runner that created the result117        self.runner = runner118        #: The standard output as bytes.119        self.stdout_bytes = stdout_bytes120        #: The standard error as bytes, or None if not available121        self.stderr_bytes = stderr_bytes122        #: The value returned from the invoked command.123        #:124        #: .. versionadded:: 8.0125        self.return_value = return_value126        #: The exit code as integer.127        self.exit_code = exit_code128        #: The exception that happened if one did.129        self.exception = exception130        #: The traceback131        self.exc_info = exc_info132 133    @property134    def output(self) -> str:135        """The (standard) output as unicode string."""136        return self.stdout137 138    @property139    def stdout(self) -> str:140        """The standard output as unicode string."""141        return self.stdout_bytes.decode(self.runner.charset, "replace").replace(142            "\r\n", "\n"143        )144 145    @property146    def stderr(self) -> str:147        """The standard error as unicode string."""148        if self.stderr_bytes is None:149            raise ValueError("stderr not separately captured")150        return self.stderr_bytes.decode(self.runner.charset, "replace").replace(151            "\r\n", "\n"152        )153 154    def __repr__(self) -> str:155        exc_str = repr(self.exception) if self.exception else "okay"156        return f"<{type(self).__name__} {exc_str}>"157 158 159class CliRunner:160    """The CLI runner provides functionality to invoke a Click command line161    script for unittesting purposes in a isolated environment.  This only162    works in single-threaded systems without any concurrency as it changes the163    global interpreter state.164 165    :param charset: the character set for the input and output data.166    :param env: a dictionary with environment variables for overriding.167    :param echo_stdin: if this is set to `True`, then reading from stdin writes168                       to stdout.  This is useful for showing examples in169                       some circumstances.  Note that regular prompts170                       will automatically echo the input.171    :param mix_stderr: if this is set to `False`, then stdout and stderr are172                       preserved as independent streams.  This is useful for173                       Unix-philosophy apps that have predictable stdout and174                       noisy stderr, such that each may be measured175                       independently176    """177 178    def __init__(179        self,180        charset: str = "utf-8",181        env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,182        echo_stdin: bool = False,183        mix_stderr: bool = True,184    ) -> None:185        self.charset = charset186        self.env: t.Mapping[str, t.Optional[str]] = env or {}187        self.echo_stdin = echo_stdin188        self.mix_stderr = mix_stderr189 190    def get_default_prog_name(self, cli: "BaseCommand") -> str:191        """Given a command object it will return the default program name192        for it.  The default is the `name` attribute or ``"root"`` if not193        set.194        """195        return cli.name or "root"196 197    def make_env(198        self, overrides: t.Optional[t.Mapping[str, t.Optional[str]]] = None199    ) -> t.Mapping[str, t.Optional[str]]:200        """Returns the environment overrides for invoking a script."""201        rv = dict(self.env)202        if overrides:203            rv.update(overrides)204        return rv205 206    @contextlib.contextmanager207    def isolation(208        self,209        input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]] = None,210        env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,211        color: bool = False,212    ) -> t.Iterator[t.Tuple[io.BytesIO, t.Optional[io.BytesIO]]]:213        """A context manager that sets up the isolation for invoking of a214        command line tool.  This sets up stdin with the given input data215        and `os.environ` with the overrides from the given dictionary.216        This also rebinds some internals in Click to be mocked (like the217        prompt functionality).218 219        This is automatically done in the :meth:`invoke` method.220 221        :param input: the input stream to put into sys.stdin.222        :param env: the environment overrides as dictionary.223        :param color: whether the output should contain color codes. The224                      application can still override this explicitly.225 226        .. versionchanged:: 8.0227            ``stderr`` is opened with ``errors="backslashreplace"``228            instead of the default ``"strict"``.229 230        .. versionchanged:: 4.0231            Added the ``color`` parameter.232        """233        bytes_input = make_input_stream(input, self.charset)234        echo_input = None235 236        old_stdin = sys.stdin237        old_stdout = sys.stdout238        old_stderr = sys.stderr239        old_forced_width = formatting.FORCED_WIDTH240        formatting.FORCED_WIDTH = 80241 242        env = self.make_env(env)243 244        bytes_output = io.BytesIO()245 246        if self.echo_stdin:247            bytes_input = echo_input = t.cast(248                t.BinaryIO, EchoingStdin(bytes_input, bytes_output)249            )250 251        sys.stdin = text_input = _NamedTextIOWrapper(252            bytes_input, encoding=self.charset, name="<stdin>", mode="r"253        )254 255        if self.echo_stdin:256            # Force unbuffered reads, otherwise TextIOWrapper reads a257            # large chunk which is echoed early.258            text_input._CHUNK_SIZE = 1  # type: ignore259 260        sys.stdout = _NamedTextIOWrapper(261            bytes_output, encoding=self.charset, name="<stdout>", mode="w"262        )263 264        bytes_error = None265        if self.mix_stderr:266            sys.stderr = sys.stdout267        else:268            bytes_error = io.BytesIO()269            sys.stderr = _NamedTextIOWrapper(270                bytes_error,271                encoding=self.charset,272                name="<stderr>",273                mode="w",274                errors="backslashreplace",275            )276 277        @_pause_echo(echo_input)  # type: ignore278        def visible_input(prompt: t.Optional[str] = None) -> str:279            sys.stdout.write(prompt or "")280            val = text_input.readline().rstrip("\r\n")281            sys.stdout.write(f"{val}\n")282            sys.stdout.flush()283            return val284 285        @_pause_echo(echo_input)  # type: ignore286        def hidden_input(prompt: t.Optional[str] = None) -> str:287            sys.stdout.write(f"{prompt or ''}\n")288            sys.stdout.flush()289            return text_input.readline().rstrip("\r\n")290 291        @_pause_echo(echo_input)  # type: ignore292        def _getchar(echo: bool) -> str:293            char = sys.stdin.read(1)294 295            if echo:296                sys.stdout.write(char)297 298            sys.stdout.flush()299            return char300 301        default_color = color302 303        def should_strip_ansi(304            stream: t.Optional[t.IO[t.Any]] = None, color: t.Optional[bool] = None305        ) -> bool:306            if color is None:307                return not default_color308            return not color309 310        old_visible_prompt_func = termui.visible_prompt_func311        old_hidden_prompt_func = termui.hidden_prompt_func312        old__getchar_func = termui._getchar313        old_should_strip_ansi = utils.should_strip_ansi  # type: ignore314        termui.visible_prompt_func = visible_input315        termui.hidden_prompt_func = hidden_input316        termui._getchar = _getchar317        utils.should_strip_ansi = should_strip_ansi  # type: ignore318 319        old_env = {}320        try:321            for key, value in env.items():322                old_env[key] = os.environ.get(key)323                if value is None:324                    try:325                        del os.environ[key]326                    except Exception:327                        pass328                else:329                    os.environ[key] = value330            yield (bytes_output, bytes_error)331        finally:332            for key, value in old_env.items():333                if value is None:334                    try:335                        del os.environ[key]336                    except Exception:337                        pass338                else:339                    os.environ[key] = value340            sys.stdout = old_stdout341            sys.stderr = old_stderr342            sys.stdin = old_stdin343            termui.visible_prompt_func = old_visible_prompt_func344            termui.hidden_prompt_func = old_hidden_prompt_func345            termui._getchar = old__getchar_func346            utils.should_strip_ansi = old_should_strip_ansi  # type: ignore347            formatting.FORCED_WIDTH = old_forced_width348 349    def invoke(350        self,351        cli: "BaseCommand",352        args: t.Optional[t.Union[str, t.Sequence[str]]] = None,353        input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]] = None,354        env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,355        catch_exceptions: bool = True,356        color: bool = False,357        **extra: t.Any,358    ) -> Result:359        """Invokes a command in an isolated environment.  The arguments are360        forwarded directly to the command line script, the `extra` keyword361        arguments are passed to the :meth:`~clickpkg.Command.main` function of362        the command.363 364        This returns a :class:`Result` object.365 366        :param cli: the command to invoke367        :param args: the arguments to invoke. It may be given as an iterable368                     or a string. When given as string it will be interpreted369                     as a Unix shell command. More details at370                     :func:`shlex.split`.371        :param input: the input data for `sys.stdin`.372        :param env: the environment overrides.373        :param catch_exceptions: Whether to catch any other exceptions than374                                 ``SystemExit``.375        :param extra: the keyword arguments to pass to :meth:`main`.376        :param color: whether the output should contain color codes. The377                      application can still override this explicitly.378 379        .. versionchanged:: 8.0380            The result object has the ``return_value`` attribute with381            the value returned from the invoked command.382 383        .. versionchanged:: 4.0384            Added the ``color`` parameter.385 386        .. versionchanged:: 3.0387            Added the ``catch_exceptions`` parameter.388 389        .. versionchanged:: 3.0390            The result object has the ``exc_info`` attribute with the391            traceback if available.392        """393        exc_info = None394        with self.isolation(input=input, env=env, color=color) as outstreams:395            return_value = None396            exception: t.Optional[BaseException] = None397            exit_code = 0398 399            if isinstance(args, str):400                args = shlex.split(args)401 402            try:403                prog_name = extra.pop("prog_name")404            except KeyError:405                prog_name = self.get_default_prog_name(cli)406 407            try:408                return_value = cli.main(args=args or (), prog_name=prog_name, **extra)409            except SystemExit as e:410                exc_info = sys.exc_info()411                e_code = t.cast(t.Optional[t.Union[int, t.Any]], e.code)412 413                if e_code is None:414                    e_code = 0415 416                if e_code != 0:417                    exception = e418 419                if not isinstance(e_code, int):420                    sys.stdout.write(str(e_code))421                    sys.stdout.write("\n")422                    e_code = 1423 424                exit_code = e_code425 426            except Exception as e:427                if not catch_exceptions:428                    raise429                exception = e430                exit_code = 1431                exc_info = sys.exc_info()432            finally:433                sys.stdout.flush()434                stdout = outstreams[0].getvalue()435                if self.mix_stderr:436                    stderr = None437                else:438                    stderr = outstreams[1].getvalue()  # type: ignore439 440        return Result(441            runner=self,442            stdout_bytes=stdout,443            stderr_bytes=stderr,444            return_value=return_value,445            exit_code=exit_code,446            exception=exception,447            exc_info=exc_info,  # type: ignore448        )449 450    @contextlib.contextmanager451    def isolated_filesystem(452        self, temp_dir: t.Optional[t.Union[str, "os.PathLike[str]"]] = None453    ) -> t.Iterator[str]:454        """A context manager that creates a temporary directory and455        changes the current working directory to it. This isolates tests456        that affect the contents of the CWD to prevent them from457        interfering with each other.458 459        :param temp_dir: Create the temporary directory under this460            directory. If given, the created directory is not removed461            when exiting.462 463        .. versionchanged:: 8.0464            Added the ``temp_dir`` parameter.465        """466        cwd = os.getcwd()467        dt = tempfile.mkdtemp(dir=temp_dir)468        os.chdir(dt)469 470        try:471            yield dt472        finally:473            os.chdir(cwd)474 475            if temp_dir is None:476                try:477                    shutil.rmtree(dt)478                except OSError:  # noqa: B014479                    pass480 
codekingpro/portable-devtools · Team Ai