rs2322tcp/internal/server/data_connection_test.go
2026-08-10 10:38:14 +02:00

314 lines
6.2 KiB
Go

/*
* ============================================================================
* Projekt.....: rs2322tcp
* Datei.......: data_connection_test.go
* Copyright (C) 2026 Dieter Lang
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Beschreibung:
* Tests für die bidirektionale TCP-/RS232-Datenverbindung.
* ============================================================================
*/
package server_test
import (
"bytes"
"io"
"net"
"sync"
"testing"
"time"
"git.lang-dieter.de/rs2322tcp/internal/server"
)
///////////////////////////////////////////////////////////////////////////////
// Test serial connection
///////////////////////////////////////////////////////////////////////////////
type testSerialConnection struct {
reader *bytes.Reader
writer bytes.Buffer
mu sync.Mutex
closed bool
closeCh chan struct{}
closeOnce sync.Once
}
func newTestSerialConnection(data []byte) *testSerialConnection {
return &testSerialConnection{
reader: bytes.NewReader(data),
closeCh: make(chan struct{}),
}
}
func (s *testSerialConnection) 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
}
// A real serial connection normally waits for further data instead
// of immediately returning EOF. Block until the connection is closed.
<-s.closeCh
return 0, io.ErrClosedPipe
}
func (s *testSerialConnection) 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 *testSerialConnection) Close() error {
s.closeOnce.Do(func() {
s.mu.Lock()
s.closed = true
s.mu.Unlock()
close(s.closeCh)
})
return nil
}
func (s *testSerialConnection) WrittenData() []byte {
s.mu.Lock()
defer s.mu.Unlock()
data := make([]byte, s.writer.Len())
copy(data, s.writer.Bytes())
return data
}
///////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////
func TestNewDataConnection(t *testing.T) {
tcpServer, tcpClient := net.Pipe()
defer tcpServer.Close()
defer tcpClient.Close()
serial := newTestSerialConnection(nil)
connection, err := server.NewDataConnection(
tcpServer,
serial,
)
if err != nil {
t.Fatalf("NewDataConnection() failed: %v", err)
}
if connection.TCPConn() == nil {
t.Fatal("TCPConn() returned nil")
}
if connection.SerialConn() == nil {
t.Fatal("SerialConn() returned nil")
}
}
func TestNewDataConnectionRejectsNilTCP(t *testing.T) {
serial := newTestSerialConnection(nil)
connection, err := server.NewDataConnection(
nil,
serial,
)
if err == nil {
t.Fatal("NewDataConnection() succeeded, want error")
}
if connection != nil {
t.Fatal("NewDataConnection() returned connection despite error")
}
}
func TestNewDataConnectionRejectsNilSerial(t *testing.T) {
tcpServer, tcpClient := net.Pipe()
defer tcpServer.Close()
defer tcpClient.Close()
connection, err := server.NewDataConnection(
tcpServer,
nil,
)
if err == nil {
t.Fatal("NewDataConnection() succeeded, want error")
}
if connection != nil {
t.Fatal("NewDataConnection() returned connection despite error")
}
}
///////////////////////////////////////////////////////////////////////////////
// TCP -> Serial
///////////////////////////////////////////////////////////////////////////////
func TestDataConnectionTCPToSerial(t *testing.T) {
tcpServer, tcpClient := net.Pipe()
defer tcpClient.Close()
serial := newTestSerialConnection(nil)
connection, err := server.NewDataConnection(
tcpServer,
serial,
)
if err != nil {
t.Fatalf("NewDataConnection() failed: %v", err)
}
done := make(chan error, 1)
go func() {
done <- connection.Run()
}()
testData := []byte("hello serial")
if _, err := tcpClient.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)
}
_ = tcpClient.Close()
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("DataConnection.Run() did not terminate")
}
}
///////////////////////////////////////////////////////////////////////////////
// Serial -> TCP
///////////////////////////////////////////////////////////////////////////////
func TestDataConnectionSerialToTCP(t *testing.T) {
tcpServer, tcpClient := net.Pipe()
defer tcpClient.Close()
testData := []byte("hello tcp")
serial := newTestSerialConnection(testData)
connection, err := server.NewDataConnection(
tcpServer,
serial,
)
if err != nil {
t.Fatalf("NewDataConnection() failed: %v", err)
}
done := make(chan error, 1)
go func() {
done <- connection.Run()
}()
buffer := make([]byte, len(testData))
if _, err := io.ReadFull(tcpClient, buffer); err != nil {
t.Fatalf("TCP Read() failed: %v", err)
}
if !bytes.Equal(buffer, testData) {
t.Fatalf(
"TCP data = %q, want %q",
buffer,
testData,
)
}
_ = tcpClient.Close()
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("DataConnection.Run() did not terminate")
}
}
///////////////////////////////////////////////////////////////////////////////
// Close
///////////////////////////////////////////////////////////////////////////////
func TestDataConnectionClose(t *testing.T) {
tcpServer, tcpClient := net.Pipe()
defer tcpClient.Close()
serial := newTestSerialConnection(nil)
connection, err := server.NewDataConnection(
tcpServer,
serial,
)
if err != nil {
t.Fatalf("NewDataConnection() failed: %v", err)
}
if err := connection.Close(); err != nil {
t.Fatalf("Close() failed: %v", err)
}
if err := connection.Close(); err != nil {
t.Fatalf("second Close() failed: %v", err)
}
buffer := make([]byte, 1)
if _, err := tcpClient.Read(buffer); err == nil {
t.Fatal("TCP connection is still open")
}
serial.mu.Lock()
closed := serial.closed
serial.mu.Unlock()
if !closed {
t.Fatal("serial connection is still open")
}
}