Repository navigation
Expand file tree
/
Copy pathcell_test.go
More file actions
132 lines (119 loc) · 3.47 KB
/
Copy pathcell_test.go
File metadata and controls
132 lines (119 loc) · 3.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright 2026 Hamed Yousefi
// SPDX-License-Identifier: MPL-2.0
package lockstep
import (
"testing"
"github.com/hmdsefi/lockstep/internal/engine"
)
// fnActor is a test actor whose handlers are functions; a nil function does nothing.
type fnActor[M any] struct {
started func(ctx *Context[M])
receive func(ctx *Context[M], msg M)
}
func (a *fnActor[M]) Started(ctx *Context[M]) {
if a.started != nil {
a.started(ctx)
}
}
func (a *fnActor[M]) Receive(ctx *Context[M], msg M) {
if a.receive != nil {
a.receive(ctx, msg)
}
}
// idle returns a factory of actors that do nothing.
func idle[M any]() func() Actor[M] {
return func() Actor[M] { return &fnActor[M]{} }
}
// actorOf returns a factory that returns a every time.
func actorOf[M any](a Actor[M]) func() Actor[M] {
return func() Actor[M] { return a }
}
// cellOf returns the cell registered for pid.
func cellOf[M any](t *testing.T, sys *System, pid PID[M]) *cell[M] {
t.Helper()
uid, a, ok := sys.reg.Lookup(pid.Address().Path())
if !ok || uid != pid.Address().UID() {
t.Fatalf("%v is not registered", pid)
}
c, ok := a.(*cell[M])
if !ok {
t.Fatalf("registry holds %T for %v", a, pid)
}
return c
}
// send sends msg to pid from the system and fails the test on an error.
func send[M any](t *testing.T, sys *System, pid PID[M], msg M) {
t.Helper()
if err := Send(sys, pid, msg); err != nil {
t.Fatalf("Send(%v): %v", pid, err)
}
}
// recordsOf returns the attributes of recs' records of kind, each as key=value strings.
func recordsOf(recs []engine.Record, kind string) [][]string {
var out [][]string
for _, r := range recs {
if r.Kind != kind {
continue
}
attrs := make([]string, 0, len(r.Attrs))
for _, a := range r.Attrs {
attrs = append(attrs, a.Key+"="+a.Value)
}
out = append(out, attrs)
}
return out
}
// ACT-110, ACT-120: one *Context per incarnation, passed to every turn; Self and Parent work at any
// time; Spawn with the context outside its turn panics.
func TestContextIdentityAndInTurn(t *testing.T) {
sys, eng := newManualSystem(t)
var ctxs []*Context[int]
var inTurn []bool
a := &fnActor[int]{
started: func(ctx *Context[int]) {
ctxs = append(ctxs, ctx)
inTurn = append(inTurn, ctx.inTurn.Load())
},
receive: func(ctx *Context[int], _ int) {
ctxs = append(ctxs, ctx)
inTurn = append(inTurn, ctx.inTurn.Load())
},
}
pid, err := Spawn(sys, "a", actorOf[int](a))
if err != nil {
t.Fatal(err)
}
eng.RunUntilIdle()
send(t, sys, pid, 1)
eng.RunUntilIdle()
send(t, sys, pid, 2)
eng.RunUntilIdle()
if len(ctxs) != 3 {
t.Fatalf("%d handler calls, want 3 (Started and two Receive)", len(ctxs))
}
for i, ctx := range ctxs {
if ctx != ctxs[0] {
t.Errorf("call %d got context %p, want %p", i, ctx, ctxs[0])
}
if !inTurn[i] {
t.Errorf("call %d: in-turn flag clear during the handler", i)
}
}
ctx := ctxs[0]
if ctx.inTurn.Load() {
t.Error("in-turn flag set after the turn")
}
if got := ctx.Self(); got != pid {
t.Errorf("Self() = %v, want %v", got, pid)
}
if got := ctx.Parent(); got != sys.user {
t.Errorf("Parent() = %v, want the user guardian %v", got, sys.user)
}
v := mustPanic(t, func() { _, _ = Spawn(ctx, "c", idle[int]()) })
if want := "lockstep: Context of " + pid.String() + " used outside its turn"; v != want {
t.Errorf("Spawn outside the turn panicked with %v, want %q", v, want)
}
if n := len(recordsOf(eng.Records(), "lockstep.spawn")); n != 1 {
t.Errorf("%d lockstep.spawn records, want 1 (the panicking Spawn records nothing)", n)
}
}