diff --git a/drivers/serial_linux_test.go b/drivers/serial_linux_test.go new file mode 100644 index 0000000..17e9c97 --- /dev/null +++ b/drivers/serial_linux_test.go @@ -0,0 +1,225 @@ +//go:build linux + +package drivers + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" +) + +// TestBaudToUnix tests the baud rate to Unix constant conversion. +func TestBaudToUnix(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + baud int + expected uint32 + wantErr bool + errMsg string + }{ + { + name: "4800 baud", + baud: 4800, + expected: unix.B4800, + wantErr: false, + }, + { + name: "9600 baud", + baud: 9600, + expected: unix.B9600, + wantErr: false, + }, + { + name: "19200 baud", + baud: 19200, + expected: unix.B19200, + wantErr: false, + }, + { + name: "38400 baud", + baud: 38400, + expected: unix.B38400, + wantErr: false, + }, + { + name: "57600 baud", + baud: 57600, + expected: unix.B57600, + wantErr: false, + }, + { + name: "115200 baud", + baud: 115200, + expected: unix.B115200, + wantErr: false, + }, + { + name: "unsupported baud 1200", + baud: 1200, + wantErr: true, + errMsg: "unsupported baud 1200", + }, + { + name: "unsupported baud 230400", + baud: 230400, + wantErr: true, + errMsg: "unsupported baud 230400", + }, + { + name: "zero baud", + baud: 0, + wantErr: true, + errMsg: "unsupported baud 0", + }, + { + name: "negative baud", + baud: -9600, + wantErr: true, + errMsg: "unsupported baud -9600", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + speed, err := baudToUnix(tt.baud) + if tt.wantErr { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errMsg) + assert.Zero(t, speed) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected, speed) + } + }) + } +} + +// TestBaudToUnixAllSupportedRates verifies all supported rates are valid Unix constants. +func TestBaudToUnixAllSupportedRates(t *testing.T) { + t.Parallel() + + supportedRates := []int{4800, 9600, 19200, 38400, 57600, 115200} + + for _, baud := range supportedRates { + t.Run(fmt.Sprintf("%d", baud), func(t *testing.T) { + speed, err := baudToUnix(baud) + require.NoError(t, err, "supported baud rate %d should not error", baud) + assert.NotZero(t, speed, "baud rate %d should return non-zero speed", baud) + }) + } +} + +// TestLinuxSerialFactoryType tests that LinuxSerialFactory implements SerialFactory. +func TestLinuxSerialFactoryType(t *testing.T) { + t.Parallel() + + var _ SerialFactory = LinuxSerialFactory{} +} + +// TestLinuxSerialPortString tests the String() method format. +func TestLinuxSerialPortString(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + port string + baud int + expected string + }{ + { + name: "USB0 at 9600", + port: "/dev/ttyUSB0", + baud: 9600, + expected: "/dev/ttyUSB0@9600", + }, + { + name: "AMA0 at 115200", + port: "/dev/ttyAMA0", + baud: 115200, + expected: "/dev/ttyAMA0@115200", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + p := &linuxSerialPort{ + port: tt.port, + baud: tt.baud, + } + assert.Equal(t, tt.expected, p.String()) + }) + } +} + +// TestLinuxSerialPortInterfaceCompliance verifies linuxSerialPort implements SerialPort. +func TestLinuxSerialPortInterfaceCompliance(t *testing.T) { + t.Parallel() + + var _ SerialPort = (*linuxSerialPort)(nil) +} + +// TestLinuxSerialFactoryOpenSerialInvalidConfig tests error handling for invalid configs. +func TestLinuxSerialFactoryOpenSerialInvalidConfig(t *testing.T) { + t.Parallel() + + factory := LinuxSerialFactory{} + + tests := []struct { + name string + cfg SerialConfig + errMsg string + }{ + { + name: "empty port", + cfg: SerialConfig{Port: "", Baud: 9600}, + errMsg: "Port is required", + }, + { + name: "zero baud", + cfg: SerialConfig{Port: "/dev/ttyUSB0", Baud: 0}, + errMsg: "Baud must be > 0", + }, + { + name: "negative baud", + cfg: SerialConfig{Port: "/dev/ttyUSB0", Baud: -115200}, + errMsg: "Baud must be > 0", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + port, err := factory.OpenSerial(tt.cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errMsg) + assert.Nil(t, port) + }) + } +} + +// TestLinuxSerialFactoryOpenSerialUnsupportedBaud tests unsupported baud rate handling. +func TestLinuxSerialFactoryOpenSerialUnsupportedBaud(t *testing.T) { + t.Parallel() + + factory := LinuxSerialFactory{} + + // Use a non-existent port path to avoid actual hardware interaction. + // The validation should fail at baud rate conversion before attempting to open. + cfg := SerialConfig{ + Port: "/dev/ttyUSB999", // Non-existent port + Baud: 230400, // Unsupported baud rate + } + + port, err := factory.OpenSerial(cfg) + // The error could be either from baud conversion or file opening. + // We just verify an error occurred and no port was returned. + require.Error(t, err) + assert.Nil(t, port) +} diff --git a/drivers/serial_test.go b/drivers/serial_test.go new file mode 100644 index 0000000..435ed8b --- /dev/null +++ b/drivers/serial_test.go @@ -0,0 +1,86 @@ +package drivers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestValidateSerialConfig tests the configuration validation logic. +func TestValidateSerialConfig(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + cfg SerialConfig + wantErr bool + errMsg string + }{ + { + name: "valid config", + cfg: SerialConfig{Port: "/dev/ttyUSB0", Baud: 9600}, + wantErr: false, + }, + { + name: "empty port", + cfg: SerialConfig{Port: "", Baud: 9600}, + wantErr: true, + errMsg: "Port is required", + }, + { + name: "zero baud", + cfg: SerialConfig{Port: "/dev/ttyUSB0", Baud: 0}, + wantErr: true, + errMsg: "Baud must be > 0", + }, + { + name: "negative baud", + cfg: SerialConfig{Port: "/dev/ttyUSB0", Baud: -9600}, + wantErr: true, + errMsg: "Baud must be > 0", + }, + { + name: "both port and baud invalid", + cfg: SerialConfig{Port: "", Baud: 0}, + wantErr: true, + errMsg: "Port is required", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + err := validateSerialConfig(tt.cfg) + if tt.wantErr { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errMsg) + } else { + require.NoError(t, err) + } + }) + } +} + +// TestSerialConfig tests SerialConfig struct initialization. +func TestSerialConfig(t *testing.T) { + t.Parallel() + + cfg := SerialConfig{ + Port: "/dev/ttyUSB0", + Baud: 115200, + } + + assert.Equal(t, "/dev/ttyUSB0", cfg.Port) + assert.Equal(t, 115200, cfg.Baud) +} + +// TestSerialConfigZeroValues tests zero value behavior. +func TestSerialConfigZeroValues(t *testing.T) { + t.Parallel() + + var cfg SerialConfig + err := validateSerialConfig(cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), "Port is required") +} diff --git a/go.mod b/go.mod index 0adf072..545d374 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,9 @@ module github.com/rustyeddy/devices go 1.24.5 require ( - github.com/maciej/bme280 v0.2.0 - github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/stretchr/testify v1.11.1 github.com/warthog618/go-gpiocdev v0.9.1 - golang.org/x/image v0.23.0 + golang.org/x/sys v0.29.0 periph.io/x/conn/v3 v3.7.2 periph.io/x/devices/v3 v3.7.4 periph.io/x/host/v3 v3.8.5 @@ -16,6 +14,5 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.29.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9384d76..c1946cb 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= -github.com/maciej/bme280 v0.2.0 h1:WsoHmIxw15AbhyoY5EWYH6loHNnsCayW1yWVLmukJVQ= -github.com/maciej/bme280 v0.2.0/go.mod h1:uhS+osHzBXnIwpXTCklgoi0q4XiA5Mr5ehJfGIPlfQY= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -16,8 +12,6 @@ github.com/warthog618/go-gpiocdev v0.9.1 h1:pwHPaqjJfhCipIQl78V+O3l9OKHivdRDdmgX github.com/warthog618/go-gpiocdev v0.9.1/go.mod h1:dN3e3t/S2aSNC+hgigGE/dBW8jE1ONk9bDSEYfoPyl8= github.com/warthog618/go-gpiosim v0.1.1 h1:MRAEv+T+itmw+3GeIGpQJBfanUVyg0l3JCTwHtwdre4= github.com/warthog618/go-gpiosim v0.1.1/go.mod h1:YXsnB+I9jdCMY4YAlMSRrlts25ltjmuIsrnoUrBLdqU= -golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= -golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=