mirror of
https://github.com/3cky/mbusd.git
synced 2024-11-12 10:03:59 +01:00
Add integration tests
This commit is contained in:
parent
fb3d6d6489
commit
a6d204bbdd
@ -6,6 +6,7 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/extern_GPL)
|
||||
include(CheckFunctionExists)
|
||||
include(CheckLibraryExists)
|
||||
include(GNUInstallDirs)
|
||||
include(FindUnixCommands)
|
||||
include(FindSystemd)
|
||||
|
||||
#TODO ISC_Posix, prog_libtool
|
||||
@ -98,11 +99,12 @@ add_custom_target(uninstall
|
||||
${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake
|
||||
)
|
||||
|
||||
# unittest target
|
||||
option(TESTS "Enable unittests" OFF)
|
||||
if(TESTS)
|
||||
add_executable(test_basic tests/test_basics.c)
|
||||
endif()
|
||||
# integration tests target
|
||||
enable_testing()
|
||||
add_test(
|
||||
NAME itests
|
||||
COMMAND ${BASH} -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/tests && ./run_itests.sh $<TARGET_FILE:mbusd>"
|
||||
)
|
||||
|
||||
## Please find Packaging stuff following
|
||||
#@source http://xit0.org/2013/04/cmake-use-git-branch-and-commit-details-in-project/
|
||||
|
35
tests/environment/mbusd_runner.sh
Executable file
35
tests/environment/mbusd_runner.sh
Executable file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
MBUSD_BIN=${MBUSD_BIN:=mbusd}
|
||||
MBUSD_ARGS="-d -L - -v9 -p /tmp/pts0 -s 19200 -P 1025"
|
||||
MBUSD_PID=/tmp/mbusd.pid
|
||||
|
||||
# check argument count
|
||||
## https://stackoverflow.com/questions/4341630/checking-for-the-correct-number-of-arguments
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "[E] usage: $0 <start|stop>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_preconditions() {
|
||||
# check if mbusd location is set
|
||||
if ! [ -x "$MBUSD_BIN" ]; then
|
||||
echo "[E] executable binary not found: ${MBUSD_BIN}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_preconditions
|
||||
CURRENT_DIR="$(dirname "$(realpath "$0")")"
|
||||
. $CURRENT_DIR/subprocess_helper.sh
|
||||
|
||||
case "$1" in
|
||||
up|start)
|
||||
CMD="$MBUSD_BIN $MBUSD_ARGS &"
|
||||
run_cmd_save_pid "$CMD" $MBUSD_PID
|
||||
;;
|
||||
down|stop)
|
||||
kill_pid $MBUSD_PID
|
||||
;;
|
||||
esac
|
||||
|
@ -89,10 +89,11 @@ class ModbusSerialServer:
|
||||
# store = ModbusSlaveContext(..., zero_mode=True)
|
||||
#---------------------------------------------------------------------------#
|
||||
store = ModbusSlaveContext(
|
||||
di = ModbusSequentialDataBlock(0, [12]*100), # discrete input
|
||||
co = ModbusSequentialDataBlock(0, [13]*100), # coils
|
||||
hr = ModbusSequentialDataBlock(0, [14]*100), # holding reg
|
||||
ir = ModbusSequentialDataBlock(0, [15]*100)) #
|
||||
di = ModbusSequentialDataBlock(0, [True]*8), # discrete inputs
|
||||
co = ModbusSequentialDataBlock(0, [False]*8), # coils
|
||||
hr = ModbusSequentialDataBlock(0, [0]*8), # holding regs
|
||||
ir = ModbusSequentialDataBlock(0, list(range(8))), # input regs
|
||||
zero_mode=True) # request(0-7) will map to the address (0-7)
|
||||
context = ModbusServerContext(slaves=store, single=True)
|
||||
|
||||
#---------------------------------------------------------------------------#
|
||||
@ -127,7 +128,7 @@ class ModbusSerialServer:
|
||||
|
||||
|
||||
def kill(self):
|
||||
print("Going to terminat the process, this could throw exceptins")
|
||||
print("Going to terminate the process, this could throw exceptions")
|
||||
if self.p is not None:
|
||||
self.p.terminate()
|
||||
|
@ -1,32 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
MBUS_SERVER_PID=/tmp/modbus_server.pid
|
||||
RTU_SLAVE_PID=/tmp/rtu_slave.pid
|
||||
|
||||
CURRENT_DIR="$(dirname "$(realpath "$0")")"
|
||||
. $CURRENT_DIR/subprocess_helper.sh
|
||||
|
||||
check_preconditions() {
|
||||
#TODO check if python module 'pymodbus' is installed
|
||||
#python -c "import foo"
|
||||
true
|
||||
python -c "import pymodbus" || exit 1
|
||||
}
|
||||
|
||||
# check argument count
|
||||
## https://stackoverflow.com/questions/4341630/checking-for-the-correct-number-of-arguments
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 up|down" >&2
|
||||
echo "[E] usage: $0 <start|stop>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_preconditions
|
||||
case "$1" in
|
||||
up|start)
|
||||
#TOOO obtain current directory
|
||||
CMD="python ${CURRENT_DIR}/modbus_server_mock.py &"
|
||||
run_cmd_save_pid "$CMD" $MBUS_SERVER_PID
|
||||
CMD="python ${CURRENT_DIR}/rtu_slave.py &"
|
||||
run_cmd_save_pid "$CMD" $RTU_SLAVE_PID
|
||||
;;
|
||||
down|stop)
|
||||
kill_pid $MBUS_SERVER_PID
|
||||
kill_pid $RTU_SLAVE_PID
|
||||
;;
|
||||
esac
|
||||
|
@ -19,7 +19,7 @@ check_preconditions() {
|
||||
# check argument count
|
||||
## https://stackoverflow.com/questions/4341630/checking-for-the-correct-number-of-arguments
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 up|down" >&2
|
||||
echo "[E] usage: $0 <start|stop>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -7,8 +7,14 @@ run_cmd_save_pid() {
|
||||
local _PID_FILE=$2
|
||||
|
||||
if [[ -e $_PID_FILE ]]; then
|
||||
echo "[D] pid_file ($_PID_FILE) exists, done"
|
||||
_PID=`cat $_PID_FILE`
|
||||
if ps -p $_PID > /dev/null; then
|
||||
echo "[D] PID file ($_PID_FILE) exists, done"
|
||||
return 1
|
||||
else
|
||||
echo "[D] removing stale PID file ($_PID_FILE)"
|
||||
rm -f $_PID_FILE
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[I] running $_CMD in background..."
|
||||
|
@ -1,75 +0,0 @@
|
||||
# Contributing to greatest
|
||||
|
||||
Thanks for taking time to contribute to greatest!
|
||||
|
||||
Please send patches or pull requests against the `develop` branch. This
|
||||
makes it easier to avoid interface changes until they can be reflected
|
||||
in version number updates.
|
||||
|
||||
Sending changes via patch or pull request acknowledges that you are
|
||||
willing and able to contribute it under this project's license. (Please
|
||||
don't contribute code you aren't legally able to share.)
|
||||
|
||||
|
||||
## Bug Reports
|
||||
|
||||
Please report bugs at [the Issues page][issues].
|
||||
|
||||
[issues]: https://github.com/silentbicycle/greatest/issues).
|
||||
|
||||
If you are reporting a bug, please include:
|
||||
|
||||
+ Your operating system name and version.
|
||||
|
||||
+ Your compiler version and target platform.
|
||||
|
||||
+ Any details about your local setup that might be helpful in
|
||||
troubleshooting.
|
||||
|
||||
+ Detailed steps to reproduce the bug.
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
Improvements to the documentation are welcome. So are requests for
|
||||
clarification -- if the docs are unclear or misleading, that's a
|
||||
potential source of bugs.
|
||||
|
||||
|
||||
## Portability
|
||||
|
||||
greatest tries to assume little about its environment. It targets ANSI C
|
||||
(C89) as a baseline, and features that are specific to C99 or later need
|
||||
to be wrapped in a version check.
|
||||
|
||||
It doesn't require a particular OS, or that there is an OS at all. (It
|
||||
may be running tests on an embedded system without an RTOS.) It uses
|
||||
`fprintf(3)` for reporting, that's about it.
|
||||
|
||||
Improvements that don't fit the portability requirements can go in
|
||||
`contrib/`, just not the main `greatest.h` header.
|
||||
|
||||
|
||||
## contrib/: Extensions and Other Tools
|
||||
|
||||
There is a `contrib/` directory for extensions. This could include
|
||||
scripts that generate test templates, add formatting to the reports, or
|
||||
better integrate greatest into build / continuous integration systems.
|
||||
Extension libraries that depend on dynamic allocation or
|
||||
platform-specific features can also go here.
|
||||
|
||||
Please include a license in any standalone scripts or extensions.
|
||||
|
||||
|
||||
## Versioning & Compatibility
|
||||
|
||||
The versioning format is MAJOR.MINOR.PATCH.
|
||||
|
||||
Improvements or minor bug fixes that do not break compatibility with
|
||||
past releases lead to patch version increases. API changes that do not
|
||||
break compatibility lead to minor version increases and reset the patch
|
||||
version, and changes that do break compatibility lead to a major version
|
||||
increase.
|
||||
|
||||
The version will be increased during the merge to master, as part of
|
||||
the release process.
|
@ -1,13 +0,0 @@
|
||||
Copyright (c) 2011-2016 Scott Vokes <vokes.s@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
@ -1,338 +0,0 @@
|
||||
# greatest
|
||||
|
||||
A testing system for C, contained in 1 file.
|
||||
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Small, Portable, Lightweight**
|
||||
|
||||
greatest doesn't depend on anything beyond ANSI C89, and the test
|
||||
scaffolding should build without warnings when compiled with
|
||||
`-Wall -Wextra -pedantic`. It is under 1,000 LOC (SLOCCount),
|
||||
and does no dynamic allocation.
|
||||
|
||||
- **Permissive License**
|
||||
|
||||
greatest is released under the [ISC License][ISC]. You can use it
|
||||
freely, even for commercial purposes.
|
||||
|
||||
- **Easy To Set Up**
|
||||
|
||||
To use, just `#include "greatest.h"` in your project. There is
|
||||
very little boilerplate. Most features are optional.
|
||||
|
||||
- **Un-Opinionated**
|
||||
|
||||
When a command-line test runner is useful, greatest can provide one,
|
||||
but it can also run as part of other programs. It doesn't depend on
|
||||
a particular build system or other tooling, and should accommodate a
|
||||
variety of testing approaches. It actively avoids imposing
|
||||
architectural choices on code under test. While greatest was
|
||||
designed with C in mind, it attempts to be usable from C++.
|
||||
|
||||
- **Modular**
|
||||
|
||||
Tests can be run individually, or grouped into suites. Suites can
|
||||
share common setup, and can be in distinct compilation
|
||||
units.
|
||||
|
||||
- **Low Friction**
|
||||
|
||||
Specific tests or suites can be run by name, for focused and rapid
|
||||
iteration during development. greatest adds very little startup
|
||||
latency.
|
||||
|
||||
|
||||
There are some compile-time options, and slightly nicer syntax for
|
||||
parametric testing (running tests with arguments) if compiled
|
||||
with a C99 or later language standard.
|
||||
|
||||
I wrote a
|
||||
[blog post](http://spin.atomicobject.com/2013/07/31/greatest-c-testing-embedded/)
|
||||
with more information.
|
||||
|
||||
[theft][], a related project, adds [property-based testing][pbt].
|
||||
|
||||
[1]: http://spin.atomicobject.com/2013/07/31/greatest-c-testing-embedded/
|
||||
[theft]: https://github.com/silentbicycle/theft
|
||||
[pbt]: https://spin.atomicobject.com/2014/09/17/property-based-testing-c/
|
||||
[ISC]: https://opensource.org/licenses/isc-license.txt
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```c
|
||||
#include "greatest.h"
|
||||
|
||||
/* A test runs various assertions, then calls PASS(), FAIL(), or SKIP(). */
|
||||
TEST x_should_equal_1(void) {
|
||||
int x = 1;
|
||||
ASSERT_EQ(1, x); /* default message */
|
||||
ASSERT_EQm("yikes, x doesn't equal 1", 1, x); /* custom message */
|
||||
/* printf expected and actual values as "%d" if they differ */
|
||||
ASSERT_EQ_FMT(1, x, "%d");
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Suites can group multiple tests with common setup. */
|
||||
SUITE(the_suite) {
|
||||
RUN_TEST(x_should_equal_1);
|
||||
}
|
||||
|
||||
/* Add definitions that need to be in the test runner's main file. */
|
||||
GREATEST_MAIN_DEFS();
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
GREATEST_MAIN_BEGIN(); /* command-line options, initialization. */
|
||||
|
||||
/* Individual tests can be run directly. */
|
||||
/* RUN_TEST(x_should_equal_1); */
|
||||
|
||||
/* Tests can also be gathered into test suites. */
|
||||
RUN_SUITE(the_suite);
|
||||
|
||||
GREATEST_MAIN_END(); /* display results */
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```sh
|
||||
$ make simple && ./simple
|
||||
cc -g -Wall -Werror -pedantic simple.c -o simple
|
||||
|
||||
* Suite the_suite:
|
||||
.
|
||||
1 test - 1 passed, 0 failed, 0 skipped (5 ticks, 0.000 sec)
|
||||
|
||||
Total: 1 test (47 ticks, 0.000 sec), 3 assertions
|
||||
Pass: 1, fail: 0, skip: 0.
|
||||
```
|
||||
|
||||
Test cases should call assertions and then end in `PASS()`, `SKIP()`,
|
||||
`FAIL()`, or one of their message variants (e.g. `SKIPm("TODO");`).
|
||||
If there are any test failures, the test runner will return 1,
|
||||
otherwise it will return 0. (Skips do not cause a test runner to
|
||||
report failure.)
|
||||
|
||||
Tests and suites are just functions, so normal C scoping rules apply.
|
||||
For example, a test or suite named "main" will have a name collision.
|
||||
|
||||
(For more examples, look at `example.c` and `example_suite.c`.)
|
||||
|
||||
|
||||
## Filtering By Name
|
||||
|
||||
greatest runs all tests by default, but can be configured to only run
|
||||
suites or tests whose names contain a filter string, and/or exclude
|
||||
tests whose name contains a filter string. When test name filtering and
|
||||
exclusion are used together, exclusion takes precedence.
|
||||
|
||||
void greatest_set_suite_filter(const char *name);
|
||||
void greatest_set_test_filter(const char *name);
|
||||
void greatest_set_test_exclude(const char *name);
|
||||
|
||||
These correspond to the following command line test runner options:
|
||||
|
||||
`-s SUITE`: Only run suites whose names contain the string "SUITE"
|
||||
`-t TEST`: Only run tests whose names contain the string "TEST"
|
||||
`-x EXCLUDE`: Exclude tests whose names contain the string "EXCLUDE"
|
||||
|
||||
For example, to run any tests with "tree" in the name, in suites with
|
||||
"pars" in the name (such as "parser"), but exclude any tests whose names
|
||||
also contain "slow":
|
||||
|
||||
./test_project -s pars -t tree -x slow
|
||||
|
||||
|
||||
## Available Assertions
|
||||
|
||||
Assertions fail the current test unless some condition holds. All
|
||||
assertions have a "message" variant (with an `m` suffix), which takes a
|
||||
custom failure message string as their first argument. For example, the
|
||||
assertion `ASSERT_EQ(apple, orange);` could instead be used like
|
||||
`ASSERT_EQm("these should match", apple, orange)`. Non-message
|
||||
assertions create a default message.
|
||||
|
||||
|
||||
### `ASSERT(COND)`
|
||||
|
||||
Assert that `COND` evaluates to a true value.
|
||||
|
||||
|
||||
### `ASSERT_FALSE(COND)`
|
||||
|
||||
Assert that `COND` evaluates to a false value.
|
||||
|
||||
|
||||
### `ASSERT_EQ(EXPECTED, ACTUAL)`
|
||||
|
||||
Assert that `EXPECTED == ACTUAL`. To compare with a custom equality test
|
||||
function, use `ASSERT_EQUAL_T` instead. To print the values if they
|
||||
differ, use `ASSERT_EQ_FMT`.
|
||||
|
||||
|
||||
### `ASSERT_EQ_FMT(EXPECTED, ACTUAL, FORMAT)`
|
||||
|
||||
Assert that `EXPECTED == ACTUAL`. If they are not equal, print their
|
||||
values using FORMAT as the `printf` format string.
|
||||
|
||||
For example: `ASSERT_EQ_FMT(123, result, "%d");`
|
||||
|
||||
Note: `EXPECTED` and `ACTUAL` will be evaluated more than once on
|
||||
failure, so they should not be a function call with side effects.
|
||||
(Since their type is not known by the macro, they cannot be
|
||||
captured in a local variable.)
|
||||
|
||||
|
||||
### `ASSERT_IN_RANGE(EXPECTED, ACTUAL, TOLERANCE)`
|
||||
|
||||
Assert that ACTUAL is within EXPECTED +/- TOLERANCE, once the values
|
||||
have been converted to a configurable floating point type
|
||||
(`GREATEST_FLOAT`).
|
||||
|
||||
|
||||
### `ASSERT_STR_EQ(EXPECTED, ACTUAL)`
|
||||
|
||||
Assert that the strings are equal
|
||||
(i.e., `strcmp(EXPECTED, ACTUAL) == 0`).
|
||||
|
||||
|
||||
### `ASSERT_STRN_EQ(EXPECTED, ACTUAL, SIZE)`
|
||||
|
||||
Assert that the first SIZE bytes of the strings are equal
|
||||
(i.e., `strncmp(EXPECTED, ACTUAL, SIZE) == 0`).
|
||||
|
||||
|
||||
### `ASSERT_MEM_EQ(EXPECTED, ACTUAL, SIZE)`
|
||||
|
||||
Assert that the first SIZE bytes of memory pointed to
|
||||
by EXPECTED and ACTUAL are equal. If the memory differs, print
|
||||
a hexdump and highlight the lines and individual bytes which
|
||||
do not match.
|
||||
|
||||
|
||||
### `ASSERT_ENUM_EQ(EXPECTED, ACTUAL, ENUM_STR_FUN)`
|
||||
|
||||
Assert that the enum value EXPECTED is equal to ACTUAL. If not, convert
|
||||
each enum value to a string using `ENUM_STR_FUN` before printing them.
|
||||
|
||||
`ENUM_STR_FUN` should have a type like:
|
||||
|
||||
const char *some_enum_str(enum some_enum x);
|
||||
|
||||
|
||||
### `ASSERT_EQUAL_T(EXPECTED, ACTUAL, TYPE_INFO, UDATA)`
|
||||
|
||||
Assert that EXPECTED and ACTUAL are equal, using the `greatest_equal_cb`
|
||||
function pointed to by `TYPE_INFO->equal` to compare them. The
|
||||
assertion's `UDATA` argument can be used to pass in arbitrary user data
|
||||
(or `NULL`). If the values are not equal and the `TYPE_INFO->print`
|
||||
function is defined, it will be used to print an "Expected: X, Got: Y"
|
||||
message.
|
||||
|
||||
|
||||
### `ASSERT_OR_LONGJMP(COND)`
|
||||
|
||||
Assert that `COND` evaluates to a true value. If not, then use
|
||||
`longjmp(3)` to immediately return from the test case and any
|
||||
intermediate function calls. (If built with `GREATEST_USE_LONGJMP`
|
||||
defined to 0, then all setjmp/longjmp-related functionality will be
|
||||
compiled out.)
|
||||
|
||||
|
||||
## Random Shuffling
|
||||
|
||||
Groups of suites or tests can be run in random order by using
|
||||
`GREATEST_SHUFFLE_SUITES` and `GREATEST_SHUFFLE_TESTS`, respectively.
|
||||
This can help find and eliminate coupling between tests.
|
||||
|
||||
The shuffling depends on the seed and the test/suite count, so a
|
||||
consistent seed will only lead to reproducible ordering until the
|
||||
group's count changes.
|
||||
|
||||
Shuffling suites:
|
||||
|
||||
SHUFFLE_SUITES(seed, {
|
||||
RUN_SUITE(suite1);
|
||||
RUN_SUITE(suite2);
|
||||
RUN_SUITE(suite3);
|
||||
RUN_SUITE(suite4);
|
||||
RUN_SUITE(suite5);
|
||||
});
|
||||
|
||||
Shuffling tests:
|
||||
|
||||
SHUFFLE_TESTS(seed, {
|
||||
RUN_TEST(test_a);
|
||||
RUN_TEST1(test_b, 12345);
|
||||
RUN_TEST(test_c);
|
||||
RUN_TESTp(test_d, "some_argument");
|
||||
RUN_TEST(test_e);
|
||||
});
|
||||
|
||||
Note: Any other code inside the block will be executed several times.
|
||||
The shuffling macro expands to a loop with (count + 1) iterations -- the
|
||||
first pass counts, and the following passes only execute the next chosen
|
||||
suite/test. In particular, avoid running tests directly inside of a
|
||||
`SHUFFLE_SUITES` block (without a suite), because the test will run over
|
||||
and over.
|
||||
|
||||
|
||||
## Sub-Functions
|
||||
|
||||
Because of how `PASS()`, `ASSERT()`, `FAIL()`, etc. are implemented
|
||||
(returning a test result enum value), calls to functions that use them
|
||||
directly from test functions must be wrapped in `CHECK_CALL`:
|
||||
|
||||
TEST example_using_subfunctions(void) {
|
||||
CHECK_CALL(less_than_three(5));
|
||||
PASS();
|
||||
}
|
||||
|
||||
This is only necessary if the called function can cause test failures.
|
||||
|
||||
|
||||
## Command Line Options
|
||||
|
||||
Test runners build with the following command line options:
|
||||
|
||||
Usage: (test_runner) [--help] [-hlfv] [-s SUITE] [-t TEST]
|
||||
-h, --help print this Help
|
||||
-l List suites and tests, then exit (dry run)
|
||||
-f Stop runner after first failure
|
||||
-v Verbose output
|
||||
-s SUITE only run suite w/ name containing SUITE substring
|
||||
-t TEST only run test w/ name containing TEST substring
|
||||
-t EXCLUDE exclude tests containing string EXCLUDE substring
|
||||
|
||||
Any arguments after `--` will be ignored.
|
||||
|
||||
If you want to run multiple test suites in parallel, look at
|
||||
[parade](https://github.com/silentbicycle/parade).
|
||||
|
||||
These command line options are processed by `GREATEST_MAIN_BEGIN();`.
|
||||
|
||||
|
||||
## Aliases
|
||||
|
||||
Most of the macros have prefixed and unprefixed forms. For example,
|
||||
`SUITE` is the same as `GREATEST_SUITE`.
|
||||
|
||||
Check the source for the list -- search for `#if GREATEST_USE_ABBREVS`.
|
||||
|
||||
These aliases can be disabled by `#define`-ing `GREATEST_USE_ABBREVS` to 0.
|
||||
|
||||
|
||||
## Color Output
|
||||
|
||||
If you want color output (`PASS` in green, `FAIL` in red, etc.), you can
|
||||
pipe the output through the included `greenest` script in `contrib/`:
|
||||
|
||||
```sh
|
||||
$ ./example -v | greenest
|
||||
```
|
||||
|
||||
(Note that `greenest` depends on a Unix-like environment.)
|
||||
|
||||
greatest itself doesn't have built-in coloring to stay small and portable.
|
@ -1,2 +0,0 @@
|
||||
# this is an external repository, its target is
|
||||
https://github.com/silentbicycle/greatest
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#https://pymodbus.readthedocs.io/en/latest/examples/synchronous-client.html
|
||||
|
||||
#---------------------------------------------------------------------------#
|
||||
# import the various server implementations
|
||||
#---------------------------------------------------------------------------#
|
||||
from pymodbus.client.sync import ModbusTcpClient
|
||||
from pymodbus.client.sync import ModbusUdpClient
|
||||
from pymodbus.client.sync import ModbusSerialClient
|
||||
|
||||
#with ModbusSerialClient(method='rtu', port='/tmp/pts1', timeout=1, baudrate=19200) as client:
|
||||
PORT_WITHOUT_ROOT_REQ = 1025
|
||||
with ModbusTcpClient('127.0.0.1', port=PORT_WITHOUT_ROOT_REQ) as client:
|
||||
result = client.read_holding_registers(1, 8, unit=1)
|
||||
print result.registers
|
93
tests/run_itests.py
Executable file
93
tests/run_itests.py
Executable file
@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import random
|
||||
import unittest
|
||||
|
||||
from pymodbus.client.sync import ModbusTcpClient
|
||||
from pymodbus.pdu import ExceptionResponse, ModbusExceptions
|
||||
from pymodbus.bit_read_message import ReadDiscreteInputsResponse, ReadCoilsResponse
|
||||
from pymodbus.bit_write_message import WriteMultipleCoilsResponse, WriteSingleCoilResponse
|
||||
from pymodbus.register_read_message import ReadInputRegistersResponse, ReadHoldingRegistersResponse
|
||||
from pymodbus.register_write_message import WriteMultipleRegistersResponse, WriteSingleRegisterResponse
|
||||
|
||||
MBUSD_PORT = 1025
|
||||
|
||||
class TestModbusRequests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.client = ModbusTcpClient('127.0.0.1', port=MBUSD_PORT)
|
||||
cls.client.connect()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.client.close()
|
||||
|
||||
def test_coils(self):
|
||||
bits = [random.randrange(2)>0 for i in range(8)]
|
||||
|
||||
# 15 Write Multiple Coils
|
||||
result = self.client.write_coils(0, bits)
|
||||
self.assertIsInstance(result, WriteMultipleCoilsResponse, result)
|
||||
self.assertEqual(result.address, 0, result)
|
||||
self.assertEqual(result.count, 8, result)
|
||||
|
||||
# 01 Read Coils
|
||||
result = self.client.read_coils(0, 8)
|
||||
self.assertIsInstance(result, ReadCoilsResponse, result)
|
||||
self.assertEqual(result.bits, bits, result)
|
||||
|
||||
# 05 Write Single Coil
|
||||
bit1 = not bits[0]
|
||||
result = self.client.write_coil(0, bit1)
|
||||
self.assertIsInstance(result, WriteSingleCoilResponse, result)
|
||||
self.assertEqual(result.address, 0, result)
|
||||
self.assertEqual(result.value, bit1, result)
|
||||
result = self.client.read_coils(0, 1)
|
||||
self.assertIsInstance(result, ReadCoilsResponse, result)
|
||||
self.assertEqual(result.bits[0], bit1, result)
|
||||
|
||||
def test_discreteInputs(self):
|
||||
# 02 Read Discrete Inputs
|
||||
result = self.client.read_discrete_inputs(0, 8)
|
||||
self.assertIsInstance(result, ReadDiscreteInputsResponse, result)
|
||||
self.assertEqual(result.bits, [True]*8, result)
|
||||
|
||||
def test_inputRegisters(self):
|
||||
# 04 Read Input Registers
|
||||
result = self.client.read_input_registers(0, 8)
|
||||
self.assertIsInstance(result, ReadInputRegistersResponse, result)
|
||||
self.assertEqual(result.registers, list(range(8)), result)
|
||||
|
||||
def test_holdingRegisters(self):
|
||||
registers = [random.randrange(8) for i in range(8)]
|
||||
|
||||
# 16 Write Multiple Holding Registers
|
||||
result = self.client.write_registers(0, registers)
|
||||
self.assertIsInstance(result, WriteMultipleRegistersResponse, result)
|
||||
self.assertEqual(result.address, 0, result)
|
||||
self.assertEqual(result.count, 8, result)
|
||||
|
||||
# 03 Read Multiple Holding Registers
|
||||
result = self.client.read_holding_registers(0, 8)
|
||||
self.assertIsInstance(result, ReadHoldingRegistersResponse, result)
|
||||
self.assertEqual(result.registers, registers, result)
|
||||
|
||||
# 06 Write Single Holding Register
|
||||
register1 = (registers[0] + 1) % 65535
|
||||
result = self.client.write_register(0, register1)
|
||||
self.assertIsInstance(result, WriteSingleRegisterResponse, result)
|
||||
self.assertEqual(result.address, 0, result)
|
||||
self.assertEqual(result.value, register1, result)
|
||||
result = self.client.read_holding_registers(0, 1)
|
||||
self.assertIsInstance(result, ReadHoldingRegistersResponse, result)
|
||||
self.assertEqual(result.registers[0], register1, result)
|
||||
|
||||
def test_exception(self):
|
||||
result = self.client.write_coil(1000, False) # invalid address 1000
|
||||
self.assertIsInstance(result, ExceptionResponse, result)
|
||||
self.assertEqual(result.original_code, 5, result) # fc05 Write Single Coil
|
||||
self.assertEqual(result.exception_code, 2, result) # Illegal Data Address
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
31
tests/run_itests.sh
Executable file
31
tests/run_itests.sh
Executable file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CWD="$(dirname "$(realpath "$0")")"
|
||||
|
||||
if ! [ -x "$1" ]; then
|
||||
echo "[E] usage: $0 <mbusd_binary_path>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export MBUSD_BIN=$1
|
||||
|
||||
function setup() {
|
||||
echo "[I] do test environment setup"
|
||||
$CWD/environment/socat_runner.sh start || return 1
|
||||
$CWD/environment/rtu_slave_runner.sh start || return 1
|
||||
$CWD/environment/mbusd_runner.sh start || return 1
|
||||
}
|
||||
|
||||
function teardown() {
|
||||
echo "[I] do test environment teardown"
|
||||
$CWD/environment/mbusd_runner.sh stop
|
||||
$CWD/environment/rtu_slave_runner.sh stop
|
||||
$CWD/environment/socat_runner.sh stop
|
||||
}
|
||||
trap teardown EXIT
|
||||
|
||||
setup || exit 1
|
||||
|
||||
$CWD/run_itests.py || exit 1
|
||||
|
||||
exit 0
|
@ -1,31 +0,0 @@
|
||||
#include "greatest/greatest.h"
|
||||
|
||||
/* A test runs various assertions, then calls PASS(), FAIL(), or SKIP(). */
|
||||
TEST x_should_equal_1(void) {
|
||||
int x = 1;
|
||||
ASSERT_EQ(1, x); /* default message */
|
||||
ASSERT_EQm("yikes, x doesn't equal 1", 1, x); /* custom message */
|
||||
/* printf expected and actual values as "%d" if they differ */
|
||||
ASSERT_EQ_FMT(1, x, "%d");
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Suites can group multiple tests with common setup. */
|
||||
SUITE(the_suite) {
|
||||
RUN_TEST(x_should_equal_1);
|
||||
}
|
||||
|
||||
/* Add definitions that need to be in the test runner's main file. */
|
||||
GREATEST_MAIN_DEFS();
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
GREATEST_MAIN_BEGIN(); /* command-line options, initialization. */
|
||||
|
||||
/* Individual tests can be run directly. */
|
||||
/* RUN_TEST(x_should_equal_1); */
|
||||
|
||||
/* Tests can also be gathered into test suites. */
|
||||
RUN_SUITE(the_suite);
|
||||
|
||||
GREATEST_MAIN_END(); /* display results */
|
||||
}
|
Loading…
Reference in New Issue
Block a user