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 · 55m ago 4b8c9e1d
history blame raw 5.3 KiB
1package 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
7import (
8 "path/filepath"
9 "strings"
10 "testing"
11 "time"
12
13 "rodent.software/vole/ci/runs"
14 "rodent.software/vole/forge/config"
15)
16
17func tagWorkflow() map[string]string {
18 return workflowFile(`version: 1
19on:
20 push:
21 tags: ['v*']
22jobs:
23 release:
24 image: alpine:3.22
25 env:
26 RELEASE_KIND: tool
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
36func TestTagPushBuildsARelease(t *testing.T) {
37 t.Parallel()
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 }
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 }
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`.
79 code, page := f.get("/" + f.repo + "/releases/v1.0.0")
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 }
86 code, body := f.get("/" + f.repo + "/releases/v1.0.0/artifacts/tool-linux")
87 if code != 200 || body != "release binary\n" {
88 t.Fatalf("artifact download = %d %q", code, body)
89 }
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)
96 }
97 repoDir := filepath.Join(f.cfg.ReposDir(), f.repo+".git")
98 tip, _, _ := runGit(t, repoDir, "ls-tree", "-r", "--name-only", "refs/heads/vole")
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)
104 }
105 // And the run page shows the tag it built for.
106 if _, runPage := f.get("/" + f.repo + "/runs/" + done.ID); !strings.Contains(runPage, "tag v1.0.0") {
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.
113func TestTagPushRefusesALightweightTag(t *testing.T) {
114 t.Parallel()
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 })
133 _, log := f.get("/" + f.repo + "/runs/" + failed.ID + "/log")
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.
138 if _, page := f.get("/" + f.repo + "/releases/v2.0.0"); strings.Contains(page, "tool-linux") {
139 t.Fatalf("a lightweight tag got release artifacts:\n%s", page)
140 }
141}
142