detached 98594d0

lint_test.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package action
4
5import (
6	"encoding/json"
7	"os"
8	"path/filepath"
9	"regexp"
10	"strings"
11	"testing"
12)
13
14// testdata/strict.json is what @actions/workflow-parser 0.3.61 answers on the
15// files of testdata/cases with parseAction and convertActionTemplate (see
16// gen/strict.mjs). TestLintAgainstReference replays it against Lint.
17
18type strictAnswer struct {
19	Errors []string `json:"errors"`
20	Name   string   `json:"name"`
21	Descr  string   `json:"description"`
22	Author string   `json:"author"`
23	Inputs []struct {
24		ID                 string `json:"id"`
25		Required           bool   `json:"required"`
26		DeprecationMessage string `json:"deprecationMessage"`
27		Default            string `json:"default"`
28	} `json:"inputs"`
29	Outputs []struct {
30		ID    string `json:"id"`
31		Value string `json:"value"`
32	} `json:"outputs"`
33	Runs struct {
34		Using  string `json:"using"`
35		Main   string `json:"main"`
36		Image  string `json:"image"`
37		PreIf  string `json:"preIf"`
38		PostIf string `json:"postIf"`
39		Steps  int    `json:"steps"`
40	} `json:"runs"`
41}
42
43var (
44	// The reference words expression parse errors like the JavaScript
45	// expression library: no position suffix. package expr follows the C#
46	// runner, which adds ". Located at position N within expression: ...".
47	exprSuffix = regexp.MustCompile(`\. Located at position \d+ within expression: .*$`)
48	// yaml.v3 and the JavaScript yaml library word syntax errors differently.
49	jsYAMLError = regexp.MustCompile(` at line \d+, column \d+:\n`)
50)
51
52// knownLintDifferences are the files on which the reference's answer is not
53// reproduced, with the reason.
54var knownLintDifferences = map[string]string{
55	"yaml_key_collection.yml": "a mapping key that is a collection: the reference reads it as undefined (\"Unexpected value 'undefined'\"), package template reports \"Unexpected value ''\" (twice); see template/PORTING.md",
56	"fold_inputs_sharp.yml":   "duplicate keys: the reference compares with JavaScript's toUpperCase (\u00df is SS), template with strings.ToUpper; Lint is not the runner's OrdinalIgnoreCase, Parse is",
57	"int32_key.yml":           jsKey,
58	"key_bool_dup.yml":        jsKey,
59	"key_float_dup.yml":       jsKey,
60	"key_hex_dup.yml":         jsKey,
61	"key_hex_input.yml":       jsKey,
62	"key_null_env.yml":        nullKey,
63	"key_null_inputs.yml":     nullKey,
64	"key_null_top.yml":        jsKey,
65	"key_number_runs.yml":     jsKey,
66}
67
68// nullKey: Lint misses a null key under a loose-key mapping; the reference and
69// the runner reject it. Fixing it in template would change how workflow files
70// are read, so it is left as it is.
71const nullKey = "Lint misses a null key under a loose-key mapping; the reference and the runner reject it"
72
73// jsKey: the reference resolves a plain key to a number, boolean or null (and
74// then crashes on it, "raw.indexOf is not a function", or words it
75// differently); template keeps keys of the workflow-style readers as strings.
76// The runner's reader resolves keys like values, which Parse reproduces.
77const jsKey = "the reference resolves non-string keys and fails on them with its own wording; Lint keeps keys as strings"
78
79func TestLintAgainstReference(t *testing.T) {
80	data, err := os.ReadFile("testdata/strict.json")
81	if err != nil {
82		t.Fatal(err)
83	}
84	var want map[string]strictAnswer
85	if err := json.Unmarshal(data, &want); err != nil {
86		t.Fatal(err)
87	}
88	files, _ := filepath.Glob("testdata/cases/*.yml")
89	for _, f := range files {
90		name := filepath.Base(f)
91		w, ok := want[name]
92		if !ok {
93			t.Errorf("%s: missing from strict.json", name)
94			continue
95		}
96		t.Run(strings.TrimSuffix(name, ".yml"), func(t *testing.T) {
97			src, err := os.ReadFile(f)
98			if err != nil {
99				t.Fatal(err)
100			}
101			errs := Lint(name, src)
102			if len(w.Errors) == 1 && jsYAMLError.MatchString(w.Errors[0]) {
103				if len(errs) != 1 || !strings.HasPrefix(errs[0].Error(), name+": ") {
104					t.Errorf("syntax error: got %q, reference %q", errTexts(errs), w.Errors)
105				}
106				return
107			}
108			var got []string
109			for _, e := range errs {
110				got = append(got, exprSuffix.ReplaceAllString(e.Error(), ""))
111			}
112			if note, known := knownLintDifferences[name]; known {
113				t.Skip(note)
114			}
115			if strings.Join(got, "\n") != strings.Join(w.Errors, "\n") {
116				t.Fatalf("errors differ\n got: %q\nwant: %q", got, w.Errors)
117			}
118		})
119	}
120}

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