ACN state handling & tests
continuous-integration/drone/pr Build is failing Details
continuous-integration/drone/push Build is failing Details

This commit is contained in:
decentral1se 2023-10-01 15:32:50 +02:00
parent c453bd831c
commit ad640636c3
Signed by: decentral1se
GPG Key ID: 03789458B3D0C410
5 changed files with 357 additions and 54 deletions

27
acn.go
View File

@ -35,17 +35,6 @@ func isConnected(m model) (bool, tea.Msg) {
}
func turnAcnOn(m *model) tea.Msg {
if ok, msg := isConnected(*m); !ok {
switch msg.(type) {
case acnConnectingMsg:
return msg
case acnAlreadyOnlineMsg:
return msg
}
}
m.acnState = connecting
mrand.New(mrand.NewSource(int64(time.Now().Nanosecond())))
port := mrand.Intn(1000) + 9600
controlPort := port + 1
@ -113,22 +102,10 @@ func turnAcnOn(m *model) tea.Msg {
}
func turnAcnOff(m *model, quitProgram bool) tea.Msg {
switch m.acnState {
case offline:
if !quitProgram {
return acnOfflineMsg{}
}
case connecting:
return acnConnectingMsg{}
}
if m.app != nil {
if m.acnState != offline {
logToFile(*m, "Shutting down application and ACN now")
m.app.Shutdown()
}
if m.acn != nil {
m.acn.Close()
}
return acnOffMsg{quitProgram: quitProgram}
}

10
logger.go Normal file
View File

@ -0,0 +1,10 @@
package main
import "log"
// LogToFile logs a message to a log file.
func logToFile(m model, msg string) {
if m.debug {
log.Printf("debug: %s", msg)
}
}

320
main_test.go Normal file
View File

@ -0,0 +1,320 @@
package main
import (
"bytes"
"os"
"os/user"
"testing"
"time"
openPrivacyLog "git.openprivacy.ca/openprivacy/log"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/x/exp/teatest"
)
const (
cairdeTestLogFile = "cairde-test.log"
cwtchTestLogFile = "cwtch-test.log"
)
var currentUser *user.User
var cairdeLogHandler *os.File
var cwtchLogHandler *openPrivacyLog.Logger
func TestMain(m *testing.M) {
defer setupAndTeardown()()
m.Run()
}
func setupAndTeardown() func() {
_, err := tea.LogToFile(cairdeTestLogFile, "debug")
if err != nil {
panic(err)
}
cairdeLogHandler, err = os.OpenFile(cairdeTestLogFile, os.O_RDONLY, 0644)
if err != nil {
panic(err)
}
cwtchLogHandler, err = openPrivacyLog.NewFile(openPrivacyLog.LevelDebug, cwtchTestLogFile)
if err == nil {
openPrivacyLog.SetStd(cwtchLogHandler)
}
currentUser, err = user.Current()
if err != nil {
panic(err)
}
return func() {
if err := cairdeLogHandler.Close(); err != nil {
panic(err)
}
if err := os.Remove(cairdeTestLogFile); err != nil {
panic(err)
}
if err := os.Remove(cwtchTestLogFile); err != nil {
panic(err)
}
}
}
func TestOnOff(t *testing.T) {
m := newModel(currentUser.Username, currentUser.HomeDir, true)
testModel := teatest.NewTestModel(t, m)
testModel.Send(tea.KeyMsg{
Type: tea.KeyRunes,
Runes: []rune("ctrl+c"),
})
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
}
func TestAcnOnOff(t *testing.T) {
m := newModel(currentUser.Username, currentUser.HomeDir, true)
testModel := teatest.NewTestModel(t, m)
testModel.Type("/acn on")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("ACN is up and running, all engines go"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*10),
)
testModel.Type("/acn off")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("ACN successfully turned off"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*3),
)
testModel.Send(tea.KeyMsg{
Type: tea.KeyRunes,
Runes: []rune("ctrl+c"),
})
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
}
func TestAcnOnThenQuit(t *testing.T) {
m := newModel(currentUser.Username, currentUser.HomeDir, true)
testModel := teatest.NewTestModel(t, m)
testModel.Type("/acn on")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("ACN is up and running, all engines go"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*10),
)
testModel.Type("/quit")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("Shutting down application and ACN now"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*3),
)
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
}
func TestAcnOnThenOnAsksToHold(t *testing.T) {
m := newModel(currentUser.Username, currentUser.HomeDir, true)
testModel := teatest.NewTestModel(t, m)
testModel.Type("/acn on")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("Initialising Tor ACN"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*10),
)
testModel.Type("/acn on")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("ACN still initialising, please hold"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*3),
)
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("ACN is up and running, all engines go"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*10),
)
testModel.Type("/quit")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("Shutting down application and ACN now"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*3),
)
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
}
func TestOnThenOffAsksToWait(t *testing.T) {
m := newModel(currentUser.Username, currentUser.HomeDir, true)
testModel := teatest.NewTestModel(t, m)
testModel.Type("/acn on")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("Initialising Tor ACN..."))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*10),
)
testModel.Type("/acn on")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("ACN still initialising, please hold"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*3),
)
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("ACN is up and running, all engines go"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*10),
)
testModel.Type("/quit")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("Shutting down application and ACN now"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*3),
)
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
}
func TestAcnOnThenQuitAsksToWait(t *testing.T) {
m := newModel(currentUser.Username, currentUser.HomeDir, true)
testModel := teatest.NewTestModel(t, m)
testModel.Type("/acn on")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("Initialising Tor ACN..."))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*10),
)
testModel.Type("/quit")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("ACN still initialising, please hold"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*3),
)
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("ACN is up and running, all engines go"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*10),
)
testModel.Type("/quit")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("Shutting down application and ACN now"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*3),
)
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
}
func TestAcnOffIsOff(t *testing.T) {
m := newModel(currentUser.Username, currentUser.HomeDir, true)
testModel := teatest.NewTestModel(t, m)
testModel.Type("/acn off")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
teatest.WaitFor(
t, cairdeLogHandler,
func(bts []byte) bool {
return bytes.Contains(bts, []byte("ACN is currently offline"))
},
teatest.WithCheckInterval(time.Millisecond*100),
teatest.WithDuration(time.Second*10),
)
testModel.Type("/quit")
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
}

View File

@ -133,6 +133,7 @@ func (m model) sendStatusCmd(lines ...string) tea.Cmd {
// we want to render messages to the status buffer but still have work to do.
func (m model) sendStatus(lines ...string) {
for _, line := range lines {
logToFile(m, line)
m.statusBuffer <- line
}
}
@ -263,6 +264,17 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds = append(cmds, m.sendStatusCmd(msg.output...))
case turnAcnOffMsg:
switch m.acnState {
case offline:
if !msg.quitProgram {
cmds = append(cmds, func() tea.Msg { return acnOfflineMsg{} })
return m, tea.Batch(cmds...)
}
case connecting:
cmds = append(cmds, func() tea.Msg { return acnConnectingMsg{} })
return m, tea.Batch(cmds...)
}
m.input.Reset()
cmds = append(cmds, func() tea.Msg {
return turnAcnOff(&m, msg.quitProgram)
@ -276,9 +288,19 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.acnState = offline
case turnAcnOnMsg:
// TODO: stop if we're already connecting!
// state management is getting a little tricky :/
switch m.acnState {
case connecting:
cmds = append(cmds, func() tea.Msg { return acnConnectingMsg{} })
return m, tea.Batch(cmds...)
case connected:
cmds = append(cmds, func() tea.Msg { return acnAlreadyOnlineMsg{} })
return m, tea.Batch(cmds...)
}
m.acnState = connecting
m.input.Reset()
cmds = append(cmds, func() tea.Msg {
return turnAcnOn(&m)
})
@ -390,6 +412,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case sendStatusMsg:
cmds = append(cmds, func() tea.Msg {
for _, line := range msg.lines {
logToFile(m, line)
m.statusBuffer <- line
}
return nil

View File

@ -1,27 +0,0 @@
package main
import (
"os/user"
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/x/exp/teatest"
)
func TestCairdeOnOffWorks(t *testing.T) {
user, err := user.Current()
if err != nil {
t.Fatal(err)
}
m := newModel(user.Username, user.HomeDir, false)
tm := teatest.NewTestModel(t, m)
tm.Send(tea.KeyMsg{
Type: tea.KeyRunes,
Runes: []rune("ctrl+c"),
})
tm.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
}