moth

Monarch Of The Hill game server
git clone https://git.woozle.org/neale/moth.git

moth / cmd / mothd
Neale Pickett  ·  2023-09-29

state_test.go

  1package main
  2
  3import (
  4	"bytes"
  5	"fmt"
  6	"os"
  7	"strings"
  8	"testing"
  9	"time"
 10
 11	"github.com/spf13/afero"
 12)
 13
 14func NewTestState() *State {
 15	s := NewState(new(afero.MemMapFs))
 16	s.refresh()
 17	return s
 18}
 19
 20func slurp(c chan bool) {
 21	for range c {
 22		// Nothing
 23	}
 24}
 25
 26func TestState(t *testing.T) {
 27	s := NewTestState()
 28	defer close(s.refreshNow)
 29	go slurp(s.refreshNow)
 30
 31	mustExist := func(path string) {
 32		_, err := s.Fs.Stat(path)
 33		if os.IsNotExist(err) {
 34			t.Errorf("File %s does not exist", path)
 35		}
 36	}
 37
 38	pl := s.PointsLog()
 39	if len(pl) != 0 {
 40		t.Errorf("Empty points log is not empty")
 41	}
 42
 43	mustExist("initialized")
 44	mustExist("hours.txt")
 45
 46	teamIDsBuf, err := afero.ReadFile(s.Fs, "teamids.txt")
 47	if err != nil {
 48		t.Errorf("Reading teamids.txt: %v", err)
 49	}
 50
 51	teamIDs := bytes.Split(teamIDsBuf, []byte("\n"))
 52	if (len(teamIDs) != 101) || (len(teamIDs[100]) > 0) {
 53		t.Errorf("There weren't 100 teamIDs, there were %d", len(teamIDs))
 54	}
 55	teamID := string(teamIDs[0])
 56
 57	if _, err := s.TeamName(teamID); err == nil {
 58		t.Errorf("Bad team ID lookup didn't return error")
 59	}
 60
 61	if err := s.SetTeamName("bad team ID", "bad team name"); err == nil {
 62		t.Errorf("Setting bad team ID didn't raise an error")
 63	}
 64
 65	teamName := "My Team"
 66	if err := s.SetTeamName(teamID, teamName); err != nil {
 67		t.Errorf("Setting team name: %v", err)
 68	}
 69	if err := s.SetTeamName(teamID, "wat"); err == nil {
 70		t.Errorf("Registering team a second time didn't fail")
 71	}
 72	s.refresh()
 73	if name, err := s.TeamName(teamID); err != nil {
 74		t.Error(err)
 75	} else if name != teamName {
 76		t.Error("Incorrect team name:", name)
 77	}
 78
 79	category := "poot"
 80	points := 3928
 81	if err := s.AwardPoints(teamID, category, points); err != nil {
 82		t.Error(err)
 83	}
 84	// Flex duplicate detection with different timestamp
 85	if f, err := s.Create("points.new/moo"); err != nil {
 86		t.Error("Creating duplicate points file:", err)
 87	} else {
 88		fmt.Fprintln(f, time.Now().Unix()+1, teamID, category, points)
 89		f.Close()
 90	}
 91
 92	s.AwardPoints(teamID, category, points)
 93	s.refresh()
 94	pl = s.PointsLog()
 95	if len(pl) != 1 {
 96		for i, award := range pl {
 97			t.Logf("pl[%d] == %s", i, award.String())
 98		}
 99		t.Errorf("After awarding duplicate points, points log has length %d", len(pl))
100	} else if (pl[0].TeamID != teamID) || (pl[0].Category != category) || (pl[0].Points != points) {
101		t.Errorf("Incorrect logged award %v", pl)
102	}
103
104	if err := s.AwardPoints(teamID, category, points); err == nil {
105		t.Error("Duplicate points award after refresh didn't fail")
106	}
107
108	if err := s.AwardPoints(teamID, category, points+1); err != nil {
109		t.Error("Awarding more points:", err)
110	}
111
112	s.refresh()
113	if len(s.PointsLog()) != 2 {
114		t.Errorf("There should be two awards")
115	}
116
117	afero.WriteFile(s, "points.log", []byte("intentional parse error\n"), 0644)
118	s.refresh()
119	if len(s.PointsLog()) != 0 {
120		t.Errorf("Intentional parse error breaks pointslog")
121	}
122	if err := s.AwardPoints(teamID, category, points); err != nil {
123		t.Error(err)
124	}
125	s.refresh()
126	if len(s.PointsLog()) != 1 {
127		t.Log(s.PointsLog())
128		t.Error("Intentional parse error screws up all parsing")
129	}
130
131	s.Fs.Remove("initialized")
132	s.refresh()
133
134	pl = s.PointsLog()
135	if len(pl) != 0 {
136		t.Errorf("After reinitialization, points log has length %d", len(pl))
137	}
138
139}
140
141// Out of order points insertion, issue #168
142func TestStateOutOfOrderAward(t *testing.T) {
143	s := NewTestState()
144
145	category := "meow"
146	points := 100
147
148	now := time.Now().Unix()
149	if err := s.awardPointsAtTime(now+20, "AA", category, points); err != nil {
150		t.Error("Awarding points to team ZZ:", err)
151	}
152	if err := s.awardPointsAtTime(now+10, "ZZ", category, points); err != nil {
153		t.Error("Awarding points to team AA:", err)
154	}
155	s.refresh()
156	pl := s.PointsLog()
157	if len(pl) != 2 {
158		t.Error("Wrong length for points log")
159	}
160	if pl[0].TeamID != "ZZ" {
161		t.Error("Out of order points insertion not properly sorted in points log")
162	}
163}
164
165func TestStateEvents(t *testing.T) {
166	s := NewTestState()
167	s.LogEvent("moo", "", "", 0)
168	s.LogEvent("moo 2", "", "", 0)
169
170	if msg := <-s.eventStream; strings.Join(msg[1:], ":") != "init:::0" {
171		t.Error("Wrong message from event stream:", msg)
172	}
173	if msg := <-s.eventStream; !strings.HasPrefix(msg[5], "state/hours.txt") {
174		t.Error("Wrong message from event stream:", msg[5])
175	}
176	if msg := <-s.eventStream; strings.Join(msg[1:], ":") != "moo:::0" {
177		t.Error("Wrong message from event stream:", msg)
178	}
179	if msg := <-s.eventStream; strings.Join(msg[1:], ":") != "moo 2:::0" {
180		t.Error("Wrong message from event stream:", msg)
181	}
182}
183
184func TestStateDisabled(t *testing.T) {
185	s := NewTestState()
186	s.refresh()
187
188	if !s.Enabled() {
189		t.Error("Brand new state is disabled")
190	}
191
192	hoursFile, err := s.Create("hours.txt")
193	if err != nil {
194		t.Error(err)
195	}
196	defer hoursFile.Close()
197	s.refresh()
198	if !s.Enabled() {
199		t.Error("Empty hours.txt not enabled")
200	}
201
202	fmt.Fprintln(hoursFile, "- 1970-01-01T01:01:01Z")
203	hoursFile.Sync()
204	s.refresh()
205	if s.Enabled() {
206		t.Error("1970-01-01")
207	}
208
209	fmt.Fprintln(hoursFile, "+ 1970-01-02 01:01:01+05:00")
210	hoursFile.Sync()
211	s.refresh()
212	if !s.Enabled() {
213		t.Error("1970-01-02")
214	}
215
216	fmt.Fprintln(hoursFile, "-")
217	hoursFile.Sync()
218	s.refresh()
219	if s.Enabled() {
220		t.Error("bare -")
221	}
222
223	fmt.Fprintln(hoursFile, "+")
224	hoursFile.Sync()
225	s.refresh()
226	if !s.Enabled() {
227		t.Error("bare +")
228	}
229
230	fmt.Fprintln(hoursFile, "")
231	fmt.Fprintln(hoursFile, "# Comment")
232	hoursFile.Sync()
233	s.refresh()
234	if !s.Enabled() {
235		t.Error("Comment")
236	}
237
238	fmt.Fprintln(hoursFile, "intentional parse error")
239	hoursFile.Sync()
240	s.refresh()
241	if !s.Enabled() {
242		t.Error("intentional parse error")
243	}
244
245	fmt.Fprintln(hoursFile, "- 1980-01-01T01:01:01Z")
246	hoursFile.Sync()
247	s.refresh()
248	if s.Enabled() {
249		t.Error("1980-01-01")
250	}
251
252	if err := s.Remove("hours.txt"); err != nil {
253		t.Error(err)
254	}
255	s.refresh()
256	if !s.Enabled() {
257		t.Error("Removing `hours.txt` disabled event")
258	}
259
260	s.Remove("initialized")
261	s.refresh()
262	if !s.Enabled() {
263		t.Error("Re-initializing didn't start event")
264	}
265}
266
267func TestStateMaintainer(t *testing.T) {
268	updateInterval := 10 * time.Millisecond
269
270	s := NewTestState()
271	go s.Maintain(updateInterval)
272
273	if _, err := s.Stat("initialized"); err != nil {
274		t.Error(err)
275	}
276	teamIDLines, err := afero.ReadFile(s, "teamids.txt")
277	if err != nil {
278		t.Error(err)
279	}
280	teamIDList := strings.Split(string(teamIDLines), "\n")
281	if len(teamIDList) != 101 {
282		t.Error("TeamIDList length is", len(teamIDList))
283	}
284	teamID := teamIDList[0]
285	if len(teamID) < 6 {
286		t.Error("Team ID too short:", teamID)
287	}
288
289	s.LogEvent("Hello!", "", "", 0)
290
291	if len(s.PointsLog()) != 0 {
292		t.Error("Points log is not empty")
293	}
294	if err := s.SetTeamName(teamID, "The Patricks"); err != nil {
295		t.Error(err)
296	}
297	if err := s.AwardPoints(teamID, "pategory", 31337); err != nil {
298		t.Error(err)
299	}
300	time.Sleep(updateInterval)
301	pl := s.PointsLog()
302	if len(pl) != 1 {
303		t.Error("Points log should have one entry")
304	}
305	if (pl[0].Category != "pategory") || (pl[0].TeamID != teamID) {
306		t.Error("Wrong points event was recorded")
307	}
308
309	time.Sleep(updateInterval)
310
311	eventLog, err := afero.ReadFile(s.Fs, "events.csv")
312	if err != nil {
313		t.Error(err)
314	} else if events := strings.Split(string(eventLog), "\n"); len(events) != 4 {
315		t.Log("Events:", events)
316		t.Error("Wrong event log length:", len(events))
317	} else if events[3] != "" {
318		t.Error("Event log didn't end with newline", events)
319	}
320}
321
322func TestDevelState(t *testing.T) {
323	s := NewTestState()
324	ds := NewDevelState(s)
325	if err := ds.SetTeamName("boog", "The Boog Team"); err != ErrAlreadyRegistered {
326		t.Error("Registering a team that doesn't exist", err)
327	} else if err == nil {
328		t.Error("Registering a team that doesn't exist didn't return ErrAlreadyRegistered")
329	}
330	if n, err := ds.TeamName("boog"); err != nil {
331		t.Error("Devel State returned error on team name lookup")
332	} else if n != "«devel:boog»" {
333		t.Error("Wrong team name", n)
334	}
335
336	if err := ds.AwardPoints("blerg", "dog", 82); err != nil {
337		t.Error("Devel State AwardPoints returned an error", err)
338	}
339}