Skip to content

Commit

Permalink
feat: tmux strategy to run tests in a tmux pane
Browse files Browse the repository at this point in the history
See [README.md](README.md) for usage.

Close #119
  • Loading branch information
gerardroche committed Jan 20, 2024
1 parent a2a7b21 commit 57db0da
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 12 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.

## 3.19.0

### Added

- Tmux strategy - Runs test commands in a tmux pane [#119](https://github.com/gerardroche/sublime-phpunit/issues/119)

## 3.18.3 - Unreleased

### Fixed
Expand Down
14 changes: 13 additions & 1 deletion Preferences.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,17 @@
// paths and the values are the replacement remote paths. Environment
// variables and user home directory ~ placeholder are expanded.
// Example: {"~/code/project1": "~/project1"}
"phpunit.docker_paths": {}
"phpunit.docker_paths": {},

// Clear screen before running tests.
"phpunit.tmux_clear": true,

// Session index or name (index starts from 1).
"phpunit.tmux_session": 1,

// Window index (index starts from 1).
"phpunit.tmux_window": 1,

// Pane index (index starts from 1).
"phpunit.tmux_pane": 1
}
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Enhance your coding experience with seamless PHPUnit integration for [Sublime Te
- [xterm] - A terminal emulator for the X Window System.
- [cmd] - A command-line interpreter for Windows.
- [PowerShell] - A cross-platform command-line shell.
- [Tmux] - A terminal multiplexer. :new:
* Zero configuration required

Read [Running PHPUnit Tests from Sublime Text](https://blog.gerardroche.com/2023/05/05/running-phpunit-tests-within-sublime-text/) for a quick introduction.
Expand All @@ -58,6 +59,7 @@ Read [Running PHPUnit Tests from Sublime Text](https://blog.gerardroche.com/2023
- [PHP Executable](#php-executable)
- [SSH](#ssh)
- [Docker](#docker)
- [Tmux](#tmux)
- [Auto Commands](#auto-commands)
- [Toggle Commands](#toggle-commands)
- [Custom Toggle Commands](#custom-toggle-commands)
Expand Down Expand Up @@ -159,7 +161,7 @@ To set this strategy:

```json
{
"phpunit.strategy": "kitty"
"phpunit.strategy": "tmux"
}
```

Expand All @@ -173,6 +175,7 @@ Available strategies and their identifiers:
| **[xterm]** | `xterm` | Sends test commands to the xterm terminal.
| **[cmd]** | `cmd` | Sends test commands to the cmd.exe terminal.
| **[PowerShell]** | `powershell` | Sends test commands to the PowerShell command shell.
| **[Tmux]** | `tmux` | Sends test commands to a Tmux pane. :new:

## Configuration

Expand All @@ -184,9 +187,9 @@ Available settings and their details:

| Setting | Type | Default | Description
| :------------------------ | :----------------- | :------------------- | :----------
| `phpunit.executable` | `string` or `list` | Auto-discovery | Path to the PHPUnit executable for running tests. Environment variables and user home directory ~ placeholder are expanded. The executable can be a string or a list of parameters. Example: `vendor/bin/phpunit`
| `phpunit.executable` | `string` or `list` | Auto-discovery | Path to the PHPUnit executable for running tests. Environment variables and user home directory ~ placeholder are expanded. The executable can be a string or a list of parameters. Example: `vendor/bin/phpunit`
| `phpunit.options` | `dict` | `{}` | Command-line options to pass to PHPUnit. Example: `{"no-coverage": true}`
| `phpunit.php_executable` | `string` | Auto-discovery | Path to the PHP executable for running tests. Environment variables and user home directory ~ placeholder are expanded. Example: `~/.phpenv/versions/8.2/bin/php`
| `phpunit.php_executable` | `string` | Auto-discovery | Path to the PHP executable for running tests. Environment variables and user home directory ~ placeholder are expanded. Example: `~/.phpenv/versions/8.2/bin/php`
| `phpunit.save_all_on_run` | `boolean` | `true` | Automatically saves all unsaved buffers before running tests.
| `phpunit.on_post_save` | `list` | `[]` | Auto commands to execute when views are saved. Example: `["phpunit_test_file"]`
| `phpunit.debug` | `boolean` | `false` | Prints debug information about the test runner.
Expand Down Expand Up @@ -226,6 +229,17 @@ Configure Docker settings for running tests within containers:

Utilize these settings to configure PHPUnitKit for streamlined testing within Docker containers.

**Tmux Settings** :rocket: :new:

Configure Tmux settings for running tests in a tmux pane:

| Setting | Type | Default | Description
| :------------------------ | :------------ | :---------------- | :----------
| `phpunit.tmux_clear` | `bool` | `true` | Clear screen before running tests.
| `phpunit.tmux_session` | `int|string` | `1` | Session index or name (index starts from 1).
| `phpunit.tmux_window` | `int` | `1` | Window index (index starts from 1).
| `phpunit.tmux_pane` | `int` | `1` | Pane index (index starts from 1).

### CLI Options

If you want some CLI options to stick around, you can configure them in your settings.
Expand Down Expand Up @@ -367,6 +381,19 @@ Command Palette → Preferences: PHPUnit Settings
}
```

### Tmux :rocket: :new:

**Example:** Run tests in a tmux pane.

```json
{
"phpunit.strategy": "tmux",
"phpunit.tmux_session": 1,
"phpunit.tmux_window": 1,
"phpunit.tmux_pane": 1
}
```

### Auto Commands

You can configure the `on_post_save` event to run the "Test File" command when views are saved. This will instruct the runner to automatically run a test every time it is saved.
Expand Down Expand Up @@ -488,6 +515,7 @@ Released under the [GPL-3.0-or-later License](LICENSE).
[ParaTest]: https://github.com/paratestphp/paratest
[Pest]: https://pestphp.com
[PowerShell]: https://learn.microsoft.com/en-us/powershell/
[Tmux]: https://github.com/tmux/tmux/wiki
[cmd]: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd
[iTerm2]: https://iterm2.com
[xterm]: https://invisible-island.net/xterm/
11 changes: 4 additions & 7 deletions lib/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,10 @@ def run(self, working_dir=None, file=None, options=None) -> None:
'options': options
})

strategy.execute(
self.window,
self.view,
env,
cmd,
working_dir
)
if get_setting(self.view, 'strategy') == 'tmux':
cmd = strategy.build_tmux_cmd(self.view, working_dir, cmd)

strategy.execute(self.window, self.view, env, cmd, working_dir)

def run_last(self) -> None:
last_test_args = get_last_run()
Expand Down
36 changes: 35 additions & 1 deletion lib/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import os
import re
import shlex

from sublime import cache_path
from sublime import load_resource
Expand All @@ -25,10 +26,11 @@
from PHPUnitKit.lib.utils import debug_message
from PHPUnitKit.lib.utils import get_setting
from PHPUnitKit.lib.utils import is_debug
from PHPUnitKit.lib.utils import shell_read


def execute(window, view, env: dict, cmd: list, working_dir: str) -> None:
if get_setting(view, 'strategy') in ('cmd', 'external', 'iterm', 'kitty', 'powershell', 'xterm'):
if get_setting(view, 'strategy') in ('cmd', 'external', 'iterm', 'kitty', 'powershell', 'tmux', 'xterm'):
window.run_command('exec', {
'env': env,
'cmd': cmd,
Expand Down Expand Up @@ -138,3 +140,35 @@ def _get_color_scheme(view):
' scheme with PHPUnit test results colors: {}'.format(str(e)))

return color_scheme


def build_tmux_cmd(view, working_dir: str, cmd: list) -> list:
tmux_session = get_setting(view, 'tmux_session')
if not tmux_session or isinstance(tmux_session, int):
sessions = shell_read(view, 'tmux list-sessions -F \'#{session_name}\'')
if sessions:
sessions = sessions.split('\n')
try:
tmux_session_index = tmux_session - 1 if isinstance(tmux_session, int) else 0
tmux_session = sessions[tmux_session_index]
except (IndexError):
tmux_session = sessions[0]

if cmd[0].startswith(working_dir):
cmd = [os.path.relpath(cmd[0], working_dir)] + cmd[1:]

tmux_cmds = []

if get_setting(view, 'tmux_clear'):
tmux_cmds.append(shlex.join(['clear']))

tmux_cmds.append(shlex.join(['cd', working_dir]))
tmux_cmds.append(shlex.join(cmd) + '\n')

tmux_target = '{}:{}.{}'.format(
tmux_session,
get_setting(view, 'tmux_window'),
get_setting(view, 'tmux_pane')
)

return ['tmux', 'send-keys', '-t', tmux_target, ';'.join(tmux_cmds)]
76 changes: 76 additions & 0 deletions lib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import os
import re
import shutil
import subprocess
import sys
import traceback

from sublime import active_window
from sublime import platform
Expand Down Expand Up @@ -750,3 +753,76 @@ def toggle_on_post_save(view, item: str) -> None:
view.settings().erase('phpunit.on_post_save')
if on_post_save != view.settings().get('phpunit.on_post_save'):
view.settings().set('phpunit.on_post_save', on_post_save)


def _get_default_shell() -> str:
if sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
return os.environ.get('SHELL', 'sh')
elif sys.platform.startswith('win'):
return 'cmd.exe'
else:
return ''


if sys.platform.startswith('win'):
try:
import ctypes
except ImportError:
traceback.print_exc()
ctypes = None

def _get_startup_info():
# Hide the child process window.
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

return startupinfo

def _get_encoding() -> str:
return str(ctypes.windll.kernel32.GetOEMCP())

def _shell_filter_newlines(text: str):
return text.replace('\r\n', '\n')

def _shell_decode(res):
return _shell_filter_newlines(res.decode(_get_encoding()))

def _shell_read(view, cmd: str) -> str:
p = subprocess.Popen([_get_default_shell(), '/c', cmd],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
startupinfo=_get_startup_info())
out, err = p.communicate()

if out:
return _shell_decode(out)

if err:
return _shell_decode(err)

return ''

else:
def _shell_read(view, cmd: str) -> str:
p = subprocess.Popen([_get_default_shell(), '-c', cmd],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)

out, err = p.communicate()

if out:
return out.decode('utf-8').strip()

if err:
return err.decode('utf-8').strip()

return ''


def shell_read(view, cmd: str) -> str:
try:
return _shell_read(view, cmd)
except Exception:
traceback.print_exc()

return ''

0 comments on commit 57db0da

Please sign in to comment.