Tests
This commit is contained in:
1 parent
ef8e21db2e
commit
a6016248f8
11 files changed
+529
-252
No files matched your search
@@ -20,8 +20,10 @@ The implementation follows a simple and practical design:
|
|||||||
- Qt is not required at the parser or decoder level
|
- Qt is not required at the parser or decoder level
|
||||||
- the same decode engine can be reused for live traffic and trace replay
|
- the same decode engine can be reused for live traffic and trace replay
|
||||||
|
|
||||||
The core library uses C++17 and has no Qt dependency. Qt Core and Qt Test are
|
The core library uses C++17 and has no Qt dependency. Qt integration targets the
|
||||||
used by the regression tests; the optional adapter requires Qt Core. Live CAN acquisition, trace-file readers,
|
installed Qt 6.8.2, which is the only supported Qt version. Qt Core and Qt Test are
|
||||||
|
used by the regression tests; model tests also link QtGui for graphical metatypes.
|
||||||
|
The optional adapter requires only Qt Core. Live CAN acquisition, trace-file readers,
|
||||||
are left to the consuming application. An optional Qt Core model adapter is provided in `qt/`.
|
are left to the consuming application. An optional Qt Core model adapter is provided in `qt/`.
|
||||||
|
|
||||||
See [changelog.md](changelog.md) for the release and development history,
|
See [changelog.md](changelog.md) for the release and development history,
|
||||||
@@ -396,7 +398,7 @@ alternative. Raw values remain exact across their full 64-bit ranges. Check
|
|||||||
Use `std::get` when the numeric type is known, or `std::get_if` to handle both:
|
Use `std::get` when the numeric type is known, or `std::get_if` to handle both:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
if (const auto *value = std::get_if<std::int64_t>(&decodedSignal.rawValue)) {
|
if (const std::int64_t *value = std::get_if<std::int64_t>(&decodedSignal.rawValue)) {
|
||||||
std::cout << *value;
|
std::cout << *value;
|
||||||
} else {
|
} else {
|
||||||
std::cout << std::get<std::uint64_t>(decodedSignal.rawValue);
|
std::cout << std::get<std::uint64_t>(decodedSignal.rawValue);
|
||||||
@@ -854,6 +856,7 @@ lines:
|
|||||||
- `SG_` → `DbcSignalHandler`
|
- `SG_` → `DbcSignalHandler`
|
||||||
- `CM_` → `DbcCommentHandler`
|
- `CM_` → `DbcCommentHandler`
|
||||||
- `VAL_` → `DbcValueDescriptionHandler`
|
- `VAL_` → `DbcValueDescriptionHandler`
|
||||||
|
- `SG_MUL_VAL_` → `DbcUnsupportedMultiplexingHandler`
|
||||||
|
|
||||||
Grammar implementations live in `core/dbc_statement_handler.cpp`; their interface,
|
Grammar implementations live in `core/dbc_statement_handler.cpp`; their interface,
|
||||||
context, and dispatcher live in `core/dbc_statement_handler.h`. `core/typefactory.h`
|
context, and dispatcher live in `core/dbc_statement_handler.h`. `core/typefactory.h`
|
||||||
@@ -875,8 +878,8 @@ RegisterType<MyCustomHandler>("CUSTOM_"); // after implementing MyCustomHandler
|
|||||||
Registration creates one handler; replacing a registration replaces that instance.
|
Registration creates one handler; replacing a registration replaces that instance.
|
||||||
Register during initialization before dispatching. A line is routed using its
|
Register during initialization before dispatching. A line is routed using its
|
||||||
full first whitespace-delimited keyword, so `BO_TX_BU_` cannot match `BO_`.
|
full first whitespace-delimited keyword, so `BO_TX_BU_` cannot match `BO_`.
|
||||||
Unregistered keywords remain ignored. `SG_` accepts spaces and tabs after its keyword; `CM_` retains its existing
|
Unregistered keywords remain ignored. `SG_` and `CM_` accept spaces and tabs
|
||||||
policy requiring a literal space.
|
after their keywords.
|
||||||
|
|
||||||
This factory handles statements inside DBC files. A separate format-level
|
This factory handles statements inside DBC files. A separate format-level
|
||||||
factory can be introduced when another file format is implemented. The decoder
|
factory can be introduced when another file format is implemented. The decoder
|
||||||
@@ -960,8 +963,9 @@ Additional behavior to account for:
|
|||||||
|
|
||||||
- unregistered DBC keywords are ignored
|
- unregistered DBC keywords are ignored
|
||||||
- an `SG_` statement before the first frame is rejected
|
- an `SG_` statement before the first frame is rejected
|
||||||
- duplicate frame and signal definitions are not rejected globally; ambiguous
|
- duplicate frame keys (normalized CAN ID and frame type) and repeated signal
|
||||||
`VAL_` references to them are rejected
|
names within a frame are rejected at their definition lines; manually created
|
||||||
|
databases are also checked for ambiguous `VAL_` references during resolution
|
||||||
- payload lengths are checked against storage bounds (`0..255`), rather than
|
- payload lengths are checked against storage bounds (`0..255`), rather than
|
||||||
CAN or CAN FD protocol limits
|
CAN or CAN FD protocol limits
|
||||||
- comment handling is limited to the supported forms and already parsed targets
|
- comment handling is limited to the supported forms and already parsed targets
|
||||||
@@ -1025,12 +1029,15 @@ build also requires Qt Core and Qt Test development packages. Other build
|
|||||||
systems can compile the sources listed in `core/core_files.pri`; no CMake
|
systems can compile the sources listed in `core/core_files.pri`; no CMake
|
||||||
configuration is currently provided.
|
configuration is currently provided.
|
||||||
|
|
||||||
|
Qt-dependent qmake targets share `qt/qt_version.pri`, which rejects Qt versions
|
||||||
|
other than 6.8.2. The core library remains independent of Qt libraries.
|
||||||
|
|
||||||
Open `dbc.pro` in Qt Creator to see two subprojects: `core` and `tests`.
|
Open `dbc.pro` in Qt Creator to see two subprojects: `core` and `tests`.
|
||||||
`core/core.pro` builds the C++17 static library `dbc_core` without a Qt dependency.
|
`core/core.pro` builds the C++17 static library `dbc_core` without a Qt dependency.
|
||||||
`tests/tests.pro` builds the QtTest executable `dbc_tests` and links that library.
|
`tests/tests.pro` builds the QtTest executable `dbc_tests` and links that library.
|
||||||
The root project declares `tests.depends = core` for parallel builds.
|
The root project declares `tests.depends = core` for parallel builds.
|
||||||
|
|
||||||
Build out of source (Qt 6; use `qmake` for Qt 5):
|
Build out of source with Qt 6.8.2:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mkdir build
|
mkdir build
|
||||||
@@ -1061,8 +1068,15 @@ qmake is still the build tool; this target does not link Qt libraries.
|
|||||||
|
|
||||||
### Optional Qt model adapter
|
### Optional Qt model adapter
|
||||||
|
|
||||||
Requires Qt 5.11 or newer (including Qt 6) for `QAbstractItemModelTester`.
|
The adapter and its `QAbstractItemModelTester` suite use Qt 6.8.2.
|
||||||
The adapter itself links only Qt Core. Enable its library and focused QtTest suite:
|
The adapter itself links only Qt Core.
|
||||||
|
|
||||||
|
The model test executable additionally links QtGui and uses QGuiApplication so
|
||||||
|
QAbstractItemModelTester can resolve graphical metatypes such as QPixmap. It
|
||||||
|
defaults to the `offscreen` platform when `QT_QPA_PLATFORM` is unset or empty;
|
||||||
|
no display server is required. An explicit platform setting is preserved.
|
||||||
|
|
||||||
|
Enable its library and focused QtTest suite:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mkdir -p build-qt
|
mkdir -p build-qt
|
||||||
@@ -1086,7 +1100,7 @@ directories when building separately. A widget application also needs `QT += wid
|
|||||||
|
|
||||||
// Run after constructing QApplication; view owns the model through QObject.
|
// Run after constructing QApplication; view owns the model through QObject.
|
||||||
QTreeView view;
|
QTreeView view;
|
||||||
auto *model = new DbcTreeModel(&view);
|
DbcTreeModel *model = new DbcTreeModel(&view);
|
||||||
model->SetTree(DbcTreeBuilder().Build(DbcParser().ParseFile("example.dbc")));
|
model->SetTree(DbcTreeBuilder().Build(DbcParser().ParseFile("example.dbc")));
|
||||||
view.setModel(model);
|
view.setModel(model);
|
||||||
view.expandAll();
|
view.expandAll();
|
||||||
@@ -1207,7 +1221,7 @@ sudo apt install qtcreator
|
|||||||
|
|
||||||
GCC supplies `gcov`; the `lcov` package supplies `lcov` and `genhtml`. Use the
|
GCC supplies `gcov`; the `lcov` package supplies `lcov` and `genhtml`. Use the
|
||||||
`gcov` version matching the GCC used by the selected Qt Creator kit. These
|
`gcov` version matching the GCC used by the selected Qt Creator kit. These
|
||||||
instructions use GCC and Qt 6.
|
instructions use GCC and Qt 6.8.2.
|
||||||
|
|
||||||
From the repository root, create a fresh coverage build. `--coverage` must be
|
From the repository root, create a fresh coverage build. `--coverage` must be
|
||||||
passed to both compilation and linking; Debug avoids optimization that can make
|
passed to both compilation and linking; Debug avoids optimization that can make
|
||||||
@@ -1271,12 +1285,16 @@ QtTest executables and the standalone tree test, yielded 97.8% line coverage
|
|||||||
(724/740) and 70.7% branch coverage (833/1178). These are a snapshot, not fixed
|
(724/740) and 70.7% branch coverage (833/1178). These are a snapshot, not fixed
|
||||||
expected values for later changes or other toolchains.
|
expected values for later changes or other toolchains.
|
||||||
|
|
||||||
|
After adding direct comment-handler and ambiguous `VAL_` reference tests, the
|
||||||
|
same configuration measured 98.4% line coverage (728/740) and 71.4% branch
|
||||||
|
coverage (841/1178).
|
||||||
|
|
||||||
See [GCC instrumentation options](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html)
|
See [GCC instrumentation options](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html)
|
||||||
and the [LCOV project documentation](https://github.com/linux-test-project/lcov).
|
and the [LCOV project documentation](https://github.com/linux-test-project/lcov).
|
||||||
|
|
||||||
### Configure the GCC coverage build in Qt Creator
|
### Configure the GCC coverage build in Qt Creator
|
||||||
|
|
||||||
1. Open `dbc.pro` and select a GCC kit with Qt 6.
|
1. Open `dbc.pro` and select a GCC kit with Qt 6.8.2.
|
||||||
2. In **Projects > Build Settings**, clone **Debug** as **Coverage**.
|
2. In **Projects > Build Settings**, clone **Debug** as **Coverage**.
|
||||||
3. Choose a separate build directory, such as `build-coverage`.
|
3. Choose a separate build directory, such as `build-coverage`.
|
||||||
4. Expand the **qmake** build step and add these **Additional arguments**:
|
4. Expand the **qmake** build step and add these **Additional arguments**:
|
||||||
@@ -1292,64 +1310,140 @@ and the [LCOV project documentation](https://github.com/linux-test-project/lcov)
|
|||||||
repository path if using a new terminal. `make check` runs both QtTest
|
repository path if using a new terminal. `make check` runs both QtTest
|
||||||
executables; the standalone tree executable must be run separately.
|
executables; the standalone tree executable must be run separately.
|
||||||
7. View `html/index.html` in a browser. This workflow produces HTML reports;
|
7. View `html/index.html` in a browser. This workflow produces HTML reports;
|
||||||
the Coco plugin described below uses a different instrumentation toolchain.
|
it does not require a licensed coverage toolchain.
|
||||||
|
|
||||||
### Coco integration in Qt Creator
|
### Configure all build variants in Qt Creator
|
||||||
|
|
||||||
Coco is a separate licensed toolchain. The Qt Creator plugin alone does not
|
The local `dbc.pro.user` stores machine-specific kit IDs and build/run settings.
|
||||||
install CoverageScanner, CoverageBrowser, or activate a Coco license. Obtain a
|
It is ignored by Git (`*.user`) and should not be committed or copied between
|
||||||
Coco license or evaluation license and download the Linux installer from
|
machines. Recreate the configurations below through the UI when setting up a
|
||||||
[Qt Account downloads](https://account.qt.io/s/downloads).
|
new checkout. The `Qt4ProjectManager` identifiers inside `.user` are historical
|
||||||
|
Qt Creator setting names; the project still requires Qt 6.8.2.
|
||||||
|
|
||||||
For this project's Debian/Ubuntu build, install the compiler, Qt development
|
The local configuration set uses these independent build directories:
|
||||||
tools, and IDE before installing Coco:
|
|
||||||
|
|
||||||
```sh
|
| Configuration | Build directory example | Purpose |
|
||||||
sudo apt install build-essential qt6-base-dev qt6-base-dev-tools qmake6 qtcreator
|
| --- | --- | --- |
|
||||||
# Coco's installer uses locate to discover compilers on Unix:
|
| Debug | `/tmp/build/dbc/Desktop-Debug` | Core library and parser/decoder/coverage regression tests |
|
||||||
sudo apt install plocate
|
| Release | `/tmp/build/dbc/Desktop-Release` | The same targets with release settings |
|
||||||
sudo updatedb
|
| Profile | `/tmp/build/dbc/Desktop-Profile` | Existing Qt Creator profiling configuration; not code coverage |
|
||||||
```
|
| Debug Qt Models | `/tmp/build/dbc/Desktop-Debug-Qt-Models` | Debug targets plus optional adapter and model tests |
|
||||||
|
| Release Qt Models | `/tmp/build/dbc/Desktop-Release-Qt-Models` | Release targets plus optional adapter and model tests |
|
||||||
|
| Debug Qt Models Strict | `/tmp/build/dbc/Desktop-Debug-Qt-Models-Strict` | Model build with `QT_FATAL_WARNINGS=1` for builds and runs |
|
||||||
|
| Coverage | `/tmp/build/dbc/Desktop-Coverage` | Instrumented core/model build, test execution, and LCOV HTML report |
|
||||||
|
| Core Only | `/tmp/build/dbc/Desktop-Core-Only` | The Qt-free core static library |
|
||||||
|
| Tree Tests | `/tmp/build/dbc/Desktop-Tree-Tests` | Standalone C++17 tree test without Qt |
|
||||||
|
| Demo | `/tmp/build/dbc/Desktop-Demo` | Core library and command-line demo |
|
||||||
|
|
||||||
Install Coco using the actual downloaded filename, for example:
|
The temporary paths are examples; choose permanent build directories if desired.
|
||||||
|
Do not share a build directory between these configurations.
|
||||||
|
|
||||||
```sh
|
To add the ordinary qmake configurations:
|
||||||
bash /path/to/SquishCocoSetup_VERSION_Linux_x86_64.run
|
|
||||||
```
|
|
||||||
|
|
||||||
The exact filename and supported Linux package depend on the downloaded release.
|
1. Open `dbc.pro` with a GCC/Qt 6.8.2 kit.
|
||||||
Coco's own tools come from this installer; `lcov` is not required for Coco.
|
2. Go to **Projects > Build Settings**. Keep the existing Debug, Release, and
|
||||||
Installing or configuring a new compiler later requires rerunning the installer
|
Profile configurations. Remove or disable a Coco build step if one remains.
|
||||||
or using `cocosetup` to register the compiler.
|
3. Select Debug, press **Clone**, and name the clone **Debug Qt Models**. Set
|
||||||
|
its build directory from the table and add `CONFIG+=dbc_qt` to the qmake
|
||||||
|
step's **Additional arguments**.
|
||||||
|
4. Clone Release as **Release Qt Models** with the same qmake argument and its
|
||||||
|
own build directory.
|
||||||
|
5. Clone Debug Qt Models as **Debug Qt Models Strict**. In **Build Environment**,
|
||||||
|
add `QT_FATAL_WARNINGS=1`. Run configurations should use the build environment.
|
||||||
|
6. Clone Debug Qt Models as **Coverage**. In qmake **Additional arguments**, use:
|
||||||
|
|
||||||
For Coco 7.4 or later, set up and check the Qt License Service using the installed
|
```text
|
||||||
`cocolic` executable (use its full path if it is not on `PATH`):
|
CONFIG+=dbc_qt CONFIG+=debug CONFIG-=release "QMAKE_CXXFLAGS+=--coverage -fprofile-abs-path" "QMAKE_LFLAGS+=--coverage"
|
||||||
|
```
|
||||||
|
|
||||||
```sh
|
7. For Coverage, select **Add Build Step > Custom Process Step** after qmake
|
||||||
cocolic --setup-qls
|
and Make, then set:
|
||||||
cocolic --check
|
|
||||||
```
|
|
||||||
|
|
||||||
Then configure Qt Creator:
|
| Field | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Command | `/bin/sh` |
|
||||||
|
| Arguments | `"%{sourceDir}/tests/coverage_report.sh" "%{sourceDir}" "%{buildDir}"` |
|
||||||
|
| Working directory | `%{buildDir}` |
|
||||||
|
|
||||||
1. Enable **Coco** in the plugin manager (**Help > About Plugins** on Linux) and
|
This script compiles the standalone tree test, resets counters, runs both
|
||||||
restart Qt Creator if requested. The Debian Qt Creator 16.0.1 package used for
|
QtTest executables and the tree test, and generates `%{buildDir}/html/index.html`.
|
||||||
this project's coverage check already includes `libCoco.so`.
|
It stops if any test fails. Therefore building Coverage also executes tests.
|
||||||
2. In **Preferences > Coco**, set the Coco installation directory.
|
`lcov` and `genhtml` must be installed. Use this configuration with the GCC kit;
|
||||||
3. Clone **Debug** as **DebugCoco**, with its own build directory. Use an ordinary
|
`gcov` and the compiler used for the standalone test must match that kit.
|
||||||
Debug configuration as the starting point, without GCC `--coverage` flags.
|
8. Run qmake and build each new configuration once. The default root builds
|
||||||
4. In **Projects > Project Settings > Coco Code Coverage**, enable coverage for
|
expose `dbc_tests`; model configurations also expose `dbc_model_tests`.
|
||||||
this configuration and save/reconfigure. Depending on the Qt Creator version,
|
|
||||||
the **Coco code coverage** build step also offers **Enable Coverage**.
|
|
||||||
5. Exclude test sources and Qt/STL headers as appropriate for a library-only
|
|
||||||
report. Enable `CONFIG+=dbc_qt` to build the optional model tests.
|
|
||||||
6. Build using Coco's compiler wrappers, then run both test executables. Use the
|
|
||||||
Coco plugin's report view or CoverageBrowser to inspect the collected results.
|
|
||||||
|
|
||||||
For qmake, the plugin generates `cocoplugin.prf` in the source directory. Coco
|
Qt Creator expands `%{sourceDir}` and `%{buildDir}` in custom steps to the source
|
||||||
must instrument the library subprojects as well as the test executables.
|
and active build directories. Use the field's variable chooser if needed.
|
||||||
The GCC/LCOV commands above do not collect Coco data.
|
|
||||||
|
|
||||||
See the official [Coco installation and license setup](https://doc.qt.io/coco/installation.html),
|
For the three independent builds, clone Debug, assign the name and build
|
||||||
[Coco license requirements](https://doc.qt.io/coco/coco-license-utilization.html),
|
directory from the table, and replace its qmake/Make build steps with the custom
|
||||||
and [Qt Creator Coco configuration](https://doc.qt.io/qtcreator/creator-coco.html).
|
process steps below. These steps are local IDE settings and do not change
|
||||||
|
`dbc.pro`. Give each step a descriptive name and retain the listed order.
|
||||||
|
|
||||||
|
For **Core Only**:
|
||||||
|
|
||||||
|
| Command | Arguments | Working directory |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `/usr/bin/mkdir` | `-p "%{buildDir}"` | `%{sourceDir}` |
|
||||||
|
| `/usr/bin/qmake6` | `"%{sourceDir}/core/core.pro" CONFIG+=debug CONFIG-=release` | `%{buildDir}` |
|
||||||
|
| `/usr/bin/make` | `-j4` | `%{buildDir}` |
|
||||||
|
|
||||||
|
Keep a clean step with `make clean` in `%{buildDir}`. Core Only creates a static
|
||||||
|
library, so there is no executable to run.
|
||||||
|
|
||||||
|
For **Tree Tests**:
|
||||||
|
|
||||||
|
1. Add a custom step with command `/usr/bin/mkdir`, arguments `-p "%{buildDir}"`,
|
||||||
|
and working directory `%{sourceDir}`.
|
||||||
|
2. Add a custom step with command `/usr/bin/c++`, working directory `%{buildDir}`,
|
||||||
|
and these arguments:
|
||||||
|
|
||||||
|
```text
|
||||||
|
-std=c++17 -O0 -g -I"%{sourceDir}/core" "%{sourceDir}/tests/tree_node_test.cpp" "%{sourceDir}/core/tree_node.cpp" "%{sourceDir}/core/dbc_tree_builder.cpp" -o "%{buildDir}/tree_node_tests"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Remove the default Make clean step: this direct compiler build creates no
|
||||||
|
Makefile. Run the resulting executable as described below.
|
||||||
|
|
||||||
|
For **Demo**:
|
||||||
|
|
||||||
|
| Command | Arguments | Working directory |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `/usr/bin/mkdir` | `-p "%{buildDir}/core" "%{buildDir}/examples"` | `%{sourceDir}` |
|
||||||
|
| `/usr/bin/qmake6` | `"%{sourceDir}/core/core.pro" CONFIG+=debug CONFIG-=release` | `%{buildDir}/core` |
|
||||||
|
| `/usr/bin/make` | `-j4` | `%{buildDir}/core` |
|
||||||
|
| `/usr/bin/qmake6` | `"%{sourceDir}/examples/demo.pro" "DBC_CORE_BUILD_DIR=%{buildDir}/core" CONFIG+=debug CONFIG-=release` | `%{buildDir}/examples` |
|
||||||
|
| `/usr/bin/make` | `-j4` | `%{buildDir}/examples` |
|
||||||
|
|
||||||
|
Replace the default clean step with two custom steps: `/usr/bin/make` with
|
||||||
|
arguments `clean`, first in `%{buildDir}/examples`, then in `%{buildDir}/core`.
|
||||||
|
|
||||||
|
To configure execution and switch variants:
|
||||||
|
|
||||||
|
1. Go to **Projects > Run Settings** and select the appropriate qmake target:
|
||||||
|
`dbc_tests` for ordinary builds or `dbc_model_tests` for model builds.
|
||||||
|
2. To run all QtTest suites together, add a **Custom Executable** named
|
||||||
|
**All QtTest suites**, with executable `/usr/bin/make`, arguments `check`,
|
||||||
|
and working directory `%{buildDir}`. Use it with an ordinary or model qmake
|
||||||
|
configuration. Coverage already runs every test in its final build step.
|
||||||
|
3. For Tree Tests, add a **Custom Executable** named **Standalone tree test**,
|
||||||
|
executable `%{buildDir}/tree_node_tests`, working directory `%{buildDir}`,
|
||||||
|
and no arguments.
|
||||||
|
4. For Demo, add a **Custom Executable** named **Demo**, executable
|
||||||
|
`%{buildDir}/examples/dbc_demo`, working directory `%{buildDir}/examples`.
|
||||||
|
Set **Command line arguments** to the quoted absolute path of your DBC file.
|
||||||
|
5. For Coverage on Linux, optionally add a **Custom Executable** named
|
||||||
|
**Coverage HTML report**, executable `/usr/bin/xdg-open`, arguments
|
||||||
|
`"%{buildDir}/html/index.html"`, working directory `%{buildDir}`. Running it
|
||||||
|
opens the generated report in the default browser.
|
||||||
|
6. Use the kit/build/run selector above the Run button to choose the build
|
||||||
|
configuration and its corresponding executable. Build with **Ctrl+B**,
|
||||||
|
then run with **Ctrl+R**. The Build Settings configuration list can also
|
||||||
|
switch the active build.
|
||||||
|
7. If `.user` was edited externally, close and reopen the project so Qt Creator
|
||||||
|
reads the new settings. Do not edit it while the project is open, because
|
||||||
|
Qt Creator can overwrite external changes when saving its session.
|
||||||
|
|
||||||
|
See Qt Creator's [build configuration guide](https://doc.qt.io/qtcreator/creator-build-settings.html)
|
||||||
|
and [run configuration guide](https://doc.qt.io/qtcreator/creator-run-settings.html).
|
||||||
@@ -10,6 +10,18 @@ existing Git tag; subsequent changes are listed as unreleased.
|
|||||||
|
|
||||||
#### Added
|
#### Added
|
||||||
|
|
||||||
|
- Added regression coverage for signed bit-length boundaries, deterministic
|
||||||
|
Intel/Motorola extraction, lookup failures, runtime validation, PGN, comments,
|
||||||
|
file formatting, metadata copying/lifetime, and TypeFactory behavior.
|
||||||
|
- Added direct-handler and resolver tests for unsupported comments and ambiguous
|
||||||
|
`VAL_` references in manually constructed databases.
|
||||||
|
- Documented GCC/gcov/LCOV instrumentation, Qt Creator setup, and HTML reports.
|
||||||
|
Coverage after the added tests: 98.4% of lines and 71.4% of branches with
|
||||||
|
GCC 14.2 and Qt 6.8.2, including the standalone tree test.
|
||||||
|
- Added a reusable post-build coverage report script and step-by-step Qt Creator
|
||||||
|
configurations for model tests, strict warning checks, coverage, core-only,
|
||||||
|
standalone tree tests, and the demo. Machine-specific `.user` settings stay
|
||||||
|
outside version control.
|
||||||
- Basic `M` / `mN` multiplexing metadata in parsed, tree, and runtime definitions.
|
- Basic `M` / `mN` multiplexing metadata in parsed, tree, and runtime definitions.
|
||||||
- Deferred selector and overlap validation with source locations: one unsigned
|
- Deferred selector and overlap validation with source locations: one unsigned
|
||||||
selector, representable branch values, and overlaps only between exclusive branches.
|
selector, representable branch values, and overlaps only between exclusive branches.
|
||||||
@@ -22,10 +34,23 @@ existing Git tag; subsequent changes are listed as unreleased.
|
|||||||
|
|
||||||
#### Changed
|
#### Changed
|
||||||
|
|
||||||
|
- Qt-dependent targets now require the installed Qt 6.8.2 through a shared qmake
|
||||||
|
version check; removed Qt 5 compatibility instructions. Core remains Qt-free.
|
||||||
|
- Used explicit local types in the additional coverage and model tests. Functions
|
||||||
|
retain explicit return types, and runtime handler selection uses TypeFactory
|
||||||
|
without lambdas or extra factory layers for concrete objects.
|
||||||
|
- Coverage documentation now describes the selected open GCC/LCOV toolchain.
|
||||||
- Overlapping unconditional signals are now rejected during parsing.
|
- Overlapping unconditional signals are now rejected during parsing.
|
||||||
|
|
||||||
#### Fixed
|
#### Fixed
|
||||||
|
|
||||||
|
- Linked QtGui only in the optional model tests and used QGuiApplication with an
|
||||||
|
offscreen default, resolving the QAbstractItemModelTester invalid QPixmap
|
||||||
|
metatype warning without adding QtGui to the adapter library.
|
||||||
|
- Accepted tabs after `CM_` by using the comment parser's keyword extraction
|
||||||
|
instead of requiring a literal-space prefix.
|
||||||
|
- Removed test compiler warnings by using `QVERIFY_THROWS_EXCEPTION` and
|
||||||
|
explicitly discarding `nodiscard` return values in exception checks.
|
||||||
- Reject duplicate frames by normalized CAN ID and frame type, including
|
- Reject duplicate frames by normalized CAN ID and frame type, including
|
||||||
equivalent extended ID encodings, and duplicate signal names within a frame.
|
equivalent extended ID encodings, and duplicate signal names within a frame.
|
||||||
Errors report the file path and line of the repeated definition. Standard and
|
Errors report the file path and line of the repeated definition. Standard and
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
include($$PWD/qt_version.pri)
|
||||||
include($$PWD/../core/core.pri)
|
include($$PWD/../core/core.pri)
|
||||||
QT += core
|
QT += core
|
||||||
INCLUDEPATH += $$PWD
|
INCLUDEPATH += $$PWD
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
TEMPLATE = lib
|
TEMPLATE = lib
|
||||||
|
include(qt_version.pri)
|
||||||
TARGET = dbc_qt
|
TARGET = dbc_qt
|
||||||
CONFIG += staticlib c++17
|
CONFIG += staticlib c++17
|
||||||
QT += core
|
QT += core
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Qt-dependent targets use the project's installed and tested Qt version.
|
||||||
|
!equals(QT_VERSION, 6.8.2): error("This project requires Qt 6.8.2; found $$QT_VERSION")
|
||||||
+33
-8
@@ -1,14 +1,20 @@
|
|||||||
# QtTest regression tests
|
# QtTest regression tests
|
||||||
|
|
||||||
For instrumented builds, LCOV HTML reports, Qt Creator coverage configuration,
|
For instrumented builds, LCOV HTML reports, and Qt Creator coverage configuration,
|
||||||
and Coco installation, see [Measuring code coverage](../README.md#measuring-code-coverage).
|
see [Measuring code coverage](../README.md#measuring-code-coverage).
|
||||||
|
For the complete IDE build/run configuration set, see
|
||||||
|
[Configure all build variants in Qt Creator](../README.md#configure-all-build-variants-in-qt-creator).
|
||||||
|
The Coverage configuration uses `coverage_report.sh` after its instrumented
|
||||||
|
build; the script takes the source and build directories as its two arguments.
|
||||||
|
|
||||||
Open the repository's root `dbc.pro` in Qt Creator. It contains `core` (a static
|
Open the repository's root `dbc.pro` in Qt Creator. It contains `core` (a static
|
||||||
C++17 library) and `tests` (the `dbc_tests` QtTest executable). Core is built first;
|
C++17 library) and `tests` (the `dbc_tests` QtTest executable). Core is built first;
|
||||||
tests link its archive rather than compiling its implementation again.
|
tests link its archive rather than compiling its implementation again.
|
||||||
Qt Core and Qt Test development packages are required for tests only.
|
Qt Core and Qt Test development packages are required for tests only.
|
||||||
|
The optional model tests also link QtGui, which is provided by Qt base development
|
||||||
|
packages. The adapter library itself still links only QtCore.
|
||||||
|
|
||||||
Build from the repository root with Qt 6 (use `qmake` for Qt 5):
|
Build from the repository root with the installed Qt 6.8.2:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mkdir build
|
mkdir build
|
||||||
@@ -18,8 +24,8 @@ make -j
|
|||||||
make check
|
make check
|
||||||
```
|
```
|
||||||
|
|
||||||
Or run `./tests/dbc_tests` directly. Both parser and decoder suites run, and the
|
Or run `./tests/dbc_tests` directly. Parser, decoder, and additional coverage
|
||||||
executable returns nonzero when either suite fails. Decoder cases appear as
|
suites run, and the executable returns nonzero when any suite fails. Decoder cases appear as
|
||||||
individual QtTest data rows; parser checks are grouped into named test slots.
|
individual QtTest data rows; parser checks are grouped into named test slots.
|
||||||
Checks also run in release builds.
|
Checks also run in release builds.
|
||||||
|
|
||||||
@@ -34,7 +40,8 @@ make
|
|||||||
make check
|
make check
|
||||||
```
|
```
|
||||||
|
|
||||||
`parser_tests.pri` and `decoder_tests.pri` include the test sources.
|
`parser_tests.pri` and `decoder_tests.pri` include the original test sources;
|
||||||
|
`tests.pro` also includes `dbc_coverage_test.cpp`.
|
||||||
`../core/core.pri` supplies the library include path and linkage.
|
`../core/core.pri` supplies the library include path and linkage.
|
||||||
|
|
||||||
## Decoder coverage
|
## Decoder coverage
|
||||||
@@ -73,14 +80,32 @@ README for its compiler command. It checks parents, sibling positions, null
|
|||||||
children, bounds, and unique_ptr transfer.
|
children, bounds, and unique_ptr transfer.
|
||||||
|
|
||||||
Enable `CONFIG+=dbc_qt` in the root qmake invocation to build `dbc_qt` and
|
Enable `CONFIG+=dbc_qt` in the root qmake invocation to build `dbc_qt` and
|
||||||
`dbc_model_tests` alongside existing tests. Qt 5.11+ or Qt 6 is required.
|
`dbc_model_tests` alongside existing tests. Qt 6.8.2 is required.
|
||||||
`make check` runs all suites. Model tests parse a temporary DBC fixture and use
|
`make check` runs all suites. Model tests parse a temporary DBC fixture and use
|
||||||
QAbstractItemModelTester, verify navigation across columns, invalid/foreign
|
QAbstractItemModelTester, verify navigation across columns, invalid/foreign
|
||||||
indexes, metadata and exact VAL_ integers, and persistent-index invalidation
|
indexes, metadata and exact VAL_ integers, and persistent-index invalidation
|
||||||
when reloading or clearing the tree. No GUI/display server is needed.
|
when reloading or clearing the tree. Their QGuiApplication registers graphical
|
||||||
|
metatypes used by QAbstractItemModelTester, avoiding the invalid QPixmap type
|
||||||
|
warning in Qt 6.8.2. The test executable defaults to `QT_QPA_PLATFORM=offscreen`
|
||||||
|
when the variable is unset or empty, so no display server is needed. An explicit
|
||||||
|
platform setting is preserved.
|
||||||
|
|
||||||
Basic multiplexing coverage includes selector declarations after branches,
|
Basic multiplexing coverage includes selector declarations after branches,
|
||||||
Intel/Motorola branch overlap, signed active values and VAL_ lookup, unconditional
|
Intel/Motorola branch overlap, signed active values and VAL_ lookup, unconditional
|
||||||
signals, inactive branches, missing selector/branch payloads, exact UINT64_MAX
|
signals, inactive branches, missing selector/branch payloads, exact UINT64_MAX
|
||||||
selector matching, malformed runtime selectors, source locations, and rejection
|
selector matching, malformed runtime selectors, source locations, and rejection
|
||||||
of invalid markers, selector references/ranges, overlaps, and SG_MUL_VAL_.
|
of invalid markers, selector references/ranges, overlaps, and SG_MUL_VAL_.
|
||||||
|
|
||||||
|
## Additional regression coverage
|
||||||
|
|
||||||
|
`dbc_coverage_test.cpp` runs as part of `dbc_tests`. It covers signed boundaries
|
||||||
|
below 64 bits, deterministic extraction comparisons for both byte orders across
|
||||||
|
lengths 1–64, frame lookup failures, exact decode statuses, malformed runtime
|
||||||
|
multiplexing, PGN boundaries, comment handling, file formatting, metadata copying
|
||||||
|
and source lifetime, physical conversion, and TypeFactory registration/creation.
|
||||||
|
Direct resolver tests construct ambiguous databases through the public metadata
|
||||||
|
API, verify source locations, and check that ambiguous targets receive no values.
|
||||||
|
|
||||||
|
All QtTest targets use Qt 6.8.2 and `QVERIFY_THROWS_EXCEPTION`. Exception tests
|
||||||
|
explicitly discard `nodiscard` results when only the exception is being checked.
|
||||||
|
The standalone tree test requires only a C++17 compiler.
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Run after building dbc.pro with GCC coverage and CONFIG+=dbc_qt.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ "$#" -ne 2 ]; then
|
||||||
|
echo "Usage: $0 SOURCE_DIRECTORY BUILD_DIRECTORY" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
dbc_source_dir=$(cd "$1" && pwd)
|
||||||
|
dbc_build_dir=$(cd "$2" && pwd)
|
||||||
|
cd "$dbc_build_dir"
|
||||||
|
|
||||||
|
if [ ! -f core/dbc_decoder.gcno ] || [ ! -f qt/dbc_tree_model.gcno ]; then
|
||||||
|
echo "Build with --coverage and CONFIG+=dbc_qt before generating the report." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p standalone
|
||||||
|
"${CXX:-c++}" -std=c++17 -O0 -g --coverage -fprofile-abs-path \
|
||||||
|
-I"$dbc_source_dir/core" \
|
||||||
|
"$dbc_source_dir/tests/tree_node_test.cpp" \
|
||||||
|
"$dbc_source_dir/core/tree_node.cpp" \
|
||||||
|
"$dbc_source_dir/core/dbc_tree_builder.cpp" \
|
||||||
|
-o standalone/tree_node_tests
|
||||||
|
|
||||||
|
lcov --capture --initial --directory . --branch-coverage \
|
||||||
|
--include "$dbc_source_dir/core/*" --include "$dbc_source_dir/qt/*" \
|
||||||
|
--output-file baseline.info --quiet
|
||||||
|
lcov --zerocounters --directory . --quiet
|
||||||
|
|
||||||
|
./tests/dbc_tests
|
||||||
|
./tests/dbc_model_tests
|
||||||
|
./standalone/tree_node_tests
|
||||||
|
|
||||||
|
lcov --capture --directory . --branch-coverage \
|
||||||
|
--include "$dbc_source_dir/core/*" --include "$dbc_source_dir/qt/*" \
|
||||||
|
--output-file executed.info --quiet
|
||||||
|
lcov --add-tracefile baseline.info --add-tracefile executed.info \
|
||||||
|
--branch-coverage --output-file coverage.info --quiet
|
||||||
|
genhtml coverage.info --branch-coverage --demangle-cpp \
|
||||||
|
--output-directory html --quiet
|
||||||
|
lcov --summary coverage.info --branch-coverage
|
||||||
|
echo "HTML report: $dbc_build_dir/html/index.html"
|
||||||
+253
-176
@@ -1,64 +1,65 @@
|
|||||||
// Additional regression coverage. Kept separate for review before the first run.
|
// Additional regression coverage. Kept separate for review before the first run.
|
||||||
#include "dbc_parser.h"
|
|
||||||
#include "dbc_decode_builder.h"
|
#include "dbc_decode_builder.h"
|
||||||
#include "dbc_decoder.h"
|
#include "dbc_decoder.h"
|
||||||
#include "dbc_tree_builder.h"
|
#include "dbc_parser.h"
|
||||||
#include "dbc_statement_handler.h"
|
#include "dbc_statement_handler.h"
|
||||||
|
#include "dbc_tree_builder.h"
|
||||||
#include "typefactory.h"
|
#include "typefactory.h"
|
||||||
#include <QtTest>
|
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
|
#include <QtTest>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
DbcDatabase ParseText(const std::string &text) {
|
DbcDatabase ParseText(const std::string &text) {
|
||||||
QTemporaryFile file;
|
QTemporaryFile file;
|
||||||
if (!file.open()) throw std::runtime_error("Cannot create DBC fixture");
|
if (!file.open()) throw std::runtime_error("Cannot create DBC fixture");
|
||||||
if (file.write(text.data(), static_cast<qint64>(text.size())) != static_cast<qint64>(text.size()) || !file.flush())
|
if (file.write(text.data(), static_cast<qint64>(text.size())) != static_cast<qint64>(text.size()) || !file.flush())
|
||||||
throw std::runtime_error("Cannot write DBC fixture");
|
throw std::runtime_error("Cannot write DBC fixture");
|
||||||
return DbcParser().ParseFile(file.fileName().toStdString());
|
return DbcParser().ParseFile(file.fileName().toStdString());
|
||||||
}
|
|
||||||
|
|
||||||
DecodeDatabase SingleSignal(const DecodeSignal &signal) {
|
|
||||||
DecodeDatabase database;
|
|
||||||
DecodeFrame frame;
|
|
||||||
frame.canId = 17U;
|
|
||||||
frame.signals.push_back(signal);
|
|
||||||
database.frames.push_back(frame);
|
|
||||||
database.frameIndexByKey.emplace(FrameKey(17U, false), 0U);
|
|
||||||
return database;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Independent reference: collect the visited bits first, then weight them.
|
|
||||||
// Motorola uses a linear MSB-first coordinate instead of sawtooth traversal.
|
|
||||||
std::uint64_t ReferenceExtract(const std::vector<std::uint8_t> &data,
|
|
||||||
ByteOrder order, unsigned start, unsigned length) {
|
|
||||||
std::uint64_t expected = 0;
|
|
||||||
for (unsigned i = 0; i < length; ++i) {
|
|
||||||
const unsigned coordinate = order == ByteOrder::Intel
|
|
||||||
? start + i : (start / 8U) * 8U + (7U - start % 8U) + i;
|
|
||||||
const unsigned bit = order == ByteOrder::Intel ? coordinate % 8U : 7U - coordinate % 8U;
|
|
||||||
const unsigned weight = order == ByteOrder::Intel ? i : length - 1U - i;
|
|
||||||
expected |= std::uint64_t((data.at(coordinate / 8U) >> bit) & 1U) << weight;
|
|
||||||
}
|
}
|
||||||
return expected;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FactoryBase {
|
DecodeDatabase SingleSignal(const DecodeSignal &signal) {
|
||||||
virtual ~FactoryBase() = default;
|
DecodeDatabase database;
|
||||||
virtual int Value() const = 0;
|
DecodeFrame frame;
|
||||||
};
|
frame.canId = 17U;
|
||||||
struct FactoryFirst : FactoryBase {
|
frame.signals.push_back(signal);
|
||||||
explicit FactoryFirst(std::unique_ptr<int> value) : value(std::move(value)) {}
|
database.frames.push_back(frame);
|
||||||
int Value() const override { return *value; }
|
database.frameIndexByKey.emplace(FrameKey(17U, false), 0U);
|
||||||
std::unique_ptr<int> value;
|
return database;
|
||||||
};
|
}
|
||||||
struct FactorySecond : FactoryFirst {
|
|
||||||
using FactoryFirst::FactoryFirst;
|
// Independent reference: collect the visited bits first, then weight them.
|
||||||
int Value() const override { return *value + 1; }
|
// Motorola uses a linear MSB-first coordinate instead of sawtooth traversal.
|
||||||
};
|
std::uint64_t ReferenceExtract(const std::vector<std::uint8_t> &data,
|
||||||
}
|
ByteOrder order, unsigned start, unsigned length) {
|
||||||
|
std::uint64_t expected = 0;
|
||||||
|
for (unsigned i = 0; i < length; ++i) {
|
||||||
|
const unsigned coordinate = order == ByteOrder::Intel
|
||||||
|
? start + i
|
||||||
|
: (start / 8U) * 8U + (7U - start % 8U) + i;
|
||||||
|
const unsigned bit = order == ByteOrder::Intel ? coordinate % 8U : 7U - coordinate % 8U;
|
||||||
|
const unsigned weight = order == ByteOrder::Intel ? i : length - 1U - i;
|
||||||
|
expected |= std::uint64_t((data.at(coordinate / 8U) >> bit) & 1U) << weight;
|
||||||
|
}
|
||||||
|
return expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FactoryBase {
|
||||||
|
virtual ~FactoryBase() = default;
|
||||||
|
virtual int Value() const = 0;
|
||||||
|
};
|
||||||
|
struct FactoryFirst : FactoryBase {
|
||||||
|
explicit FactoryFirst(std::unique_ptr<int> value) : value(std::move(value)) {}
|
||||||
|
int Value() const override { return *value; }
|
||||||
|
std::unique_ptr<int> value;
|
||||||
|
};
|
||||||
|
struct FactorySecond : FactoryFirst {
|
||||||
|
using FactoryFirst::FactoryFirst;
|
||||||
|
int Value() const override { return *value + 1; }
|
||||||
|
};
|
||||||
|
}// namespace
|
||||||
|
|
||||||
class CoverageTest : public QObject {
|
class CoverageTest : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -100,12 +101,12 @@ void CoverageTest::frameLookup() {
|
|||||||
raw.canId = 17U;
|
raw.canId = 17U;
|
||||||
DecodeDatabase empty;
|
DecodeDatabase empty;
|
||||||
QVERIFY(decoder.FindFrame(empty, 17U, false) == nullptr);
|
QVERIFY(decoder.FindFrame(empty, 17U, false) == nullptr);
|
||||||
const auto absent = decoder.Decode(empty, raw);
|
const DecodedFrameValue absent = decoder.Decode(empty, raw);
|
||||||
QVERIFY(!absent.valid);
|
QVERIFY(!absent.valid);
|
||||||
QVERIFY(absent.definition == nullptr);
|
QVERIFY(absent.definition == nullptr);
|
||||||
QVERIFY(absent.signals.empty());
|
QVERIFY(absent.signals.empty());
|
||||||
DecodeSignal signal;
|
DecodeSignal signal;
|
||||||
auto database = SingleSignal(signal);
|
DecodeDatabase database = SingleSignal(signal);
|
||||||
QVERIFY(decoder.FindFrame(database, 18U, false) == nullptr);
|
QVERIFY(decoder.FindFrame(database, 18U, false) == nullptr);
|
||||||
QVERIFY(decoder.FindFrame(database, 17U, true) == nullptr);
|
QVERIFY(decoder.FindFrame(database, 17U, true) == nullptr);
|
||||||
raw.isExtended = true;
|
raw.isExtended = true;
|
||||||
@@ -119,7 +120,7 @@ void CoverageTest::frameLookup() {
|
|||||||
QVERIFY(!decoder.Decode(database, raw).valid);
|
QVERIFY(!decoder.Decode(database, raw).valid);
|
||||||
database.frameIndexByKey.at(FrameKey(17U, false)) = 0U;
|
database.frameIndexByKey.at(FrameKey(17U, false)) = 0U;
|
||||||
database.frames[0].signals.clear();
|
database.frames[0].signals.clear();
|
||||||
const auto matched = decoder.Decode(database, raw);
|
const DecodedFrameValue matched = decoder.Decode(database, raw);
|
||||||
QVERIFY(matched.valid);
|
QVERIFY(matched.valid);
|
||||||
QCOMPARE(matched.definition, &database.frames[0]);
|
QCOMPARE(matched.definition, &database.frames[0]);
|
||||||
QVERIFY(matched.signals.empty());
|
QVERIFY(matched.signals.empty());
|
||||||
@@ -130,11 +131,11 @@ void CoverageTest::signedBoundaries_data() {
|
|||||||
QTest::addColumn<unsigned>("length");
|
QTest::addColumn<unsigned>("length");
|
||||||
QTest::addColumn<qlonglong>("expected");
|
QTest::addColumn<qlonglong>("expected");
|
||||||
for (int order = 0; order < 2; ++order) {
|
for (int order = 0; order < 2; ++order) {
|
||||||
for (unsigned length : {1U, 2U, 7U, 8U, 31U, 32U, 63U}) {
|
for (unsigned length: {1U, 2U, 7U, 8U, 31U, 32U, 63U}) {
|
||||||
const std::int64_t half = std::int64_t(1) << (length - 1U);
|
const std::int64_t half = std::int64_t(1) << (length - 1U);
|
||||||
const std::array<std::int64_t, 4> values{{0, -1, -half, half - 1}};
|
const std::array<std::int64_t, 4> values{{0, -1, -half, half - 1}};
|
||||||
for (unsigned index = 0; index < values.size(); ++index) {
|
for (unsigned index = 0; index < values.size(); ++index) {
|
||||||
const auto name = QByteArray::number(order) + "-" + QByteArray::number(length) + "-" + QByteArray::number(index);
|
const QByteArray name = QByteArray::number(order) + "-" + QByteArray::number(length) + "-" + QByteArray::number(index);
|
||||||
QTest::newRow(name.constData()) << order << length << qlonglong(values[index]);
|
QTest::newRow(name.constData()) << order << length << qlonglong(values[index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,8 +160,8 @@ void CoverageTest::signedBoundaries() {
|
|||||||
const unsigned position = order == 0 ? i % 8U : 7U - i % 8U;
|
const unsigned position = order == 0 ? i % 8U : 7U - i % 8U;
|
||||||
raw.data[i / 8U] |= static_cast<std::uint8_t>(((bits >> weight) & 1U) << position);
|
raw.data[i / 8U] |= static_cast<std::uint8_t>(((bits >> weight) & 1U) << position);
|
||||||
}
|
}
|
||||||
const auto database = SingleSignal(signal);
|
const DecodeDatabase database = SingleSignal(signal);
|
||||||
const auto result = DbcDecoder().Decode(database, raw);
|
const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
|
||||||
QVERIFY(result.signals[0].valid);
|
QVERIFY(result.signals[0].valid);
|
||||||
QVERIFY(std::holds_alternative<std::int64_t>(result.signals[0].rawValue));
|
QVERIFY(std::holds_alternative<std::int64_t>(result.signals[0].rawValue));
|
||||||
QCOMPARE(std::get<std::int64_t>(result.signals[0].rawValue), static_cast<std::int64_t>(expected));
|
QCOMPARE(std::get<std::int64_t>(result.signals[0].rawValue), static_cast<std::int64_t>(expected));
|
||||||
@@ -173,7 +174,7 @@ void CoverageTest::extraction_data() {
|
|||||||
for (int order = 0; order < 2; ++order)
|
for (int order = 0; order < 2; ++order)
|
||||||
for (unsigned start = 0; start < 16U; ++start)
|
for (unsigned start = 0; start < 16U; ++start)
|
||||||
for (unsigned length = 1; length <= 64U; ++length) {
|
for (unsigned length = 1; length <= 64U; ++length) {
|
||||||
const auto name = QByteArray::number(order) + "-" + QByteArray::number(start) + "-" + QByteArray::number(length);
|
const QByteArray name = QByteArray::number(order) + "-" + QByteArray::number(start) + "-" + QByteArray::number(length);
|
||||||
QTest::newRow(name.constData()) << order << start << length;
|
QTest::newRow(name.constData()) << order << start << length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,17 +187,18 @@ void CoverageTest::extraction() {
|
|||||||
signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
|
signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
|
||||||
signal.startBit = start;
|
signal.startBit = start;
|
||||||
signal.length = length;
|
signal.length = length;
|
||||||
const auto database = SingleSignal(signal);
|
const DecodeDatabase database = SingleSignal(signal);
|
||||||
RawCanFrame raw;
|
RawCanFrame raw;
|
||||||
raw.canId = 17U;
|
raw.canId = 17U;
|
||||||
const std::vector<std::vector<std::uint8_t>> patterns = {
|
const std::vector<std::vector<std::uint8_t>> patterns = {
|
||||||
std::vector<std::uint8_t>(10U, 0U), std::vector<std::uint8_t>(10U, 255U),
|
std::vector<std::uint8_t>(10U, 0U),
|
||||||
{0xA5, 0x3C, 0x81, 0x7E, 0x12, 0xD4, 0x69, 0xB0, 0x5A, 0xC3}};
|
std::vector<std::uint8_t>(10U, 255U),
|
||||||
for (const auto &data : patterns) {
|
{0xA5, 0x3C, 0x81, 0x7E, 0x12, 0xD4, 0x69, 0xB0, 0x5A, 0xC3}};
|
||||||
|
for (const std::vector<std::uint8_t> &data: patterns) {
|
||||||
raw.data = data;
|
raw.data = data;
|
||||||
const unsigned firstCoordinate = order == 0 ? start : (start / 8U) * 8U + 7U - start % 8U;
|
const unsigned firstCoordinate = order == 0 ? start : (start / 8U) * 8U + 7U - start % 8U;
|
||||||
raw.data.resize((firstCoordinate + length + 7U) / 8U);
|
raw.data.resize((firstCoordinate + length + 7U) / 8U);
|
||||||
const auto decoded = DbcDecoder().Decode(database, raw);
|
const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
|
||||||
QVERIFY(decoded.signals[0].valid);
|
QVERIFY(decoded.signals[0].valid);
|
||||||
QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), ReferenceExtract(data, signal.byteOrder, start, length));
|
QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), ReferenceExtract(data, signal.byteOrder, start, length));
|
||||||
}
|
}
|
||||||
@@ -210,7 +212,7 @@ void CoverageTest::invalidSignalStatus_data() {
|
|||||||
QTest::addColumn<int>("status");
|
QTest::addColumn<int>("status");
|
||||||
for (int order = 0; order < 2; ++order) {
|
for (int order = 0; order < 2; ++order) {
|
||||||
const unsigned start = order == 0 ? 0U : 7U;
|
const unsigned start = order == 0 ? 0U : 7U;
|
||||||
const auto prefix = QByteArray::number(order);
|
const QByteArray prefix = QByteArray::number(order);
|
||||||
QTest::newRow((prefix + "-zero").constData()) << order << start << 0U << 1U << int(SignalDecodeStatus::InvalidDefinition);
|
QTest::newRow((prefix + "-zero").constData()) << order << start << 0U << 1U << int(SignalDecodeStatus::InvalidDefinition);
|
||||||
QTest::newRow((prefix + "-65").constData()) << order << start << 65U << 9U << int(SignalDecodeStatus::InvalidDefinition);
|
QTest::newRow((prefix + "-65").constData()) << order << start << 65U << 9U << int(SignalDecodeStatus::InvalidDefinition);
|
||||||
QTest::newRow((prefix + "-empty").constData()) << order << start << 8U << 0U << int(SignalDecodeStatus::InsufficientPayload);
|
QTest::newRow((prefix + "-empty").constData()) << order << start << 8U << 0U << int(SignalDecodeStatus::InsufficientPayload);
|
||||||
@@ -220,14 +222,20 @@ void CoverageTest::invalidSignalStatus_data() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::invalidSignalStatus() {
|
void CoverageTest::invalidSignalStatus() {
|
||||||
QFETCH(int, order); QFETCH(unsigned, start); QFETCH(unsigned, length);
|
QFETCH(int, order);
|
||||||
QFETCH(unsigned, bytes); QFETCH(int, status);
|
QFETCH(unsigned, start);
|
||||||
|
QFETCH(unsigned, length);
|
||||||
|
QFETCH(unsigned, bytes);
|
||||||
|
QFETCH(int, status);
|
||||||
DecodeSignal signal;
|
DecodeSignal signal;
|
||||||
signal.startBit = start; signal.length = length;
|
signal.startBit = start;
|
||||||
|
signal.length = length;
|
||||||
signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
|
signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
|
||||||
const auto database = SingleSignal(signal);
|
const DecodeDatabase database = SingleSignal(signal);
|
||||||
RawCanFrame raw; raw.canId = 17U; raw.data.assign(bytes, 255U);
|
RawCanFrame raw;
|
||||||
const auto decoded = DbcDecoder().Decode(database, raw);
|
raw.canId = 17U;
|
||||||
|
raw.data.assign(bytes, 255U);
|
||||||
|
const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
|
||||||
QVERIFY(decoded.valid);
|
QVERIFY(decoded.valid);
|
||||||
QVERIFY(!decoded.signals[0].valid);
|
QVERIFY(!decoded.signals[0].valid);
|
||||||
QCOMPARE(int(decoded.signals[0].status), status);
|
QCOMPARE(int(decoded.signals[0].status), status);
|
||||||
@@ -244,19 +252,28 @@ void CoverageTest::malformedRuntimeMultiplexing_data() {
|
|||||||
|
|
||||||
void CoverageTest::malformedRuntimeMultiplexing() {
|
void CoverageTest::malformedRuntimeMultiplexing() {
|
||||||
QFETCH(int, kind);
|
QFETCH(int, kind);
|
||||||
DecodeSignal selector; selector.length = 1U; selector.multiplexing.role = MultiplexRole::Selector;
|
DecodeSignal selector;
|
||||||
auto database = SingleSignal(selector);
|
selector.length = 1U;
|
||||||
DecodeSignal branch; branch.startBit = 8U; branch.length = 8U;
|
selector.multiplexing.role = MultiplexRole::Selector;
|
||||||
branch.multiplexing.role = MultiplexRole::Branch; branch.multiplexing.value = 1U;
|
DecodeDatabase database = SingleSignal(selector);
|
||||||
|
DecodeSignal branch;
|
||||||
|
branch.startBit = 8U;
|
||||||
|
branch.length = 8U;
|
||||||
|
branch.multiplexing.role = MultiplexRole::Branch;
|
||||||
|
branch.multiplexing.value = 1U;
|
||||||
database.frames[0].signals.push_back(branch);
|
database.frames[0].signals.push_back(branch);
|
||||||
DecodeSignal ordinary; ordinary.startBit = 16U; ordinary.length = 8U;
|
DecodeSignal ordinary;
|
||||||
|
ordinary.startBit = 16U;
|
||||||
|
ordinary.length = 8U;
|
||||||
database.frames[0].signals.push_back(ordinary);
|
database.frames[0].signals.push_back(ordinary);
|
||||||
if (kind == 0) database.frames[0].signals.push_back(selector);
|
if (kind == 0) database.frames[0].signals.push_back(selector);
|
||||||
if (kind == 1) database.frames[0].signals[0].valueType = ValueType::Signed;
|
if (kind == 1) database.frames[0].signals[0].valueType = ValueType::Signed;
|
||||||
if (kind == 2) database.frames[0].signals[0].length = 65U;
|
if (kind == 2) database.frames[0].signals[0].length = 65U;
|
||||||
if (kind == 3) database.frames[0].signals[1].multiplexing.value = 2U;
|
if (kind == 3) database.frames[0].signals[1].multiplexing.value = 2U;
|
||||||
RawCanFrame raw; raw.canId = 17U; raw.data = {1U, 42U, 99U};
|
RawCanFrame raw;
|
||||||
const auto result = DbcDecoder().Decode(database, raw);
|
raw.canId = 17U;
|
||||||
|
raw.data = {1U, 42U, 99U};
|
||||||
|
const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
|
||||||
QCOMPARE(int(result.signals[1].status), int(SignalDecodeStatus::InvalidDefinition));
|
QCOMPARE(int(result.signals[1].status), int(SignalDecodeStatus::InvalidDefinition));
|
||||||
QVERIFY(!result.signals[1].valid);
|
QVERIFY(!result.signals[1].valid);
|
||||||
QVERIFY(result.signals[2].valid);
|
QVERIFY(result.signals[2].valid);
|
||||||
@@ -268,28 +285,34 @@ void CoverageTest::malformedRuntimeMultiplexing() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::pgn_data() {
|
void CoverageTest::pgn_data() {
|
||||||
QTest::addColumn<unsigned>("id"); QTest::addColumn<unsigned>("expected"); QTest::addColumn<bool>("hasPgn");
|
QTest::addColumn<unsigned>("id");
|
||||||
|
QTest::addColumn<unsigned>("expected");
|
||||||
|
QTest::addColumn<bool>("hasPgn");
|
||||||
QTest::newRow("standard") << 0x7FFU << 0U << false;
|
QTest::newRow("standard") << 0x7FFU << 0U << false;
|
||||||
for (unsigned dp : {0U, 1U})
|
for (unsigned dp: {0U, 1U})
|
||||||
for (unsigned pf : {239U, 240U})
|
for (unsigned pf: {239U, 240U})
|
||||||
for (unsigned ps : {0U, 0xABU}) {
|
for (unsigned ps: {0U, 0xABU}) {
|
||||||
const unsigned id = 0x80000000U | (dp << 24U) | (pf << 16U) | (ps << 8U) | 0x42U;
|
const unsigned id = 0x80000000U | (dp << 24U) | (pf << 16U) | (ps << 8U) | 0x42U;
|
||||||
const unsigned expected = (dp << 16U) | (pf == 239U ? 0xEF00U : 0xF000U | ps);
|
const unsigned expected = (dp << 16U) | (pf == 239U ? 0xEF00U : 0xF000U | ps);
|
||||||
const auto name = QByteArray::number(dp) + "-" + QByteArray::number(pf) + "-" + QByteArray::number(ps);
|
const QByteArray name = QByteArray::number(dp) + "-" + QByteArray::number(pf) + "-" + QByteArray::number(ps);
|
||||||
QTest::newRow(name.constData()) << id << expected << true;
|
QTest::newRow(name.constData()) << id << expected << true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::pgn() {
|
void CoverageTest::pgn() {
|
||||||
QFETCH(unsigned, id); QFETCH(unsigned, expected); QFETCH(bool, hasPgn);
|
QFETCH(unsigned, id);
|
||||||
const auto parsed = ParseText("BO_ " + std::to_string(id) + " Frame: 8 ECU\n");
|
QFETCH(unsigned, expected);
|
||||||
|
QFETCH(bool, hasPgn);
|
||||||
|
const DbcDatabase parsed = ParseText("BO_ " + std::to_string(id) + " Frame: 8 ECU\n");
|
||||||
QCOMPARE(parsed.frames[0].hasPgn, hasPgn);
|
QCOMPARE(parsed.frames[0].hasPgn, hasPgn);
|
||||||
QCOMPARE(parsed.frames[0].pgn, expected);
|
QCOMPARE(parsed.frames[0].pgn, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::comments_data() {
|
void CoverageTest::comments_data() {
|
||||||
QTest::addColumn<QString>("before"); QTest::addColumn<QString>("after");
|
QTest::addColumn<QString>("before");
|
||||||
QTest::addColumn<QString>("frameComment"); QTest::addColumn<QString>("signalComment");
|
QTest::addColumn<QString>("after");
|
||||||
|
QTest::addColumn<QString>("frameComment");
|
||||||
|
QTest::addColumn<QString>("signalComment");
|
||||||
QTest::newRow("tabs") << QString::fromUtf8("") << QString::fromUtf8("CM_\tBO_ 1 \"Frame\";\nCM_\tSG_ 1 State \"Signal\";\n") << QString::fromUtf8("Frame") << QString::fromUtf8("Signal");
|
QTest::newRow("tabs") << QString::fromUtf8("") << QString::fromUtf8("CM_\tBO_ 1 \"Frame\";\nCM_\tSG_ 1 State \"Signal\";\n") << QString::fromUtf8("Frame") << QString::fromUtf8("Signal");
|
||||||
QTest::newRow("empty-overwrite") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 \"Old\";\nCM_ BO_ 1 \"\";\nCM_ SG_ 1 State \"Old\";\nCM_ SG_ 1 State \"\";\n") << QString::fromUtf8("") << QString::fromUtf8("");
|
QTest::newRow("empty-overwrite") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 \"Old\";\nCM_ BO_ 1 \"\";\nCM_ SG_ 1 State \"Old\";\nCM_ SG_ 1 State \"\";\n") << QString::fromUtf8("") << QString::fromUtf8("");
|
||||||
QTest::newRow("last-wins") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 \"First\";\nCM_ BO_ 1 \"Last\";\nCM_ SG_ 1 State \"First\";\nCM_ SG_ 1 State \"Last\";\n") << QString::fromUtf8("Last") << QString::fromUtf8("Last");
|
QTest::newRow("last-wins") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 \"First\";\nCM_ BO_ 1 \"Last\";\nCM_ SG_ 1 State \"First\";\nCM_ SG_ 1 State \"Last\";\n") << QString::fromUtf8("Last") << QString::fromUtf8("Last");
|
||||||
@@ -300,8 +323,11 @@ void CoverageTest::comments_data() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::comments() {
|
void CoverageTest::comments() {
|
||||||
QFETCH(QString, before); QFETCH(QString, after); QFETCH(QString, frameComment); QFETCH(QString, signalComment);
|
QFETCH(QString, before);
|
||||||
const auto parsed = ParseText(before.toStdString() + "BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n" + after.toStdString());
|
QFETCH(QString, after);
|
||||||
|
QFETCH(QString, frameComment);
|
||||||
|
QFETCH(QString, signalComment);
|
||||||
|
const DbcDatabase parsed = ParseText(before.toStdString() + "BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n" + after.toStdString());
|
||||||
QCOMPARE(parsed.frames[0].comment, frameComment.toStdString());
|
QCOMPARE(parsed.frames[0].comment, frameComment.toStdString());
|
||||||
QCOMPARE(parsed.frames[0].signals[0].comment, signalComment.toStdString());
|
QCOMPARE(parsed.frames[0].signals[0].comment, signalComment.toStdString());
|
||||||
}
|
}
|
||||||
@@ -384,12 +410,12 @@ void CoverageTest::ambiguousValueReferences() {
|
|||||||
QFAIL("The resolver must reject an ambiguous target");
|
QFAIL("The resolver must reject an ambiguous target");
|
||||||
} catch (const std::runtime_error &error) {
|
} catch (const std::runtime_error &error) {
|
||||||
const std::string message = duplicateFrames ? "Ambiguous VAL_ frame reference."
|
const std::string message = duplicateFrames ? "Ambiguous VAL_ frame reference."
|
||||||
: "Ambiguous VAL_ signal reference: State";
|
: "Ambiguous VAL_ signal reference: State";
|
||||||
QCOMPARE(std::string(error.what()), path + ":37: " + message);
|
QCOMPARE(std::string(error.what()), path + ":37: " + message);
|
||||||
}
|
}
|
||||||
// Ambiguity must be detected before applying any descriptions.
|
// Ambiguity must be detected before applying any descriptions.
|
||||||
for (const auto &candidate : database.frames)
|
for (const FrameInfo &candidate: database.frames)
|
||||||
for (const auto &candidateSignal : candidate.signals)
|
for (const SignalInfo &candidateSignal: candidate.signals)
|
||||||
QVERIFY(candidateSignal.valueDescriptions.empty());
|
QVERIFY(candidateSignal.valueDescriptions.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,8 +441,8 @@ void CoverageTest::valueReferenceScope() {
|
|||||||
handler.Handle("VAL_ 2147483649 State 0 \"Extended\";", context);
|
handler.Handle("VAL_ 2147483649 State 0 \"Extended\";", context);
|
||||||
context.ResolveValueDescriptions("manual database.dbc");
|
context.ResolveValueDescriptions("manual database.dbc");
|
||||||
|
|
||||||
const auto *standard = FindValueDescription(database.frames[0].signals[0].valueDescriptions, std::uint64_t(0));
|
const std::string *standard = FindValueDescription(database.frames[0].signals[0].valueDescriptions, std::uint64_t(0));
|
||||||
const auto *extended = FindValueDescription(database.frames[1].signals[0].valueDescriptions, std::uint64_t(0));
|
const std::string *extended = FindValueDescription(database.frames[1].signals[0].valueDescriptions, std::uint64_t(0));
|
||||||
QVERIFY(standard != nullptr);
|
QVERIFY(standard != nullptr);
|
||||||
QVERIFY(extended != nullptr);
|
QVERIFY(extended != nullptr);
|
||||||
QCOMPARE(*standard, std::string("Standard"));
|
QCOMPARE(*standard, std::string("Standard"));
|
||||||
@@ -425,7 +451,8 @@ void CoverageTest::valueReferenceScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::fileFormatting_data() {
|
void CoverageTest::fileFormatting_data() {
|
||||||
QTest::addColumn<QString>("text"); QTest::addColumn<unsigned>("frames");
|
QTest::addColumn<QString>("text");
|
||||||
|
QTest::addColumn<unsigned>("frames");
|
||||||
QTest::newRow("empty") << QString::fromUtf8("") << 0U;
|
QTest::newRow("empty") << QString::fromUtf8("") << 0U;
|
||||||
QTest::newRow("whitespace") << QString::fromUtf8(" \t\r\n\n\t") << 0U;
|
QTest::newRow("whitespace") << QString::fromUtf8(" \t\r\n\n\t") << 0U;
|
||||||
QTest::newRow("unknown-only") << QString::fromUtf8("VERSION \"test\"\nBU_: ECU\nUNKNOWN_ data\n") << 0U;
|
QTest::newRow("unknown-only") << QString::fromUtf8("VERSION \"test\"\nBU_: ECU\nUNKNOWN_ data\n") << 0U;
|
||||||
@@ -434,14 +461,15 @@ void CoverageTest::fileFormatting_data() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::fileFormatting() {
|
void CoverageTest::fileFormatting() {
|
||||||
QFETCH(QString, text); QFETCH(unsigned, frames);
|
QFETCH(QString, text);
|
||||||
const auto parsed = ParseText(text.toStdString());
|
QFETCH(unsigned, frames);
|
||||||
|
const DbcDatabase parsed = ParseText(text.toStdString());
|
||||||
QCOMPARE(parsed.frames.size(), std::size_t(frames));
|
QCOMPARE(parsed.frames.size(), std::size_t(frames));
|
||||||
if (frames) {
|
if (frames) {
|
||||||
QCOMPARE(parsed.frames[0].signals.size(), std::size_t(1));
|
QCOMPARE(parsed.frames[0].signals.size(), std::size_t(1));
|
||||||
if (text.contains("\r\nBO_")) {
|
if (text.contains("\r\nBO_")) {
|
||||||
QCOMPARE(parsed.frames[0].comment, std::string("Comment"));
|
QCOMPARE(parsed.frames[0].comment, std::string("Comment"));
|
||||||
const auto *description = FindValueDescription(parsed.frames[0].signals[0].valueDescriptions, std::uint64_t(0));
|
const std::string *description = FindValueDescription(parsed.frames[0].signals[0].valueDescriptions, std::uint64_t(0));
|
||||||
QVERIFY(description != nullptr);
|
QVERIFY(description != nullptr);
|
||||||
QCOMPARE(*description, std::string("Off"));
|
QCOMPARE(*description, std::string("Off"));
|
||||||
}
|
}
|
||||||
@@ -451,31 +479,33 @@ void CoverageTest::fileFormatting() {
|
|||||||
void CoverageTest::missingFile() {
|
void CoverageTest::missingFile() {
|
||||||
QTemporaryFile file;
|
QTemporaryFile file;
|
||||||
QVERIFY(file.open());
|
QVERIFY(file.open());
|
||||||
const auto path = file.fileName();
|
const QString path = file.fileName();
|
||||||
QVERIFY(file.remove());
|
QVERIFY(file.remove());
|
||||||
try { DbcParser().ParseFile(path.toStdString()); QFAIL("Missing file must throw"); }
|
try {
|
||||||
catch (const std::runtime_error &error) {
|
DbcParser().ParseFile(path.toStdString());
|
||||||
|
QFAIL("Missing file must throw");
|
||||||
|
} catch (const std::runtime_error &error) {
|
||||||
QCOMPARE(std::string(error.what()), "Failed to open DBC file: " + path.toStdString());
|
QCOMPARE(std::string(error.what()), "Failed to open DBC file: " + path.toStdString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::builders() {
|
void CoverageTest::builders() {
|
||||||
const auto emptyRuntime = DbcDecodeBuilder().Build(DbcDatabase());
|
const DecodeDatabase emptyRuntime = DbcDecodeBuilder().Build(DbcDatabase());
|
||||||
QVERIFY(emptyRuntime.frames.empty());
|
QVERIFY(emptyRuntime.frames.empty());
|
||||||
QVERIFY(emptyRuntime.frameIndexByKey.empty());
|
QVERIFY(emptyRuntime.frameIndexByKey.empty());
|
||||||
const auto emptyTree = DbcTreeBuilder().Build(DbcDatabase());
|
const std::unique_ptr<TreeNode> emptyTree = DbcTreeBuilder().Build(DbcDatabase());
|
||||||
QCOMPARE(emptyTree->GetType(), NodeType::Root);
|
QCOMPARE(emptyTree->GetType(), NodeType::Root);
|
||||||
QCOMPARE(emptyTree->GetName(), std::string("dbc"));
|
QCOMPARE(emptyTree->GetName(), std::string("dbc"));
|
||||||
QCOMPARE(emptyTree->GetChildCount(), std::size_t(0));
|
QCOMPARE(emptyTree->GetChildCount(), std::size_t(0));
|
||||||
QVERIFY(emptyTree->GetFrame() == nullptr && emptyTree->GetSignal() == nullptr);
|
QVERIFY(emptyTree->GetFrame() == nullptr && emptyTree->GetSignal() == nullptr);
|
||||||
const auto expected = ParseText(
|
const DbcDatabase expected = ParseText(
|
||||||
"BO_ 256 Standard: 3 ECU1\n"
|
"BO_ 256 Standard: 3 ECU1\n"
|
||||||
"SG_ Mode M : 0|8@1+ (2,3) [0|513] \"mode\" ECU1,ECU2\n"
|
"SG_ Mode M : 0|8@1+ (2,3) [0|513] \"mode\" ECU1,ECU2\n"
|
||||||
"SG_ Branch m1 : 15|8@0- (-2,5) [-249|261] \"rpm\" ECU2,ECU3\n"
|
"SG_ Branch m1 : 15|8@0- (-2,5) [-249|261] \"rpm\" ECU2,ECU3\n"
|
||||||
"SG_ Always : 16|8@1+ (0.25,-1) [-1|62.75] \"V\" ECU3\n"
|
"SG_ Always : 16|8@1+ (0.25,-1) [-1|62.75] \"V\" ECU3\n"
|
||||||
"CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Branch \"Branch comment\";\n"
|
"CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Branch \"Branch comment\";\n"
|
||||||
"VAL_ 256 Branch -1 \"Negative\" 1 \"Positive\";\n"
|
"VAL_ 256 Branch -1 \"Negative\" 1 \"Positive\";\n"
|
||||||
"BO_ 2163212866 Extended: 8 ECU4\n");
|
"BO_ 2163212866 Extended: 8 ECU4\n");
|
||||||
DecodeDatabase runtime;
|
DecodeDatabase runtime;
|
||||||
std::unique_ptr<TreeNode> tree;
|
std::unique_ptr<TreeNode> tree;
|
||||||
{
|
{
|
||||||
@@ -500,44 +530,62 @@ void CoverageTest::builders() {
|
|||||||
QVERIFY(node->GetSignal() == nullptr);
|
QVERIFY(node->GetSignal() == nullptr);
|
||||||
QCOMPARE(runtime.frameIndexByKey.at(FrameKey(frame.canId, frame.isExtended)), i);
|
QCOMPARE(runtime.frameIndexByKey.at(FrameKey(frame.canId, frame.isExtended)), i);
|
||||||
const FrameInfo &copied = *node->GetFrame();
|
const FrameInfo &copied = *node->GetFrame();
|
||||||
QCOMPARE(copied.name, frame.name); QCOMPARE(decodedFrame.name, frame.name);
|
QCOMPARE(copied.name, frame.name);
|
||||||
QCOMPARE(copied.canId, frame.canId); QCOMPARE(decodedFrame.canId, frame.canId);
|
QCOMPARE(decodedFrame.name, frame.name);
|
||||||
QCOMPARE(copied.isExtended, frame.isExtended); QCOMPARE(decodedFrame.isExtended, frame.isExtended);
|
QCOMPARE(copied.canId, frame.canId);
|
||||||
QCOMPARE(copied.dlc, frame.dlc); QCOMPARE(decodedFrame.dlc, frame.dlc);
|
QCOMPARE(decodedFrame.canId, frame.canId);
|
||||||
QCOMPARE(copied.pgn, frame.pgn); QCOMPARE(decodedFrame.pgn, frame.pgn);
|
QCOMPARE(copied.isExtended, frame.isExtended);
|
||||||
QCOMPARE(copied.hasPgn, frame.hasPgn); QCOMPARE(decodedFrame.hasPgn, frame.hasPgn);
|
QCOMPARE(decodedFrame.isExtended, frame.isExtended);
|
||||||
QCOMPARE(copied.transmitter, frame.transmitter); QCOMPARE(decodedFrame.transmitter, frame.transmitter);
|
QCOMPARE(copied.dlc, frame.dlc);
|
||||||
QCOMPARE(copied.comment, frame.comment); QCOMPARE(decodedFrame.comment, frame.comment);
|
QCOMPARE(decodedFrame.dlc, frame.dlc);
|
||||||
|
QCOMPARE(copied.pgn, frame.pgn);
|
||||||
|
QCOMPARE(decodedFrame.pgn, frame.pgn);
|
||||||
|
QCOMPARE(copied.hasPgn, frame.hasPgn);
|
||||||
|
QCOMPARE(decodedFrame.hasPgn, frame.hasPgn);
|
||||||
|
QCOMPARE(copied.transmitter, frame.transmitter);
|
||||||
|
QCOMPARE(decodedFrame.transmitter, frame.transmitter);
|
||||||
|
QCOMPARE(copied.comment, frame.comment);
|
||||||
|
QCOMPARE(decodedFrame.comment, frame.comment);
|
||||||
QCOMPARE(node->GetChildCount(), frame.signals.size());
|
QCOMPARE(node->GetChildCount(), frame.signals.size());
|
||||||
QCOMPARE(copied.signals.size(), frame.signals.size());
|
QCOMPARE(copied.signals.size(), frame.signals.size());
|
||||||
QCOMPARE(decodedFrame.signals.size(), frame.signals.size());
|
QCOMPARE(decodedFrame.signals.size(), frame.signals.size());
|
||||||
for (std::size_t j = 0; j < frame.signals.size(); ++j) {
|
for (std::size_t j = 0; j < frame.signals.size(); ++j) {
|
||||||
const auto &original = frame.signals[j];
|
const SignalInfo &original = frame.signals[j];
|
||||||
const auto &target = decodedFrame.signals[j];
|
const DecodeSignal &target = decodedFrame.signals[j];
|
||||||
const auto *leaf = node->GetChild(j);
|
const TreeNode *leaf = node->GetChild(j);
|
||||||
QVERIFY(leaf && leaf->GetSignal());
|
QVERIFY(leaf && leaf->GetSignal());
|
||||||
QCOMPARE(leaf->GetType(), NodeType::Signal);
|
QCOMPARE(leaf->GetType(), NodeType::Signal);
|
||||||
QCOMPARE(leaf->GetName(), original.name);
|
QCOMPARE(leaf->GetName(), original.name);
|
||||||
QVERIFY(leaf->GetFrame() == nullptr);
|
QVERIFY(leaf->GetFrame() == nullptr);
|
||||||
// Check both the frame payload copy and each child payload copy.
|
// Check both the frame payload copy and each child payload copy.
|
||||||
for (const SignalInfo *copy : {&copied.signals[j], leaf->GetSignal()}) {
|
for (const SignalInfo *copy: {&copied.signals[j], leaf->GetSignal()}) {
|
||||||
QCOMPARE(copy->name, original.name); QCOMPARE(copy->startBit, original.startBit);
|
QCOMPARE(copy->name, original.name);
|
||||||
QCOMPARE(copy->length, original.length); QCOMPARE(copy->isLittleEndian, original.isLittleEndian);
|
QCOMPARE(copy->startBit, original.startBit);
|
||||||
QCOMPARE(copy->isSigned, original.isSigned); QCOMPARE(copy->factor, original.factor);
|
QCOMPARE(copy->length, original.length);
|
||||||
QCOMPARE(copy->offset, original.offset); QCOMPARE(copy->minimum, original.minimum);
|
QCOMPARE(copy->isLittleEndian, original.isLittleEndian);
|
||||||
QCOMPARE(copy->maximum, original.maximum); QCOMPARE(copy->unit, original.unit);
|
QCOMPARE(copy->isSigned, original.isSigned);
|
||||||
QCOMPARE(copy->comment, original.comment); QVERIFY(copy->receivers == original.receivers);
|
QCOMPARE(copy->factor, original.factor);
|
||||||
|
QCOMPARE(copy->offset, original.offset);
|
||||||
|
QCOMPARE(copy->minimum, original.minimum);
|
||||||
|
QCOMPARE(copy->maximum, original.maximum);
|
||||||
|
QCOMPARE(copy->unit, original.unit);
|
||||||
|
QCOMPARE(copy->comment, original.comment);
|
||||||
|
QVERIFY(copy->receivers == original.receivers);
|
||||||
QVERIFY(copy->valueDescriptions == original.valueDescriptions);
|
QVERIFY(copy->valueDescriptions == original.valueDescriptions);
|
||||||
QCOMPARE(copy->multiplexing.role, original.multiplexing.role);
|
QCOMPARE(copy->multiplexing.role, original.multiplexing.role);
|
||||||
QCOMPARE(copy->multiplexing.value, original.multiplexing.value);
|
QCOMPARE(copy->multiplexing.value, original.multiplexing.value);
|
||||||
}
|
}
|
||||||
QCOMPARE(target.name, original.name); QCOMPARE(target.startBit, original.startBit);
|
QCOMPARE(target.name, original.name);
|
||||||
|
QCOMPARE(target.startBit, original.startBit);
|
||||||
QCOMPARE(target.length, original.length);
|
QCOMPARE(target.length, original.length);
|
||||||
QCOMPARE(target.byteOrder, original.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola);
|
QCOMPARE(target.byteOrder, original.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola);
|
||||||
QCOMPARE(target.valueType, original.isSigned ? ValueType::Signed : ValueType::Unsigned);
|
QCOMPARE(target.valueType, original.isSigned ? ValueType::Signed : ValueType::Unsigned);
|
||||||
QCOMPARE(target.factor, original.factor); QCOMPARE(target.offset, original.offset);
|
QCOMPARE(target.factor, original.factor);
|
||||||
QCOMPARE(target.minimum, original.minimum); QCOMPARE(target.maximum, original.maximum);
|
QCOMPARE(target.offset, original.offset);
|
||||||
QCOMPARE(target.unit, original.unit); QCOMPARE(target.comment, original.comment);
|
QCOMPARE(target.minimum, original.minimum);
|
||||||
|
QCOMPARE(target.maximum, original.maximum);
|
||||||
|
QCOMPARE(target.unit, original.unit);
|
||||||
|
QCOMPARE(target.comment, original.comment);
|
||||||
QVERIFY(target.receivers == original.receivers);
|
QVERIFY(target.receivers == original.receivers);
|
||||||
QVERIFY(target.valueDescriptions == original.valueDescriptions);
|
QVERIFY(target.valueDescriptions == original.valueDescriptions);
|
||||||
QCOMPARE(target.multiplexing.role, original.multiplexing.role);
|
QCOMPARE(target.multiplexing.role, original.multiplexing.role);
|
||||||
@@ -547,93 +595,122 @@ void CoverageTest::builders() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::physicalConversion_data() {
|
void CoverageTest::physicalConversion_data() {
|
||||||
QTest::addColumn<double>("factor"); QTest::addColumn<double>("offset"); QTest::addColumn<double>("expected");
|
QTest::addColumn<double>("factor");
|
||||||
|
QTest::addColumn<double>("offset");
|
||||||
|
QTest::addColumn<double>("expected");
|
||||||
QTest::newRow("zero-factor") << 0.0 << 7.0 << 7.0;
|
QTest::newRow("zero-factor") << 0.0 << 7.0 << 7.0;
|
||||||
QTest::newRow("negative-factor") << -2.0 << 5.0 << -15.0;
|
QTest::newRow("negative-factor") << -2.0 << 5.0 << -15.0;
|
||||||
QTest::newRow("above-declared-range") << 2.0 << 3.0 << 23.0;
|
QTest::newRow("above-declared-range") << 2.0 << 3.0 << 23.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::physicalConversion() {
|
void CoverageTest::physicalConversion() {
|
||||||
QFETCH(double, factor); QFETCH(double, offset); QFETCH(double, expected);
|
QFETCH(double, factor);
|
||||||
DecodeSignal signal; signal.length = 8U; signal.factor = factor; signal.offset = offset;
|
QFETCH(double, offset);
|
||||||
signal.minimum = 0.0; signal.maximum = 1.0;
|
QFETCH(double, expected);
|
||||||
const auto database = SingleSignal(signal);
|
DecodeSignal signal;
|
||||||
RawCanFrame raw; raw.canId = 17U; raw.data = {10U};
|
signal.length = 8U;
|
||||||
const auto decoded = DbcDecoder().Decode(database, raw);
|
signal.factor = factor;
|
||||||
|
signal.offset = offset;
|
||||||
|
signal.minimum = 0.0;
|
||||||
|
signal.maximum = 1.0;
|
||||||
|
const DecodeDatabase database = SingleSignal(signal);
|
||||||
|
RawCanFrame raw;
|
||||||
|
raw.canId = 17U;
|
||||||
|
raw.data = {10U};
|
||||||
|
const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
|
||||||
QVERIFY(decoded.signals[0].valid);
|
QVERIFY(decoded.signals[0].valid);
|
||||||
QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), std::uint64_t(10));
|
QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), std::uint64_t(10));
|
||||||
QCOMPARE(decoded.signals[0].physicalValue, expected);
|
QCOMPARE(decoded.signals[0].physicalValue, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::multiplexingScenarios() {
|
void CoverageTest::multiplexingScenarios() {
|
||||||
const auto selectorOnly = DbcDecodeBuilder().Build(ParseText(
|
const DecodeDatabase selectorOnly = DbcDecodeBuilder().Build(ParseText(
|
||||||
"BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"));
|
"BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"));
|
||||||
RawCanFrame raw; raw.canId = 1U; raw.data = {1U};
|
RawCanFrame raw;
|
||||||
const auto only = DbcDecoder().Decode(selectorOnly, raw);
|
raw.canId = 1U;
|
||||||
|
raw.data = {1U};
|
||||||
|
const DecodedFrameValue only = DbcDecoder().Decode(selectorOnly, raw);
|
||||||
QCOMPARE(only.signals.size(), std::size_t(1));
|
QCOMPARE(only.signals.size(), std::size_t(1));
|
||||||
QVERIFY(only.signals[0].valid);
|
QVERIFY(only.signals[0].valid);
|
||||||
const auto database = DbcDecodeBuilder().Build(ParseText(
|
const DecodeDatabase database = DbcDecodeBuilder().Build(ParseText(
|
||||||
"BO_ 1 First: 3 ECU\n"
|
"BO_ 1 First: 3 ECU\n"
|
||||||
"SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
|
"SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
|
||||||
"SG_ A m0 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
|
"SG_ A m0 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
|
||||||
"SG_ B m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
|
"SG_ B m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
|
||||||
"BO_ 2 Second: 2 ECU\n"
|
"BO_ 2 Second: 2 ECU\n"
|
||||||
"SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
|
"SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
|
||||||
"SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"));
|
"SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"));
|
||||||
raw.data = {0U, 42U, 99U};
|
raw.data = {0U, 42U, 99U};
|
||||||
const auto first = DbcDecoder().Decode(database, raw);
|
const DecodedFrameValue first = DbcDecoder().Decode(database, raw);
|
||||||
QVERIFY(first.signals[1].valid && first.signals[2].valid);
|
QVERIFY(first.signals[1].valid && first.signals[2].valid);
|
||||||
QCOMPARE(std::get<std::uint64_t>(first.signals[1].rawValue), std::uint64_t(42));
|
QCOMPARE(std::get<std::uint64_t>(first.signals[1].rawValue), std::uint64_t(42));
|
||||||
QCOMPARE(std::get<std::uint64_t>(first.signals[2].rawValue), std::uint64_t(99));
|
QCOMPARE(std::get<std::uint64_t>(first.signals[2].rawValue), std::uint64_t(99));
|
||||||
raw.canId = 2U;
|
raw.canId = 2U;
|
||||||
const auto secondInactive = DbcDecoder().Decode(database, raw);
|
const DecodedFrameValue secondInactive = DbcDecoder().Decode(database, raw);
|
||||||
QCOMPARE(secondInactive.signals[1].status, SignalDecodeStatus::Inactive);
|
QCOMPARE(secondInactive.signals[1].status, SignalDecodeStatus::Inactive);
|
||||||
raw.data[0] = 1U;
|
raw.data[0] = 1U;
|
||||||
QVERIFY(DbcDecoder().Decode(database, raw).signals[1].valid);
|
QVERIFY(DbcDecoder().Decode(database, raw).signals[1].valid);
|
||||||
raw.canId = 1U;
|
raw.canId = 1U;
|
||||||
const auto firstInactive = DbcDecoder().Decode(database, raw);
|
const DecodedFrameValue firstInactive = DbcDecoder().Decode(database, raw);
|
||||||
QCOMPARE(firstInactive.signals[1].status, SignalDecodeStatus::Inactive);
|
QCOMPARE(firstInactive.signals[1].status, SignalDecodeStatus::Inactive);
|
||||||
QCOMPARE(firstInactive.signals[2].status, SignalDecodeStatus::Inactive);
|
QCOMPARE(firstInactive.signals[2].status, SignalDecodeStatus::Inactive);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::multiplexingErrorLocations_data() {
|
void CoverageTest::multiplexingErrorLocations_data() {
|
||||||
QTest::addColumn<QString>("text"); QTest::addColumn<unsigned>("line"); QTest::addColumn<QString>("message");
|
QTest::addColumn<QString>("text");
|
||||||
|
QTest::addColumn<unsigned>("line");
|
||||||
|
QTest::addColumn<QString>("message");
|
||||||
const QString header = "BO_ 1 Frame: 3 ECU\n\n";
|
const QString header = "BO_ 1 Frame: 3 ECU\n\n";
|
||||||
const QString selector = "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n";
|
const QString selector = "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n";
|
||||||
const QString branch = "SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
|
const QString branch = "SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
|
||||||
QTest::newRow("missing-selector") << header + branch << 3U << QString::fromUtf8("Multiplexed branch has no selector.");
|
QTest::newRow("missing-selector") << header + branch << 3U << QString::fromUtf8("Multiplexed branch has no selector.");
|
||||||
QTest::newRow("second-selector") << header + selector + "\nSG_ Other M : 16|1@1+ (1,0) [0|1] \"\" ECU\n" << 5U << QString::fromUtf8("Multiplexing requires one unsigned selector per frame.");
|
QTest::newRow("second-selector") << header + selector + "\nSG_ Other M : 16|1@1+ (1,0) [0|1] \"\" ECU\n"
|
||||||
QTest::newRow("signed-selector") << header + "SG_ Mode M : 0|1@1- (1,0) [-1|0] \"\" ECU\n" << 3U << QString::fromUtf8("Multiplexing requires one unsigned selector per frame.");
|
<< 5U << QString::fromUtf8("Multiplexing requires one unsigned selector per frame.");
|
||||||
|
QTest::newRow("signed-selector") << header + "SG_ Mode M : 0|1@1- (1,0) [-1|0] \"\" ECU\n"
|
||||||
|
<< 3U << QString::fromUtf8("Multiplexing requires one unsigned selector per frame.");
|
||||||
QTest::newRow("branch-range") << header + "SG_ A m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n" + selector << 3U << QString::fromUtf8("Branch value exceeds selector raw range.");
|
QTest::newRow("branch-range") << header + "SG_ A m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n" + selector << 3U << QString::fromUtf8("Branch value exceeds selector raw range.");
|
||||||
QTest::newRow("same-branch-overlap") << header + selector + branch + "SG_ B m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n" << 5U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
|
QTest::newRow("same-branch-overlap") << header + selector + branch + "SG_ B m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
|
||||||
QTest::newRow("selector-overlap") << header + selector + "SG_ A m1 : 0|1@1+ (1,0) [0|1] \"\" ECU\n" << 4U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
|
<< 5U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
|
||||||
QTest::newRow("unconditional-overlap") << header + selector + branch + "SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n" << 5U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
|
QTest::newRow("selector-overlap") << header + selector + "SG_ A m1 : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
|
||||||
QTest::newRow("ordinary-overlap") << header + "SG_ A : 8|8@1+ (1,0) [0|255] \"\" ECU\nSG_ B : 15|8@0+ (1,0) [0|255] \"\" ECU\n" << 4U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
|
<< 4U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
|
||||||
QTest::newRow("extended-multiplexing") << header + "SG_MUL_VAL_ 1 A Mode 1-2;\n" << 3U << QString::fromUtf8("Extended and nested multiplexing are unsupported.");
|
QTest::newRow("unconditional-overlap") << header + selector + branch + "SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
|
||||||
QTest::newRow("nested-marker") << header + "SG_ A m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n" << 3U << QString::fromUtf8("Invalid or unsupported multiplexing marker.");
|
<< 5U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
|
||||||
|
QTest::newRow("ordinary-overlap") << header + "SG_ A : 8|8@1+ (1,0) [0|255] \"\" ECU\nSG_ B : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
|
||||||
|
<< 4U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
|
||||||
|
QTest::newRow("extended-multiplexing") << header + "SG_MUL_VAL_ 1 A Mode 1-2;\n"
|
||||||
|
<< 3U << QString::fromUtf8("Extended and nested multiplexing are unsupported.");
|
||||||
|
QTest::newRow("nested-marker") << header + "SG_ A m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
|
||||||
|
<< 3U << QString::fromUtf8("Invalid or unsupported multiplexing marker.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::multiplexingErrorLocations() {
|
void CoverageTest::multiplexingErrorLocations() {
|
||||||
QFETCH(QString, text); QFETCH(unsigned, line); QFETCH(QString, message);
|
QFETCH(QString, text);
|
||||||
QTemporaryFile file; QVERIFY(file.open());
|
QFETCH(unsigned, line);
|
||||||
|
QFETCH(QString, message);
|
||||||
|
QTemporaryFile file;
|
||||||
|
QVERIFY(file.open());
|
||||||
const QByteArray content = text.toUtf8();
|
const QByteArray content = text.toUtf8();
|
||||||
QCOMPARE(file.write(content), qint64(content.size())); QVERIFY(file.flush());
|
QCOMPARE(file.write(content), qint64(content.size()));
|
||||||
try { DbcParser().ParseFile(file.fileName().toStdString()); QFAIL("Expected multiplexing rejection"); }
|
QVERIFY(file.flush());
|
||||||
catch (const std::runtime_error &error) {
|
try {
|
||||||
|
DbcParser().ParseFile(file.fileName().toStdString());
|
||||||
|
QFAIL("Expected multiplexing rejection");
|
||||||
|
} catch (const std::runtime_error &error) {
|
||||||
QCOMPARE(QString::fromStdString(error.what()), file.fileName() + ":" + QString::number(line) + ": " + message);
|
QCOMPARE(QString::fromStdString(error.what()), file.fileName() + ":" + QString::number(line) + ": " + message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoverageTest::escapedValueDescriptions() {
|
void CoverageTest::escapedValueDescriptions() {
|
||||||
const auto parsed = ParseText(
|
const DbcDatabase parsed = ParseText(
|
||||||
"BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
|
"BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
|
||||||
R"dbc(VAL_ 1 State -0 "Ноль" 1 "C:\\trace" 2 "\\\"quoted\"\\";)dbc" "\n");
|
R"dbc(VAL_ 1 State -0 "Ноль" 1 "C:\\trace" 2 "\\\"quoted\"\\";)dbc"
|
||||||
const auto &values = parsed.frames[0].signals[0].valueDescriptions;
|
"\n");
|
||||||
|
const ValueDescriptions &values = parsed.frames[0].signals[0].valueDescriptions;
|
||||||
QCOMPARE(values.size(), std::size_t(3));
|
QCOMPARE(values.size(), std::size_t(3));
|
||||||
QVERIFY(values.find(RawSignalValue(std::int64_t(0))) == values.end());
|
QVERIFY(values.find(RawSignalValue(std::int64_t(0))) == values.end());
|
||||||
const auto *zero = FindValueDescription(values, std::int64_t(0));
|
const std::string *zero = FindValueDescription(values, std::int64_t(0));
|
||||||
const auto *path = FindValueDescription(values, std::uint64_t(1));
|
const std::string *path = FindValueDescription(values, std::uint64_t(1));
|
||||||
const auto *quoted = FindValueDescription(values, std::uint64_t(2));
|
const std::string *quoted = FindValueDescription(values, std::uint64_t(2));
|
||||||
QVERIFY(zero && path && quoted);
|
QVERIFY(zero && path && quoted);
|
||||||
QCOMPARE(*zero, std::string("Ноль"));
|
QCOMPARE(*zero, std::string("Ноль"));
|
||||||
QCOMPARE(*path, std::string("C:\\trace"));
|
QCOMPARE(*path, std::string("C:\\trace"));
|
||||||
@@ -649,11 +726,11 @@ void CoverageTest::factory() {
|
|||||||
QVERIFY_THROWS_EXCEPTION(std::out_of_range, static_cast<void>(factory.create("missing", std::make_unique<int>(1))));
|
QVERIFY_THROWS_EXCEPTION(std::out_of_range, static_cast<void>(factory.create("missing", std::make_unique<int>(1))));
|
||||||
factory.registerType<FactoryFirst>("value");
|
factory.registerType<FactoryFirst>("value");
|
||||||
QVERIFY(factory.contains("value"));
|
QVERIFY(factory.contains("value"));
|
||||||
auto argument = std::make_unique<int>(42);
|
std::unique_ptr<int> argument = std::make_unique<int>(42);
|
||||||
const auto first = factory.create("value", std::move(argument));
|
const std::shared_ptr<FactoryBase> first = factory.create("value", std::move(argument));
|
||||||
QVERIFY(argument == nullptr);
|
QVERIFY(argument == nullptr);
|
||||||
QCOMPARE(first->Value(), 42);
|
QCOMPARE(first->Value(), 42);
|
||||||
const auto creator = factory.creator("value");
|
const TypeFactory<std::string, FactoryBase, std::unique_ptr<int>>::CreatorFn creator = factory.creator("value");
|
||||||
QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
|
QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
|
||||||
factory.registerType<FactorySecond>("value");
|
factory.registerType<FactorySecond>("value");
|
||||||
QCOMPARE(factory.create("value", std::make_unique<int>(42))->Value(), 43);
|
QCOMPARE(factory.create("value", std::make_unique<int>(42))->Value(), 43);
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
#include "dbc_tree_builder.h"
|
#include "dbc_tree_builder.h"
|
||||||
#include "dbc_tree_model.h"
|
#include "dbc_tree_model.h"
|
||||||
#include <QAbstractItemModelTester>
|
#include <QAbstractItemModelTester>
|
||||||
|
#include <QGuiApplication>
|
||||||
#include <QSignalSpy>
|
#include <QSignalSpy>
|
||||||
#include <QTemporaryFile>
|
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <QTemporaryFile>
|
||||||
#include <QtTest>
|
#include <QtTest>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
@@ -50,8 +51,8 @@ private Q_SLOTS:
|
|||||||
const QStringList headers = {"Name", "CAN ID", "Payload (bytes)", "Start bit",
|
const QStringList headers = {"Name", "CAN ID", "Payload (bytes)", "Start bit",
|
||||||
"Length (bits)", "Byte order", "Signedness", "Unit", "Comment"};
|
"Length (bits)", "Byte order", "Signedness", "Unit", "Comment"};
|
||||||
for (int column = 0; column < model.columnCount(); ++column) {
|
for (int column = 0; column < model.columnCount(); ++column) {
|
||||||
const auto frameIndex = model.index(0, column);
|
const QModelIndex frameIndex = model.index(0, column);
|
||||||
const auto signalIndex = model.index(0, column, parent);
|
const QModelIndex signalIndex = model.index(0, column, parent);
|
||||||
QCOMPARE(model.headerData(column, Qt::Horizontal).toString(), headers.at(column));
|
QCOMPARE(model.headerData(column, Qt::Horizontal).toString(), headers.at(column));
|
||||||
QVERIFY(!model.headerData(column, Qt::Horizontal, Qt::ToolTipRole).isValid());
|
QVERIFY(!model.headerData(column, Qt::Horizontal, Qt::ToolTipRole).isValid());
|
||||||
QCOMPARE(model.data(frameIndex, Qt::ToolTipRole).toString(), QString("Frame tooltip"));
|
QCOMPARE(model.data(frameIndex, Qt::ToolTipRole).toString(), QString("Frame tooltip"));
|
||||||
@@ -66,7 +67,7 @@ private Q_SLOTS:
|
|||||||
QCOMPARE(model.data(signalIndex, DbcTreeModel::LittleEndianRole).toBool(), false);
|
QCOMPARE(model.data(signalIndex, DbcTreeModel::LittleEndianRole).toBool(), false);
|
||||||
QCOMPARE(model.data(signalIndex, DbcTreeModel::SignedRole).toBool(), true);
|
QCOMPARE(model.data(signalIndex, DbcTreeModel::SignedRole).toBool(), true);
|
||||||
QCOMPARE(model.data(signalIndex, DbcTreeModel::UnitRole).toString(), QString("rpm"));
|
QCOMPARE(model.data(signalIndex, DbcTreeModel::UnitRole).toString(), QString("rpm"));
|
||||||
const auto descriptions = model.data(signalIndex, DbcTreeModel::ValueDescriptionsRole);
|
const QVariant descriptions = model.data(signalIndex, DbcTreeModel::ValueDescriptionsRole);
|
||||||
QVERIFY(descriptions.isValid());
|
QVERIFY(descriptions.isValid());
|
||||||
QVERIFY(descriptions.toList().isEmpty());
|
QVERIFY(descriptions.toList().isEmpty());
|
||||||
QVERIFY(!model.data(frameIndex, Qt::UserRole + 1000).isValid());
|
QVERIFY(!model.data(frameIndex, Qt::UserRole + 1000).isValid());
|
||||||
@@ -203,5 +204,12 @@ private Q_SLOTS:
|
|||||||
QCOMPARE(resets.count(), 4);
|
QCOMPARE(resets.count(), 4);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
QTEST_GUILESS_MAIN(ModelTest)
|
int main(int argc, char **argv) {
|
||||||
|
// Model tests need QtGui's metatypes, but do not need a display server.
|
||||||
|
if (qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM"))
|
||||||
|
qputenv("QT_QPA_PLATFORM", "offscreen");
|
||||||
|
QGuiApplication application(argc, argv);
|
||||||
|
ModelTest tests;
|
||||||
|
return QTest::qExec(&tests, argc, argv);
|
||||||
|
}
|
||||||
#include "dbc_tree_model_test.moc"
|
#include "dbc_tree_model_test.moc"
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
TEMPLATE = app
|
TEMPLATE = app
|
||||||
TARGET = dbc_model_tests
|
TARGET = dbc_model_tests
|
||||||
QT += core testlib
|
QT += core gui testlib
|
||||||
QT -= gui
|
|
||||||
CONFIG += console testcase c++17 no_keywords
|
CONFIG += console testcase c++17 no_keywords
|
||||||
CONFIG -= app_bundle debug_and_release debug_and_release_target
|
CONFIG -= app_bundle debug_and_release debug_and_release_target
|
||||||
include(../qt/qt.pri)
|
include(../qt/qt.pri)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
TEMPLATE = app
|
TEMPLATE = app
|
||||||
|
include(../qt/qt_version.pri)
|
||||||
TARGET = dbc_tests
|
TARGET = dbc_tests
|
||||||
QT += core testlib
|
QT += core testlib
|
||||||
QT -= gui
|
QT -= gui
|
||||||
|
|||||||
Reference in new issue
Block a user