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.
Phase 4: record the queue's new shape, and let the pins be tested while running
d798eaacf9b210fcdc2b51372d64a0e21e239bab · Keenan Nemetz · 16d ago · parent ac433531
Phase 4: record the queue's new shape, and let the pins be tested while running Docs, plus the two tests whose setup the new cancellation invalidates. docs/roadmap.md gains slices 7 and 8, rewrites the Phase 4 remaining list (the push-event table is gone by decision; what is left is the toolchain in the record, a narrowing/targeting filter for a mixed fleet, and a per-repository cap somebody would have to tune), adds an exit criterion for "a push whose spool file was never written still runs", and appends four decision-log rows: reconcile from the refs rather than persist events, park an idle lease on a wake-up channel, cancel queued superseded runs and order the lease by in-flight count, and keep runner presence in the queue filtered by the clone rule. README describes the new queue behaviour and /runners, and its stale "instance ed25519 key generated at first boot" line is replaced: the instance holds no signing key. Two tests built their scenario on a superseded run being started — queue v1, push v2, then run both. That is exactly what the queue no longer does, so both now hold the first build inside its step with a sentinel file, land the second push while it is running, and release it. The properties are unchanged and now tested where they actually apply: a build that is running when the branch moves must not publish a site (TestWorkflowRefusesAStaleSiteDeploy), and a run executes the steps it was queued with, not the workflow as the branch has it now (TestQueuedRunUsesTheWorkflowItWasQueuedFrom). A run that is still waiting when the new tip arrives is canceled, which TestANewerTipCancelsTheQueuedBuildOfTheOlderOne covers. scripts/ci.sh green before these comment-and-doc edits (internal/server 132.9s, everything else included); gofmt, vet, build and the markdown link check green after.
internal/server/integration_test.go +25−14
| @@ -2854,23 +2854,40 @@ jobs: | |||
| 2854 | 2854 | // TestQueuedRunUsesTheWorkflowItWasQueuedFrom: editing a workflow after a push | |
| 2855 | 2855 | // must not change what the already-queued run executes. The step list is pinned | |
| 2856 | 2856 | // when the run is created, from the blob that was pushed. | |
| 2857 | // | ||
| 2858 | // The first run is held inside its step, so the second push lands while it is running. | ||
| 2859 | // A run still waiting when a newer tip arrives is canceled rather than started — the | ||
| 2860 | // pinning question only exists for a run that got as far as executing. | ||
| 2857 | 2861 | func TestQueuedRunUsesTheWorkflowItWasQueuedFrom(t *testing.T) { | |
| 2858 | 2862 | f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 5 }) | |
| 2859 | 2863 | f.publish(f.repo) | |
| 2860 | 2864 | ||
| 2865 | sentinel := filepath.Join(t.TempDir(), "release") | ||
| 2861 | 2866 | work := workRepo(t, workflowFile(`version: 1 | |
| 2862 | 2867 | jobs: | |
| 2863 | 2868 | test: | |
| 2864 | 2869 | steps: | |
| 2865 | - run: echo first-version | ||
| 2870 | - run: while [ ! -f `+sentinel+` ]; do sleep 0.1; done; echo first-version | ||
| 2866 | 2871 | `)) | |
| 2867 | 2872 | if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil { | |
| 2868 | 2873 | t.Fatalf("push v1: %s", errs) | |
| 2869 | 2874 | } | |
| 2870 | 2875 | firstSHA := mustSHA(t, work) | |
| 2871 | f.waitForRun(t, 5*time.Second, func(r *runs.Run) bool { return r.SHA == firstSHA }) | ||
| 2876 | first := f.waitForRun(t, 5*time.Second, func(r *runs.Run) bool { return r.SHA == firstSHA }) | ||
| 2877 | |||
| 2878 | // Lease the v1 run and hold it in its step. | ||
| 2879 | held := exec.Command(binPath, "runner", "--remote", f.sshBase(), "--once", "--dir", t.TempDir()) | ||
| 2880 | held.Env = f.ciEnv() | ||
| 2881 | held.Stderr = io.Discard | ||
| 2882 | if err := held.Start(); err != nil { | ||
| 2883 | t.Fatal(err) | ||
| 2884 | } | ||
| 2885 | t.Cleanup(func() { _ = held.Process.Kill(); _ = held.Wait() }) | ||
| 2886 | f.waitForRun(t, 15*time.Second, func(r *runs.Run) bool { | ||
| 2887 | return r.ID == first.ID && r.Status == runs.StatusRunning | ||
| 2888 | }) | ||
| 2872 | 2889 | ||
| 2873 | // Second push rewrites the workflow. | ||
| 2890 | // Second push rewrites the workflow, while the first run is executing. | ||
| 2874 | 2891 | if err := os.WriteFile(filepath.Join(work, ".vole/workflows/ci.yaml"), | |
| 2875 | 2892 | []byte("version: 1\njobs:\n test:\n steps:\n - run: echo second-version\n"), 0o644); err != nil { | |
| 2876 | 2893 | t.Fatal(err) | |
| @@ -2887,20 +2904,14 @@ jobs: | |||
| 2887 | 2904 | secondSHA := mustSHA(t, work) | |
| 2888 | 2905 | f.waitForRun(t, 5*time.Second, func(r *runs.Run) bool { return r.SHA == secondSHA }) | |
| 2889 | 2906 | ||
| 2890 | // Run both jobs. | ||
| 2891 | for i := 0; i < 2; i++ { | ||
| 2892 | cmd := exec.Command(binPath, "runner", "--remote", f.sshBase(), "--once", "--dir", t.TempDir()) | ||
| 2893 | cmd.Env = f.ciEnv() | ||
| 2894 | cmd.Stderr = io.Discard | ||
| 2895 | if err := cmd.Start(); err != nil { | ||
| 2896 | t.Fatal(err) | ||
| 2897 | } | ||
| 2898 | _ = cmd.Wait() | ||
| 2907 | // Release the held build, then run the job queued from v2. | ||
| 2908 | if err := os.WriteFile(sentinel, []byte("go\n"), 0o644); err != nil { | ||
| 2909 | t.Fatal(err) | ||
| 2899 | 2910 | } | |
| 2900 | |||
| 2901 | first := f.waitForRun(t, 30*time.Second, func(r *runs.Run) bool { | ||
| 2911 | first = f.waitForRun(t, 30*time.Second, func(r *runs.Run) bool { | ||
| 2902 | 2912 | return r.SHA == firstSHA && r.Status != runs.StatusQueued && r.Status != runs.StatusRunning | |
| 2903 | 2913 | }) | |
| 2914 | f.runOnce(t) | ||
| 2904 | 2915 | second := f.waitForRun(t, 30*time.Second, func(r *runs.Run) bool { | |
| 2905 | 2916 | return r.SHA == secondSHA && r.Status != runs.StatusQueued && r.Status != runs.StatusRunning | |
| 2906 | 2917 | }) | |