Repository: nektos/act (language: go)

Issue:
Support needs.*.result
### Act version

0.2.31

### Feature description

Act is missing support for the `result` property in the needs context object. This property tells you if a job succeeded or failed.

https://docs.github.com/en/actions/learn-github-actions/contexts#needs-context

I expect this workflow to work, but `needs.build.result` is not defined when running with act.
```
name: Build and deploy
on: push

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      build_id: ${{ steps.build_step.outputs.build_id }}
    steps:
      - uses: actions/checkout@v3
      - name: Build
        id: build_step
        run: |
          ./build
          echo "::set-output name=build_id::$BUILD_ID"
  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: ./deploy --build ${{ needs.build.outputs.build_id }}
  debug:
    needs: [build, deploy]
    runs-on: ubuntu-latest
    if: ${{ needs.build.result == 'failure' }}
    steps:
      - uses: actions/checkout@v3
      - run: ./debug
 ```


Discussion / hints:

Fix patch:
diff --git a/pkg/exprparser/interpreter.go b/pkg/exprparser/interpreter.go
index 7b76f3bb381..6b276fd14dd 100644
--- a/pkg/exprparser/interpreter.go
+++ b/pkg/exprparser/interpreter.go
@@ -20,10 +20,15 @@ type EvaluationEnvironment struct {
 	Secrets  map[string]string
 	Strategy map[string]interface{}
 	Matrix   map[string]interface{}
-	Needs    map[string]map[string]map[string]string
+	Needs    map[string]Needs
 	Inputs   map[string]interface{}
 }
 
+type Needs struct {
+	Outputs map[string]string `json:"outputs"`
+	Result  string            `json:"result"`
+}
+
 type Config struct {
 	Run        *model.Run
 	WorkingDir string
diff --git a/pkg/runner/expression.go b/pkg/runner/expression.go
index dc6b0e5b75c..a8d506ea67e 100644
--- a/pkg/runner/expression.go
+++ b/pkg/runner/expression.go
@@ -26,7 +26,7 @@ func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEval
 
 func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map[string]string) ExpressionEvaluator {
 	// todo: cleanup EvaluationEnvironment creation
-	using := make(map[string]map[string]map[string]string)
+	using := make(map[string]exprparser.Needs)
 	strategy := make(map[string]interface{})
 	if rc.Run != nil {
 		job := rc.Run.Job()
@@ -39,8 +39,9 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map
 		jobNeeds := rc.Run.Job().Needs()
 
 		for _, needs := range jobNeeds {
-			using[needs] = map[string]map[string]string{
-				"outputs": jobs[needs].Outputs,
+			using[needs] = exprparser.Needs{
+				Outputs: jobs[needs].Outputs,
+				Result:  jobs[needs].Result,
 			}
 		}
 	}
@@ -86,10 +87,11 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
 	jobs := rc.Run.Workflow.Jobs
 	jobNeeds := rc.Run.Job().Needs()
 
-	using := make(map[string]map[string]map[string]string)
+	using := make(map[string]exprparser.Needs)
 	for _, needs := range jobNeeds {
-		using[needs] = map[string]map[string]string{
-			"outputs": jobs[needs].Outputs,
+		using[needs] = exprparser.Needs{
+			Outputs: jobs[needs].Outputs,
+			Result:  jobs[needs].Result,
 		}
 	}
 


Test patch:
diff --git a/pkg/exprparser/interpreter_test.go b/pkg/exprparser/interpreter_test.go
index 2547aae5d14..d6f58a7c99f 100644
--- a/pkg/exprparser/interpreter_test.go
+++ b/pkg/exprparser/interpreter_test.go
@@ -555,6 +555,7 @@ func TestContexts(t *testing.T) {
 		{"strategy.fail-fast", true, "strategy-context"},
 		{"matrix.os", "Linux", "matrix-context"},
 		{"needs.job-id.outputs.output-name", "value", "needs-context"},
+		{"needs.job-id.result", "success", "needs-context"},
 		{"inputs.name", "value", "inputs-context"},
 	}
 
@@ -593,11 +594,12 @@ func TestContexts(t *testing.T) {
 		Matrix: map[string]interface{}{
 			"os": "Linux",
 		},
-		Needs: map[string]map[string]map[string]string{
+		Needs: map[string]Needs{
 			"job-id": {
-				"outputs": {
+				Outputs: map[string]string{
 					"output-name": "value",
 				},
+				Result: "success",
 			},
 		},
 		Inputs: map[string]interface{}{
diff --git a/pkg/runner/runner_test.go b/pkg/runner/runner_test.go
index 1f6141177da..5cf4837593b 100644
--- a/pkg/runner/runner_test.go
+++ b/pkg/runner/runner_test.go
@@ -196,6 +196,7 @@ func TestRunEvent(t *testing.T) {
 		{workdir, "workflow_dispatch_no_inputs_mapping", "workflow_dispatch", "", platforms, secrets},
 		{workdir, "workflow_dispatch-scalar", "workflow_dispatch", "", platforms, secrets},
 		{workdir, "workflow_dispatch-scalar-composite-action", "workflow_dispatch", "", platforms, secrets},
+		{workdir, "job-needs-context-contains-result", "push", "", platforms, secrets},
 		{"../model/testdata", "strategy", "push", "", platforms, secrets}, // TODO: move all testdata into pkg so we can validate it with planner and runner
 		// {"testdata", "issue-228", "push", "", platforms, }, // TODO [igni]: Remove this once everything passes
 		{"../model/testdata", "container-volumes", "push", "", platforms, secrets},
diff --git a/pkg/runner/testdata/job-needs-context-contains-result/push.yml b/pkg/runner/testdata/job-needs-context-contains-result/push.yml
new file mode 100644
index 00000000000..0ecbcea1ec4
--- /dev/null
+++ b/pkg/runner/testdata/job-needs-context-contains-result/push.yml
@@ -0,0 +1,15 @@
+on:
+  push:
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+    - run: exit 0
+  assert:
+    needs: test
+    if: |
+      ( always() && !cancelled() ) && (
+      ( needs.test.result != 'success' || !success() ) )
+    runs-on: ubuntu-latest
+    steps:
+    - run: exit 1