-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathbisect.go
157 lines (141 loc) · 4.03 KB
/
bisect.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Copyright 2025 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 main
import (
"errors"
"fmt"
"net/http"
"regexp"
"sort"
"strings"
"golang.org/x/oscar/internal/bisect"
"golang.org/x/oscar/internal/github"
)
// bisectLogPage is the data for the bisect log HTML template.
type bisectLogPage struct {
CommonPage
Tasks []*bisect.Task // all bisection tasks
}
var bisectLogPageTmpl = newTemplate(bisectLogTmplFile, nil)
func (g *Gaby) handleBisectLog(w http.ResponseWriter, r *http.Request) {
var p bisectLogPage
for _, t := range g.bisect.BisectionTasks() {
p.Tasks = append(p.Tasks, t)
}
// Sort the tasks by creation time, from newest to oldest.
sort.SliceStable(p.Tasks, func(i, j int) bool {
return p.Tasks[i].Created.After(p.Tasks[j].Created)
})
p.setCommonPage()
b, err := Exec(bisectLogPageTmpl, &p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, _ = w.Write(b)
}
func (p *bisectLogPage) setCommonPage() {
p.CommonPage = CommonPage{
ID: bisectlogID,
Description: "Browse bisection tasks performed by Oscar.",
Form: Form{
Inputs: nil,
SubmitText: "void",
},
}
}
// parseBisectTrigger extracts a bisection Request, if any,
// from a GitHub issue comment trigger. The expected request
// format for the comment body is as follows:
//
// - there can be empty lines
// - the first non-empty line is "@gabyhelp bisect [bad] [good]".
// Both bad and good are optional, but if one is present so must
// be the other.
// - the regression body comes after and it is enclosed with
// triple backticks (```).
//
// An error is returned if the trigger contains a bisect directive
// but the rest of the comment is not properly formatted.
func parseBisectTrigger(trigger *github.WebhookIssueCommentEvent) (*bisect.Request, error) {
body := trigger.Comment.Body
// Find the bisection directive line, if any.
lines := strings.Split(body, "\n")
i, bad, good, err := bisectDirectiveLine(lines)
if err != nil {
return nil, err
}
if i == -1 {
return nil, nil
}
// If bad and good commits are not provided,
// set them to default values.
if bad == "" {
bad = "master"
}
if good == "" {
good = "go1.22.0"
}
// Extract the regression code from the rest
// of the body.
rest := strings.Join(lines[i+1:], "\n")
regression := strings.TrimSpace(bisectRegression(rest))
if regression == "" {
return nil, errors.New("missing bisect regression")
}
return &bisect.Request{
Trigger: trigger.Comment.URL,
Issue: trigger.Issue.URL,
Repo: "https://go.googlesource.com/go",
Fail: bad,
Pass: good,
Body: regression,
}, nil
}
// bisectDirectiveLine checks if there is a line
// encoding a bisection directive. If so, it
// returns the position of the line and it extracts
// the bisect commits from the line. If the line
// is not properly formatted, returns an error.
func bisectDirectiveLine(lines []string) (int, string, string, error) {
for i, l := range lines {
l = strings.TrimSpace(l)
fs := strings.Fields(l)
if len(fs) < 2 {
continue
}
if fs[0] != "@gabyhelp" || fs[1] != "bisect" {
continue
}
// Bisect directive identified, check for
// commits and errors.
if len(fs) == 2 {
// Only bisect directive is present.
return i, "", "", nil
}
if len(fs) == 4 {
// Both the bisect directive and
// two commits are present.
return i, fs[2], fs[3], nil
}
return i, "", "", fmt.Errorf("bisect directive not properly formatted: %s", l)
}
return -1, "", "", nil
}
// regressionRegexp matches any text, including
// newlines, that is surrounded by triple backticks.
var regressionRegexp = regexp.MustCompile("(?s)```(.+)```")
// bisectRegression extracts a regression test
// case from body.
func bisectRegression(body string) string {
matches := regressionRegexp.FindAllStringSubmatch(body, -1)
if len(matches) != 1 {
return ""
}
rmatches := matches[0]
if len(rmatches) != 2 {
return ""
}
return rmatches[1]
}