Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to rtk (Rust Token Killer) will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Features

* **php:** add PHPUnit support with compact output (65%+ token savings). Handles `phpunit`, `vendor/bin/phpunit`, `bin/phpunit` (Symfony), and `php ...` variants. State-machine filter captures failures with test names and assertion messages, ignoring header/progress/timing noise. Integrates with `rtk discover` and tee output recovery.

## [0.33.1](https://github.com/rtk-ai/rtk/compare/v0.33.0...v0.33.1) (2026-03-25)


Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ rtk go test # Go tests (NDJSON, -90%)
rtk cargo test # Cargo tests (-90%)
rtk rake test # Ruby minitest (-90%)
rtk rspec # RSpec tests (JSON, -60%+)
rtk phpunit # PHPUnit tests (failures only, -65%+)
```

### Build & Lint
Expand Down Expand Up @@ -408,6 +409,7 @@ Plugin in `openclaw/` directory. Uses `before_tool_call` hook, delegates to `rtk
| `rake test` / `rails test` | `rtk rake test` |
| `rspec` / `bundle exec rspec` | `rtk rspec` |
| `rubocop` / `bundle exec rubocop` | `rtk rubocop` |
| `phpunit` / `vendor/bin/phpunit` / `bin/phpunit` | `rtk phpunit` |
| `bundle install/update` | `rtk bundle ...` |
| `docker ps/images/logs` | `rtk docker ...` |
| `kubectl get/logs` | `rtk kubectl ...` |
Expand Down
12 changes: 11 additions & 1 deletion scripts/test-all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,17 @@ else
skip_test "rtk rake" "rake not installed"
fi

# ── 31. Global flags ────────────────────────────────
# ── 31. PHP (conditional) ───────────────────────────

section "PHP (conditional)"

if command -v phpunit &>/dev/null || [ -f vendor/bin/phpunit ] || [ -f bin/phpunit ]; then
assert_help "rtk phpunit" rtk phpunit --help
else
skip_test "rtk phpunit" "phpunit not installed"
fi

# ── 32. Global flags ────────────────────────────────

section "Global flags"

Expand Down
99 changes: 99 additions & 0 deletions src/discover/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2325,4 +2325,103 @@ mod tests {
assert_eq!(strip_git_global_opts("git status"), "git status");
assert_eq!(strip_git_global_opts("cargo test"), "cargo test");
}

// --- PHP tooling ---

#[test]
fn test_classify_phpunit() {
assert!(matches!(
classify_command("phpunit tests/"),
Classification::Supported {
rtk_equivalent: "rtk phpunit",
..
}
));
}

#[test]
fn test_classify_vendor_bin_phpunit() {
assert!(matches!(
classify_command("vendor/bin/phpunit --filter EmailTest"),
Classification::Supported {
rtk_equivalent: "rtk phpunit",
..
}
));
}

#[test]
fn test_classify_php_vendor_bin_phpunit() {
assert!(matches!(
classify_command("php vendor/bin/phpunit tests/"),
Classification::Supported {
rtk_equivalent: "rtk phpunit",
..
}
));
}

#[test]
fn test_classify_bin_phpunit() {
// bin/phpunit is used by Symfony apps
assert!(matches!(
classify_command("bin/phpunit tests/"),
Classification::Supported {
rtk_equivalent: "rtk phpunit",
..
}
));
}

#[test]
fn test_classify_php_bin_phpunit() {
assert!(matches!(
classify_command("php bin/phpunit --filter EmailTest"),
Classification::Supported {
rtk_equivalent: "rtk phpunit",
..
}
));
}

#[test]
fn test_rewrite_phpunit() {
assert_eq!(
rewrite_command("phpunit tests/", &[]),
Some("rtk phpunit tests/".into())
);
}

#[test]
fn test_rewrite_vendor_bin_phpunit() {
assert_eq!(
rewrite_command("vendor/bin/phpunit --filter EmailTest", &[]),
Some("rtk phpunit --filter EmailTest".into())
);
}

#[test]
fn test_rewrite_php_vendor_bin_phpunit() {
assert_eq!(
rewrite_command("php vendor/bin/phpunit tests/", &[]),
Some("rtk phpunit tests/".into())
);
}

#[test]
fn test_rewrite_bin_phpunit() {
// bin/phpunit used by Symfony apps
assert_eq!(
rewrite_command("bin/phpunit tests/", &[]),
Some("rtk phpunit tests/".into())
);
}

#[test]
fn test_rewrite_php_bin_phpunit() {
assert_eq!(
rewrite_command("php bin/phpunit --filter EmailTest", &[]),
Some("rtk phpunit --filter EmailTest".into())
);
}
}
17 changes: 17 additions & 0 deletions src/discover/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub const PATTERNS: &[&str] = &[
r"^(?:bundle\s+exec\s+)?(?:bin/)?(?:rake|rails)\s+test",
r"^(?:bundle\s+exec\s+)?rspec(?:\s|$)",
r"^(?:bundle\s+exec\s+)?rubocop(?:\s|$)",
// PHP tooling
r"^(?:php\s+)?(?:(?:vendor/bin|bin)/)?phpunit(?:\s|$)",
// AWS CLI
r"^aws\s+",
// PostgreSQL
Expand Down Expand Up @@ -376,6 +378,21 @@ pub const RULES: &[RtkRule] = &[
subcmd_savings: &[],
subcmd_status: &[],
},
// PHP tooling
RtkRule {
rtk_cmd: "rtk phpunit",
rewrite_prefixes: &[
"php vendor/bin/phpunit",
"php bin/phpunit",
"vendor/bin/phpunit",
"bin/phpunit",
"phpunit",
],
category: "Tests",
savings_pct: 65.0,
subcmd_savings: &[],
subcmd_status: &[],
},
// AWS CLI
RtkRule {
rtk_cmd: "rtk aws",
Expand Down
12 changes: 12 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ mod next_cmd;
mod npm_cmd;
mod parser;
mod permissions;
mod phpunit_cmd;
mod pip_cmd;
mod playwright_cmd;
mod pnpm_cmd;
Expand Down Expand Up @@ -723,6 +724,12 @@ enum Commands {
#[command(subcommand)]
command: HookCommands,
},

/// Run PHPUnit tests with compact output (PHP)
Phpunit {
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
}

#[derive(Subcommand)]
Expand Down Expand Up @@ -2244,6 +2251,10 @@ fn main() -> Result<()> {
verify_cmd::run(None, require_all)?;
}
}

Commands::Phpunit { args } => {
phpunit_cmd::run(&args, cli.verbose)?;
}
}

Ok(())
Expand Down Expand Up @@ -2303,6 +2314,7 @@ fn is_operational_command(cmd: &Commands) -> bool {
| Commands::Go { .. }
| Commands::GolangciLint { .. }
| Commands::Gt { .. }
| Commands::Phpunit { .. }
)
}

Expand Down
Loading