feat(cicontext): rules:changes: path-glob evaluation; 100% test coverage
release / Build and publish release (push) Successful in 1m12s
ci / vet, staticcheck, test, build (push) Failing after 1m54s

- Add --changes PATH and --changes-from REF flags to glint check and glint graph
  for rules:changes: evaluation. --changes marks files explicitly; --changes-from
  runs git diff --name-only <REF> automatically. Both flags can be combined.
- Implement doublestar glob matching (*, ** across path segments) in EvalJob and
  EvalWorkflow; extended {paths, compare_to} map form supported.
- Without --changes/--changes-from the condition stays permissive (existing behaviour).
- Context summary line now shows changed-file count when file data is provided.
- Achieve 100% statement coverage: comprehensive tests added across all packages;
  removed provably dead code; added testability seams (exit, userHomeDirFn,
  execCommandOutput variables) to cover previously unreachable paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 22:47:32 +02:00
parent 04f17f8616
commit b21ef5c0bb
14 changed files with 1163 additions and 13 deletions
+37
View File
@@ -1,11 +1,20 @@
package resolver
import (
"fmt"
"testing"
"git.k3nny.fr/glint/internal/model"
)
// errorYAMLMarshaler implements yaml.Marshaler and always returns an error,
// allowing tests to trigger the yaml.Marshal failure path in Resolve.
type errorYAMLMarshaler struct{}
func (e errorYAMLMarshaler) MarshalYAML() (interface{}, error) {
return nil, fmt.Errorf("forced marshal error for test")
}
// ── parseExtends ──────────────────────────────────────────────────────────────
func TestParseExtends(t *testing.T) {
@@ -253,4 +262,32 @@ func TestResolve(t *testing.T) {
t.Fatal("expected error for invalid extends type")
}
})
t.Run("yaml.Marshal fails — errorYAMLMarshaler injected into merged map", func(t *testing.T) {
// Inject a value whose MarshalYAML() returns an error so yaml.Marshal
// fails at extends.go:74-77.
p := buildPipeline(map[string]map[string]any{
".base": {"script": []any{"echo"}},
"child": {"extends": ".base", "bad": errorYAMLMarshaler{}},
})
_, err := Resolve(p)
if err == nil {
t.Fatal("expected error when yaml.Marshal fails")
}
})
t.Run("yaml.Unmarshal fails — map injected where []string expected", func(t *testing.T) {
// Marshal succeeds (maps are valid YAML), but Unmarshal into model.Job
// fails because Dependencies []string cannot hold a mapping.
p := buildPipeline(map[string]map[string]any{
".base": {
"dependencies": map[string]any{"invalid": "not-a-list"},
},
"child": {"extends": ".base", "stage": "build"},
})
_, err := Resolve(p)
if err == nil {
t.Fatal("expected error when yaml.Unmarshal fails on incompatible type")
}
})
}