A blind write after a read could lose the build of the publish that wrote first until a bundle restart learned it again. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
67 lines
2.1 KiB
Go
67 lines
2.1 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
|
|
"distribution/internal/holder"
|
|
)
|
|
|
|
// casMem is a kept state with revisions; before writes it lets another writer write first, once.
|
|
type casMem struct {
|
|
value []byte
|
|
rev uint64
|
|
before func()
|
|
}
|
|
|
|
func (m *casMem) Get(string) (*stdio.StateEntry, error) {
|
|
if m.value == nil {
|
|
return nil, nil
|
|
}
|
|
return &stdio.StateEntry{Value: m.value, Revision: m.rev}, nil
|
|
}
|
|
func (m *casMem) write(v any, at uint64) (uint64, error) {
|
|
if f := m.before; f != nil {
|
|
m.before = nil
|
|
f()
|
|
}
|
|
if m.rev != at {
|
|
return 0, stdio.ErrChanged
|
|
}
|
|
m.value, _ = json.Marshal(v)
|
|
m.rev++
|
|
return m.rev, nil
|
|
}
|
|
func (m *casMem) Create(_ string, v any) (uint64, error) { return m.write(v, 0) }
|
|
func (m *casMem) Update(_ string, v any, rev uint64) (uint64, error) { return m.write(v, rev) }
|
|
|
|
// TestTwoFirstPublishesAtOnceAreBothKept: the publishing builds are written with compare-and-set, so a writer that lost
|
|
// the race reads again and neither build is lost (A3f finding 2).
|
|
func TestTwoFirstPublishesAtOnceAreBothKept(t *testing.T) {
|
|
m := &casMem{}
|
|
m.before = func() {
|
|
// Another tools process learns mesh-build-go's build first.
|
|
m.value, _ = json.Marshal(holder.Publishing{Name: holder.PublishingKey, Modules: map[string][]string{"mesh-build-go": {"fp-go"}}})
|
|
m.rev = 1
|
|
}
|
|
if learnt, err := learnInto(m, "mesh-build-node", "fp-node", 3); err != nil || !learnt {
|
|
t.Fatalf("learnInto = %v, %v", learnt, err)
|
|
}
|
|
var got holder.Publishing
|
|
_ = json.Unmarshal(m.value, &got)
|
|
want := map[string][]string{"mesh-build-go": {"fp-go"}, "mesh-build-node": {"fp-node"}}
|
|
if !reflect.DeepEqual(got.Modules, want) {
|
|
t.Errorf("kept %v, want both builds %v", got.Modules, want)
|
|
}
|
|
if learnt, err := learnInto(m, "mesh-build-node", "fp-node", 3); err != nil || learnt {
|
|
t.Errorf("learning a kept build again = %v, %v, want not new and no write", learnt, err)
|
|
}
|
|
m.before = func() { m.rev++ }
|
|
if _, err := learnInto(m, "mesh-build-oci", "fp-oci", 1); err == nil {
|
|
t.Error("a write lost on every attempt answered no error")
|
|
}
|
|
}
|