blob: 18215911e31fe278d3591acfbb759e37baceaf2c [file] [log] [blame] [edit]
// Copyright 2023 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package main
import (
"fmt"
"sort"
"strings"
"golang.org/x/net/context"
)
const (
EmptyLabel BugLabelType = ""
SubsystemLabel BugLabelType = "subsystems"
PriorityLabel BugLabelType = "prio"
NoRemindersLabel BugLabelType = "no-reminders"
OriginLabel BugLabelType = "origin"
MissingBackportLabel BugLabelType = "missing-backport"
)
type BugPrio string
const (
LowPrioBug BugPrio = "low"
NormalPrioBug BugPrio = "normal"
HighPrioBug BugPrio = "high"
)
type oneOf []string
type subsetOf []string
type trueFalse struct{}
func makeLabelSet(c context.Context, ns string) *labelSet {
ret := map[BugLabelType]interface{}{
PriorityLabel: oneOf([]string{
string(LowPrioBug),
string(NormalPrioBug),
string(HighPrioBug),
}),
NoRemindersLabel: trueFalse{},
MissingBackportLabel: trueFalse{},
}
service := getNsConfig(c, ns).Subsystems.Service
if service != nil {
names := []string{}
for _, item := range service.List() {
names = append(names, item.Name)
}
ret[SubsystemLabel] = subsetOf(names)
}
originLabels := []string{}
for _, repo := range getNsConfig(c, ns).Repos {
if repo.LabelIntroduced != "" {
originLabels = append(originLabels, repo.LabelIntroduced)
}
if repo.LabelReached != "" {
originLabels = append(originLabels, repo.LabelReached)
}
}
if len(originLabels) > 0 {
ret[OriginLabel] = subsetOf(originLabels)
}
return &labelSet{
c: c,
ns: ns,
labels: ret,
}
}
type labelSet struct {
c context.Context
ns string
labels map[BugLabelType]interface{}
}
func (s *labelSet) FindLabel(label BugLabelType) bool {
_, ok := s.labels[label]
return ok
}
func (s *labelSet) ValidateValues(label BugLabelType, values []BugLabel) string {
rules := s.labels[label]
if rules == nil {
return ""
}
switch v := rules.(type) {
case oneOf:
if len(values) != 1 {
return "You must specify only one of the allowed values"
}
if !stringInList([]string(v), values[0].Value) {
return fmt.Sprintf("%q is not among the allowed values", values[0].Value)
}
case subsetOf:
for _, item := range values {
if !stringInList([]string(v), item.Value) {
return fmt.Sprintf("%q is not among the allowed values", item.Value)
}
}
case trueFalse:
if len(values) != 1 || values[0].Value != "" {
return "This label does not support any values"
}
}
return ""
}
func (s *labelSet) Help() string {
var sortedKeys []BugLabelType
for key := range s.labels {
sortedKeys = append(sortedKeys, key)
}
sort.Slice(sortedKeys, func(i, j int) bool {
return string(sortedKeys[i]) < string(sortedKeys[j])
})
var help strings.Builder
for _, key := range sortedKeys {
if help.Len() > 0 {
help.WriteString(", ")
}
if key == SubsystemLabel {
help.WriteString(fmt.Sprintf("%s: {.. see below ..}", key))
continue
}
switch v := s.labels[key].(type) {
case oneOf:
help.WriteString(string(key))
help.WriteString(": {")
list := []string(v)
for i := range list {
if i > 0 {
help.WriteString(", ")
}
help.WriteString(list[i])
}
help.WriteByte('}')
case trueFalse:
help.WriteString(string(key))
}
}
var sb strings.Builder
writeWrapped(&sb, help.String())
if _, ok := s.labels[SubsystemLabel]; ok {
url := subsystemListURL(s.c, s.ns)
if url != "" {
sb.WriteString(fmt.Sprintf("\nThe list of subsystems: %s", url))
}
}
return sb.String()
}
func writeWrapped(sb *strings.Builder, str string) {
const limit = 80
lineLen := 0
for _, token := range strings.Fields(str) {
if lineLen >= limit ||
lineLen > 0 && lineLen+len(token) >= limit {
sb.WriteByte('\n')
lineLen = 0
}
if lineLen > 0 {
sb.WriteString(" ")
}
sb.WriteString(token)
lineLen += len(token)
}
}
func (bug *Bug) HasLabel(label BugLabelType, value string) bool {
for _, item := range bug.Labels {
if item.Label == label && item.Value == value {
return true
}
}
return false
}
func (bug *Bug) LabelValues(label BugLabelType) []BugLabel {
var ret []BugLabel
for _, item := range bug.Labels {
if item.Label == label {
ret = append(ret, item)
}
}
return ret
}
func (bug *Bug) SetLabels(set *labelSet, values []BugLabel) error {
var label BugLabelType
for _, v := range values {
if label != EmptyLabel && label != v.Label {
return fmt.Errorf("values of the same label type are expected")
}
label = v.Label
}
if errStr := set.ValidateValues(label, values); errStr != "" {
return fmt.Errorf("%s", errStr)
}
bug.UnsetLabels(label)
bug.Labels = append(bug.Labels, values...)
return nil
}
func (bug *Bug) UnsetLabels(labels ...BugLabelType) map[BugLabelType]struct{} {
toDelete := map[BugLabelType]struct{}{}
notFound := map[BugLabelType]struct{}{}
for _, name := range labels {
toDelete[name] = struct{}{}
notFound[name] = struct{}{}
}
var newList []BugLabel
for _, item := range bug.Labels {
if _, ok := toDelete[item.Label]; ok {
delete(notFound, item.Label)
continue
}
newList = append(newList, item)
}
bug.Labels = newList
return notFound
}
func (bug *Bug) HasUserLabel(label BugLabelType) bool {
for _, item := range bug.Labels {
if item.Label == label && item.SetBy != "" {
return true
}
}
return false
}
func (bug *Bug) prio() BugPrio {
for _, label := range bug.LabelValues(PriorityLabel) {
return BugPrio(label.Value)
}
return NormalPrioBug
}
var bugPrioOrder = map[BugPrio]int{
LowPrioBug: 1,
NormalPrioBug: 2,
HighPrioBug: 3,
}
func (bp BugPrio) LessThan(other BugPrio) bool {
return bugPrioOrder[bp] < bugPrioOrder[other]
}