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.

Blame · server/tag_release_test.go

Line attribution on main · 141 line(s)

0f442b1c
Keenan Nemetz
17d ago
1 package server_test
2
3 // A tag push is a release build: the workflow opts in with on.push.tags, the run
4 // carries the tag, and the run's release-flagged artifacts are committed as the
5 // tag's release artifacts — the same pointers `vole release attach` writes.
6
7 import (
8 "path/filepath"
9 "strings"
10 "testing"
11 "time"
12
d37d301f
Keenan Nemetz
16h ago
13 "rodent.software/vole/ci/runs"
14 "rodent.software/vole/forge/config"
0f442b1c
Keenan Nemetz
17d ago
15 )
16
17 func tagWorkflow() map[string]string {
18 return workflowFile(`version: 1
19 on:
20 push:
21 tags: ['v*']
22 jobs:
23 release:
70b70264
Keenan Nemetz
15d ago
24 image: alpine:3.22
3b39a84c
Keenan Nemetz
14d ago
25 env:
26 RELEASE_KIND: tool
0f442b1c
Keenan Nemetz
17d ago
27 steps:
28 - name: build
29 run: mkdir -p dist && printf 'release binary\n' > dist/tool-linux
30 artifacts:
31 - path: dist/*
32 release: true
33 `)
34 }
35
36 func TestTagPushBuildsARelease(t *testing.T) {
44530e74
Keenan Nemetz
16d ago
37 t.Parallel()
0f442b1c
Keenan Nemetz
17d ago
38 f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 5 })
39 f.publish(f.repo)
40
41 work := workRepo(t, tagWorkflow())
42 if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil {
43 t.Fatalf("push: %s", errs)
44 }
45 // A workflow that says "tags:" runs on tags: a branch push must not queue it, or
46 // every commit would try to publish a release.
47 time.Sleep(1500 * time.Millisecond) // one spool tick
48 if list, _ := f.srv.Runs.Recent(f.repo, 10); len(list) != 0 {
49 t.Fatalf("a branch push queued %d run(s) for a tag-only workflow", len(list))
50 }
51
52 if _, errs, err := runGit(t, work, "tag", "-a", "v1.0.0", "-m", "first release\n"); err != nil {
53 t.Fatalf("tag: %s", errs)
54 }
55 if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "refs/tags/v1.0.0:refs/tags/v1.0.0"); err != nil {
56 t.Fatalf("push tag: %s", errs)
57 }
58
59 queued := f.waitForRun(t, 5*time.Second, func(r *runs.Run) bool { return r.Status == runs.StatusQueued })
60 if queued.Tag != "v1.0.0" || !queued.TagAnnotated || queued.Branch != "" {
61 t.Fatalf("queued run: tag=%q annotated=%v branch=%q", queued.Tag, queued.TagAnnotated, queued.Branch)
62 }
63 if queued.SHA == "" {
64 t.Fatal("the run has no commit: a tag object is not something a runner can check out")
65 }
66 if len(queued.ReleaseArtifacts) != 1 || queued.ReleaseArtifacts[0] != "dist/*" {
67 t.Fatalf("release artifacts = %v", queued.ReleaseArtifacts)
68 }
3b39a84c
Keenan Nemetz
14d ago
69 // The job's environment is pinned with everything else and travels with the lease.
70 if queued.Env["RELEASE_KIND"] != "tool" {
71 t.Fatalf("queued env = %v", queued.Env)
72 }
0f442b1c
Keenan Nemetz
17d ago
73
74 f.runOnce(t)
75 done := f.terminalRun(t)
76
77 // The release page is the proof: the runner committed the pointers, and the web
78 // reads them exactly as it reads a person's `vole release attach`.
ed03b3d1
Keenan Nemetz
3h ago
79 code, page := f.get("/" + f.repo + "/releases/v1.0.0")
0f442b1c
Keenan Nemetz
17d ago
80 if code != 200 {
81 t.Fatalf("release page = %d", code)
82 }
83 if !strings.Contains(page, "tool-linux") {
84 t.Fatalf("release page does not show the built artifact:\n%s", page)
85 }
ed03b3d1
Keenan Nemetz
3h ago
86 code, body := f.get("/" + f.repo + "/releases/v1.0.0/artifacts/tool-linux")
0f442b1c
Keenan Nemetz
17d ago
87 if code != 200 || body != "release binary\n" {
88 t.Fatalf("artifact download = %d %q", code, body)
89 }
b312e166
Keenan Nemetz
10d ago
90 // The record is in the database. The branch carries the release pointer, and it
91 // no longer carries a run record: a release can still name the build that made it,
92 // but the CI history does not travel with a clone.
93 rec, err := f.srv.Runs.Attempt(done.ID, done.Attempts)
94 if err != nil || rec == nil {
95 t.Fatalf("the run has no stored attempt: %v", err)
0f442b1c
Keenan Nemetz
17d ago
96 }
b312e166
Keenan Nemetz
10d ago
97 repoDir := filepath.Join(f.cfg.ReposDir(), f.repo+".git")
90fdaf39
Keenan Nemetz
16d ago
98 tip, _, _ := runGit(t, repoDir, "ls-tree", "-r", "--name-only", "refs/heads/vole")
b312e166
Keenan Nemetz
10d ago
99 if !strings.Contains(tip, "releases/v1.0.0/tool-linux") {
100 t.Fatalf("the release commit does not carry the pointer; it has:\n%s", tip)
101 }
102 if strings.Contains(tip, "runs/") {
103 t.Fatalf("the release commit still carries a run record; it has:\n%s", tip)
90fdaf39
Keenan Nemetz
16d ago
104 }
0f442b1c
Keenan Nemetz
17d ago
105 // And the run page shows the tag it built for.
ed03b3d1
Keenan Nemetz
3h ago
106 if _, runPage := f.get("/" + f.repo + "/runs/" + done.ID); !strings.Contains(runPage, "tag v1.0.0") {
0f442b1c
Keenan Nemetz
17d ago
107 t.Fatalf("run page does not show the tag:\n%s", runPage)
108 }
109 }
110
111 // TestTagPushRefusesALightweightTag: a lightweight tag has no notes and no tagger, so
112 // it cannot name a release. The build still runs — the failure is at the publish.
113 func TestTagPushRefusesALightweightTag(t *testing.T) {
44530e74
Keenan Nemetz
16d ago
114 t.Parallel()
0f442b1c
Keenan Nemetz
17d ago
115 f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 5 })
116 f.publish(f.repo)
117 work := workRepo(t, tagWorkflow())
118 if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil {
119 t.Fatalf("push: %s", errs)
120 }
121 if _, errs, err := runGit(t, work, "tag", "v2.0.0"); err != nil {
122 t.Fatalf("tag: %s", errs)
123 }
124 if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "refs/tags/v2.0.0:refs/tags/v2.0.0"); err != nil {
125 t.Fatalf("push tag: %s", errs)
126 }
127 queued := f.waitForRun(t, 5*time.Second, func(r *runs.Run) bool { return r.Status == runs.StatusQueued })
128 if queued.TagAnnotated {
129 t.Fatal("a lightweight tag was reported as annotated")
130 }
131 f.runOnce(t)
132 failed := f.waitForRun(t, 10*time.Second, func(r *runs.Run) bool { return r.Status == runs.StatusFailed })
ed03b3d1
Keenan Nemetz
3h ago
133 _, log := f.get("/" + f.repo + "/runs/" + failed.ID + "/log")
0f442b1c
Keenan Nemetz
17d ago
134 if !strings.Contains(log, "lightweight tag") {
135 t.Fatalf("the failure does not explain itself:\n%s", log)
136 }
137 // Nothing was published, so the release page has no artifacts.
ed03b3d1
Keenan Nemetz
3h ago
138 if _, page := f.get("/" + f.repo + "/releases/v2.0.0"); strings.Contains(page, "tool-linux") {
0f442b1c
Keenan Nemetz
17d ago
139 t.Fatalf("a lightweight tag got release artifacts:\n%s", page)
140 }
141 }