diff --git a/cmd/phpup/main.go b/cmd/phpup/main.go index dc4bd53..a818e4b 100644 --- a/cmd/phpup/main.go +++ b/cmd/phpup/main.go @@ -45,10 +45,11 @@ func main() { if err != nil { log.Fatalf("parse inputs: %v", err) } + p.ApplyCoverage() if p.Verbose { - log.Printf("Plan: PHP %s, extensions=%v, os=%s, arch=%s, ts=%s", - p.PHPVersion, p.Extensions, p.OS, p.Arch, p.ThreadSafety) + log.Printf("Plan: PHP %s, extensions=%v, os=%s, arch=%s, ts=%s, coverage=%s", + p.PHPVersion, p.Extensions, p.OS, p.Arch, p.ThreadSafety, p.Coverage) } // 2. Load embedded lockfile @@ -61,7 +62,9 @@ func main() { cat := &catalog.Catalog{ PHP: &catalog.PHPSpec{BundledExtensions: bundledExtensions}, Extensions: map[string]*catalog.ExtensionSpec{ - "redis": {Name: "redis", Kind: catalog.ExtensionKindPECL, Versions: []string{"6.2.0"}}, + "redis": {Name: "redis", Kind: catalog.ExtensionKindPECL, Versions: []string{"6.2.0"}}, + "xdebug": {Name: "xdebug", Kind: catalog.ExtensionKindPECL, Versions: []string{"3.5.1"}, Ini: []string{"zend_extension=xdebug", "xdebug.mode=coverage"}}, + "pcov": {Name: "pcov", Kind: catalog.ExtensionKindPECL, Versions: []string{"1.0.12"}, Ini: []string{"extension=pcov", "pcov.enabled=1"}}, }, } diff --git a/internal/plan/plan.go b/internal/plan/plan.go index 3db5199..c6d8f0b 100644 --- a/internal/plan/plan.go +++ b/internal/plan/plan.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "sort" "strings" ) @@ -118,6 +119,26 @@ func ParsePHPVersionFile(path string) (string, error) { return strings.TrimSpace(string(data)), nil } +// ApplyCoverage adds the requested coverage driver (xdebug or pcov) to the +// extensions list so it is resolved, fetched, and composed like any other +// extension. "none" is a no-op. +func (p *Plan) ApplyCoverage() { + var driver string + switch p.Coverage { + case CoverageXdebug: + driver = "xdebug" + case CoveragePCOV: + driver = "pcov" + default: + return + } + if slices.Contains(p.Extensions, driver) { + return + } + p.Extensions = append(p.Extensions, driver) + sort.Strings(p.Extensions) +} + func (p *Plan) Hash() string { h := sha256.New() _, _ = fmt.Fprintf(h, "php:%s\n", p.PHPVersion) diff --git a/internal/plan/plan_test.go b/internal/plan/plan_test.go index 8e16bae..a4ddc6c 100644 --- a/internal/plan/plan_test.go +++ b/internal/plan/plan_test.go @@ -110,6 +110,38 @@ func TestParsePHPVersionFile(t *testing.T) { } } +func TestApplyCoverage(t *testing.T) { + tests := []struct { + name string + coverage CoverageDriver + initial []string + want []string + }{ + {"none is no-op", CoverageNone, []string{"intl"}, []string{"intl"}}, + {"empty is no-op", CoverageDriver(""), []string{"intl"}, []string{"intl"}}, + {"xdebug adds driver", CoverageXdebug, []string{"intl"}, []string{"intl", "xdebug"}}, + {"pcov adds driver", CoveragePCOV, []string{"intl"}, []string{"intl", "pcov"}}, + {"xdebug keeps sort order", CoverageXdebug, []string{"redis"}, []string{"redis", "xdebug"}}, + {"pcov sorted before redis", CoveragePCOV, []string{"redis"}, []string{"pcov", "redis"}}, + {"already present: no duplicate", CoverageXdebug, []string{"xdebug"}, []string{"xdebug"}}, + {"empty extensions + xdebug", CoverageXdebug, nil, []string{"xdebug"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Plan{Coverage: tt.coverage, Extensions: append([]string(nil), tt.initial...)} + p.ApplyCoverage() + if len(p.Extensions) != len(tt.want) { + t.Fatalf("Extensions = %v, want %v", p.Extensions, tt.want) + } + for i := range p.Extensions { + if p.Extensions[i] != tt.want[i] { + t.Errorf("Extensions[%d] = %q, want %q", i, p.Extensions[i], tt.want[i]) + } + } + }) + } +} + func TestHashDeterminism(t *testing.T) { p := &Plan{ PHPVersion: "8.4",