Skip to content

Commit

Permalink
Merge pull request #1119 from k1LoW/defer
Browse files Browse the repository at this point in the history
  • Loading branch information
k1LoW authored Dec 19, 2024
2 parents e1b024f + e647346 commit b0c0192
Show file tree
Hide file tree
Showing 25 changed files with 506 additions and 89 deletions.
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ steps:

### `steps[*].loop:` `steps.<key>.loop:`

Loop settings for steps.
Loop setting for step.

#### Simple loop step

Expand Down Expand Up @@ -711,6 +711,28 @@ steps:
[...]
```

### `steps[*].defer:` `steps.<key>.defer:` [THIS IS EXPERIMENT]

Deferring setting for step.

```yaml
steps:
-
defer: true
req:
/cart:
delete:
body: null
[...]
```

The step marked `defer` behaves as follows.

- If `defer: true` is set, run of the step is deferred until finish of the runbook.
- Steps marked with `defer` are always run even if the running of intermediate steps fails.
- If there are multiple steps marked with `defer`, they are run in LIFO order.
- Also, the included steps are added to run sequence of the parent runbook's deferred steps.

## Variables to be stored

runn can use variables and functions when running step.
Expand Down
8 changes: 6 additions & 2 deletions bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ func (rnr *bindRunner) Run(ctx context.Context, s *step, first bool) error {
store := o.store.toMap()
store[storeRootKeyIncluded] = o.included
if first {
store[storeRootKeyPrevious] = o.store.latest()
if !s.deferred {
store[storeRootKeyPrevious] = o.store.latest()
}
} else {
store[storeRootKeyPrevious] = o.store.previous()
if !s.deferred {
store[storeRootKeyPrevious] = o.store.previous()
}
store[storeRootKeyCurrent] = o.store.latest()
}
keys := lo.Keys(cond)
Expand Down
2 changes: 1 addition & 1 deletion cdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func (rnr *cdpRunner) Renew() error {

func (rnr *cdpRunner) Run(ctx context.Context, s *step) error {
o := s.parent
cas, err := parseCDPActions(s.cdpActions, o.expandBeforeRecord)
cas, err := parseCDPActions(s.cdpActions, s, o.expandBeforeRecord)
if err != nil {
return fmt.Errorf("failed to parse: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion db.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func normalizeDSN(dsn string) string {

func (rnr *dbRunner) Run(ctx context.Context, s *step) error {
o := s.parent
e, err := o.expandBeforeRecord(s.dbQuery)
e, err := o.expandBeforeRecord(s.dbQuery, s)
if err != nil {
return err
}
Expand Down
12 changes: 9 additions & 3 deletions dbg.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ func (c *completer) do(d prompt.Document) ([]prompt.Suggest, pstrings.RuneNumber
// print
store := c.step.parent.store.toMap()
store[storeRootKeyIncluded] = c.step.parent.included
store[storeRootKeyPrevious] = c.step.parent.store.latest()
if !c.step.deferred {
store[storeRootKeyPrevious] = c.step.parent.store.latest()
}
keys := storeKeys(store)
for _, k := range keys {
if strings.HasPrefix(k, w) {
Expand Down Expand Up @@ -193,7 +195,9 @@ L:
}
store := s.parent.store.toMapForDbg()
store[storeRootKeyIncluded] = s.parent.included
store[storeRootKeyPrevious] = s.parent.store.latest()
if !s.deferred {
store[storeRootKeyPrevious] = s.parent.store.latest()
}
e, err := Eval(cmd[1], store)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%s\n", err.Error())
Expand Down Expand Up @@ -244,7 +248,9 @@ L:
case "variables", "v":
store := s.parent.store.toMapForDbg()
store[storeRootKeyIncluded] = s.parent.included
store[storeRootKeyPrevious] = s.parent.store.latest()
if !s.deferred {
store[storeRootKeyPrevious] = s.parent.store.latest()
}
keys := lo.Keys(store)
sort.Strings(keys)
for _, k := range keys {
Expand Down
3 changes: 3 additions & 0 deletions defer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package runn

const deferSectionKey = "defer"
95 changes: 95 additions & 0 deletions defer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package runn

import (
"context"
"testing"
)

func TestDeferRun(t *testing.T) {
tests := []struct {
book string
}{
{"testdata/book/defer.yml"},
{"testdata/book/defer_map.yml"},
}
for _, tt := range tests {
t.Run(tt.book, func(t *testing.T) {
ctx := context.Background()
o, err := New(Book(tt.book))
if err != nil {
t.Fatal(err)
}
if err := o.Run(ctx); err == nil {
t.Fatal("expected error")
}

if o.useMap {
if want := 8; len(o.store.stepMap) != want {
t.Errorf("o.store.stepMap got %v, want %v", len(o.store.stepMap), want)
}
} else {
if want := 8; len(o.store.stepList) != want {
t.Errorf("o.store.stepList got %v, want %v", len(o.store.stepList), want)
}
}
r := o.Result()
if want := 8; len(r.StepResults) != want {
t.Errorf("r.StepResults got %v, want %v", len(r.StepResults), want)
}

t.Run("main steps", func(t *testing.T) {
wantResults := []struct {
desc string
skipped bool
err bool
}{
{"step 1", false, false},
{"include step", false, false},
{"step 2", false, false},
{"step 3", false, true},
{"step 4", true, false},
{"defererd step c", false, false},
{"defererd step b", false, true},
{"defererd step a", false, false},
}
for i, want := range wantResults {
got := r.StepResults[i]
if got.Desc != want.desc {
t.Errorf("got %v, want %v", got.Desc, want.desc)
}
if got.Skipped != want.skipped {
t.Errorf("got %v, want %v", got.Skipped, want.skipped)
}
if (got.Err == nil) == want.err {
t.Errorf("got %v, want %v", got.Err, want.err)
}
}
})

t.Run("include steps", func(t *testing.T) {
wantResults := []struct {
desc string
skipped bool
err bool
}{
{"included step 1", false, false},
{"included step 2", false, false},
{"included defererd step d", false, false},
}

for i, want := range wantResults {
got := r.StepResults[1].IncludedRunResults[0].StepResults[i]
if got.Desc != want.desc {
t.Errorf("got %v, want %v", got.Desc, want.desc)
}
if got.Skipped != want.skipped {
t.Errorf("got %v, want %v", got.Skipped, want.skipped)
}
if (got.Err == nil) == want.err {
t.Errorf("got %v, want %v", got.Err, want.err)
}
}
})
})
}
}
133 changes: 133 additions & 0 deletions docs/designs/defer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Behavior of `defer:`

Authors: @k1low

Status: Add continuously

## Objective

This document describes the `defer:` behavior in runn.

## Backgroud

Allow post-processing of the runbook to be described using `defer:`

## Behavior of `defer:`

As the name suggests, `defer:` is inspired by the [defer](https://go.dev/blog/defer-panic-and-recover) of the Go programming language.

Behavior is also similar to the defer of the Go programming language, but some differences.

The order of run of steps not marked `defer:` are as follows.

```yaml
# main.yml
steps:
- desc: step 1
test: true
- desc: step 2
test: true
- desc: step 3
test: true
- desc: step 4
include:
path: include.yml
- desc: step 5
test: true
```
```yaml
# include.yml
steps:
- desc: included step 1
test: true
- desc: included step 2
test: true
- desc: included step 3
test: true
```
``` mermaid
flowchart TB
subgraph "(main.yml)"
A[step 1]
B[step 2]
C[step 3]
G[step 5]
end

subgraph "step 4 (include.yml)"
D[included step 1]
E[included step 2]
F[included step 3]
end

A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
```

The steps with `defer:` are run as follows.

```yaml
# main.yml
steps:
- desc: step 1
test: true
- desc: step 2
defer: true
test: true
- desc: step 3
defer: true
test: true
- desc: step 4
include:
path: include.yml
- desc: step 5
test: true
```
```yaml
# include.yml
steps:
- desc: included step 1
test: true
- desc: included step 2
defer: true
test: true
- desc: included step 3
test: true
```
``` mermaid
flowchart TB
subgraph "(main.yml)"
A[step 1]
B["step 2 (defer: true)"]
C["step 3 (defer: true)"]
G[step 5]
end

subgraph "step 4 (include.yml)"
D[included step 1]
E["included step 2 (defer: true)"]
F[included step 3]
end

A --> D
D --> F
F --> G
G --> E
E --> C
C --> B
```

The step marked `defer` behaves as follows.

- If `defer: true` is set, run of the step is deferred until finish of the runbook.
- Steps marked with `defer` are always run even if the running of intermediate steps fails.
- If there are multiple steps marked with `defer`, they are run in LIFO order.
- Also, the included steps are added to run sequence of the parent runbook's deferred steps.
8 changes: 6 additions & 2 deletions dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ func (rnr *dumpRunner) Run(ctx context.Context, s *step, first bool) error {
store := o.store.toMap()
store[storeRootKeyIncluded] = o.included
if first {
store[storeRootKeyPrevious] = o.store.latest()
if !s.deferred {
store[storeRootKeyPrevious] = o.store.latest()
}
} else {
store[storeRootKeyPrevious] = o.store.previous()
if !s.deferred {
store[storeRootKeyPrevious] = o.store.previous()
}
store[storeRootKeyCurrent] = o.store.latest()
}
if r.out == "" {
Expand Down
2 changes: 1 addition & 1 deletion exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (rnr *execRunner) Run(ctx context.Context, s *step) error {
}
globalScopes.mu.RUnlock()
o := s.parent
e, err := o.expandBeforeRecord(s.execCommand)
e, err := o.expandBeforeRecord(s.execCommand, s)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (rnr *grpcRunner) Close() error {

func (rnr *grpcRunner) Run(ctx context.Context, s *step) error {
o := s.parent
req, err := parseGrpcRequest(s.grpcRequest, o.expandBeforeRecord)
req, err := parseGrpcRequest(s.grpcRequest, s, o.expandBeforeRecord)
if err != nil {
return err
}
Expand Down Expand Up @@ -732,7 +732,7 @@ func setHeaders(ctx context.Context, h metadata.MD) context.Context {
func (rnr *grpcRunner) setMessage(req proto.Message, message map[string]any, s *step) error {
o := s.parent
// Lazy expand due to the possibility of computing variables between multiple messages.
e, err := o.expandBeforeRecord(message)
e, err := o.expandBeforeRecord(message, s)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion http.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ func isLocalhost(domain string) (bool, error) {

func (rnr *httpRunner) Run(ctx context.Context, s *step) error {
o := s.parent
e, err := o.expandBeforeRecord(s.httpRequest)
e, err := o.expandBeforeRecord(s.httpRequest, s)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit b0c0192

Please sign in to comment.