diff --git a/CHANGES b/CHANGES index 6327f36f515..728719430a6 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,12 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force +### Internal improvements + +- CLI Tests: Refactor `tests/cli` (#858) + + - Fix resolution of direcotries + ## tmuxp 1.23.0 (_yanked_, 2022-12-28) _Yanked release: `tmuxp load` issues, see #856_ diff --git a/tests/cli/__init__.py b/tests/cli/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py new file mode 100644 index 00000000000..87c76fce6bf --- /dev/null +++ b/tests/cli/test_cli.py @@ -0,0 +1,139 @@ +import argparse +import os +import pathlib +import typing as t + +import pytest + +import libtmux +from libtmux.server import Server +from tmuxp import cli +from tmuxp.cli.import_config import get_teamocil_dir, get_tmuxinator_dir +from tmuxp.cli.load import _reattach, load_plugins +from tmuxp.cli.utils import tmuxp_echo +from tmuxp.config_reader import ConfigReader +from tmuxp.workspace import loader +from tmuxp.workspace.builder import WorkspaceBuilder +from tmuxp.workspace.finders import find_workspace_file + +from ..fixtures import utils as test_utils + +if t.TYPE_CHECKING: + import _pytest.capture + + +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 os.path.exists(tmp_path) + + +@pytest.mark.parametrize( + "cli_args", + [ + (["--help"]), + (["-h"]), + ], +) +def test_help( + cli_args: t.List[str], + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture, +) -> None: + try: + cli.cli(cli_args) + except SystemExit: + pass + result = capsys.readouterr() + + assert "usage: tmuxp [-h] [--version] [--log-level log-level]" in result.out + + +def test_resolve_behavior( + tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch +) -> None: + expect = tmp_path + monkeypatch.chdir(tmp_path) + assert pathlib.Path("../").resolve() == pathlib.Path(os.path.dirname(expect)) + assert pathlib.Path(".").resolve() == expect + assert pathlib.Path("./").resolve() == expect + assert pathlib.Path(expect).resolve() == expect + + +def test_get_tmuxinator_dir(monkeypatch: pytest.MonkeyPatch) -> None: + assert get_tmuxinator_dir() == os.path.expanduser("~/.tmuxinator/") + + monkeypatch.setenv("HOME", "/moo") + assert get_tmuxinator_dir() == "/moo/.tmuxinator/" + assert get_tmuxinator_dir() == os.path.expanduser("~/.tmuxinator/") + + +def test_get_teamocil_dir(monkeypatch: pytest.MonkeyPatch) -> None: + assert get_teamocil_dir() == os.path.expanduser("~/.teamocil/") + + monkeypatch.setenv("HOME", "/moo") + assert get_teamocil_dir() == "/moo/.teamocil/" + assert get_teamocil_dir() == os.path.expanduser("~/.teamocil/") + + +def test_pass_config_dir_ClickPath( + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture, +) -> None: + + configdir = tmp_path / "myconfigdir" + configdir.mkdir() + user_config_name = "myconfig" + user_config = configdir / f"{user_config_name}.yaml" + user_config.touch() + + expect = str(user_config) + + parser = argparse.ArgumentParser() + parser.add_argument("workspace_file", type=str) + + def config_cmd(workspace_file: str) -> None: + tmuxp_echo(find_workspace_file(workspace_file, workspace_dir=configdir)) + + def check_cmd(config_arg) -> "_pytest.capture.CaptureResult": + args = parser.parse_args([config_arg]) + config_cmd(workspace_file=args.workspace_file) + return capsys.readouterr() + + monkeypatch.chdir(configdir) + + assert expect in check_cmd("myconfig").out + assert expect in check_cmd("myconfig.yaml").out + assert expect in check_cmd("./myconfig.yaml").out + assert str(user_config) in check_cmd(str(configdir / "myconfig.yaml")).out + + with pytest.raises(FileNotFoundError): + assert "FileNotFoundError" in check_cmd(".tmuxp.json").out + + +def test_reattach_plugins( + monkeypatch_plugin_test_packages: None, server: "Server" +) -> None: + config_plugins = test_utils.read_workspace_file("workspace/builder/plugin_r.yaml") + + sconfig = ConfigReader._load(format="yaml", content=config_plugins) + sconfig = loader.expand(sconfig) + + # open it detached + builder = WorkspaceBuilder( + sconf=sconfig, plugins=load_plugins(sconfig), server=server + ) + builder.build() + + try: + _reattach(builder) + except libtmux.exc.LibTmuxException: + pass + + assert builder.session is not None + proc = builder.session.cmd("display-message", "-p", "'#S'") + + assert proc.stdout[0] == "'plugin_test_r'" diff --git a/tests/cli/test_convert.py b/tests/cli/test_convert.py new file mode 100644 index 00000000000..b923c4ea1d2 --- /dev/null +++ b/tests/cli/test_convert.py @@ -0,0 +1,86 @@ +import io +import json +import pathlib +import typing as t + +import pytest + +from tmuxp import cli + + +@pytest.mark.parametrize( + "cli_args", + [ + (["convert", "."]), + (["convert", ".tmuxp.yaml"]), + (["convert", ".tmuxp.yaml", "-y"]), + (["convert", ".tmuxp.yml"]), + (["convert", ".tmuxp.yml", "-y"]), + ], +) +def test_convert( + cli_args: t.List[str], + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + # create dummy tmuxp yaml so we don't get yelled at + filename = cli_args[1] + if filename == ".": + filename = ".tmuxp.yaml" + file_ext = filename.rsplit(".", 1)[-1] + assert file_ext in ["yaml", "yml"], file_ext + workspace_file_path = tmp_path / filename + workspace_file_path.write_text("\nsession_name: hello\n", encoding="utf-8") + oh_my_zsh_path = tmp_path / ".oh-my-zsh" + oh_my_zsh_path.mkdir() + monkeypatch.setenv("HOME", str(tmp_path)) + + monkeypatch.chdir(tmp_path) + + # If autoconfirm (-y) no need to prompt y + input_args = "y\ny\n" if "-y" not in cli_args else "" + + monkeypatch.setattr("sys.stdin", io.StringIO(input_args)) + try: + cli.cli(cli_args) + except SystemExit: + pass + tmuxp_json = tmp_path / ".tmuxp.json" + assert tmuxp_json.exists() + assert tmuxp_json.open().read() == json.dumps({"session_name": "hello"}, indent=2) + + +@pytest.mark.parametrize( + "cli_args", + [ + (["convert", "."]), + (["convert", ".tmuxp.json"]), + (["convert", ".tmuxp.json", "-y"]), + ], +) +def test_convert_json( + cli_args: t.List[str], + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + # 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") + oh_my_zsh_path = tmp_path / ".oh-my-zsh" + oh_my_zsh_path.mkdir() + monkeypatch.setenv("HOME", str(tmp_path)) + + monkeypatch.chdir(tmp_path) + + # If autoconfirm (-y) no need to prompt y + input_args = "y\ny\n" if "-y" not in cli_args else "" + + monkeypatch.setattr("sys.stdin", io.StringIO(input_args)) + try: + cli.cli(cli_args) + except SystemExit: + pass + + tmuxp_yaml = tmp_path / ".tmuxp.yaml" + assert tmuxp_yaml.exists() + assert tmuxp_yaml.open().read() == "session_name: hello\n" diff --git a/tests/cli/test_debug_info.py b/tests/cli/test_debug_info.py new file mode 100644 index 00000000000..0dfd23f19cc --- /dev/null +++ b/tests/cli/test_debug_info.py @@ -0,0 +1,30 @@ +import pathlib + +import pytest + +from tmuxp import cli + + +def test_debug_info_cli( + monkeypatch: pytest.MonkeyPatch, + tmp_path: pathlib.Path, + capsys: pytest.CaptureFixture, +) -> None: + monkeypatch.setenv("SHELL", "/bin/bash") + + cli.cli(["debug-info"]) + cli_output = capsys.readouterr().out + assert "environment" in cli_output + assert "python version" in cli_output + assert "system PATH" in cli_output + assert "tmux version" in cli_output + assert "libtmux version" in cli_output + assert "tmuxp version" in cli_output + assert "tmux path" in cli_output + assert "tmuxp path" in cli_output + assert "shell" in cli_output + assert "tmux session" in cli_output + assert "tmux windows" in cli_output + assert "tmux panes" in cli_output + assert "tmux global options" in cli_output + assert "tmux window options" in cli_output diff --git a/tests/cli/test_freeze.py b/tests/cli/test_freeze.py new file mode 100644 index 00000000000..87ddc58c143 --- /dev/null +++ b/tests/cli/test_freeze.py @@ -0,0 +1,105 @@ +import io +import pathlib +import typing as t + +import pytest + +from libtmux.server import Server +from tmuxp import cli +from tmuxp.config_reader import ConfigReader + + +@pytest.mark.parametrize( + "cli_args,inputs", + [ + (["freeze", "myfrozensession"], ["y\n", "./la.yaml\n", "y\n"]), + ( # Exists + ["freeze", "myfrozensession"], + ["y\n", "./exists.yaml\n", "./la.yaml\n", "y\n"], + ), + ( # Imply current session if not entered + ["freeze"], + ["y\n", "./la.yaml\n", "y\n"], + ), + (["freeze"], ["y\n", "./exists.yaml\n", "./la.yaml\n", "y\n"]), # Exists + ], +) +def test_freeze( + server: "Server", + cli_args: t.List[str], + inputs: t.List[str], + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("HOME", str(tmp_path)) + exists_yaml = tmp_path / "exists.yaml" + exists_yaml.touch() + + server.new_session(session_name="myfirstsession") + server.new_session(session_name="myfrozensession") + + # Assign an active pane to the session + second_session = server.sessions[1] + first_pane_on_second_session_id = second_session.windows[0].panes[0].pane_id + assert first_pane_on_second_session_id + monkeypatch.setenv("TMUX_PANE", first_pane_on_second_session_id) + + monkeypatch.chdir(tmp_path) + # Use tmux server (socket name) used in the test + assert server.socket_name is not None + cli_args = cli_args + ["-L", server.socket_name] + + monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) + try: + cli.cli(cli_args) + except SystemExit: + pass + + yaml_config_path = tmp_path / "la.yaml" + assert yaml_config_path.exists() + + yaml_config = yaml_config_path.open().read() + frozen_config = ConfigReader._load(format="yaml", content=yaml_config) + + assert frozen_config["session_name"] == "myfrozensession" + + +@pytest.mark.parametrize( + "cli_args,inputs", + [ + ( # Overwrite + ["freeze", "mysession", "--force"], + ["\n", "\n", "y\n", "./exists.yaml\n", "y\n"], + ), + ( # Imply current session if not entered + ["freeze", "--force"], + ["\n", "\n", "y\n", "./exists.yaml\n", "y\n"], + ), + ], +) +def test_freeze_overwrite( + server: "Server", + cli_args: t.List[str], + inputs: t.List[str], + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("HOME", str(tmp_path)) + exists_yaml = tmp_path / "exists.yaml" + exists_yaml.touch() + + server.new_session(session_name="mysession") + + monkeypatch.chdir(tmp_path) + # Use tmux server (socket name) used in the test + assert server.socket_name is not None + cli_args = cli_args + ["-L", server.socket_name] + + monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) + try: + cli.cli(cli_args) + except SystemExit: + pass + + yaml_config_path = tmp_path / "exists.yaml" + assert yaml_config_path.exists() diff --git a/tests/cli/test_import.py b/tests/cli/test_import.py new file mode 100644 index 00000000000..0d1e76a9220 --- /dev/null +++ b/tests/cli/test_import.py @@ -0,0 +1,118 @@ +import io +import pathlib +import typing as t + +import pytest + +from tmuxp import cli + +from ..fixtures import utils as test_utils + + +@pytest.mark.parametrize("cli_args", [(["import"])]) +def test_import( + cli_args: t.List[str], + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture, +) -> None: + cli.cli(cli_args) + result = capsys.readouterr() + assert "tmuxinator" in result.out + assert "teamocil" in result.out + + +@pytest.mark.parametrize( + "cli_args,inputs", + [ + ( + ["import", "teamocil", "./.teamocil/config.yaml"], + ["\n", "y\n", "./la.yaml\n", "y\n"], + ), + ( + ["import", "teamocil", "./.teamocil/config.yaml"], + ["\n", "y\n", "./exists.yaml\n", "./la.yaml\n", "y\n"], + ), + ( + ["import", "teamocil", "config"], + ["\n", "y\n", "./exists.yaml\n", "./la.yaml\n", "y\n"], + ), + ], +) +def test_import_teamocil( + cli_args: t.List[str], + inputs: t.List[str], + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + teamocil_config = test_utils.read_workspace_file("import_teamocil/test4.yaml") + + teamocil_path = tmp_path / ".teamocil" + teamocil_path.mkdir() + + teamocil_config_path = teamocil_path / "config.yaml" + teamocil_config_path.write_text(teamocil_config, encoding="utf-8") + + exists_yaml = tmp_path / "exists.yaml" + exists_yaml.touch() + + monkeypatch.setenv("HOME", str(tmp_path)) + + monkeypatch.chdir(tmp_path) + monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) + + try: + cli.cli(cli_args) + except SystemExit: + pass + + new_config_yaml = tmp_path / "la.yaml" + assert new_config_yaml.exists() + + +@pytest.mark.parametrize( + "cli_args,inputs", + [ + ( + ["import", "tmuxinator", "./.tmuxinator/config.yaml"], + ["\n", "y\n", "./la.yaml\n", "y\n"], + ), + ( + ["import", "tmuxinator", "./.tmuxinator/config.yaml"], + ["\n", "y\n", "./exists.yaml\n", "./la.yaml\n", "y\n"], + ), + ( + ["import", "tmuxinator", "config"], + ["\n", "y\n", "./exists.yaml\n", "./la.yaml\n", "y\n"], + ), + ], +) +def test_import_tmuxinator( + cli_args: t.List[str], + inputs: t.List[str], + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + tmuxinator_config = test_utils.read_workspace_file("import_tmuxinator/test3.yaml") + + tmuxinator_path = tmp_path / ".tmuxinator" + tmuxinator_path.mkdir() + + tmuxinator_config_path = tmuxinator_path / "config.yaml" + tmuxinator_config_path.write_text(tmuxinator_config, encoding="utf-8") + + exists_yaml = tmp_path / "exists.yaml" + exists_yaml.touch() + + monkeypatch.setenv("HOME", str(tmp_path)) + + monkeypatch.chdir(tmp_path) + + monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) + try: + cli.cli(cli_args) + except SystemExit: + pass + + new_config_yaml = tmp_path / "la.yaml" + assert new_config_yaml.exists() diff --git a/tests/cli/test_load.py b/tests/cli/test_load.py new file mode 100644 index 00000000000..5c65ff16a40 --- /dev/null +++ b/tests/cli/test_load.py @@ -0,0 +1,630 @@ +import io +import pathlib +import typing as t + +import pytest + +from pytest_mock import MockerFixture + +import libtmux +from libtmux.common import has_lt_version +from libtmux.server import Server +from libtmux.session import Session +from tmuxp import cli +from tmuxp.cli.load import ( + _load_append_windows_to_current_session, + _load_attached, + load_plugins, + load_workspace, +) +from tmuxp.config_reader import ConfigReader +from tmuxp.workspace import loader +from tmuxp.workspace.builder import WorkspaceBuilder + +from ..constants import FIXTURE_PATH +from ..fixtures import utils as test_utils + + +def test_load_workspace(server: "Server", monkeypatch: pytest.MonkeyPatch) -> None: + # this is an implementation test. Since this testsuite may be ran within + # a tmux session by the developer himself, delete the TMUX variable + # temporarily. + monkeypatch.delenv("TMUX", raising=False) + session_file = FIXTURE_PATH / "workspace/builder" / "two_pane.yaml" + + # open it detached + session = load_workspace( + session_file, socket_name=server.socket_name, detached=True + ) + + assert isinstance(session, Session) + assert session.name == "sample workspace" + + +def test_load_workspace_passes_tmux_config( + server: "Server", monkeypatch: pytest.MonkeyPatch +) -> None: + # this is an implementation test. Since this testsuite may be ran within + # a tmux session by the developer himself, delete the TMUX variable + # temporarily. + monkeypatch.delenv("TMUX", raising=False) + session_file = FIXTURE_PATH / "workspace/builder" / "two_pane.yaml" + + # open it detached + session = load_workspace( + session_file, + socket_name=server.socket_name, + tmux_config_file=str(FIXTURE_PATH / "tmux" / "tmux.conf"), + detached=True, + ) + + assert isinstance(session, Session) + assert isinstance(session.server, Server) + assert session.server.config_file == str(FIXTURE_PATH / "tmux" / "tmux.conf") + + +def test_load_workspace_named_session( + server: "Server", monkeypatch: pytest.MonkeyPatch +) -> None: + # this is an implementation test. Since this testsuite may be ran within + # a tmux session by the developer himself, delete the TMUX variable + # temporarily. + monkeypatch.delenv("TMUX", raising=False) + session_file = FIXTURE_PATH / "workspace/builder" / "two_pane.yaml" + + # open it detached + session = load_workspace( + session_file, + socket_name=server.socket_name, + new_session_name="tmuxp-new", + detached=True, + ) + + assert isinstance(session, Session) + assert session.name == "tmuxp-new" + + +@pytest.mark.skipif( + 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 +) -> None: + monkeypatch.delenv("TMUX", raising=False) + session_file = FIXTURE_PATH / "workspace/builder" / "two_pane.yaml" + + # open it detached + session = load_workspace( + session_file, socket_name=server.socket_name, detached=True + ) + + assert isinstance(session, Session) + assert session.name == "sample workspace" + + projfile = tmp_path / "simple.yaml" + + projfile.write_text( + """ +session_name: sampleconfi +start_directory: './' +windows: +- panes: + - echo 'hey'""", + encoding="utf-8", + ) + + # open it detached + session = load_workspace( + str(projfile), 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 +) -> None: + # this is an implementation test. Since this testsuite may be ran within + # a tmux session by the developer himself, delete the TMUX variable + # temporarily. + monkeypatch.delenv("TMUX", raising=False) + + realtemp = tmp_path / "myrealtemp" + realtemp.mkdir() + linktemp = tmp_path / "symlinktemp" + linktemp.symlink_to(realtemp) + projfile = linktemp / "simple.yaml" + + projfile.write_text( + """ +session_name: samplesimple +start_directory: './' +windows: +- panes: + - echo 'hey'""", + encoding="utf-8", + ) + + # open it detached + session = load_workspace( + str(projfile), socket_name=server.socket_name, detached=True + ) + assert session is not None + assert session.attached_window is not None + pane = session.attached_window.attached_pane + + assert isinstance(session, Session) + assert session.name == "samplesimple" + + assert pane is not None + assert pane.pane_current_path == str(realtemp) + + +if t.TYPE_CHECKING: + from typing_extensions import TypeAlias + + ExpectedOutput: TypeAlias = t.Optional[t.Union[str, t.List[str]]] + + +class CLILoadFixture(t.NamedTuple): + test_id: str + cli_args: t.List[t.Union[str, t.List[str]]] + config_paths: t.List[str] + session_names: t.List[str] + expected_exit_code: int + expected_in_out: "ExpectedOutput" = None + expected_not_in_out: "ExpectedOutput" = None + expected_in_err: "ExpectedOutput" = None + expected_not_in_err: "ExpectedOutput" = None + + +TEST_LOAD_FIXTURES = [ + CLILoadFixture( + test_id="dir-relative-dot-samedir", + cli_args=["load", "."], + config_paths=["{tmp_path}/.tmuxp.yaml"], + session_names=["my_config"], + expected_exit_code=0, + expected_in_out=None, + expected_not_in_out=None, + ), + CLILoadFixture( + test_id="dir-relative-dot-slash-samedir", + cli_args=["load", "./"], + config_paths=["{tmp_path}/.tmuxp.yaml"], + session_names=["my_config"], + expected_exit_code=0, + expected_in_out=None, + expected_not_in_out=None, + ), + CLILoadFixture( + test_id="dir-relative-file-samedir", + cli_args=["load", "./.tmuxp.yaml"], + config_paths=["{tmp_path}/.tmuxp.yaml"], + session_names=["my_config"], + expected_exit_code=0, + expected_in_out=None, + expected_not_in_out=None, + ), + CLILoadFixture( + test_id="filename-relative-file-samedir", + cli_args=["load", "./my_config.yaml"], + config_paths=["{tmp_path}/my_config.yaml"], + session_names=["my_config"], + expected_exit_code=0, + expected_in_out=None, + expected_not_in_out=None, + ), + CLILoadFixture( + test_id="configdir-session-name", + cli_args=["load", "my_config"], + config_paths=["{TMUXP_CONFIGDIR}/my_config.yaml"], + session_names=["my_config"], + expected_exit_code=0, + expected_in_out=None, + expected_not_in_out=None, + ), + CLILoadFixture( + test_id="configdir-absolute", + cli_args=["load", "~/.config/tmuxp/my_config.yaml"], + config_paths=["{TMUXP_CONFIGDIR}/my_config.yaml"], + session_names=["my_config"], + expected_exit_code=0, + expected_in_out=None, + expected_not_in_out=None, + ), + # + # Multiple configs + # + CLILoadFixture( + test_id="configdir-session-name-double", + cli_args=["load", "my_config", "second_config"], + config_paths=[ + "{TMUXP_CONFIGDIR}/my_config.yaml", + "{TMUXP_CONFIGDIR}/second_config.yaml", + ], + session_names=["my_config", "second_config"], + expected_exit_code=0, + expected_in_out=None, + expected_not_in_out=None, + ), +] + + +@pytest.mark.parametrize( + list(CLILoadFixture._fields), + TEST_LOAD_FIXTURES, + ids=[test.test_id for test in TEST_LOAD_FIXTURES], +) +@pytest.mark.usefixtures("tmuxp_configdir_default") +def test_load( + tmp_path: pathlib.Path, + tmuxp_configdir: pathlib.Path, + server: "Server", + session: Session, + capsys: pytest.CaptureFixture, + monkeypatch: pytest.MonkeyPatch, + test_id: str, + cli_args: t.List[str], + config_paths: t.List[str], + session_names: t.List[str], + expected_exit_code: int, + expected_in_out: "ExpectedOutput", + expected_not_in_out: "ExpectedOutput", + expected_in_err: "ExpectedOutput", + expected_not_in_err: "ExpectedOutput", +) -> None: + assert server.socket_name is not None + + monkeypatch.chdir(tmp_path) + for session_name, config_path in zip(session_names, config_paths): + tmuxp_config = pathlib.Path( + config_path.format(tmp_path=tmp_path, TMUXP_CONFIGDIR=tmuxp_configdir) + ) + tmuxp_config.write_text( + """ + session_name: {session_name} + windows: + - window_name: test + panes: + - + """.format( + session_name=session_name + ), + encoding="utf-8", + ) + + try: + cli.cli([*cli_args, "-d", "-L", server.socket_name, "-y"]) + except SystemExit: + pass + + result = capsys.readouterr() + output = "".join(list(result.out)) + + if expected_in_out is not None: + if isinstance(expected_in_out, str): + expected_in_out = [expected_in_out] + for needle in expected_in_out: + assert needle in output + + if expected_not_in_out is not None: + if isinstance(expected_not_in_out, str): + expected_not_in_out = [expected_not_in_out] + for needle in expected_not_in_out: + assert needle not in output + + for session_name in session_names: + assert server.has_session(session_name) + + +def test_regression_00132_session_name_with_dots( + tmp_path: pathlib.Path, + server: "Server", + session: Session, + capsys: pytest.CaptureFixture, +) -> None: + yaml_config = FIXTURE_PATH / "workspace/builder" / "regression_00132_dots.yaml" + cli_args = [str(yaml_config)] + with pytest.raises(libtmux.exc.BadSessionName): + cli.cli(["load", *cli_args]) + + +@pytest.mark.parametrize( + "cli_args", [["load", ".", "-d"], ["load", ".tmuxp.yaml", "-d"]] +) +def test_load_zsh_autotitle_warning( + cli_args: t.List[str], + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture, + server: "Server", +) -> None: + # create dummy tmuxp yaml so we don't get yelled at + yaml_config = tmp_path / ".tmuxp.yaml" + yaml_config.write_text( + """ + session_name: test + windows: + - window_name: test + panes: + - + """, + encoding="utf-8", + ) + oh_my_zsh_path = tmp_path / ".oh-my-zsh" + oh_my_zsh_path.mkdir() + monkeypatch.setenv("HOME", str(tmp_path)) + + monkeypatch.chdir(tmp_path) + + monkeypatch.delenv("DISABLE_AUTO_TITLE", raising=False) + monkeypatch.setenv("SHELL", "zsh") + + # Use tmux server (socket name) used in the test + assert server.socket_name is not None + cli_args = cli_args + ["-L", server.socket_name] + + cli.cli(cli_args) + result = capsys.readouterr() + assert "Please set" in result.out + + monkeypatch.setenv("DISABLE_AUTO_TITLE", "false") + cli.cli(cli_args) + result = capsys.readouterr() + assert "Please set" in result.out + + monkeypatch.setenv("DISABLE_AUTO_TITLE", "true") + cli.cli(cli_args) + result = capsys.readouterr() + assert "Please set" not in result.out + + monkeypatch.delenv("DISABLE_AUTO_TITLE", raising=False) + monkeypatch.setenv("SHELL", "sh") + cli.cli(cli_args) + result = capsys.readouterr() + assert "Please set" not in result.out + + +@pytest.mark.parametrize( + "cli_args", + [ + (["load", ".", "--log-file", "log.txt", "-d"]), + ], +) +def test_load_log_file( + cli_args: t.List[str], + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture, +) -> None: + # create dummy tmuxp yaml that breaks to prevent actually loading tmux + tmuxp_config_path = tmp_path / ".tmuxp.yaml" + tmuxp_config_path.write_text( + """ +session_name: hello + - + """, + encoding="utf-8", + ) + oh_my_zsh_path = tmp_path / ".oh-my-zsh" + oh_my_zsh_path.mkdir() + monkeypatch.setenv("HOME", str(tmp_path)) + + monkeypatch.chdir(tmp_path) + + try: + cli.cli(cli_args) + except Exception: + pass + result = capsys.readouterr() + log_file_path = tmp_path / "log.txt" + assert "Loading" in log_file_path.open().read() + assert result.out is not None + + +def test_load_plugins(monkeypatch_plugin_test_packages: None) -> None: + from tmuxp_test_plugin_bwb.plugin import ( # type: ignore + PluginBeforeWorkspaceBuilder, + ) + + plugins_config = test_utils.read_workspace_file("workspace/builder/plugin_bwb.yaml") + + sconfig = ConfigReader._load(format="yaml", content=plugins_config) + sconfig = loader.expand(sconfig) + + plugins = load_plugins(sconfig) + + assert len(plugins) == 1 + + test_plugin_class_types = [ + PluginBeforeWorkspaceBuilder().__class__, + ] + for plugin in plugins: + assert plugin.__class__ in test_plugin_class_types + + +@pytest.mark.skip("Not sure how to clean up the tmux session this makes") +@pytest.mark.parametrize( + "cli_args,inputs", + [ + ( + ["load", "tests/fixtures/workspace/builder/plugin_versions_fail.yaml"], + ["y\n"], + ) + ], +) +def test_load_plugins_version_fail_skip( + monkeypatch_plugin_test_packages, cli_args, inputs, capsys: pytest.CaptureFixture +) -> None: + try: + cli.cli(cli_args) + except SystemExit: + pass + result = capsys.readouterr() + + assert "[Loading]" in result.out + + +@pytest.mark.parametrize( + "cli_args,inputs", + [ + ( + ["load", "tests/fixtures/workspace/builder/plugin_versions_fail.yaml"], + ["n\n"], + ) + ], +) +def test_load_plugins_version_fail_no_skip( + monkeypatch_plugin_test_packages: None, + cli_args: t.List[str], + inputs: t.List[str], + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture, +) -> None: + monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) + + try: + cli.cli(cli_args) + except SystemExit: + pass + result = capsys.readouterr() + + assert "[Not Skipping]" in result.out + + +@pytest.mark.parametrize( + "cli_args", + [(["load", "tests/fixtures/workspace/builder/plugin_missing_fail.yaml"])], +) +def test_load_plugins_plugin_missing( + monkeypatch_plugin_test_packages: None, + cli_args: t.List[str], + capsys: pytest.CaptureFixture, +) -> None: + try: + cli.cli(cli_args) + except SystemExit: + pass + result = capsys.readouterr() + + assert "[Plugin Error]" in result.out + + +def test_plugin_system_before_script( + monkeypatch_plugin_test_packages: None, + server: "Server", + monkeypatch: pytest.MonkeyPatch, +) -> None: + # this is an implementation test. Since this testsuite may be ran within + # a tmux session by the developer himself, delete the TMUX variable + # temporarily. + monkeypatch.delenv("TMUX", raising=False) + session_file = FIXTURE_PATH / "workspace/builder" / "plugin_bs.yaml" + + # open it detached + session = load_workspace( + session_file, socket_name=server.socket_name, detached=True + ) + + assert isinstance(session, libtmux.Session) + assert session.name == "plugin_test_bs" + + +def test_load_attached( + server: "Server", monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture +) -> None: + # Load a session and attach from outside tmux + monkeypatch.delenv("TMUX", raising=False) + + attach_session_mock = mocker.patch("libtmux.session.Session.attach_session") + attach_session_mock.return_value.stderr = None + + yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") + sconfig = ConfigReader._load(format="yaml", content=yaml_config) + + builder = WorkspaceBuilder(sconf=sconfig, server=server) + + _load_attached(builder, False) + + assert attach_session_mock.call_count == 1 + + +def test_load_attached_detached( + server: "Server", monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture +) -> None: + # Load a session but don't attach + monkeypatch.delenv("TMUX", raising=False) + + attach_session_mock = mocker.patch("libtmux.session.Session.attach_session") + attach_session_mock.return_value.stderr = None + + yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") + sconfig = ConfigReader._load(format="yaml", content=yaml_config) + + builder = WorkspaceBuilder(sconf=sconfig, server=server) + + _load_attached(builder, True) + + assert attach_session_mock.call_count == 0 + + +def test_load_attached_within_tmux( + server: "Server", monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture +) -> None: + # Load a session and attach from within tmux + monkeypatch.setenv("TMUX", "/tmp/tmux-1234/default,123,0") + + switch_client_mock = mocker.patch("libtmux.session.Session.switch_client") + switch_client_mock.return_value.stderr = None + + yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") + sconfig = ConfigReader._load(format="yaml", content=yaml_config) + + builder = WorkspaceBuilder(sconf=sconfig, server=server) + + _load_attached(builder, False) + + assert switch_client_mock.call_count == 1 + + +def test_load_attached_within_tmux_detached( + server: "Server", monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture +) -> None: + # Load a session and attach from within tmux + monkeypatch.setenv("TMUX", "/tmp/tmux-1234/default,123,0") + + switch_client_mock = mocker.patch("libtmux.session.Session.switch_client") + switch_client_mock.return_value.stderr = None + + yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") + sconfig = ConfigReader._load(format="yaml", content=yaml_config) + + builder = WorkspaceBuilder(sconf=sconfig, server=server) + + _load_attached(builder, True) + + assert switch_client_mock.call_count == 1 + + +def test_load_append_windows_to_current_session( + server: "Server", monkeypatch: pytest.MonkeyPatch +) -> None: + yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") + sconfig = ConfigReader._load(format="yaml", content=yaml_config) + + builder = WorkspaceBuilder(sconf=sconfig, server=server) + builder.build() + + assert len(server.sessions) == 1 + assert len(server.windows) == 3 + + # Assign an active pane to the session + assert server.panes[0].pane_id + monkeypatch.setenv("TMUX_PANE", server.panes[0].pane_id) + + builder = WorkspaceBuilder(sconf=sconfig, server=server) + _load_append_windows_to_current_session(builder) + + assert len(server.sessions) == 1 + assert len(server.windows) == 6 diff --git a/tests/cli/test_ls.py b/tests/cli/test_ls.py new file mode 100644 index 00000000000..75d6de7db54 --- /dev/null +++ b/tests/cli/test_ls.py @@ -0,0 +1,45 @@ +import os +import pathlib + +import pytest + +from tmuxp import cli + + +def test_ls_cli( + monkeypatch: pytest.MonkeyPatch, + tmp_path: pathlib.Path, + capsys: pytest.CaptureFixture, +) -> None: + monkeypatch.setenv("HOME", str(tmp_path)) + monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / ".config")) + + filenames = [ + ".git/", + ".gitignore/", + "session_1.yaml", + "session_2.yaml", + "session_3.json", + "session_4.txt", + ] + + # should ignore: + # - directories should be ignored + # - extensions not covered in VALID_WORKSPACE_DIR_FILE_EXTENSIONS + ignored_filenames = [".git/", ".gitignore/", "session_4.txt"] + stems = [os.path.splitext(f)[0] for f in filenames if f not in ignored_filenames] + + for filename in filenames: + location = tmp_path / f".tmuxp/{filename}" + if filename.endswith("/"): + location.mkdir(parents=True) + else: + location.touch() + + try: + cli.cli(["ls"]) + except SystemExit: + pass + cli_output = capsys.readouterr().out + + assert cli_output == "\n".join(stems) + "\n" diff --git a/tests/cli/test_shell.py b/tests/cli/test_shell.py new file mode 100644 index 00000000000..28241205921 --- /dev/null +++ b/tests/cli/test_shell.py @@ -0,0 +1,288 @@ +import io +import pathlib +import subprocess +import typing as t + +import pytest + +from libtmux.server import Server +from libtmux.session import Session +from tmuxp import cli, exc + + +@pytest.mark.parametrize("cli_cmd", [["shell"], ["shell", "--pdb"]]) +@pytest.mark.parametrize( + "cli_args,inputs,env,expected_output", + [ + ( + ["-L{SOCKET_NAME}", "-c", "print(str(server.socket_name))"], + [], + {}, + "{SERVER_SOCKET_NAME}", + ), + ( + [ + "-L{SOCKET_NAME}", + "{SESSION_NAME}", + "-c", + "print(session.name)", + ], + [], + {}, + "{SESSION_NAME}", + ), + ( + [ + "-L{SOCKET_NAME}", + "{SESSION_NAME}", + "{WINDOW_NAME}", + "-c", + "print(server.has_session(session.name))", + ], + [], + {}, + "True", + ), + ( + [ + "-L{SOCKET_NAME}", + "{SESSION_NAME}", + "{WINDOW_NAME}", + "-c", + "print(window.name)", + ], + [], + {}, + "{WINDOW_NAME}", + ), + ( + [ + "-L{SOCKET_NAME}", + "{SESSION_NAME}", + "{WINDOW_NAME}", + "-c", + "print(pane.id)", + ], + [], + {}, + "{PANE_ID}", + ), + ( + [ + "-L{SOCKET_NAME}", + "-c", + "print(pane.id)", + ], + [], + {"TMUX_PANE": "{PANE_ID}"}, + "{PANE_ID}", + ), + ], +) +def test_shell( + cli_cmd: t.List[str], + cli_args: t.List[str], + inputs: t.List[t.Any], + expected_output: str, + env: t.Dict[str, str], + server: "Server", + session: Session, + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture, +) -> None: + monkeypatch.setenv("HOME", str(tmp_path)) + window_name = "my_window" + window = session.new_window(window_name=window_name) + window.split_window() + + assert window.attached_pane is not None + + template_ctx = dict( + SOCKET_NAME=server.socket_name, + SESSION_NAME=session.name, + WINDOW_NAME=window_name, + PANE_ID=window.attached_pane.id, + SERVER_SOCKET_NAME=server.socket_name, + ) + + cli_args = cli_cmd + [cli_arg.format(**template_ctx) for cli_arg in cli_args] + + for k, v in env.items(): + monkeypatch.setenv(k, v.format(**template_ctx)) + + monkeypatch.chdir(tmp_path) + + cli.cli(cli_args) + result = capsys.readouterr() + assert expected_output.format(**template_ctx) in result.out + + +@pytest.mark.parametrize( + "cli_cmd", + [ + ["shell"], + ["shell", "--pdb"], + ], +) +@pytest.mark.parametrize( + "cli_args,inputs,env,template_ctx,exception,message", + [ + ( + ["-LDoesNotExist", "-c", "print(str(server.socket_name))"], + [], + {}, + {}, + subprocess.CalledProcessError, + r".*DoesNotExist.*", + ), + ( + [ + "-L{SOCKET_NAME}", + "nonexistant_session", + "-c", + "print(str(server.socket_name))", + ], + [], + {}, + {"session_name": "nonexistant_session"}, + exc.TmuxpException, + "Session not found: nonexistant_session", + ), + ( + [ + "-L{SOCKET_NAME}", + "{SESSION_NAME}", + "nonexistant_window", + "-c", + "print(str(server.socket_name))", + ], + [], + {}, + {"window_name": "nonexistant_window"}, + exc.TmuxpException, + "Window not found: {WINDOW_NAME}", + ), + ], +) +def test_shell_target_missing( + cli_cmd: t.List[str], + cli_args: t.List[str], + inputs: t.List[t.Any], + env: t.Dict[t.Any, t.Any], + template_ctx: t.Dict[str, str], + exception: t.Union[ + t.Type[exc.TmuxpException], t.Type[subprocess.CalledProcessError] + ], + message: str, + socket_name: str, + server: "Server", + session: Session, + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture, +) -> None: + monkeypatch.setenv("HOME", str(tmp_path)) + window_name = "my_window" + window = session.new_window(window_name=window_name) + window.split_window() + + assert server.socket_name is not None + assert session.name is not None + + template_ctx.update( + dict( + SOCKET_NAME=server.socket_name, + SESSION_NAME=session.name, + WINDOW_NAME=template_ctx.get("window_name", window_name), + ) + ) + cli_args = cli_cmd + [cli_arg.format(**template_ctx) for cli_arg in cli_args] + + for k, v in env.items(): + monkeypatch.setenv(k, v.format(**template_ctx)) + + monkeypatch.chdir(tmp_path) + + if exception is not None: + with pytest.raises(exception, match=message.format(**template_ctx)): + cli.cli(cli_args) + else: + cli.cli(cli_args) + result = capsys.readouterr() + assert message.format(**template_ctx) in result.out + + +@pytest.mark.parametrize( + "cli_cmd", + [ + # ['shell'], + # ['shell', '--pdb'), + ["shell", "--code"], + # ['shell', '--bpython'], + # ['shell', '--ptipython'], + # ['shell', '--ptpython'], + # ['shell', '--ipython'], + ], +) +@pytest.mark.parametrize( + "cli_args,inputs,env,message", + [ + ( + [ + "-L{SOCKET_NAME}", + ], + [], + {}, + "(InteractiveConsole)", + ), + ( + [ + "-L{SOCKET_NAME}", + ], + [], + {"PANE_ID": "{PANE_ID}"}, + "(InteractiveConsole)", + ), + ], +) +def test_shell_interactive( + cli_cmd: t.List[str], + cli_args: t.List[str], + inputs: t.List[t.Any], + env: t.Dict[str, str], + message: str, + server: "Server", + session: Session, + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture, +) -> None: + monkeypatch.setenv("HOME", str(tmp_path)) + window_name = "my_window" + window = session.new_window(window_name=window_name) + window.split_window() + + assert window.attached_pane is not None + + template_ctx = dict( + SOCKET_NAME=server.socket_name, + SESSION_NAME=session.name, + WINDOW_NAME=window_name, + PANE_ID=window.attached_pane.id, + SERVER_SOCKET_NAME=server.socket_name, + ) + + cli_args = cli_cmd + [cli_arg.format(**template_ctx) for cli_arg in cli_args] + + for k, v in env.items(): + monkeypatch.setenv(k, v.format(**template_ctx)) + + monkeypatch.chdir(tmp_path) + monkeypatch.setattr("sys.stdin", io.StringIO("exit()\r")) + try: + cli.cli(cli_args) + except SystemExit: + pass + result = capsys.readouterr() + assert message.format(**template_ctx) in result.err diff --git a/tests/test_cli.py b/tests/test_cli.py deleted file mode 100644 index eb65417b854..00000000000 --- a/tests/test_cli.py +++ /dev/null @@ -1,1403 +0,0 @@ -"""Test for tmuxp command line interface.""" -import argparse -import io -import json -import os -import pathlib -import subprocess -import typing as t - -import pytest - -from pytest_mock import MockerFixture - -import libtmux -from libtmux.common import has_lt_version -from libtmux.server import Server -from libtmux.session import Session -from tmuxp import cli, exc -from tmuxp.cli.import_config import get_teamocil_dir, get_tmuxinator_dir -from tmuxp.cli.load import ( - _load_append_windows_to_current_session, - _load_attached, - _reattach, - load_plugins, - load_workspace, -) -from tmuxp.cli.utils import tmuxp_echo -from tmuxp.config_reader import ConfigReader -from tmuxp.workspace import loader -from tmuxp.workspace.builder import WorkspaceBuilder -from tmuxp.workspace.finders import find_workspace_file - -from .constants import FIXTURE_PATH -from .fixtures import utils as test_utils - -if t.TYPE_CHECKING: - import _pytest.capture - - -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 os.path.exists(tmp_path) - - -""" - scans for .tmuxp.{yaml,yml,json} in directory, returns first result - log warning if multiple found: - - - current directory: ., ./, noarg - - relative to cwd directory: ../, ./hello/, hello/, ./hello/ - - absolute directory: /path/to/dir, /path/to/dir/, ~/ - - no path, no ext, config_dir: projectname, tmuxp - - load file directly - - - - no directory (cwd): .tmuxp.yaml - - relative to cwd: ../.tmuxp.yaml, ./hello/.tmuxp.yaml - - absolute path: /path/to/file.yaml, ~/path/to/file/.tmuxp.yaml - - Any case where file is not found should return error. -""" - - -def test_load_workspace(server: "Server", monkeypatch: pytest.MonkeyPatch) -> None: - # this is an implementation test. Since this testsuite may be ran within - # a tmux session by the developer himself, delete the TMUX variable - # temporarily. - monkeypatch.delenv("TMUX", raising=False) - session_file = FIXTURE_PATH / "workspace/builder" / "two_pane.yaml" - - # open it detached - session = load_workspace( - session_file, socket_name=server.socket_name, detached=True - ) - - assert isinstance(session, Session) - assert session.name == "sample workspace" - - -def test_load_workspace_passes_tmux_config( - server: "Server", monkeypatch: pytest.MonkeyPatch -) -> None: - # this is an implementation test. Since this testsuite may be ran within - # a tmux session by the developer himself, delete the TMUX variable - # temporarily. - monkeypatch.delenv("TMUX", raising=False) - session_file = FIXTURE_PATH / "workspace/builder" / "two_pane.yaml" - - # open it detached - session = load_workspace( - session_file, - socket_name=server.socket_name, - tmux_config_file=str(FIXTURE_PATH / "tmux" / "tmux.conf"), - detached=True, - ) - - assert isinstance(session, Session) - assert isinstance(session.server, Server) - assert session.server.config_file == str(FIXTURE_PATH / "tmux" / "tmux.conf") - - -def test_load_workspace_named_session( - server: "Server", monkeypatch: pytest.MonkeyPatch -) -> None: - # this is an implementation test. Since this testsuite may be ran within - # a tmux session by the developer himself, delete the TMUX variable - # temporarily. - monkeypatch.delenv("TMUX", raising=False) - session_file = FIXTURE_PATH / "workspace/builder" / "two_pane.yaml" - - # open it detached - session = load_workspace( - session_file, - socket_name=server.socket_name, - new_session_name="tmuxp-new", - detached=True, - ) - - assert isinstance(session, Session) - assert session.name == "tmuxp-new" - - -@pytest.mark.skipif( - 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 -) -> None: - monkeypatch.delenv("TMUX", raising=False) - session_file = FIXTURE_PATH / "workspace/builder" / "two_pane.yaml" - - # open it detached - session = load_workspace( - session_file, socket_name=server.socket_name, detached=True - ) - - assert isinstance(session, Session) - assert session.name == "sample workspace" - - projfile = tmp_path / "simple.yaml" - - projfile.write_text( - """ -session_name: sampleconfi -start_directory: './' -windows: -- panes: - - echo 'hey'""", - encoding="utf-8", - ) - - # open it detached - session = load_workspace( - str(projfile), 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 -) -> None: - # this is an implementation test. Since this testsuite may be ran within - # a tmux session by the developer himself, delete the TMUX variable - # temporarily. - monkeypatch.delenv("TMUX", raising=False) - - realtemp = tmp_path / "myrealtemp" - realtemp.mkdir() - linktemp = tmp_path / "symlinktemp" - linktemp.symlink_to(realtemp) - projfile = linktemp / "simple.yaml" - - projfile.write_text( - """ -session_name: samplesimple -start_directory: './' -windows: -- panes: - - echo 'hey'""", - encoding="utf-8", - ) - - # open it detached - session = load_workspace( - str(projfile), socket_name=server.socket_name, detached=True - ) - assert session is not None - assert session.attached_window is not None - pane = session.attached_window.attached_pane - - assert isinstance(session, Session) - assert session.name == "samplesimple" - - assert pane is not None - assert pane.pane_current_path == str(realtemp) - - -if t.TYPE_CHECKING: - from typing_extensions import TypeAlias - - ExpectedOutput: TypeAlias = t.Optional[t.Union[str, t.List[str]]] - - -class CLILoadFixture(t.NamedTuple): - test_id: str - cli_args: t.List[t.Union[str, t.List[str]]] - config_paths: t.List[str] - session_names: t.List[str] - expected_exit_code: int - expected_in_out: "ExpectedOutput" = None - expected_not_in_out: "ExpectedOutput" = None - expected_in_err: "ExpectedOutput" = None - expected_not_in_err: "ExpectedOutput" = None - - -TEST_LOAD_FIXTURES = [ - CLILoadFixture( - test_id="dir-relative-dot-samedir", - cli_args=["load", "."], - config_paths=["{tmp_path}/.tmuxp.yaml"], - session_names=["my_config"], - expected_exit_code=0, - expected_in_out=None, - expected_not_in_out=None, - ), - CLILoadFixture( - test_id="dir-relative-dot-slash-samedir", - cli_args=["load", "./"], - config_paths=["{tmp_path}/.tmuxp.yaml"], - session_names=["my_config"], - expected_exit_code=0, - expected_in_out=None, - expected_not_in_out=None, - ), - CLILoadFixture( - test_id="dir-relative-file-samedir", - cli_args=["load", "./.tmuxp.yaml"], - config_paths=["{tmp_path}/.tmuxp.yaml"], - session_names=["my_config"], - expected_exit_code=0, - expected_in_out=None, - expected_not_in_out=None, - ), - CLILoadFixture( - test_id="filename-relative-file-samedir", - cli_args=["load", "./my_config.yaml"], - config_paths=["{tmp_path}/my_config.yaml"], - session_names=["my_config"], - expected_exit_code=0, - expected_in_out=None, - expected_not_in_out=None, - ), - CLILoadFixture( - test_id="configdir-session-name", - cli_args=["load", "my_config"], - config_paths=["{TMUXP_CONFIGDIR}/my_config.yaml"], - session_names=["my_config"], - expected_exit_code=0, - expected_in_out=None, - expected_not_in_out=None, - ), - CLILoadFixture( - test_id="configdir-absolute", - cli_args=["load", "~/.config/tmuxp/my_config.yaml"], - config_paths=["{TMUXP_CONFIGDIR}/my_config.yaml"], - session_names=["my_config"], - expected_exit_code=0, - expected_in_out=None, - expected_not_in_out=None, - ), - # - # Multiple configs - # - CLILoadFixture( - test_id="configdir-session-name-double", - cli_args=["load", "my_config", "second_config"], - config_paths=[ - "{TMUXP_CONFIGDIR}/my_config.yaml", - "{TMUXP_CONFIGDIR}/second_config.yaml", - ], - session_names=["my_config", "second_config"], - expected_exit_code=0, - expected_in_out=None, - expected_not_in_out=None, - ), -] - - -@pytest.mark.parametrize( - list(CLILoadFixture._fields), - TEST_LOAD_FIXTURES, - ids=[test.test_id for test in TEST_LOAD_FIXTURES], -) -@pytest.mark.usefixtures("tmuxp_configdir_default") -def test_load( - tmp_path: pathlib.Path, - tmuxp_configdir: pathlib.Path, - server: "Server", - session: Session, - capsys: pytest.CaptureFixture, - monkeypatch: pytest.MonkeyPatch, - test_id: str, - cli_args: t.List[str], - config_paths: t.List[str], - session_names: t.List[str], - expected_exit_code: int, - expected_in_out: "ExpectedOutput", - expected_not_in_out: "ExpectedOutput", - expected_in_err: "ExpectedOutput", - expected_not_in_err: "ExpectedOutput", -) -> None: - assert server.socket_name is not None - - monkeypatch.chdir(tmp_path) - for session_name, config_path in zip(session_names, config_paths): - tmuxp_config = pathlib.Path( - config_path.format(tmp_path=tmp_path, TMUXP_CONFIGDIR=tmuxp_configdir) - ) - tmuxp_config.write_text( - """ - session_name: {session_name} - windows: - - window_name: test - panes: - - - """.format( - session_name=session_name - ), - encoding="utf-8", - ) - - try: - cli.cli([*cli_args, "-d", "-L", server.socket_name, "-y"]) - except SystemExit: - pass - - result = capsys.readouterr() - output = "".join(list(result.out)) - - if expected_in_out is not None: - if isinstance(expected_in_out, str): - expected_in_out = [expected_in_out] - for needle in expected_in_out: - assert needle in output - - if expected_not_in_out is not None: - if isinstance(expected_not_in_out, str): - expected_not_in_out = [expected_not_in_out] - for needle in expected_not_in_out: - assert needle not in output - - for session_name in session_names: - assert server.has_session(session_name) - - -def test_regression_00132_session_name_with_dots( - tmp_path: pathlib.Path, - server: "Server", - session: Session, - capsys: pytest.CaptureFixture, -) -> None: - yaml_config = FIXTURE_PATH / "workspace/builder" / "regression_00132_dots.yaml" - cli_args = [str(yaml_config)] - with pytest.raises(libtmux.exc.BadSessionName): - cli.cli(["load", *cli_args]) - - -@pytest.mark.parametrize( - "cli_args", [["load", ".", "-d"], ["load", ".tmuxp.yaml", "-d"]] -) -def test_load_zsh_autotitle_warning( - cli_args: t.List[str], - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, - server: "Server", -) -> None: - # create dummy tmuxp yaml so we don't get yelled at - yaml_config = tmp_path / ".tmuxp.yaml" - yaml_config.write_text( - """ - session_name: test - windows: - - window_name: test - panes: - - - """, - encoding="utf-8", - ) - oh_my_zsh_path = tmp_path / ".oh-my-zsh" - oh_my_zsh_path.mkdir() - monkeypatch.setenv("HOME", str(tmp_path)) - - monkeypatch.chdir(tmp_path) - - monkeypatch.delenv("DISABLE_AUTO_TITLE", raising=False) - monkeypatch.setenv("SHELL", "zsh") - - # Use tmux server (socket name) used in the test - assert server.socket_name is not None - cli_args = cli_args + ["-L", server.socket_name] - - cli.cli(cli_args) - result = capsys.readouterr() - assert "Please set" in result.out - - monkeypatch.setenv("DISABLE_AUTO_TITLE", "false") - cli.cli(cli_args) - result = capsys.readouterr() - assert "Please set" in result.out - - monkeypatch.setenv("DISABLE_AUTO_TITLE", "true") - cli.cli(cli_args) - result = capsys.readouterr() - assert "Please set" not in result.out - - monkeypatch.delenv("DISABLE_AUTO_TITLE", raising=False) - monkeypatch.setenv("SHELL", "sh") - cli.cli(cli_args) - result = capsys.readouterr() - assert "Please set" not in result.out - - -@pytest.mark.parametrize( - "cli_args", - [ - (["load", ".", "--log-file", "log.txt", "-d"]), - ], -) -def test_load_log_file( - cli_args: t.List[str], - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, -) -> None: - # create dummy tmuxp yaml that breaks to prevent actually loading tmux - tmuxp_config_path = tmp_path / ".tmuxp.yaml" - tmuxp_config_path.write_text( - """ -session_name: hello - - - """, - encoding="utf-8", - ) - oh_my_zsh_path = tmp_path / ".oh-my-zsh" - oh_my_zsh_path.mkdir() - monkeypatch.setenv("HOME", str(tmp_path)) - - monkeypatch.chdir(tmp_path) - - try: - cli.cli(cli_args) - except Exception: - pass - result = capsys.readouterr() - log_file_path = tmp_path / "log.txt" - assert "Loading" in log_file_path.open().read() - assert result.out is not None - - -@pytest.mark.parametrize("cli_cmd", [["shell"], ["shell", "--pdb"]]) -@pytest.mark.parametrize( - "cli_args,inputs,env,expected_output", - [ - ( - ["-L{SOCKET_NAME}", "-c", "print(str(server.socket_name))"], - [], - {}, - "{SERVER_SOCKET_NAME}", - ), - ( - [ - "-L{SOCKET_NAME}", - "{SESSION_NAME}", - "-c", - "print(session.name)", - ], - [], - {}, - "{SESSION_NAME}", - ), - ( - [ - "-L{SOCKET_NAME}", - "{SESSION_NAME}", - "{WINDOW_NAME}", - "-c", - "print(server.has_session(session.name))", - ], - [], - {}, - "True", - ), - ( - [ - "-L{SOCKET_NAME}", - "{SESSION_NAME}", - "{WINDOW_NAME}", - "-c", - "print(window.name)", - ], - [], - {}, - "{WINDOW_NAME}", - ), - ( - [ - "-L{SOCKET_NAME}", - "{SESSION_NAME}", - "{WINDOW_NAME}", - "-c", - "print(pane.id)", - ], - [], - {}, - "{PANE_ID}", - ), - ( - [ - "-L{SOCKET_NAME}", - "-c", - "print(pane.id)", - ], - [], - {"TMUX_PANE": "{PANE_ID}"}, - "{PANE_ID}", - ), - ], -) -def test_shell( - cli_cmd: t.List[str], - cli_args: t.List[str], - inputs: t.List[t.Any], - expected_output: str, - env: t.Dict[str, str], - server: "Server", - session: Session, - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, -) -> None: - monkeypatch.setenv("HOME", str(tmp_path)) - window_name = "my_window" - window = session.new_window(window_name=window_name) - window.split_window() - - assert window.attached_pane is not None - - template_ctx = dict( - SOCKET_NAME=server.socket_name, - SESSION_NAME=session.name, - WINDOW_NAME=window_name, - PANE_ID=window.attached_pane.id, - SERVER_SOCKET_NAME=server.socket_name, - ) - - cli_args = cli_cmd + [cli_arg.format(**template_ctx) for cli_arg in cli_args] - - for k, v in env.items(): - monkeypatch.setenv(k, v.format(**template_ctx)) - - monkeypatch.chdir(tmp_path) - - cli.cli(cli_args) - result = capsys.readouterr() - assert expected_output.format(**template_ctx) in result.out - - -@pytest.mark.parametrize( - "cli_cmd", - [ - ["shell"], - ["shell", "--pdb"], - ], -) -@pytest.mark.parametrize( - "cli_args,inputs,env,template_ctx,exception,message", - [ - ( - ["-LDoesNotExist", "-c", "print(str(server.socket_name))"], - [], - {}, - {}, - subprocess.CalledProcessError, - r".*DoesNotExist.*", - ), - ( - [ - "-L{SOCKET_NAME}", - "nonexistant_session", - "-c", - "print(str(server.socket_name))", - ], - [], - {}, - {"session_name": "nonexistant_session"}, - exc.TmuxpException, - "Session not found: nonexistant_session", - ), - ( - [ - "-L{SOCKET_NAME}", - "{SESSION_NAME}", - "nonexistant_window", - "-c", - "print(str(server.socket_name))", - ], - [], - {}, - {"window_name": "nonexistant_window"}, - exc.TmuxpException, - "Window not found: {WINDOW_NAME}", - ), - ], -) -def test_shell_target_missing( - cli_cmd: t.List[str], - cli_args: t.List[str], - inputs: t.List[t.Any], - env: t.Dict[t.Any, t.Any], - template_ctx: t.Dict[str, str], - exception: t.Union[ - t.Type[exc.TmuxpException], t.Type[subprocess.CalledProcessError] - ], - message: str, - socket_name: str, - server: "Server", - session: Session, - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, -) -> None: - monkeypatch.setenv("HOME", str(tmp_path)) - window_name = "my_window" - window = session.new_window(window_name=window_name) - window.split_window() - - assert server.socket_name is not None - assert session.name is not None - - template_ctx.update( - dict( - SOCKET_NAME=server.socket_name, - SESSION_NAME=session.name, - WINDOW_NAME=template_ctx.get("window_name", window_name), - ) - ) - cli_args = cli_cmd + [cli_arg.format(**template_ctx) for cli_arg in cli_args] - - for k, v in env.items(): - monkeypatch.setenv(k, v.format(**template_ctx)) - - monkeypatch.chdir(tmp_path) - - if exception is not None: - with pytest.raises(exception, match=message.format(**template_ctx)): - cli.cli(cli_args) - else: - cli.cli(cli_args) - result = capsys.readouterr() - assert message.format(**template_ctx) in result.out - - -@pytest.mark.parametrize( - "cli_cmd", - [ - # ['shell'], - # ['shell', '--pdb'), - ["shell", "--code"], - # ['shell', '--bpython'], - # ['shell', '--ptipython'], - # ['shell', '--ptpython'], - # ['shell', '--ipython'], - ], -) -@pytest.mark.parametrize( - "cli_args,inputs,env,message", - [ - ( - [ - "-L{SOCKET_NAME}", - ], - [], - {}, - "(InteractiveConsole)", - ), - ( - [ - "-L{SOCKET_NAME}", - ], - [], - {"PANE_ID": "{PANE_ID}"}, - "(InteractiveConsole)", - ), - ], -) -def test_shell_interactive( - cli_cmd: t.List[str], - cli_args: t.List[str], - inputs: t.List[t.Any], - env: t.Dict[str, str], - message: str, - server: "Server", - session: Session, - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, -) -> None: - monkeypatch.setenv("HOME", str(tmp_path)) - window_name = "my_window" - window = session.new_window(window_name=window_name) - window.split_window() - - assert window.attached_pane is not None - - template_ctx = dict( - SOCKET_NAME=server.socket_name, - SESSION_NAME=session.name, - WINDOW_NAME=window_name, - PANE_ID=window.attached_pane.id, - SERVER_SOCKET_NAME=server.socket_name, - ) - - cli_args = cli_cmd + [cli_arg.format(**template_ctx) for cli_arg in cli_args] - - for k, v in env.items(): - monkeypatch.setenv(k, v.format(**template_ctx)) - - monkeypatch.chdir(tmp_path) - monkeypatch.setattr("sys.stdin", io.StringIO("exit()\r")) - try: - cli.cli(cli_args) - except SystemExit: - pass - result = capsys.readouterr() - assert message.format(**template_ctx) in result.err - - -@pytest.mark.parametrize( - "cli_args", - [ - (["convert", "."]), - (["convert", ".tmuxp.yaml"]), - (["convert", ".tmuxp.yaml", "-y"]), - (["convert", ".tmuxp.yml"]), - (["convert", ".tmuxp.yml", "-y"]), - ], -) -def test_convert( - cli_args: t.List[str], - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - # create dummy tmuxp yaml so we don't get yelled at - filename = cli_args[1] - if filename == ".": - filename = ".tmuxp.yaml" - file_ext = filename.rsplit(".", 1)[-1] - assert file_ext in ["yaml", "yml"], file_ext - workspace_file_path = tmp_path / filename - workspace_file_path.write_text("\nsession_name: hello\n", encoding="utf-8") - oh_my_zsh_path = tmp_path / ".oh-my-zsh" - oh_my_zsh_path.mkdir() - monkeypatch.setenv("HOME", str(tmp_path)) - - monkeypatch.chdir(tmp_path) - - # If autoconfirm (-y) no need to prompt y - input_args = "y\ny\n" if "-y" not in cli_args else "" - - monkeypatch.setattr("sys.stdin", io.StringIO(input_args)) - try: - cli.cli(cli_args) - except SystemExit: - pass - tmuxp_json = tmp_path / ".tmuxp.json" - assert tmuxp_json.exists() - assert tmuxp_json.open().read() == json.dumps({"session_name": "hello"}, indent=2) - - -@pytest.mark.parametrize( - "cli_args", - [ - (["convert", "."]), - (["convert", ".tmuxp.json"]), - (["convert", ".tmuxp.json", "-y"]), - ], -) -def test_convert_json( - cli_args: t.List[str], - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - # 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") - oh_my_zsh_path = tmp_path / ".oh-my-zsh" - oh_my_zsh_path.mkdir() - monkeypatch.setenv("HOME", str(tmp_path)) - - monkeypatch.chdir(tmp_path) - - # If autoconfirm (-y) no need to prompt y - input_args = "y\ny\n" if "-y" not in cli_args else "" - - monkeypatch.setattr("sys.stdin", io.StringIO(input_args)) - try: - cli.cli(cli_args) - except SystemExit: - pass - - tmuxp_yaml = tmp_path / ".tmuxp.yaml" - assert tmuxp_yaml.exists() - assert tmuxp_yaml.open().read() == "session_name: hello\n" - - -@pytest.mark.parametrize("cli_args", [(["import"])]) -def test_import( - cli_args: t.List[str], - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, -) -> None: - cli.cli(cli_args) - result = capsys.readouterr() - assert "tmuxinator" in result.out - assert "teamocil" in result.out - - -@pytest.mark.parametrize( - "cli_args", - [ - (["--help"]), - (["-h"]), - ], -) -def test_help( - cli_args: t.List[str], - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, -) -> None: - try: - cli.cli(cli_args) - except SystemExit: - pass - result = capsys.readouterr() - - assert "usage: tmuxp [-h] [--version] [--log-level log-level]" in result.out - - -@pytest.mark.parametrize( - "cli_args,inputs", - [ - ( - ["import", "teamocil", "./.teamocil/config.yaml"], - ["\n", "y\n", "./la.yaml\n", "y\n"], - ), - ( - ["import", "teamocil", "./.teamocil/config.yaml"], - ["\n", "y\n", "./exists.yaml\n", "./la.yaml\n", "y\n"], - ), - ( - ["import", "teamocil", "config"], - ["\n", "y\n", "./exists.yaml\n", "./la.yaml\n", "y\n"], - ), - ], -) -def test_import_teamocil( - cli_args: t.List[str], - inputs: t.List[str], - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - teamocil_config = test_utils.read_workspace_file("import_teamocil/test4.yaml") - - teamocil_path = tmp_path / ".teamocil" - teamocil_path.mkdir() - - teamocil_config_path = teamocil_path / "config.yaml" - teamocil_config_path.write_text(teamocil_config, encoding="utf-8") - - exists_yaml = tmp_path / "exists.yaml" - exists_yaml.touch() - - monkeypatch.setenv("HOME", str(tmp_path)) - - monkeypatch.chdir(tmp_path) - monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) - - try: - cli.cli(cli_args) - except SystemExit: - pass - - new_config_yaml = tmp_path / "la.yaml" - assert new_config_yaml.exists() - - -@pytest.mark.parametrize( - "cli_args,inputs", - [ - ( - ["import", "tmuxinator", "./.tmuxinator/config.yaml"], - ["\n", "y\n", "./la.yaml\n", "y\n"], - ), - ( - ["import", "tmuxinator", "./.tmuxinator/config.yaml"], - ["\n", "y\n", "./exists.yaml\n", "./la.yaml\n", "y\n"], - ), - ( - ["import", "tmuxinator", "config"], - ["\n", "y\n", "./exists.yaml\n", "./la.yaml\n", "y\n"], - ), - ], -) -def test_import_tmuxinator( - cli_args: t.List[str], - inputs: t.List[str], - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - tmuxinator_config = test_utils.read_workspace_file("import_tmuxinator/test3.yaml") - - tmuxinator_path = tmp_path / ".tmuxinator" - tmuxinator_path.mkdir() - - tmuxinator_config_path = tmuxinator_path / "config.yaml" - tmuxinator_config_path.write_text(tmuxinator_config, encoding="utf-8") - - exists_yaml = tmp_path / "exists.yaml" - exists_yaml.touch() - - monkeypatch.setenv("HOME", str(tmp_path)) - - monkeypatch.chdir(tmp_path) - - monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) - try: - cli.cli(cli_args) - except SystemExit: - pass - - new_config_yaml = tmp_path / "la.yaml" - assert new_config_yaml.exists() - - -@pytest.mark.parametrize( - "cli_args,inputs", - [ - (["freeze", "myfrozensession"], ["y\n", "./la.yaml\n", "y\n"]), - ( # Exists - ["freeze", "myfrozensession"], - ["y\n", "./exists.yaml\n", "./la.yaml\n", "y\n"], - ), - ( # Imply current session if not entered - ["freeze"], - ["y\n", "./la.yaml\n", "y\n"], - ), - (["freeze"], ["y\n", "./exists.yaml\n", "./la.yaml\n", "y\n"]), # Exists - ], -) -def test_freeze( - server: "Server", - cli_args: t.List[str], - inputs: t.List[str], - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - monkeypatch.setenv("HOME", str(tmp_path)) - exists_yaml = tmp_path / "exists.yaml" - exists_yaml.touch() - - server.new_session(session_name="myfirstsession") - server.new_session(session_name="myfrozensession") - - # Assign an active pane to the session - second_session = server.sessions[1] - first_pane_on_second_session_id = second_session.windows[0].panes[0].pane_id - assert first_pane_on_second_session_id - monkeypatch.setenv("TMUX_PANE", first_pane_on_second_session_id) - - monkeypatch.chdir(tmp_path) - # Use tmux server (socket name) used in the test - assert server.socket_name is not None - cli_args = cli_args + ["-L", server.socket_name] - - monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) - try: - cli.cli(cli_args) - except SystemExit: - pass - - yaml_config_path = tmp_path / "la.yaml" - assert yaml_config_path.exists() - - yaml_config = yaml_config_path.open().read() - frozen_config = ConfigReader._load(format="yaml", content=yaml_config) - - assert frozen_config["session_name"] == "myfrozensession" - - -@pytest.mark.parametrize( - "cli_args,inputs", - [ - ( # Overwrite - ["freeze", "mysession", "--force"], - ["\n", "\n", "y\n", "./exists.yaml\n", "y\n"], - ), - ( # Imply current session if not entered - ["freeze", "--force"], - ["\n", "\n", "y\n", "./exists.yaml\n", "y\n"], - ), - ], -) -def test_freeze_overwrite( - server: "Server", - cli_args: t.List[str], - inputs: t.List[str], - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - monkeypatch.setenv("HOME", str(tmp_path)) - exists_yaml = tmp_path / "exists.yaml" - exists_yaml.touch() - - server.new_session(session_name="mysession") - - monkeypatch.chdir(tmp_path) - # Use tmux server (socket name) used in the test - assert server.socket_name is not None - cli_args = cli_args + ["-L", server.socket_name] - - monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) - try: - cli.cli(cli_args) - except SystemExit: - pass - - yaml_config_path = tmp_path / "exists.yaml" - assert yaml_config_path.exists() - - -def test_resolve_behavior( - tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch -) -> None: - expect = str(tmp_path) - monkeypatch.chdir(tmp_path) - pathlib.Path("../").resolve() == os.path.dirname(expect) - pathlib.Path(".").resolve() == expect - pathlib.Path("./").resolve() == expect - pathlib.Path(expect).resolve() == expect - - -def test_get_tmuxinator_dir(monkeypatch: pytest.MonkeyPatch) -> None: - assert get_tmuxinator_dir() == os.path.expanduser("~/.tmuxinator/") - - monkeypatch.setenv("HOME", "/moo") - assert get_tmuxinator_dir() == "/moo/.tmuxinator/" - assert get_tmuxinator_dir() == os.path.expanduser("~/.tmuxinator/") - - -def test_get_teamocil_dir(monkeypatch: pytest.MonkeyPatch) -> None: - assert get_teamocil_dir() == os.path.expanduser("~/.teamocil/") - - monkeypatch.setenv("HOME", "/moo") - assert get_teamocil_dir() == "/moo/.teamocil/" - assert get_teamocil_dir() == os.path.expanduser("~/.teamocil/") - - -def test_pass_config_dir_ClickPath( - tmp_path: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, -) -> None: - - configdir = tmp_path / "myconfigdir" - configdir.mkdir() - user_config_name = "myconfig" - user_config = configdir / f"{user_config_name}.yaml" - user_config.touch() - - expect = str(user_config) - - parser = argparse.ArgumentParser() - parser.add_argument("workspace_file", type=str) - - def config_cmd(workspace_file: str) -> None: - tmuxp_echo(find_workspace_file(workspace_file, workspace_dir=configdir)) - - def check_cmd(config_arg) -> "_pytest.capture.CaptureResult": - args = parser.parse_args([config_arg]) - config_cmd(workspace_file=args.workspace_file) - return capsys.readouterr() - - monkeypatch.chdir(configdir) - - assert expect in check_cmd("myconfig").out - assert expect in check_cmd("myconfig.yaml").out - assert expect in check_cmd("./myconfig.yaml").out - assert str(user_config) in check_cmd(str(configdir / "myconfig.yaml")).out - - with pytest.raises(FileNotFoundError): - assert "FileNotFoundError" in check_cmd(".tmuxp.json").out - - -def test_ls_cli( - monkeypatch: pytest.MonkeyPatch, - tmp_path: pathlib.Path, - capsys: pytest.CaptureFixture, -) -> None: - monkeypatch.setenv("HOME", str(tmp_path)) - monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / ".config")) - - filenames = [ - ".git/", - ".gitignore/", - "session_1.yaml", - "session_2.yaml", - "session_3.json", - "session_4.txt", - ] - - # should ignore: - # - directories should be ignored - # - extensions not covered in VALID_WORKSPACE_DIR_FILE_EXTENSIONS - ignored_filenames = [".git/", ".gitignore/", "session_4.txt"] - stems = [os.path.splitext(f)[0] for f in filenames if f not in ignored_filenames] - - for filename in filenames: - location = tmp_path / f".tmuxp/{filename}" - if filename.endswith("/"): - location.mkdir(parents=True) - else: - location.touch() - - try: - cli.cli(["ls"]) - except SystemExit: - pass - cli_output = capsys.readouterr().out - - assert cli_output == "\n".join(stems) + "\n" - - -def test_load_plugins(monkeypatch_plugin_test_packages: None) -> None: - from tmuxp_test_plugin_bwb.plugin import ( # type: ignore - PluginBeforeWorkspaceBuilder, - ) - - plugins_config = test_utils.read_workspace_file("workspace/builder/plugin_bwb.yaml") - - sconfig = ConfigReader._load(format="yaml", content=plugins_config) - sconfig = loader.expand(sconfig) - - plugins = load_plugins(sconfig) - - assert len(plugins) == 1 - - test_plugin_class_types = [ - PluginBeforeWorkspaceBuilder().__class__, - ] - for plugin in plugins: - assert plugin.__class__ in test_plugin_class_types - - -@pytest.mark.skip("Not sure how to clean up the tmux session this makes") -@pytest.mark.parametrize( - "cli_args,inputs", - [ - ( - ["load", "tests/fixtures/workspace/builder/plugin_versions_fail.yaml"], - ["y\n"], - ) - ], -) -def test_load_plugins_version_fail_skip( - monkeypatch_plugin_test_packages, cli_args, inputs, capsys: pytest.CaptureFixture -) -> None: - try: - cli.cli(cli_args) - except SystemExit: - pass - result = capsys.readouterr() - - assert "[Loading]" in result.out - - -@pytest.mark.parametrize( - "cli_args,inputs", - [ - ( - ["load", "tests/fixtures/workspace/builder/plugin_versions_fail.yaml"], - ["n\n"], - ) - ], -) -def test_load_plugins_version_fail_no_skip( - monkeypatch_plugin_test_packages: None, - cli_args: t.List[str], - inputs: t.List[str], - monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, -) -> None: - monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) - - try: - cli.cli(cli_args) - except SystemExit: - pass - result = capsys.readouterr() - - assert "[Not Skipping]" in result.out - - -@pytest.mark.parametrize( - "cli_args", - [(["load", "tests/fixtures/workspace/builder/plugin_missing_fail.yaml"])], -) -def test_load_plugins_plugin_missing( - monkeypatch_plugin_test_packages: None, - cli_args: t.List[str], - capsys: pytest.CaptureFixture, -) -> None: - try: - cli.cli(cli_args) - except SystemExit: - pass - result = capsys.readouterr() - - assert "[Plugin Error]" in result.out - - -def test_plugin_system_before_script( - monkeypatch_plugin_test_packages: None, - server: "Server", - monkeypatch: pytest.MonkeyPatch, -) -> None: - # this is an implementation test. Since this testsuite may be ran within - # a tmux session by the developer himself, delete the TMUX variable - # temporarily. - monkeypatch.delenv("TMUX", raising=False) - session_file = FIXTURE_PATH / "workspace/builder" / "plugin_bs.yaml" - - # open it detached - session = load_workspace( - session_file, socket_name=server.socket_name, detached=True - ) - - assert isinstance(session, libtmux.Session) - assert session.name == "plugin_test_bs" - - -def test_reattach_plugins( - monkeypatch_plugin_test_packages: None, server: "Server" -) -> None: - config_plugins = test_utils.read_workspace_file("workspace/builder/plugin_r.yaml") - - sconfig = ConfigReader._load(format="yaml", content=config_plugins) - sconfig = loader.expand(sconfig) - - # open it detached - builder = WorkspaceBuilder( - sconf=sconfig, plugins=load_plugins(sconfig), server=server - ) - builder.build() - - try: - _reattach(builder) - except libtmux.exc.LibTmuxException: - pass - - assert builder.session is not None - proc = builder.session.cmd("display-message", "-p", "'#S'") - - assert proc.stdout[0] == "'plugin_test_r'" - - -def test_load_attached( - server: "Server", monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture -) -> None: - # Load a session and attach from outside tmux - monkeypatch.delenv("TMUX", raising=False) - - attach_session_mock = mocker.patch("libtmux.session.Session.attach_session") - attach_session_mock.return_value.stderr = None - - yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") - sconfig = ConfigReader._load(format="yaml", content=yaml_config) - - builder = WorkspaceBuilder(sconf=sconfig, server=server) - - _load_attached(builder, False) - - assert attach_session_mock.call_count == 1 - - -def test_load_attached_detached( - server: "Server", monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture -) -> None: - # Load a session but don't attach - monkeypatch.delenv("TMUX", raising=False) - - attach_session_mock = mocker.patch("libtmux.session.Session.attach_session") - attach_session_mock.return_value.stderr = None - - yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") - sconfig = ConfigReader._load(format="yaml", content=yaml_config) - - builder = WorkspaceBuilder(sconf=sconfig, server=server) - - _load_attached(builder, True) - - assert attach_session_mock.call_count == 0 - - -def test_load_attached_within_tmux( - server: "Server", monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture -) -> None: - # Load a session and attach from within tmux - monkeypatch.setenv("TMUX", "/tmp/tmux-1234/default,123,0") - - switch_client_mock = mocker.patch("libtmux.session.Session.switch_client") - switch_client_mock.return_value.stderr = None - - yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") - sconfig = ConfigReader._load(format="yaml", content=yaml_config) - - builder = WorkspaceBuilder(sconf=sconfig, server=server) - - _load_attached(builder, False) - - assert switch_client_mock.call_count == 1 - - -def test_load_attached_within_tmux_detached( - server: "Server", monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture -) -> None: - # Load a session and attach from within tmux - monkeypatch.setenv("TMUX", "/tmp/tmux-1234/default,123,0") - - switch_client_mock = mocker.patch("libtmux.session.Session.switch_client") - switch_client_mock.return_value.stderr = None - - yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") - sconfig = ConfigReader._load(format="yaml", content=yaml_config) - - builder = WorkspaceBuilder(sconf=sconfig, server=server) - - _load_attached(builder, True) - - assert switch_client_mock.call_count == 1 - - -def test_load_append_windows_to_current_session( - server: "Server", monkeypatch: pytest.MonkeyPatch -) -> None: - yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") - sconfig = ConfigReader._load(format="yaml", content=yaml_config) - - builder = WorkspaceBuilder(sconf=sconfig, server=server) - builder.build() - - assert len(server.sessions) == 1 - assert len(server.windows) == 3 - - # Assign an active pane to the session - assert server.panes[0].pane_id - monkeypatch.setenv("TMUX_PANE", server.panes[0].pane_id) - - builder = WorkspaceBuilder(sconf=sconfig, server=server) - _load_append_windows_to_current_session(builder) - - assert len(server.sessions) == 1 - assert len(server.windows) == 6 - - -def test_debug_info_cli( - monkeypatch: pytest.MonkeyPatch, - tmp_path: pathlib.Path, - capsys: pytest.CaptureFixture, -) -> None: - monkeypatch.setenv("SHELL", "/bin/bash") - - cli.cli(["debug-info"]) - cli_output = capsys.readouterr().out - assert "environment" in cli_output - assert "python version" in cli_output - assert "system PATH" in cli_output - assert "tmux version" in cli_output - assert "libtmux version" in cli_output - assert "tmuxp version" in cli_output - assert "tmux path" in cli_output - assert "tmuxp path" in cli_output - assert "shell" in cli_output - assert "tmux session" in cli_output - assert "tmux windows" in cli_output - assert "tmux panes" in cli_output - assert "tmux global options" in cli_output - assert "tmux window options" in cli_output