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) {