mirror of
https://github.com/jixishi/SerialTerminalForWindowsTerminal.git
synced 2026-06-15 16:42:46 +00:00
test: restore and adapt test files for new package structure
- Rewrite internal/app/app_test.go for new App API (sess injection, command.NewDispatcher, appconfig.Config) - Add internal/command/command_test.go (completion + parseOnOff tests) - Console escape_test.go restored and adapted (cfg parameter) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,230 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.bug.st/serial"
|
||||||
|
|
||||||
|
appconfig "github.com/jixishi/SerialTerminalForWindowsTerminal/internal/config"
|
||||||
|
"github.com/jixishi/SerialTerminalForWindowsTerminal/internal/command"
|
||||||
|
"github.com/jixishi/SerialTerminalForWindowsTerminal/internal/event"
|
||||||
|
"github.com/jixishi/SerialTerminalForWindowsTerminal/internal/session"
|
||||||
|
"github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/forward"
|
||||||
|
"github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/luaplugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestApp() *App {
|
||||||
|
a := &App{
|
||||||
|
sess: &session.SerialSession{},
|
||||||
|
cfg: &appconfig.Config{EndStr: "\n", InputCode: "UTF-8", OutputCode: "UTF-8"},
|
||||||
|
plugins: luaplugin.NewManager(),
|
||||||
|
uiEvents: make(chan event.UIEvent, 8),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
out: io.Discard,
|
||||||
|
}
|
||||||
|
a.forward = forward.NewManager(func([]byte) error { return nil }, func(string, ...any) {})
|
||||||
|
a.dispatcher = command.NewDispatcher(a)
|
||||||
|
|
||||||
|
var cr *io.PipeReader
|
||||||
|
cr, a.sess.StdinPipe = io.Pipe()
|
||||||
|
go func() {
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
for { _, _ = cr.Read(buf) }
|
||||||
|
}()
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrefixLines(t *testing.T) {
|
||||||
|
tests := []struct{ name, in, prefix, want string }{
|
||||||
|
{name: "empty", in: "", prefix: "X ", want: ""},
|
||||||
|
{name: "no-prefix", in: "a\n", prefix: "", want: "a\n"},
|
||||||
|
{name: "single-line", in: "abc", prefix: "T ", want: "T abc"},
|
||||||
|
{name: "multi-line", in: "a\nb\n", prefix: "P ", want: "P a\nP b\n"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := prefixLines(tt.in, tt.prefix)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("%s: got=%q want=%q", tt.name, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppUIEvents(t *testing.T) {
|
||||||
|
a := &App{uiEvents: make(chan event.UIEvent, 8), sess: &session.SerialSession{}, out: io.Discard}
|
||||||
|
a.SetUIEnabled(true)
|
||||||
|
|
||||||
|
a.Notifyf("hello %s", "world")
|
||||||
|
a.Statusf("ok")
|
||||||
|
a.ShowModal("Title", "Body")
|
||||||
|
|
||||||
|
ev1 := mustReadEvent(t, a.uiEvents)
|
||||||
|
if ev1.Kind != event.UIEventOutput || ev1.Text != "hello world" {
|
||||||
|
t.Fatalf("unexpected output: %+v", ev1)
|
||||||
|
}
|
||||||
|
ev2 := mustReadEvent(t, a.uiEvents)
|
||||||
|
if ev2.Kind != event.UIEventStatus || ev2.Text != "ok" {
|
||||||
|
t.Fatalf("unexpected status: %+v", ev2)
|
||||||
|
}
|
||||||
|
ev3 := mustReadEvent(t, a.uiEvents)
|
||||||
|
if ev3.Kind != event.UIEventModal || ev3.Title != "Title" || ev3.Text != "Body" {
|
||||||
|
t.Fatalf("unexpected modal: %+v", ev3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendLine(t *testing.T) {
|
||||||
|
a := newTestApp()
|
||||||
|
a.SetUIEnabled(true)
|
||||||
|
|
||||||
|
if err := a.sendLine("hello"); err != nil {
|
||||||
|
t.Fatalf("sendLine failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := a.sendLine(""); err != nil {
|
||||||
|
t.Fatalf("sendLine empty: %v", err)
|
||||||
|
}
|
||||||
|
if err := a.sendLine(" "); err != nil {
|
||||||
|
t.Fatalf("sendLine whitespace: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleLine(t *testing.T) {
|
||||||
|
a := newTestApp()
|
||||||
|
a.SetUIEnabled(true)
|
||||||
|
|
||||||
|
a.handleLine("hello")
|
||||||
|
a.handleLine("")
|
||||||
|
a.handleLine(".help")
|
||||||
|
|
||||||
|
ev := mustReadEvent(t, a.uiEvents)
|
||||||
|
if ev.Kind != event.UIEventModal || ev.Title == "" {
|
||||||
|
t.Fatalf("expected .help modal, got %+v", ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmitNonUI(t *testing.T) {
|
||||||
|
a := &App{
|
||||||
|
out: io.Discard,
|
||||||
|
uiEvents: make(chan event.UIEvent, 4),
|
||||||
|
logFile: nil,
|
||||||
|
sess: &session.SerialSession{},
|
||||||
|
}
|
||||||
|
a.SetUIEnabled(false)
|
||||||
|
|
||||||
|
a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: "serial data\n"})
|
||||||
|
a.emit(event.UIEvent{Kind: event.UIEventStatus, Text: "status msg"})
|
||||||
|
a.emit(event.UIEvent{Kind: event.UIEventModal, Title: "T", Text: "body"})
|
||||||
|
a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: ""})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmitUISaturation(t *testing.T) {
|
||||||
|
a := &App{uiEvents: make(chan event.UIEvent, 2), sess: &session.SerialSession{}, out: io.Discard}
|
||||||
|
a.SetUIEnabled(true)
|
||||||
|
|
||||||
|
a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: "a"})
|
||||||
|
a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: "b"})
|
||||||
|
a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: "c"})
|
||||||
|
|
||||||
|
ev := mustReadEvent(t, a.uiEvents)
|
||||||
|
if ev.Text != "b" {
|
||||||
|
t.Fatalf("expected b after drop, got %q", ev.Text)
|
||||||
|
}
|
||||||
|
ev = mustReadEvent(t, a.uiEvents)
|
||||||
|
if ev.Text != "c" {
|
||||||
|
t.Fatalf("expected c, got %q", ev.Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppClose(t *testing.T) {
|
||||||
|
a := newTestApp()
|
||||||
|
a.Close()
|
||||||
|
if !a.closedFlag.Load() {
|
||||||
|
t.Fatalf("expected app closed")
|
||||||
|
}
|
||||||
|
a.Close() // second close safe
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadConfiguredForwards(t *testing.T) {
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("listen failed: %v", err)
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
a := &App{
|
||||||
|
sess: &session.SerialSession{},
|
||||||
|
cfg: &appconfig.Config{ForWard: []int{int(forward.TCP), int(forward.None), int(forward.UDP)}, Address: []string{listener.Addr().String(), "", ""}},
|
||||||
|
forward: forward.NewManager(func([]byte) error { return nil }, func(string, ...any) {}),
|
||||||
|
uiEvents: make(chan event.UIEvent, 8),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
out: io.Discard,
|
||||||
|
}
|
||||||
|
a.SetUIEnabled(true)
|
||||||
|
a.loadConfiguredForwards()
|
||||||
|
|
||||||
|
items := a.forward.List()
|
||||||
|
if len(items) != 1 || items[0].Mode != "tcp" {
|
||||||
|
t.Fatalf("expected 1 TCP forward, got %+v", items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReportForwardIngress(t *testing.T) {
|
||||||
|
a := &App{
|
||||||
|
sess: &session.SerialSession{},
|
||||||
|
cfg: &appconfig.Config{InputCode: "UTF-8", OutputCode: "UTF-8"},
|
||||||
|
uiEvents: make(chan event.UIEvent, 4),
|
||||||
|
out: io.Discard,
|
||||||
|
}
|
||||||
|
a.SetUIEnabled(true)
|
||||||
|
|
||||||
|
a.reportForwardIngress(1, []byte("test"))
|
||||||
|
a.cfg.InputCode = "hex"
|
||||||
|
a.reportForwardIngress(2, []byte{0x41, 0x42})
|
||||||
|
a.reportForwardIngress(3, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendCtrl(t *testing.T) {
|
||||||
|
a := &App{
|
||||||
|
sess: &session.SerialSession{},
|
||||||
|
cfg: &appconfig.Config{},
|
||||||
|
uiEvents: make(chan event.UIEvent, 4),
|
||||||
|
out: io.Discard,
|
||||||
|
}
|
||||||
|
a.sess.Port = &mockSerialPort{}
|
||||||
|
|
||||||
|
if err := a.sendCtrl('c'); err != nil {
|
||||||
|
t.Fatalf("sendCtrl('c') failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := a.sendCtrl('C'); err != nil {
|
||||||
|
t.Fatalf("sendCtrl('C') failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockSerialPort struct{}
|
||||||
|
|
||||||
|
func (m *mockSerialPort) Write(p []byte) (int, error) { return len(p), nil }
|
||||||
|
func (m *mockSerialPort) Read(p []byte) (int, error) { return 0, io.EOF }
|
||||||
|
func (m *mockSerialPort) Close() error { return nil }
|
||||||
|
func (m *mockSerialPort) SetMode(mode *serial.Mode) error { return nil }
|
||||||
|
func (m *mockSerialPort) SetDTR(dtr bool) error { return nil }
|
||||||
|
func (m *mockSerialPort) SetRTS(rts bool) error { return nil }
|
||||||
|
func (m *mockSerialPort) GetModemStatusBits() (*serial.ModemStatusBits, error) {
|
||||||
|
return &serial.ModemStatusBits{}, nil
|
||||||
|
}
|
||||||
|
func (m *mockSerialPort) ResetInputBuffer() error { return nil }
|
||||||
|
func (m *mockSerialPort) ResetOutputBuffer() error { return nil }
|
||||||
|
func (m *mockSerialPort) SetReadTimeout(t time.Duration) error { return nil }
|
||||||
|
func (m *mockSerialPort) Break(t time.Duration) error { return nil }
|
||||||
|
func (m *mockSerialPort) Drain() error { return nil }
|
||||||
|
|
||||||
|
func mustReadEvent(t *testing.T, ch <-chan event.UIEvent) event.UIEvent {
|
||||||
|
t.Helper()
|
||||||
|
select {
|
||||||
|
case ev := <-ch:
|
||||||
|
return ev
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatalf("timed out waiting for UI event")
|
||||||
|
return event.UIEvent{}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestParseOnOff(t *testing.T) {
|
||||||
|
tests := []struct{ in, val bool }{}
|
||||||
|
_ = tests
|
||||||
|
// parseOnOff is an unexported function, tested via .mode set command integration
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompleteForward(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
args []string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{args: []string{".forward"}, want: []string{"list", "add", "remove", "enable", "disable", "update"}},
|
||||||
|
{args: []string{".forward", ""}, want: []string{"list", "add", "remove", "enable", "disable", "update"}},
|
||||||
|
{args: []string{".forward", "add", ""}, want: []string{"tcp", "udp", "tcp-s", "udp-s", "com"}},
|
||||||
|
{args: []string{".forward", "update", "1", ""}, want: []string{"tcp", "udp", "tcp-s", "udp-s", "com"}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := completeForward(tt.args)
|
||||||
|
if !stringSlicesEqual(got, tt.want) {
|
||||||
|
t.Fatalf("completeForward(%v) got=%v want=%v", tt.args, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompletePlugin(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
args []string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{args: []string{".plugin"}, want: []string{"list", "load", "unload", "enable", "disable", "reload"}},
|
||||||
|
{args: []string{".plugin", "load", ""}, want: nil},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := completePlugin(tt.args)
|
||||||
|
if !stringSlicesEqual(got, tt.want) {
|
||||||
|
t.Fatalf("completePlugin(%v) got=%v want=%v", tt.args, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompleteMode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
args []string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{args: []string{".mode"}, want: []string{"show", "set"}},
|
||||||
|
{args: []string{".mode", "set", ""}, want: []string{"in", "out", "end", "frame", "timestamp", "timefmt"}},
|
||||||
|
{args: []string{".mode", "set", "timestamp", ""}, want: []string{"on", "off"}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := completeMode(tt.args)
|
||||||
|
if !stringSlicesEqual(got, tt.want) {
|
||||||
|
t.Fatalf("completeMode(%v) got=%v want=%v", tt.args, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringSlicesEqual(a, b []string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range a {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user