Give every test a bus of its own, at the release the mesh runs (hq ADR 0237)

The live tests reached one shared bus and assert, read and remove the mesh's own objects by
their fixed names, so packages run in parallel deleted what each other read and the suite
passed only one package at a time; a red suite read as noise. internal/testbus starts a server
per test, linked in at the nats-server release go.mod pins, and a test holds that pin to the
catalogue's bus image and to the facts snapshot's bus when there is one, so the tests never run
a bus the mesh does not. The waiter test read a timing (the most connections held at one look)
and now reads the state it means (the fewest held across the wait). make check runs the packages
in parallel under the race detector, with a timeout.
This commit is contained in:
jochen
2026-10-06 21:17:02 +02:00
parent 0090bf6af7
commit be92762969
627 changed files with 437174 additions and 166 deletions
+4 -7
View File
@@ -2,24 +2,21 @@ package broker
import (
"fmt"
"os"
"testing"
"time"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-controller/internal/testbus"
)
// A consumer that reacts to announcements, made on a stream that keeps a week of them, starts from now:
// made from the start it replays every merge and build of that week (novox/hq issue 248). And a reset
// re-makes a stuck one from now, its configuration otherwise kept, and refuses a work queue.
//
// docker run -d --rm --name t -p 14231:4222 nats:2.10-alpine -js
// MESH_TEST_NATS=nats://127.0.0.1:14231 go test ./internal/broker/ -run TestAConsumerMadeFromNow
// go test ./internal/broker/ -run TestAConsumerMadeFromNow (each test on a bus of its own: internal/testbus)
func TestAConsumerMadeFromNowNeverReplaysTheStreamsHistory(t *testing.T) {
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
js, err := Dial(url)
if err != nil {
t.Fatal(err)
+5 -11
View File
@@ -2,12 +2,13 @@ package broker
import (
"fmt"
"os"
"strings"
"testing"
"time"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-controller/internal/testbus"
)
const twoSeconds = 2 * time.Second
@@ -17,13 +18,9 @@ const twoSeconds = 2 * time.Second
// so bringing one to match must replace it — and must not replay what it already acknowledged
// (novox/hq 04-ISSUES/156).
//
// docker run -d --rm --name t -p 14231:4222 nats:2.10-alpine -js
// MESH_TEST_NATS=nats://127.0.0.1:14231 go test ./internal/broker/ -run TestUpgrading
// go test ./internal/broker/ -run TestUpgrading (each test on a bus of its own: internal/testbus)
func TestUpgradingAConsumerWhoseDeliverySubjectMoved(t *testing.T) {
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
js, err := Dial(url)
if err != nil {
t.Fatal(err)
@@ -133,10 +130,7 @@ func TestUpgradingAConsumerWhoseDeliverySubjectMoved(t *testing.T) {
// not undo. The controller's own two consumers are in exactly this position: it asserts them before
// it subscribes.
func TestAConsumerNothingIsBoundToDoesMove(t *testing.T) {
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
js, err := Dial(url)
if err != nil {
t.Fatal(err)
+4 -7
View File
@@ -1,8 +1,9 @@
package broker
import (
"os"
"testing"
"github.com/novox/mesh-controller/internal/testbus"
)
// Against a real server, because the questions here are all "does the server accept this" —
@@ -10,13 +11,9 @@ import (
//
// Skipped unless MESH_TEST_NATS names one, so the ordinary suite stays fast and offline:
//
// docker run -d --rm --name t -p 14222:4222 nats:2.10-alpine -js
// MESH_TEST_NATS=nats://127.0.0.1:14222 go test ./internal/broker/ -run TestAgainstARealServer
// go test ./internal/broker/ -run TestAgainstARealServer (each test on a bus of its own: internal/testbus)
func TestAgainstARealServer(t *testing.T) {
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
js, err := Dial(url)
if err != nil {
t.Fatal(err)
+4 -7
View File
@@ -1,10 +1,11 @@
package broker
import (
"os"
"testing"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-controller/internal/testbus"
)
// Raising the bus's objects against a real server.
@@ -14,15 +15,11 @@ import (
// could answer: that asserting twice changes nothing, and that a consumer really is bound to the one
// subject its node is allowed to read.
//
// docker run -d --rm --name t -p 14227:4222 nats:2.10-alpine -js
// MESH_TEST_NATS=nats://127.0.0.1:14227 go test ./internal/broker/ -run TestRaising
// go test ./internal/broker/ -run TestRaising (each test on a bus of its own: internal/testbus)
func aLiveBus(t *testing.T) *JetStream {
t.Helper()
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
js, err := Dial(url)
if err != nil {
t.Fatal(err)
+5 -11
View File
@@ -1,11 +1,12 @@
package broker
import (
"os"
"testing"
"time"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-controller/internal/testbus"
)
// A seat's worker that changed from push to pull delivery strands a holder built for the new shape
@@ -14,13 +15,9 @@ import (
// shape, type included: on a work queue it re-makes one of the wrong type, losing nothing, and a
// pull subscription then binds and takes what was pending.
//
// docker run -d --rm --name t -p 14231:4222 nats:2.10-alpine -js
// MESH_TEST_NATS=nats://127.0.0.1:14231 go test ./internal/broker/ -run TestAWorker
// go test ./internal/broker/ -run TestAWorker (each test on a bus of its own: internal/testbus)
func TestAWorkerOfTheWrongTypeIsRemadeOnAWorkQueueAndAPullThenBinds(t *testing.T) {
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
js, err := Dial(url)
if err != nil {
t.Fatal(err)
@@ -112,10 +109,7 @@ func TestAWorkerOfTheWrongTypeIsRemadeOnAWorkQueueAndAPullThenBinds(t *testing.T
// re-making it from the start would replay what it acknowledged (novox/hq issue 156), and leaving it
// for a hand re-made it exactly that way on 2026-10-03 (issue 207).
func TestAWorkerOfTheWrongTypeOnAHistoryStreamIsRemadeFromNowOn(t *testing.T) {
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
js, err := Dial(url)
if err != nil {
t.Fatal(err)
+3 -5
View File
@@ -2,13 +2,14 @@ package conditions
import (
"context"
"os"
"testing"
"time"
"github.com/nats-io/nats.go/jetstream"
"github.com/novox/mesh-controller/internal/broker"
"github.com/novox/mesh-controller/internal/testbus"
)
// The condition store against a real server: compare-and-set, an unreadable store refused, and the
@@ -16,10 +17,7 @@ import (
func busStoreForTest(t *testing.T) (*broker.JetStream, Backend, History) {
t.Helper()
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
js, err := broker.Dial(url)
if err != nil {
t.Fatal(err)
+11 -8
View File
@@ -69,16 +69,19 @@ func TestAWaiterPinsNoConnectionAndGivesUp(t *testing.T) {
done <- err
}()
}
// While they wait, the pool lends nothing to them for longer than a look.
pinned := 0
for range 10 {
time.Sleep(60 * time.Millisecond)
if n := int(pool.Stat().AcquiredConns() - base); n > pinned {
pinned = n
// While they wait, the pool lends nothing to them for longer than a look: **pinned is held throughout**,
// so at some moment of the wait fewer than all of them hold one. Asked of the least seen over many
// looks, not of the most: a look that lands while every waiter is mid-query is a busy machine, not a
// pinned connection, and on a loaded one (the suite's packages at once, the race detector) it did land.
least := waiters
for range 100 {
time.Sleep(10 * time.Millisecond)
if n := int(pool.Stat().AcquiredConns() - base); n < least {
least = n
}
}
if pinned >= waiters {
t.Fatalf("%d connections were held by %d waiters", pinned, waiters)
if least >= waiters {
t.Fatalf("%d waiters held %d connections at every look across their wait", waiters, least)
}
for range waiters {
if err := <-done; !errors.Is(err, ErrNodeBusy) || !strings.Contains(err.Error(), "anchor") {
+49
View File
@@ -0,0 +1,49 @@
package inventory
import (
"encoding/json"
"os"
"strings"
"testing"
)
// **The store the tests run is the store the mesh runs** (novox/hq ADR 0227 rule 9, ADR 0237). In a merge
// check the build seat raises the store of the major release the facts snapshot says the mesh's store
// runs; this holds the two equal there, so a test passing here is a test passing on that release. Without
// a snapshot — on a workstation — it says what it ran against and compares nothing.
func TestTheStoreTheTestsRunIsTheStoreTheMeshRuns(t *testing.T) {
inv := ForTest(t)
ran, err := inv.ServerVersionForTest(t)
if err != nil {
t.Fatal(err)
}
path := os.Getenv("MESH_FACTS")
if path == "" {
t.Logf("the tests ran the store at %s; with no MESH_FACTS, nothing to compare it with", ran)
return
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
var f struct {
Versions struct {
Store string `json:"store"`
} `json:"versions"`
}
if err := json.Unmarshal(body, &f); err != nil {
t.Fatal(err)
}
major := func(v string) string { m, _, _ := strings.Cut(strings.TrimSpace(v), "."); return m }
if f.Versions.Store != "" && major(ran) != major(f.Versions.Store) {
t.Errorf("the tests ran the store at %s and the mesh's store runs %s", ran, f.Versions.Store)
}
}
// ServerVersionForTest is the store's own word for its release.
func (i *Inventory) ServerVersionForTest(t *testing.T) (string, error) {
t.Helper()
var v string
err := i.store.Pool().QueryRow(t.Context(), `show server_version`).Scan(&v)
return v, err
}
+6 -11
View File
@@ -4,13 +4,14 @@ import (
"context"
"errors"
"fmt"
"os"
"sync"
"testing"
"time"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"github.com/novox/mesh-controller/internal/testbus"
)
// Two controllers at once, on a real bus (novox/hq to-be 45 §6, replay R1's half that lives here):
@@ -18,7 +19,7 @@ import (
// stops renewing is taken over once its key expires, and has stopped acting before then; a renewal
// refused is a loss at once; and a bucket raised again from nothing never issues an epoch twice.
//
// MESH_TEST_NATS=nats://127.0.0.1:14222 go test ./internal/lease/
// go test ./internal/lease/ (each test on a bus of its own: internal/testbus)
// The bounds in these tests are the design's divided by five, so the cases run in seconds.
const (
@@ -30,10 +31,7 @@ const (
// aBucket is a fresh lease bucket on the test bus, with the test's age.
func aBucket(t *testing.T) (jetstream.JetStream, string) {
t.Helper()
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
conn, err := nats.Connect(url)
if err != nil {
t.Fatal(err)
@@ -55,7 +53,7 @@ func aBucket(t *testing.T) (jetstream.JetStream, string) {
// aController is one controller instance's lease over the bucket, on a connection of its own.
func aController(t *testing.T, bucket, name string, floor func(context.Context) (uint64, error)) *Lease {
t.Helper()
conn, err := nats.Connect(os.Getenv("MESH_TEST_NATS"))
conn, err := nats.Connect(testbus.URL(t))
if err != nil {
t.Fatal(err)
}
@@ -275,10 +273,7 @@ func TestAnEpochIsNeverIssuedTwiceWhenTheBucketStartsOver(t *testing.T) {
}
func TestALeaseBucketWithoutAnAgeIsRefused(t *testing.T) {
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
conn, err := nats.Connect(url)
if err != nil {
t.Fatal(err)
+3 -4
View File
@@ -8,6 +8,8 @@ import (
"time"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-controller/internal/testbus"
)
// **A reader's own consumer gone is not a consumer lost**: every bucket watch and stream read-back
@@ -64,10 +66,7 @@ func TestNatsARefusedQuestionIsSaidAtOnce(t *testing.T) {
// The refusal reaches whoever waits on that subject, and only them.
func TestNatsARefusalReachesItsWaiter(t *testing.T) {
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
conn, err := nats.Connect(url)
if err != nil {
t.Fatal(err)
+5 -8
View File
@@ -5,7 +5,6 @@ import (
"encoding/json"
"io"
"log"
"os"
"sync"
"testing"
"time"
@@ -13,6 +12,8 @@ import (
"github.com/nats-io/nats.go"
"github.com/novox/mesh-controller/internal/broker"
"github.com/novox/mesh-controller/internal/testbus"
)
// A build, end to end, against a real server.
@@ -22,15 +23,11 @@ import (
// catalogue places it. On the old bus that fan-out came from a shared exchange, and it would be easy
// to write a version where only the asker hears it and nobody notices for weeks.
//
// docker run -d --rm --name t -p 14230:4222 nats:2.10-alpine -js
// MESH_TEST_NATS=nats://127.0.0.1:14230 go test ./internal/link/ -run TestNatsABuild
// go test ./internal/link/ -run TestNatsABuild (each test on a bus of its own: internal/testbus)
func aBusWithTheBuildRole(t *testing.T) *broker.JetStream {
t.Helper()
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
js, err := broker.Dial(url)
if err != nil {
t.Fatal(err)
@@ -349,7 +346,7 @@ func TestNatsAMachineOnTheRetiredBuildRoleTakesThatRolesAsks(t *testing.T) {
})
}()
asker, err := BuildsOverNATSOn(os.Getenv("MESH_TEST_NATS"), TheBuildMachineBefore)
asker, err := BuildsOverNATSOn(testbus.URL(t), TheBuildMachineBefore)
if err != nil {
t.Fatal(err)
}
+6 -15
View File
@@ -3,12 +3,13 @@ package link
import (
"context"
"encoding/json"
"os"
"strings"
"testing"
"time"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-controller/internal/testbus"
)
// **Both implementations, one fixture.** The point of the seam is not that the transport can be
@@ -17,13 +18,9 @@ import (
//
// Against a real server, because what the fixture pins is what reaches the wire:
//
// docker run -d --rm --name t -p 14222:4222 nats:2.10-alpine -js
// MESH_TEST_NATS=nats://127.0.0.1:14222 go test ./internal/link/ -run TestTheNatsBus
// go test ./internal/link/ -run TestTheNatsBus (each test on a bus of its own: internal/testbus)
func TestTheNatsBusEmitsTheEnvelopeTheFixturePins(t *testing.T) {
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
f := loadFixture(t, "events/module-event.json")
conn, err := nats.Connect(url)
@@ -99,10 +96,7 @@ func TestAnEventsSubjectIsDerivedFromItsSource(t *testing.T) {
// tool is slow" is the first thing a person asking wants, and waiting out the timeout to say it
// is how a fast answer becomes a slow non-answer.
func TestAskingAToolNobodyServesFailsAtOnce(t *testing.T) {
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
conn, err := nats.Connect(url)
if err != nil {
t.Fatal(err)
@@ -125,10 +119,7 @@ func TestAskingAToolNobodyServesFailsAtOnce(t *testing.T) {
// And a served tool answers, with no reply queue to declare and no correlation to check.
func TestAskingAServedToolAnswers(t *testing.T) {
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
conn, err := nats.Connect(url)
if err != nil {
t.Fatal(err)
+4 -7
View File
@@ -1,11 +1,12 @@
package link
import (
"os"
"testing"
"time"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-controller/internal/testbus"
)
// **Verified 2026-09-27**: the caller asked for a reply to `_INBOX.LCr3M83q…` and the consumer
@@ -18,13 +19,9 @@ import (
// enrolment design rests on it — the reply subject travels in the payload instead — so it is
// checked rather than believed.
//
// docker run -d --rm --name t -p 14222:4222 nats:2.10-alpine -js
// MESH_TEST_NATS=nats://127.0.0.1:14222 go test ./internal/link/ -run TestAReplyAddress
// go test ./internal/link/ -run TestAReplyAddress (each test on a bus of its own: internal/testbus)
func TestAReplyAddressDoesNotSurviveAStream(t *testing.T) {
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
conn, err := nats.Connect(url)
if err != nil {
t.Fatal(err)
+4 -7
View File
@@ -6,7 +6,6 @@ import (
"errors"
"io"
"log"
"os"
"sync"
"testing"
"time"
@@ -14,6 +13,8 @@ import (
"github.com/nats-io/nats.go"
"github.com/novox/mesh-controller/internal/broker"
"github.com/novox/mesh-controller/internal/testbus"
)
// The consume side against a real server, because what is being checked is what the server does.
@@ -23,15 +24,11 @@ import (
// to an address carried in the payload reaches a caller waiting on its own inbox. Each is a claim
// about a server, so each is asked of one:
//
// docker run -d --rm --name t -p 14222:4222 nats:2.10-alpine -js
// MESH_TEST_NATS=nats://127.0.0.1:14222 go test ./internal/link/ -run TestNats
// go test ./internal/link/ -run TestNats (each test on a bus of its own: internal/testbus)
func aBus(t *testing.T) *broker.JetStream {
t.Helper()
url := os.Getenv("MESH_TEST_NATS")
if url == "" {
t.Skip("MESH_TEST_NATS unset")
}
url := testbus.URL(t)
js, err := broker.Dial(url)
if err != nil {
t.Fatal(err)
+3 -2
View File
@@ -3,11 +3,12 @@ package link
import (
"context"
"encoding/json"
"os"
"testing"
"time"
"github.com/novox/mesh-controller/internal/broker"
"github.com/novox/mesh-controller/internal/testbus"
)
// novox/hq issue 213: while a machine hands its controller over, the new controller (the process)
@@ -29,7 +30,7 @@ func TestNatsASecondControllerStandsByAndTakesOverWhenTheFirstLetsGo(t *testing.
})
// The new one, on a connection of its own as the process would have.
second, err := broker.Dial(os.Getenv("MESH_TEST_NATS"))
second, err := broker.Dial(testbus.URL(t))
if err != nil {
t.Fatal(err)
}
+77
View File
@@ -0,0 +1,77 @@
// Package testbus is the bus a test runs against: a server of its own, of the release the mesh runs,
// started for the one test and gone after it.
//
// **Why one per test.** The live tests reached one shared bus, and on it they assert, read and remove the
// mesh's own objects — streams and consumers with fixed names, because those names are the mesh's. Two
// packages at once was one deleting a consumer the other was reading through, and a test left a stream
// behind for the next to trip on: the suite passed one package at a time and failed or hung run as Go
// runs it, which taught everybody to read a red suite as noise. A server per test makes every test start
// from nothing and leave nothing, so the suite is the same run in any order, at any parallelism, under
// the race detector, on a workstation or on the build seat.
//
// **Why this release.** The server is linked in at the version go.mod pins, and a test beside this one
// holds that pin to the release the mesh runs — the catalogue's bus image, and the facts snapshot's when
// a merge check has one (novox/hq ADR 0227 rule 9: a version the mesh runs is the one its tests run).
// A bus the 2.10 line skipped messages on (issue 266) would have been caught by the tests that ran on it,
// had they run on it.
//
// A person may still point a run at a bus of their own — another release, a lab's — with
// MESH_TEST_NATS_EXTERNAL=1 and MESH_TEST_NATS; then the run is theirs to serialise.
package testbus
import (
"os"
"sync"
"testing"
"time"
"github.com/nats-io/nats-server/v2/server"
)
// Version is the release of the server the tests run.
const Version = server.VERSION
// URL is the bus of this test alone: started at its first ask, the same one at every ask after — a
// test that dials twice, or hands the address to the code it tests, reaches one bus — and shut down
// when the test ends.
func URL(t testing.TB) string {
t.Helper()
if os.Getenv("MESH_TEST_NATS_EXTERNAL") == "1" {
if url := os.Getenv("MESH_TEST_NATS"); url != "" {
return url
}
t.Fatal("MESH_TEST_NATS_EXTERNAL=1 and MESH_TEST_NATS names no bus")
}
if url, ok := buses.Load(t); ok {
return url.(string)
}
url := Start(t).ClientURL()
buses.Store(t, url)
t.Cleanup(func() { buses.Delete(t) })
return url
}
// buses are the running tests' buses, by test.
var buses sync.Map
// Start is a server with JetStream, for this test alone: on loopback, on a port the system chooses, its
// store in the test's own directory.
func Start(t testing.TB) *server.Server {
t.Helper()
opts := &server.Options{Host: "127.0.0.1", Port: server.RANDOM_PORT, JetStream: true, StoreDir: t.TempDir(),
NoLog: true, NoSigs: true}
s, err := server.NewServer(opts)
if err != nil {
t.Fatalf("a bus for this test could not be made: %v", err)
}
go s.Start()
if !s.ReadyForConnections(30 * time.Second) {
s.Shutdown()
t.Fatal("a bus for this test did not come up within 30s")
}
t.Cleanup(func() {
s.Shutdown()
s.WaitForShutdown()
})
return s
}
+96
View File
@@ -0,0 +1,96 @@
package testbus
import (
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"encoding/json"
"github.com/nats-io/nats.go"
)
// **The bus the tests run is the bus the mesh runs** (novox/hq ADR 0227 rule 9, to-be 45 §9). The server
// linked into the tests is held to the release the catalogue's bus image is — read from beside this
// checkout — and, in a merge check that has the facts snapshot, to the release the mesh's bus server says
// it runs. A bus upgrade moves the catalogue's pin; this then fails until the tests' pin moves with it,
// so the controller is never tested against a bus the mesh no longer runs, or not yet.
func TestTheBusTheTestsRunIsTheBusTheMeshRuns(t *testing.T) {
dockerfile := filepath.Join("..", "..", "..", "mesh-catalog", "modules", "nats", "Dockerfile")
raw, err := os.ReadFile(dockerfile)
switch {
case err == nil:
pinned := regexp.MustCompile(`upstream: nats ([0-9.]+)-alpine`).FindSubmatch(raw)
if pinned == nil {
t.Fatalf("%s says no release its digest is", dockerfile)
}
if string(pinned[1]) != Version {
t.Errorf("the tests run bus %s and the catalogue's bus image is %s: move go.mod's nats-server pin "+
"(and `go mod vendor`) with the image", Version, pinned[1])
}
case os.IsNotExist(err):
t.Logf("the catalogue is not beside this checkout, so its bus image is not compared")
default:
t.Fatal(err)
}
path := os.Getenv("MESH_FACTS")
if path == "" {
t.Logf("no MESH_FACTS: the bus the mesh runs is compared in a merge check, which has it")
return
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
// Read as little of the snapshot as this needs (internal/facts holds the whole shape).
var f struct {
Taken string `json:"taken"`
Versions struct {
Bus string `json:"bus"`
} `json:"versions"`
}
if err := json.Unmarshal(body, &f); err != nil {
t.Fatal(err)
}
if f.Versions.Bus != "" && strings.TrimPrefix(f.Versions.Bus, "v") != Version {
t.Errorf("the tests run bus %s and the mesh's bus runs %s (facts of %s)", Version, f.Versions.Bus, f.Taken)
}
}
// A test's bus is its own: what one test makes, the next never sees.
func TestEveryTestHasABusOfItsOwn(t *testing.T) {
seen := map[string]bool{}
for i := 0; i < 2; i++ {
t.Run("one", func(t *testing.T) {
url := URL(t)
if seen[url] {
t.Fatalf("two tests were handed %s", url)
}
seen[url] = true
conn, err := nats.Connect(url)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
js, err := conn.JetStream()
if err != nil {
t.Fatal(err)
}
if _, err := js.StreamInfo("EVENTS"); err == nil {
t.Fatal("a stream an earlier test made is here")
}
if _, err := js.AddStream(&nats.StreamConfig{Name: "EVENTS", Subjects: []string{"mesh.>"}}); err != nil {
t.Fatal(err)
}
})
}
// Asked twice, one test reaches one bus.
if a, b := URL(t), URL(t); a != b {
t.Errorf("one test was handed two buses: %s and %s", a, b)
}
if v := strings.Count(Version, "."); v != 2 {
t.Errorf("the server's release reads %q", Version)
}
}