tcunit / TcUnit

An unit testing framework for Beckhoff's TwinCAT 3
Other
258 stars 72 forks source link

AssertEquals(ANY) for STRING and WSTRING should look at LEN/WLEN #108

Closed sagatowski closed 4 years ago

sagatowski commented 4 years ago

The method AssertEquals(ANY) takes an ANY. It does so by first comparing the data types, and if they match, compare the size of the inputs. For most data values this might make sense, but STRING and WSTRING are two exceptions. If two strings are declared as:

- a : STRING(80) := 'KalleAnka'
- b : STRING(255) := 'KalleAnka'

Although their declaration is different in terms of size, their content is equal,

This is visible when the free function F_AnyToUnionValue() is used, as this stores the data in a fixed size value (255 for string and wstring, although wstring actually stores more bytes than that).

Instead the following should be done: in.

(* Check whether the size of the inputs differs *)
IF NOT DataTypesNotEquals THEN
    IF (Expected.diSize <> Actual.diSize) THEN
        DataSizeNotEquals := TRUE;
    END_IF
END_IF

Make exceptions for STRING and WSTRING, and use the Tc2_Standard.LEN and Tc2_Standard.WLEN functions instead.

I-Campbell commented 4 years ago

The Tc2_Standard.LEN and Tc2_Standard.WLEN are limited to 255 characters. However Beckhoff states "As a rule, TwinCAT does not limit the string length" in infosys > TwinCAT 3 > TE1000 XAE > PLC > Reference Programming > Data types > STRING. I present two compare functions (below) for any length string or wstring. You can find a library with test suite at my forge account (with cfUnit). The functions themselves do not depend on any libs.

FUNCTION StringEquals : BOOL
VAR_INPUT
    a : ANY;
    b : ANY;
END_VAR
VAR
    i : DINT;
END_VAR

IF a.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_STRING AND b.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_STRING THEN
    //LOOP Exits when i = last byte of smallest Wstring, 
    //or when two bytes are not equal,
    //or when either bytes is 0x00 (terminating character)
    WHILE (i < (MIN(a.diSize,b.diSize) - 1)) AND (a.pValue[i] = b.pValue[i]) AND (a.pValue[i] <> 0) AND (b.pValue[i] <> 0) DO
        i := i+1;
    END_WHILE
    StringEquals := (a.pValue[i] = 0) AND (b.pValue[i] = 0);
ELSE
    StringEquals := FALSE;
END_IF

and

FUNCTION WstringEquals : BOOL
VAR_INPUT
    a : ANY;
    b : ANY;
END_VAR
VAR
    i : DINT;
    pwA : POINTER TO WORD;
    pwB : POINTER TO WORD;
END_VAR

IF a.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_WSTRING AND b.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_WSTRING THEN
    pwA := a.pValue;
    pwB := b.pValue;
    //LOOP Exits when i = last word of smallest wWstring, 
    //or when two words are not equal,
    //or when either word is 0x0000 (terminating character)
    WHILE (i < (MIN(a.diSize/2,b.diSize/2) - 1)) AND (pwA[i] = pwB[i]) AND (pwA[i] <> 0) AND (pwB[i] <> 0) DO
        i := i+1;
    END_WHILE
    WstringEquals := (pwA[i] = 0) AND (pwB[i] = 0);
ELSE
    WstringEquals := FALSE;
END_IF
sagatowski commented 4 years ago

The Tc2_Standard.LEN and Tc2_Standard.WLEN are limited to 255 characters. However Beckhoff states "As a rule, TwinCAT does not limit the string length" in infosys > TwinCAT 3 > TE1000 XAE > PLC > Reference Programming > Data types > STRING. I present two compare functions (below) for any length string or wstring. You can find a library with test suite at my forge account (with cfUnit). The functions themselves do not depend on any libs.

FUNCTION StringEquals : BOOL
VAR_INPUT
  a : ANY;
  b : ANY;
END_VAR
VAR
  i : DINT;
END_VAR

IF a.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_STRING AND b.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_STRING THEN
  //LOOP Exits when i = last byte of smallest Wstring, 
  //or when two bytes are not equal,
  //or when either bytes is 0x00 (terminating character)
  WHILE (i < (MIN(a.diSize,b.diSize) - 1)) AND (a.pValue[i] = b.pValue[i]) AND (a.pValue[i] <> 0) AND (b.pValue[i] <> 0) DO
      i := i+1;
  END_WHILE
  StringEquals := (a.pValue[i] = 0) AND (b.pValue[i] = 0);
ELSE
  StringEquals := FALSE;
END_IF

and

FUNCTION WstringEquals : BOOL
VAR_INPUT
  a : ANY;
  b : ANY;
END_VAR
VAR
  i : DINT;
  pwA : POINTER TO WORD;
  pwB : POINTER TO WORD;
END_VAR

IF a.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_WSTRING AND b.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_WSTRING THEN
  pwA := a.pValue;
  pwB := b.pValue;
  //LOOP Exits when i = last word of smallest wWstring, 
  //or when two words are not equal,
  //or when either word is 0x0000 (terminating character)
  WHILE (i < (MIN(a.diSize/2,b.diSize/2) - 1)) AND (pwA[i] = pwB[i]) AND (pwA[i] <> 0) AND (pwB[i] <> 0) DO
      i := i+1;
  END_WHILE
  WstringEquals := (pwA[i] = 0) AND (pwB[i] = 0);
ELSE
  WstringEquals := FALSE;
END_IF

This is neat stuff! The current version of TcUnit only supports the same string length as the beckhoff string-functions (255). I might include these functions into TcUnit (with appropriate credits), so that TcUnit can use strings/wstrings longer than 255!

sagatowski commented 4 years ago

Solved in commit 13276ed.