feat(php): add testing and quality commands
Testing: - core php test - run PHPUnit/Pest (auto-detected) - core php test --parallel - parallel testing - core php test --coverage - with coverage - core php test --filter <pattern> - filter tests Quality: - core php fmt - format with Laravel Pint - core php fmt --fix - auto-fix issues - core php analyse - run PHPStan/Larastan - core php analyse --level 9 - set analysis level Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5568f86768
commit
5759d84268
3 changed files with 601 additions and 0 deletions
|
|
@ -63,6 +63,9 @@ func AddPHPCommands(parent *clir.Cli) {
|
||||||
addPHPBuildCommand(phpCmd)
|
addPHPBuildCommand(phpCmd)
|
||||||
addPHPServeCommand(phpCmd)
|
addPHPServeCommand(phpCmd)
|
||||||
addPHPShellCommand(phpCmd)
|
addPHPShellCommand(phpCmd)
|
||||||
|
addPHPTestCommand(phpCmd)
|
||||||
|
addPHPFmtCommand(phpCmd)
|
||||||
|
addPHPAnalyseCommand(phpCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPDevCommand(parent *clir.Command) {
|
func addPHPDevCommand(parent *clir.Command) {
|
||||||
|
|
@ -814,3 +817,188 @@ func addPHPShellCommand(parent *clir.Command) {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addPHPTestCommand(parent *clir.Command) {
|
||||||
|
var (
|
||||||
|
parallel bool
|
||||||
|
coverage bool
|
||||||
|
filter string
|
||||||
|
group string
|
||||||
|
)
|
||||||
|
|
||||||
|
testCmd := parent.NewSubCommand("test", "Run PHP tests (PHPUnit/Pest)")
|
||||||
|
testCmd.LongDescription("Run PHP tests using PHPUnit or Pest.\n\n" +
|
||||||
|
"Auto-detects Pest if tests/Pest.php exists, otherwise uses PHPUnit.\n\n" +
|
||||||
|
"Examples:\n" +
|
||||||
|
" core php test # Run all tests\n" +
|
||||||
|
" core php test --parallel # Run tests in parallel\n" +
|
||||||
|
" core php test --coverage # Run with coverage\n" +
|
||||||
|
" core php test --filter UserTest # Filter by test name")
|
||||||
|
|
||||||
|
testCmd.BoolFlag("parallel", "Run tests in parallel", ¶llel)
|
||||||
|
testCmd.BoolFlag("coverage", "Generate code coverage", &coverage)
|
||||||
|
testCmd.StringFlag("filter", "Filter tests by name pattern", &filter)
|
||||||
|
testCmd.StringFlag("group", "Run only tests in specified group", &group)
|
||||||
|
|
||||||
|
testCmd.Action(func() error {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !php.IsPHPProject(cwd) {
|
||||||
|
return fmt.Errorf("not a PHP project (missing composer.json)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect test runner
|
||||||
|
runner := php.DetectTestRunner(cwd)
|
||||||
|
fmt.Printf("%s Running tests with %s\n\n", dimStyle.Render("PHP:"), runner)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
opts := php.TestOptions{
|
||||||
|
Dir: cwd,
|
||||||
|
Filter: filter,
|
||||||
|
Parallel: parallel,
|
||||||
|
Coverage: coverage,
|
||||||
|
Output: os.Stdout,
|
||||||
|
}
|
||||||
|
|
||||||
|
if group != "" {
|
||||||
|
opts.Groups = []string{group}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := php.RunTests(ctx, opts); err != nil {
|
||||||
|
return fmt.Errorf("tests failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPHPFmtCommand(parent *clir.Command) {
|
||||||
|
var (
|
||||||
|
fix bool
|
||||||
|
diff bool
|
||||||
|
)
|
||||||
|
|
||||||
|
fmtCmd := parent.NewSubCommand("fmt", "Format PHP code with Laravel Pint")
|
||||||
|
fmtCmd.LongDescription("Format PHP code using Laravel Pint.\n\n" +
|
||||||
|
"Examples:\n" +
|
||||||
|
" core php fmt # Check formatting (dry-run)\n" +
|
||||||
|
" core php fmt --fix # Auto-fix formatting issues\n" +
|
||||||
|
" core php fmt --diff # Show diff of changes")
|
||||||
|
|
||||||
|
fmtCmd.BoolFlag("fix", "Auto-fix formatting issues", &fix)
|
||||||
|
fmtCmd.BoolFlag("diff", "Show diff of changes", &diff)
|
||||||
|
|
||||||
|
fmtCmd.Action(func() error {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !php.IsPHPProject(cwd) {
|
||||||
|
return fmt.Errorf("not a PHP project (missing composer.json)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect formatter
|
||||||
|
formatter, found := php.DetectFormatter(cwd)
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("no formatter found (install Laravel Pint: composer require laravel/pint --dev)")
|
||||||
|
}
|
||||||
|
|
||||||
|
action := "Checking"
|
||||||
|
if fix {
|
||||||
|
action = "Formatting"
|
||||||
|
}
|
||||||
|
fmt.Printf("%s %s code with %s\n\n", dimStyle.Render("PHP:"), action, formatter)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
opts := php.FormatOptions{
|
||||||
|
Dir: cwd,
|
||||||
|
Fix: fix,
|
||||||
|
Diff: diff,
|
||||||
|
Output: os.Stdout,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get any additional paths from args
|
||||||
|
if args := fmtCmd.OtherArgs(); len(args) > 0 {
|
||||||
|
opts.Paths = args
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := php.Format(ctx, opts); err != nil {
|
||||||
|
if fix {
|
||||||
|
return fmt.Errorf("formatting failed: %w", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("formatting issues found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fix {
|
||||||
|
fmt.Printf("\n%s Code formatted successfully\n", successStyle.Render("Done:"))
|
||||||
|
} else {
|
||||||
|
fmt.Printf("\n%s No formatting issues found\n", successStyle.Render("Done:"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPHPAnalyseCommand(parent *clir.Command) {
|
||||||
|
var (
|
||||||
|
level int
|
||||||
|
memory string
|
||||||
|
)
|
||||||
|
|
||||||
|
analyseCmd := parent.NewSubCommand("analyse", "Run PHPStan static analysis")
|
||||||
|
analyseCmd.LongDescription("Run PHPStan or Larastan static analysis.\n\n" +
|
||||||
|
"Auto-detects Larastan if installed, otherwise uses PHPStan.\n\n" +
|
||||||
|
"Examples:\n" +
|
||||||
|
" core php analyse # Run analysis\n" +
|
||||||
|
" core php analyse --level 9 # Run at max strictness\n" +
|
||||||
|
" core php analyse --memory 2G # Increase memory limit")
|
||||||
|
|
||||||
|
analyseCmd.IntFlag("level", "PHPStan analysis level (0-9)", &level)
|
||||||
|
analyseCmd.StringFlag("memory", "Memory limit (e.g., 2G)", &memory)
|
||||||
|
|
||||||
|
analyseCmd.Action(func() error {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !php.IsPHPProject(cwd) {
|
||||||
|
return fmt.Errorf("not a PHP project (missing composer.json)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect analyser
|
||||||
|
analyser, found := php.DetectAnalyser(cwd)
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("no static analyser found (install PHPStan: composer require phpstan/phpstan --dev)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s Running static analysis with %s\n\n", dimStyle.Render("PHP:"), analyser)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
opts := php.AnalyseOptions{
|
||||||
|
Dir: cwd,
|
||||||
|
Level: level,
|
||||||
|
Memory: memory,
|
||||||
|
Output: os.Stdout,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get any additional paths from args
|
||||||
|
if args := analyseCmd.OtherArgs(); len(args) > 0 {
|
||||||
|
opts.Paths = args
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := php.Analyse(ctx, opts); err != nil {
|
||||||
|
return fmt.Errorf("analysis found issues: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n%s No issues found\n", successStyle.Render("Done:"))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
238
pkg/php/quality.go
Normal file
238
pkg/php/quality.go
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
package php
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FormatOptions configures PHP code formatting.
|
||||||
|
type FormatOptions struct {
|
||||||
|
// Dir is the project directory (defaults to current working directory).
|
||||||
|
Dir string
|
||||||
|
|
||||||
|
// Fix automatically fixes formatting issues.
|
||||||
|
Fix bool
|
||||||
|
|
||||||
|
// Diff shows a diff of changes instead of modifying files.
|
||||||
|
Diff bool
|
||||||
|
|
||||||
|
// Paths limits formatting to specific paths.
|
||||||
|
Paths []string
|
||||||
|
|
||||||
|
// Output is the writer for output (defaults to os.Stdout).
|
||||||
|
Output io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnalyseOptions configures PHP static analysis.
|
||||||
|
type AnalyseOptions struct {
|
||||||
|
// Dir is the project directory (defaults to current working directory).
|
||||||
|
Dir string
|
||||||
|
|
||||||
|
// Level is the PHPStan analysis level (0-9).
|
||||||
|
Level int
|
||||||
|
|
||||||
|
// Paths limits analysis to specific paths.
|
||||||
|
Paths []string
|
||||||
|
|
||||||
|
// Memory is the memory limit for analysis (e.g., "2G").
|
||||||
|
Memory string
|
||||||
|
|
||||||
|
// Output is the writer for output (defaults to os.Stdout).
|
||||||
|
Output io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatterType represents the detected formatter.
|
||||||
|
type FormatterType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FormatterPint FormatterType = "pint"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnalyserType represents the detected static analyser.
|
||||||
|
type AnalyserType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AnalyserPHPStan AnalyserType = "phpstan"
|
||||||
|
AnalyserLarastan AnalyserType = "larastan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DetectFormatter detects which formatter is available in the project.
|
||||||
|
func DetectFormatter(dir string) (FormatterType, bool) {
|
||||||
|
// Check for Pint config
|
||||||
|
pintConfig := filepath.Join(dir, "pint.json")
|
||||||
|
if _, err := os.Stat(pintConfig); err == nil {
|
||||||
|
return FormatterPint, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for vendor binary
|
||||||
|
pintBin := filepath.Join(dir, "vendor", "bin", "pint")
|
||||||
|
if _, err := os.Stat(pintBin); err == nil {
|
||||||
|
return FormatterPint, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetectAnalyser detects which static analyser is available in the project.
|
||||||
|
func DetectAnalyser(dir string) (AnalyserType, bool) {
|
||||||
|
// Check for PHPStan config
|
||||||
|
phpstanConfig := filepath.Join(dir, "phpstan.neon")
|
||||||
|
phpstanDistConfig := filepath.Join(dir, "phpstan.neon.dist")
|
||||||
|
|
||||||
|
hasConfig := false
|
||||||
|
if _, err := os.Stat(phpstanConfig); err == nil {
|
||||||
|
hasConfig = true
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(phpstanDistConfig); err == nil {
|
||||||
|
hasConfig = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for vendor binary
|
||||||
|
phpstanBin := filepath.Join(dir, "vendor", "bin", "phpstan")
|
||||||
|
hasBin := false
|
||||||
|
if _, err := os.Stat(phpstanBin); err == nil {
|
||||||
|
hasBin = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasConfig || hasBin {
|
||||||
|
// Check if it's Larastan (Laravel-specific PHPStan)
|
||||||
|
larastanPath := filepath.Join(dir, "vendor", "larastan", "larastan")
|
||||||
|
if _, err := os.Stat(larastanPath); err == nil {
|
||||||
|
return AnalyserLarastan, true
|
||||||
|
}
|
||||||
|
// Also check nunomaduro/larastan
|
||||||
|
larastanPath2 := filepath.Join(dir, "vendor", "nunomaduro", "larastan")
|
||||||
|
if _, err := os.Stat(larastanPath2); err == nil {
|
||||||
|
return AnalyserLarastan, true
|
||||||
|
}
|
||||||
|
return AnalyserPHPStan, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format runs Laravel Pint to format PHP code.
|
||||||
|
func Format(ctx context.Context, opts FormatOptions) error {
|
||||||
|
if opts.Dir == "" {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
}
|
||||||
|
opts.Dir = cwd
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Output == nil {
|
||||||
|
opts.Output = os.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if formatter is available
|
||||||
|
formatter, found := DetectFormatter(opts.Dir)
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("no formatter found (install Laravel Pint: composer require laravel/pint --dev)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdName string
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
switch formatter {
|
||||||
|
case FormatterPint:
|
||||||
|
cmdName, args = buildPintCommand(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, cmdName, args...)
|
||||||
|
cmd.Dir = opts.Dir
|
||||||
|
cmd.Stdout = opts.Output
|
||||||
|
cmd.Stderr = opts.Output
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyse runs PHPStan or Larastan for static analysis.
|
||||||
|
func Analyse(ctx context.Context, opts AnalyseOptions) error {
|
||||||
|
if opts.Dir == "" {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
}
|
||||||
|
opts.Dir = cwd
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Output == nil {
|
||||||
|
opts.Output = os.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if analyser is available
|
||||||
|
analyser, found := DetectAnalyser(opts.Dir)
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("no static analyser found (install PHPStan: composer require phpstan/phpstan --dev)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdName string
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
switch analyser {
|
||||||
|
case AnalyserPHPStan, AnalyserLarastan:
|
||||||
|
cmdName, args = buildPHPStanCommand(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, cmdName, args...)
|
||||||
|
cmd.Dir = opts.Dir
|
||||||
|
cmd.Stdout = opts.Output
|
||||||
|
cmd.Stderr = opts.Output
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildPintCommand builds the command for running Laravel Pint.
|
||||||
|
func buildPintCommand(opts FormatOptions) (string, []string) {
|
||||||
|
// Check for vendor binary first
|
||||||
|
vendorBin := filepath.Join(opts.Dir, "vendor", "bin", "pint")
|
||||||
|
cmdName := "pint"
|
||||||
|
if _, err := os.Stat(vendorBin); err == nil {
|
||||||
|
cmdName = vendorBin
|
||||||
|
}
|
||||||
|
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
if !opts.Fix {
|
||||||
|
args = append(args, "--test")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Diff {
|
||||||
|
args = append(args, "--diff")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add specific paths if provided
|
||||||
|
args = append(args, opts.Paths...)
|
||||||
|
|
||||||
|
return cmdName, args
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildPHPStanCommand builds the command for running PHPStan.
|
||||||
|
func buildPHPStanCommand(opts AnalyseOptions) (string, []string) {
|
||||||
|
// Check for vendor binary first
|
||||||
|
vendorBin := filepath.Join(opts.Dir, "vendor", "bin", "phpstan")
|
||||||
|
cmdName := "phpstan"
|
||||||
|
if _, err := os.Stat(vendorBin); err == nil {
|
||||||
|
cmdName = vendorBin
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"analyse"}
|
||||||
|
|
||||||
|
if opts.Level > 0 {
|
||||||
|
args = append(args, "--level", fmt.Sprintf("%d", opts.Level))
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Memory != "" {
|
||||||
|
args = append(args, "--memory-limit", opts.Memory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add specific paths if provided
|
||||||
|
args = append(args, opts.Paths...)
|
||||||
|
|
||||||
|
return cmdName, args
|
||||||
|
}
|
||||||
175
pkg/php/testing.go
Normal file
175
pkg/php/testing.go
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
package php
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestOptions configures PHP test execution.
|
||||||
|
type TestOptions struct {
|
||||||
|
// Dir is the project directory (defaults to current working directory).
|
||||||
|
Dir string
|
||||||
|
|
||||||
|
// Filter filters tests by name pattern.
|
||||||
|
Filter string
|
||||||
|
|
||||||
|
// Parallel runs tests in parallel.
|
||||||
|
Parallel bool
|
||||||
|
|
||||||
|
// Coverage generates code coverage.
|
||||||
|
Coverage bool
|
||||||
|
|
||||||
|
// CoverageFormat is the coverage output format (text, html, clover).
|
||||||
|
CoverageFormat string
|
||||||
|
|
||||||
|
// Groups runs only tests in the specified groups.
|
||||||
|
Groups []string
|
||||||
|
|
||||||
|
// Output is the writer for test output (defaults to os.Stdout).
|
||||||
|
Output io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRunner represents the detected test runner.
|
||||||
|
type TestRunner string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TestRunnerPest TestRunner = "pest"
|
||||||
|
TestRunnerPHPUnit TestRunner = "phpunit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DetectTestRunner detects which test runner is available in the project.
|
||||||
|
// Returns Pest if tests/Pest.php exists, otherwise PHPUnit.
|
||||||
|
func DetectTestRunner(dir string) TestRunner {
|
||||||
|
// Check for Pest
|
||||||
|
pestFile := filepath.Join(dir, "tests", "Pest.php")
|
||||||
|
if _, err := os.Stat(pestFile); err == nil {
|
||||||
|
return TestRunnerPest
|
||||||
|
}
|
||||||
|
|
||||||
|
return TestRunnerPHPUnit
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunTests runs PHPUnit or Pest tests.
|
||||||
|
func RunTests(ctx context.Context, opts TestOptions) error {
|
||||||
|
if opts.Dir == "" {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
}
|
||||||
|
opts.Dir = cwd
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Output == nil {
|
||||||
|
opts.Output = os.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect test runner
|
||||||
|
runner := DetectTestRunner(opts.Dir)
|
||||||
|
|
||||||
|
// Build command based on runner
|
||||||
|
var cmdName string
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
switch runner {
|
||||||
|
case TestRunnerPest:
|
||||||
|
cmdName, args = buildPestCommand(opts)
|
||||||
|
default:
|
||||||
|
cmdName, args = buildPHPUnitCommand(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, cmdName, args...)
|
||||||
|
cmd.Dir = opts.Dir
|
||||||
|
cmd.Stdout = opts.Output
|
||||||
|
cmd.Stderr = opts.Output
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunParallel runs tests in parallel using the appropriate runner.
|
||||||
|
func RunParallel(ctx context.Context, opts TestOptions) error {
|
||||||
|
opts.Parallel = true
|
||||||
|
return RunTests(ctx, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildPestCommand builds the command for running Pest tests.
|
||||||
|
func buildPestCommand(opts TestOptions) (string, []string) {
|
||||||
|
// Check for vendor binary first
|
||||||
|
vendorBin := filepath.Join(opts.Dir, "vendor", "bin", "pest")
|
||||||
|
cmdName := "pest"
|
||||||
|
if _, err := os.Stat(vendorBin); err == nil {
|
||||||
|
cmdName = vendorBin
|
||||||
|
}
|
||||||
|
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
if opts.Filter != "" {
|
||||||
|
args = append(args, "--filter", opts.Filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Parallel {
|
||||||
|
args = append(args, "--parallel")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Coverage {
|
||||||
|
switch opts.CoverageFormat {
|
||||||
|
case "html":
|
||||||
|
args = append(args, "--coverage-html", "coverage")
|
||||||
|
case "clover":
|
||||||
|
args = append(args, "--coverage-clover", "coverage.xml")
|
||||||
|
default:
|
||||||
|
args = append(args, "--coverage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range opts.Groups {
|
||||||
|
args = append(args, "--group", group)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmdName, args
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildPHPUnitCommand builds the command for running PHPUnit tests.
|
||||||
|
func buildPHPUnitCommand(opts TestOptions) (string, []string) {
|
||||||
|
// Check for vendor binary first
|
||||||
|
vendorBin := filepath.Join(opts.Dir, "vendor", "bin", "phpunit")
|
||||||
|
cmdName := "phpunit"
|
||||||
|
if _, err := os.Stat(vendorBin); err == nil {
|
||||||
|
cmdName = vendorBin
|
||||||
|
}
|
||||||
|
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
if opts.Filter != "" {
|
||||||
|
args = append(args, "--filter", opts.Filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Parallel {
|
||||||
|
// PHPUnit uses paratest for parallel execution
|
||||||
|
paratestBin := filepath.Join(opts.Dir, "vendor", "bin", "paratest")
|
||||||
|
if _, err := os.Stat(paratestBin); err == nil {
|
||||||
|
cmdName = paratestBin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Coverage {
|
||||||
|
switch opts.CoverageFormat {
|
||||||
|
case "html":
|
||||||
|
args = append(args, "--coverage-html", "coverage")
|
||||||
|
case "clover":
|
||||||
|
args = append(args, "--coverage-clover", "coverage.xml")
|
||||||
|
default:
|
||||||
|
args = append(args, "--coverage-text")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range opts.Groups {
|
||||||
|
args = append(args, "--group", group)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmdName, args
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue