blob: baeccb0a284ae5fdc910d0342dc0b61b71ae27cf [file] [log] [blame] [edit]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package bisect
import (
"encoding/json"
"fmt"
"iter"
"time"
"golang.org/x/oscar/internal/storage"
"golang.org/x/oscar/internal/storage/timed"
"rsc.io/ordered"
)
// Status is the status of the bisection task.
type Status int
const (
// Bisection task is ready to start.
StatusReady Status = iota
// Bisection task is enqueued.
StatusQueued
// Bisection task is in progress.
StatusStarted
// Bisection task failed.
StatusFailed
// Bisection task finished successfully.
StatusSucceeded
)
// Task contains metadata and progress information
// on bisection run that is saved to the database
// and also used as a Cloud Task queue entry.
type Task struct {
// ID is the unique identifier for the task.
ID string
// Trigger identifies what triggered the
// bisection task. For instance, it can be
// the URL of a GitHub comment requesting
// a bisection.
Trigger string
// Issue identifies the problem associated
// with the bisection. For instance, Issue
// can be the URL of a GitHub issue for which
// the bisection is ran.
Issue string
// Repository is the git repo on which the
// bisection is performed.
Repository string
// Bad is the commit hash or tag
// from which the bisection starts.
Bad string
// Good is the commit hash or tag
// at which the bisection ends.
Good string
// Regression is Go code reproducing a
// bug that needs to be bisected. Currently,
// it is expected to be a Go test.
Regression string
// Output is the output of bisection. It
// can contain progress and debug messages.
Output string
// Commit is the hash of the commit found
// by git bisect.
Commit string
// Error is a message describing bisection
// failure, if any.
Error string
// Status is the status of the bisection.
Status Status
// Updated is the last time the bisection
// task data was updated. Together with
// Status, Updated can be used to infer
// when the task finished.
Updated time.Time
// Created is the time the bisection task
// was created and queued for execution.
Created time.Time
}
// Name of a task is its issue combined with ID.
func (t *Task) Name() string {
return fmt.Sprintf("%s-%s", t.Issue, t.ID)
}
// Path is always "bisect". This is the gaby
// endpoint to which the task data will be sent.
func (t *Task) Path() string {
return "bisect"
}
// Params encodes task ID.
func (t *Task) Params() string {
return "id=" + t.ID
}
// A Request for bisection.
type Request struct {
// Trigger identifies what triggered the
// request. For instance, it can be the URL
// of a GitHub comment requesting a bisection.
Trigger string
// Issue identifies the problem associated
// with the request. For instance, Issue
// can be the URL of a GitHub issue for which
// the bisection is requested.
Issue string
// Fail is the commit hash or tag at which
// the bisection regression occurs.
Fail string
// Pass is the commit hash or tag at which
// the bisection regression does not occur.
Pass string
// Body is the regression as a Go program.
Body string
// Repo is the git repository to which the
// regression applies.
Repo string
}
// BisectionTasks returns an iterator over the bisection tasks.
// The first iterator value is the task ID and the other value
// is the task itself.
func (c *Client) BisectionTasks() iter.Seq2[string, *Task] {
return func(yield func(string, *Task) bool) {
for key, fn := range c.db.Scan(o(taskKind), o(taskKind, ordered.Inf)) {
var id string
if err := ordered.Decode(key, nil, &id); err != nil {
c.db.Panic("bisect client task decode", "key", storage.Fmt(key), "err", err)
}
var t Task
if err := json.Unmarshal(fn(), &t); err != nil {
c.db.Panic("bisect client task unmarshal", "key", storage.Fmt(key), "err", err)
}
if !yield(id, &t) {
return
}
}
}
}
// TaskWatcher returns a new [timed.Watcher] with the given name.
// It picks up where any previous Watcher of the same name left off.
func (c *Client) TaskWatcher(name string) *timed.Watcher[*TaskEvent] {
return timed.NewWatcher(c.slog, c.db, name, taskUpdateKind, c.decodeTaskEvent)
}
// decodeTaskEvent decodes a taskUpdateKind [timed.Entry] into
// a task event.
func (c *Client) decodeTaskEvent(t *timed.Entry) *TaskEvent {
te := TaskEvent{
DBTime: t.ModTime,
}
if err := ordered.Decode(t.Key, &te.ID); err != nil {
c.db.Panic("bisect task event decode", "key", storage.Fmt(t.Key), "err", err)
}
return &te
}
// A TaskEvent is a bisection [Task]
// event returned by bisection watchers.
type TaskEvent struct {
DBTime timed.DBTime // when event was created
ID string // ID of the bisection task
}