Commit b9b563bb11

b9b563bb11a6639f53973b4130202aecff864f71

parent: aa224ebf3b

Verified · cmc ci/build: success ci/test: success

cmc <hello@cleberg.net> · 2026-09-08 06:13 UTC

store: queue stats cast the claim-wait average to whole seconds

SQLite's AVG is a float and the scan target is an int64, so `admin
runners` failed on the first production read where the average was
not whole. The e2e test only ever averaged one build.

Ref #184
internal/store/builds.go +2 −2
@@ -213,8 +213,8 @@ func (s *Store) QueueStats() (QueueStats, error) {
213213 err := s.DB.QueryRow(`SELECT
214214 (SELECT COUNT(*) FROM builds WHERE status = 'pending'),
215215 COUNT(*),
216 COALESCE(AVG(strftime('%s', started_at) - strftime('%s', created_at)), 0),
217 COALESCE(MAX(strftime('%s', started_at) - strftime('%s', created_at)), 0),
216 CAST(COALESCE(AVG(strftime('%s', started_at) - strftime('%s', created_at)), 0) AS INTEGER),
217 CAST(COALESCE(MAX(strftime('%s', started_at) - strftime('%s', created_at)), 0) AS INTEGER),
218218 (SELECT COUNT(*) FROM builds WHERE reaped_at >= ?)
219219 FROM builds WHERE started_at >= ?`, since, since).
220220 Scan(&q.Pending, &q.Claimed24h, &q.ClaimWaitAvgS, &q.ClaimWaitMaxS, &q.Reaped24h)
internal/store/builds_test.go +34
@@ -297,3 +297,37 @@ func TestReapStaleBuildsAfterLogClosed(t *testing.T) {
297297 t.Fatal(err)
298298 }
299299}
300
301// AVG is a float in SQLite; the stats scan it as whole seconds.
302func TestQueueStatsFractionalAverage(t *testing.T) {
303 s := open(t)
304 if err := s.MigrateUp(); err != nil {
305 t.Fatal(err)
306 }
307 uid, err := s.CreateUser("cmc", true)
308 if err != nil {
309 t.Fatal(err)
310 }
311 if _, err := s.CreateRepo("user", uid, "orgo", "public"); err != nil {
312 t.Fatal(err)
313 }
314 for range 2 {
315 if _, err := s.CreateBuild(1, "test", "abc123", "main", `["true"]`, "", "", true); err != nil {
316 t.Fatal(err)
317 }
318 if _, ok, err := s.ClaimBuild(nil); err != nil || !ok {
319 t.Fatalf("claim: %v ok=%v", err, ok)
320 }
321 }
322 // Waits of 1 s and 2 s: an average of 1.5.
323 if _, err := s.DB.Exec(`UPDATE builds SET created_at = strftime('%Y-%m-%dT%H:%M:%SZ', started_at, '-' || number || ' seconds')`); err != nil {
324 t.Fatal(err)
325 }
326 q, err := s.QueueStats()
327 if err != nil {
328 t.Fatal(err)
329 }
330 if q.Claimed24h != 2 || q.ClaimWaitAvgS != 1 || q.ClaimWaitMaxS != 2 || q.Pending != 0 || q.Reaped24h != 0 {
331 t.Fatalf("stats: %+v", q)
332 }
333}