Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 25m ago 4b8c9e1d
history blame raw 5.7 KiB
1package ci
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8)
9
10func TestImageDefinitionRendersDockerfile(t *testing.T) {
11 t.Parallel()
12 content := `version: 1
13from: golang:1.27-bookworm@sha256:abc
14env:
15 CGO_ENABLED: "0"
16 MESSAGE: "hello world"
17run:
18 - apt-get update && apt-get install -y --no-install-recommends jq
19 - go install example.com/tool@v1.2.3
20`
21 def, problems := ParseImage(".vole/images/go.yaml", content)
22 if len(problems) > 0 {
23 t.Fatalf("valid image rejected: %v", problems)
24 }
25 if def.From != "golang:1.27-bookworm@sha256:abc" || len(def.Run) != 2 {
26 t.Fatalf("definition = %+v", def)
27 }
28 dockerfile := def.Dockerfile()
29 for _, want := range []string{
30 "FROM golang:1.27-bookworm@sha256:abc",
31 `ENV CGO_ENABLED="0"`,
32 `ENV MESSAGE="hello world"`,
33 `RUN ["/bin/sh", "-c", "apt-get update && apt-get install -y --no-install-recommends jq"]`,
34 `RUN ["/bin/sh", "-c", "go install example.com/tool@v1.2.3"]`,
35 } {
36 if !strings.Contains(dockerfile, want) {
37 t.Fatalf("Dockerfile does not contain %q:\n%s", want, dockerfile)
38 }
39 }
40 if ImageDigest(content) == "" || ImageDigest(content) != ImageDigest(content) {
41 t.Fatal("definition digest is not stable")
42 }
43}
44
45func TestImageDefinitionProblems(t *testing.T) {
46 t.Parallel()
47 cases := []struct {
48 name, body, want string
49 }{
50 {"no version", "from: alpine:3.22\n", "version is required"},
51 {"bad version", "version: 2\nfrom: alpine:3.22\n", "not supported"},
52 {"no from", "version: 1\n", "image reference is required"},
53 {"bad from", "version: 1\nfrom: \"./local\"\n", "not an image reference"},
54 {"reserved from", "version: 1\nfrom: file:go\n", "starts with"},
55 {"bad env", "version: 1\nfrom: alpine:3.22\nenv:\n bad-name: x\n", "environment variable name"},
56 {"multiline env", "version: 1\nfrom: alpine:3.22\nenv:\n A: \"x\\ny\"\n", "one line"},
57 {"empty run", "version: 1\nfrom: alpine:3.22\nrun:\n - \"\"\n", "command is empty"},
58 {"multiline run", "version: 1\nfrom: alpine:3.22\nrun:\n - \"echo one\\necho two\"\n", "one line"},
59 }
60 for _, tc := range cases {
61 t.Run(tc.name, func(t *testing.T) {
62 t.Parallel()
63 if _, problems := ParseImage(".vole/images/x.yaml", tc.body); !strings.Contains(strings.Join(problems, " "), tc.want) {
64 t.Fatalf("problems = %v, want %q", problems, tc.want)
65 }
66 })
67 }
68}
69
70func TestWorkflowRequiresExactlyOneImage(t *testing.T) {
71 t.Parallel()
72 base := "version: 1\njobs:\n build:\n %s\n steps:\n - run: true\n"
73 cases := []struct {
74 name, field, want string
75 }{
76 {"missing", "", "image or image-file is required"},
77 {"both", "image: alpine:3.22\n image-file: go", "mutually exclusive"},
78 {"bad ref", "image: \"has space\"", "not an image reference"},
79 {"reserved ref", "image: file:go", "starts with"},
80 {"bad file name", "image-file: Go", "not an image name"},
81 }
82 for _, tc := range cases {
83 t.Run(tc.name, func(t *testing.T) {
84 t.Parallel()
85 if _, problems := Parse("ci.yaml", strings.Replace(base, "%s", tc.field, 1)); !strings.Contains(strings.Join(problems, " "), tc.want) {
86 t.Fatalf("problems = %v, want %q", problems, tc.want)
87 }
88 })
89 }
90
91 wf, problems := Parse("ci.yaml", strings.Replace(base, "%s", "image: golang:1.27", 1))
92 if len(problems) > 0 {
93 t.Fatalf("direct image rejected: %v", problems)
94 }
95 if wf.Jobs[0].Image != "golang:1.27" || wf.Jobs[0].ImageFile != "" {
96 t.Fatalf("job = %+v", wf.Jobs[0])
97 }
98}
99
100func TestCheckResolvesImageFile(t *testing.T) {
101 t.Parallel()
102 content := "version: 1\nfrom: golang:1.27-bookworm\nrun:\n - go version\n"
103 files := map[string]string{
104 ".vole/workflows/ci.yaml": "version: 1\njobs:\n build:\n image-file: go\n steps:\n - run: go test ./...\n",
105 }
106 images := map[string]string{".vole/images/go.yaml": content}
107 wfs, problems := CheckWithImages(files, images)
108 if len(problems) > 0 {
109 t.Fatalf("valid image-file rejected: %v", problems)
110 }
111 job := wfs[0].Jobs[0]
112 if job.Image != "file:go" || job.ImageFile != "go" || job.ImageDefinition != content {
113 t.Fatalf("job = %+v", job)
114 }
115 if job.ImageSHA != ImageDigest(content) {
116 t.Fatalf("ImageSHA = %q, want %q", job.ImageSHA, ImageDigest(content))
117 }
118
119 // A reference with no definition is a push-time error, not a mystery build.
120 _, problems = CheckWithImages(files, map[string]string{})
121 if !strings.Contains(strings.Join(problems, " "), ImagePath("go")) {
122 t.Fatalf("missing definition problems = %v", problems)
123 }
124
125 // Unreferenced definitions are still validated.
126 _, problems = CheckWithImages(nil, map[string]string{".vole/images/bad.yaml": "version: 1\n"})
127 if !strings.Contains(strings.Join(problems, " "), "image reference is required") {
128 t.Fatalf("unreferenced bad definition problems = %v", problems)
129 }
130
131 // Two extensions for one name are ambiguous.
132 _, problems = CheckWithImages(files, map[string]string{
133 ".vole/images/go.yaml": content,
134 ".vole/images/go.yml": content,
135 })
136 if !strings.Contains(strings.Join(problems, " "), "already defined") {
137 t.Fatalf("duplicate definition problems = %v", problems)
138 }
139}
140
141func TestCheckDirReadsImages(t *testing.T) {
142 t.Parallel()
143 root := t.TempDir()
144 write := func(path, content string) {
145 t.Helper()
146 p := filepath.Join(root, filepath.FromSlash(path))
147 if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
148 t.Fatal(err)
149 }
150 if err := os.WriteFile(p, []byte(content), 0o644); err != nil {
151 t.Fatal(err)
152 }
153 }
154 write(".vole/workflows/ci.yaml", "version: 1\njobs:\n build:\n image-file: go\n steps:\n - run: true\n")
155 write(".vole/images/go.yaml", "version: 1\nfrom: golang:1.27\n")
156 wfs, problems, err := CheckDir(root)
157 if err != nil {
158 t.Fatal(err)
159 }
160 if len(problems) > 0 {
161 t.Fatalf("CheckDir: %v", problems)
162 }
163 if wfs[0].Jobs[0].ImageDefinition == "" || wfs[0].Jobs[0].ImageSHA == "" {
164 t.Fatalf("image definition was not read: %+v", wfs[0].Jobs[0])
165 }
166}
167