internal/ci/sched_test.go

9d8fd17820457f0c60320c9f4a8b0da1db45a6b0
gitbay/internal/ci/sched_test.go history · blame · raw

104 lines · 3361 bytes

  1package ci
  2
  3import (
  4	"os"
  5	"os/exec"
  6	"path/filepath"
  7	"testing"
  8	"time"
  9
 10	"gitbay.org/gitbay/internal/store"
 11)
 12
 13// TestSchedulerRunDue drives one scheduler pass against a real store and
 14// bare repo: a due entry queues a build, sets a pending status, and
 15// advances next_run; entries whose job vanished are dropped.
 16func TestSchedulerRunDue(t *testing.T) {
 17	st, err := store.Open(":memory:")
 18	if err != nil {
 19		t.Fatal(err)
 20	}
 21	defer st.Close()
 22	if err := st.MigrateUp(); err != nil {
 23		t.Fatal(err)
 24	}
 25	uid, err := st.CreateUser("alice", false)
 26	if err != nil {
 27		t.Fatal(err)
 28	}
 29	_ = uid
 30	repoID, err := st.CreateRepo("user", uid, "app", "public")
 31	if err != nil {
 32		t.Fatal(err)
 33	}
 34
 35	// A bare repo whose main holds a ci.yml with one scheduled job.
 36	work := t.TempDir()
 37	env := append(os.Environ(),
 38		"GIT_CONFIG_NOSYSTEM=1", "GIT_CONFIG_GLOBAL=/dev/null",
 39		"GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.test",
 40		"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.test")
 41	git := func(dir string, args ...string) {
 42		t.Helper()
 43		cmd := exec.Command("git", args...)
 44		cmd.Dir = dir
 45		cmd.Env = env
 46		if out, err := cmd.CombinedOutput(); err != nil {
 47			t.Fatalf("git %v: %v\n%s", args, err, out)
 48		}
 49	}
 50	src := filepath.Join(work, "src")
 51	os.MkdirAll(filepath.Join(src, ".gitbay"), 0o755)
 52	os.WriteFile(filepath.Join(src, ".gitbay", "ci.yml"),
 53		[]byte("jobs:\n  nightly:\n    schedule: \"0 6 * * *\"\n    steps: [echo hi]\n"), 0o644)
 54	git(work, "init", "-q", "-b", "main", "src")
 55	git(src, "add", ".")
 56	git(src, "commit", "-q", "-m", "base")
 57	bare := filepath.Join(work, "bare.git")
 58	git(work, "clone", "-q", "--bare", src, "bare.git")
 59
 60	now := time.Now()
 61	past := now.Add(-time.Hour).UTC().Format("2006-01-02T15:04:05Z")
 62	if err := st.SyncSchedules(repoID, []store.Schedule{
 63		{RepoID: repoID, Job: "nightly", Cron: "0 6 * * *", NextRun: past},
 64		{RepoID: repoID, Job: "gone", Cron: "0 7 * * *", NextRun: past},
 65	}); err != nil {
 66		t.Fatal(err)
 67	}
 68
 69	s := &Scheduler{St: st, SiteURL: "https://x.test",
 70		RepoDir: func(owner, name string) string { return bare }}
 71	s.RunDue(now)
 72
 73	builds, err := st.ListBuilds(repoID, 10)
 74	if err != nil || len(builds) != 1 {
 75		t.Fatalf("builds after run: %v %v", builds, err)
 76	}
 77	if builds[0].Job != "nightly" || builds[0].Status != "pending" || builds[0].Ref != "main" {
 78		t.Fatalf("queued build wrong: %+v", builds[0])
 79	}
 80	statuses, _ := st.ListCommitStatuses(repoID, builds[0].SHA)
 81	if len(statuses) != 1 || statuses[0].Context != "ci/nightly" || statuses[0].State != "pending" {
 82		t.Fatalf("status wrong: %+v", statuses)
 83	}
 84	// next_run advanced past now; the vanished job's entry is gone.
 85	due, _ := st.DueSchedules(now.UTC().Format("2006-01-02T15:04:05Z"))
 86	if len(due) != 0 {
 87		t.Fatalf("still due after run: %+v", due)
 88	}
 89	all, _ := st.DueSchedules("9999-01-01T00:00:00Z")
 90	if len(all) != 1 || all[0].Job != "nightly" {
 91		t.Fatalf("schedule set after run: %+v", all)
 92	}
 93	// Cron fires in server-local time, stored as UTC.
 94	cr, _ := ParseCron("0 6 * * *")
 95	if want := cr.Next(now).UTC().Format("2006-01-02T15:04:05Z"); all[0].NextRun != want {
 96		t.Fatalf("next_run = %s, want %s", all[0].NextRun, want)
 97	}
 98
 99	// A second pass fires nothing: next_run is in the future.
100	s.RunDue(now)
101	if builds, _ = st.ListBuilds(repoID, 10); len(builds) != 1 {
102		t.Fatalf("second pass queued extra builds: %+v", builds)
103	}
104}