Skip to content

Commit

Permalink
feat: pass attempt number, max to cmd in env var
Browse files Browse the repository at this point in the history
  • Loading branch information
dbohdan committed Nov 20, 2024
1 parent 55338ef commit 3f134ca
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 11 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

/help
/recur
/test/env
/test/exit99
/test/hello
/test/sleep
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
TEST_BINARIES := test/exit99 test/hello test/sleep
TEST_BINARIES := test/env test/exit99 test/hello test/sleep

.PHONY: all
all: README.md recur
Expand All @@ -21,6 +21,9 @@ release:
test: recur $(TEST_BINARIES)
go test

test/env: test/env.go
go build -o $@ test/env.go

test/exit99: test/exit99.go
go build -o $@ test/exit99.go

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ recur exits with the last command's exit code unless the user overrides this in
When the command is not found during the last attempt,
recur exits with the code 255.

recur sets the environment variable `RECUR_ATTEMPT` for the command it runs to the current attempt number.
This way the command can access the attempt counter.
recur also sets `RECUR_MAX_ATTEMPTS` to the value of `-a`/`--attempts`.

## Conditions

recur supports a limited form of scripting.
Expand Down
4 changes: 4 additions & 0 deletions README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ recur exits with the last command's exit code unless the user overrides this in
When the command is not found during the last attempt,
recur exits with the code 255.

recur sets the environment variable `RECUR_ATTEMPT` for the command it runs to the current attempt number.
This way the command can access the attempt counter.
recur also sets `RECUR_MAX_ATTEMPTS` to the value of `-a`/`--attempts`.

## Conditions

recur supports a limited form of scripting.
Expand Down
14 changes: 12 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import (
)

const (
envVarAttempt = "RECUR_ATTEMPT"
envVarMaxAttempts = "RECUR_MAX_ATTEMPTS"
exitCodeCommandNotFound = 255
exitCodeError = -1
maxVerboseLevel = 3
Expand Down Expand Up @@ -213,7 +215,7 @@ func evaluateCondition(attemptInfo attempt, expr string) (bool, error) {
return bool(val.Truth()), nil
}

func executeCommand(command string, args []string, timeout time.Duration) commandResult {
func executeCommand(command string, args []string, timeout time.Duration, envVars []string) commandResult {
if _, err := exec.LookPath(command); err != nil {
return commandResult{
Status: statusNotFound,
Expand All @@ -233,6 +235,10 @@ func executeCommand(command string, args []string, timeout time.Duration) comman
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin

if len(envVars) > 0 {
cmd.Env = append(os.Environ(), envVars...)
}

err := cmd.Run()
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
Expand Down Expand Up @@ -312,7 +318,11 @@ func retry(config retryConfig) (int, error) {
startTime = attemptStart
}

cmdResult = executeCommand(config.Command, config.Args, config.Timeout)
envVars := []string{
fmt.Sprintf("%s=%d", envVarAttempt, attemptNum),
fmt.Sprintf("%s=%d", envVarMaxAttempts, config.MaxAttempts),
}
cmdResult = executeCommand(config.Command, config.Args, config.Timeout, envVars)

attemptEnd := time.Now()
attemptDuration := attemptEnd.Sub(attemptStart)
Expand Down
25 changes: 17 additions & 8 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
)

var (
commandEnv = "test/env"
commandExit99 = "test/exit99"
commandHello = "test/hello"
commandRecur = "./recur"
Expand Down Expand Up @@ -70,19 +71,27 @@ func TestEcho(t *testing.T) {
}
}

func TestEnv(t *testing.T) {
_, _, err := runCommand(commandEnv)

if _, ok := err.(*exec.ExitError); ok {
t.Errorf("Expected exit status 0, got %v", err)
}
}

func TestExitCode(t *testing.T) {
_, _, err := runCommand(commandExit99)

if exitErr, ok := err.(*exec.ExitError); !ok || exitErr.ExitCode() != 99 {
t.Errorf("Expected exit code 99, got %v", err)
t.Errorf("Expected exit status 99, got %v", err)
}
}

func TestCommandNotFound(t *testing.T) {
_, _, err := runCommand(noSuchCommand)

if exitErr, ok := err.(*exec.ExitError); !ok || exitErr.ExitCode() != 255 {
t.Errorf("Expected exit code 255, got %v", err)
t.Errorf("Expected exit status 255, got %v", err)
}
}

Expand All @@ -98,7 +107,7 @@ func TestAttemptsTrailingGarbageOptions(t *testing.T) {
_, _, err := runCommand("-a", "0abcdef", commandHello)

if exitErr, ok := err.(*exec.ExitError); !ok || exitErr.ExitCode() != 2 {
t.Errorf("Expected exit code 2, got %v", err)
t.Errorf("Expected exit status 2, got %v", err)
}
}

Expand Down Expand Up @@ -182,7 +191,7 @@ func TestConditionExitArgNone(t *testing.T) {
_, _, err := runCommand("-c", "exit(None)", commandHello)

if exitErr, ok := err.(*exec.ExitError); !ok || exitErr.ExitCode() != 255 {
t.Errorf("Expected exit code 255, got %v", err)
t.Errorf("Expected exit status 255, got %v", err)
}
}

Expand All @@ -194,7 +203,7 @@ func TestConditionExitArgTooLarge(t *testing.T) {
}

if exitErr, ok := err.(*exec.ExitError); !ok || exitErr.ExitCode() != 1 {
t.Errorf("Expected exit code 1, got %v", err)
t.Errorf("Expected exit status 1, got %v", err)
}
}

Expand All @@ -206,7 +215,7 @@ func TestConditionExitArgWrongType(t *testing.T) {
}

if exitErr, ok := err.(*exec.ExitError); !ok || exitErr.ExitCode() != 1 {
t.Errorf("Expected exit code 1, got %v", err)
t.Errorf("Expected exit status 1, got %v", err)
}
}

Expand All @@ -222,15 +231,15 @@ func TestConditionCommandNotFound(t *testing.T) {
_, _, err := runCommand("--condition", "command_found or exit(42)", noSuchCommand)

if exitErr, ok := err.(*exec.ExitError); !ok || exitErr.ExitCode() != 42 {
t.Errorf("Expected exit code 42, got %v", err)
t.Errorf("Expected exit status 42, got %v", err)
}
}

func TestConditionCommandNotFoundCode(t *testing.T) {
_, _, err := runCommand("--condition", "code == None and exit(42)", noSuchCommand)

if exitErr, ok := err.(*exec.ExitError); !ok || exitErr.ExitCode() != 42 {
t.Errorf("Expected exit code 42, got %v", err)
t.Errorf("Expected exit status 42, got %v", err)
}
}

Expand Down
1 change: 1 addition & 0 deletions test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ if (((Get-Variable 'IsWindows' -Scope 'Global' -ErrorAction 'Ignore') -and
}
$build = @{
"recur" = "main.go"
"test/env" = "test/env.go"
"test/exit99" = "test/exit99.go"
"test/hello" = "test/hello.go"
"test/sleep" = "test/sleep.go"
Expand Down
29 changes: 29 additions & 0 deletions test/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"fmt"
"os"
"strconv"
)

func main() {
envRecurAttempt := os.Getenv("RECUR_ATTEMPT")
attempt, err := strconv.Atoi(envRecurAttempt)
if err != nil {
fmt.Printf("%v\n", err)
os.Exit(101)
}

envRecurMaxAttempts := os.Getenv("RECUR_MAX_ATTEMPTS")
maxAttempts, err := strconv.Atoi(envRecurMaxAttempts)
if err != nil {
fmt.Printf("%v\n", err)
os.Exit(102)
}

if attempt == 3 && maxAttempts == 10 {
os.Exit(0)
} else {
os.Exit(103)
}
}

0 comments on commit 3f134ca

Please sign in to comment.