diff --git a/CHANGES b/CHANGES index 17416adea74..738d8c0b2ee 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,11 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force ### CI - Move CodeQL from advanced configuration file to GitHub's default +- Add pydocstyle rule to ruff (#891) + +### Documentation + +- Add docstrings to functions, methods, classes, and packages (#891) ## tmuxp 1.32.1 (2023-11-23) diff --git a/conftest.py b/conftest.py index 024ccbf9d96..c8fa6dfb700 100644 --- a/conftest.py +++ b/conftest.py @@ -1,8 +1,13 @@ -"""Conftest.py (root-level) +"""Conftest.py (root-level). We keep this in root pytest fixtures in pytest's doctest plugin to be available, as well -as avoiding conftest.py from being included in the wheel. +as avoiding conftest.py from being included in the wheel, in addition to pytest_plugin +for pytester only being available via the root directory. + +See "pytest_plugins in non-top-level conftest files" in +https://docs.pytest.org/en/stable/deprecations.html """ + import logging import os import pathlib @@ -26,7 +31,7 @@ @pytest.mark.skipif(not USING_ZSH, reason="Using ZSH") @pytest.fixture(autouse=USING_ZSH, scope="session") def zshrc(user_path: pathlib.Path) -> pathlib.Path: - """This quiets ZSH default message. + """Quiets ZSH default message. Needs a startup file .zshenv, .zprofile, .zshrc, .zlogin. """ @@ -37,11 +42,13 @@ def zshrc(user_path: pathlib.Path) -> pathlib.Path: @pytest.fixture(autouse=True) def home_path_default(monkeypatch: pytest.MonkeyPatch, user_path: pathlib.Path) -> None: + """Set HOME to user_path (random, temporary directory).""" monkeypatch.setenv("HOME", str(user_path)) @pytest.fixture def tmuxp_configdir(user_path: pathlib.Path) -> pathlib.Path: + """Ensure and return tmuxp config directory.""" xdg_config_dir = user_path / ".config" xdg_config_dir.mkdir(exist_ok=True) @@ -54,12 +61,14 @@ def tmuxp_configdir(user_path: pathlib.Path) -> pathlib.Path: def tmuxp_configdir_default( monkeypatch: pytest.MonkeyPatch, tmuxp_configdir: pathlib.Path ) -> None: + """Set tmuxp configuration directory for ``TMUXP_CONFIGDIR``.""" monkeypatch.setenv("TMUXP_CONFIGDIR", str(tmuxp_configdir)) assert get_workspace_dir() == str(tmuxp_configdir) @pytest.fixture(scope="function") def monkeypatch_plugin_test_packages(monkeypatch: pytest.MonkeyPatch) -> None: + """Monkeypatch tmuxp plugin fixtures to python path.""" paths = [ "tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/", "tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/", @@ -74,12 +83,14 @@ def monkeypatch_plugin_test_packages(monkeypatch: pytest.MonkeyPatch) -> None: @pytest.fixture(scope="function") def session_params(session_params: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + """Terminal-friendly tmuxp session_params for dimensions.""" session_params.update({"x": 800, "y": 600}) return session_params @pytest.fixture(scope="function") def socket_name(request: pytest.FixtureRequest) -> str: + """Random socket name for tmuxp.""" return "tmuxp_test%s" % next(namer) @@ -89,6 +100,7 @@ def add_doctest_fixtures( doctest_namespace: t.Dict[str, t.Any], tmp_path: pathlib.Path, ) -> None: + """Harness pytest fixtures to doctests namespace.""" if isinstance(request._pyfuncitem, DoctestItem) and shutil.which("tmux"): doctest_namespace["server"] = request.getfixturevalue("server") session: "Session" = request.getfixturevalue("session") diff --git a/docs/_ext/aafig.py b/docs/_ext/aafig.py index d3885405e2d..b0f0b284cc1 100644 --- a/docs/_ext/aafig.py +++ b/docs/_ext/aafig.py @@ -1,14 +1,15 @@ -""" - sphinxcontrib.aafig - ~~~~~~~~~~~~~~~~~~~ +"""aafig plugin for sphinx. + +sphinxcontrib.aafig. +~~~~~~~~~~~~~~~~~~~ - Allow embedded ASCII art to be rendered as nice looking images - using the aafigure reStructuredText extension. +Allow embedded ASCII art to be rendered as nice looking images +using the aafigure reStructuredText extension. - See the README file for details. +See the README file for details. - :author: Leandro Lucarella - :license: BOLA, see LICENSE for details +:author: Leandro Lucarella +:license: BOLA, see LICENSE for details """ import logging import posixpath @@ -61,9 +62,7 @@ class AafigError(SphinxError): class AafigDirective(images.Image): # type:ignore - """ - Directive to insert an ASCII art figure to be rendered by aafigure. - """ + """Directive to insert an ASCII art figure to be rendered by aafigure.""" has_content = True required_arguments = 0 @@ -155,10 +154,7 @@ def __init__(self, *args: object, **kwargs: object) -> None: def render_aafigure( app: "Sphinx", text: str, options: t.Dict[str, str] ) -> t.Tuple[str, str, t.Optional[str], t.Optional[str]]: - """ - Render an ASCII art figure into the requested format output file. - """ - + """Render an ASCII art figure into the requested format output file.""" if aafigure is None: raise AafigureNotInstalled() diff --git a/docs/conf.py b/docs/conf.py index afe2de2a710..bddd5b1f8ec 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,5 @@ # flake8: NOQA: E501 +"""Sphinx documentation configuration for tmuxp.""" import contextlib import inspect import pathlib @@ -142,7 +143,7 @@ def linkcode_resolve(domain: str, info: t.Dict[str, str]) -> t.Union[None, str]: """ - Determine the URL corresponding to Python object + Determine the URL corresponding to Python object. Notes ----- @@ -212,7 +213,7 @@ def linkcode_resolve(domain: str, info: t.Dict[str, str]) -> t.Union[None, str]: def remove_tabs_js(app: "Sphinx", exc: Exception) -> None: - # Fix for sphinx-inline-tabs#18 + """Fix for sphinx-inline-tabs#18.""" if app.builder.format == "html" and not exc: tabs_js = pathlib.Path(app.builder.outdir) / "_static" / "tabs.js" with contextlib.suppress(FileNotFoundError): @@ -220,4 +221,5 @@ def remove_tabs_js(app: "Sphinx", exc: Exception) -> None: def setup(app: "Sphinx") -> None: + """Sphinx setup hook.""" app.connect("build-finished", remove_tabs_js) diff --git a/pyproject.toml b/pyproject.toml index 9ebfde9c762..1c4536cd4b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -146,6 +146,7 @@ select = [ "TRY", # Trycertatops "PERF", # Perflint "RUF", # Ruff-specific rules + "D", # pydocstyle ] [tool.ruff.isort] @@ -154,6 +155,9 @@ known-first-party = [ ] combine-as-imports = true +[tool.ruff.pydocstyle] +convention = "numpy" + [tool.ruff.per-file-ignores] "*/__init__.py" = ["F401"] "src/tmuxp/workspace/finders.py" = ["PTH"] diff --git a/src/tmuxp/__about__.py b/src/tmuxp/__about__.py index 2b798fbdf6d..2b7f6a77e59 100644 --- a/src/tmuxp/__about__.py +++ b/src/tmuxp/__about__.py @@ -1,3 +1,4 @@ +"""Metadata for tmuxp package.""" __title__ = "tmuxp" __package_name__ = "tmuxp" __version__ = "1.32.1" diff --git a/src/tmuxp/_types.py b/src/tmuxp/_types.py index 79eac5c9c2a..7e3eb7d7229 100644 --- a/src/tmuxp/_types.py +++ b/src/tmuxp/_types.py @@ -1,4 +1,4 @@ -"""Internal, :const:`typing.TYPE_CHECKING` guarded :term:`type annotations ` +"""Internal, :const:`typing.TYPE_CHECKING` guarded :term:`typings `. These are _not_ to be imported at runtime as `typing_extensions` is not bundled with tmuxp. Usage example: diff --git a/src/tmuxp/cli/__init__.py b/src/tmuxp/cli/__init__.py index d68246db1f2..0d1e6d2d388 100644 --- a/src/tmuxp/cli/__init__.py +++ b/src/tmuxp/cli/__init__.py @@ -1,9 +1,4 @@ -"""CLI utilities for tmuxp. - -tmuxp.cli -~~~~~~~~~ - -""" +"""CLI utilities for tmuxp.""" import argparse import logging import os @@ -45,6 +40,7 @@ def create_parser() -> argparse.ArgumentParser: + """Create CLI :class:`argparse.ArgumentParser` for tmuxp.""" parser = argparse.ArgumentParser(prog="tmuxp") parser.add_argument( "--version", @@ -97,6 +93,8 @@ def create_parser() -> argparse.ArgumentParser: class CLINamespace(argparse.Namespace): + """Typed :class:`argparse.Namespace` for tmuxp root-level CLI.""" + log_level: "CLIVerbosity" subparser_name: "CLISubparserName" import_subparser_name: t.Optional["CLIImportSubparserName"] @@ -111,8 +109,8 @@ def cli(_args: t.Optional[t.List[str]] = None) -> None: Pass the "--help" argument to any command to see detailed help. See detailed documentation and examples at: - http://tmuxp.git-pull.com/""" - + http://tmuxp.git-pull.com/ + """ try: has_minimum_version() except TmuxCommandNotFound: @@ -186,6 +184,5 @@ def startup(config_dir: pathlib.Path) -> None: ---------- str : get_workspace_dir(): Config directory to search """ - if not os.path.exists(config_dir): os.makedirs(config_dir) diff --git a/src/tmuxp/cli/convert.py b/src/tmuxp/cli/convert.py index e0e2a2c03a1..88d995d67d6 100644 --- a/src/tmuxp/cli/convert.py +++ b/src/tmuxp/cli/convert.py @@ -1,3 +1,4 @@ +"""CLI for ``tmuxp convert`` subcommand.""" import argparse import os import pathlib @@ -16,6 +17,7 @@ def create_convert_subparser( parser: argparse.ArgumentParser, ) -> argparse.ArgumentParser: + """Augment :class:`argparse.ArgumentParser` with ``convert`` subcommand.""" workspace_file = parser.add_argument( dest="workspace_file", type=str, @@ -40,6 +42,8 @@ def create_convert_subparser( class ConvertUnknownFileType(exc.TmuxpException): + """Raise if tmuxp convert encounters an unknown filetype.""" + def __init__(self, ext: str, *args: object, **kwargs: object) -> None: return super().__init__( f"Unknown filetype: {ext} (valid: [.json, .yaml, .yml])" @@ -51,7 +55,7 @@ def command_convert( answer_yes: bool, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: - """Convert a tmuxp config between JSON and YAML.""" + """Entrypoint for ``tmuxp convert`` convert a tmuxp config between JSON and YAML.""" workspace_file = find_workspace_file( workspace_file, workspace_dir=get_workspace_dir() ) diff --git a/src/tmuxp/cli/debug_info.py b/src/tmuxp/cli/debug_info.py index df6a65e19dc..d6b06618bb8 100644 --- a/src/tmuxp/cli/debug_info.py +++ b/src/tmuxp/cli/debug_info.py @@ -1,3 +1,4 @@ +"""CLI for ``tmuxp debug-info`` subcommand.""" import argparse import os import pathlib @@ -19,32 +20,25 @@ def create_debug_info_subparser( parser: argparse.ArgumentParser, ) -> argparse.ArgumentParser: + """Augment :class:`argparse.ArgumentParser` with ``debug-info`` subcommand.""" return parser def command_debug_info( parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: - """ - Print debug info to submit with Issues. - """ + """Entrypoint for ``tmuxp debug-info`` to print debug info to submit with issues.""" def prepend_tab(strings: t.List[str]) -> t.List[str]: - """ - Prepend tab to strings in list. - """ + """Prepend tab to strings in list.""" return ["\t%s" % x for x in strings] def output_break() -> str: - """ - Generate output break. - """ + """Generate output break.""" return "-" * 25 def format_tmux_resp(std_resp: tmux_cmd) -> str: - """ - Format tmux command response for tmuxp stdout. - """ + """Format tmux command response for tmuxp stdout.""" return "\n".join( [ "\n".join(prepend_tab(std_resp.stdout)), diff --git a/src/tmuxp/cli/edit.py b/src/tmuxp/cli/edit.py index 31b263dd362..f18a63bef3f 100644 --- a/src/tmuxp/cli/edit.py +++ b/src/tmuxp/cli/edit.py @@ -1,3 +1,4 @@ +"""CLI for ``tmuxp edit`` subcommand.""" import argparse import os import pathlib @@ -10,6 +11,7 @@ def create_edit_subparser( parser: argparse.ArgumentParser, ) -> argparse.ArgumentParser: + """Augment :class:`argparse.ArgumentParser` with ``edit`` subcommand.""" parser.add_argument( dest="workspace_file", metavar="workspace-file", @@ -23,6 +25,7 @@ def command_edit( workspace_file: t.Union[str, pathlib.Path], parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: + """Entrypoint for ``tmuxp edit``, open tmuxp workspace file in system editor.""" workspace_file = find_workspace_file(workspace_file) sys_editor = os.environ.get("EDITOR", "vim") diff --git a/src/tmuxp/cli/freeze.py b/src/tmuxp/cli/freeze.py index 88a2f9751bf..f82dbb10329 100644 --- a/src/tmuxp/cli/freeze.py +++ b/src/tmuxp/cli/freeze.py @@ -1,3 +1,4 @@ +"""CLI for ``tmuxp freeze`` subcommand.""" import argparse import os import pathlib @@ -21,6 +22,8 @@ class CLIFreezeNamespace(argparse.Namespace): + """Typed :class:`argparse.Namespace` for tmuxp freeze command.""" + session_name: str socket_name: t.Optional[str] socket_path: t.Optional[str] @@ -34,6 +37,7 @@ class CLIFreezeNamespace(argparse.Namespace): def create_freeze_subparser( parser: argparse.ArgumentParser, ) -> argparse.ArgumentParser: + """Augment :class:`argparse.ArgumentParser` with ``freeze`` subcommand.""" parser.add_argument( dest="session_name", metavar="session-name", @@ -87,10 +91,11 @@ def command_freeze( args: CLIFreezeNamespace, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: - """Snapshot a tmux session into a tmuxp workspace. + """Entrypoint for ``tmuxp freeze``, snapshot a tmux session into a tmuxp workspace. - If SESSION_NAME is provided, snapshot that session. Otherwise, use the - current session.""" + If SESSION_NAME is provided, snapshot that session. Otherwise, use the current + session. + """ server = Server(socket_name=args.socket_name, socket_path=args.socket_path) try: diff --git a/src/tmuxp/cli/import_config.py b/src/tmuxp/cli/import_config.py index 9048331fa9c..70f4bf71f5e 100644 --- a/src/tmuxp/cli/import_config.py +++ b/src/tmuxp/cli/import_config.py @@ -1,3 +1,4 @@ +"""CLI for ``tmuxp shell`` subcommand.""" import argparse import os import pathlib @@ -12,8 +13,7 @@ def get_tmuxinator_dir() -> pathlib.Path: - """ - Return tmuxinator configuration directory. + """Return tmuxinator configuration directory. Checks for ``TMUXINATOR_CONFIG`` environmental variable. @@ -33,8 +33,7 @@ def get_tmuxinator_dir() -> pathlib.Path: def get_teamocil_dir() -> pathlib.Path: - """ - Return teamocil configuration directory. + """Return teamocil configuration directory. Returns ------- @@ -66,6 +65,7 @@ def command_import( def create_import_subparser( parser: argparse.ArgumentParser, ) -> argparse.ArgumentParser: + """Augment :class:`argparse.ArgumentParser` with ``import`` subparser.""" importsubparser = parser.add_subparsers( title="commands", description="valid commands", help="additional help" ) @@ -117,7 +117,10 @@ def create_import_subparser( class ImportConfigFn(t.Protocol): + """Typing for import configuration callback function.""" + def __call__(self, workspace_dict: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + """Execute tmuxp import function.""" ... @@ -126,6 +129,7 @@ def import_config( importfunc: ImportConfigFn, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: + """Import a configuration from a workspace_file.""" existing_workspace_file = ConfigReader._from_file(pathlib.Path(workspace_file)) cfg_reader = ConfigReader(importfunc(existing_workspace_file)) @@ -175,8 +179,11 @@ def command_import_tmuxinator( workspace_file: str, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: - """Convert a tmuxinator config from workspace_file to tmuxp format and import - it into tmuxp.""" + """Entrypoint for ``tmuxp import tmuxinator`` subcommand. + + Converts a tmuxinator config from workspace_file to tmuxp format and import + it into tmuxp. + """ workspace_file = find_workspace_file( workspace_file, workspace_dir=get_tmuxinator_dir() ) @@ -187,8 +194,11 @@ def command_import_teamocil( workspace_file: str, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: - """Convert a teamocil config from workspace_file to tmuxp format and import - it into tmuxp.""" + """Entrypoint for ``tmuxp import teamocil`` subcommand. + + Convert a teamocil config from workspace_file to tmuxp format and import + it into tmuxp. + """ workspace_file = find_workspace_file( workspace_file, workspace_dir=get_teamocil_dir() ) diff --git a/src/tmuxp/cli/load.py b/src/tmuxp/cli/load.py index e965299f90f..4311e87efc4 100644 --- a/src/tmuxp/cli/load.py +++ b/src/tmuxp/cli/load.py @@ -1,9 +1,4 @@ -"""Command line tool for managing tmuxp workspaces. - -tmuxp.cli.load -~~~~~~~~~~~~~~ - -""" +"""CLI for ``tmuxp load`` subcommand.""" import argparse import importlib import logging @@ -31,11 +26,15 @@ CLIColorsLiteral: TypeAlias = t.Literal[56, 88] class OptionOverrides(TypedDict): + """Optional argument overrides for tmuxp load.""" + detached: NotRequired[bool] new_session_name: NotRequired[t.Optional[str]] class CLILoadNamespace(argparse.Namespace): + """Typed :class:`argparse.Namespace` for tmuxp load command.""" + workspace_files: t.List[str] socket_name: t.Optional[str] socket_path: t.Optional[str] @@ -50,8 +49,8 @@ class CLILoadNamespace(argparse.Namespace): def set_layout_hook(session: Session, hook_name: str) -> None: """Set layout hooks to normalize layout. - References: - + References + ---------- - tmuxp issue: https://github.com/tmux-python/tmuxp/issues/309 - tmux issue: https://github.com/tmux/tmux/issues/1106 @@ -105,9 +104,7 @@ def set_layout_hook(session: Session, hook_name: str) -> None: def load_plugins(session_config: t.Dict[str, t.Any]) -> t.List[t.Any]: - """ - Load and return plugins in workspace - """ + """Load and return plugins in workspace.""" plugins = [] if "plugins" in session_config: for plugin in session_config["plugins"]: @@ -155,7 +152,7 @@ def load_plugins(session_config: t.Dict[str, t.Any]) -> t.List[t.Any]: def _reattach(builder: WorkspaceBuilder) -> None: """ - Reattach session (depending on env being inside tmux already or not) + Reattach session (depending on env being inside tmux already or not). Parameters ---------- @@ -185,7 +182,7 @@ def _reattach(builder: WorkspaceBuilder) -> None: def _load_attached(builder: WorkspaceBuilder, detached: bool) -> None: """ - Load workspace in new session + Load workspace in new session. Parameters ---------- @@ -219,7 +216,7 @@ def _load_attached(builder: WorkspaceBuilder, detached: bool) -> None: def _load_detached(builder: WorkspaceBuilder) -> None: """ - Load workspace in new session but don't attach + Load workspace in new session but don't attach. Parameters ---------- @@ -238,7 +235,7 @@ def _load_detached(builder: WorkspaceBuilder) -> None: def _load_append_windows_to_current_session(builder: WorkspaceBuilder) -> None: """ - Load workspace as new windows in current session + Load workspace as new windows in current session. Parameters ---------- @@ -253,8 +250,7 @@ def _load_append_windows_to_current_session(builder: WorkspaceBuilder) -> None: def _setup_plugins(builder: WorkspaceBuilder) -> Session: - """ - Runs after before_script + """Execute hooks for plugins running after ``before_script``. Parameters ---------- @@ -278,8 +274,7 @@ def load_workspace( answer_yes: bool = False, append: bool = False, ) -> t.Optional[Session]: - """ - Load a tmux "workspace" session via tmuxp file. + """Entrypoint for ``tmuxp load``, load a tmuxp "workspace" session via config file. Parameters ---------- @@ -305,7 +300,6 @@ def load_workspace( Notes ----- - tmuxp will check and load a workspace file. The file will use ConfigReader to load a JSON/YAML into a :py:obj:`dict`. Then :func:`loader.expand` and :func:`loader.trickle` will be used to expand any shorthands, template @@ -489,6 +483,7 @@ def load_workspace( def create_load_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: + """Augment :class:`argparse.ArgumentParser` with ``load`` subcommand.""" workspace_files = parser.add_argument( "workspace_files", nargs="+", diff --git a/src/tmuxp/cli/ls.py b/src/tmuxp/cli/ls.py index e0447edf32e..d76577741f4 100644 --- a/src/tmuxp/cli/ls.py +++ b/src/tmuxp/cli/ls.py @@ -1,3 +1,4 @@ +"""CLI for ``tmuxp ls`` subcommand.""" import argparse import os import typing as t @@ -9,12 +10,14 @@ def create_ls_subparser( parser: argparse.ArgumentParser, ) -> argparse.ArgumentParser: + """Augment :class:`argparse.ArgumentParser` with ``ls`` subcommand.""" return parser def command_ls( parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: + """Entrypoint for ``tmuxp ls`` subcommand.""" tmuxp_dir = get_workspace_dir() if os.path.exists(tmuxp_dir) and os.path.isdir(tmuxp_dir): for f in sorted(os.listdir(tmuxp_dir)): diff --git a/src/tmuxp/cli/shell.py b/src/tmuxp/cli/shell.py index a0087ba3593..f6831d178e4 100644 --- a/src/tmuxp/cli/shell.py +++ b/src/tmuxp/cli/shell.py @@ -1,3 +1,4 @@ +"""CLI for ``tmuxp shell`` subcommand.""" import argparse import os import pathlib @@ -18,6 +19,8 @@ class CLIShellNamespace(argparse.Namespace): + """Typed :class:`argparse.Namespace` for tmuxp shell command.""" + session_name: str socket_name: t.Optional[str] socket_path: t.Optional[str] @@ -31,6 +34,7 @@ class CLIShellNamespace(argparse.Namespace): def create_shell_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: + """Augment :class:`argparse.ArgumentParser` with ``shell`` subcommand.""" parser.add_argument("session_name", metavar="session-name", nargs="?") parser.add_argument("window_name", metavar="window-name", nargs="?") parser.add_argument( @@ -132,7 +136,7 @@ def command_shell( args: CLIShellNamespace, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: - """Launch python shell for tmux server, session, window and pane. + """Entrypoint for ``tmuxp shell`` for tmux server, session, window and pane. Priority given to loaded session/window/pane objects: diff --git a/src/tmuxp/cli/utils.py b/src/tmuxp/cli/utils.py index 482f20d0421..4dc861cf6be 100644 --- a/src/tmuxp/cli/utils.py +++ b/src/tmuxp/cli/utils.py @@ -1,3 +1,4 @@ +"""CLI utility helpers for tmuxp.""" import logging import re import typing as t @@ -18,9 +19,7 @@ def tmuxp_echo( log_level: str = "INFO", style_log: bool = False, ) -> None: - """ - Combines logging.log and click.echo - """ + """Combine logging.log and click.echo.""" if message is None: return @@ -38,16 +37,25 @@ def prompt( value_proc: t.Optional[t.Callable[[str], str]] = None, ) -> str: """Return user input from command line. + + Parameters + ---------- + :param name: prompt text + :param default: default value if no input provided. + + Returns + ------- + str + + See Also + -------- :meth:`~prompt`, :meth:`~prompt_bool` and :meth:`prompt_choices` are from `flask-script`_. See the `flask-script license`_. + .. _flask-script: https://github.com/techniq/flask-script .. _flask-script license: https://github.com/techniq/flask-script/blob/master/LICENSE - :param name: prompt text - :param default: default value if no input provided. - :rtype: string """ - _prompt = name + (default and " [%s]" % default or "") _prompt += name.endswith("?") and " " or ": " while True: @@ -71,14 +79,19 @@ def prompt_bool( yes_choices: t.Optional[t.Sequence[t.Any]] = None, no_choices: t.Optional[t.Sequence[t.Any]] = None, ) -> bool: - """Return user input from command line and converts to boolean value. + """Return True / False by prompting user input from command line. + + Parameters + ---------- :param name: prompt text :param default: default value if no input provided. :param yes_choices: default 'y', 'yes', '1', 'on', 'true', 't' :param no_choices: default 'n', 'no', '0', 'off', 'false', 'f' - :rtype: bool - """ + Returns + ------- + bool + """ yes_choices = yes_choices or ("y", "yes", "1", "on", "true", "t") no_choices = no_choices or ("n", "no", "0", "off", "false", "f") @@ -114,14 +127,19 @@ def prompt_choices( no_choice: t.Sequence[str] = ("none",), ) -> t.Optional[str]: """Return user input from command line from set of provided choices. + + Parameters + ---------- :param name: prompt text :param choices: list or tuple of available choices. Choices may be single strings or (key, value) tuples. :param default: default value if no input provided. :param no_choice: acceptable list of strings for "null choice" - :rtype: str - """ + Returns + ------- + str + """ _choices: t.List[str] = [] options: t.List[str] = [] @@ -148,6 +166,7 @@ def prompt_choices( def strip_ansi(value: str) -> str: + """Clear ANSI from a string value.""" return _ansi_re.sub("", value) @@ -187,6 +206,8 @@ def _interpret_color( class UnknownStyleColor(Exception): + """Raised when encountering an unknown terminal style color.""" + def __init__(self, color: "CLIColour", *args: object, **kwargs: object) -> None: return super().__init__(f"Unknown color {color!r}", *args, **kwargs) @@ -205,7 +226,7 @@ def style( strikethrough: t.Optional[bool] = None, reset: bool = True, ) -> str: - """Credit: click""" + """Credit: click.""" if not isinstance(text, str): text = str(text) @@ -246,11 +267,12 @@ def style( def unstyle(text: str) -> str: - """Removes ANSI styling information from a string. Usually it's not - necessary to use this function as tmuxp_echo function will + """Remove ANSI styling information from a string. + + Usually it's not necessary to use this function as tmuxp_echo function will automatically remove styling if necessary. - credit: click. + Credit: click. :param text: the text to remove style information from. """ diff --git a/src/tmuxp/config_reader.py b/src/tmuxp/config_reader.py index 6c0913d2c36..6943aff30dc 100644 --- a/src/tmuxp/config_reader.py +++ b/src/tmuxp/config_reader.py @@ -1,3 +1,4 @@ +"""Configuration parser for YAML and JSON files.""" import json import pathlib import typing as t @@ -118,7 +119,7 @@ def _from_file(cls, path: pathlib.Path) -> t.Dict[str, t.Any]: @classmethod def from_file(cls, path: pathlib.Path) -> "ConfigReader": - r"""Load data from file path + r"""Load data from file path. **YAML file** diff --git a/src/tmuxp/exc.py b/src/tmuxp/exc.py index 3d04c63735d..eff64bc4197 100644 --- a/src/tmuxp/exc.py +++ b/src/tmuxp/exc.py @@ -12,18 +12,21 @@ class TmuxpException(Exception): - """Base Exception for Tmuxp Errors.""" class WorkspaceError(TmuxpException): - """Error parsing tmuxp workspace data.""" class SessionNotFound(TmuxpException): + """tmux session not found.""" + def __init__( - self, session_target: t.Optional[str] = None, *args: object, **kwargs: object + self, + session_target: t.Optional[str] = None, + *args: object, + **kwargs: object, ) -> None: msg = "Session not found" if session_target is not None: @@ -32,8 +35,13 @@ def __init__( class WindowNotFound(TmuxpException): + """tmux window not found.""" + def __init__( - self, window_target: t.Optional[str] = None, *args: object, **kwargs: object + self, + window_target: t.Optional[str] = None, + *args: object, + **kwargs: object, ) -> None: msg = "Window not found" if window_target is not None: @@ -42,8 +50,13 @@ def __init__( class PaneNotFound(TmuxpException): + """tmux pane not found.""" + def __init__( - self, pane_target: t.Optional[str] = None, *args: object, **kwargs: object + self, + pane_target: t.Optional[str] = None, + *args: object, + **kwargs: object, ) -> None: msg = "Pane not found" if pane_target is not None: @@ -52,14 +65,19 @@ def __init__( class EmptyWorkspaceException(WorkspaceError): - """Workspace file is empty.""" - def __init__(self, *args: object, **kwargs: object) -> None: + def __init__( + self, + *args: object, + **kwargs: object, + ) -> None: return super().__init__("Session configuration is empty.", *args, **kwargs) class SessionMissingWorkspaceException(WorkspaceError, ObjectDoesNotExist): + """Session missing while loading tmuxp workspace.""" + def __init__(self, *args: object, **kwargs: object) -> None: return super().__init__( "No session object exists for WorkspaceBuilder. " @@ -70,16 +88,19 @@ def __init__(self, *args: object, **kwargs: object) -> None: class ActiveSessionMissingWorkspaceException(WorkspaceError): + """Active session cannot be found while loading tmuxp workspace.""" + def __init__(self, *args: object, **kwargs: object) -> None: return super().__init__("No session active.", *args, **kwargs) class TmuxpPluginException(TmuxpException): - """Base Exception for Tmuxp Errors.""" class BeforeLoadScriptNotExists(OSError): + """Raises if shell script could not be found.""" + def __init__(self, *args: object, **kwargs: object) -> None: super().__init__(*args, **kwargs) @@ -88,8 +109,9 @@ def __init__(self, *args: object, **kwargs: object) -> None: @implements_to_string class BeforeLoadScriptError(Exception): + """Shell script execution error. - """Exception replacing :py:class:`subprocess.CalledProcessError` for + Replaces :py:class:`subprocess.CalledProcessError` for :meth:`tmuxp.util.run_before_script`. """ @@ -107,4 +129,5 @@ def __init__( ) def __str__(self) -> str: + """Return shell error message.""" return self.message diff --git a/src/tmuxp/log.py b/src/tmuxp/log.py index b05b5ac7ef9..62baacf0d29 100644 --- a/src/tmuxp/log.py +++ b/src/tmuxp/log.py @@ -32,11 +32,9 @@ def setup_logger( logger: t.Optional[logging.Logger] = None, level: str = "INFO" ) -> None: - """ - Setup logging for CLI use. + """Configure tmuxp's logging for CLI use. - Tries to do some conditionals to prevent handlers from being added twice. - Just to be safe. + Can checks for any existing loggers to prevent loading handlers twice. Parameters ---------- @@ -58,6 +56,7 @@ def set_style( prefix: str = "", suffix: str = "", ) -> str: + """Stylize terminal logging output.""" if stylized: return prefix + style_before + message + style_after + suffix @@ -65,6 +64,8 @@ def set_style( class LogFormatter(logging.Formatter): + """Format logs for tmuxp.""" + def template( self: logging.Formatter, record: logging.LogRecord, @@ -119,6 +120,7 @@ def __init__(self, color: bool = True, **kwargs: t.Any) -> None: logging.Formatter.__init__(self, **kwargs) def format(self, record: logging.LogRecord) -> str: + """Format a log record.""" try: record.message = record.getMessage() except Exception as e: @@ -155,7 +157,6 @@ def debug_log_template( str Log template. """ - reset = Style.RESET_ALL levelname = ( LEVEL_COLORS.get(record.levelname, "") diff --git a/src/tmuxp/plugin.py b/src/tmuxp/plugin.py index f6b49432d22..24a1de7d3f8 100644 --- a/src/tmuxp/plugin.py +++ b/src/tmuxp/plugin.py @@ -1,3 +1,4 @@ +"""Plugin system for tmuxp.""" import typing as t import libtmux @@ -34,18 +35,24 @@ from ._types import PluginConfigSchema class VersionConstraints(TypedDict): + """Version constraints mapping for a tmuxp plugin.""" + version: t.Union[Version, str] vmin: str vmax: t.Optional[str] incompatible: t.List[t.Union[t.Any, str]] class TmuxpPluginVersionConstraints(TypedDict): + """Version constraints for a tmuxp plugin.""" + tmux: VersionConstraints tmuxp: VersionConstraints libtmux: VersionConstraints class Config(t.TypedDict): + """tmuxp plugin configuration mapping.""" + plugin_name: str tmux_min_version: str tmux_max_version: t.Optional[str] @@ -73,12 +80,14 @@ class Config(t.TypedDict): def validate_plugin_config(config: "PluginConfigSchema") -> "TypeGuard[Config]": + """Return True if tmuxp plugin configuration valid, also upcasts.""" return isinstance(config, dict) def setup_plugin_config( config: "PluginConfigSchema", default_config: "Config" = DEFAULT_CONFIG ) -> "Config": + """Initialize tmuxp plugin configuration.""" new_config = config.copy() for default_key, default_value in default_config.items(): if default_key not in new_config: @@ -90,6 +99,8 @@ def setup_plugin_config( class TmuxpPlugin: + """Base class for a tmuxp plugin.""" + def __init__(self, **kwargs: "Unpack[PluginConfigSchema]") -> None: """ Initialize plugin. @@ -169,9 +180,7 @@ def __init__(self, **kwargs: "Unpack[PluginConfigSchema]") -> None: self._version_check() def _version_check(self) -> None: - """ - Check all dependency versions for compatibility. - """ + """Check all dependency versions for compatibility.""" for dep, constraints in self.version_constraints.items(): assert isinstance(constraints, dict) try: @@ -192,9 +201,7 @@ def _pass_version_check( vmax: t.Optional[str], incompatible: t.List[t.Union[t.Any, str]], ) -> bool: - """ - Provide affirmative if version compatibility is correct. - """ + """Provide affirmative if version compatibility is correct.""" if vmin and version < Version(vmin): return False if vmax and version > Version(vmax): diff --git a/src/tmuxp/shell.py b/src/tmuxp/shell.py index d5f6aaca950..946eca37cf7 100644 --- a/src/tmuxp/shell.py +++ b/src/tmuxp/shell.py @@ -25,12 +25,16 @@ ] class LaunchOptionalImports(TypedDict): + """tmuxp shell optional imports.""" + server: NotRequired["Server"] session: NotRequired["Session"] window: NotRequired["Window"] pane: NotRequired["Pane"] class LaunchImports(t.TypedDict): + """tmuxp shell launch import mapping.""" + libtmux: ModuleType Server: t.Type[Server] Session: t.Type[Session] @@ -43,6 +47,7 @@ class LaunchImports(t.TypedDict): def has_ipython() -> bool: + """Return True if ipython is installed.""" try: from IPython import start_ipython # NOQA F841 except ImportError: @@ -55,6 +60,7 @@ def has_ipython() -> bool: def has_ptpython() -> bool: + """Return True if ptpython is installed.""" try: from ptpython.repl import embed, run_config # F841 except ImportError: @@ -67,6 +73,7 @@ def has_ptpython() -> bool: def has_ptipython() -> bool: + """Return True if ptpython + ipython are both installed.""" try: from ptpython.ipython import embed # F841 from ptpython.repl import run_config # F841 @@ -81,6 +88,7 @@ def has_ptipython() -> bool: def has_bpython() -> bool: + """Return True if bpython is installed.""" try: from bpython import embed # NOQA F841 except ImportError: @@ -89,6 +97,7 @@ def has_bpython() -> bool: def detect_best_shell() -> "CLIShellLiteral": + """Return the best, most feature-rich shell available.""" if has_ptipython(): return "ptipython" elif has_ptpython(): @@ -103,6 +112,7 @@ def detect_best_shell() -> "CLIShellLiteral": def get_bpython( options: "LaunchOptionalImports", extra_args: t.Optional[t.Dict[str, t.Any]] = None ) -> t.Callable[[], None]: + """Return bpython shell.""" if extra_args is None: extra_args = {} @@ -119,6 +129,7 @@ def launch_bpython() -> None: def get_ipython_arguments() -> t.List[str]: + """Return ipython shell args via ``IPYTHON_ARGUMENTS`` environment variables.""" ipython_args = "IPYTHON_ARGUMENTS" return os.environ.get(ipython_args, "").split() @@ -126,6 +137,7 @@ def get_ipython_arguments() -> t.List[str]: def get_ipython( options: "LaunchOptionalImports", **extra_args: t.Dict[str, t.Any] ) -> t.Any: + """Return ipython shell.""" try: from IPython import start_ipython @@ -151,6 +163,7 @@ def launch_ipython() -> None: def get_ptpython(options: "LaunchOptionalImports", vi_mode: bool = False) -> t.Any: + """Return ptpython shell.""" try: from ptpython.repl import embed, run_config except ImportError: @@ -170,7 +183,7 @@ def launch_ptpython() -> None: def get_ptipython(options: "LaunchOptionalImports", vi_mode: bool = False) -> t.Any: - """Based on django-extensions + """Based on django-extensions. Run renamed to launch, get_imported_objects renamed to get_launch_args """ @@ -196,6 +209,7 @@ def launch_ptipython() -> None: def get_launch_args(**kwargs: "Unpack[LaunchOptionalImports]") -> "LaunchImports": + """Return tmuxp shell launch arguments, counting for overrides.""" import libtmux from libtmux.pane import Pane from libtmux.server import Server @@ -216,6 +230,7 @@ def get_launch_args(**kwargs: "Unpack[LaunchOptionalImports]") -> "LaunchImports def get_code(use_pythonrc: bool, imported_objects: "LaunchImports") -> t.Any: + """Launch basic python shell via :mod:`code`.""" import code try: @@ -274,6 +289,7 @@ def launch( use_vi_mode: bool = False, **kwargs: "Unpack[LaunchOptionalImports]", ) -> None: + """Launch interactive libtmux shell for tmuxp shell.""" # Also allowing passing shell='code' to force using code.interact imported_objects = get_launch_args(**kwargs) diff --git a/src/tmuxp/types.py b/src/tmuxp/types.py index f1f6c138f88..59d3a3aa681 100644 --- a/src/tmuxp/types.py +++ b/src/tmuxp/types.py @@ -1,8 +1,7 @@ -"""Internal :term:`type annotations ` +"""Internal :term:`type annotations `. Notes ----- - :class:`StrPath` and :class:`StrOrBytesPath` is based on `typeshed's`_. .. _typeshed's: https://github.com/python/typeshed/blob/9687d5/stdlib/_typeshed/__init__.pyi#L98 diff --git a/src/tmuxp/util.py b/src/tmuxp/util.py index 8a306fbedf7..888f8f551d1 100644 --- a/src/tmuxp/util.py +++ b/src/tmuxp/util.py @@ -30,7 +30,7 @@ def run_before_script( script_file: t.Union[str, pathlib.Path], cwd: t.Optional[pathlib.Path] = None ) -> int: - """Function to wrap try/except for subprocess.check_call().""" + """Execute a shell script, wraps :meth:`subprocess.check_call()` in a try/catch.""" try: proc = subprocess.Popen( shlex.split(str(script_file)), @@ -91,7 +91,7 @@ def oh_my_zsh_auto_title() -> None: def get_current_pane(server: "Server") -> t.Optional["Pane"]: - """Return Pane if one found in env""" + """Return Pane if one found in env.""" if os.getenv("TMUX_PANE") is not None: try: return next(p for p in server.panes if p.pane_id == os.getenv("TMUX_PANE")) @@ -105,6 +105,7 @@ def get_session( session_name: t.Optional[str] = None, current_pane: t.Optional["Pane"] = None, ) -> "Session": + """Get tmux session for server by session name, respects current pane, if passed.""" try: if session_name: session = server.sessions.get(session_name=session_name) @@ -131,6 +132,7 @@ def get_window( window_name: t.Optional[str] = None, current_pane: t.Optional["Pane"] = None, ) -> "Window": + """Get tmux window for server by window name, respects current pane, if passed.""" try: if window_name: window = session.windows.get(window_name=window_name) @@ -150,6 +152,7 @@ def get_window( def get_pane(window: "Window", current_pane: t.Optional["Pane"] = None) -> "Pane": + """Get tmux pane for server by pane name, respects current pane, if passed.""" pane = None try: if current_pane is not None: diff --git a/src/tmuxp/workspace/__init__.py b/src/tmuxp/workspace/__init__.py index e69de29bb2d..ac87e57f625 100644 --- a/src/tmuxp/workspace/__init__.py +++ b/src/tmuxp/workspace/__init__.py @@ -0,0 +1 @@ +"""tmuxp workspace functionality.""" diff --git a/src/tmuxp/workspace/builder.py b/src/tmuxp/workspace/builder.py index f149d87d4d7..4fb6f7c60d8 100644 --- a/src/tmuxp/workspace/builder.py +++ b/src/tmuxp/workspace/builder.py @@ -33,7 +33,6 @@ class WorkspaceBuilder: Examples -------- - >>> import yaml >>> session_config = yaml.load(''' @@ -161,7 +160,6 @@ def __init__( TODO: Initialize :class:`libtmux.Session` from here, in ``self.session``. """ - if plugins is None: plugins = [] if not session_config: @@ -189,11 +187,13 @@ def __init__( @property def session(self) -> Session: + """Return tmux session using in workspace builder session.""" if self._session is None: raise exc.SessionMissingWorkspaceException() return self._session def session_exists(self, session_name: str) -> bool: + """Return true if tmux session already exists.""" assert session_name is not None assert isinstance(session_name, str) assert self.server is not None @@ -222,7 +222,6 @@ def build(self, session: t.Optional[Session] = None, append: bool = False) -> No append : bool append windows in current active session """ - if not session: if not self.server: raise exc.TmuxpException( @@ -559,6 +558,7 @@ def config_after_window( window.set_window_option(key, val) def find_current_attached_session(self) -> Session: + """Return current attached session.""" assert self.server is not None current_active_pane = get_current_pane(self.server) @@ -575,4 +575,5 @@ def find_current_attached_session(self) -> Session: ) def first_window_pass(self, i: int, session: Session, append: bool) -> bool: + """Return True first window, used when iterating session windows.""" return len(session.windows) == 1 and i == 1 and not append diff --git a/src/tmuxp/workspace/constants.py b/src/tmuxp/workspace/constants.py index 50a463af895..da825868703 100644 --- a/src/tmuxp/workspace/constants.py +++ b/src/tmuxp/workspace/constants.py @@ -1 +1,2 @@ +"""Constant variables for tmuxp workspace functionality.""" VALID_WORKSPACE_DIR_FILE_EXTENSIONS = [".yaml", ".yml", ".json"] diff --git a/src/tmuxp/workspace/finders.py b/src/tmuxp/workspace/finders.py index ecc7fea5a27..3eb8837cc52 100644 --- a/src/tmuxp/workspace/finders.py +++ b/src/tmuxp/workspace/finders.py @@ -1,3 +1,4 @@ +"""Workspace (configuration file) finders for tmuxp.""" import logging import os import pathlib @@ -113,7 +114,6 @@ def get_workspace_dir() -> str: str : absolute path to tmuxp config directory """ - paths = [] if "TMUXP_CONFIGDIR" in os.environ: paths.append(os.environ["TMUXP_CONFIGDIR"]) diff --git a/src/tmuxp/workspace/freezer.py b/src/tmuxp/workspace/freezer.py index 0d1d14e98fb..9c6d6306852 100644 --- a/src/tmuxp/workspace/freezer.py +++ b/src/tmuxp/workspace/freezer.py @@ -1,3 +1,4 @@ +"""Tmux session freezing functionality for tmuxp.""" import typing as t from libtmux.pane import Pane @@ -19,7 +20,6 @@ def inline(workspace_dict: t.Dict[str, t.Any]) -> t.Any: dict workspace with shorthands inlined. """ - if ( "shell_command" in workspace_dict and isinstance(workspace_dict["shell_command"], list) diff --git a/src/tmuxp/workspace/importers.py b/src/tmuxp/workspace/importers.py index 9f60208f28c..a33b44a035c 100644 --- a/src/tmuxp/workspace/importers.py +++ b/src/tmuxp/workspace/importers.py @@ -1,3 +1,4 @@ +"""Configuration import adapters to load teamocil, tmuxinator, etc. in tmuxp.""" import typing as t @@ -15,7 +16,6 @@ def import_tmuxinator(workspace_dict: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: ------- dict """ - tmuxp_workspace = {} if "project_name" in workspace_dict: @@ -111,7 +111,6 @@ def import_teamocil(workspace_dict: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: Notes ----- - Todos: - change 'root' to a cd or start_directory @@ -120,7 +119,6 @@ def import_teamocil(workspace_dict: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: - clear - cmd_separator """ - tmuxp_workspace = {} if "session" in workspace_dict: diff --git a/src/tmuxp/workspace/loader.py b/src/tmuxp/workspace/loader.py index 96c029069cf..a75aa61f428 100644 --- a/src/tmuxp/workspace/loader.py +++ b/src/tmuxp/workspace/loader.py @@ -13,7 +13,7 @@ def expandshell(value: str) -> str: - """Returned with variables expanded based on user's ``$HOME`` and ``env``. + """Resolve shell variables based on user's ``$HOME`` and ``env``. :py:func:`os.path.expanduser` and :py:fubasednc:`os.path.expandvars`. @@ -31,6 +31,7 @@ def expandshell(value: str) -> str: def expand_cmd(p: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + """Resolve shell variables and expand shorthands in a tmuxp config mapping.""" if isinstance(p, str): p = {"shell_command": [p]} elif isinstance(p, list): @@ -71,7 +72,7 @@ def expand( cwd: t.Optional[t.Union[pathlib.Path, str]] = None, parent: t.Optional[t.Any] = None, ) -> t.Dict[str, t.Any]: - """Return workspace with shorthand and inline properties expanded. + """Resolve workspace variables and expand shorthand style / inline properties. This is necessary to keep the code in the :class:`WorkspaceBuilder` clean and also allow for neat, short-hand "sugarified" syntax. @@ -102,7 +103,6 @@ def expand( ------- dict """ - # Note: cli.py will expand workspaces relative to project's workspace directory # for the first cwd argument. cwd = pathlib.Path().cwd() if not cwd else pathlib.Path(cwd) @@ -208,7 +208,6 @@ def trickle(workspace_dict: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: ------- dict """ - # prepends a pane's ``shell_command`` list with the window and sessions' # ``shell_command_before``. diff --git a/src/tmuxp/workspace/validation.py b/src/tmuxp/workspace/validation.py index 02578e1de80..a3b6a393b1d 100644 --- a/src/tmuxp/workspace/validation.py +++ b/src/tmuxp/workspace/validation.py @@ -1,13 +1,18 @@ +"""Validation errors for tmuxp configuration files.""" import typing as t from .. import exc class SchemaValidationError(exc.WorkspaceError): + """Tmuxp configuration validation base error.""" + pass class SessionNameMissingValidationError(SchemaValidationError): + """Tmuxp configuration error for session name missing.""" + def __init__(self, *args: object, **kwargs: object) -> None: return super().__init__( 'workspace requires "session_name"', @@ -17,6 +22,8 @@ def __init__(self, *args: object, **kwargs: object) -> None: class WindowListMissingValidationError(SchemaValidationError): + """Tmuxp configuration error for window list missing.""" + def __init__(self, *args: object, **kwargs: object) -> None: return super().__init__( 'workspace requires list of "windows"', @@ -26,6 +33,8 @@ def __init__(self, *args: object, **kwargs: object) -> None: class WindowNameMissingValidationError(SchemaValidationError): + """Tmuxp configuration error for missing window_name.""" + def __init__(self, *args: object, **kwargs: object) -> None: return super().__init__( 'workspace window is missing "window_name"', @@ -35,6 +44,8 @@ def __init__(self, *args: object, **kwargs: object) -> None: class InvalidPluginsValidationError(SchemaValidationError): + """Tmuxp configuration error for invalid plugins.""" + def __init__(self, plugins: t.Any, *args: object, **kwargs: object) -> None: return super().__init__( '"plugins" only supports list type. ' diff --git a/tests/__init__.py b/tests/__init__.py index e69de29bb2d..6940f228687 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for tmuxp.""" diff --git a/tests/cli/__init__.py b/tests/cli/__init__.py index e69de29bb2d..95437011db7 100644 --- a/tests/cli/__init__.py +++ b/tests/cli/__init__.py @@ -0,0 +1 @@ +"""CLI tests for tmuxp.""" diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index 48f5be0bf1f..50710327712 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -1,3 +1,4 @@ +"""CLI tests for tmuxp's core shell functionality.""" import argparse import contextlib import pathlib @@ -24,7 +25,6 @@ def test_creates_config_dir_not_exists(tmp_path: pathlib.Path) -> None: """cli.startup() creates config dir if not exists.""" - cli.startup(tmp_path) assert tmp_path.exists() @@ -42,6 +42,7 @@ def test_help( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: + """Test tmuxp --help / -h.""" # In scrunched terminals, prevent width causing differentiation in result.out. monkeypatch.setenv("COLUMNS", "100") monkeypatch.setenv("LINES", "100") @@ -55,8 +56,10 @@ def test_help( def test_resolve_behavior( - tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, ) -> None: + """Test resolution of file paths.""" expect = tmp_path monkeypatch.chdir(tmp_path) assert pathlib.Path("../").resolve() == expect.parent @@ -65,7 +68,10 @@ def test_resolve_behavior( assert pathlib.Path(expect).resolve() == expect -def test_get_tmuxinator_dir(monkeypatch: pytest.MonkeyPatch) -> None: +def test_get_tmuxinator_dir( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test get_tmuxinator_dir() helper function.""" assert get_tmuxinator_dir() == pathlib.Path("~/.tmuxinator").expanduser() monkeypatch.setenv("HOME", "/moo") @@ -74,7 +80,10 @@ def test_get_tmuxinator_dir(monkeypatch: pytest.MonkeyPatch) -> None: assert get_tmuxinator_dir() == pathlib.Path("~/.tmuxinator/").expanduser() -def test_get_teamocil_dir(monkeypatch: pytest.MonkeyPatch) -> None: +def test_get_teamocil_dir( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test get_teamocil_dir() helper function.""" assert get_teamocil_dir() == pathlib.Path("~/.teamocil/").expanduser() monkeypatch.setenv("HOME", "/moo") @@ -83,11 +92,12 @@ def test_get_teamocil_dir(monkeypatch: pytest.MonkeyPatch) -> None: assert get_teamocil_dir() == pathlib.Path("~/.teamocil/").expanduser() -def test_pass_config_dir_ClickPath( +def test_pass_config_dir_argparse( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: + """Test workspace configurations can be detected via directory.""" configdir = tmp_path / "myconfigdir" configdir.mkdir() user_config_name = "myconfig" @@ -119,8 +129,10 @@ def check_cmd(config_arg: str) -> "_pytest.capture.CaptureResult[str]": def test_reattach_plugins( - monkeypatch_plugin_test_packages: None, server: "Server" + monkeypatch_plugin_test_packages: None, + server: "Server", ) -> None: + """Test reattach plugin hook.""" config_plugins = test_utils.read_workspace_file("workspace/builder/plugin_r.yaml") session_configig = ConfigReader._load(format="yaml", content=config_plugins) diff --git a/tests/cli/test_convert.py b/tests/cli/test_convert.py index 6a36e3e46d6..3042d078333 100644 --- a/tests/cli/test_convert.py +++ b/tests/cli/test_convert.py @@ -1,3 +1,4 @@ +"""CLI tests for tmuxp convert.""" import contextlib import io import json @@ -24,6 +25,7 @@ def test_convert( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: + """Parametrized tests for tmuxp convert.""" # create dummy tmuxp yaml so we don't get yelled at filename = cli_args[1] if filename == ".": @@ -63,6 +65,7 @@ def test_convert_json( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: + """CLI test using tmuxp convert to convert configuration from json to yaml.""" # create dummy tmuxp yaml so we don't get yelled at json_config = tmp_path / ".tmuxp.json" json_config.write_text('{"session_name": "hello"}', encoding="utf-8") diff --git a/tests/cli/test_debug_info.py b/tests/cli/test_debug_info.py index df76fc830df..daba2c69fba 100644 --- a/tests/cli/test_debug_info.py +++ b/tests/cli/test_debug_info.py @@ -1,3 +1,4 @@ +"""CLI tests for tmuxp debuginfo.""" import pathlib import pytest @@ -10,6 +11,7 @@ def test_debug_info_cli( tmp_path: pathlib.Path, capsys: pytest.CaptureFixture[str], ) -> None: + """Basic CLI test for tmuxp debug-info.""" monkeypatch.setenv("SHELL", "/bin/bash") cli.cli(["debug-info"]) diff --git a/tests/cli/test_freeze.py b/tests/cli/test_freeze.py index 9d5fac36e2a..96ca742d7d2 100644 --- a/tests/cli/test_freeze.py +++ b/tests/cli/test_freeze.py @@ -1,3 +1,4 @@ +"""Test workspace freezing functionality for tmuxp.""" import contextlib import io import pathlib @@ -32,6 +33,7 @@ def test_freeze( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: + """Parametrized test for freezing a tmux session to a tmuxp config file.""" monkeypatch.setenv("HOME", str(tmp_path)) exists_yaml = tmp_path / "exists.yaml" exists_yaml.touch() @@ -83,6 +85,7 @@ def test_freeze_overwrite( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: + """Test overwrite prompt when freezing a tmuxp configuration file.""" monkeypatch.setenv("HOME", str(tmp_path)) exists_yaml = tmp_path / "exists.yaml" exists_yaml.touch() diff --git a/tests/cli/test_import.py b/tests/cli/test_import.py index b08f78a438d..ce418889e30 100644 --- a/tests/cli/test_import.py +++ b/tests/cli/test_import.py @@ -1,3 +1,4 @@ +"""CLI tests for tmuxp import.""" import contextlib import io import pathlib @@ -17,6 +18,7 @@ def test_import( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: + """Basic CLI test for tmuxp import.""" cli.cli(cli_args) result = capsys.readouterr() assert "tmuxinator" in result.out @@ -46,6 +48,7 @@ def test_import_teamocil( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: + """CLI test for tmuxp import w/ teamocil.""" teamocil_config = test_utils.read_workspace_file("import_teamocil/test4.yaml") teamocil_path = tmp_path / ".teamocil" @@ -92,6 +95,7 @@ def test_import_tmuxinator( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: + """CLI test for tmuxp import w/ tmuxinator.""" tmuxinator_config = test_utils.read_workspace_file("import_tmuxinator/test3.yaml") tmuxinator_path = tmp_path / ".tmuxinator" diff --git a/tests/cli/test_load.py b/tests/cli/test_load.py index 399cea5fe24..13f7d100c6f 100644 --- a/tests/cli/test_load.py +++ b/tests/cli/test_load.py @@ -1,3 +1,4 @@ +"""CLI tests for tmuxp load.""" import contextlib import io import pathlib @@ -25,7 +26,11 @@ from ..fixtures import utils as test_utils -def test_load_workspace(server: "Server", monkeypatch: pytest.MonkeyPatch) -> None: +def test_load_workspace( + server: "Server", + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Generic test for loading a tmuxp workspace via tmuxp load.""" # this is an implementation test. Since this testsuite may be ran within # a tmux session by the developer himself, delete the TMUX variable # temporarily. @@ -42,8 +47,10 @@ def test_load_workspace(server: "Server", monkeypatch: pytest.MonkeyPatch) -> No def test_load_workspace_passes_tmux_config( - server: "Server", monkeypatch: pytest.MonkeyPatch + server: "Server", + monkeypatch: pytest.MonkeyPatch, ) -> None: + """Test tmuxp load with a tmux configuration file.""" # this is an implementation test. Since this testsuite may be ran within # a tmux session by the developer himself, delete the TMUX variable # temporarily. @@ -64,8 +71,10 @@ def test_load_workspace_passes_tmux_config( def test_load_workspace_named_session( - server: "Server", monkeypatch: pytest.MonkeyPatch + server: "Server", + monkeypatch: pytest.MonkeyPatch, ) -> None: + """Test tmuxp load with a custom tmux session name.""" # this is an implementation test. Since this testsuite may be ran within # a tmux session by the developer himself, delete the TMUX variable # temporarily. @@ -88,8 +97,11 @@ def test_load_workspace_named_session( has_lt_version("2.1"), reason="exact session name matches only tmux >= 2.1" ) def test_load_workspace_name_match_regression_252( - tmp_path: pathlib.Path, server: "Server", monkeypatch: pytest.MonkeyPatch + tmp_path: pathlib.Path, + server: "Server", + monkeypatch: pytest.MonkeyPatch, ) -> None: + """Test tmuxp load for a regression where tmux shell names would not match.""" monkeypatch.delenv("TMUX", raising=False) session_file = FIXTURE_PATH / "workspace/builder" / "two_pane.yaml" @@ -101,9 +113,9 @@ def test_load_workspace_name_match_regression_252( assert isinstance(session, Session) assert session.name == "sample workspace" - projfile = tmp_path / "simple.yaml" + workspace_file = tmp_path / "simple.yaml" - projfile.write_text( + workspace_file.write_text( """ session_name: sampleconfi start_directory: './' @@ -115,15 +127,18 @@ def test_load_workspace_name_match_regression_252( # open it detached session = load_workspace( - str(projfile), socket_name=server.socket_name, detached=True + str(workspace_file), socket_name=server.socket_name, detached=True ) assert session is not None assert session.name == "sampleconfi" def test_load_symlinked_workspace( - server: "Server", tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch + server: "Server", + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, ) -> None: + """Test tmuxp load can follow a symlinked tmuxp config file.""" # this is an implementation test. Since this testsuite may be ran within # a tmux session by the developer himself, delete the TMUX variable # temporarily. @@ -133,9 +148,9 @@ def test_load_symlinked_workspace( realtemp.mkdir() linktemp = tmp_path / "symlinktemp" linktemp.symlink_to(realtemp) - projfile = linktemp / "simple.yaml" + workspace_file = linktemp / "simple.yaml" - projfile.write_text( + workspace_file.write_text( """ session_name: samplesimple start_directory: './' @@ -147,7 +162,7 @@ def test_load_symlinked_workspace( # open it detached session = load_workspace( - str(projfile), socket_name=server.socket_name, detached=True + str(workspace_file), socket_name=server.socket_name, detached=True ) assert session is not None assert session.attached_window is not None @@ -167,7 +182,12 @@ def test_load_symlinked_workspace( class CLILoadFixture(t.NamedTuple): + """Test fixture for tmuxp load tests.""" + + # pytest (internal): Test fixture name test_id: str + + # test params cli_args: t.List[t.Union[str, t.List[str]]] config_paths: t.List[str] session_names: t.List[str] @@ -274,6 +294,7 @@ def test_load( expected_in_err: "ExpectedOutput", expected_not_in_err: "ExpectedOutput", ) -> None: + """Parametrized test battery for tmuxp load CLI command.""" assert server.socket_name is not None monkeypatch.chdir(tmp_path) @@ -320,6 +341,7 @@ def test_regression_00132_session_name_with_dots( session: Session, capsys: pytest.CaptureFixture[str], ) -> None: + """Regression test for session names with dots.""" yaml_config = FIXTURE_PATH / "workspace/builder" / "regression_00132_dots.yaml" cli_args = [str(yaml_config)] with pytest.raises(libtmux.exc.BadSessionName): @@ -336,6 +358,7 @@ def test_load_zsh_autotitle_warning( capsys: pytest.CaptureFixture[str], server: "Server", ) -> None: + """Test loading ZSH without DISABLE_AUTO_TITLE raises warning.""" # create dummy tmuxp yaml so we don't get yelled at yaml_config = tmp_path / ".tmuxp.yaml" yaml_config.write_text( @@ -394,6 +417,7 @@ def test_load_log_file( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: + """Test loading via tmuxp load with --log-file.""" # create dummy tmuxp yaml that breaks to prevent actually loading tmux tmuxp_config_path = tmp_path / ".tmuxp.yaml" tmuxp_config_path.write_text( @@ -418,7 +442,10 @@ def test_load_log_file( assert result.out is not None -def test_load_plugins(monkeypatch_plugin_test_packages: None) -> None: +def test_load_plugins( + monkeypatch_plugin_test_packages: None, +) -> None: + """Test loading via tmuxp load with plugins.""" from tmuxp_test_plugin_bwb.plugin import ( # type: ignore PluginBeforeWorkspaceBuilder, ) @@ -455,6 +482,7 @@ def test_load_plugins_version_fail_skip( inputs: t.List[str], capsys: pytest.CaptureFixture[str], ) -> None: + """Test tmuxp load with plugins failing version constraints can continue.""" with contextlib.suppress(SystemExit): cli.cli(cli_args) @@ -479,6 +507,7 @@ def test_load_plugins_version_fail_no_skip( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: + """Test tmuxp load with plugins failing version constraints can exit.""" monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) with contextlib.suppress(SystemExit): @@ -498,6 +527,7 @@ def test_load_plugins_plugin_missing( cli_args: t.List[str], capsys: pytest.CaptureFixture[str], ) -> None: + """Test tmuxp load with plugins missing raise an error.""" with contextlib.suppress(SystemExit): cli.cli(cli_args) @@ -511,6 +541,7 @@ def test_plugin_system_before_script( server: "Server", monkeypatch: pytest.MonkeyPatch, ) -> None: + """Test tmuxp load with sessions using before_script.""" # this is an implementation test. Since this testsuite may be ran within # a tmux session by the developer himself, delete the TMUX variable # temporarily. @@ -527,8 +558,11 @@ def test_plugin_system_before_script( def test_load_attached( - server: "Server", monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture + server: "Server", + monkeypatch: pytest.MonkeyPatch, + mocker: MockerFixture, ) -> None: + """Test tmuxp load's attachment behavior.""" # Load a session and attach from outside tmux monkeypatch.delenv("TMUX", raising=False) @@ -546,8 +580,11 @@ def test_load_attached( def test_load_attached_detached( - server: "Server", monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture + server: "Server", + monkeypatch: pytest.MonkeyPatch, + mocker: MockerFixture, ) -> None: + """Test tmuxp load when sessions are build without attaching client.""" # Load a session but don't attach monkeypatch.delenv("TMUX", raising=False) @@ -565,8 +602,11 @@ def test_load_attached_detached( def test_load_attached_within_tmux( - server: "Server", monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture + server: "Server", + monkeypatch: pytest.MonkeyPatch, + mocker: MockerFixture, ) -> None: + """Test loading via tmuxp load when already within a tmux session.""" # Load a session and attach from within tmux monkeypatch.setenv("TMUX", "/tmp/tmux-1234/default,123,0") @@ -584,8 +624,11 @@ def test_load_attached_within_tmux( def test_load_attached_within_tmux_detached( - server: "Server", monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture + server: "Server", + monkeypatch: pytest.MonkeyPatch, + mocker: MockerFixture, ) -> None: + """Test loading via tmuxp load within a tmux session switches clients.""" # Load a session and attach from within tmux monkeypatch.setenv("TMUX", "/tmp/tmux-1234/default,123,0") @@ -603,8 +646,10 @@ def test_load_attached_within_tmux_detached( def test_load_append_windows_to_current_session( - server: "Server", monkeypatch: pytest.MonkeyPatch + server: "Server", + monkeypatch: pytest.MonkeyPatch, ) -> None: + """Test tmuxp load when windows are appended to the current session.""" yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") session_config = ConfigReader._load(format="yaml", content=yaml_config) diff --git a/tests/cli/test_ls.py b/tests/cli/test_ls.py index f8a2113ec9c..7fdcf9ffde1 100644 --- a/tests/cli/test_ls.py +++ b/tests/cli/test_ls.py @@ -1,3 +1,4 @@ +"""CLI tests for tmuxp ls command.""" import contextlib import pathlib @@ -11,6 +12,7 @@ def test_ls_cli( tmp_path: pathlib.Path, capsys: pytest.CaptureFixture[str], ) -> None: + """CLI test for tmuxp ls.""" monkeypatch.setenv("HOME", str(tmp_path)) monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / ".config")) diff --git a/tests/cli/test_shell.py b/tests/cli/test_shell.py index 75658471df9..6d96bd0fa26 100644 --- a/tests/cli/test_shell.py +++ b/tests/cli/test_shell.py @@ -1,3 +1,4 @@ +"""CLI tests for tmuxp shell.""" import contextlib import io import pathlib @@ -116,6 +117,7 @@ def test_shell( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: + """CLI tests for tmuxp shell.""" monkeypatch.setenv("HOME", str(tmp_path)) window_name = "my_window" window = session.new_window(window_name=window_name) @@ -207,6 +209,7 @@ def test_shell_target_missing( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: + """CLI tests for tmuxp shell when target is not specified.""" monkeypatch.setenv("HOME", str(tmp_path)) window_name = "my_window" window = session.new_window(window_name=window_name) @@ -283,6 +286,7 @@ def test_shell_interactive( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: + """CLI tests for tmuxp shell when shell is specified.""" monkeypatch.setenv("HOME", str(tmp_path)) window_name = "my_window" window = session.new_window(window_name=window_name) diff --git a/tests/constants.py b/tests/constants.py index 9fa14e00f07..eff81eced5a 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -1,3 +1,4 @@ +"""Constant variables for tmuxp tests.""" import pathlib TESTS_PATH = pathlib.Path(__file__).parent diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py index eb018c3ff09..32c2ed61025 100644 --- a/tests/fixtures/__init__.py +++ b/tests/fixtures/__init__.py @@ -1 +1,2 @@ +"""Fixture test data for tmuxp.""" from . import utils diff --git a/tests/fixtures/import_teamocil/__init__.py b/tests/fixtures/import_teamocil/__init__.py index 6d5ce91af9e..5367bcbc497 100644 --- a/tests/fixtures/import_teamocil/__init__.py +++ b/tests/fixtures/import_teamocil/__init__.py @@ -1 +1,2 @@ +"""Teamocil data fixtures for import_teamocil tests.""" from . import layouts, test1, test2, test3, test4 diff --git a/tests/fixtures/import_teamocil/layouts.py b/tests/fixtures/import_teamocil/layouts.py index 14d509c9697..f6a949ac340 100644 --- a/tests/fixtures/import_teamocil/layouts.py +++ b/tests/fixtures/import_teamocil/layouts.py @@ -1,3 +1,4 @@ +"""Teamocil data fixtures for import_teamocil tests, for layout testing.""" from .. import utils as test_utils teamocil_yaml_file = test_utils.get_workspace_file("import_teamocil/layouts.yaml") diff --git a/tests/fixtures/import_teamocil/test1.py b/tests/fixtures/import_teamocil/test1.py index d55a92d4a34..2a7cd33b82f 100644 --- a/tests/fixtures/import_teamocil/test1.py +++ b/tests/fixtures/import_teamocil/test1.py @@ -1,3 +1,4 @@ +"""Teamocil data fixtures for import_teamocil tests, 1st test.""" from .. import utils as test_utils teamocil_yaml = test_utils.read_workspace_file("import_teamocil/test1.yaml") diff --git a/tests/fixtures/import_teamocil/test2.py b/tests/fixtures/import_teamocil/test2.py index 3478b22c77b..82c682eaff3 100644 --- a/tests/fixtures/import_teamocil/test2.py +++ b/tests/fixtures/import_teamocil/test2.py @@ -1,3 +1,4 @@ +"""Teamocil data fixtures for import_teamocil tests, 2nd test.""" from .. import utils as test_utils teamocil_yaml = test_utils.read_workspace_file("import_teamocil/test2.yaml") diff --git a/tests/fixtures/import_teamocil/test3.py b/tests/fixtures/import_teamocil/test3.py index 4f18f11d049..6249355d18c 100644 --- a/tests/fixtures/import_teamocil/test3.py +++ b/tests/fixtures/import_teamocil/test3.py @@ -1,3 +1,4 @@ +"""Teamocil data fixtures for import_teamocil tests, 3rd test.""" from .. import utils as test_utils teamocil_yaml = test_utils.read_workspace_file("import_teamocil/test3.yaml") diff --git a/tests/fixtures/import_teamocil/test4.py b/tests/fixtures/import_teamocil/test4.py index 62c0ead874f..f22b6595b22 100644 --- a/tests/fixtures/import_teamocil/test4.py +++ b/tests/fixtures/import_teamocil/test4.py @@ -1,3 +1,4 @@ +"""Teamocil data fixtures for import_teamocil tests, 4th test.""" from .. import utils as test_utils teamocil_yaml = test_utils.read_workspace_file("import_teamocil/test4.yaml") diff --git a/tests/fixtures/import_tmuxinator/__init__.py b/tests/fixtures/import_tmuxinator/__init__.py index 91bc1d0f43e..dc711b29b7b 100644 --- a/tests/fixtures/import_tmuxinator/__init__.py +++ b/tests/fixtures/import_tmuxinator/__init__.py @@ -1 +1,2 @@ +"""Tmuxinator data fixtures for import_tmuxinator tests.""" from . import test1, test2, test3 diff --git a/tests/fixtures/import_tmuxinator/test1.py b/tests/fixtures/import_tmuxinator/test1.py index d7139f68a0d..684634dcded 100644 --- a/tests/fixtures/import_tmuxinator/test1.py +++ b/tests/fixtures/import_tmuxinator/test1.py @@ -1,3 +1,4 @@ +"""Tmuxinator data fixtures for import_tmuxinator tests, 1st dataset.""" from .. import utils as test_utils tmuxinator_yaml = test_utils.read_workspace_file("import_tmuxinator/test1.yaml") diff --git a/tests/fixtures/import_tmuxinator/test2.py b/tests/fixtures/import_tmuxinator/test2.py index 008b4e2f31d..d8dba900ceb 100644 --- a/tests/fixtures/import_tmuxinator/test2.py +++ b/tests/fixtures/import_tmuxinator/test2.py @@ -1,3 +1,4 @@ +"""Tmuxinator data fixtures for import_tmuxinator tests, 2nd dataset.""" from .. import utils as test_utils tmuxinator_yaml = test_utils.read_workspace_file("import_tmuxinator/test2.yaml") diff --git a/tests/fixtures/import_tmuxinator/test3.py b/tests/fixtures/import_tmuxinator/test3.py index dc0468d414d..86300a5156a 100644 --- a/tests/fixtures/import_tmuxinator/test3.py +++ b/tests/fixtures/import_tmuxinator/test3.py @@ -1,3 +1,4 @@ +"""Tmuxinator data fixtures for import_tmuxinator tests, 3rd dataset.""" from .. import utils as test_utils tmuxinator_yaml = test_utils.read_workspace_file("import_tmuxinator/test3.yaml") diff --git a/tests/fixtures/pluginsystem/__init__.py b/tests/fixtures/pluginsystem/__init__.py index e69de29bb2d..3e3bca44393 100644 --- a/tests/fixtures/pluginsystem/__init__.py +++ b/tests/fixtures/pluginsystem/__init__.py @@ -0,0 +1 @@ +""""Test data for tmuxp plugin system.""" diff --git a/tests/fixtures/pluginsystem/partials/__init__.py b/tests/fixtures/pluginsystem/partials/__init__.py index e69de29bb2d..793b5237355 100644 --- a/tests/fixtures/pluginsystem/partials/__init__.py +++ b/tests/fixtures/pluginsystem/partials/__init__.py @@ -0,0 +1 @@ +"""Tmuxp tests for plugins.""" diff --git a/tests/fixtures/pluginsystem/partials/_types.py b/tests/fixtures/pluginsystem/partials/_types.py index ebe24eea04c..460c71375de 100644 --- a/tests/fixtures/pluginsystem/partials/_types.py +++ b/tests/fixtures/pluginsystem/partials/_types.py @@ -1,4 +1,4 @@ -"""Internal, :const:`typing.TYPE_CHECKING` guarded :term:`type annotations ` +"""Internal, :const:`typing.TYPE_CHECKING` scoped :term:`type annotations `. These are _not_ to be imported at runtime as `typing_extensions` is not bundled with tmuxp. Usage example: @@ -15,7 +15,7 @@ class PluginTestConfigSchema(TypedDict): - """Same as PluginConfigSchema, but with tmux, libtmux, and tmuxp version""" + """Same as PluginConfigSchema, but with tmux, libtmux, and tmuxp version.""" tmux_version: NotRequired[str] libtmux_version: NotRequired[str] diff --git a/tests/fixtures/pluginsystem/partials/all_pass.py b/tests/fixtures/pluginsystem/partials/all_pass.py index 393fa00c429..19cbeb23f86 100644 --- a/tests/fixtures/pluginsystem/partials/all_pass.py +++ b/tests/fixtures/pluginsystem/partials/all_pass.py @@ -1,3 +1,4 @@ +"""Tmuxp test plugin with version constraints guaranteed to pass.""" import typing as t from .test_plugin_helpers import MyTestTmuxpPlugin @@ -7,6 +8,8 @@ class AllVersionPassPlugin(MyTestTmuxpPlugin): + """Tmuxp plugin with config constraints guaranteed to validate.""" + def __init__(self) -> None: config: "PluginTestConfigSchema" = { "plugin_name": "tmuxp-plugin-my-tmuxp-plugin", diff --git a/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py b/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py index f801c21391a..b8a18466a5d 100644 --- a/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py @@ -1,3 +1,4 @@ +"""Fixtures for tmuxp plugins for libtmux version exceptions.""" import typing as t from .test_plugin_helpers import MyTestTmuxpPlugin @@ -7,6 +8,8 @@ class LibtmuxVersionFailMinPlugin(MyTestTmuxpPlugin): + """Tmuxp plugin that fails when libtmux below minimum version constraint.""" + def __init__(self) -> None: config: "PluginTestConfigSchema" = { "plugin_name": "libtmux-min-version-fail", @@ -17,6 +20,8 @@ def __init__(self) -> None: class LibtmuxVersionFailMaxPlugin(MyTestTmuxpPlugin): + """Tmuxp plugin that fails when libtmux above maximum version constraint.""" + def __init__(self) -> None: config: "PluginTestConfigSchema" = { "plugin_name": "libtmux-max-version-fail", @@ -27,6 +32,8 @@ def __init__(self) -> None: class LibtmuxVersionFailIncompatiblePlugin(MyTestTmuxpPlugin): + """Tmuxp plugin that fails when libtmux version constraint is invalid.""" + def __init__(self) -> None: config: "PluginTestConfigSchema" = { "plugin_name": "libtmux-incompatible-version-fail", diff --git a/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py b/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py index 8b6c054e29b..874a06c6422 100644 --- a/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py +++ b/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py @@ -1,3 +1,4 @@ +"""Tmuxp test plugin for asserting version constraints.""" import typing as t from tmuxp.plugin import TmuxpPlugin @@ -11,6 +12,8 @@ class MyTestTmuxpPlugin(TmuxpPlugin): + """Base class for testing tmuxp plugins with version constraints.""" + def __init__(self, **config: "Unpack[PluginTestConfigSchema]") -> None: assert isinstance(config, dict) tmux_version = config.pop("tmux_version", None) diff --git a/tests/fixtures/pluginsystem/partials/tmux_version_fail.py b/tests/fixtures/pluginsystem/partials/tmux_version_fail.py index acf886d588c..b30f3c40829 100644 --- a/tests/fixtures/pluginsystem/partials/tmux_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/tmux_version_fail.py @@ -1,3 +1,4 @@ +"""Fixtures for tmuxp plugins for tmux version exceptions.""" import typing as t from .test_plugin_helpers import MyTestTmuxpPlugin @@ -7,6 +8,8 @@ class TmuxVersionFailMinPlugin(MyTestTmuxpPlugin): + """Tmuxp plugin that fails when tmux below minimum version constraint.""" + def __init__(self) -> None: config: "PluginTestConfigSchema" = { "plugin_name": "tmux-min-version-fail", @@ -17,6 +20,8 @@ def __init__(self) -> None: class TmuxVersionFailMaxPlugin(MyTestTmuxpPlugin): + """Tmuxp plugin that fails when tmux above maximum version constraint.""" + def __init__(self) -> None: config: "PluginTestConfigSchema" = { "plugin_name": "tmux-max-version-fail", @@ -27,6 +32,8 @@ def __init__(self) -> None: class TmuxVersionFailIncompatiblePlugin(MyTestTmuxpPlugin): + """Tmuxp plugin that fails when tmux version constraint is invalid.""" + def __init__(self) -> None: config: "PluginTestConfigSchema" = { "plugin_name": "tmux-incompatible-version-fail", diff --git a/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py b/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py index 702d843e7a0..ed99d406b66 100644 --- a/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py @@ -1,3 +1,4 @@ +"""Fixtures for tmuxp plugins for tmuxp version exceptions.""" import typing as t from .test_plugin_helpers import MyTestTmuxpPlugin @@ -7,6 +8,8 @@ class TmuxpVersionFailMinPlugin(MyTestTmuxpPlugin): + """Tmuxp plugin that fails when tmuxp below minimum version constraint.""" + def __init__(self) -> None: config: "PluginTestConfigSchema" = { "plugin_name": "tmuxp-min-version-fail", @@ -17,6 +20,8 @@ def __init__(self) -> None: class TmuxpVersionFailMaxPlugin(MyTestTmuxpPlugin): + """Tmuxp plugin that fails when tmuxp above maximum version constraint.""" + def __init__(self) -> None: config: "PluginTestConfigSchema" = { "plugin_name": "tmuxp-max-version-fail", @@ -27,6 +32,8 @@ def __init__(self) -> None: class TmuxpVersionFailIncompatiblePlugin(MyTestTmuxpPlugin): + """Tmuxp plugin that fails when tmuxp version constraint is invalid.""" + def __init__(self) -> None: config: "PluginTestConfigSchema" = { "plugin_name": "tmuxp-incompatible-version-fail", diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/__init__.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/__init__.py index e69de29bb2d..9cbdb91f118 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/__init__.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/__init__.py @@ -0,0 +1 @@ +"""Example tmuxp plugin that runs after window creation completions.""" diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/plugin.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/plugin.py index b4305ca7e7f..e2a77e8c662 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/plugin.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/plugin.py @@ -1,3 +1,4 @@ +"""Tmuxp example plugin for after_window_finished.""" import typing as t from tmuxp.plugin import TmuxpPlugin @@ -7,10 +8,13 @@ class PluginAfterWindowFinished(TmuxpPlugin): + """Tmuxp plugin that runs after window creation completes.""" + def __init__(self) -> None: self.message: str = "[+] This is the Tmuxp Test Plugin" def after_window_finished(self, window: "Window") -> None: + """Run hook after window creation completed.""" if window.name == "editor": window.rename_window("plugin_test_awf") elif window.name == "awf_mw_test": diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/__init__.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/__init__.py index e69de29bb2d..4329e60aae7 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/__init__.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/__init__.py @@ -0,0 +1 @@ +"""Example tmuxp plugin module that hooks in before_script, if declared.""" diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/plugin.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/plugin.py index e2fbf98aca9..bfc7175a733 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/plugin.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/plugin.py @@ -1,3 +1,4 @@ +"""Tmux plugin that runs before_script, if it is declared in configuration.""" import typing as t from tmuxp.plugin import TmuxpPlugin @@ -7,8 +8,11 @@ class PluginBeforeScript(TmuxpPlugin): + """Tmuxp plugin that runs before_script.""" + def __init__(self) -> None: self.message: str = "[+] This is the Tmuxp Test Plugin" def before_script(self, session: "Session") -> None: + """Run hook during before_script, if it is declared.""" session.rename_session("plugin_test_bs") diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/__init__.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/__init__.py index e69de29bb2d..98ca0ca0fc7 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/__init__.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/__init__.py @@ -0,0 +1 @@ +"""Example tmuxp plugin that runs before workspace builder inits.""" diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/plugin.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/plugin.py index d1be0b37723..cd2fb1e4f0e 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/plugin.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/plugin.py @@ -1,3 +1,4 @@ +"""Tmuxp example plugin for before_worksplace_builder.""" import typing as t from tmuxp.plugin import TmuxpPlugin @@ -7,8 +8,11 @@ class PluginBeforeWorkspaceBuilder(TmuxpPlugin): + """Tmuxp plugin that runs before worksplace builder starts.""" + def __init__(self) -> None: self.message: str = "[+] This is the Tmuxp Test Plugin" def before_workspace_builder(self, session: "Session") -> None: + """Run hook before workspace builder begins.""" session.rename_session("plugin_test_bwb") diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/__init__.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/__init__.py index e69de29bb2d..2afcb8fce89 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/__init__.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/__init__.py @@ -0,0 +1 @@ +"""Tmuxp plugin test, that is destined for failure.""" diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/plugin.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/plugin.py index 5534c5afedf..a8cc86206ed 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/plugin.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/plugin.py @@ -1,3 +1,4 @@ +"""Tmuxp example plugin that fails on initialization.""" import typing as t from tmuxp.plugin import TmuxpPlugin @@ -7,6 +8,8 @@ class PluginFailVersion(TmuxpPlugin): + """A tmuxp plugin that is doomed to fail. DOOMED.""" + def __init__(self) -> None: config: "PluginConfigSchema" = { "plugin_name": "tmuxp-plugin-fail-version", diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/__init__.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/__init__.py index e69de29bb2d..f649064840a 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/__init__.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/__init__.py @@ -0,0 +1 @@ +"""Example tmuxp plugin module for hooks on window creation.""" diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/plugin.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/plugin.py index 5f900a494f6..add308db6ff 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/plugin.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/plugin.py @@ -1,3 +1,4 @@ +"""Tmuxp example plugin for on_window_create.""" import typing as t from tmuxp.plugin import TmuxpPlugin @@ -7,10 +8,13 @@ class PluginOnWindowCreate(TmuxpPlugin): + """Tmuxp plugin to test custom functionality on window creation.""" + def __init__(self) -> None: self.message: str = "[+] This is the Tmuxp Test Plugin" def on_window_create(self, window: "Window") -> None: + """Apply hook that runs for tmux on session reattach.""" if window.name == "editor": window.rename_window("plugin_test_owc") elif window.name == "owc_mw_test": diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/__init__.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/__init__.py index e69de29bb2d..6e01504fd64 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/__init__.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/__init__.py @@ -0,0 +1 @@ +"""Example tmuxp plugin module for reattaching sessions.""" diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/plugin.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/plugin.py index 25a43e93f96..b206c5da701 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/plugin.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/plugin.py @@ -1,3 +1,4 @@ +"""Tmuxp example plugin for reattaching session.""" import typing as t from tmuxp.plugin import TmuxpPlugin @@ -7,8 +8,11 @@ class PluginReattach(TmuxpPlugin): + """Tmuxp plugin to test renaming session on reattach.""" + def __init__(self) -> None: self.message: str = "[+] This is the Tmuxp Test Plugin" def reattach(self, session: "Session") -> None: + """Apply hook that runs for tmux on session reattach.""" session.rename_session("plugin_test_r") diff --git a/tests/fixtures/structures.py b/tests/fixtures/structures.py index 85f35d10f40..cd236ad2184 100644 --- a/tests/fixtures/structures.py +++ b/tests/fixtures/structures.py @@ -1,9 +1,12 @@ +"""Typings / structures for tmuxp fixtures.""" import dataclasses import typing as t @dataclasses.dataclass class WorkspaceTestData: + """Workspace data fixtures for tmuxp tests.""" + expand1: t.Any expand2: t.Any expand_blank: t.Any diff --git a/tests/fixtures/utils.py b/tests/fixtures/utils.py index 858227c5b3a..955ebc6c569 100644 --- a/tests/fixtures/utils.py +++ b/tests/fixtures/utils.py @@ -1,3 +1,4 @@ +"""Utility functions for tmuxp fixtures.""" import pathlib import typing as t @@ -7,15 +8,17 @@ def get_workspace_file( _file: t.Union[str, pathlib.Path], ) -> pathlib.Path: - """Return fixture data, relative to __file__""" + """Return fixture data, relative to __file__.""" if isinstance(_file, str): _file = pathlib.Path(_file) return FIXTURE_PATH / _file -def read_workspace_file(_file: t.Union[pathlib.Path, str]) -> str: - """Return fixture data, relative to __file__""" +def read_workspace_file( + _file: t.Union[pathlib.Path, str], +) -> str: + """Return fixture data, relative to __file__.""" if isinstance(_file, str): _file = pathlib.Path(_file) @@ -23,8 +26,11 @@ def read_workspace_file(_file: t.Union[pathlib.Path, str]) -> str: def write_config( - config_path: pathlib.Path, filename: str, content: str + config_path: pathlib.Path, + filename: str, + content: str, ) -> pathlib.Path: + """Write configuration content to file.""" config = config_path / filename config.write_text(content, encoding="utf-8") return config diff --git a/tests/fixtures/workspace/__init__.py b/tests/fixtures/workspace/__init__.py index 097409c1569..632b47fbeb9 100644 --- a/tests/fixtures/workspace/__init__.py +++ b/tests/fixtures/workspace/__init__.py @@ -1,3 +1,4 @@ +"""Workspace data fixtures for tmuxp tests.""" from . import ( expand1, expand2, diff --git a/tests/fixtures/workspace/expand1.py b/tests/fixtures/workspace/expand1.py index 540229b77a6..187111d86e0 100644 --- a/tests/fixtures/workspace/expand1.py +++ b/tests/fixtures/workspace/expand1.py @@ -1,3 +1,4 @@ +"""Examples of expansion of tmuxp configurations from shorthand style.""" import pathlib import typing as t @@ -32,6 +33,7 @@ def after_workspace() -> t.Dict[str, t.Any]: + """After expansion of shorthand style.""" return { "session_name": "sample workspace", "start_directory": str(pathlib.Path().home()), diff --git a/tests/fixtures/workspace/expand2.py b/tests/fixtures/workspace/expand2.py index aac7d52378a..335f13708b2 100644 --- a/tests/fixtures/workspace/expand2.py +++ b/tests/fixtures/workspace/expand2.py @@ -1,13 +1,16 @@ +"""YAML examples of expansion of tmuxp configurations from shorthand style.""" import pathlib from .. import utils as test_utils def unexpanded_yaml() -> str: + """Return unexpanded, shorthand YAML tmuxp configuration.""" return test_utils.read_workspace_file("workspace/expand2-unexpanded.yaml") def expanded_yaml() -> str: + """Return expanded, verbose YAML tmuxp configuration.""" return test_utils.read_workspace_file("workspace/expand2-expanded.yaml").format( HOME=str(pathlib.Path().home()) ) diff --git a/tests/fixtures/workspace/expand_blank.py b/tests/fixtures/workspace/expand_blank.py index 4d0dad3b490..d1fbd2e5504 100644 --- a/tests/fixtures/workspace/expand_blank.py +++ b/tests/fixtures/workspace/expand_blank.py @@ -1,3 +1,4 @@ +"""Expected expanded configuration for empty workspace panes.""" expected = { "session_name": "Blank pane test", "windows": [ diff --git a/tests/fixtures/workspace/sample_workspace.py b/tests/fixtures/workspace/sample_workspace.py index baff8d4c924..62ceae252a2 100644 --- a/tests/fixtures/workspace/sample_workspace.py +++ b/tests/fixtures/workspace/sample_workspace.py @@ -1,3 +1,4 @@ +"""Example workspace fixture for tmuxp WorkspaceBuilder.""" sample_workspace_dict = { "session_name": "sample workspace", "start_directory": "~", diff --git a/tests/fixtures/workspace/shell_command_before.py b/tests/fixtures/workspace/shell_command_before.py index 69f5c2f2b4e..cbb44250473 100644 --- a/tests/fixtures/workspace/shell_command_before.py +++ b/tests/fixtures/workspace/shell_command_before.py @@ -1,3 +1,4 @@ +"""Test fixture for tmuxp to demonstrate shell_command_before.""" import pathlib import typing as t @@ -39,6 +40,7 @@ def config_expanded() -> t.Dict[str, t.Any]: + """Return expanded configuration for shell_command_before example.""" return { # shell_command_before is string in some areas "session_name": "sample workspace", "start_directory": "/", @@ -91,6 +93,7 @@ def config_expanded() -> t.Dict[str, t.Any]: def config_after() -> t.Dict[str, t.Any]: + """Return expected configuration for shell_command_before example.""" return { # shell_command_before is string in some areas "session_name": "sample workspace", "start_directory": "/", diff --git a/tests/fixtures/workspace/shell_command_before_session.py b/tests/fixtures/workspace/shell_command_before_session.py index 79329382d4a..b103d666e54 100644 --- a/tests/fixtures/workspace/shell_command_before_session.py +++ b/tests/fixtures/workspace/shell_command_before_session.py @@ -1,3 +1,4 @@ +"""Tests shell_command_before configuration.""" from .. import utils as test_utils before = test_utils.read_workspace_file("workspace/shell_command_before_session.yaml") diff --git a/tests/fixtures/workspace/trickle.py b/tests/fixtures/workspace/trickle.py index 662ebaf45b0..68fa31a9df6 100644 --- a/tests/fixtures/workspace/trickle.py +++ b/tests/fixtures/workspace/trickle.py @@ -1,3 +1,4 @@ +"""Test data for tmuxp workspace fixture to demo object tree inheritance.""" before = { # shell_command_before is string in some areas "session_name": "sample workspace", "start_directory": "/var", diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 2151d7ce950..023ee332189 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,4 +1,4 @@ -"""Test for tmuxp plugin api.""" +"""Tests for tmuxp plugin API.""" import pytest from tmuxp.exc import TmuxpPluginException @@ -23,62 +23,73 @@ @pytest.fixture(autouse=True) def autopatch_sitedir(monkeypatch_plugin_test_packages: None) -> None: + """Fixture automatically used that patches sitedir.""" pass def test_all_pass() -> None: + """Plugin for tmuxp that loads successfully.""" AllVersionPassPlugin() def test_tmux_version_fail_min() -> None: + """Plugin raises if tmux version is below minimum constraint.""" with pytest.raises(TmuxpPluginException, match=r"Incompatible.*") as exc_info: TmuxVersionFailMinPlugin() assert "tmux-min-version-fail" in str(exc_info.value) def test_tmux_version_fail_max() -> None: + """Plugin raises if tmux version is above maximum constraint.""" with pytest.raises(TmuxpPluginException, match=r"Incompatible.*") as exc_info: TmuxVersionFailMaxPlugin() assert "tmux-max-version-fail" in str(exc_info.value) def test_tmux_version_fail_incompatible() -> None: + """Plugin raises if tmuxp version is incompatible.""" with pytest.raises(TmuxpPluginException, match=r"Incompatible.*") as exc_info: TmuxVersionFailIncompatiblePlugin() assert "tmux-incompatible-version-fail" in str(exc_info.value) def test_tmuxp_version_fail_min() -> None: + """Plugin raises if tmuxp version is below minimum constraint.""" with pytest.raises(TmuxpPluginException, match=r"Incompatible.*") as exc_info: TmuxpVersionFailMinPlugin() assert "tmuxp-min-version-fail" in str(exc_info.value) def test_tmuxp_version_fail_max() -> None: + """Plugin raises if tmuxp version is above max constraint.""" with pytest.raises(TmuxpPluginException, match=r"Incompatible.*") as exc_info: TmuxpVersionFailMaxPlugin() assert "tmuxp-max-version-fail" in str(exc_info.value) def test_tmuxp_version_fail_incompatible() -> None: + """Plugin raises if libtmux version is below minimum constraint.""" with pytest.raises(TmuxpPluginException, match=r"Incompatible.*") as exc_info: TmuxpVersionFailIncompatiblePlugin() assert "tmuxp-incompatible-version-fail" in str(exc_info.value) def test_libtmux_version_fail_min() -> None: + """Plugin raises if libtmux version is below minimum constraint.""" with pytest.raises(TmuxpPluginException, match=r"Incompatible.*") as exc_info: LibtmuxVersionFailMinPlugin() assert "libtmux-min-version-fail" in str(exc_info.value) def test_libtmux_version_fail_max() -> None: + """Plugin raises if libtmux version is above max constraint.""" with pytest.raises(TmuxpPluginException, match=r"Incompatible.*") as exc_info: LibtmuxVersionFailMaxPlugin() assert "libtmux-max-version-fail" in str(exc_info.value) def test_libtmux_version_fail_incompatible() -> None: + """Plugin raises if libtmux version is incompatible.""" with pytest.raises(TmuxpPluginException, match=r"Incompatible.*") as exc_info: LibtmuxVersionFailIncompatiblePlugin() assert "libtmux-incompatible-version-fail" in str(exc_info.value) diff --git a/tests/test_shell.py b/tests/test_shell.py index 2f87bddba9b..4fe574d4287 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -1,12 +1,15 @@ +"""Tests for tmuxp shell module.""" from tmuxp import shell def test_detect_best_shell() -> None: + """detect_best_shell() returns a a string of the best shell.""" result = shell.detect_best_shell() assert isinstance(result, str) def test_shell_detect() -> None: + """Tests shell detection functions.""" assert isinstance(shell.has_bpython(), bool) assert isinstance(shell.has_ipython(), bool) assert isinstance(shell.has_ptpython(), bool) diff --git a/tests/test_util.py b/tests/test_util.py index dbb4f8b5e2c..d8a24527ad9 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,4 +1,4 @@ -"""Tests for utility functions in tmux.""" +"""Tests for tmuxp's utility functions.""" import pytest from libtmux.server import Server @@ -9,7 +9,8 @@ from .constants import FIXTURE_PATH -def test_raise_BeforeLoadScriptNotExists_if_not_exists() -> None: +def test_run_before_script_raise_BeforeLoadScriptNotExists_if_not_exists() -> None: + """run_before_script() raises BeforeLoadScriptNotExists if script not found.""" script_file = FIXTURE_PATH / "script_noexists.sh" with pytest.raises(BeforeLoadScriptNotExists): @@ -19,7 +20,8 @@ def test_raise_BeforeLoadScriptNotExists_if_not_exists() -> None: run_before_script(script_file) -def test_raise_BeforeLoadScriptError_if_retcode() -> None: +def test_run_before_script_raise_BeforeLoadScriptError_if_retcode() -> None: + """run_before_script() raises BeforeLoadScriptNotExists if script fails.""" script_file = FIXTURE_PATH / "script_failed.sh" with pytest.raises(BeforeLoadScriptError): @@ -27,6 +29,7 @@ def test_raise_BeforeLoadScriptError_if_retcode() -> None: def test_return_stdout_if_ok(capsys: pytest.CaptureFixture[str]) -> None: + """run_before_script() returns stdout if script succeeds.""" script_file = FIXTURE_PATH / "script_complete.sh" run_before_script(script_file) @@ -35,6 +38,7 @@ def test_return_stdout_if_ok(capsys: pytest.CaptureFixture[str]) -> None: def test_beforeload_returncode() -> None: + """run_before_script() returns returncode if script fails.""" script_file = FIXTURE_PATH / "script_failed.sh" with pytest.raises(exc.BeforeLoadScriptError) as excinfo: @@ -43,6 +47,7 @@ def test_beforeload_returncode() -> None: def test_beforeload_returns_stderr_messages() -> None: + """run_before_script() returns stderr messages if script fails.""" script_file = FIXTURE_PATH / "script_failed.sh" with pytest.raises(exc.BeforeLoadScriptError) as excinfo: @@ -51,8 +56,10 @@ def test_beforeload_returns_stderr_messages() -> None: def test_get_session_should_default_to_local_attached_session( - server: Server, monkeypatch: pytest.MonkeyPatch + server: Server, + monkeypatch: pytest.MonkeyPatch, ) -> None: + """get_session() should launch current terminal's tmux session, if inside one.""" server.new_session(session_name="myfirstsession") second_session = server.new_session(session_name="mysecondsession") @@ -67,6 +74,7 @@ def test_get_session_should_default_to_local_attached_session( def test_get_session_should_return_first_session_if_no_active_session( server: Server, ) -> None: + """get_session() should return first session if no active session.""" first_session = server.new_session(session_name="myfirstsession") server.new_session(session_name="mysecondsession") diff --git a/tests/tests/__init__.py b/tests/tests/__init__.py index e69de29bb2d..f887a3d06e1 100644 --- a/tests/tests/__init__.py +++ b/tests/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for tmuxp's pytest helpers.""" diff --git a/tests/tests/test_helpers.py b/tests/tests/test_helpers.py index 5928f73937a..22b04d87b1b 100644 --- a/tests/tests/test_helpers.py +++ b/tests/tests/test_helpers.py @@ -1,10 +1,11 @@ -"""Tests for .'s helper and utility functions.""" +"""Tests for tmuxp's helper and utility functions.""" import pytest from libtmux.server import Server from libtmux.test import get_test_session_name, temp_session -def test_kills_session(server: Server) -> None: +def test_temp_session_kills_session_on_exit(server: Server) -> None: + """Test temp_session() context manager kills session on exit.""" server = server session_name = get_test_session_name(server=server) @@ -16,9 +17,8 @@ def test_kills_session(server: Server) -> None: @pytest.mark.flaky(reruns=5) -def test_if_session_killed_before(server: Server) -> None: - """Handles situation where session already closed within context""" - +def test_temp_session_if_session_killed_before_exit(server: Server) -> None: + """Handles situation where session already closed within context.""" server = server session_name = get_test_session_name(server=server) diff --git a/tests/workspace/__init__.py b/tests/workspace/__init__.py index e69de29bb2d..ec8ccc92b49 100644 --- a/tests/workspace/__init__.py +++ b/tests/workspace/__init__.py @@ -0,0 +1 @@ +"""Workspace tests for tmuxp.""" diff --git a/tests/workspace/conftest.py b/tests/workspace/conftest.py index 932055675af..286acc8c6cd 100644 --- a/tests/workspace/conftest.py +++ b/tests/workspace/conftest.py @@ -1,3 +1,4 @@ +"""Pytest configuration for tmuxp workspace tests.""" import types import pytest @@ -7,7 +8,7 @@ @pytest.fixture def config_fixture() -> WorkspaceTestData: - """Deferred import of tmuxp.tests.fixtures.* + """Deferred import of tmuxp.tests.fixtures.*. pytest setup (conftest.py) patches os.environ["HOME"], delay execution of os.path.expanduser until here. diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index 87d519cf218..24a00b0603a 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -1,4 +1,4 @@ -"""Test for tmuxp workspacebuilder.""" +"""Test for tmuxp workspace builder.""" import functools import os import pathlib @@ -29,11 +29,15 @@ if t.TYPE_CHECKING: class AssertCallbackProtocol(t.Protocol): + """Assertion callback type protocol.""" + def __call__(self, cmd: str, hist: str) -> bool: + """Run function code for testing assertion.""" ... def test_split_windows(session: Session) -> None: + """Test workspace builder splits windows in a tmux session.""" workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/two_pane.yaml") ) @@ -54,6 +58,7 @@ def test_split_windows(session: Session) -> None: def test_split_windows_three_pane(session: Session) -> None: + """Test workspace builder splits windows in a tmux session.""" workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/three_pane.yaml") ) @@ -76,6 +81,7 @@ def test_split_windows_three_pane(session: Session) -> None: def test_focus_pane_index(session: Session) -> None: + """Test focus of pane by index works correctly, including with pane-base-index.""" workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/focus_and_pane.yaml") ) @@ -154,6 +160,7 @@ def f_check_again() -> bool: """.strip() ) def test_suppress_history(session: Session) -> None: + """Test suppression of command history.""" workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/suppress_history.yaml") ) @@ -213,6 +220,7 @@ def f(p: Pane, buffer_name: str, assertCase: AssertCallbackProtocol) -> bool: def test_session_options(session: Session) -> None: + """Test setting of options to session scope.""" workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/session_options.yaml") ) @@ -231,6 +239,7 @@ def test_session_options(session: Session) -> None: def test_global_options(session: Session) -> None: + """Test setting of global options.""" workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/global_options.yaml") ) @@ -246,8 +255,10 @@ def test_global_options(session: Session) -> None: def test_global_session_env_options( - session: Session, monkeypatch: pytest.MonkeyPatch + session: Session, + monkeypatch: pytest.MonkeyPatch, ) -> None: + """Test setting of global option variables.""" visual_silence = "on" monkeypatch.setenv("VISUAL_SILENCE", str(visual_silence)) repeat_time = 738 @@ -272,7 +283,10 @@ def test_global_session_env_options( ) -def test_window_options(session: Session) -> None: +def test_window_options( + session: Session, +) -> None: + """Test setting of window options.""" workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/window_options.yaml") ) @@ -301,7 +315,10 @@ def test_window_options(session: Session) -> None: @pytest.mark.flaky(reruns=5) -def test_window_options_after(session: Session) -> None: +def test_window_options_after( + session: Session, +) -> None: + """Test setting window options via options_after (WorkspaceBuilder.after_window).""" workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/window_options_after.yaml") ) @@ -339,7 +356,10 @@ def f() -> bool: ), "Synchronized command did not execute properly" -def test_window_shell(session: Session) -> None: +def test_window_shell( + session: Session, +) -> None: + """Test execution of commands via tmuxp configuration.""" workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/window_shell.yaml") ) @@ -365,7 +385,10 @@ def f(w: Window) -> bool: has_lt_version("3.0"), reason="needs -e flag for new-window and split-window introduced in tmux 3.0", ) -def test_environment_variables(session: Session) -> None: +def test_environment_variables( + session: Session, +) -> None: + """Test setting of environmental variables in tmux via workspace builder.""" workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/environment_vars.yaml") ) @@ -407,9 +430,11 @@ def test_environment_variables(session: Session) -> None: has_gte_version("3.0"), reason="warnings are not needed for tmux >= 3.0", ) -def test_environment_variables_logs( - session: Session, caplog: pytest.LogCaptureFixture +def test_environment_variables_warns_prior_to_tmux_3_0( + session: Session, + caplog: pytest.LogCaptureFixture, ) -> None: + """Warns when environmental variables cannot be set prior to tmux 3.0.""" workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/environment_vars.yaml") ) @@ -454,8 +479,10 @@ def test_environment_variables_logs( def test_automatic_rename_option( - server: "Server", monkeypatch: pytest.MonkeyPatch + server: "Server", + monkeypatch: pytest.MonkeyPatch, ) -> None: + """Test workspace builder with automatic renaming enabled.""" monkeypatch.setenv("DISABLE_AUTO_TITLE", "true") workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/window_automatic_rename.yaml") @@ -497,8 +524,13 @@ def check_window_name_match() -> bool: assert retry_until(check_window_name_mismatch, 2, interval=0.25) -def test_blank_pane_count(session: Session) -> None: - """:todo: Verify blank panes of various types build into workspaces.""" +def test_blank_pane_spawn( + session: Session, +) -> None: + """Test various ways of spawning blank panes from a tmuxp configuration. + + :todo: Verify blank panes of various types build into workspaces. + """ yaml_workspace_file = EXAMPLE_PATH / "blank-panes.yaml" test_config = ConfigReader._from_file(yaml_workspace_file) @@ -526,6 +558,7 @@ def test_blank_pane_count(session: Session) -> None: def test_start_directory(session: Session, tmp_path: pathlib.Path) -> None: + """Test workspace builder setting start_directory relative to current directory.""" test_dir = tmp_path / "foo bar" test_dir.mkdir() @@ -560,7 +593,9 @@ def f(path: str, p: Pane) -> bool: def test_start_directory_relative(session: Session, tmp_path: pathlib.Path) -> None: - """Same as above test, but with relative start directory, mimicking + """Test workspace builder setting start_directory relative to project file. + + Same as above test, but with relative start directory, mimicking loading it from a location of project file. Like:: $ tmuxp load ~/workspace/myproject/.tmuxp.yaml @@ -616,6 +651,7 @@ def f(path: str, p: Pane) -> bool: has_lt_version("3.2a"), reason="needs format introduced in tmux >= 3.2a" ) def test_start_directory_sets_session_path(server: Server) -> None: + """Test start_directory setting path in session_path.""" workspace = ConfigReader._from_file( test_utils.get_workspace_file( "workspace/builder/start_directory_session_path.yaml" @@ -688,7 +724,10 @@ def f(pane_path: str, p: Pane) -> bool: assert retry_until(_f) -def test_window_index(session: Session) -> None: +def test_window_index( + session: Session, +) -> None: + """Test window_index respected by workspace builder.""" proc = session.cmd("show-option", "-gv", "base-index") base_index = int(proc.stdout[0]) name_index_map = {"zero": 0 + base_index, "one": 1 + base_index, "five": 5} @@ -706,7 +745,10 @@ def test_window_index(session: Session) -> None: assert int(window.window_index) == expected_index -def test_before_load_throw_error_if_retcode_error(server: Server) -> None: +def test_before_script_throw_error_if_retcode_error( + server: Server, +) -> None: + """Test tmuxp configuration before_script when command fails.""" config_script_fails = test_utils.read_workspace_file( "workspace/builder/config_script_fails.yaml" ) @@ -731,7 +773,10 @@ def test_before_load_throw_error_if_retcode_error(server: Server) -> None: assert not result, "Kills session if before_script exits with errcode" -def test_before_load_throw_error_if_file_not_exists(server: Server) -> None: +def test_before_script_throw_error_if_file_not_exists( + server: Server, +) -> None: + """Test tmuxp configuration before_script when script does not exist.""" config_script_not_exists = test_utils.read_workspace_file( "workspace/builder/config_script_not_exists.yaml" ) @@ -757,7 +802,10 @@ def test_before_load_throw_error_if_file_not_exists(server: Server) -> None: assert not result, "Kills session if before_script doesn't exist" -def test_before_load_true_if_test_passes(server: Server) -> None: +def test_before_script_true_if_test_passes( + server: Server, +) -> None: + """Test tmuxp configuration before_script when command succeeds.""" config_script_completes = test_utils.read_workspace_file( "workspace/builder/config_script_completes.yaml" ) @@ -775,7 +823,10 @@ def test_before_load_true_if_test_passes(server: Server) -> None: builder.build(session=session) -def test_before_load_true_if_test_passes_with_args(server: Server) -> None: +def test_before_script_true_if_test_passes_with_args( + server: Server, +) -> None: + """Test tmuxp configuration before_script when command passes w/ args.""" config_script_completes = test_utils.read_workspace_file( "workspace/builder/config_script_completes.yaml" ) @@ -795,8 +846,10 @@ def test_before_load_true_if_test_passes_with_args(server: Server) -> None: def test_plugin_system_before_workspace_builder( - monkeypatch_plugin_test_packages: None, session: Session + monkeypatch_plugin_test_packages: None, + session: Session, ) -> None: + """Test tmuxp configuration plugin hook before workspace builder starts.""" workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/plugin_bwb.yaml") ) @@ -814,8 +867,10 @@ def test_plugin_system_before_workspace_builder( def test_plugin_system_on_window_create( - monkeypatch_plugin_test_packages: None, session: Session + monkeypatch_plugin_test_packages: None, + session: Session, ) -> None: + """Test tmuxp configuration plugin hooks work on window creation.""" workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/plugin_owc.yaml") ) @@ -833,8 +888,10 @@ def test_plugin_system_on_window_create( def test_plugin_system_after_window_finished( - monkeypatch_plugin_test_packages: None, session: Session + monkeypatch_plugin_test_packages: None, + session: Session, ) -> None: + """Test tmuxp configuration plugin hooks work after windows created.""" workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/plugin_awf.yaml") ) @@ -851,7 +908,10 @@ def test_plugin_system_after_window_finished( assert proc.stdout[0] == "'plugin_test_awf'" -def test_plugin_system_on_window_create_multiple_windows(session: Session) -> None: +def test_plugin_system_on_window_create_multiple_windows( + session: Session, +) -> None: + """Test tmuxp configuration plugin hooks work on windows creation.""" workspace = ConfigReader._from_file( path=test_utils.get_workspace_file( "workspace/builder/plugin_owc_multiple_windows.yaml" @@ -872,8 +932,10 @@ def test_plugin_system_on_window_create_multiple_windows(session: Session) -> No def test_plugin_system_after_window_finished_multiple_windows( - monkeypatch_plugin_test_packages: None, session: Session + monkeypatch_plugin_test_packages: None, + session: Session, ) -> None: + """Test tmuxp configuration plugin hooks work after windows created.""" workspace = ConfigReader._from_file( path=test_utils.get_workspace_file( "workspace/builder/plugin_awf_multiple_windows.yaml" @@ -894,8 +956,10 @@ def test_plugin_system_after_window_finished_multiple_windows( def test_plugin_system_multiple_plugins( - monkeypatch_plugin_test_packages: None, session: Session + monkeypatch_plugin_test_packages: None, + session: Session, ) -> None: + """Test tmuxp plugin system works with multiple plugins.""" workspace = ConfigReader._from_file( path=test_utils.get_workspace_file( "workspace/builder/plugin_multiple_plugins.yaml" @@ -921,7 +985,10 @@ def test_plugin_system_multiple_plugins( assert proc.stdout[0] == "'mp_test_awf'" -def test_load_configs_same_session(server: Server) -> None: +def test_load_configs_same_session( + server: Server, +) -> None: + """Test tmuxp configuration can be loaded into same session.""" workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/three_windows.yaml") ) @@ -952,7 +1019,10 @@ def test_load_configs_same_session(server: Server) -> None: assert len(server.sessions[1].windows) == 4 -def test_load_configs_separate_sessions(server: Server) -> None: +def test_load_configs_separate_sessions( + server: Server, +) -> None: + """Test workspace builder can load configuration in separate session.""" workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/three_windows.yaml") ) @@ -976,8 +1046,10 @@ def test_load_configs_separate_sessions(server: Server) -> None: def test_find_current_active_pane( - server: Server, monkeypatch: pytest.MonkeyPatch + server: Server, + monkeypatch: pytest.MonkeyPatch, ) -> None: + """Tests workspace builder can find the current active pane (and session).""" workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/three_windows.yaml") ) @@ -1135,6 +1207,7 @@ def test_load_workspace_enter( output: str, should_see: bool, ) -> None: + """Test workspace enters commands to panes in tmuxp configuration.""" yaml_workspace = tmp_path / "simple.yaml" yaml_workspace.write_text(yaml, encoding="utf-8") workspace = ConfigReader._from_file(yaml_workspace) @@ -1257,6 +1330,7 @@ def test_load_workspace_sleep( sleep: int, output: str, ) -> None: + """Test sleep commands in tmuxp configuration.""" yaml_workspace = tmp_path / "simple.yaml" yaml_workspace.write_text(yaml, encoding="utf-8") workspace = ConfigReader._from_file(yaml_workspace) @@ -1290,6 +1364,7 @@ def test_load_workspace_sleep( def test_first_pane_start_directory(session: Session, tmp_path: pathlib.Path) -> None: + """Test the first pane start_directory sticks.""" yaml_workspace = test_utils.get_workspace_file( "workspace/builder/first_pane_start_directory.yaml" ) @@ -1322,6 +1397,7 @@ def f(path: str, p: Pane) -> bool: has_lt_version("2.9"), reason="needs option introduced in tmux >= 2.9" ) def test_layout_main_horizontal(session: Session) -> None: + """Test that tmux's main-horizontal layout is used when specified.""" yaml_workspace = test_utils.get_workspace_file("workspace/builder/three_pane.yaml") workspace = ConfigReader._from_file(path=yaml_workspace) @@ -1359,7 +1435,12 @@ def is_almost_equal(x: int, y: int) -> bool: class DefaultSizeNamespaceFixture(t.NamedTuple): + """Pytest fixture default-size option in tmuxp workspace builder.""" + + # pytest parametrize needs a unique id for each fixture test_id: str + + # test params TMUXP_DEFAULT_SIZE: t.Optional[str] raises: bool confoverrides: t.Dict[str, t.Any] diff --git a/tests/workspace/test_config.py b/tests/workspace/test_config.py index 5bef626f964..db3023dcaff 100644 --- a/tests/workspace/test_config.py +++ b/tests/workspace/test_config.py @@ -14,13 +14,8 @@ from ..fixtures.structures import WorkspaceTestData -def load_yaml(path: t.Union[str, pathlib.Path]) -> t.Dict[str, t.Any]: - return ConfigReader._from_file( - pathlib.Path(path) if isinstance(path, str) else path - ) - - def load_workspace(path: t.Union[str, pathlib.Path]) -> t.Dict[str, t.Any]: + """Load tmuxp workspace configuration from file.""" return ConfigReader._from_file( pathlib.Path(path) if isinstance(path, str) else path ) @@ -29,6 +24,7 @@ def load_workspace(path: t.Union[str, pathlib.Path]) -> t.Dict[str, t.Any]: def test_export_json( tmp_path: pathlib.Path, config_fixture: "WorkspaceTestData" ) -> None: + """Test exporting configuration dictionary to JSON.""" json_workspace_file = tmp_path / "config.json" configparser = ConfigReader(config_fixture.sample_workspace.sample_workspace_dict) @@ -41,39 +37,6 @@ def test_export_json( assert config_fixture.sample_workspace.sample_workspace_dict == new_workspace_data -# -# There's no tests for this -# -def test_find_workspace_file(tmp_path: pathlib.Path) -> None: - configs = [str(tmp_path / x) for x in tmp_path.rglob("*.[json][ini][yaml]")] - - garbage_file = tmp_path / "config.psd" - garbage_file.write_text("wat", encoding="utf-8") - - # for _r, _d, f in os.walk(str(tmp_path)): - # configs.extend( - # [str(tmp_path / x) for x in f if x.endswith((".json", ".ini", "yaml"))] - # ) - - files = 0 - config_json = tmp_path / "config.json" - config_yaml = tmp_path / "config.yaml" - config_ini = tmp_path / "config.ini" - if config_json.exists(): - files += 1 - assert str(config_json) in configs - - if config_yaml.exists(): - files += 1 - assert str(config_yaml) in configs - - if config_ini.exists(): - files += 1 - assert str(config_ini) in configs - - assert len(configs) == files - - def test_workspace_expand1(config_fixture: "WorkspaceTestData") -> None: """Expand shell commands from string to list.""" test_workspace = loader.expand(config_fixture.expand1.before_workspace) @@ -133,6 +96,7 @@ def test_workspace_expand2(config_fixture: "WorkspaceTestData") -> None: def test_inheritance_workspace() -> None: + """TODO: Create a test to verify workspace config inheritance to object tree.""" workspace = inheritance_workspace_before # TODO: Look at verifying window_start_directory @@ -172,6 +136,7 @@ def test_shell_command_before(config_fixture: "WorkspaceTestData") -> None: def test_in_session_scope(config_fixture: "WorkspaceTestData") -> None: + """Verify shell_command before_session is in session scope.""" sconfig = ConfigReader._load( format="yaml", content=config_fixture.shell_command_before_session.before ) @@ -185,11 +150,13 @@ def test_in_session_scope(config_fixture: "WorkspaceTestData") -> None: def test_trickle_relative_start_directory(config_fixture: "WorkspaceTestData") -> None: + """Verify tmuxp config proliferates relative start directory to descendants.""" test_workspace = loader.trickle(config_fixture.trickle.before) assert test_workspace == config_fixture.trickle.expected def test_trickle_window_with_no_pane_workspace() -> None: + """Verify tmuxp window config automatically infers a single pane.""" test_yaml = """ session_name: test_session windows: @@ -240,6 +207,7 @@ def test_expands_blank_panes(config_fixture: "WorkspaceTestData") -> None: def test_no_session_name() -> None: + """Verify exception raised when tmuxp configuration has no session name.""" yaml_workspace = """ - window_name: editor panes: @@ -261,6 +229,7 @@ def test_no_session_name() -> None: def test_no_windows() -> None: + """Verify exception raised when tmuxp configuration has no windows.""" yaml_workspace = """ session_name: test session """ @@ -273,6 +242,7 @@ def test_no_windows() -> None: def test_no_window_name() -> None: + """Verify exception raised when tmuxp config missing window name.""" yaml_workspace = """ session_name: test session windows: @@ -295,6 +265,7 @@ def test_no_window_name() -> None: def test_replaces_env_variables(monkeypatch: pytest.MonkeyPatch) -> None: + """Test loading configuration resolves environmental variables.""" env_key = "TESTHEY92" env_val = "HEYO1" yaml_workspace = """ @@ -335,7 +306,8 @@ def test_replaces_env_variables(monkeypatch: pytest.MonkeyPatch) -> None: assert "logging @ %s" % env_val == sconfig["windows"][1]["window_name"] -def test_plugins() -> None: +def test_validate_plugins() -> None: + """Test validation of plugins loading via tmuxp configuration file.""" yaml_workspace = """ session_name: test session plugins: tmuxp-plugin-one.plugin.TestPluginOne diff --git a/tests/workspace/test_finder.py b/tests/workspace/test_finder.py index a1e79a7d17f..fb8cb7da801 100644 --- a/tests/workspace/test_finder.py +++ b/tests/workspace/test_finder.py @@ -1,3 +1,4 @@ +"""Test config file searching for tmuxp.""" import argparse import pathlib import typing as t @@ -20,7 +21,6 @@ def test_in_dir_from_config_dir(tmp_path: pathlib.Path) -> None: """config.in_dir() finds configs config dir.""" - cli.startup(tmp_path) yaml_config = tmp_path / "myconfig.yaml" yaml_config.touch() @@ -33,7 +33,6 @@ def test_in_dir_from_config_dir(tmp_path: pathlib.Path) -> None: def test_ignore_non_configs_from_current_dir(tmp_path: pathlib.Path) -> None: """cli.in_dir() ignore non-config from config dir.""" - cli.startup(tmp_path) junk_config = tmp_path / "myconfig.psd" @@ -48,7 +47,6 @@ def test_get_configs_cwd( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch ) -> None: """config.in_cwd() find config in shell current working directory.""" - confdir = tmp_path / "tmuxpconf2" confdir.mkdir() @@ -78,12 +76,14 @@ def test_get_configs_cwd( ], ) def test_is_pure_name(path: str, expect: bool) -> None: + """Test is_pure_name() is truthy when file, not directory or config alias.""" assert is_pure_name(path) == expect def test_tmuxp_configdir_env_var( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch ) -> None: + """Tests get_workspace_dir() when TMUXP_CONFIGDIR is set.""" monkeypatch.setenv("TMUXP_CONFIGDIR", str(tmp_path)) assert get_workspace_dir() == str(tmp_path) @@ -92,6 +92,7 @@ def test_tmuxp_configdir_env_var( def test_tmuxp_configdir_xdg_config_dir( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch ) -> None: + """Test get_workspace_dir() when XDG_CONFIG_HOME is set.""" monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path)) tmux_dir = tmp_path / "tmuxp" tmux_dir.mkdir() @@ -101,6 +102,7 @@ def test_tmuxp_configdir_xdg_config_dir( @pytest.fixture def homedir(tmp_path: pathlib.Path) -> pathlib.Path: + """Fixture to ensure and return a home directory.""" home = tmp_path / "home" home.mkdir() return home @@ -108,6 +110,7 @@ def homedir(tmp_path: pathlib.Path) -> pathlib.Path: @pytest.fixture def configdir(homedir: pathlib.Path) -> pathlib.Path: + """Fixture to ensure user directory for tmuxp and return it, via homedir fixture.""" conf = homedir / ".tmuxp" conf.mkdir() return conf @@ -115,6 +118,7 @@ def configdir(homedir: pathlib.Path) -> pathlib.Path: @pytest.fixture def projectdir(homedir: pathlib.Path) -> pathlib.Path: + """Fixture to ensure and return an example project dir.""" proj = homedir / "work" / "project" proj.mkdir(parents=True) return proj @@ -127,6 +131,7 @@ def test_resolve_dot( projectdir: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: + """Test find_workspace_file() resolves dots as relative / current directory.""" monkeypatch.setenv("HOME", str(homedir)) monkeypatch.setenv("XDG_CONFIG_HOME", str(homedir / ".config")) @@ -242,6 +247,7 @@ def test_find_workspace_file_arg( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: + """Test find_workspace_file() via file path.""" parser = argparse.ArgumentParser() parser.add_argument("workspace_file", type=str) diff --git a/tests/workspace/test_freezer.py b/tests/workspace/test_freezer.py index 718ab13c4d8..1447b7f4d14 100644 --- a/tests/workspace/test_freezer.py +++ b/tests/workspace/test_freezer.py @@ -1,4 +1,4 @@ -"""Tests for freezing tmux sessions with tmuxp.""" +"""Tests tmux session freezing functionality for tmuxp.""" import pathlib import time import typing @@ -16,6 +16,7 @@ def test_freeze_config(session: Session) -> None: + """Test freezing a tmux session.""" session_config = ConfigReader._from_file( test_utils.get_workspace_file("workspace/freezer/sample_workspace.yaml") ) @@ -80,7 +81,6 @@ def test_freeze_config(session: Session) -> None: def test_inline_workspace() -> None: """:meth:`freezer.inline()` shell commands list to string.""" - test_workspace = freezer.inline(ibefore_workspace) assert test_workspace == iafter_workspace @@ -88,6 +88,7 @@ def test_inline_workspace() -> None: def test_export_yaml( tmp_path: pathlib.Path, config_fixture: "WorkspaceTestData" ) -> None: + """Test exporting a frozen tmux session to YAML.""" yaml_workspace_file = tmp_path / "config.yaml" sample_workspace = freezer.inline( diff --git a/tests/workspace/test_import_teamocil.py b/tests/workspace/test_import_teamocil.py index 12ddc0513f3..f7d1f2dfa49 100644 --- a/tests/workspace/test_import_teamocil.py +++ b/tests/workspace/test_import_teamocil.py @@ -39,6 +39,7 @@ def test_config_to_dict( teamocil_dict: t.Dict[str, t.Any], tmuxp_dict: t.Dict[str, t.Any], ) -> None: + """Test exporting teamocil configuration to dictionary.""" yaml_to_dict = config_reader.ConfigReader._load( format="yaml", content=teamocil_yaml ) @@ -59,7 +60,8 @@ def multisession_config() -> ( """Return loaded multisession teamocil config as a dictionary. Also prevents re-running assertion the loads the yaml, since ordering of - deep list items like panes will be inconsistent.""" + deep list items like panes will be inconsistent. + """ teamocil_yaml_file = fixtures.layouts.teamocil_yaml_file test_config = config_reader.ConfigReader._from_file(teamocil_yaml_file) teamocil_dict: t.Dict[str, t.Any] = fixtures.layouts.teamocil_dict @@ -88,6 +90,7 @@ def test_multisession_config( expected: t.Dict[str, t.Any], multisession_config: t.Dict[str, t.Any], ) -> None: + """Test importing teamocil multisession configuration.""" # teamocil can fit multiple sessions in a config assert importers.import_teamocil(multisession_config[session_name]) == expected diff --git a/tests/workspace/test_import_tmuxinator.py b/tests/workspace/test_import_tmuxinator.py index f68f9243b3a..f478312c5be 100644 --- a/tests/workspace/test_import_tmuxinator.py +++ b/tests/workspace/test_import_tmuxinator.py @@ -34,6 +34,7 @@ def test_config_to_dict( tmuxinator_dict: t.Dict[str, t.Any], tmuxp_dict: t.Dict[str, t.Any], ) -> None: + """Test exporting tmuxinator configuration to dictionary.""" yaml_to_dict = ConfigReader._load(format="yaml", content=tmuxinator_yaml) assert yaml_to_dict == tmuxinator_dict