Point standard output at standard error while a one-head delivery check runs, so its answer is the only JSON (issue 1311)
mesh/merge-gate out of date: judged against trunk 40350d82fa20, the trunk is now 70cb217601f9; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 40350d82fa20, the trunk is now 70cb217601f9; judged again on a recheck or when it is merged
mesh/delivery delivered
mesh/merge-gate out of date: judged against trunk 40350d82fa20, the trunk is now 70cb217601f9; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 40350d82fa20, the trunk is now 70cb217601f9; judged again on a recheck or when it is merged
mesh/delivery delivered
This commit is contained in:
@@ -902,16 +902,14 @@ func deliveryCommand(ctx context.Context, args []string) error {
|
|||||||
if *group == "" && len(members) == 1 {
|
if *group == "" && len(members) == 1 {
|
||||||
// One head, checked again as the forge's announcement would have it (a recheck): the controller's
|
// One head, checked again as the forge's announcement would have it (a recheck): the controller's
|
||||||
// own path for a pull request, run because the delivery's owner asked.
|
// own path for a pull request, run because the delivery's owner asked.
|
||||||
// Its prose goes to standard error: standard output is the answer's JSON alone, or the seat verb
|
return recheckOneHead(members[0].ID, answer, func() (string, error) {
|
||||||
// reads no answer from it (novox/hq issue 1311).
|
var asked string
|
||||||
var asked string
|
err := sayingOnTheBus(ctx, inv, func() (err error) {
|
||||||
if err := sayingOnTheBus(ctx, inv, func() (err error) {
|
asked, err = (following{open: open, oneShot: true}).pullUpdated(ctx, members[0].pull(), os.Stderr)
|
||||||
asked, err = (following{open: open, oneShot: true}).pullUpdated(ctx, members[0].pull(), os.Stderr)
|
return err
|
||||||
return err
|
})
|
||||||
}); err != nil {
|
return asked, err
|
||||||
return err
|
})
|
||||||
}
|
|
||||||
return answer(recheckAnswer(asked, members[0].ID))
|
|
||||||
}
|
}
|
||||||
if *group == "" || len(members) < 2 {
|
if *group == "" || len(members) < 2 {
|
||||||
return errors.New("delivery check --group <id> --members <json> (two heads or more), or --members <one head>")
|
return errors.New("delivery check --group <id> --members <json> (two heads or more), or --members <one head>")
|
||||||
@@ -1388,3 +1386,19 @@ func overNATS(conn *nats.Conn) (link.Bus, error) {
|
|||||||
func recheckAnswer(asked, member string) map[string]any {
|
func recheckAnswer(asked, member string) map[string]any {
|
||||||
return map[string]any{"asked": asked, "rechecked": member}
|
return map[string]any{"asked": asked, "rechecked": member}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recheckOneHead runs a one-head `delivery check` and answers it as data. Standard output is the answer's JSON alone,
|
||||||
|
// or the seat verb reads no answer from it (novox/hq issue 1311): while the check runs, whatever it prints to standard
|
||||||
|
// output (the controller's own path prints its prose there, as do the stores it reads) goes to standard error.
|
||||||
|
func recheckOneHead(member string, answer func(any) error, ask func() (string, error)) error {
|
||||||
|
real := os.Stdout
|
||||||
|
os.Stdout = os.Stderr
|
||||||
|
asked, err := func() (string, error) {
|
||||||
|
defer func() { os.Stdout = real }()
|
||||||
|
return ask()
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return answer(recheckAnswer(asked, member))
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -44,3 +46,40 @@ func TestSayCheckedWritesTheSameLineToItsWriter(t *testing.T) {
|
|||||||
t.Fatalf("sayChecked said %q; want %q", w.String(), want)
|
t.Fatalf("sayChecked said %q; want %q", w.String(), want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whatever the check prints to standard output while it runs (checks.go namedForTheCheck, the inventory's pointer
|
||||||
|
// repair) must not reach the answer's standard output: it is exactly one JSON document (novox/hq issue 1311).
|
||||||
|
func TestOneHeadRecheckLeavesStandardOutputTheAnswerAlone(t *testing.T) {
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
e, ew, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
realOut, realErr := os.Stdout, os.Stderr
|
||||||
|
os.Stdout, os.Stderr = w, ew
|
||||||
|
runErr := recheckOneHead("novox/x@abc", func(v any) error { return json.NewEncoder(os.Stdout).Encode(v) },
|
||||||
|
func() (string, error) {
|
||||||
|
fmt.Println(" the earlier checks' builds could not be read, so the check builds")
|
||||||
|
fmt.Fprintln(os.Stdout, "repaired a pointer")
|
||||||
|
return "check-42", nil
|
||||||
|
})
|
||||||
|
os.Stdout, os.Stderr = realOut, realErr
|
||||||
|
w.Close()
|
||||||
|
ew.Close()
|
||||||
|
if runErr != nil {
|
||||||
|
t.Fatal(runErr)
|
||||||
|
}
|
||||||
|
var out, errOut bytes.Buffer
|
||||||
|
_, _ = out.ReadFrom(r)
|
||||||
|
_, _ = errOut.ReadFrom(e)
|
||||||
|
var doc map[string]any
|
||||||
|
if err := json.Unmarshal(out.Bytes(), &doc); err != nil || doc["asked"] != "check-42" || doc["rechecked"] != "novox/x@abc" {
|
||||||
|
t.Fatalf("standard output %q is not the answer alone (%v)", out.String(), err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(errOut.String(), "repaired a pointer") {
|
||||||
|
t.Fatalf("the prose should be on standard error: %q", errOut.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user