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 22, 2024
1 parent a2a7b21 commit 70e5eed
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 25 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
20 changes: 19 additions & 1 deletion Preferences.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,23 @@
// 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 the terminal screen before running tests.
"phpunit.tmux_clear": true,

// Clear the terminal's scrollback buffer using the extended "E3" capability.
"phpunit.tmux_clear_scrollback": true,

// Specify the session, window, and pane which should be used to run tests.
//
// Format: `{session}:{window}.{pane}`
//
// The default means the current pane.
//
// For example, ":{start}.{top}", would mean the current session,
// lowest-numbered window, top pane.
//
// See http://man.openbsd.org/OpenBSD-current/man1/tmux.1#COMMANDS
"phpunit.tmux_target": ":."
}
62 changes: 46 additions & 16 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 @@ -150,17 +152,13 @@ Enhance your testing workflow with these commands for efficient testing directly

PHPUnitKit can run tests using different execution environments known as "strategies".

**Example:** Using the Kitty Terminal Strategy

To set this strategy:
**Example:** Using the Tmux Strategy

1. Open the Command Palette: `Preferences: PHPUnit Settings`
2. Add the following to your settings:

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

Available strategies and their identifiers:
Expand All @@ -173,6 +171,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 the Tmux terminal multiplexer. :new:

## Configuration

Expand All @@ -184,9 +183,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 All @@ -198,9 +197,7 @@ Available settings and their details:
| `phpunit.paratest` | `boolean` | `false` | Uses ParaTest to run tests.
| `phpunit.pest` | `boolean` | `false` | Uses Pest to run tests.

These settings allow you to customize PHPUnitKit according to your preferences and requirements.

**SSH Settings** :rocket:
**SSH Settings**

Configure SSH settings for running tests remotely:

Expand All @@ -212,9 +209,7 @@ Configure SSH settings for running tests remotely:
| `phpunit.ssh_host` | `string` | `null` | Host for running tests via SSH. Example: `homestead.test`
| `phpunit.ssh_paths` | `dict` | `{}` | Path mapping for running tests via SSH. Keys: local paths, Values: corresponding remote paths. Environment variables and user home directory ~ placeholder are expanded. Example: `{"~/code/project1": "~/project1"}`

Use these settings to configure PHPUnitKit's SSH options for seamless remote testing.

**Docker Settings** :rocket:
**Docker Settings**

Configure Docker settings for running tests within containers:

Expand All @@ -224,7 +219,15 @@ Configure Docker settings for running tests within containers:
| `phpunit.docker_command` | `list` | `[]` | Command to use when running tests via Docker. Example: `["docker", "exec", "-it", "my-container"]`
| `phpunit.docker_paths` | `dict` | `{}` | Path mapping for running tests via Docker. Keys: local paths, Values: corresponding remote paths. Environment variables and user home directory ~ placeholder are expanded. Example: `{"~/code/project1": "~/project1"}`

Utilize these settings to configure PHPUnitKit for streamlined testing within Docker containers.
**Tmux Settings** :new:

Configure Tmux settings for running tests in a tmux pane:

| Setting | Type | Default | Description
| :-------------------------------- | :------------ | :-------- | :----------
| `phpunit.tmux_clear` | `bool` | `true` | Clear the terminal screen before running tests.
| `phpunit.tmux_clear_scrollback` | `bool` | `false` | Clear the terminal's scrollback buffer using the extended "E3" capability.
| `phpunit.tmux_target` | `string` | `:.` | Specify the session, window, and pane which should be used to run tests. <br><br>Format: `{session}:{window}.{pane}` <br><br>The default means the current pane. <br><br>For example, `:{start}.{top}` would mean the current session, lowest-numbered window, top pane. <br><br>See [Tmux documentation](http://man.openbsd.org/OpenBSD-current/man1/tmux.1#COMMANDS) for target usage.

### CLI Options

Expand Down Expand Up @@ -367,6 +370,32 @@ Command Palette → Preferences: PHPUnit Settings
}
```

### Tmux :new:

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

```json
{
"phpunit.strategy": "tmux",
"phpunit.options": { "colors": true, "no-coverage": true }
}
```

Tip: Use the **`no-coverage`** option with the Command Palette **PHPUnit: Toggle --no-coverage** to turn code coverage on and off for quicker test runs when you just don't need the code coverage report.

**Example:** Run tests in current session, lowest-numbered window, and top pane

```json
{
"phpunit.strategy": "tmux",
"phpunit.tmux_target": ":{start}.{top}",
}
```

The target accepts the format `{target-session}:{target-window}.{target-pane}`. The default is `:.`, which means the current pane.

See [Tmux documentation](http://man.openbsd.org/OpenBSD-current/man1/tmux.1#COMMANDS) for more details on the target usage.

### 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 +517,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
31 changes: 30 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 @@ -28,7 +29,7 @@


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 +139,31 @@ 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_target = get_setting(view, 'tmux_target')

# Try make initial cmd relative to working directory to reduce length.
if cmd[0].startswith(working_dir):
cmd = [os.path.relpath(cmd[0], working_dir)] + cmd[1:]

key_cmds = []

# Clear the terminal screen.
if get_setting(view, 'tmux_clear'):
clear_cmd = ['clear']
if get_setting(view, 'tmux_clear_scrollback'):
clear_cmd.append('-x')
key_cmds.append(shlex.join(clear_cmd))

# Switch to the working directory.
key_cmds.append(shlex.join(['cd', working_dir]))

# The test command.
key_cmds.append(shlex.join(cmd))

# Run inside a subshell to avoid changing the current working directory.
keys = '({})\n'.format(' && '.join(key_cmds))

return ['tmux', 'send-keys', '-t', tmux_target, keys]
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 70e5eed

Please sign in to comment.