The seat-definition reader reads the format fields milestone 47/G adds #178
@@ -4,6 +4,8 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||
)
|
||||
|
||||
// The delivery seat answers "what did the mesh's checks say of this pull request?" as a verb of its own,
|
||||
@@ -58,3 +60,18 @@ func TestTheDeliverySeatPromisesChecks(t *testing.T) {
|
||||
t.Fatalf("a holder without %s: %v", before[0], err)
|
||||
}
|
||||
}
|
||||
|
||||
// An event whose subject carries parts is said by its subject with `*` for each part, so a consumer naming one
|
||||
// instance or every instance of it is heard as the seat's (design 47 *What each step says*).
|
||||
func TestAnEventWithPartsIsTheSeatsByItsSubject(t *testing.T) {
|
||||
s := SeatFromDefinition(seatdef.Definition{Seat: "mesh-delivery", Scope: "mesh", Events: []seatdef.Message{
|
||||
{Name: "owed", Subject: "owed.<repository>.<number>"}, {Name: "pull.updated"}}})
|
||||
for _, c := range []struct {
|
||||
event string
|
||||
says bool
|
||||
}{{"owed.*.*", true}, {"owed.novox-mesh-sdk.12", true}, {"owed", false}, {"pull.updated", true}, {"pull.merged", false}} {
|
||||
if got := SeatSays(s.Emits, c.event); got != c.says {
|
||||
t.Errorf("%s: says %v, want %v (emits %v)", c.event, got, c.says, s.Emits)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// contribution kind given as data gets its checks (withShapeCode), which are code and never a definition's.
|
||||
func SeatFromDefinition(d seatdef.Definition) Seat {
|
||||
s := Seat{Name: d.Seat, Scope: d.Scope, Replicated: d.Replicated, Delivers: d.Delivers,
|
||||
Accepts: seatdef.MessageNames(d.Accepts), Emits: seatdef.MessageNames(d.Events), PulledByKind: d.PulledByKind,
|
||||
Accepts: seatdef.MessageNames(d.Accepts), Emits: localEvents(d.Events), PulledByKind: d.PulledByKind,
|
||||
Yields: d.Yields, HeldByTheEngine: d.HeldByTheEngine, Decision: d.Decision}
|
||||
if len(s.Accepts) == 0 {
|
||||
s.Accepts = nil
|
||||
@@ -62,6 +62,16 @@ func SeatsFromDefinitions(defs []seatdef.Definition) []Seat {
|
||||
return out
|
||||
}
|
||||
|
||||
// localEvents are the events as a manifest names them: an event whose subject carries parts by its subject with `*`
|
||||
// for each part (owed.*.*), so SeatSays matches a consumer's name against it.
|
||||
func localEvents(es []seatdef.Message) []string {
|
||||
out := make([]string, 0, len(es))
|
||||
for _, e := range es {
|
||||
out = append(out, e.Local())
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func examples(in []map[string]any) []map[string]any {
|
||||
if in == nil {
|
||||
return nil
|
||||
|
||||
+216
-1
@@ -20,6 +20,7 @@ import (
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MaxSize is the largest definition, or part, the mesh keeps (design 26: at most 256 KiB a definition).
|
||||
@@ -92,6 +93,105 @@ type Definition struct {
|
||||
Paths []Path `json:"paths,omitempty"`
|
||||
// Defs are named types a schema refers to as `{"$ref": "#/defs/<name>"}`.
|
||||
Defs map[string]map[string]any `json:"defs,omitempty"`
|
||||
// Acts are the seat's acting rules (novox/hq ADR 0656 §1; design 47 *What ADR 0656 leaves to this design*): each step
|
||||
// of a delivery the seat's holder acts in, on which modules, and how long an entry waits there for the holder's new
|
||||
// version. At either grain: a seat-list definition names its holder's step too.
|
||||
Acts []ActingRule `json:"acts,omitempty"`
|
||||
}
|
||||
|
||||
// ActingRule is one acting rule of a seat: the step its holder acts in, the modules it acts on (every module, or those
|
||||
// whose manifest says this language), and how long an entry waits in that step for the holder's new version, a
|
||||
// duration such as "30m"; none is the step's default (DefaultBound).
|
||||
type ActingRule struct {
|
||||
Step string `json:"step"`
|
||||
On string `json:"on,omitempty"`
|
||||
Bound string `json:"bound,omitempty"`
|
||||
}
|
||||
|
||||
// The steps of a delivery a seat's holder acts in (design 47): what the indexer, the checker, the build modules, the
|
||||
// node-engine, the grants part and the assigner each do for a change.
|
||||
const (
|
||||
StepTakeIn = "take-in"
|
||||
StepValidate = "validate"
|
||||
StepCheck = "check"
|
||||
StepBuild = "build"
|
||||
StepCompose = "compose"
|
||||
StepGrant = "grant"
|
||||
StepPlace = "place"
|
||||
)
|
||||
|
||||
// Steps are the steps an acting rule names, in a delivery's order.
|
||||
var Steps = []string{StepTakeIn, StepValidate, StepCheck, StepBuild, StepCompose, StepGrant, StepPlace}
|
||||
|
||||
// OnEvery is an acting rule's `on` for every module; an absent `on` reads as it.
|
||||
const OnEvery = "every"
|
||||
|
||||
// DefaultBound is how long an entry waits in step for its acting part's new version when the rule names no bound
|
||||
// (design 47 *An acting part's new version acts first*): 60 minutes to build, 30 to compose, 20 to check (the first
|
||||
// tier's), 10 to grant and to place; the indexer's take-in and validate have no wait, so zero.
|
||||
func DefaultBound(step string) time.Duration {
|
||||
switch step {
|
||||
case StepBuild:
|
||||
return 60 * time.Minute
|
||||
case StepCompose:
|
||||
return 30 * time.Minute
|
||||
case StepCheck:
|
||||
return 20 * time.Minute
|
||||
case StepGrant, StepPlace:
|
||||
return 10 * time.Minute
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Acting is the modules the rule acts on: OnEvery, or a language.
|
||||
func (r ActingRule) Acting() string {
|
||||
if r.On == "" {
|
||||
return OnEvery
|
||||
}
|
||||
return r.On
|
||||
}
|
||||
|
||||
// ActsOn says whether the rule acts on a module whose manifest says language.
|
||||
func (r ActingRule) ActsOn(language string) bool {
|
||||
on := r.Acting()
|
||||
return on == OnEvery || strings.EqualFold(on, strings.TrimSpace(language))
|
||||
}
|
||||
|
||||
// Wait is how long an entry waits in the rule's step for its acting part's new version: its bound, or the step's
|
||||
// default. A bound the reader refused never reaches here; one that does not parse reads as the default.
|
||||
func (r ActingRule) Wait() time.Duration {
|
||||
if d, err := time.ParseDuration(r.Bound); err == nil && d > 0 {
|
||||
return d
|
||||
}
|
||||
return DefaultBound(r.Step)
|
||||
}
|
||||
|
||||
func checkActs(rs []ActingRule) Problems {
|
||||
var ps Problems
|
||||
seen := map[string]bool{}
|
||||
for i, r := range rs {
|
||||
where := fmt.Sprintf("acts[%d]", i)
|
||||
switch {
|
||||
case r.Step == "":
|
||||
ps = append(ps, Problem{where + ".step", "missing"})
|
||||
case !slices.Contains(Steps, r.Step):
|
||||
ps = append(ps, Problem{where + ".step", fmt.Sprintf("%q is not a step (%s)", r.Step, strings.Join(Steps, ", "))})
|
||||
}
|
||||
if r.On != "" && r.On != OnEvery && !wordName.MatchString(r.On) {
|
||||
ps = append(ps, Problem{where + ".on", fmt.Sprintf("%q is neither every nor a language's lower-case name", r.On)})
|
||||
}
|
||||
if r.Bound != "" {
|
||||
if d, err := time.ParseDuration(r.Bound); err != nil || d <= 0 {
|
||||
ps = append(ps, Problem{where + ".bound", fmt.Sprintf("%q is not a duration such as 30m", r.Bound)})
|
||||
}
|
||||
}
|
||||
if k := r.Step + " " + r.Acting(); r.Step != "" && seen[k] {
|
||||
ps = append(ps, Problem{where, fmt.Sprintf("the step %s on %s twice", r.Step, r.Acting())})
|
||||
} else {
|
||||
seen[k] = true
|
||||
}
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
// Verb is one verb the seat serves: its input and, written whole, its answer, each a JSON Schema.
|
||||
@@ -110,10 +210,103 @@ type Verb struct {
|
||||
|
||||
// Message is one accepted message or one event of the seat, its body a JSON Schema.
|
||||
type Message struct {
|
||||
// Name is the message's name; an event's may be lower-case words joined by dots (pull.updated), as a module's
|
||||
// event keys are.
|
||||
Name string `json:"name"`
|
||||
Body map[string]any `json:"body,omitempty"`
|
||||
// ByCaller says the subject ends in the name of who sent it (or, for an event, of who it is for).
|
||||
ByCaller bool `json:"by-caller,omitempty"`
|
||||
// Subject is an event's subject after `mesh.seat.<seat>.event.` when it carries parts (design 47 *What each step
|
||||
// says*: `owed.<repository>.<number>`, `running.<node>.<module>`, `milestone.<milestone>.asked`): its tokens are
|
||||
// the name's words in order with parts between angle brackets among them, so a reader that knows only the name
|
||||
// still knows the event. Empty, the subject is the name. A part `<node>` is the node that publishes it, which
|
||||
// the grants part grants a node's account alone (ADR 0363 §2).
|
||||
Subject string `json:"subject,omitempty"`
|
||||
// Keep says how an event is kept when not as the family keeps the seat's others, in a stream of its own named
|
||||
// KeptStream: KeepLastPerSubject, the last message of each subject and no age limit (design 47: the node-engine's
|
||||
// running.<node>.<module>, so the last build of every module on every node stays on the bus however long ago it
|
||||
// was said, and a durable consumer starting from the last message per subject holds them all); or KeepStream,
|
||||
// every message within MaxAgeDays and MaxMiB.
|
||||
Keep string `json:"keep,omitempty"`
|
||||
// MaxAgeDays and MaxMiB bound an event kept KeepStream: how long a message is kept and how much the stream holds
|
||||
// (design 47 *What the gate owes the forge*: the stream `owed`, 7 days and at most 64 MiB). Given with KeepStream
|
||||
// alone.
|
||||
MaxAgeDays int `json:"max-age-days,omitempty"`
|
||||
MaxMiB int `json:"max-mib,omitempty"`
|
||||
}
|
||||
|
||||
// The ways an event is kept in a stream of its own, KeptStream, instead of its seat's family.
|
||||
const (
|
||||
// KeepLastPerSubject keeps the last message of each subject, with no age limit.
|
||||
KeepLastPerSubject = "last-per-subject"
|
||||
// KeepStream keeps every message for MaxAgeDays, at most MaxMiB in all.
|
||||
KeepStream = "stream"
|
||||
)
|
||||
|
||||
// KeptStream is the stream an event with Keep is kept in: `<seat>.<event>`, as a family is `<seat>.events`.
|
||||
func KeptStream(seat string, m Message) string { return seat + "." + m.Name }
|
||||
|
||||
// SubjectPattern is the event's subject after the seat's event prefix with its parts between angle brackets: the
|
||||
// name itself when the definition gives no subject.
|
||||
func (m Message) SubjectPattern() string {
|
||||
if m.Subject == "" {
|
||||
return m.Name
|
||||
}
|
||||
return m.Subject
|
||||
}
|
||||
|
||||
// Parts are the names of the subject's parts, in order; none for a subject that is the name.
|
||||
func (m Message) Parts() []string {
|
||||
var out []string
|
||||
for _, tok := range strings.Split(m.SubjectPattern(), ".") {
|
||||
if keyPartOK.MatchString(tok) {
|
||||
out = append(out, tok[1:len(tok)-1])
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Local is the subject after the seat's event prefix with `*` for each part: what a manifest's `emits` and
|
||||
// `consumes` name of the event, and the filter that hears every instance of it.
|
||||
func (m Message) Local() string {
|
||||
toks := strings.Split(m.SubjectPattern(), ".")
|
||||
for i, tok := range toks {
|
||||
if keyPartOK.MatchString(tok) {
|
||||
toks[i] = "*"
|
||||
}
|
||||
}
|
||||
return strings.Join(toks, ".")
|
||||
}
|
||||
|
||||
// checkSubject says what is wrong with an event's subject, or "".
|
||||
func checkSubject(m Message) string {
|
||||
if m.Subject == "" {
|
||||
return ""
|
||||
}
|
||||
if m.ByCaller {
|
||||
return "an event named by its caller ends in the caller's name; it carries no subject of its own"
|
||||
}
|
||||
var words, parts []string
|
||||
for _, tok := range strings.Split(m.Subject, ".") {
|
||||
switch {
|
||||
case keyPartOK.MatchString(tok):
|
||||
if slices.Contains(parts, tok) {
|
||||
return fmt.Sprintf("part %s twice", tok)
|
||||
}
|
||||
parts = append(parts, tok)
|
||||
case wordName.MatchString(tok):
|
||||
words = append(words, tok)
|
||||
default:
|
||||
return fmt.Sprintf("%q is neither a lower-case word nor a part between < and >", tok)
|
||||
}
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
return "no part: an event whose subject is its name gives no subject"
|
||||
}
|
||||
if strings.Join(words, ".") != m.Name {
|
||||
return fmt.Sprintf("its words are %q, not the event's name %q", strings.Join(words, "."), m.Name)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// HolderEvent is one of the holder's own events listed beside the seat.
|
||||
@@ -461,6 +654,7 @@ func (d Definition) check() Problems {
|
||||
}
|
||||
}
|
||||
ps = append(ps, checkStates(d.States)...)
|
||||
ps = append(ps, checkActs(d.Acts)...)
|
||||
for i, p := range d.Provisions {
|
||||
where := fmt.Sprintf("provisions[%d]", i)
|
||||
if p.Name == "" {
|
||||
@@ -582,12 +776,33 @@ func checkMessages(field string, ms []Message, whole bool) Problems {
|
||||
seen := map[string]bool{}
|
||||
for i, m := range ms {
|
||||
where := fmt.Sprintf("%s[%d]", field, i)
|
||||
if !wordName.MatchString(m.Name) {
|
||||
named := wordName
|
||||
if field == "events" {
|
||||
named = eventName
|
||||
}
|
||||
if !named.MatchString(m.Name) {
|
||||
ps = append(ps, Problem{where + ".name", "missing, or not a lower-case name"})
|
||||
} else if seen[m.Name] {
|
||||
ps = append(ps, Problem{where + ".name", fmt.Sprintf("%q twice", m.Name)})
|
||||
}
|
||||
seen[m.Name] = true
|
||||
switch {
|
||||
case m.Keep != "" && field != "events":
|
||||
ps = append(ps, Problem{where + ".keep", "only an event is kept"})
|
||||
case m.Keep != "" && m.Keep != KeepLastPerSubject && m.Keep != KeepStream:
|
||||
ps = append(ps, Problem{where + ".keep", fmt.Sprintf("%q is not %s or %s", m.Keep, KeepLastPerSubject, KeepStream)})
|
||||
case m.Keep == KeepLastPerSubject && len(m.Parts()) == 0 && !m.ByCaller:
|
||||
ps = append(ps, Problem{where + ".keep", "an event of one subject kept last per subject is one message: give it a subject with parts"})
|
||||
case m.Keep == KeepStream && (m.MaxAgeDays <= 0 || m.MaxMiB <= 0):
|
||||
ps = append(ps, Problem{where + ".keep", "an event kept in a stream of its own gives its max-age-days and max-mib"})
|
||||
case m.Keep != KeepStream && (m.MaxAgeDays != 0 || m.MaxMiB != 0):
|
||||
ps = append(ps, Problem{where + ".max-age-days", "only an event kept as a stream of its own gives max-age-days and max-mib"})
|
||||
}
|
||||
if m.Subject != "" && field != "events" {
|
||||
ps = append(ps, Problem{where + ".subject", "only an event carries a subject of its own"})
|
||||
} else if why := checkSubject(m); why != "" {
|
||||
ps = append(ps, Problem{where + ".subject", why})
|
||||
}
|
||||
if whole && m.Body == nil {
|
||||
ps = append(ps, Problem{where + ".body", "missing: a whole definition gives every message's body"})
|
||||
}
|
||||
|
||||
@@ -52,6 +52,31 @@ func TestADefinitionIsReadStrictlyNamingWhatIsWrong(t *testing.T) {
|
||||
`"subject":"mesh.mod.<holder>.event.verdict","family":"checker.asks"}],"family":`, 1), "a family names the holder's own stream"},
|
||||
{"a holder's event on a subject of no module", strings.Replace(whole, `"family":`, `"holder-events":[{"name":"verdict",`+
|
||||
`"subject":"mesh.event.verdict"}],"family":`, 1), "holder-events[0].subject: not mesh.mod.<holder>.event.verdict"},
|
||||
{"an event with dotted words", strings.Replace(whole, `"name":"created"`, `"name":"pull.updated"`, 1), ""},
|
||||
{"an event whose subject carries parts", strings.Replace(whole, `"name":"created"`,
|
||||
`"name":"milestone.asked","subject":"milestone.<milestone>.asked"`, 1), ""},
|
||||
{"a subject whose words are not the name", strings.Replace(whole, `"name":"created"`,
|
||||
`"name":"owed","subject":"owing.<repository>"`, 1), `its words are "owing", not the event's name "owed"`},
|
||||
{"a subject with no part", strings.Replace(whole, `"name":"created"`, `"name":"owed","subject":"owed"`, 1),
|
||||
"events[0].subject: no part"},
|
||||
{"a subject's part twice", strings.Replace(whole, `"name":"created"`,
|
||||
`"name":"running","subject":"running.<node>.<node>"`, 1), "part <node> twice"},
|
||||
{"a subject on an event named by its caller", strings.Replace(whole, `"name":"created"`,
|
||||
`"name":"owed","by-caller":true,"subject":"owed.<number>"`, 1), "carries no subject of its own"},
|
||||
{"an event kept last per subject", strings.Replace(whole, `"name":"created"`,
|
||||
`"name":"running","subject":"running.<node>.<module>","keep":"last-per-subject"`, 1), ""},
|
||||
{"an event kept otherwise", strings.Replace(whole, `"name":"created"`,
|
||||
`"name":"running","subject":"running.<node>","keep":"forever"`, 1), `events[0].keep: "forever" is not last-per-subject or stream`},
|
||||
{"an event kept in a stream of its own, bounded", strings.Replace(whole, `"name":"created"`,
|
||||
`"name":"owed","subject":"owed.<repository>.<number>","keep":"stream","max-age-days":7,"max-mib":64`, 1), ""},
|
||||
{"an event kept in a stream of its own, unbounded", strings.Replace(whole, `"name":"created"`,
|
||||
`"name":"owed","subject":"owed.<repository>.<number>","keep":"stream","max-age-days":7`, 1), "gives its max-age-days and max-mib"},
|
||||
{"a bound on an event kept by its family", strings.Replace(whole, `"name":"created"`,
|
||||
`"name":"owed","max-mib":64`, 1), "events[0].max-age-days: only an event kept as a stream"},
|
||||
{"an event of one subject kept last per subject", strings.Replace(whole, `"name":"created"`,
|
||||
`"name":"running","keep":"last-per-subject"`, 1), "give it a subject with parts"},
|
||||
{"an accept with dotted words", strings.Replace(whole, `"events":`,
|
||||
`"accepts":[{"name":"run.now","body":{}}],"events":`, 1), "accepts[0].name"},
|
||||
{"a verb twice", strings.Replace(listGrain, `]}`, `,{"name":"add"}]}`, 1), `"add" twice`},
|
||||
{"states at the seat list's grain", strings.Replace(listGrain, `"domain":"Core","decision":"ADR 1",`,
|
||||
`"domain":"Core","decision":"ADR 1","states":[{"name":"asks"}],`, 1), "grain: seat-list holds"},
|
||||
@@ -74,6 +99,21 @@ func TestADefinitionIsReadStrictlyNamingWhatIsWrong(t *testing.T) {
|
||||
"key-forms":[{"key":"<id>","value":{}}]}`), "states[0].history"},
|
||||
{"a state twice", withState(withState(whole, `{"name":"asks","template":"state","key-forms":[{"key":"<id>","value":{}}]}`),
|
||||
`{"name":"asks","template":"log","key-forms":[{"key":"<id>","value":{}}]}`), `"asks" twice`},
|
||||
// W1.1: a seat's acting rules (design 47 *What ADR 0656 leaves to this design*), at either grain.
|
||||
{"acting rules", strings.Replace(whole, `"family":`, `"acts":[{"step":"take-in"},{"step":"validate","on":"every"},`+
|
||||
`{"step":"build","on":"go","bound":"60m"}],"family":`, 1), ""},
|
||||
{"an acting rule at the seat list's grain", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"compose","bound":"30m"}],"verbs":`, 1), ""},
|
||||
{"an acting rule with no step", strings.Replace(listGrain, `"verbs":`, `"acts":[{"on":"go"}],"verbs":`, 1), "acts[0].step: missing"},
|
||||
{"an acting rule of an unknown step", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"deploy"}],"verbs":`, 1),
|
||||
`acts[0].step: "deploy" is not a step (take-in, validate, check, build, compose, grant, place)`},
|
||||
{"an acting rule on no language", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"build","on":"Go Lang"}],"verbs":`, 1),
|
||||
"acts[0].on"},
|
||||
{"an acting rule's bound that is no duration", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"build","bound":"an hour"}],"verbs":`, 1),
|
||||
`acts[0].bound: "an hour" is not a duration`},
|
||||
{"an acting rule's field the format does not know", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"build","when":"now"}],"verbs":`, 1),
|
||||
"when: a field the format does not know"},
|
||||
{"one step on one language twice", strings.Replace(listGrain, `"verbs":`, `"acts":[{"step":"grant"},{"step":"grant","on":"every"}],"verbs":`, 1),
|
||||
"acts[1]: the step grant on every twice"},
|
||||
{"over 256 KiB", listGrain[:len(listGrain)-1] + `,"description":"` + strings.Repeat("x", MaxSize) + `"}`,
|
||||
ErrTooLarge.Error()},
|
||||
}
|
||||
@@ -150,3 +190,52 @@ func withState(def, state string) string {
|
||||
}
|
||||
return strings.Replace(def, `"domain":"Core","decision":"ADR 1",`, `"domain":"Core","decision":"ADR 1","states":[`+state+`],`, 1)
|
||||
}
|
||||
|
||||
// TestAnEventsSubjectNamesItsParts holds the three readings of an event's subject: its pattern, its parts in order and
|
||||
// the local name a manifest's emits and consumes give it.
|
||||
func TestAnEventsSubjectNamesItsParts(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
m Message
|
||||
pattern, parts, local string
|
||||
}{
|
||||
{Message{Name: "created"}, "created", "", "created"},
|
||||
{Message{Name: "pull.updated"}, "pull.updated", "", "pull.updated"},
|
||||
{Message{Name: "running", Subject: "running.<node>.<module>"}, "running.<node>.<module>", "node,module", "running.*.*"},
|
||||
{Message{Name: "milestone.asked", Subject: "milestone.<milestone>.asked"}, "milestone.<milestone>.asked", "milestone", "milestone.*.asked"},
|
||||
} {
|
||||
if got := c.m.SubjectPattern(); got != c.pattern {
|
||||
t.Errorf("%s: pattern %q, want %q", c.m.Name, got, c.pattern)
|
||||
}
|
||||
if got := strings.Join(c.m.Parts(), ","); got != c.parts {
|
||||
t.Errorf("%s: parts %q, want %q", c.m.Name, got, c.parts)
|
||||
}
|
||||
if got := c.m.Local(); got != c.local {
|
||||
t.Errorf("%s: local %q, want %q", c.m.Name, got, c.local)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// W1.1: what an acting rule reads as where it says less (design 47 *An acting part's new version acts first*).
|
||||
func TestAnActingRuleReadsItsStepsDefaults(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
rule ActingRule
|
||||
language string
|
||||
acts bool
|
||||
wait string
|
||||
}{
|
||||
{ActingRule{Step: StepBuild, On: "go"}, "go", true, "1h0m0s"},
|
||||
{ActingRule{Step: StepBuild, On: "go"}, "typescript", false, "1h0m0s"},
|
||||
{ActingRule{Step: StepCompose}, "anything", true, "30m0s"},
|
||||
{ActingRule{Step: StepCheck, On: OnEvery}, "", true, "20m0s"},
|
||||
{ActingRule{Step: StepGrant, Bound: "5m"}, "go", true, "5m0s"},
|
||||
{ActingRule{Step: StepPlace}, "go", true, "10m0s"},
|
||||
{ActingRule{Step: StepTakeIn}, "go", true, "0s"},
|
||||
} {
|
||||
if got := c.rule.ActsOn(c.language); got != c.acts {
|
||||
t.Errorf("%+v acts on %q: %v, want %v", c.rule, c.language, got, c.acts)
|
||||
}
|
||||
if got := c.rule.Wait().String(); got != c.wait {
|
||||
t.Errorf("%+v waits %s, want %s", c.rule, got, c.wait)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user