314 lines
6.2 KiB
Go
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")
|
|
}
|
|
}
|