Start doing more tests

This commit is contained in:
Neale Pickett 2019-12-01 18:58:09 -07:00
parent 65f810539a
commit 7825f196be
3 changed files with 88 additions and 56 deletions

View File

@ -2,6 +2,7 @@ package main
import ( import (
"github.com/namsral/flag" "github.com/namsral/flag"
"github.com/spf13/afero"
"log" "log"
"time" "time"
) )
@ -35,15 +36,17 @@ func main() {
"Bind [host]:port for HTTP service", "Bind [host]:port for HTTP service",
) )
stateFs := afero.NewBasePathFs(afero.NewOsFs(), *statePath)
theme := NewTheme(*themePath) theme := NewTheme(*themePath)
state := NewState(*statePath) state := NewState(stateFs)
puzzles := NewMothballs(*puzzlePath) puzzles := NewMothballs(*puzzlePath)
go theme.Run(*refreshInterval) go theme.Run(*refreshInterval)
go state.Run(*refreshInterval) go state.Run(*refreshInterval)
go puzzles.Run(*refreshInterval) go puzzles.Run(*refreshInterval)
log.Println("I would be binding to", *bindStr) log.Println("I would be binding to", *bindStr)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
log.Print(state.Export("")) log.Print(state.Export(""))
time.Sleep(19 * time.Second) time.Sleep(19 * time.Second)

View File

@ -3,10 +3,11 @@ package main
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"io/ioutil" "github.com/spf13/afero"
"log" "log"
"math/rand" "math/rand"
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -33,31 +34,29 @@ type StateExport struct {
// We use the filesystem for synchronization between threads. // We use the filesystem for synchronization between threads.
// The only thing State methods need to know is the path to the state directory. // The only thing State methods need to know is the path to the state directory.
type State struct { type State struct {
Component
Enabled bool Enabled bool
update chan bool update chan bool
fs afero.Fs
} }
func NewState(baseDir string) *State { func NewState(fs afero.Fs) *State {
return &State{ return &State{
Component: Component{
baseDir: baseDir,
},
Enabled: true, Enabled: true,
update: make(chan bool, 10), update: make(chan bool, 10),
fs: fs,
} }
} }
// Check a few things to see if this state directory is "enabled". // Check a few things to see if this state directory is "enabled".
func (s *State) UpdateEnabled() { func (s *State) UpdateEnabled() {
if _, err := os.Stat(s.path("enabled")); os.IsNotExist(err) { if _, err := s.fs.Stat("enabled"); os.IsNotExist(err) {
s.Enabled = false s.Enabled = false
log.Print("Suspended: enabled file missing") log.Print("Suspended: enabled file missing")
return return
} }
nextEnabled := true nextEnabled := true
untilFile, err := os.Open(s.path("hours")) untilFile, err := s.fs.Open("hours")
if err != nil { if err != nil {
return return
} }
@ -101,8 +100,8 @@ func (s *State) UpdateEnabled() {
// Returns team name given a team ID. // Returns team name given a team ID.
func (s *State) TeamName(teamId string) (string, error) { func (s *State) TeamName(teamId string) (string, error) {
teamFile := s.path("teams", teamId) teamFile := filepath.Join("teams", teamId)
teamNameBytes, err := ioutil.ReadFile(teamFile) teamNameBytes, err := afero.ReadFile(s.fs, teamFile)
teamName := strings.TrimSpace(string(teamNameBytes)) teamName := strings.TrimSpace(string(teamNameBytes))
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -116,15 +115,14 @@ func (s *State) TeamName(teamId string) (string, error) {
// Write out team name. This can only be done once. // Write out team name. This can only be done once.
func (s *State) SetTeamName(teamId string, teamName string) error { func (s *State) SetTeamName(teamId string, teamName string) error {
teamFile := s.path("teams", teamId) teamFile := filepath.Join("teams", teamId)
err := ioutil.WriteFile(teamFile, []byte(teamName), os.ModeExclusive|0644) err := afero.WriteFile(s.fs, teamFile, []byte(teamName), os.ModeExclusive|0644)
return err return err
} }
// Retrieve the current points log // Retrieve the current points log
func (s *State) PointsLog() []*Award { func (s *State) PointsLog() []*Award {
pointsFile := s.path("points.log") f, err := s.fs.Open("points.log")
f, err := os.Open(pointsFile)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return nil return nil
@ -160,8 +158,7 @@ func (s *State) Export(teamId string) *StateExport {
} }
// Read in messages // Read in messages
messagesFile := s.path("messages.txt") if f, err := s.fs.Open("messages.txt"); err != nil {
if f, err := os.Open(messagesFile); err != nil {
log.Print(err) log.Print(err)
} else { } else {
defer f.Close() defer f.Close()
@ -223,14 +220,14 @@ func (s *State) AwardPoints(teamId, category string, points int) error {
} }
fn := fmt.Sprintf("%s-%s-%d", teamId, category, points) fn := fmt.Sprintf("%s-%s-%d", teamId, category, points)
tmpfn := s.path("points.tmp", fn) tmpfn := filepath.Join("points.tmp", fn)
newfn := s.path("points.new", fn) newfn := filepath.Join("points.new", fn)
if err := ioutil.WriteFile(tmpfn, []byte(a.String()), 0644); err != nil { if err := afero.WriteFile(s.fs, tmpfn, []byte(a.String()), 0644); err != nil {
return err return err
} }
if err := os.Rename(tmpfn, newfn); err != nil { if err := s.fs.Rename(tmpfn, newfn); err != nil {
return err return err
} }
@ -241,14 +238,14 @@ func (s *State) AwardPoints(teamId, category string, points int) error {
// collectPoints gathers up files in points.new/ and appends their contents to points.log, // collectPoints gathers up files in points.new/ and appends their contents to points.log,
// removing each points.new/ file as it goes. // removing each points.new/ file as it goes.
func (s *State) collectPoints() { func (s *State) collectPoints() {
files, err := ioutil.ReadDir(s.path("points.new")) files, err := afero.ReadDir(s.fs, "points.new")
if err != nil { if err != nil {
log.Print(err) log.Print(err)
return return
} }
for _, f := range files { for _, f := range files {
filename := s.path("points.new", f.Name()) filename := filepath.Join("points.new", f.Name())
awardstr, err := ioutil.ReadFile(filename) awardstr, err := afero.ReadFile(s.fs, filename)
if err != nil { if err != nil {
log.Print("Opening new points: ", err) log.Print("Opening new points: ", err)
continue continue
@ -272,7 +269,7 @@ func (s *State) collectPoints() {
} else { } else {
log.Print("Award: ", award.String()) log.Print("Award: ", award.String())
logf, err := os.OpenFile(s.path("points.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) logf, err := s.fs.OpenFile("points.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
log.Print("Can't append to points log: ", err) log.Print("Can't append to points log: ", err)
return return
@ -281,7 +278,7 @@ func (s *State) collectPoints() {
logf.Close() logf.Close()
} }
if err := os.Remove(filename); err != nil { if err := s.fs.Remove(filename); err != nil {
log.Print("Unable to remove new points file: ", err) log.Print("Unable to remove new points file: ", err)
} }
} }
@ -289,28 +286,28 @@ func (s *State) collectPoints() {
func (s *State) maybeInitialize() { func (s *State) maybeInitialize() {
// Are we supposed to re-initialize? // Are we supposed to re-initialize?
if _, err := os.Stat(s.path("initialized")); !os.IsNotExist(err) { if _, err := s.fs.Stat("initialized"); !os.IsNotExist(err) {
return return
} }
log.Print("initialized file missing, re-initializing") log.Print("initialized file missing, re-initializing")
// Remove any extant control and state files // Remove any extant control and state files
os.Remove(s.path("enabled")) s.fs.Remove("enabled")
os.Remove(s.path("until")) s.fs.Remove("hours")
os.Remove(s.path("points.log")) s.fs.Remove("points.log")
os.Remove(s.path("messages.txt")) s.fs.Remove("messages.txt")
os.RemoveAll(s.path("points.tmp")) s.fs.RemoveAll("points.tmp")
os.RemoveAll(s.path("points.new")) s.fs.RemoveAll("points.new")
os.RemoveAll(s.path("teams")) s.fs.RemoveAll("teams")
// Make sure various subdirectories exist // Make sure various subdirectories exist
os.Mkdir(s.path("points.tmp"), 0755) s.fs.Mkdir("points.tmp", 0755)
os.Mkdir(s.path("points.new"), 0755) s.fs.Mkdir("points.new", 0755)
os.Mkdir(s.path("teams"), 0755) s.fs.Mkdir("teams", 0755)
// Preseed available team ids if file doesn't exist // Preseed available team ids if file doesn't exist
if f, err := os.OpenFile(s.path("teamids.txt"), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644); err == nil { if f, err := s.fs.OpenFile("teamids.txt", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644); err == nil {
defer f.Close() defer f.Close()
for i := 0; i <= 100; i += 1 { for i := 0; i <= 100; i += 1 {
fmt.Fprintln(f, mktoken()) fmt.Fprintln(f, mktoken())
@ -318,18 +315,21 @@ func (s *State) maybeInitialize() {
} }
// Create some files // Create some files
ioutil.WriteFile( afero.WriteFile(
s.path("initialized"), s.fs,
"initialized",
[]byte("state/initialized: remove to re-initialize the contest\n"), []byte("state/initialized: remove to re-initialize the contest\n"),
0644, 0644,
) )
ioutil.WriteFile( afero.WriteFile(
s.path("enabled"), s.fs,
"enabled",
[]byte("state/enabled: remove to suspend the contest\n"), []byte("state/enabled: remove to suspend the contest\n"),
0644, 0644,
) )
ioutil.WriteFile( afero.WriteFile(
s.path("hours"), s.fs,
"hours",
[]byte( []byte(
"# state/hours: when the contest is enabled\n"+ "# state/hours: when the contest is enabled\n"+
"# Lines starting with + enable, with - disable.\n"+ "# Lines starting with + enable, with - disable.\n"+
@ -339,27 +339,31 @@ func (s *State) maybeInitialize() {
), ),
0644, 0644,
) )
ioutil.WriteFile( afero.WriteFile(
s.path("messages.txt"), s.fs,
"messages.txt",
[]byte(fmt.Sprintf("[%s] Initialized.\n", time.Now().UTC().Format(time.RFC3339))), []byte(fmt.Sprintf("[%s] Initialized.\n", time.Now().UTC().Format(time.RFC3339))),
0644, 0644,
) )
ioutil.WriteFile( afero.WriteFile(
s.path("points.log"), s.fs,
"points.log",
[]byte(""), []byte(""),
0644, 0644,
) )
} }
func (s *State) Cleanup() {
s.maybeInitialize()
s.UpdateEnabled()
if s.Enabled {
s.collectPoints()
}
}
func (s *State) Run(updateInterval time.Duration) { func (s *State) Run(updateInterval time.Duration) {
for { for {
s.maybeInitialize() s.Cleanup()
s.UpdateEnabled()
if s.Enabled {
s.collectPoints()
}
// Wait for something to happen
select { select {
case <-s.update: case <-s.update:
case <-time.After(updateInterval): case <-time.After(updateInterval):

25
cmd/mothd/state_test.go Normal file
View File

@ -0,0 +1,25 @@
package main
import (
"github.com/spf13/afero"
"os"
"testing"
)
func TestState(t *testing.T) {
fs := new(afero.MemMapFs)
mustExist := func(path string) {
_, err := fs.Stat(path)
if os.IsNotExist(err) {
t.Errorf("File %s does not exist", path)
}
}
s := NewState(fs)
s.Cleanup()
mustExist("initialized")
mustExist("enabled")
mustExist("hours")
}