This is an automated email from the ASF dual-hosted git repository. sruehl pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit 47f2add3ef9f320bc79ede844902ce507f084e45 Author: Sebastian Rühl <[email protected]> AuthorDate: Tue May 19 16:34:25 2026 +0200 refactor(plc4go): internalize external dependencies --- plc4go/go.mod | 6 - plc4go/go.sum | 17 -- plc4go/internal/bacnetip/Discoverer.go | 6 +- plc4go/pkg/api/cache/PlcConnectionCache.go | 5 +- plc4go/pkg/api/cache/PlcConnectionCache_test.go | 20 +- plc4go/pkg/api/cache/connectionContainer.go | 6 +- plc4go/pkg/api/cache/connectionContainer_test.go | 22 +- plc4go/pkg/api/cache/plcConnectionLease_test.go | 30 +-- .../protocols/df1/readwrite/model/StaticHelper.go | 33 ++- plc4go/spi/codegen/fields/FieldReaderVirtual.go | 39 ++-- plc4go/spi/codegen/fields/virtualConv.go | 171 ++++++++++++++ plc4go/spi/codegen/fields/virtualConv_test.go | 247 +++++++++++++++++++++ plc4go/spi/testutils/DriverTestRunner.go | 2 +- plc4go/spi/testutils/ParserSerializerTestRunner.go | 2 +- plc4go/spi/testutils/xmldom/xmldom.go | 220 ++++++++++++++++++ plc4go/spi/testutils/xmldom/xmldom_test.go | 183 +++++++++++++++ 16 files changed, 908 insertions(+), 101 deletions(-) diff --git a/plc4go/go.mod b/plc4go/go.mod index cf76051e76..15d1e10e1a 100644 --- a/plc4go/go.mod +++ b/plc4go/go.mod @@ -22,9 +22,7 @@ module github.com/apache/plc4x/plc4go go 1.26 require ( - github.com/IBM/netaddr v1.5.0 github.com/ajankovic/xdiff v0.0.1 - github.com/cstockton/go-conv v1.0.0 github.com/fatih/color v1.19.0 github.com/google/uuid v1.6.0 github.com/gopacket/gopacket v1.5.0 @@ -32,17 +30,13 @@ require ( github.com/libp2p/go-reuseport v0.4.0 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.35.1 - github.com/snksoft/crc v1.1.0 github.com/stretchr/testify v1.11.1 - github.com/subchen/go-xmldom v1.1.2 - github.com/viney-shih/go-lock v1.1.2 golang.org/x/net v0.54.0 golang.org/x/text v0.37.0 golang.org/x/tools v0.45.0 ) require ( - github.com/antchfx/xpath v1.3.5 // indirect github.com/bitfield/gotestdox v0.2.2 // indirect github.com/brunoga/deep v1.3.1 // indirect github.com/clipperhouse/uax29/v2 v2.6.0 // indirect diff --git a/plc4go/go.sum b/plc4go/go.sum index 882a34104d..b6a345fda8 100644 --- a/plc4go/go.sum +++ b/plc4go/go.sum @@ -1,10 +1,5 @@ -github.com/IBM/netaddr v1.5.0 h1:IJlFZe1+nFs09TeMB/HOP4+xBnX2iM/xgiDOgZgTJq0= -github.com/IBM/netaddr v1.5.0/go.mod h1:DDBPeYgbFzoXHjSz9Jwk7K8wmWV4+a/Kv0LqRnb8we4= github.com/ajankovic/xdiff v0.0.1 h1:V1cj8t5xwYzm6ZGPqPOlAc9AIajXuTEn41D/1MJBWMM= github.com/ajankovic/xdiff v0.0.1/go.mod h1:SUmEZ67uB97I0zkiuQ+lb+LOms9ipn8X+p+2RdJV710= -github.com/antchfx/xpath v0.0.0-20170515025933-1f3266e77307/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= -github.com/antchfx/xpath v1.3.5 h1:PqbXLC3TkfeZyakF5eeh3NTWEbYl4VHNVeufANzDbKQ= -github.com/antchfx/xpath v1.3.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/bitfield/gotestdox v0.2.2 h1:x6RcPAbBbErKLnapz1QeAlf3ospg8efBsedU93CDsnE= github.com/bitfield/gotestdox v0.2.2/go.mod h1:D+gwtS0urjBrzguAkTM2wodsTQYFHdpx8eqRJ3N+9pY= github.com/brunoga/deep v1.3.1 h1:bSrL6FhAZa6JlVv4vsi7Hg8SLwroDb1kgDERRVipBCo= @@ -12,8 +7,6 @@ github.com/brunoga/deep v1.3.1/go.mod h1:GDV6dnXqn80ezsLSZ5Wlv1PdKAWAO4L5PnKYtv2 github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/cstockton/go-conv v1.0.0 h1:zj/q/0MpQ/97XfiC9glWiohO8lhgR4TTnHYZifLTv6I= -github.com/cstockton/go-conv v1.0.0/go.mod h1:HuiHkkRgOA0IoBNPC7ysG7kNpjDYlgM7Kj62yQPxjy4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -85,8 +78,6 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99 github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/snksoft/crc v1.1.0 h1:HkLdI4taFlgGGG1KvsWMpz78PkOC9TkPVpTV/cuWn48= -github.com/snksoft/crc v1.1.0/go.mod h1:5/gUOsgAm7OmIhb6WJzw7w5g2zfJi4FrHYgGPdshE+A= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -95,17 +86,11 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= -github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/subchen/go-xmldom v1.1.2 h1:7evI2YqfYYOnuj+PBwyaOZZYjl3iWq35P6KfBUw9jeU= -github.com/subchen/go-xmldom v1.1.2/go.mod h1:6Pg/HuX5/T4Jlj0IPJF1sRxKVoI/rrKP6LIMge9d5/8= github.com/vektra/mockery/v3 v3.6.3 h1:YaBokm33LG0E8w1TccCkcJr/6tRf+zSYiHS8ct4ttCc= github.com/vektra/mockery/v3 v3.6.3/go.mod h1:kh58mpWIghfUCfFkNCP72TJmuJUR/dKnrqBZ3HspVq8= -github.com/viney-shih/go-lock v1.1.2 h1:3TdGTiHZCPqBdTvFbQZQN/TRZzKF3KWw2rFEyKz3YqA= -github.com/viney-shih/go-lock v1.1.2/go.mod h1:Yijm78Ljteb3kRiJrbLAxVntkUukGu5uzSxq/xV7OO8= github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= @@ -125,7 +110,6 @@ golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -140,7 +124,6 @@ golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/gotestsum v1.13.0 h1:+Lh454O9mu9AMG1APV4o0y7oDYKyik/3kBOiCqiEpRo= diff --git a/plc4go/internal/bacnetip/Discoverer.go b/plc4go/internal/bacnetip/Discoverer.go index b96f61312f..6052b3072d 100644 --- a/plc4go/internal/bacnetip/Discoverer.go +++ b/plc4go/internal/bacnetip/Discoverer.go @@ -29,7 +29,6 @@ import ( "sync" "time" - "github.com/IBM/netaddr" "github.com/libp2p/go-reuseport" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -367,7 +366,10 @@ func (d *Discoverer) buildupCommunicationChannels(ctx context.Context, interface } _, cidr, _ := net.ParseCIDR(unicastAddress.String()) - broadcastAddr := netaddr.BroadcastAddr(cidr) + broadcastAddr := make(net.IP, len(cidr.IP)) + for i := range broadcastAddr { + broadcastAddr[i] = cidr.IP[i] | ^cidr.Mask[i] + } // Handle undirected broadcastConnection, err := reuseport.ListenPacket("udp4", fmt.Sprintf("%v:%d", broadcastAddr, bacNetPort)) if err != nil { diff --git a/plc4go/pkg/api/cache/PlcConnectionCache.go b/plc4go/pkg/api/cache/PlcConnectionCache.go index 4ff7a58442..eed96f8339 100644 --- a/plc4go/pkg/api/cache/PlcConnectionCache.go +++ b/plc4go/pkg/api/cache/PlcConnectionCache.go @@ -28,7 +28,6 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog" - "github.com/viney-shih/go-lock" "github.com/apache/plc4x/plc4go/pkg/api" "github.com/apache/plc4x/plc4go/pkg/api/config" @@ -52,7 +51,7 @@ func NewPlcConnectionCache(driverManager plc4go.PlcDriverManager, withConnection driverManager: driverManager, maxLeaseTime: maxLeaseTime, maxWaitTime: maxLeaseTime * 5, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, log: log, @@ -109,7 +108,7 @@ type plcConnectionCache struct { maxLeaseTime time.Duration maxWaitTime time.Duration - cacheLock lock.RWMutex + cacheLock *sync.RWMutex connections map[string]*connectionContainer tracer tracer.Tracer diff --git a/plc4go/pkg/api/cache/PlcConnectionCache_test.go b/plc4go/pkg/api/cache/PlcConnectionCache_test.go index fdb8bb227b..e3031bf280 100644 --- a/plc4go/pkg/api/cache/PlcConnectionCache_test.go +++ b/plc4go/pkg/api/cache/PlcConnectionCache_test.go @@ -23,12 +23,12 @@ import ( "context" "fmt" "strings" + "sync" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/viney-shih/go-lock" "github.com/apache/plc4x/plc4go/internal/simulated" "github.com/apache/plc4x/plc4go/pkg/api" @@ -293,7 +293,7 @@ func TestPlcConnectionCache_ReusingAnExistingConnection(t *testing.T) { driverManager: driverManager, maxLeaseTime: 5 * time.Second, maxWaitTime: 25 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -374,7 +374,7 @@ func TestPlcConnectionCache_MultipleConcurrentConnectionRequests(t *testing.T) { driverManager: driverManager, maxLeaseTime: 5 * time.Second, maxWaitTime: 25 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -385,8 +385,8 @@ func TestPlcConnectionCache_MultipleConcurrentConnectionRequests(t *testing.T) { t.Errorf("Expected %d connections in the cache but got %d", 0, len(cache.connections)) } - floodGate := lock.NewCASMutex() // floodgate is use because we want both get connection to get executed in short order - floodGate.Lock() // We use a cas mutex write lock to lock the floodgate + floodGate := &sync.RWMutex{} // floodgate is use because we want both get connection to get executed in short order + floodGate.Lock() // We use a cas mutex write lock to lock the floodgate // Read once from the cache. firstRun := executeAndTestReadFromPlc( @@ -473,7 +473,7 @@ func TestPlcConnectionCache_ConnectWithError(t *testing.T) { driverManager: driverManager, maxLeaseTime: 5 * time.Second, maxWaitTime: 25 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -509,7 +509,7 @@ func TestPlcConnectionCache_ReturningConnectionWithPingError(t *testing.T) { driverManager: driverManager, maxLeaseTime: 5 * time.Second, maxWaitTime: 25 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -557,7 +557,7 @@ func TestPlcConnectionCache_PingTimeout(t *testing.T) { driverManager: driverManager, maxLeaseTime: 5 * time.Second, maxWaitTime: 25 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -611,7 +611,7 @@ func TestPlcConnectionCache_SecondCallGetNewConnectionAfterPingTimeout(t *testin driverManager: driverManager, maxLeaseTime: 5 * time.Second, maxWaitTime: 25 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -707,7 +707,7 @@ func TestPlcConnectionCache_MaximumWaitTimeReached(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } diff --git a/plc4go/pkg/api/cache/connectionContainer.go b/plc4go/pkg/api/cache/connectionContainer.go index 20776b5742..1c343d7811 100644 --- a/plc4go/pkg/api/cache/connectionContainer.go +++ b/plc4go/pkg/api/cache/connectionContainer.go @@ -22,16 +22,16 @@ package cache import ( "context" "fmt" + "sync" "github.com/pkg/errors" "github.com/rs/zerolog" - "github.com/viney-shih/go-lock" plc4go "github.com/apache/plc4x/plc4go/pkg/api" ) type connectionContainer struct { - lock lock.RWMutex + lock *sync.RWMutex connectionString string driverManager plc4go.PlcDriverManager tracerEnabled bool @@ -58,7 +58,7 @@ func newConnectionContainer(log zerolog.Logger, driverManager plc4go.PlcDriverMa return &connectionContainer{ driverManager: driverManager, connectionString: connectionString, - lock: lock.NewCASMutex(), + lock: &sync.RWMutex{}, leaseCounter: 0, closed: false, state: StateInitialized, diff --git a/plc4go/pkg/api/cache/connectionContainer_test.go b/plc4go/pkg/api/cache/connectionContainer_test.go index 451b709295..a3a74869fd 100644 --- a/plc4go/pkg/api/cache/connectionContainer_test.go +++ b/plc4go/pkg/api/cache/connectionContainer_test.go @@ -22,10 +22,10 @@ package cache import ( "context" "fmt" + "sync" "testing" "github.com/stretchr/testify/assert" - "github.com/viney-shih/go-lock" "github.com/apache/plc4x/plc4go/internal/simulated" plc4go "github.com/apache/plc4x/plc4go/pkg/api" @@ -36,7 +36,7 @@ import ( func Test_connectionContainer_String(t1 *testing.T) { type fields struct { - lock lock.RWMutex + lock *sync.RWMutex connectionString string driverManager plc4go.PlcDriverManager tracerEnabled bool @@ -83,7 +83,7 @@ func Test_connectionContainer_String(t1 *testing.T) { func Test_connectionContainer_addListener(t1 *testing.T) { type fields struct { - lock lock.RWMutex + lock *sync.RWMutex connectionString string driverManager plc4go.PlcDriverManager tracerEnabled bool @@ -105,7 +105,7 @@ func Test_connectionContainer_addListener(t1 *testing.T) { { name: "add it", fields: fields{ - lock: lock.NewCASMutex(), + lock: &sync.RWMutex{}, }, }, } @@ -131,7 +131,7 @@ func Test_connectionContainer_addListener(t1 *testing.T) { func Test_connectionContainer_connect(t1 *testing.T) { type fields struct { - lock lock.RWMutex + lock *sync.RWMutex connectionString string driverManager plc4go.PlcDriverManager tracerEnabled bool @@ -151,7 +151,7 @@ func Test_connectionContainer_connect(t1 *testing.T) { name: "connect fresh", fields: fields{ connectionString: "simulated://1.2.3.4:42", - lock: lock.NewCASMutex(), + lock: &sync.RWMutex{}, }, setup: func(t *testing.T, fields *fields) { logger := testutils.ProduceTestingLogger(t) @@ -190,7 +190,7 @@ func Test_connectionContainer_connect(t1 *testing.T) { func Test_connectionContainer_lease(t1 *testing.T) { type fields struct { - lock lock.RWMutex + lock *sync.RWMutex connectionString string driverManager plc4go.PlcDriverManager tracerEnabled bool @@ -214,7 +214,7 @@ func Test_connectionContainer_lease(t1 *testing.T) { name: "lease fresh", fields: fields{ connectionString: "simulated://1.2.3.4:42", - lock: lock.NewCASMutex(), + lock: &sync.RWMutex{}, }, args: args{ ctx: t1.Context(), @@ -258,7 +258,7 @@ func Test_connectionContainer_lease(t1 *testing.T) { func Test_connectionContainer_returnConnection(t1 *testing.T) { type fields struct { - lock lock.RWMutex + lock *sync.RWMutex connectionString string driverManager plc4go.PlcDriverManager tracerEnabled bool @@ -283,7 +283,7 @@ func Test_connectionContainer_returnConnection(t1 *testing.T) { name: "return connection fresh", fields: fields{ connectionString: "simulated://1.2.3.4:42", - lock: lock.NewCASMutex(), + lock: &sync.RWMutex{}, }, args: args{ state: StateInitialized, @@ -304,7 +304,7 @@ func Test_connectionContainer_returnConnection(t1 *testing.T) { name: "return unconnected connection", fields: fields{ connectionString: "simulated://1.2.3.4:42", - lock: lock.NewCASMutex(), + lock: &sync.RWMutex{}, }, args: args{ state: StateInUse, diff --git a/plc4go/pkg/api/cache/plcConnectionLease_test.go b/plc4go/pkg/api/cache/plcConnectionLease_test.go index 49904879ee..8f01210133 100644 --- a/plc4go/pkg/api/cache/plcConnectionLease_test.go +++ b/plc4go/pkg/api/cache/plcConnectionLease_test.go @@ -20,11 +20,11 @@ package cache import ( + "sync" "testing" "time" "github.com/stretchr/testify/assert" - "github.com/viney-shih/go-lock" "github.com/apache/plc4x/plc4go/internal/simulated" plc4go "github.com/apache/plc4x/plc4go/pkg/api" @@ -45,7 +45,7 @@ func TestLeasedPlcConnection_IsTraceEnabled(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -79,7 +79,7 @@ func TestLeasedPlcConnection_GetTracer(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -108,7 +108,7 @@ func TestLeasedPlcConnection_GetConnectionId(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -135,7 +135,7 @@ func TestLeasedPlcConnection_Connect(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -162,7 +162,7 @@ func TestLeasedPlcConnection_BlockingClose(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -191,7 +191,7 @@ func TestLeasedPlcConnection_Close(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -220,7 +220,7 @@ func TestLeasedPlcConnection_IsConnected(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -251,7 +251,7 @@ func TestLeasedPlcConnection_Ping(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -289,7 +289,7 @@ func TestLeasedPlcConnection_GetMetadata(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -333,7 +333,7 @@ func TestLeasedPlcConnection_ReadRequestBuilder(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -374,7 +374,7 @@ func TestLeasedPlcConnection_WriteRequestBuilder(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -415,7 +415,7 @@ func TestLeasedPlcConnection_SubscriptionRequestBuilder(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -456,7 +456,7 @@ func TestLeasedPlcConnection_UnsubscriptionRequestBuilder(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } @@ -505,7 +505,7 @@ func TestLeasedPlcConnection_BrowseRequestBuilder(t *testing.T) { driverManager: driverManager, maxLeaseTime: 1 * time.Second, maxWaitTime: 5 * time.Second, - cacheLock: lock.NewCASMutex(), + cacheLock: &sync.RWMutex{}, connections: make(map[string]*connectionContainer), tracer: nil, } diff --git a/plc4go/protocols/df1/readwrite/model/StaticHelper.go b/plc4go/protocols/df1/readwrite/model/StaticHelper.go index 12a6c01360..af0807227d 100644 --- a/plc4go/protocols/df1/readwrite/model/StaticHelper.go +++ b/plc4go/protocols/df1/readwrite/model/StaticHelper.go @@ -22,29 +22,38 @@ package model import ( "context" - "github.com/snksoft/crc" - "github.com/apache/plc4x/plc4go/spi/utils" ) -var table *crc.Table - -func init() { - // CRC-16/DF-1 - table = crc.NewTable(&crc.Parameters{Width: 16, Polynomial: 0x8005, Init: 0x0000, ReflectIn: true, ReflectOut: true, FinalXor: 0x0000}) +// crc16df1Update accumulates CRC-16/DF-1 (CRC-16/ARC variant) over data. +// Parameters: poly=0x8005, init=0x0000, reflectIn=true, reflectOut=true, finalXor=0. +// The reflected polynomial is 0xA001. +func crc16df1Update(crc uint16, data []byte) uint16 { + const refPoly uint16 = 0xA001 + for _, b := range data { + crc ^= uint16(b) + for range 8 { + if crc&1 != 0 { + crc = (crc >> 1) ^ refPoly + } else { + crc >>= 1 + } + } + } + return crc } func CrcCheck(ctx context.Context, destinationAddress uint8, sourceAddress uint8, command DF1Command) func() (uint16, error) { return func() (uint16, error) { - df1Crc := table.InitCrc() - df1Crc = table.UpdateCrc(df1Crc, []byte{destinationAddress, sourceAddress}) + df1Crc := uint16(0) + df1Crc = crc16df1Update(df1Crc, []byte{destinationAddress, sourceAddress}) bytes, err := command.Serialize() if err != nil { return 0, err } - df1Crc = table.UpdateCrc(df1Crc, bytes) - df1Crc = table.UpdateCrc(df1Crc, []byte{0x03}) - return table.CRC16(df1Crc), nil + df1Crc = crc16df1Update(df1Crc, bytes) + df1Crc = crc16df1Update(df1Crc, []byte{0x03}) + return df1Crc, nil } } diff --git a/plc4go/spi/codegen/fields/FieldReaderVirtual.go b/plc4go/spi/codegen/fields/FieldReaderVirtual.go index e8eeecc93b..d6b0b08da0 100644 --- a/plc4go/spi/codegen/fields/FieldReaderVirtual.go +++ b/plc4go/spi/codegen/fields/FieldReaderVirtual.go @@ -23,7 +23,6 @@ import ( "context" "fmt" - "github.com/cstockton/go-conv" "github.com/rs/zerolog" "github.com/apache/plc4x/plc4go/spi/codegen" @@ -44,37 +43,37 @@ func (f *FieldReaderVirtual[T]) ReadVirtualField(ctx context.Context, logicalNam f.log.Debug().Str("logicalName", logicalName).Msg("reading field") switch klass.(type) { case *bool: - return f.toT(conv.Bool(valueExpression)) + return f.toT(convBool(valueExpression)) case *int8: - return f.toT(conv.Int8(valueExpression)) + return f.toT(convInt8(valueExpression)) case *uint8: - return f.toT(conv.Uint8(valueExpression)) + return f.toT(convUint8(valueExpression)) case *int16: - return f.toT(conv.Int16(valueExpression)) + return f.toT(convInt16(valueExpression)) case *uint16: - return f.toT(conv.Uint16(valueExpression)) + return f.toT(convUint16(valueExpression)) case *int32: - return f.toT(conv.Int32(valueExpression)) + return f.toT(convInt32(valueExpression)) case *uint32: - return f.toT(conv.Uint32(valueExpression)) + return f.toT(convUint32(valueExpression)) case *int64: - return f.toT(conv.Int64(valueExpression)) + return f.toT(convInt64(valueExpression)) case *uint64: - return f.toT(conv.Uint64(valueExpression)) + return f.toT(convUint64(valueExpression)) case *int: - return f.toT(conv.Int(valueExpression)) + return f.toT(convInt(valueExpression)) case *uint: - return f.toT(conv.Uint(valueExpression)) - //case *uintptr: - // return f.toT(conv.Uintptr(valueExpression)) + return f.toT(convUint(valueExpression)) + case *uintptr: + return f.toT(convUintptr(valueExpression)) case *float32: - return f.toT(conv.Float32(valueExpression)) + return f.toT(convFloat32(valueExpression)) case *float64: - return f.toT(conv.Float64(valueExpression)) - //case *complex64: - // return f.toT(conv.Complex64(valueExpression)) - //case *complex128: - // return f.toT(conv.Complex128(valueExpression)) + return f.toT(convFloat64(valueExpression)) + case *complex64: + return f.toT(convComplex64(valueExpression)) + case *complex128: + return f.toT(convComplex128(valueExpression)) case *string: return any(fmt.Sprintf("%v", valueExpression)).(T), nil } diff --git a/plc4go/spi/codegen/fields/virtualConv.go b/plc4go/spi/codegen/fields/virtualConv.go new file mode 100644 index 0000000000..6530cf8cd9 --- /dev/null +++ b/plc4go/spi/codegen/fields/virtualConv.go @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package fields + +import ( + "reflect" + "strconv" + + "github.com/pkg/errors" +) + +// convBool converts an arbitrary value to a bool. Booleans pass through, numerics are true when +// non-zero, strings parse via strconv.ParseBool. +func convBool(v any) (bool, error) { + if v == nil { + return false, nil + } + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Bool: + return rv.Bool(), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int() != 0, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return rv.Uint() != 0, nil + case reflect.Float32, reflect.Float64: + return rv.Float() != 0, nil + case reflect.Complex64, reflect.Complex128: + return rv.Complex() != 0, nil + case reflect.String: + return strconv.ParseBool(rv.String()) + } + return false, errors.Errorf("cannot convert %T to bool", v) +} + +// convInt64 converts an arbitrary value to int64. Booleans yield 0/1, floats truncate, complex +// values drop the imaginary part, strings parse via strconv.ParseInt. +func convInt64(v any) (int64, error) { + if v == nil { + return 0, nil + } + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Bool: + if rv.Bool() { + return 1, nil + } + return 0, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return int64(rv.Uint()), nil + case reflect.Float32, reflect.Float64: + return int64(rv.Float()), nil + case reflect.Complex64, reflect.Complex128: + return int64(real(rv.Complex())), nil + case reflect.String: + return strconv.ParseInt(rv.String(), 10, 64) + } + return 0, errors.Errorf("cannot convert %T to int64", v) +} + +// convUint64 converts an arbitrary value to uint64. Negative numbers wrap via two's complement. +func convUint64(v any) (uint64, error) { + if v == nil { + return 0, nil + } + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Bool: + if rv.Bool() { + return 1, nil + } + return 0, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return uint64(rv.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return rv.Uint(), nil + case reflect.Float32, reflect.Float64: + return uint64(rv.Float()), nil + case reflect.Complex64, reflect.Complex128: + return uint64(real(rv.Complex())), nil + case reflect.String: + return strconv.ParseUint(rv.String(), 10, 64) + } + return 0, errors.Errorf("cannot convert %T to uint64", v) +} + +// convFloat64 converts an arbitrary value to float64. +func convFloat64(v any) (float64, error) { + if v == nil { + return 0, nil + } + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Bool: + if rv.Bool() { + return 1, nil + } + return 0, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(rv.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return float64(rv.Uint()), nil + case reflect.Float32, reflect.Float64: + return rv.Float(), nil + case reflect.Complex64, reflect.Complex128: + return real(rv.Complex()), nil + case reflect.String: + return strconv.ParseFloat(rv.String(), 64) + } + return 0, errors.Errorf("cannot convert %T to float64", v) +} + +// convComplex128 converts an arbitrary value to complex128. Numerics become the real part with +// imaginary 0; strings parse via strconv.ParseComplex. +func convComplex128(v any) (complex128, error) { + if v == nil { + return 0, nil + } + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Bool: + if rv.Bool() { + return 1, nil + } + return 0, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return complex(float64(rv.Int()), 0), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return complex(float64(rv.Uint()), 0), nil + case reflect.Float32, reflect.Float64: + return complex(rv.Float(), 0), nil + case reflect.Complex64, reflect.Complex128: + return rv.Complex(), nil + case reflect.String: + return strconv.ParseComplex(rv.String(), 128) + } + return 0, errors.Errorf("cannot convert %T to complex128", v) +} + +// Typed narrow wrappers so the call site can funnel directly: f.toT(convInt8(v)). + +func convInt(v any) (int, error) { n, err := convInt64(v); return int(n), err } +func convInt8(v any) (int8, error) { n, err := convInt64(v); return int8(n), err } +func convInt16(v any) (int16, error) { n, err := convInt64(v); return int16(n), err } +func convInt32(v any) (int32, error) { n, err := convInt64(v); return int32(n), err } +func convUint(v any) (uint, error) { n, err := convUint64(v); return uint(n), err } +func convUint8(v any) (uint8, error) { n, err := convUint64(v); return uint8(n), err } +func convUint16(v any) (uint16, error) { n, err := convUint64(v); return uint16(n), err } +func convUint32(v any) (uint32, error) { n, err := convUint64(v); return uint32(n), err } +func convUintptr(v any) (uintptr, error) { n, err := convUint64(v); return uintptr(n), err } +func convFloat32(v any) (float32, error) { n, err := convFloat64(v); return float32(n), err } +func convComplex64(v any) (complex64, error) { c, err := convComplex128(v); return complex64(c), err } diff --git a/plc4go/spi/codegen/fields/virtualConv_test.go b/plc4go/spi/codegen/fields/virtualConv_test.go new file mode 100644 index 0000000000..d3a0489f3d --- /dev/null +++ b/plc4go/spi/codegen/fields/virtualConv_test.go @@ -0,0 +1,247 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package fields + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// ── convBool ─────────────────────────────────────────────────────────────── + +func TestConvBool(t *testing.T) { + cases := []struct { + name string + in any + want bool + }{ + {"nil", nil, false}, + {"true", true, true}, + {"false", false, false}, + {"int non-zero", int(5), true}, + {"int zero", int(0), false}, + {"int8 negative", int8(-1), true}, + {"uint non-zero", uint(1), true}, + {"uintptr non-zero", uintptr(7), true}, + {"float non-zero", 1.5, true}, + {"float zero", 0.0, false}, + {"complex non-zero", complex(1, 0), true}, + {"complex zero", complex(0, 0), false}, + {"string true", "true", true}, + {"string false", "false", false}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := convBool(tc.in) + require.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} + +func TestConvBool_UnsupportedType(t *testing.T) { + _, err := convBool([]int{1, 2}) + assert.Error(t, err) +} + +func TestConvBool_BadString(t *testing.T) { + _, err := convBool("not-a-bool") + assert.Error(t, err) +} + +// ── convInt64 / signed narrow ────────────────────────────────────────────── + +func TestConvInt64(t *testing.T) { + cases := []struct { + name string + in any + want int64 + }{ + {"nil", nil, 0}, + {"true", true, 1}, + {"false", false, 0}, + {"int", int(-42), -42}, + {"int8", int8(-1), -1}, + {"int64 max", int64(1<<62 + 1), 1<<62 + 1}, + {"uint", uint(7), 7}, + {"uintptr", uintptr(8), 8}, + {"float trunc", 3.9, 3}, + {"complex real", complex(2.5, 99), 2}, + {"string", "-123", -123}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := convInt64(tc.in) + require.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} + +func TestConvInt64_UnsupportedType(t *testing.T) { + _, err := convInt64(struct{}{}) + assert.Error(t, err) +} + +func TestSignedNarrowWrappers(t *testing.T) { + // Use values that trivially fit and verify the narrowing happens (cast). + i, err := convInt(int64(42)) + require.NoError(t, err) + assert.Equal(t, 42, i) + + i8, err := convInt8(int(127)) + require.NoError(t, err) + assert.Equal(t, int8(127), i8) + + i16, err := convInt16(int(-32768)) + require.NoError(t, err) + assert.Equal(t, int16(-32768), i16) + + i32, err := convInt32(int64(-1)) + require.NoError(t, err) + assert.Equal(t, int32(-1), i32) +} + +func TestSignedNarrowWrappers_Truncate(t *testing.T) { + // int8 holds values in [-128, 127]; 300 wraps via two's complement narrowing. + got, err := convInt8(int64(300)) + require.NoError(t, err) + assert.Equal(t, int8(300-256), got) +} + +// ── convUint64 / unsigned narrow ─────────────────────────────────────────── + +func TestConvUint64(t *testing.T) { + cases := []struct { + name string + in any + want uint64 + }{ + {"nil", nil, 0}, + {"true", true, 1}, + {"false", false, 0}, + {"int positive", int(42), 42}, + {"uint8", uint8(255), 255}, + {"uintptr", uintptr(9), 9}, + {"float trunc", 3.9, 3}, + {"complex real", complex(7.0, 1.0), 7}, + {"string", "1234", 1234}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := convUint64(tc.in) + require.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} + +func TestConvUint64_NegativeWraps(t *testing.T) { + // int(-1) wraps to uint64(max) via two's complement, matching go-conv behavior. + got, err := convUint64(int(-1)) + require.NoError(t, err) + assert.Equal(t, uint64(0xFFFFFFFFFFFFFFFF), got) +} + +func TestUnsignedNarrowWrappers(t *testing.T) { + u, err := convUint(uint64(7)) + require.NoError(t, err) + assert.Equal(t, uint(7), u) + + u8, err := convUint8(uint64(255)) + require.NoError(t, err) + assert.Equal(t, uint8(255), u8) + + u16, err := convUint16(uint64(0xFFFF)) + require.NoError(t, err) + assert.Equal(t, uint16(0xFFFF), u16) + + u32, err := convUint32(uint64(0xDEADBEEF)) + require.NoError(t, err) + assert.Equal(t, uint32(0xDEADBEEF), u32) + + up, err := convUintptr(uint64(42)) + require.NoError(t, err) + assert.Equal(t, uintptr(42), up) +} + +// ── convFloat64 / float narrow ───────────────────────────────────────────── + +func TestConvFloat64(t *testing.T) { + cases := []struct { + name string + in any + want float64 + }{ + {"nil", nil, 0}, + {"true", true, 1}, + {"int", int(-3), -3}, + {"uint", uint(7), 7}, + {"float", 1.5, 1.5}, + {"complex real", complex(2.25, 99), 2.25}, + {"string", "3.14", 3.14}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := convFloat64(tc.in) + require.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} + +func TestConvFloat32(t *testing.T) { + got, err := convFloat32(float64(1.5)) + require.NoError(t, err) + assert.Equal(t, float32(1.5), got) +} + +// ── convComplex128 / convComplex64 ───────────────────────────────────────── + +func TestConvComplex128(t *testing.T) { + cases := []struct { + name string + in any + want complex128 + }{ + {"nil", nil, 0}, + {"true", true, 1}, + {"int", int(3), complex(3, 0)}, + {"uint", uint(4), complex(4, 0)}, + {"float", 2.5, complex(2.5, 0)}, + {"complex passthrough", complex(1, 2), complex(1, 2)}, + {"string", "(1+2i)", complex(1, 2)}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := convComplex128(tc.in) + require.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} + +func TestConvComplex64(t *testing.T) { + got, err := convComplex64(complex(1.5, 2.0)) + require.NoError(t, err) + assert.Equal(t, complex64(complex(1.5, 2.0)), got) +} diff --git a/plc4go/spi/testutils/DriverTestRunner.go b/plc4go/spi/testutils/DriverTestRunner.go index f8b91375b3..501d48ea28 100644 --- a/plc4go/spi/testutils/DriverTestRunner.go +++ b/plc4go/spi/testutils/DriverTestRunner.go @@ -33,7 +33,6 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/assert" - "github.com/subchen/go-xmldom" "github.com/apache/plc4x/plc4go/pkg/api" "github.com/apache/plc4x/plc4go/pkg/api/config" @@ -41,6 +40,7 @@ import ( "github.com/apache/plc4x/plc4go/spi" "github.com/apache/plc4x/plc4go/spi/options" "github.com/apache/plc4x/plc4go/spi/options/converter" + "github.com/apache/plc4x/plc4go/spi/testutils/xmldom" "github.com/apache/plc4x/plc4go/spi/transports" "github.com/apache/plc4x/plc4go/spi/transports/test" "github.com/apache/plc4x/plc4go/spi/utils" diff --git a/plc4go/spi/testutils/ParserSerializerTestRunner.go b/plc4go/spi/testutils/ParserSerializerTestRunner.go index 5d1359da7e..f45f078d98 100644 --- a/plc4go/spi/testutils/ParserSerializerTestRunner.go +++ b/plc4go/spi/testutils/ParserSerializerTestRunner.go @@ -32,9 +32,9 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/assert" - "github.com/subchen/go-xmldom" "github.com/apache/plc4x/plc4go/spi/options" + "github.com/apache/plc4x/plc4go/spi/testutils/xmldom" "github.com/apache/plc4x/plc4go/spi/utils" ) diff --git a/plc4go/spi/testutils/xmldom/xmldom.go b/plc4go/spi/testutils/xmldom/xmldom.go new file mode 100644 index 0000000000..15c853ab70 --- /dev/null +++ b/plc4go/spi/testutils/xmldom/xmldom.go @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Package xmldom provides a tiny DOM tree over encoding/xml for the test harness. +// It replaces the subset of github.com/subchen/go-xmldom that plc4go's testutils used. +package xmldom + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "strings" +) + +type Attribute struct { + Name string + Value string +} + +type Node struct { + Document *Document + Parent *Node + Name string + Attributes []*Attribute + Children []*Node + Text string +} + +type Document struct { + ProcInst string + Directives []string + Root *Node +} + +// Must panics if err is non-nil; otherwise returns doc. +func Must(doc *Document, err error) *Document { + if err != nil { + panic(err) + } + return doc +} + +// Parse reads an XML document from r and returns a DOM tree. +func Parse(r io.Reader) (*Document, error) { + dec := xml.NewDecoder(r) + doc := &Document{} + var current *Node + for { + tok, err := dec.Token() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + switch t := tok.(type) { + case xml.StartElement: + el := &Node{Document: doc, Parent: current, Name: t.Name.Local} + for _, a := range t.Attr { + el.Attributes = append(el.Attributes, &Attribute{Name: a.Name.Local, Value: a.Value}) + } + if current != nil { + current.Children = append(current.Children, el) + } + current = el + if doc.Root == nil { + doc.Root = el + } + case xml.EndElement: + if current != nil { + current = current.Parent + } + case xml.CharData: + if current != nil { + current.Text = string(bytes.TrimSpace(t)) + } + case xml.ProcInst: + doc.ProcInst = fmt.Sprintf("<?%s %s?>", t.Target, string(t.Inst)) + case xml.Directive: + doc.Directives = append(doc.Directives, fmt.Sprintf("<!%s>", string(t))) + } + } + return doc, nil +} + +func (n *Node) GetAttribute(name string) *Attribute { + for _, a := range n.Attributes { + if a.Name == name { + return a + } + } + return nil +} + +func (n *Node) GetAttributeValue(name string) string { + if a := n.GetAttribute(name); a != nil { + return a.Value + } + return "" +} + +func (n *Node) GetChild(name string) *Node { + for _, c := range n.Children { + if c.Name == name { + return c + } + } + return nil +} + +func (n *Node) GetChildren(name string) []*Node { + var out []*Node + for _, c := range n.Children { + if c.Name == name { + out = append(out, c) + } + } + return out +} + +func (n *Node) FirstChild() *Node { + if len(n.Children) > 0 { + return n.Children[0] + } + return nil +} + +// FindOneByName performs a depth-first search and returns the first node whose name matches. +func (n *Node) FindOneByName(name string) *Node { + if n.Name == name { + return n + } + for _, c := range n.Children { + if x := c.FindOneByName(name); x != nil { + return x + } + } + return nil +} + +// FindByName performs a depth-first search and returns every node whose name matches. +func (n *Node) FindByName(name string) []*Node { + var out []*Node + if n.Name == name { + out = append(out, n) + } + for _, c := range n.Children { + out = append(out, c.FindByName(name)...) + } + return out +} + +// XMLPretty renders the node tree with two-space indentation, matching the format produced by +// the previous xmldom library so golden-file test fixtures keep comparing equal. +func (n *Node) XMLPretty() string { + buf := new(bytes.Buffer) + printXML(buf, n, 0, " ") + return buf.String() +} + +func printXML(buf *bytes.Buffer, n *Node, level int, indent string) { + pretty := len(indent) > 0 + if pretty { + buf.WriteString(strings.Repeat(indent, level)) + } + buf.WriteByte('<') + buf.WriteString(n.Name) + for _, a := range n.Attributes { + buf.WriteByte(' ') + buf.WriteString(a.Name) + buf.WriteString(`="`) + xml.Escape(buf, []byte(a.Value)) + buf.WriteByte('"') + } + if len(n.Children) == 0 && len(n.Text) == 0 { + buf.WriteString(" />") + if pretty { + buf.WriteByte('\n') + } + return + } + buf.WriteByte('>') + if len(n.Children) > 0 { + if pretty { + buf.WriteByte('\n') + } + for _, c := range n.Children { + printXML(buf, c, level+1, indent) + } + } + if len(n.Text) > 0 { + xml.EscapeText(buf, []byte(n.Text)) + } + if len(n.Children) > 0 && pretty { + buf.WriteString(strings.Repeat(indent, level)) + } + buf.WriteString("</") + buf.WriteString(n.Name) + buf.WriteByte('>') + if pretty { + buf.WriteByte('\n') + } +} diff --git a/plc4go/spi/testutils/xmldom/xmldom_test.go b/plc4go/spi/testutils/xmldom/xmldom_test.go new file mode 100644 index 0000000000..377e1af471 --- /dev/null +++ b/plc4go/spi/testutils/xmldom/xmldom_test.go @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package xmldom + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const sampleXML = `<?xml version="1.0" encoding="UTF-8"?> +<testsuite byteOrder="LITTLE_ENDIAN"> + <name>example</name> + <driver-parameters> + <parameter><name>host</name><value>1.2.3.4</value></parameter> + <parameter><name>port</name><value>1234</value></parameter> + </driver-parameters> + <testcase> + <name>case1</name> + <raw>DEADBEEF</raw> + </testcase> +</testsuite>` + +func parseSample(t *testing.T) *Document { + t.Helper() + doc, err := Parse(strings.NewReader(sampleXML)) + require.NoError(t, err) + require.NotNil(t, doc.Root) + return doc +} + +func TestParse_BuildsRoot(t *testing.T) { + doc := parseSample(t) + assert.Equal(t, "testsuite", doc.Root.Name) + assert.Equal(t, "LITTLE_ENDIAN", doc.Root.GetAttributeValue("byteOrder")) +} + +func TestParse_PreservesParentLinks(t *testing.T) { + doc := parseSample(t) + name := doc.Root.GetChild("name") + require.NotNil(t, name) + assert.Equal(t, doc.Root, name.Parent) +} + +func TestParse_ProcInst(t *testing.T) { + doc := parseSample(t) + assert.Contains(t, doc.ProcInst, `xml`) + assert.Contains(t, doc.ProcInst, `version="1.0"`) +} + +func TestParse_Error(t *testing.T) { + _, err := Parse(strings.NewReader("<unclosed>")) + assert.Error(t, err) +} + +func TestMust_PanicsOnError(t *testing.T) { + assert.Panics(t, func() { + Must(nil, assert.AnError) + }) +} + +func TestMust_ReturnsDocOnSuccess(t *testing.T) { + doc := &Document{} + got := Must(doc, nil) + assert.Same(t, doc, got) +} + +func TestGetAttribute_Missing(t *testing.T) { + doc := parseSample(t) + assert.Nil(t, doc.Root.GetAttribute("missing")) + assert.Equal(t, "", doc.Root.GetAttributeValue("missing")) +} + +func TestGetChild(t *testing.T) { + doc := parseSample(t) + name := doc.Root.GetChild("name") + require.NotNil(t, name) + assert.Equal(t, "example", name.Text) + assert.Nil(t, doc.Root.GetChild("does-not-exist")) +} + +func TestGetChildren(t *testing.T) { + doc := parseSample(t) + params := doc.Root.GetChild("driver-parameters").GetChildren("parameter") + assert.Len(t, params, 2) + + // GetChildren is direct-children-only — does NOT descend. + directNames := doc.Root.GetChildren("name") + assert.Len(t, directNames, 1, "GetChildren should only match direct children") +} + +func TestFirstChild(t *testing.T) { + doc := parseSample(t) + assert.Equal(t, "name", doc.Root.FirstChild().Name) + + leaf := &Node{} + assert.Nil(t, leaf.FirstChild()) +} + +func TestFindOneByName_DepthFirst(t *testing.T) { + doc := parseSample(t) + // `name` appears under <testsuite>, under each <parameter>, and under <testcase>. + // Depth-first should hit the top-level <name> first. + found := doc.Root.FindOneByName("name") + require.NotNil(t, found) + assert.Equal(t, "example", found.Text) + + assert.Nil(t, doc.Root.FindOneByName("does-not-exist")) +} + +func TestFindByName_RecursiveAll(t *testing.T) { + doc := parseSample(t) + // <name> elements: 1 top-level, 2 in parameters, 1 in testcase = 4 total. + all := doc.Root.FindByName("name") + assert.Len(t, all, 4) +} + +func TestXMLPretty_SelfClosing(t *testing.T) { + n := &Node{Name: "leaf"} + assert.Equal(t, "<leaf />\n", n.XMLPretty()) +} + +func TestXMLPretty_WithText(t *testing.T) { + n := &Node{Name: "v", Text: "hello"} + assert.Equal(t, "<v>hello</v>\n", n.XMLPretty()) +} + +func TestXMLPretty_EscapesText(t *testing.T) { + n := &Node{Name: "v", Text: "a<b&c"} + out := n.XMLPretty() + assert.Contains(t, out, "<") + assert.Contains(t, out, "&") +} + +func TestXMLPretty_AttributesAndChildren(t *testing.T) { + doc := parseSample(t) + tc := doc.Root.GetChild("testcase") + require.NotNil(t, tc) + out := tc.XMLPretty() + // Should contain nested elements with 2-space indentation. + assert.Contains(t, out, "<testcase>") + assert.Contains(t, out, " <name>case1</name>") + assert.Contains(t, out, " <raw>DEADBEEF</raw>") + assert.Contains(t, out, "</testcase>") +} + +func TestXMLPretty_EscapesAttribute(t *testing.T) { + n := &Node{ + Name: "v", + Attributes: []*Attribute{{Name: "a", Value: `"<>&`}}, + } + out := n.XMLPretty() + assert.Contains(t, out, `"`) + assert.Contains(t, out, `<`) + assert.Contains(t, out, `&`) +} + +func TestRoundTrip_TextMutation(t *testing.T) { + // The harness's normalizeXml mutates Node.Text in place; make sure that's possible. + doc := parseSample(t) + name := doc.Root.GetChild("name") + name.Text = "changed" + assert.Equal(t, "changed", doc.Root.GetChild("name").Text) +}
