Skip to content

Commit c24cfc7

Browse files
Expression evaluator fixes (#1009)
* refactor: remove debug error output Errors should always be logged with an error level and not debug level. Since the error is returned here, it will be logged later as an error. Presumably this was a leftover from debugging the executor chain in: PR: #971 * refactor: debug log wich expression is going to be evaluated * fix: handle nil in EvalBool We've seen this issue when the env map is not set-up properly, i.e. when the env map is nil, EvalBool might return nil, which should be handled as a falsy value. * fix: fail on error in if expression and return the evaluation error Stop running the workflow in case an expression cannot be evaluated. Fixes: #1008 * fix: remove quotes from inside expression syntax in test It looks like having an expression inside double quotes inside the expression syntax is not valid: https://github.com/ZauberNerd/act-test/actions/runs/1881986429 The workflow is not valid. .github/workflows/test.yml (Line: 10, Col: 13): Unexpected symbol: '"endsWith'. Located at position 1 within expression: "endsWith('Hello world', 'ld')" * refactor: export IsTruthy function from exprparser package * refactor: use IsTruthy function in EvalBool * refactor: move debug log for expression rewrite to rewrite function Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 7d43396 commit c24cfc7

File tree

7 files changed

+37
-64
lines changed

7 files changed

+37
-64
lines changed

pkg/common/executor.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ func (e Executor) Then(then Executor) Executor {
136136
case Warning:
137137
log.Warning(err.Error())
138138
default:
139-
log.Debugf("%+v", err)
140139
return err
141140
}
142141
}

pkg/exprparser/interpreter.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ func (impl *interperterImpl) evaluateNot(notNode *actionlint.NotOpNode) (interfa
274274
return nil, err
275275
}
276276

277-
return !impl.isTruthy(reflect.ValueOf(operand)), nil
277+
return !IsTruthy(operand), nil
278278
}
279279

280280
func (impl *interperterImpl) evaluateCompare(compareNode *actionlint.CompareOpNode) (interface{}, error) {
@@ -434,7 +434,8 @@ func (impl *interperterImpl) compareNumber(left float64, right float64, kind act
434434
}
435435
}
436436

437-
func (impl *interperterImpl) isTruthy(value reflect.Value) bool {
437+
func IsTruthy(input interface{}) bool {
438+
value := reflect.ValueOf(input)
438439
switch value.Kind() {
439440
case reflect.Bool:
440441
return value.Bool()
@@ -452,10 +453,7 @@ func (impl *interperterImpl) isTruthy(value reflect.Value) bool {
452453

453454
return value.Float() != 0
454455

455-
case reflect.Map:
456-
return true
457-
458-
case reflect.Slice:
456+
case reflect.Map, reflect.Slice:
459457
return true
460458

461459
default:
@@ -503,14 +501,14 @@ func (impl *interperterImpl) evaluateLogicalCompare(compareNode *actionlint.Logi
503501

504502
switch compareNode.Kind {
505503
case actionlint.LogicalOpNodeKindAnd:
506-
if impl.isTruthy(leftValue) {
504+
if IsTruthy(left) {
507505
return impl.getSafeValue(rightValue), nil
508506
}
509507

510508
return impl.getSafeValue(leftValue), nil
511509

512510
case actionlint.LogicalOpNodeKindOr:
513-
if impl.isTruthy(leftValue) {
511+
if IsTruthy(left) {
514512
return impl.getSafeValue(leftValue), nil
515513
}
516514

pkg/runner/expression.go

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package runner
22

33
import (
44
"fmt"
5-
"math"
65
"regexp"
76
"strings"
87

@@ -128,7 +127,9 @@ type expressionEvaluator struct {
128127
}
129128

130129
func (ee expressionEvaluator) evaluate(in string, isIfExpression bool) (interface{}, error) {
130+
log.Debugf("evaluating expression '%s'", in)
131131
evaluated, err := ee.interpreter.Evaluate(in, isIfExpression)
132+
log.Debugf("expression '%s' evaluated to '%t'", in, evaluated)
132133
return evaluated, err
133134
}
134135

@@ -141,9 +142,6 @@ func (ee expressionEvaluator) evaluateScalarYamlNode(node *yaml.Node) error {
141142
return nil
142143
}
143144
expr, _ := rewriteSubExpression(in, false)
144-
if in != expr {
145-
log.Debugf("expression '%s' rewritten to '%s'", in, expr)
146-
}
147145
res, err := ee.evaluate(expr, false)
148146
if err != nil {
149147
return err
@@ -214,18 +212,12 @@ func (ee expressionEvaluator) Interpolate(in string) string {
214212
}
215213

216214
expr, _ := rewriteSubExpression(in, true)
217-
if in != expr {
218-
log.Debugf("expression '%s' rewritten to '%s'", in, expr)
219-
}
220-
221215
evaluated, err := ee.evaluate(expr, false)
222216
if err != nil {
223217
log.Errorf("Unable to interpolate expression '%s': %s", expr, err)
224218
return ""
225219
}
226220

227-
log.Debugf("expression '%s' evaluated to '%s'", expr, evaluated)
228-
229221
value, ok := evaluated.(string)
230222
if !ok {
231223
panic(fmt.Sprintf("Expression %s did not evaluate to a string", expr))
@@ -237,37 +229,13 @@ func (ee expressionEvaluator) Interpolate(in string) string {
237229
// EvalBool evaluates an expression against given evaluator
238230
func EvalBool(evaluator ExpressionEvaluator, expr string) (bool, error) {
239231
nextExpr, _ := rewriteSubExpression(expr, false)
240-
if expr != nextExpr {
241-
log.Debugf("expression '%s' rewritten to '%s'", expr, nextExpr)
242-
}
243232

244233
evaluated, err := evaluator.evaluate(nextExpr, true)
245234
if err != nil {
246235
return false, err
247236
}
248237

249-
var result bool
250-
251-
switch t := evaluated.(type) {
252-
case bool:
253-
result = t
254-
case string:
255-
result = t != ""
256-
case int:
257-
result = t != 0
258-
case float64:
259-
if math.IsNaN(t) {
260-
result = false
261-
} else {
262-
result = t != 0
263-
}
264-
default:
265-
return false, fmt.Errorf("Unable to map return type to boolean for '%s'", expr)
266-
}
267-
268-
log.Debugf("expression '%s' evaluated to '%t'", nextExpr, result)
269-
270-
return result, nil
238+
return exprparser.IsTruthy(evaluated), nil
271239
}
272240

273241
func escapeFormatString(in string) string {
@@ -334,5 +302,9 @@ func rewriteSubExpression(in string, forceFormat bool) (string, error) {
334302
return in, nil
335303
}
336304

337-
return fmt.Sprintf("format('%s', %s)", strings.ReplaceAll(formatOut, "'", "''"), strings.Join(results, ", ")), nil
305+
out := fmt.Sprintf("format('%s', %s)", strings.ReplaceAll(formatOut, "'", "''"), strings.Join(results, ", "))
306+
if in != out {
307+
log.Debugf("expression '%s' rewritten to '%s'", in, out)
308+
}
309+
return out, nil
338310
}

pkg/runner/run_context.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,18 @@ func (rc *RunContext) steps() []*model.Step {
275275

276276
// Executor returns a pipeline executor for all the steps in the job
277277
func (rc *RunContext) Executor() common.Executor {
278-
return newJobExecutor(rc).If(rc.isEnabled)
278+
return func(ctx context.Context) error {
279+
isEnabled, err := rc.isEnabled(ctx)
280+
if err != nil {
281+
return err
282+
}
283+
284+
if isEnabled {
285+
return newJobExecutor(rc)(ctx)
286+
}
287+
288+
return nil
289+
}
279290
}
280291

281292
// Executor returns a pipeline executor for all the steps in the job
@@ -405,17 +416,16 @@ func (rc *RunContext) hostname() string {
405416
return *hostname
406417
}
407418

408-
func (rc *RunContext) isEnabled(ctx context.Context) bool {
419+
func (rc *RunContext) isEnabled(ctx context.Context) (bool, error) {
409420
job := rc.Run.Job()
410421
l := common.Logger(ctx)
411422
runJob, err := EvalBool(rc.ExprEval, job.If.Value)
412423
if err != nil {
413-
common.Logger(ctx).Errorf(" \u274C Error in if: expression - %s", job.Name)
414-
return false
424+
return false, fmt.Errorf(" \u274C Error in if-expression: \"if: %s\" (%s)", job.If.Value, err)
415425
}
416426
if !runJob {
417427
l.Debugf("Skipping job '%s' due to '%s'", job.Name, job.If.Value)
418-
return false
428+
return false, nil
419429
}
420430

421431
img := rc.platformImage()
@@ -428,9 +438,9 @@ func (rc *RunContext) isEnabled(ctx context.Context) bool {
428438
platformName := rc.ExprEval.Interpolate(runnerLabel)
429439
l.Infof("\U0001F6A7 Skipping unsupported platform -- Try running with `-P %+v=...`", platformName)
430440
}
431-
return false
441+
return false, nil
432442
}
433-
return true
443+
return true, nil
434444
}
435445

436446
func mergeMaps(maps ...map[string]string) map[string]string {

pkg/runner/step_context.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,7 @@ func (sc *StepContext) interpolateEnv(exprEval ExpressionEvaluator) {
171171
func (sc *StepContext) isEnabled(ctx context.Context) (bool, error) {
172172
runStep, err := EvalBool(sc.NewExpressionEvaluator(), sc.Step.If.Value)
173173
if err != nil {
174-
common.Logger(ctx).Errorf(" \u274C Error in if: expression - %s", sc.Step)
175-
exprEval, err := sc.setupEnv(ctx)
176-
if err != nil {
177-
return false, err
178-
}
179-
sc.RunContext.ExprEval = exprEval
180-
return false, err
174+
return false, fmt.Errorf(" \u274C Error in if-expression: \"if: %s\" (%s)", sc.Step.If.Value, err)
181175
}
182176

183177
return runStep, nil

pkg/runner/testdata/issue-597/spelling.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
name: issue-597
22
on: push
3-
3+
44

55
jobs:
66
my_first_job:
7-
7+
88
runs-on: ubuntu-latest
99
steps:
1010
- name: My first false step
@@ -14,7 +14,7 @@ jobs:
1414
ref: refs/pull/${{github.event.pull_request.number}}/merge
1515
fetch-depth: 5
1616
- name: My first true step
17-
if: ${{"endsWith('Hello world', 'ld')"}}
17+
if: ${{endsWith('Hello world', 'ld')}}
1818
uses: actions/hello-world-javascript-action@main
1919
with:
2020
who-to-greet: "Renst the Octocat"

pkg/runner/testdata/uses-composite/push.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
secret_input: ${{secrets.test_input_optional || 'NO SUCH SECRET'}}
1818
env:
1919
secret_input: ${{secrets.test_input_optional || 'NO SUCH SECRET'}}
20-
- if: steps.composite.outputs.test_output != "test_output_value"
20+
- if: steps.composite.outputs.test_output != 'test_output_value'
2121
run: |
2222
echo "steps.composite.outputs.test_output=${{ steps.composite.outputs.test_output }}"
2323
exit 1
@@ -38,7 +38,7 @@ jobs:
3838
test_input_optional_with_default_overriden: 'test_input_optional_with_default_overriden'
3939
test_input_required_with_default_overriden: 'test_input_required_with_default_overriden'
4040

41-
- if: steps.composite2.outputs.test_output != "test_output_value"
41+
- if: steps.composite2.outputs.test_output != 'test_output_value'
4242
run: |
4343
echo "steps.composite.outputs.test_output=${{ steps.composite2.outputs.test_output }}"
4444
exit 1

0 commit comments

Comments
 (0)