package coordinator import ( "errors" "sync" "sync/atomic" "testing" ) func TestContextUpdateConcurrentNoLostUpdate(t *testing.T) { ctx := &UserProjectContext{ Version: 1, BlackboardFacts: map[string]string{"market": "国内"}, } var ok, stale int32 var wg sync.WaitGroup for i := 0; i < 2; i++ { wg.Add(1) go func(label string) { defer wg.Done() err := ApplyContextUpdate(ctx, ContextUpdate{ ExpectedVersion: 1, BlackboardSet: map[string]string{"market": label}, }) switch { case err == nil: atomic.AddInt32(&ok, 1) case errors.Is(err, ErrStaleContext): atomic.AddInt32(&stale, 1) default: t.Errorf("unexpected %v", err) } }("v" + string(rune('a'+i))) } wg.Wait() if ok != 1 || stale != 1 { t.Fatalf("ok=%d stale=%d version=%d facts=%v", ok, stale, ctx.Version, ctx.BlackboardFacts) } if ctx.Version != 2 { t.Fatalf("version %d", ctx.Version) } if ctx.BlackboardFacts["market"] == "国内" { t.Fatal("winner did not apply") } } func TestContextUpdateFillDoesNotOverwrite(t *testing.T) { ctx := &UserProjectContext{Version: 0, BlackboardFacts: map[string]string{"a": "1"}} if err := ApplyContextUpdate(ctx, ContextUpdate{ExpectedVersion: 0, BlackboardFill: map[string]string{"a": "x", "b": "2"}}); err != nil { t.Fatal(err) } if ctx.BlackboardFacts["a"] != "1" || ctx.BlackboardFacts["b"] != "2" { t.Fatalf("%v", ctx.BlackboardFacts) } }