package catalogue import ( "encoding/json" "fmt" "strconv" "strings" "testing" ) // Defends novox/hq ADR 0255: a seat may receive a kind as data in its own shape, which the holder // renders with its template; the holder places every combination of the shape's placing fields once; // a contribution may be placed only where the machine reports a capability; and an offered kind makes // no dependency on its seat. const barTemplate = `{{if eq .shows "battery"}}[[block]] block = "battery" {{with .options}}{{with .device}}device = {{quote .}} {{end}}{{end}}{{else if eq .shows "command"}}[[block]] block = "custom" command = {{quote .options.command}} interval = {{.options.interval}} {{else if eq .shows "state"}}[[block]] block = "custom" command = {{quote (printf "show %s %s" .options.state (or .options.key .machine))}} {{end}}` func barHolder() Manifest { place := func(bar string) string { return "own-first\n${contribution:node-bar:block:bar=" + bar + ",place=resources}" + "own-middle\n${contribution:node-bar:block:bar=" + bar + ",place=status}own-last\n" } return Manifest{Module: "a-bar", Claims: []Claim{{Name: BarSeat, Renders: map[string]string{BarKindBlock: barTemplate}}}, Resources: []map[string]any{ {"id": "bottom", "type": "file", "path": "/b", "content": place("bottom")}, {"id": "top", "type": "file", "path": "/t", "content": place("top")}, }} } func block(data string) SeatContribution { var d map[string]any if err := json.Unmarshal([]byte(data), &d); err != nil { panic(err) } return SeatContribution{Seat: BarSeat, Kind: BarKindBlock, Data: d} } func composedFile(t *testing.T, r Resolution, id string) string { t.Helper() out, err := r.Declaration(Rendering{}) if err != nil { t.Fatal(err) } for _, res := range out { if res["id"] == id { return res["content"].(string) } } t.Fatalf("%s was not composed", id) return "" } func TestABlockIsRenderedByTheHolderInItsPlaceAndOrderNamedByModule(t *testing.T) { power := Manifest{Module: "power", Contributions: []SeatContribution{ block(`{"bar":"bottom","place":"status","shows":"battery"}`), block(`{"bar":"bottom","place":"status","order":20,"shows":"command","options":{"command":"draw \"now\"","interval":5}}`), }} other := Manifest{Module: "another", Contributions: []SeatContribution{ block(`{"bar":"bottom","place":"status","order":70,"shows":"battery","options":{"device":"BAT1"}}`), block(`{"bar":"top","place":"resources","shows":"command","options":{"command":"up","interval":60}}`), }} r := Resolution{Node: "laptop", Account: "op", Modules: []Manifest{barHolder(), power, other}} // Ordered by order (20, 50, 70), then module order; each piece under its module's name; the // command quoted so its own quotes cannot end the line. want := "own-first\nown-middle\n" + "# power\n[[block]]\nblock = \"custom\"\ncommand = \"draw \\\"now\\\"\"\ninterval = 5\n" + "# power\n[[block]]\nblock = \"battery\"\n" + "# another\n[[block]]\nblock = \"battery\"\ndevice = \"BAT1\"\n" + "own-last\n" if got := composedFile(t, r, "a-bar.bottom"); got != want { t.Fatalf("the bottom bar is\n%s\nnot\n%s", got, want) } want = "own-first\n# another\n[[block]]\nblock = \"custom\"\ncommand = \"up\"\ninterval = 60\nown-middle\nown-last\n" if got := composedFile(t, r, "a-bar.top"); got != want { t.Fatalf("the top bar is\n%s\nnot\n%s", got, want) } } func TestAContributionIfACapabilityIsPlacedOnlyWhereTheMachineReportsIt(t *testing.T) { c := block(`{"bar":"bottom","place":"status","shows":"battery"}`) c.IfCapability = "battery" power := Manifest{Module: "power", Contributions: []SeatContribution{c}} for _, c := range []struct { caps map[string]bool has bool }{{map[string]bool{"battery": true}, true}, {map[string]bool{"seat": true}, false}, {nil, false}} { r := Resolution{Node: "n", Account: "op", Capabilities: c.caps, Modules: []Manifest{barHolder(), power}} if bar := composedFile(t, r, "a-bar.bottom"); strings.Contains(bar, `block = "battery"`) != c.has { t.Errorf("with %v: the battery placed is not %v:\n%s", c.caps, c.has, bar) } } } func TestResolveCarriesTheMachinesCapabilities(t *testing.T) { node := workstation() node.Capabilities["battery"] = true r, err := Resolve(shelf(Manifest{Module: "a"}), []string{"a"}, node, World{}) if err != nil { t.Fatal(err) } if !r.Capabilities["battery"] || !r.Capabilities["seat"] { t.Fatalf("the resolution carries %v, not the node's %v", r.Capabilities, node.Capabilities) } } func TestAPieceTheHolderCannotRenderIsLeftOutAndNamedNotTheMachine(t *testing.T) { // A shows the shape gained after the holder's template was written: built here, past the check. power := Manifest{Module: "power", Contributions: []SeatContribution{ block(`{"bar":"bottom","place":"status","shows":"gpu"}`), block(`{"bar":"bottom","place":"status","shows":"battery"}`), }} r := Resolution{Node: "n", Account: "op", Modules: []Manifest{barHolder(), power}} c, err := r.Compose(Rendering{}) if err != nil { t.Fatalf("one piece failed the whole machine: %v", err) } if len(c.Unplaced) != 1 || !strings.Contains(c.Unplaced[0], "power's node-bar:block") || !strings.Contains(c.Unplaced[0], "shows=gpu") || !strings.Contains(c.Unplaced[0], "renders nothing") { t.Fatalf("unplaced: %v", c.Unplaced) } for _, res := range c.Resources { if res["id"] == "a-bar.bottom" && !strings.Contains(res["content"].(string), `block = "battery"`) { t.Fatalf("the piece beside it was lost too: %s", res["content"]) } } } func TestQuoteIsATomlBasicStringOfTheSameText(t *testing.T) { for _, in := range []string{`plain`, `say "hi" \ there`, "tab\tnew\nline", "del\x7fend", "nul\x00 esc\x1b", "<&> é ☃"} { q, err := quote(in) if err != nil { t.Fatal(err) } got, err := tomlBasicString(q) if err != nil || got != in { t.Errorf("quote(%q) = %s, which TOML reads as %q: %v", in, q, got, err) } } } // tomlBasicString reads one TOML basic string, as the TOML specification (1.0, "String") defines it: // any character but a quote, a backslash and the control characters U+0000–U+0008, U+000A–U+001F // and U+007F, which are written as escapes. func tomlBasicString(s string) (string, error) { if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { return "", fmt.Errorf("not quoted") } var b strings.Builder rs := []rune(s[1 : len(s)-1]) for i := 0; i < len(rs); i++ { r := rs[i] switch { case r == '"': return "", fmt.Errorf("a bare quote at %d", i) case r == 0x7f || (r < 0x20 && r != '\t'): return "", fmt.Errorf("a bare control character %U at %d", r, i) case r != '\\': b.WriteRune(r) continue } i++ if i >= len(rs) { return "", fmt.Errorf("an escape at the end") } switch rs[i] { case 'b': b.WriteRune('\b') case 't': b.WriteRune('\t') case 'n': b.WriteRune('\n') case 'f': b.WriteRune('\f') case 'r': b.WriteRune('\r') case '"': b.WriteRune('"') case '\\': b.WriteRune('\\') case 'u', 'U': n := 4 if rs[i] == 'U' { n = 8 } if i+n > len(rs)-1 { return "", fmt.Errorf("a short \\u escape") } v, err := strconv.ParseUint(string(rs[i+1:i+1+n]), 16, 32) if err != nil { return "", err } b.WriteRune(rune(v)) i += n default: return "", fmt.Errorf("the escape \\%c is not TOML's", rs[i]) } } return b.String(), nil } func TestAnOfferedBlockMakesNoDependencyOnTheBar(t *testing.T) { power := Manifest{Module: "power", Contributions: []SeatContribution{ block(`{"bar":"bottom","place":"status","shows":"battery"}`), {Seat: HotkeysSeat, Kind: "trigger", Content: "x"}, }} for _, seat := range DependsOn(nil, power) { if seat == BarSeat { t.Fatalf("a block made the power module depend on the bar: %v", DependsOn(nil, power)) } } // And on a machine with no bar it is placed nowhere, and nothing fails. r := Resolution{Node: "server", Account: "op", Capabilities: map[string]bool{"battery": true}, Modules: []Manifest{hotkeysHolder(), power}} if _, err := r.Declaration(Rendering{}); err != nil { t.Fatal(err) } } func TestTheBarReceivesBlocksAsData(t *testing.T) { s, _ := SeatNamed(BarSeat) if len(s.Receives) != 1 || s.Receives[0].Kind != BarKindBlock || s.Receives[0].Shape == nil || !s.Receives[0].Offered { t.Fatalf("node-bar receives %+v", s.Receives) } for _, ex := range s.Receives[0].Shape.Examples { if p := barBlockProblems(ex); len(p) > 0 { t.Errorf("the shape's own example %v is refused: %v", ex, p) } } } func TestAStateBlockIsRenderedForThisMachineAndTheHolderIsGrantedToReadIt(t *testing.T) { power := Manifest{Module: "power", State: []StateDeclaration{{Name: "draw"}}, Contributions: []SeatContribution{ block(`{"bar":"bottom","place":"status","shows":"state","options":{"state":"power.draw"}}`), }} r := Resolution{Node: "laptop", Account: "op", Modules: []Manifest{barHolder(), power}} if bar := composedFile(t, r, "a-bar.bottom"); !strings.Contains(bar, `command = "show power.draw laptop"`) { t.Fatalf("the block does not read this machine's key:\n%s", bar) } if got := ReadsGranted(barHolder(), []Manifest{barHolder(), power}, "laptop"); len(got) != 1 || got[0] != (StateRead{Read: "power.draw", Key: "laptop"}) { t.Fatalf("the holder is granted %v", got) } // Only the holder, and only for what the modules on its machine offer. if got := ReadsGranted(power, []Manifest{barHolder(), power}, "laptop"); len(got) != 0 { t.Fatalf("a contributor is granted %v", got) } if got := ReadsGranted(barHolder(), []Manifest{barHolder()}, "laptop"); len(got) != 0 { t.Fatalf("a holder alone is granted %v", got) } // Issue 774 (design 57 L3): the block as an item grants the same read, at this machine's own key alone, even where // the item names another machine's. for _, attrs := range []map[string]string{{"shows": "state", "state": "power.draw"}, {"shows": "state", "state": "power.draw", "key": "other"}} { moved := Manifest{Module: "power", State: []StateDeclaration{{Name: "draw"}}, Items: []Item{{ Collection: "contribution:node-bar:block", Name: "power-draw", IfCapability: "power-meter", Attributes: attrs}}} if got := ReadsGranted(barHolder(), []Manifest{barHolder(), moved}, "laptop"); len(got) != 1 || got[0] != (StateRead{Read: "power.draw", Key: "laptop"}) { t.Errorf("the holder is granted %v by the item %v", got, attrs) } } } func TestPerMachineStateIsDeclaredAndKeptInItsLongForm(t *testing.T) { m, err := decodeForTest([]byte(`{"module":"power","state":[{"name":"draw","ttl-seconds":30,"per-machine":true}]}`)) if err != nil || len(m.State) != 1 || !m.State[0].PerMachine { t.Fatalf("%+v %v", m.State, err) } raw, _ := json.Marshal(StateDeclaration{Name: "draw", PerMachine: true}) if string(raw) != `{"name":"draw","per-machine":true}` { t.Fatalf("%s", raw) } }