Skip to content

Commit

Permalink
🤟 trace: wire
Browse files Browse the repository at this point in the history
  • Loading branch information
rjeczalik committed Jun 30, 2024
1 parent 8907865 commit aad643e
Show file tree
Hide file tree
Showing 11 changed files with 443 additions and 134 deletions.
2 changes: 1 addition & 1 deletion hkt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func newRunCommand(ctx context.Context, app *command.App) *cobra.Command {
return err
}

return app.Render(s)
return app.Render(s.Events)
},
Version: version,
SilenceUsage: true,
Expand Down
85 changes: 84 additions & 1 deletion pkg/check/check.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package check // import "hookt.dev/cmd/pkg/check"

import "sync"
import (
"context"
"strconv"
"sync"

"github.com/itchyny/gojq"
"hookt.dev/cmd/pkg/trace"
)

type S struct {
mu sync.Mutex

Events Events

Steps struct {
OK int
Fail int
Expand All @@ -22,3 +31,77 @@ func (s *S) Fail() {
s.Steps.Fail++
s.mu.Unlock()
}

func (s *S) Trace() trace.PatternTrace {
return trace.PatternTrace{
ParseKey: func(ctx context.Context, q *gojq.Query, err error) {
if err != nil {
return
}

n, _ := strconv.Atoi(trace.Get(ctx, "step-index"))
desc := trace.Get(ctx, "step-desc")
group := trace.Get(ctx, "pattern-group")
pattern := trace.Get(ctx, "pattern")

s.mu.Lock()

if n >= len(s.Events) {
for i := len(s.Events); i <= n; i++ {
s.Events = append(s.Events, Event{})
}
}

s.Events[n].Desc = desc
s.Events[n].MarkPattern(group, pattern, false)

s.mu.Unlock()
},
EqualMatch: func(ctx context.Context, a, b any, eq bool) {
if !eq {
return
}

n, _ := strconv.Atoi(trace.Get(ctx, "step-index"))
group := trace.Get(ctx, "pattern-group")
pattern := trace.Get(ctx, "pattern")

s.mu.Lock()

s.Events[n].MarkPattern(group, pattern, eq)

s.mu.Unlock()
},
}
}

func (e *Event) MarkPattern(group, pattern string, ok bool) {
switch group {
case "match":
if e.Match == nil {
e.Match = make(map[string]bool)
}
e.Match[pattern] = ok
case "pass":
if e.Pass == nil {
e.Pass = make(map[string]bool)
}
e.Pass[pattern] = ok
case "fail":
if e.Fail == nil {
e.Fail = make(map[string]bool)
}
e.Fail[pattern] = ok
default:
panic("unknown group: " + group)
}
}

type Event struct {
Desc string `json:"desc,omitempty"`
Match map[string]bool `json:"match"`
Pass map[string]bool `json:"pass,omitempty"`
Fail map[string]bool `json:"fail,omitempty"`
}

type Events []Event
30 changes: 25 additions & 5 deletions pkg/hookt/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"context"
"log/slog"
"os"
"strconv"

"github.com/lmittmann/tint"
"golang.org/x/sync/errgroup"
"hookt.dev/cmd/pkg/check"
"hookt.dev/cmd/pkg/errors"
"hookt.dev/cmd/pkg/plugin/builtin"
"hookt.dev/cmd/pkg/proto"
"hookt.dev/cmd/pkg/trace"
)

var plugins []proto.Interface
Expand Down Expand Up @@ -43,17 +45,24 @@ func (e *Engine) Run(ctx context.Context, file string) (*check.S, error) {
return nil, errors.New("failed to read file: %w", err)
}

var s check.S

ctx = trace.WithPattern(ctx, trace.ContextPattern(ctx).Join(s.Trace()))

w, err := e.p.Parse(ctx, p)
if err != nil {
return nil, errors.New("failed to parse file: %w", err)
}

var s check.S

var g errgroup.Group

for _, job := range w.Jobs {
for _, step := range job.Steps {
for i, job := range w.Jobs {
ctx := trace.With(ctx, "job", job.ID)
ctx = trace.With(ctx, "job-index", strconv.Itoa(i))
for j, step := range job.Steps {
ctx := trace.With(ctx, "step", step.ID)
ctx = trace.With(ctx, "step-desc", step.Desc)
ctx = trace.With(ctx, "step-index", strconv.Itoa(j))
g.Go(func() error {
r, ok := step.With.(proto.Runner)
if !ok {
Expand All @@ -80,5 +89,16 @@ func (e *Engine) Run(ctx context.Context, file string) (*check.S, error) {
}
}

return &s, g.Wait()
done := make(chan error)

go func() {
done <- g.Wait()
}()

select {
case <-ctx.Done():
return &s, ctx.Err()
case err := <-done:
return &s, err
}
}
1 change: 0 additions & 1 deletion pkg/hookt/reflow.go

This file was deleted.

35 changes: 35 additions & 0 deletions pkg/id/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package id

import (
"math/rand"
"strings"
)

const (
all = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)

func Gen(n int) string {
return gen(all, 6, 1<<6-1, 63/6, n)
}

// source: https://stackoverflow.com/questions/22892120/
func gen(chars string, idxBits uint, idxMask int64, idxMax int, n int) string {
var sb strings.Builder

sb.Grow(n)

for i, cache, remain := n-1, rand.Int63(), idxMax; i >= 0; {
if remain == 0 {
cache, remain = rand.Int63(), idxMax
}
if idx := int(cache & idxMask); idx < len(chars) {
sb.WriteByte(chars[idx])
i--
}
cache >>= idxBits
remain--
}

return sb.String()
}
27 changes: 13 additions & 14 deletions pkg/plugin/builtin/event/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ package event // import "hookt.dev/cmd/pkg/plugin/builtin/event"

import (
"context"
"encoding/json"
"log/slog"
"time"

"hookt.dev/cmd/pkg/check"
"hookt.dev/cmd/pkg/errors"
"hookt.dev/cmd/pkg/plugin/builtin/event/wire"
"hookt.dev/cmd/pkg/proto"
"hookt.dev/cmd/pkg/trace"
)

type Plugin struct {
Expand Down Expand Up @@ -94,7 +94,7 @@ func (p *Plugin) process() {
}
}

func (p *Plugin) Step(context.Context) any {
func (p *Plugin) Step(ctx context.Context) any {
c := make(chan proto.Message)
p.c[c] = struct{}{}
it, _ := time.ParseDuration(p.Config.InactiveTimeout)
Expand All @@ -113,32 +113,30 @@ type Step struct {
it time.Duration
}

func str(v any) string {
if v == nil {
return ""
}
p, _ := json.Marshal(v)
return string(p)
func group(ctx context.Context, name string) context.Context {
return trace.With(ctx, "pattern-group", name)
}

func (s *Step) Run(ctx context.Context, c *check.S) error {
slog.Debug("event: run",
"match", str(s.Match),
"pass", str(s.Pass),
"fail", str(s.Fail),
"match", s.Match,
"pass", s.Pass,
"fail", s.Fail,
)

match, err := s.p.p.Pattern(ctx, s.Match)
tr := trace.ContextPattern(ctx)

match, err := s.p.p.Patterns(group(ctx, "match"), s.Match)
if err != nil {
return errors.New("failed to parse match pattern: %w", err)
}

pass, err := s.p.p.Pattern(ctx, s.Pass)
pass, err := s.p.p.Patterns(group(ctx, "pass"), s.Pass)
if err != nil {
return errors.New("failed to parse pass pattern: %w", err)
}

fail, err := s.p.p.Pattern(ctx, s.Fail)
fail, err := s.p.p.Patterns(group(ctx, "fail"), s.Fail)
if err != nil {
return errors.New("failed to parse fail pattern: %w", err)
}
Expand All @@ -150,6 +148,7 @@ func (s *Step) Run(ctx context.Context, c *check.S) error {
select {
case <-inactive.C:
c.Fail()
tr.MatchTimeout(ctx)
return errors.New("step has timed out after %v", s.it)
case msg := <-s.c:
if !inactive.Stop() {
Expand Down
54 changes: 35 additions & 19 deletions pkg/proto/pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"context"
"fmt"
"log/slog"
"text/template"
"sort"

"hookt.dev/cmd/pkg/errors"
"hookt.dev/cmd/pkg/proto/wire"
Expand All @@ -15,14 +15,19 @@ import (
"sigs.k8s.io/yaml"
)

type Pattern map[*gojq.Query]func(context.Context, any) (bool, error)
type Pattern struct {
Key *gojq.Query
Match func(context.Context, any) (bool, error)
}

type Patterns []*Pattern

func (p Pattern) Match(ctx context.Context, obj any) (bool, error) {
for q, fn := range p {
it := q.RunWithContext(ctx, obj)
func (p Patterns) Match(ctx context.Context, obj any) (bool, error) {
for _, p := range p {
it := p.Key.RunWithContext(ctx, obj)

slog.Debug("pattern",
"query", q.String(),
"query", p.Key.String(),
)

// TODO: Handle multiple results?
Expand All @@ -31,9 +36,9 @@ func (p Pattern) Match(ctx context.Context, obj any) (bool, error) {
return false, nil
}

ok, err := fn(ctx, v)
ok, err := p.Match(ctx, v)
if err != nil {
return false, errors.New("failed to match jq %q: %w", q.String(), err)
return false, errors.New("failed to match jq %q: %w", p.Key.String(), err)
}
if !ok {
return false, nil
Expand All @@ -43,16 +48,23 @@ func (p Pattern) Match(ctx context.Context, obj any) (bool, error) {
return len(p) != 0, nil
}

func (p *P) Pattern(ctx context.Context, obj wire.Object) (Pattern, error) {
func (p *P) Patterns(ctx context.Context, obj wire.Object) (Patterns, error) {
var (
pt = make(Pattern)
pt = make(Patterns, 0, len(obj))
tr = trace.ContextPattern(ctx)
err error
)

for k, raw := range obj {
q, e := gojq.Parse(k)
tr.ParseKey(k, q, e)
var (
e error
q Pattern
)

ctx := trace.With(ctx, "pattern", k)

q.Key, e = gojq.Parse(k)
tr.ParseKey(ctx, q.Key, e)
if e != nil {
err = errors.Join(
err,
Expand All @@ -64,7 +76,7 @@ func (p *P) Pattern(ctx context.Context, obj wire.Object) (Pattern, error) {
var want any

e = yaml.Unmarshal(raw, &want)
tr.UnmarshalValue(k, raw, want, e)
tr.UnmarshalValue(ctx, raw, want, e)
if e != nil {
err = errors.Join(
err,
Expand All @@ -80,20 +92,24 @@ func (p *P) Pattern(ctx context.Context, obj wire.Object) (Pattern, error) {

switch want := want.(type) {
case bool:
pt[q] = func(_ context.Context, got any) (bool, error) { return want || got == nil, nil }
q.Match = func(_ context.Context, got any) (bool, error) { return want || got == nil, nil }
case string:
tv := tr.TemplateValue
tr.TemplateValue = func(_, v string, t *template.Template, e error) { tv(k, v, t, e) }
pt[q] = p.t.Match(trace.WithPattern(ctx, tr), want)
q.Match = p.t.Match(ctx, want)
default:
pt[q] = func(_ context.Context, got any) (bool, error) {
q.Match = func(_ context.Context, got any) (bool, error) {
ok := cmpEqual(want, got)
tr.EqualMatch(k, want, got, ok)
tr.EqualMatch(ctx, want, got, ok)
return ok, nil
}
}

pt = append(pt, &q)
}

sort.Slice(pt, func(i, j int) bool {
return pt[i].Key.String() < pt[j].Key.String()
})

return pt, err
}

Expand Down
Loading

0 comments on commit aad643e

Please sign in to comment.