Hardwarefehler an Client zurückmelden
This commit is contained in:
parent
a33546fbed
commit
1621ed4330
12 changed files with 247 additions and 22 deletions
|
|
@ -3,6 +3,9 @@
|
|||
"address": "0.0.0.0",
|
||||
"port": 5000
|
||||
},
|
||||
|
||||
"hardware_error_response": "ERROR - HARDWARE NOT AVAILABLE",
|
||||
|
||||
"devices": [
|
||||
{
|
||||
"id": "radio",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,16 @@ import (
|
|||
"git.lang-dieter.de/rs2322tcp/internal/server"
|
||||
)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Test configuration
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const testHardwareErrorResponse = "TEST ERROR RESPONSE"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Virtual serial pair
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func startVirtualSerialPair(t *testing.T) (string, string, func()) {
|
||||
t.Helper()
|
||||
|
||||
|
|
@ -79,6 +89,10 @@ func startVirtualSerialPair(t *testing.T) (string, string, func()) {
|
|||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Read helper
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func readExactWithTimeout(
|
||||
t *testing.T,
|
||||
reader io.Reader,
|
||||
|
|
@ -105,6 +119,10 @@ func readExactWithTimeout(
|
|||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Integration test
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func TestClientServerSerialIntegration(t *testing.T) {
|
||||
serialA, serialB, cleanup := startVirtualSerialPair(t)
|
||||
defer cleanup()
|
||||
|
|
@ -130,6 +148,7 @@ func TestClientServerSerialIntegration(t *testing.T) {
|
|||
Address: "127.0.0.1",
|
||||
Port: controlPort,
|
||||
},
|
||||
HardwareErrorResponse: testHardwareErrorResponse,
|
||||
Devices: []config.DeviceConfig{
|
||||
{
|
||||
ID: "radio",
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ func TestClientRuntimeSerialIntegration(t *testing.T) {
|
|||
Address: "127.0.0.1",
|
||||
Port: controlPort,
|
||||
},
|
||||
HardwareErrorResponse: testHardwareErrorResponse,
|
||||
Devices: []config.DeviceConfig{
|
||||
{
|
||||
ID: "radio",
|
||||
|
|
|
|||
|
|
@ -23,8 +23,9 @@ import (
|
|||
|
||||
// ServerConfig contains the complete server configuration.
|
||||
type ServerConfig struct {
|
||||
Listen ListenConfig `json:"listen"`
|
||||
Devices []DeviceConfig `json:"devices"`
|
||||
Listen ListenConfig `json:"listen"`
|
||||
HardwareErrorResponse string `json:"hardware_error_response"`
|
||||
Devices []DeviceConfig `json:"devices"`
|
||||
}
|
||||
|
||||
// ListenConfig contains the network listener configuration.
|
||||
|
|
@ -88,6 +89,9 @@ const (
|
|||
// DefaultVirtualPortLast is the last virtual USB serial port number
|
||||
// used when no virtual port range is specified.
|
||||
DefaultVirtualPortLast = 199
|
||||
|
||||
// Fehlermeldung wenn Hardware nicht erreichbar ist
|
||||
DefaultHardwareErrorResponse = "ERROR - HARDWARE NOT AVAILABLE"
|
||||
)
|
||||
|
||||
// DefaultVirtualPortRange returns the default virtual port range.
|
||||
|
|
@ -110,6 +114,10 @@ func LoadServer(filename string) (*ServerConfig, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if cfg.HardwareErrorResponse == "" {
|
||||
cfg.HardwareErrorResponse = DefaultHardwareErrorResponse
|
||||
}
|
||||
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,14 @@ func TestLoadServer(t *testing.T) {
|
|||
t.Fatalf("LoadServer() failed: %v", err)
|
||||
}
|
||||
|
||||
if cfg.HardwareErrorResponse != config.DefaultHardwareErrorResponse {
|
||||
t.Errorf(
|
||||
"HardwareErrorResponse = %q, want %q",
|
||||
cfg.HardwareErrorResponse,
|
||||
config.DefaultHardwareErrorResponse,
|
||||
)
|
||||
}
|
||||
|
||||
if cfg.Listen.Address != "0.0.0.0" {
|
||||
t.Errorf("Listen.Address = %q, want %q",
|
||||
cfg.Listen.Address, "0.0.0.0")
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ package serial
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
bugserial "go.bug.st/serial"
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ import (
|
|||
|
||||
// Connection represents an opened serial connection.
|
||||
type Connection struct {
|
||||
mu sync.Mutex
|
||||
port bugserial.Port
|
||||
}
|
||||
|
||||
|
|
@ -126,20 +128,36 @@ func createMode(device config.DeviceConfig) (*bugserial.Mode, error) {
|
|||
|
||||
// Read reads data from the serial connection.
|
||||
func (c *Connection) Read(p []byte) (int, error) {
|
||||
if c == nil || c.port == nil {
|
||||
if c == nil {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
return c.port.Read(p)
|
||||
c.mu.Lock()
|
||||
port := c.port
|
||||
c.mu.Unlock()
|
||||
|
||||
if port == nil {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
return port.Read(p)
|
||||
}
|
||||
|
||||
// Write writes data to the serial connection.
|
||||
func (c *Connection) Write(p []byte) (int, error) {
|
||||
if c == nil || c.port == nil {
|
||||
if c == nil {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
return c.port.Write(p)
|
||||
c.mu.Lock()
|
||||
port := c.port
|
||||
c.mu.Unlock()
|
||||
|
||||
if port == nil {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
return port.Write(p)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -148,12 +166,21 @@ func (c *Connection) Write(p []byte) (int, error) {
|
|||
|
||||
// Close closes the serial connection.
|
||||
func (c *Connection) Close() error {
|
||||
if c == nil || c.port == nil {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := c.port.Close()
|
||||
c.mu.Lock()
|
||||
|
||||
if c.port == nil {
|
||||
c.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
port := c.port
|
||||
c.port = nil
|
||||
|
||||
return err
|
||||
c.mu.Unlock()
|
||||
|
||||
return port.Close()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -336,6 +336,7 @@ func (s *ControlServer) runDataListener(
|
|||
dataConnection, err := NewDataConnection(
|
||||
tcpConn,
|
||||
serialConn,
|
||||
s.config.HardwareErrorResponse,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf(
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ func testServerConfig() *config.ServerConfig {
|
|||
Address: "127.0.0.1",
|
||||
Port: 5000,
|
||||
},
|
||||
HardwareErrorResponse: testHardwareErrorResponse,
|
||||
Devices: []config.DeviceConfig{
|
||||
{
|
||||
ID: "radio",
|
||||
|
|
@ -451,6 +452,7 @@ func TestControlServerDataConnection(t *testing.T) {
|
|||
Address: "127.0.0.1",
|
||||
Port: 5000,
|
||||
},
|
||||
HardwareErrorResponse: testHardwareErrorResponse,
|
||||
Devices: []config.DeviceConfig{
|
||||
{
|
||||
ID: "radio",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* ============================================================================
|
||||
* Projekt.....: rs2322tcp
|
||||
* Datei.......: data_connection.go
|
||||
* Datei.......: internal/server/data_connection.go
|
||||
* Copyright (C) 2026 Dieter Lang
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
@ -31,12 +31,17 @@ import (
|
|||
// TCP -> Serial
|
||||
// TCP <- Serial
|
||||
//
|
||||
// If writing TCP data to the serial device fails, the configured hardware
|
||||
// error response is sent back over the TCP connection before the connection
|
||||
// is terminated.
|
||||
//
|
||||
// The serial side is represented by an io.ReadWriteCloser so that this
|
||||
// server component does not depend directly on the concrete serial
|
||||
// implementation.
|
||||
type DataConnection struct {
|
||||
tcp net.Conn
|
||||
serial io.ReadWriteCloser
|
||||
tcp net.Conn
|
||||
serial io.ReadWriteCloser
|
||||
hardwareErrorResponse string
|
||||
|
||||
closeOnce sync.Once
|
||||
closeErr error
|
||||
|
|
@ -50,6 +55,7 @@ type DataConnection struct {
|
|||
func NewDataConnection(
|
||||
tcp net.Conn,
|
||||
serial io.ReadWriteCloser,
|
||||
hardwareErrorResponse string,
|
||||
) (*DataConnection, error) {
|
||||
if tcp == nil {
|
||||
return nil, fmt.Errorf("TCP connection is nil")
|
||||
|
|
@ -59,9 +65,14 @@ func NewDataConnection(
|
|||
return nil, fmt.Errorf("serial connection is nil")
|
||||
}
|
||||
|
||||
if hardwareErrorResponse == "" {
|
||||
return nil, fmt.Errorf("hardware error response is empty")
|
||||
}
|
||||
|
||||
return &DataConnection{
|
||||
tcp: tcp,
|
||||
serial: serial,
|
||||
tcp: tcp,
|
||||
serial: serial,
|
||||
hardwareErrorResponse: hardwareErrorResponse,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +98,54 @@ func (c *DataConnection) SerialConn() io.ReadWriteCloser {
|
|||
return c.serial
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Data transfer
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// copyTCPToSerial transfers data from the TCP connection to the serial
|
||||
// device.
|
||||
//
|
||||
// If writing to the serial device fails, the configured hardware error
|
||||
// response is sent back to the TCP client before the transfer terminates.
|
||||
func (c *DataConnection) copyTCPToSerial() error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("data connection is nil")
|
||||
}
|
||||
|
||||
buffer := make([]byte, 32*1024)
|
||||
|
||||
for {
|
||||
n, err := c.tcp.Read(buffer)
|
||||
|
||||
if n > 0 {
|
||||
if _, writeErr := c.serial.Write(buffer[:n]); writeErr != nil {
|
||||
_, _ = io.WriteString(
|
||||
c.tcp,
|
||||
c.hardwareErrorResponse,
|
||||
)
|
||||
|
||||
return writeErr
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copySerialToTCP transfers data from the serial device to the TCP
|
||||
// connection.
|
||||
func (c *DataConnection) copySerialToTCP() error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("data connection is nil")
|
||||
}
|
||||
|
||||
_, err := io.Copy(c.tcp, c.serial)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Run
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -96,6 +155,10 @@ func (c *DataConnection) SerialConn() io.ReadWriteCloser {
|
|||
// Run blocks until one of the two transfer directions terminates.
|
||||
// The other direction is then stopped and both connections are closed.
|
||||
//
|
||||
// If writing data from TCP to the serial device fails, the configured
|
||||
// hardware error response is sent to the TCP client before the connection
|
||||
// is closed.
|
||||
//
|
||||
// The first non-EOF transfer error is returned.
|
||||
func (c *DataConnection) Run() error {
|
||||
if c == nil {
|
||||
|
|
@ -111,15 +174,13 @@ func (c *DataConnection) Run() error {
|
|||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
_, err := io.Copy(c.serial, c.tcp)
|
||||
errCh <- err
|
||||
errCh <- c.copyTCPToSerial()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
_, err := io.Copy(c.tcp, c.serial)
|
||||
errCh <- err
|
||||
errCh <- c.copySerialToTCP()
|
||||
}()
|
||||
|
||||
err := <-errCh
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ package server_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
|
@ -31,6 +32,8 @@ type testSerialConnection struct {
|
|||
reader *bytes.Reader
|
||||
writer bytes.Buffer
|
||||
|
||||
writeErr error
|
||||
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
|
||||
|
|
@ -45,6 +48,14 @@ func newTestSerialConnection(data []byte) *testSerialConnection {
|
|||
}
|
||||
}
|
||||
|
||||
func newFailingTestSerialConnection(writeErr error) *testSerialConnection {
|
||||
return &testSerialConnection{
|
||||
reader: bytes.NewReader(nil),
|
||||
writeErr: writeErr,
|
||||
closeCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *testSerialConnection) Read(p []byte) (int, error) {
|
||||
s.mu.Lock()
|
||||
|
||||
|
|
@ -76,6 +87,10 @@ func (s *testSerialConnection) Write(p []byte) (int, error) {
|
|||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
if s.writeErr != nil {
|
||||
return 0, s.writeErr
|
||||
}
|
||||
|
||||
return s.writer.Write(p)
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +131,7 @@ func TestNewDataConnection(t *testing.T) {
|
|||
connection, err := server.NewDataConnection(
|
||||
tcpServer,
|
||||
serial,
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewDataConnection() failed: %v", err)
|
||||
|
|
@ -136,6 +152,7 @@ func TestNewDataConnectionRejectsNilTCP(t *testing.T) {
|
|||
connection, err := server.NewDataConnection(
|
||||
nil,
|
||||
serial,
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
|
|
@ -155,6 +172,7 @@ func TestNewDataConnectionRejectsNilSerial(t *testing.T) {
|
|||
connection, err := server.NewDataConnection(
|
||||
tcpServer,
|
||||
nil,
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
|
|
@ -179,6 +197,7 @@ func TestDataConnectionTCPToSerial(t *testing.T) {
|
|||
connection, err := server.NewDataConnection(
|
||||
tcpServer,
|
||||
serial,
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewDataConnection() failed: %v", err)
|
||||
|
|
@ -223,6 +242,60 @@ func TestDataConnectionTCPToSerial(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// TCP -> Serial error
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func TestDataConnectionTCPToSerialWriteError(t *testing.T) {
|
||||
tcpServer, tcpClient := net.Pipe()
|
||||
defer tcpClient.Close()
|
||||
|
||||
writeErr := errors.New("hardware write failed")
|
||||
|
||||
serial := newFailingTestSerialConnection(writeErr)
|
||||
|
||||
connection, err := server.NewDataConnection(
|
||||
tcpServer,
|
||||
serial,
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewDataConnection() failed: %v", err)
|
||||
}
|
||||
|
||||
done := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
done <- connection.Run()
|
||||
}()
|
||||
|
||||
testData := []byte("hello unavailable hardware")
|
||||
|
||||
if _, err := tcpClient.Write(testData); err != nil {
|
||||
t.Fatalf("TCP Write() failed: %v", err)
|
||||
}
|
||||
|
||||
errorResponse := make([]byte, len(testHardwareErrorResponse))
|
||||
|
||||
if _, err := io.ReadFull(tcpClient, errorResponse); err != nil {
|
||||
t.Fatalf("TCP Read() failed: %v", err)
|
||||
}
|
||||
|
||||
if string(errorResponse) != testHardwareErrorResponse {
|
||||
t.Fatalf(
|
||||
"error response = %q, want %q",
|
||||
string(errorResponse),
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("DataConnection.Run() did not terminate")
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Serial -> TCP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -238,6 +311,7 @@ func TestDataConnectionSerialToTCP(t *testing.T) {
|
|||
connection, err := server.NewDataConnection(
|
||||
tcpServer,
|
||||
serial,
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewDataConnection() failed: %v", err)
|
||||
|
|
@ -285,6 +359,7 @@ func TestDataConnectionClose(t *testing.T) {
|
|||
connection, err := server.NewDataConnection(
|
||||
tcpServer,
|
||||
serial,
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewDataConnection() failed: %v", err)
|
||||
|
|
|
|||
|
|
@ -38,8 +38,9 @@ type SerialFactory func() (io.ReadWriteCloser, error)
|
|||
// DataHandler accepts TCP data connections and connects them to a serial
|
||||
// device.
|
||||
type DataHandler struct {
|
||||
listener *DataListener
|
||||
serialFactory SerialFactory
|
||||
listener *DataListener
|
||||
serialFactory SerialFactory
|
||||
hardwareErrorResponse string
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -53,6 +54,7 @@ type DataHandler struct {
|
|||
func NewDataHandler(
|
||||
listener *DataListener,
|
||||
serialFactory SerialFactory,
|
||||
hardwareErrorResponse string,
|
||||
) (*DataHandler, error) {
|
||||
if listener == nil {
|
||||
return nil, fmt.Errorf("data listener is nil")
|
||||
|
|
@ -62,9 +64,14 @@ func NewDataHandler(
|
|||
return nil, fmt.Errorf("serial factory is nil")
|
||||
}
|
||||
|
||||
if hardwareErrorResponse == "" {
|
||||
return nil, fmt.Errorf("hardware error response is empty")
|
||||
}
|
||||
|
||||
return &DataHandler{
|
||||
listener: listener,
|
||||
serialFactory: serialFactory,
|
||||
listener: listener,
|
||||
serialFactory: serialFactory,
|
||||
hardwareErrorResponse: hardwareErrorResponse,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -114,6 +121,7 @@ func (h *DataHandler) handleConnection(tcpConn net.Conn) {
|
|||
dataConnection, err := NewDataConnection(
|
||||
tcpConn,
|
||||
serialConn,
|
||||
h.hardwareErrorResponse,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("create data connection: %v", err)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,12 @@ import (
|
|||
"git.lang-dieter.de/rs2322tcp/internal/server"
|
||||
)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Test constants
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const testHardwareErrorResponse = "TEST ERROR RESPONSE"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Test serial connection
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -119,6 +125,7 @@ func TestNewDataHandler(t *testing.T) {
|
|||
func() (io.ReadWriteCloser, error) {
|
||||
return serial, nil
|
||||
},
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewDataHandler() failed: %v", err)
|
||||
|
|
@ -135,6 +142,7 @@ func TestNewDataHandlerRejectsNilListener(t *testing.T) {
|
|||
func() (io.ReadWriteCloser, error) {
|
||||
return newHandlerTestSerial(nil), nil
|
||||
},
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
|
|
@ -156,6 +164,7 @@ func TestNewDataHandlerRejectsNilFactory(t *testing.T) {
|
|||
handler, err := server.NewDataHandler(
|
||||
listener,
|
||||
nil,
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
|
|
@ -185,6 +194,7 @@ func TestDataHandlerTCPToSerial(t *testing.T) {
|
|||
func() (io.ReadWriteCloser, error) {
|
||||
return serial, nil
|
||||
},
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewDataHandler() failed: %v", err)
|
||||
|
|
@ -255,6 +265,7 @@ func TestDataHandlerSerialToTCP(t *testing.T) {
|
|||
func() (io.ReadWriteCloser, error) {
|
||||
return serial, nil
|
||||
},
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewDataHandler() failed: %v", err)
|
||||
|
|
@ -311,6 +322,7 @@ func TestDataHandlerSerialFactoryError(t *testing.T) {
|
|||
func() (io.ReadWriteCloser, error) {
|
||||
return nil, io.ErrClosedPipe
|
||||
},
|
||||
testHardwareErrorResponse,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewDataHandler() failed: %v", err)
|
||||
|
|
|
|||
Loading…
Reference in a new issue