v0.1.0 28f9d6e

recorded_test.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package workflow
4
5import (
6	"reflect"
7	"testing"
8)
9
10// TestRecordedInvalidWorkflows parses the workflow files of the recorded
11// scenarios expr-status-in-env, expr-status-in-run, invalid-needs and
12// invalid-schema, which GitHub refused (a failed run without jobs; the
13// recordings do not hold the messages), and checks that this package refuses
14// them, with the reference parser's messages, except for the status functions
15// whose message was observed on the github.com run page. The valid sibling of
16// each scenario is in TestRecordedValid.
17func TestRecordedInvalidWorkflows(t *testing.T) {
18	const on = "on:\n  push:\n    branches: ['scn/**']\n"
19	tests := []struct {
20		name, src string
21		want      []string
22	}{
23		{"expr-status-in-env", on + `jobs:
24  main:
25    runs-on: ubuntu-24.04
26    steps:
27      - name: status functions in env
28        env:
29          ST_SUCCESS: ${{ success() }}
30          ST_FAILURE: ${{ failure() }}
31          ST_ALWAYS: ${{ always() }}
32          ST_CANCELLED: ${{ cancelled() }}
33        run: env | grep '^ST_' | sort
34`, []string{
35			"w.yml (Line: 10, Col: 23): Unrecognized function: 'success'. Located at position 1 within expression: success()",
36			"w.yml (Line: 11, Col: 23): Unrecognized function: 'failure'. Located at position 1 within expression: failure()",
37			"w.yml (Line: 12, Col: 22): Unrecognized function: 'always'. Located at position 1 within expression: always()",
38			"w.yml (Line: 13, Col: 25): Unrecognized function: 'cancelled'. Located at position 1 within expression: cancelled()",
39		}},
40		{"expr-status-in-run", on + `jobs:
41  main:
42    runs-on: ubuntu-24.04
43    steps:
44      - name: status functions in run
45        run: |
46          echo "success=${{ success() }}"
47          echo "failure=${{ failure() }}"
48`, []string{
49			"w.yml (Line: 9, Col: 14): Unrecognized function: 'success'. Located at position 1 within expression: success()",
50			"w.yml (Line: 9, Col: 14): Unrecognized function: 'failure'. Located at position 1 within expression: failure()",
51		}},
52		{"invalid-needs unknown-need", on + `jobs:
53  main:
54    needs: nowhere
55    runs-on: ubuntu-24.04
56    steps:
57      - run: echo SHOULD-NOT-RUN
58`, []string{"w.yml (Line: 5, Col: 3): The workflow must contain at least one job with no dependencies."}},
59		{"invalid-needs unknown-need-with-root", on + `jobs:
60  root:
61    runs-on: ubuntu-24.04
62    steps:
63      - run: echo SHOULD-NOT-RUN
64  dependent:
65    needs: [root, nowhere]
66    runs-on: ubuntu-24.04
67    steps:
68      - run: echo SHOULD-NOT-RUN
69`, []string{"w.yml (Line: 10, Col: 19): Job 'dependent' depends on unknown job 'nowhere'."}},
70		{"invalid-needs cycle", on + `jobs:
71  a:
72    needs: b
73    runs-on: ubuntu-24.04
74    steps:
75      - run: echo SHOULD-NOT-RUN
76  b:
77    needs: a
78    runs-on: ubuntu-24.04
79    steps:
80      - run: echo SHOULD-NOT-RUN
81`, []string{"w.yml (Line: 5, Col: 3): The workflow must contain at least one job with no dependencies."}},
82		{"invalid-needs cycle-with-root", on + `jobs:
83  root:
84    runs-on: ubuntu-24.04
85    steps:
86      - run: echo SHOULD-NOT-RUN
87  a:
88    needs: [root, b]
89    runs-on: ubuntu-24.04
90    steps:
91      - run: echo SHOULD-NOT-RUN
92  b:
93    needs: a
94    runs-on: ubuntu-24.04
95    steps:
96      - run: echo SHOULD-NOT-RUN
97`, []string{
98			"w.yml (Line: 10, Col: 19): Job 'a' depends on job 'b' which creates a cycle in the dependency graph.",
99			"w.yml (Line: 15, Col: 12): Job 'b' depends on job 'a' which creates a cycle in the dependency graph.",
100		}},
101		{"invalid-schema unknown-key", on + `jobs:
102  main:
103    runs-on: ubuntu-24.04
104    unknown-key: true
105    steps:
106      - run: echo SHOULD-NOT-RUN
107`, []string{"w.yml (Line: 7, Col: 5): Unexpected value 'unknown-key'"}},
108		{"invalid-schema no-runs-on", on + `jobs:
109  main:
110    steps:
111      - run: echo SHOULD-NOT-RUN
112`, []string{"w.yml (Line: 6, Col: 5): Required property is missing: runs-on"}},
113		{"invalid-schema empty-step", on + `jobs:
114  main:
115    runs-on: ubuntu-24.04
116    steps:
117      - name: nothing to run
118`, []string{"w.yml (Line: 8, Col: 9): There's not enough info to determine what you meant. Add one of these properties: run, shell, uses, with, working-directory"}},
119	}
120	for _, tc := range tests {
121		t.Run(tc.name, func(t *testing.T) {
122			_, errs := Parse("w.yml", []byte(tc.src))
123			var got []string
124			for _, e := range errs {
125				got = append(got, e.Error())
126			}
127			if !reflect.DeepEqual(got, tc.want) {
128				t.Errorf("errors:\n  got  %q\n  want %q", got, tc.want)
129			}
130		})
131	}
132}
133
134// TestRecordedValid checks that the valid siblings of the invalid scenarios
135// and the status functions in conditions, which GitHub accepted, parse.
136func TestRecordedValid(t *testing.T) {
137	for name, src := range map[string]string{
138		"valid sibling":          "name: valid-sibling\non:\n  push:\n    branches: ['scn/**']\n    tags: ['scn-*']\njobs:\n  main:\n    runs-on: ubuntu-24.04\n    steps:\n      - run: echo valid-sibling-ran\n",
139		"status functions in if": "on: push\njobs:\n  main:\n    runs-on: x\n    if: success() || failure()\n    steps:\n      - if: always() && !cancelled()\n        run: echo\n",
140	} {
141		if _, errs := Parse("w.yml", []byte(src)); len(errs) > 0 {
142			t.Errorf("%s: %v", name, errs)
143		}
144	}
145}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 3

↑↓ moveenter openesc close