From b0131d18e2e660917b49e39a1678568dd1fa96c2 Mon Sep 17 00:00:00 2001 From: Devon Adair Date: Thu, 18 Sep 2025 00:25:04 -0400 Subject: [PATCH] Add examples directory and enable builds in CI Created a directory of . In there I have a simple function to add these source files. It creates and executable and links to the library. Building of the examples was enabled in CI and then execute them to compare to the example output for verification they work. --- .github/workflows/pipeline.yml | 13 ++++++- CMakeLists.txt | 3 ++ examples/CMakeLists.txt | 28 ++++++++++++++ examples/README.md | 35 +++++++++++++++++ examples/dbcs/HelloWorld.dbc | 38 +++++++++++++++++++ examples/src/example_parse_message.cpp | 22 +++++++++++ examples/src/example_read_simple_dbc.cpp | 48 ++++++++++++++++++++++++ examples/validate_examples.py | 36 ++++++++++++++++++ src/message.cpp | 2 +- src/signal.cpp | 8 ++-- 10 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 examples/CMakeLists.txt create mode 100644 examples/README.md create mode 100644 examples/dbcs/HelloWorld.dbc create mode 100644 examples/src/example_parse_message.cpp create mode 100644 examples/src/example_read_simple_dbc.cpp create mode 100755 examples/validate_examples.py diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 3f96f40..c7c4acc 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -125,7 +125,7 @@ jobs: strategy: fail-fast: false matrix: - os: [macos-15-large] + os: [macos-15-intel] cxx: - g++ - clang++ @@ -229,3 +229,14 @@ jobs: - name: Run build run: cmake --build build + test-examples: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Make build directory + run: cmake -Bbuild -DDBC_BUILD_EXAMPLES=ON -H$GITHUB_WORKSPACE + + - name: Run example tests + run: cmake --build build --target test_examples diff --git a/CMakeLists.txt b/CMakeLists.txt index 551af92..7e06b04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ option(DBC_ENABLE_TESTS "Enable Unittests" ON) option(DBC_TEST_LOCALE_INDEPENDENCE "Used to deterime if the libary is locale agnostic when it comes to converting floats. You need `de_DE.UTF-8` locale installed for this testing." OFF) option(DBC_GENERATE_DOCS "Use doxygen if installed to generated documentation files" OFF) option(DBC_GENERATE_SINGLE_HEADER "This will run the generator for the single header file version. Default is OFF since we make a static build. Requires cargo installed." OFF) +option(DBC_BUILD_EXAMPLES "Build all the examples in the examples folder." OFF) # ---------------------- # set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -123,3 +124,5 @@ add_custom_target(clang-tidy-fix clang-tidy -fix-notes -p ${CMAKE_BINARY_DIR}/compile_commands.json ${SOURCE_FILES} ${HEADER_FILES} DEPENDS ${SOURCE_FILES} ${HEADER_FILES} ) + +add_subdirectory(examples) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..c3a852d --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,28 @@ +project(examples) + +set(EXECUTABLE_EXAMPLES "") + +function(add_example_executable SOURCE_FILE) + set(SOURCE_NAME "") + string(REPLACE "src/example_" "" SOURCE_NAME ${SOURCE_FILE}) + string(REPLACE ".cpp" "" SOURCE_NAME ${SOURCE_NAME}) + + add_executable(${SOURCE_NAME} ${SOURCE_FILE}) + target_link_libraries(${SOURCE_NAME} PRIVATE dbc) + target_compile_definitions(${SOURCE_NAME} PRIVATE DBC_FILE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/dbcs") + + set(EXECUTABLE_EXAMPLES "examples/${SOURCE_NAME} ${EXECUTABLE_EXAMPLES} " PARENT_SCOPE) +endfunction() + +if(DBC_BUILD_EXAMPLES) + add_example_executable(src/example_read_simple_dbc.cpp) + add_example_executable(src/example_parse_message.cpp) + + string(STRIP ${EXECUTABLE_EXAMPLES} EXECUTABLE_EXAMPLES) + + add_custom_target(test_examples ALL + DEPENDS "${EXECUTABLE_EXAMPLES}" + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMAND ${PROJECT_SOURCE_DIR}/validate_examples.py + ) +endif() diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..a25ea01 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,35 @@ +# Examples + +You will find all the examples in the `src/` directory. +Each example shall be prefixed with `example_` and then named +with the use case then end with `.cpp`. + +There are tests to build and validate that all examples are working. +You can validate output as well. You need to enclose the expected output +with `=== +++ ===`. The python script will parse out the text between +and compare the stdout to the text. You are not required to have test +output. + +## Build and Run Examples + +They are turned off by default and enabled via the `DBC_BUILD_EXAMPLES` +option. + +A quick getting started is: + +```shell +cmake -DDBC_BUILD_EXAMPLES=ON -Bbuild -H. +cmake --build build --target read_simple_dbc +./build/examples/read_simple_dbc +``` + +## Testing Examples + +There is a cmake target to build the examples and run the tests. + +You can do so with: + +```shell +cmake -DDBC_BUILD_EXAMPLES=ON -Bbuild -H. +cmake --build build --target test_examples +``` \ No newline at end of file diff --git a/examples/dbcs/HelloWorld.dbc b/examples/dbcs/HelloWorld.dbc new file mode 100644 index 0000000..f9c6f53 --- /dev/null +++ b/examples/dbcs/HelloWorld.dbc @@ -0,0 +1,38 @@ +VERSION "1.0.0" + +NS_ : + BA_ + BA_DEF_ + BA_DEF_DEF_ + BA_DEF_DEF_REL_ + BA_DEF_REL_ + BA_DEF_SGTYPE_ + BA_REL_ + BA_SGTYPE_ + BO_TX_BU_ + BU_BO_REL_ + BU_EV_REL_ + BU_SG_REL_ + CAT_ + CAT_DEF_ + CM_ + ENVVAR_DATA_ + EV_DATA_ + FILTER + NS_DESC_ + SGTYPE_ + SGTYPE_VAL_ + SG_MUL_VAL_ + SIGTYPE_VALTYPE_ + SIG_GROUP_ + SIG_TYPE_REF_ + SIG_VALTYPE_ + VAL_ + VAL_TABLE_ + +BS_: + +BU_: DBG DRIVER IO MOTOR SENSOR + +BO_ 500 IO_DEBUG: 4 IO + SG_ IO_DEBUG_test_unsigned : 0|8@1+ (1,0) [0|0] "m/s" DBG diff --git a/examples/src/example_parse_message.cpp b/examples/src/example_parse_message.cpp new file mode 100644 index 0000000..9db348b --- /dev/null +++ b/examples/src/example_parse_message.cpp @@ -0,0 +1,22 @@ +/* + +*/ + +#include +#include + +// `DBC_FILE_PATH` is defined in the build system as an absolute path +static const std::string dbc_file_path = std::string(DBC_FILE_PATH) + "/HelloWorld.dbc"; + +int main() { + Libdbc::DbcParser parser = Libdbc::DbcParser(); + parser.parse_file(dbc_file_path); + + // Not required. Just using this to show the message information in the output. + std::cout << parser.get_messages()[0] << std::endl; + + Libdbc::Message::ParseSignalsStatus status = parser.parse_message(337, std::vector({0, 4, 252, 19, 0, 0, 0, 0}), out_values); + if (status != Libdbc::Message::ParseSignalsStatus::Success) { + return 1; + } +} diff --git a/examples/src/example_read_simple_dbc.cpp b/examples/src/example_read_simple_dbc.cpp new file mode 100644 index 0000000..4a9a472 --- /dev/null +++ b/examples/src/example_read_simple_dbc.cpp @@ -0,0 +1,48 @@ +/* + This is the the very basic use case of the library. + Here we are just reading a very basic dbc with one message and signal. + + All we do below is read the basic information of the dbc files + such as the nodes, messages, and signals. This library supports + the << operator to print out the messages and signals. + + EXAMPLE OUT: +*/ +// clang-format off +/* +=== +++ === +Version: 1.0.0 +Node: DBG +Node: DRIVER +Node: IO +Node: MOTOR +Node: SENSOR +Message: {id: 500, name: IO_DEBUG, size: 4, node: IO} +Signal: {name: IO_DEBUG_test_unsigned, Multiplexed: False, Start bit: 0, Size: 8, Endianness: Little endian, Value Type: Unsigned, Min: 0.000000, Max: 0.000000, Unit: (m/s), receivers: DBG} +=== +++ === +*/ +// clang-format on + +#include +#include + +// `DBC_FILE_PATH` is defined in the build system as an absolute path +static const std::string dbc_file_path = std::string(DBC_FILE_PATH) + "/HelloWorld.dbc"; + +int main() { + Libdbc::DbcParser parser = Libdbc::DbcParser(); + parser.parse_file(dbc_file_path); + + std::cout << "Version: " << parser.get_version() << std::endl; + + for (auto const& node : parser.get_nodes()) { + std::cout << "Node: " << node << std::endl; + } + + for (auto const& message : parser.get_messages()) { + std::cout << message << std::endl; + for (auto const& signal : message.get_signals()) { + std::cout << signal << std::endl; + } + } +} diff --git a/examples/validate_examples.py b/examples/validate_examples.py new file mode 100755 index 0000000..28395f4 --- /dev/null +++ b/examples/validate_examples.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +import pathlib +import subprocess +import os +import unittest + +class TestExamples(unittest.TestCase): + def setUp(self): + filenames = next(os.walk("./src/"), (None, None, []))[2] + self.examples = [file for file in filenames if ".cpp" in file] + self.assertGreaterEqual(len(self.examples), 1) + self.maxDiff = None # Enable big diffs of text + + def tearDown(self): + pass + + def test_hello_world(self): + for example in self.examples: + contents = pathlib.Path("src/" + example).read_text() + example_binary_name = example.split("example_")[1].split(".cpp")[0] + output = contents.split("=== +++ ===") + if len(output) < 2: + print(f"Skipping {example_binary_name} because no output to test.") + continue + + test_output = output[1].lstrip() + + result = subprocess.run("../build/examples/" + example_binary_name, capture_output=True, text=True) + with self.subTest(msg='Check example run'): + self.assertEqual(result.returncode, 0) + self.assertEqual(result.stdout, test_output) + self.assertEqual(result.stderr, "") + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/src/message.cpp b/src/message.cpp index ffe4526..2865956 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -120,7 +120,7 @@ void Message::add_value_description(const std::string& signal_name, const std::v std::ostream& operator<<(std::ostream& out, const Message& msg) { out << "Message: {id: " << msg.id() << ", "; out << "name: " << msg.m_name << ", "; - out << "size: " << msg.m_size << ", "; + out << "size: " << std::to_string(msg.m_size) << ", "; out << "node: " << msg.m_node << "}"; return out; } diff --git a/src/signal.cpp b/src/signal.cpp index d40bf2f..11d033c 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -42,13 +42,13 @@ bool Signal::operator<(const Signal& rhs) const { } std::ostream& operator<<(std::ostream& out, const Signal& sig) { - out << "Signal {name: " << sig.name << ", "; + out << "Signal: {name: " << sig.name << ", "; out << "Multiplexed: " << (sig.is_multiplexed ? "True" : "False") << ", "; - out << "Start bit: " << sig.start_bit << ", "; - out << "Size: " << sig.size << ", "; + out << "Start bit: " << std::to_string(sig.start_bit) << ", "; + out << "Size: " << std::to_string(sig.size) << ", "; out << "Endianness: " << (sig.is_bigendian ? "Big endian" : "Little endian") << ", "; out << "Value Type: " << (sig.is_signed ? "Signed" : "Unsigned") << ", "; - out << "Min: " << sig.min << ", Max: " << sig.max << ", "; + out << "Min: " << std::to_string(sig.min) << ", Max: " << std::to_string(sig.max) << ", "; out << "Unit: (" << sig.unit << "), "; out << "receivers: "; for (const auto& reciever : sig.receivers) {