rs2322tcp/internal/server/data_handler_test.go
2026-08-11 15:19:36 +02:00

362 lines
7.4 KiB
Go

/*
* ============================================================================
* Projekt.....: rs2322tcp
* Datei.......: data_handler_test.go
* Copyright (C) 2026 Dieter Lang
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Beschreibung:
* Tests für die Verbindung von DataListener, TCP-Datenverbindung und
* serieller Geräteverbindung.
* ============================================================================
*/
package server_test
import (
"bytes"
"io"
"net"
"sync"
"testing"
"time"
"git.lang-dieter.de/rs2322tcp/internal/server"
)
///////////////////////////////////////////////////////////////////////////////
// Test constants
///////////////////////////////////////////////////////////////////////////////
const testHardwareErrorResponse = "TEST ERROR RESPONSE"
///////////////////////////////////////////////////////////////////////////////
// Test serial connection
///////////////////////////////////////////////////////////////////////////////
type handlerTestSerial struct {
reader *bytes.Reader
mu sync.Mutex
writer bytes.Buffer
closed bool
closeCh chan struct{}
closeOnce sync.Once
}
func newHandlerTestSerial(data []byte) *handlerTestSerial {
return &handlerTestSerial{
reader: bytes.NewReader(data),
closeCh: make(chan struct{}),
}
}
func (s *handlerTestSerial) Read(p []byte) (int, error) {
s.mu.Lock()
if s.closed {
s.mu.Unlock()
return 0, io.ErrClosedPipe
}
n, err := s.reader.Read(p)
s.mu.Unlock()
if err != io.EOF {
return n, err
}
<-s.closeCh
return 0, io.ErrClosedPipe
}
func (s *handlerTestSerial) Write(p []byte) (int, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return 0, io.ErrClosedPipe
}
return s.writer.Write(p)
}
func (s *handlerTestSerial) Close() error {
s.closeOnce.Do(func() {
s.mu.Lock()
s.closed = true
s.mu.Unlock()
close(s.closeCh)
})
return nil
}
func (s *handlerTestSerial) WrittenData() []byte {
s.mu.Lock()
defer s.mu.Unlock()
data := make([]byte, s.writer.Len())
copy(data, s.writer.Bytes())
return data
}
///////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////
func TestNewDataHandler(t *testing.T) {
listener, err := server.NewDataListener()
if err != nil {
t.Fatalf("NewDataListener() failed: %v", err)
}
defer listener.Close()
serial := newHandlerTestSerial(nil)
handler, err := server.NewDataHandler(
listener,
func() (io.ReadWriteCloser, error) {
return serial, nil
},
testHardwareErrorResponse,
)
if err != nil {
t.Fatalf("NewDataHandler() failed: %v", err)
}
if handler == nil {
t.Fatal("NewDataHandler() returned nil")
}
}
func TestNewDataHandlerRejectsNilListener(t *testing.T) {
handler, err := server.NewDataHandler(
nil,
func() (io.ReadWriteCloser, error) {
return newHandlerTestSerial(nil), nil
},
testHardwareErrorResponse,
)
if err == nil {
t.Fatal("NewDataHandler() succeeded, want error")
}
if handler != nil {
t.Fatal("NewDataHandler() returned handler despite error")
}
}
func TestNewDataHandlerRejectsNilFactory(t *testing.T) {
listener, err := server.NewDataListener()
if err != nil {
t.Fatalf("NewDataListener() failed: %v", err)
}
defer listener.Close()
handler, err := server.NewDataHandler(
listener,
nil,
testHardwareErrorResponse,
)
if err == nil {
t.Fatal("NewDataHandler() succeeded, want error")
}
if handler != nil {
t.Fatal("NewDataHandler() returned handler despite error")
}
}
///////////////////////////////////////////////////////////////////////////////
// TCP -> Serial
///////////////////////////////////////////////////////////////////////////////
func TestDataHandlerTCPToSerial(t *testing.T) {
listener, err := server.NewDataListener()
if err != nil {
t.Fatalf("NewDataListener() failed: %v", err)
}
defer listener.Close()
serial := newHandlerTestSerial(nil)
handler, err := server.NewDataHandler(
listener,
func() (io.ReadWriteCloser, error) {
return serial, nil
},
testHardwareErrorResponse,
)
if err != nil {
t.Fatalf("NewDataHandler() failed: %v", err)
}
serveDone := make(chan error, 1)
go func() {
serveDone <- handler.Serve()
}()
tcpConn, err := net.Dial("tcp", listener.Addr().String())
if err != nil {
t.Fatalf("net.Dial() failed: %v", err)
}
testData := []byte("handler tcp to serial")
if _, err := tcpConn.Write(testData); err != nil {
t.Fatalf("TCP Write() failed: %v", err)
}
deadline := time.Now().Add(time.Second)
for {
if bytes.Equal(serial.WrittenData(), testData) {
break
}
if time.Now().After(deadline) {
t.Fatalf(
"serial data = %q, want %q",
serial.WrittenData(),
testData,
)
}
time.Sleep(time.Millisecond)
}
_ = tcpConn.Close()
_ = listener.Close()
select {
case <-serveDone:
case <-time.After(time.Second):
t.Fatal("DataHandler.Serve() did not terminate")
}
}
///////////////////////////////////////////////////////////////////////////////
// Serial -> TCP
///////////////////////////////////////////////////////////////////////////////
func TestDataHandlerSerialToTCP(t *testing.T) {
listener, err := server.NewDataListener()
if err != nil {
t.Fatalf("NewDataListener() failed: %v", err)
}
defer listener.Close()
testData := []byte("handler serial to tcp")
serial := newHandlerTestSerial(testData)
handler, err := server.NewDataHandler(
listener,
func() (io.ReadWriteCloser, error) {
return serial, nil
},
testHardwareErrorResponse,
)
if err != nil {
t.Fatalf("NewDataHandler() failed: %v", err)
}
serveDone := make(chan error, 1)
go func() {
serveDone <- handler.Serve()
}()
tcpConn, err := net.Dial("tcp", listener.Addr().String())
if err != nil {
t.Fatalf("net.Dial() failed: %v", err)
}
buffer := make([]byte, len(testData))
if _, err := io.ReadFull(tcpConn, buffer); err != nil {
t.Fatalf("TCP Read() failed: %v", err)
}
if !bytes.Equal(buffer, testData) {
t.Fatalf(
"TCP data = %q, want %q",
buffer,
testData,
)
}
_ = tcpConn.Close()
_ = listener.Close()
select {
case <-serveDone:
case <-time.After(time.Second):
t.Fatal("DataHandler.Serve() did not terminate")
}
}
///////////////////////////////////////////////////////////////////////////////
// Serial factory error
///////////////////////////////////////////////////////////////////////////////
func TestDataHandlerSerialFactoryError(t *testing.T) {
listener, err := server.NewDataListener()
if err != nil {
t.Fatalf("NewDataListener() failed: %v", err)
}
defer listener.Close()
handler, err := server.NewDataHandler(
listener,
func() (io.ReadWriteCloser, error) {
return nil, io.ErrClosedPipe
},
testHardwareErrorResponse,
)
if err != nil {
t.Fatalf("NewDataHandler() failed: %v", err)
}
serveDone := make(chan error, 1)
go func() {
serveDone <- handler.Serve()
}()
tcpConn, err := net.Dial("tcp", listener.Addr().String())
if err != nil {
t.Fatalf("net.Dial() failed: %v", err)
}
// The handler must close the TCP connection when the serial factory
// fails.
buffer := make([]byte, 1)
_ = tcpConn.SetReadDeadline(time.Now().Add(time.Second))
_, readErr := tcpConn.Read(buffer)
if readErr == nil {
t.Fatal("TCP connection remained open after serial factory error")
}
_ = tcpConn.Close()
_ = listener.Close()
select {
case <-serveDone:
case <-time.After(time.Second):
t.Fatal("DataHandler.Serve() did not terminate")
}
}