blob: 6b866eb53cd2384023f74695a7b3a1e9b2365caa [file] [log] [blame]
/*
* 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 testutils
import (
"fmt"
"math/rand"
"reflect"
"strings"
"testing"
"time"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/apache/plc4x/plc4go/pkg/api"
apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
spiValues "github.com/apache/plc4x/plc4go/spi/values"
)
type ManualTestCase struct {
Address string
ExpectedReadValue any
WriteValue any
UnwrappedValue bool
}
type ManualTestSuite struct {
ConnectionString string
DriverManager plc4go.PlcDriverManager
TestCases []ManualTestCase
t *testing.T
}
func NewManualTestSuite(t *testing.T, connectionString string, driverManager plc4go.PlcDriverManager) *ManualTestSuite {
return &ManualTestSuite{
ConnectionString: connectionString,
DriverManager: driverManager,
t: t,
}
}
func (m *ManualTestSuite) AddTestCase(address string, expectedReadValue any, testCaseOptions ...WithTestCaseOption) {
testCase := ManualTestCase{Address: address, ExpectedReadValue: expectedReadValue, UnwrappedValue: true}
for _, testCaseOption := range testCaseOptions {
testCaseOption(testCase)
}
m.TestCases = append(m.TestCases, testCase)
}
type WithTestCaseOption func(testCase ManualTestCase)
// WithUnwrappedValue is a WithTestCaseOption which can be used to control if plc4go.PlcValue
func WithUnwrappedValue(unwrap bool) WithTestCaseOption {
return func(testCase ManualTestCase) {
testCase.UnwrappedValue = unwrap
}
}
func (m *ManualTestSuite) Run() plc4go.PlcConnection {
connectionResult := <-m.DriverManager.GetConnection(m.ConnectionString)
if err := connectionResult.GetErr(); err != nil {
tracer, ok := errors.Cause(err).(interface{ StackTrace() errors.StackTrace })
if ok {
stackTrace := tracer.StackTrace()
for _, f := range stackTrace {
fmt.Printf("%+s:%d\n", f, f)
}
fmt.Println()
}
m.t.Error(err)
m.t.FailNow()
}
connection := connectionResult.GetConnection()
m.t.Cleanup(func() {
connection.Close()
})
m.t.Log("Reading all types in separate requests")
// Run all entries separately:
for _, testCase := range m.TestCases {
tagName := testCase.Address
m.t.Run(tagName, func(t *testing.T) {
m.runSingleTest(t, connection, tagName, testCase)
})
}
m.t.Run("combinedTest", func(t *testing.T) {
m.runBurstTest(t, connection)
})
return connection
}
func (m *ManualTestSuite) runSingleTest(t *testing.T, connection plc4go.PlcConnection, tagName string, testCase ManualTestCase) {
// Prepare the read-request
readRequestBuilder := connection.ReadRequestBuilder()
readRequestBuilder.AddTagAddress(tagName, testCase.Address)
readRequest, err := readRequestBuilder.Build()
if err != nil {
t.Fatal(err)
return
}
// Execute the read request
readResponseResult := <-readRequest.Execute()
if readResponseResult.GetErr() != nil {
t.Fatalf("Error getting response %v", readResponseResult.GetErr())
return
}
readResponse := readResponseResult.GetResponse()
// Check the result
assert.Equalf(t, 1, len(readResponse.GetTagNames()), "response should have a tag for %s", tagName)
assert.Equalf(t, tagName, readResponse.GetTagNames()[0], "first tag should be equal to %s", tagName)
assert.Equalf(t, apiModel.PlcResponseCode_OK, readResponse.GetResponseCode(tagName), "response code should be ok for %s", tagName)
assert.NotNil(t, readResponse.GetValue(tagName), tagName)
expectation := reflect.ValueOf(testCase.ExpectedReadValue)
if readResponse.GetValue(tagName).IsList() && (expectation.Kind() == reflect.Slice || expectation.Kind() == reflect.Array) {
plcList := readResponse.GetValue(tagName).GetList()
for j := 0; j < expectation.Len(); j++ {
var actual any
actual = plcList[j]
if testCase.UnwrappedValue {
switch actualCasted := actual.(type) {
case spiValues.PlcBOOL:
actual = actualCasted.GetBool()
case spiValues.PlcWORD:
actual = actualCasted.GetInt8()
default:
t.Fatalf("%T not yet mapped", actualCasted)
}
}
assert.Equal(t, expectation.Index(j).Interface(), actual, fmt.Sprintf("%s[%d]", tagName, j))
}
} else {
assert.Equal(t, fmt.Sprint(testCase.ExpectedReadValue), readResponse.GetValue(tagName).GetString(), tagName)
}
}
func (m *ManualTestSuite) runBurstTest(t *testing.T, connection plc4go.PlcConnection) {
// Read all items in one big request.
// Shuffle the list of test cases and run the test 10 times.
t.Log("Reading all items together in random order")
for i := 0; i < 100; i++ {
t.Logf(" - run number %d of %d", i, 100)
shuffledTestcases := append(make([]ManualTestCase, 0), m.TestCases...)
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(shuffledTestcases), func(i, j int) {
shuffledTestcases[i], shuffledTestcases[j] = shuffledTestcases[j], shuffledTestcases[i]
})
sb := strings.Builder{}
for _, testCase := range shuffledTestcases {
sb.WriteString(testCase.Address)
sb.WriteString(", ")
}
t.Logf(" using order: %s", sb.String())
builder := connection.ReadRequestBuilder()
for _, testCase := range shuffledTestcases {
tagName := testCase.Address
builder.AddTagAddress(tagName, testCase.Address)
}
readRequest, err := builder.Build()
if err != nil {
t.Errorf("Error building request %v", err)
return
}
// Execute the read request
readResponseResult := <-readRequest.Execute()
if readResponseResult.GetErr() != nil {
t.Errorf("Error getting response %v", err)
return
}
readResponse := readResponseResult.GetResponse()
// Check the result
assert.Equal(t, len(shuffledTestcases), len(readResponse.GetTagNames()))
for _, testCase := range shuffledTestcases {
tagName := testCase.Address
assert.Equalf(t, apiModel.PlcResponseCode_OK, readResponse.GetResponseCode(tagName), "response code should be ok for %s", tagName)
assert.NotNil(t, readResponse.GetValue(tagName))
expectation := reflect.ValueOf(testCase.ExpectedReadValue)
if readResponse.GetValue(tagName).IsList() && (expectation.Kind() == reflect.Slice || expectation.Kind() == reflect.Array) {
plcList := readResponse.GetValue(tagName).GetList()
for j := 0; j < expectation.Len(); j++ {
var actual any
actual = plcList[j]
if testCase.UnwrappedValue {
switch actualCasted := actual.(type) {
case spiValues.PlcBOOL:
actual = actualCasted.GetBool()
case spiValues.PlcWORD:
actual = actualCasted.GetInt8()
default:
t.Fatalf("%T not yet mapped", actualCasted)
}
}
assert.Equal(t, expectation.Index(j).Interface(), actual, fmt.Sprintf("%s[%d]", tagName, j))
}
} else {
assert.Equal(t, fmt.Sprint(testCase.ExpectedReadValue), readResponse.GetValue(tagName).GetString(), tagName)
}
}
}
}