diff --git a/.clang-format b/.clang-format index e5d309b..350cdc2 100644 --- a/.clang-format +++ b/.clang-format @@ -44,6 +44,8 @@ BraceWrapping: AfterClass: false AfterCaseLabel: false +InsertBraces: true + # CrlInstruction *a; DerivePointerAlignment: false PointerAlignment: Left @@ -104,3 +106,4 @@ AllowShortLambdasOnASingleLine: Empty AllowAllArgumentsOnNextLine: false InsertNewlineAtEOF: true +LineEnding: LF diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..3f85e19 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,169 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,clang-analyzer-*,clang-analyzer-cplusplus*,cert-*,cppcoreguidelines-*,portability-*,readability-*,clang-diagnostic-*,misc-include-cleaner' +WarningsAsErrors: '' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: none +User: dwag +CheckOptions: + readability-simplify-subscript-expr.Types: '::std::basic_string;::std::basic_string_view;::std::vector;::std::array' + readability-suspicious-call-argument.PrefixSimilarAbove: '30' + modernize-replace-auto-ptr.IncludeStyle: llvm + cppcoreguidelines-no-malloc.Reallocations: '::realloc' + cppcoreguidelines-owning-memory.LegacyResourceConsumers: '::free;::realloc;::freopen;::fclose' + readability-static-accessed-through-instance.NameSpecifierNestingThreshold: '3' + readability-simplify-boolean-expr.SimplifyDeMorgan: 'true' + readability-function-size.VariableThreshold: '4294967295' + cert-dcl16-c.NewSuffixes: 'L;LL;LU;LLU' + cert-dcl51-cpp.AggressiveDependentMemberLookup: 'false' + readability-identifier-naming.GetConfigPerFile: 'true' + cert-err61-cpp.MaxSize: '10' + cert-sig30-c.AsyncSafeFunctionSet: POSIX + readability-inconsistent-declaration-parameter-name.Strict: 'false' + cppcoreguidelines-macro-usage.CheckCapsOnly: 'false' + readability-suspicious-call-argument.DiceDissimilarBelow: '60' + cert-dcl37-c.AllowedIdentifiers: '' + readability-function-size.NestingThreshold: '4294967295' + cppcoreguidelines-narrowing-conversions.IgnoreConversionFromTypes: '' + readability-function-size.ParameterThreshold: '4294967295' + readability-suspicious-call-argument.Equality: 'true' + readability-function-cognitive-complexity.IgnoreMacros: 'false' + cert-str34-c.DiagnoseSignedUnsignedCharComparisons: 'false' + cert-err33-c.CheckedFunctions: '::aligned_alloc;::asctime_s;::at_quick_exit;::atexit;::bsearch;::bsearch_s;::btowc;::c16rtomb;::c32rtomb;::calloc;::clock;::cnd_broadcast;::cnd_init;::cnd_signal;::cnd_timedwait;::cnd_wait;::ctime_s;::fclose;::fflush;::fgetc;::fgetpos;::fgets;::fgetwc;::fopen;::fopen_s;::fprintf;::fprintf_s;::fputc;::fputs;::fputwc;::fputws;::fread;::freopen;::freopen_s;::fscanf;::fscanf_s;::fseek;::fsetpos;::ftell;::fwprintf;::fwprintf_s;::fwrite;::fwscanf;::fwscanf_s;::getc;::getchar;::getenv;::getenv_s;::gets_s;::getwc;::getwchar;::gmtime;::gmtime_s;::localtime;::localtime_s;::malloc;::mbrtoc16;::mbrtoc32;::mbsrtowcs;::mbsrtowcs_s;::mbstowcs;::mbstowcs_s;::memchr;::mktime;::mtx_init;::mtx_lock;::mtx_timedlock;::mtx_trylock;::mtx_unlock;::printf_s;::putc;::putwc;::raise;::realloc;::remove;::rename;::scanf;::scanf_s;::setlocale;::setvbuf;::signal;::snprintf;::snprintf_s;::sprintf;::sprintf_s;::sscanf;::sscanf_s;::strchr;::strerror_s;::strftime;::strpbrk;::strrchr;::strstr;::strtod;::strtof;::strtoimax;::strtok;::strtok_s;::strtol;::strtold;::strtoll;::strtoul;::strtoull;::strtoumax;::strxfrm;::swprintf;::swprintf_s;::swscanf;::swscanf_s;::thrd_create;::thrd_detach;::thrd_join;::thrd_sleep;::time;::timespec_get;::tmpfile;::tmpfile_s;::tmpnam;::tmpnam_s;::tss_create;::tss_get;::tss_set;::ungetc;::ungetwc;::vfprintf;::vfprintf_s;::vfscanf;::vfscanf_s;::vfwprintf;::vfwprintf_s;::vfwscanf;::vfwscanf_s;::vprintf_s;::vscanf;::vscanf_s;::vsnprintf;::vsnprintf_s;::vsprintf;::vsprintf_s;::vsscanf;::vsscanf_s;::vswprintf;::vswprintf_s;::vswscanf;::vswscanf_s;::vwprintf_s;::vwscanf;::vwscanf_s;::wcrtomb;::wcschr;::wcsftime;::wcspbrk;::wcsrchr;::wcsrtombs;::wcsrtombs_s;::wcsstr;::wcstod;::wcstof;::wcstoimax;::wcstok;::wcstok_s;::wcstol;::wcstold;::wcstoll;::wcstombs;::wcstombs_s;::wcstoul;::wcstoull;::wcstoumax;::wcsxfrm;::wctob;::wctrans;::wctype;::wmemchr;::wprintf_s;::wscanf;::wscanf_s;' + cppcoreguidelines-explicit-virtual-functions.AllowOverrideAndFinal: 'false' + readability-redundant-smartptr-get.IgnoreMacros: 'true' + readability-identifier-naming.AggressiveDependentMemberLookup: 'false' + readability-const-return-type.IgnoreMacros: 'true' + cert-err61-cpp.WarnOnLargeObjects: 'false' + cert-err09-cpp.WarnOnLargeObjects: 'false' + readability-identifier-length.MinimumParameterNameLength: '3' + cppcoreguidelines-owning-memory.LegacyResourceProducers: '::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile' + cppcoreguidelines-narrowing-conversions.WarnOnFloatingPointNarrowingConversion: 'true' + cppcoreguidelines-init-variables.IncludeStyle: llvm + readability-avoid-const-params-in-decls.IgnoreMacros: 'true' + google-readability-namespace-comments.ShortNamespaceLines: '10' + readability-suspicious-call-argument.JaroWinklerDissimilarBelow: '75' + readability-suspicious-call-argument.SuffixSimilarAbove: '30' + readability-suspicious-call-argument.Suffix: 'true' + cert-err09-cpp.MaxSize: '10' + cppcoreguidelines-narrowing-conversions.WarnOnIntegerNarrowingConversion: 'true' + cppcoreguidelines-prefer-member-initializer.UseAssignment: 'false' + cert-oop57-cpp.MemSetNames: '' + cppcoreguidelines-explicit-virtual-functions.FinalSpelling: final + readability-function-cognitive-complexity.DescribeBasicIncrements: 'true' + readability-suspicious-call-argument.MinimumIdentifierNameLength: '3' + modernize-loop-convert.NamingStyle: CamelCase + cppcoreguidelines-pro-type-member-init.UseAssignment: 'false' + readability-inconsistent-declaration-parameter-name.IgnoreMacros: 'true' + readability-suspicious-call-argument.SubstringDissimilarBelow: '40' + readability-identifier-naming.IgnoreFailedSplit: 'false' + modernize-pass-by-value.IncludeStyle: llvm + readability-qualified-auto.AddConstToQualified: 'true' + cppcoreguidelines-explicit-virtual-functions.OverrideSpelling: override + readability-simplify-boolean-expr.ChainedConditionalReturn: 'false' + readability-else-after-return.WarnOnConditionVariables: 'true' + readability-uppercase-literal-suffix.IgnoreMacros: 'true' + modernize-use-nullptr.NullMacros: 'NULL' + cert-dcl59-cpp.HeaderFileExtensions: ';h;hh;hpp;hxx' + readability-suspicious-call-argument.SuffixDissimilarBelow: '25' + cppcoreguidelines-macro-usage.AllowedRegexp: '^DEBUG_*' + readability-suspicious-call-argument.LevenshteinSimilarAbove: '66' + cppcoreguidelines-narrowing-conversions.PedanticMode: 'false' + readability-suspicious-call-argument.Levenshtein: 'true' + readability-suspicious-call-argument.JaroWinkler: 'true' + readability-suspicious-call-argument.Prefix: 'true' + readability-simplify-boolean-expr.ChainedConditionalAssignment: 'false' + cppcoreguidelines-avoid-magic-numbers.IgnoreAllFloatingPointValues: 'false' + cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions: 'false' + cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField: 'false' + cert-err09-cpp.CheckThrowTemporaries: 'true' + cert-dcl51-cpp.Invert: 'false' + readability-function-size.LineThreshold: '4294967295' + modernize-loop-convert.MaxCopySize: '16' + readability-suspicious-call-argument.PrefixDissimilarBelow: '25' + portability-simd-intrinsics.Suggest: 'false' + cppcoreguidelines-pro-bounds-constant-array-index.GslHeader: '' + cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors: 'false' + readability-redundant-string-init.StringNames: '::std::basic_string_view;::std::basic_string' + readability-magic-numbers.IgnoreBitFieldsWidths: 'true' + readability-braces-around-statements.ShortStatementLines: '0' + readability-suspicious-call-argument.LevenshteinDissimilarBelow: '50' + readability-magic-numbers.IgnoredFloatingPointValues: '1.0;100.0;' + readability-redundant-declaration.IgnoreMacros: 'true' + readability-implicit-bool-conversion.AllowPointerConditions: 'false' + readability-identifier-length.IgnoredExceptionVariableNames: '^[e]$' + google-readability-braces-around-statements.ShortStatementLines: '1' + portability-restrict-system-includes.Includes: '*' + cppcoreguidelines-pro-type-member-init.IgnoreArrays: 'false' + readability-else-after-return.WarnOnUnfixable: 'true' + cert-dcl51-cpp.AllowedIdentifiers: '' + cppcoreguidelines-avoid-magic-numbers.IgnoredFloatingPointValues: '1.0;100.0;' + cert-oop57-cpp.MemCpyNames: '' + readability-suspicious-call-argument.SubstringSimilarAbove: '50' + cppcoreguidelines-macro-usage.IgnoreCommandLineMacros: 'true' + readability-suspicious-call-argument.Substring: 'true' + cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle: llvm + cppcoreguidelines-narrowing-conversions.WarnWithinTemplateInstantiation: 'false' + readability-identifier-length.IgnoredVariableNames: '' + readability-magic-numbers.IgnoreAllFloatingPointValues: 'false' + readability-identifier-length.MinimumVariableNameLength: '3' + readability-suspicious-call-argument.Abbreviations: 'arr=array;cnt=count;idx=index;src=source;stmt=statement;cpy=copy;dest=destination;dist=distancedst=distance;ptr=pointer;wdth=width;str=string;ln=line;srv=server;attr=attribute;ref=reference;buf=buffer;col=column;nr=number;vec=vector;len=length;elem=element;val=value;i=index;var=variable;hght=height;cl=client;num=number;pos=position;lst=list;addr=address' + cppcoreguidelines-narrowing-conversions.WarnOnEquivalentBitWidth: 'true' + cppcoreguidelines-avoid-magic-numbers.IgnoredIntegerValues: '1;2;3;4;' + cppcoreguidelines-non-private-member-variables-in-classes.IgnorePublicMemberVariables: 'false' + modernize-loop-convert.MinConfidence: reasonable + readability-uniqueptr-delete-release.PreferResetCall: 'false' + cppcoreguidelines-avoid-magic-numbers.IgnorePowersOf2IntegerValues: 'false' + readability-identifier-length.MinimumExceptionNameLength: '2' + cert-oop57-cpp.MemCmpNames: '' + google-readability-namespace-comments.SpacesBeforeComments: '2' + cppcoreguidelines-avoid-magic-numbers.IgnoreBitFieldsWidths: 'true' + cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: 'true' + readability-uppercase-literal-suffix.NewSuffixes: '' + readability-function-cognitive-complexity.Threshold: '25' + cppcoreguidelines-no-malloc.Allocations: '::malloc;::calloc' + readability-simplify-boolean-expr.SimplifyDeMorganRelaxed: 'false' + cppcoreguidelines-narrowing-conversions.WarnOnIntegerToFloatingPointNarrowingConversion: 'true' + readability-function-size.BranchThreshold: '4294967295' + cert-err61-cpp.CheckThrowTemporaries: 'true' + readability-implicit-bool-conversion.AllowIntegerConditions: 'false' + readability-identifier-length.IgnoredParameterNames: '^[n]$' + readability-function-size.StatementThreshold: '800' + readability-identifier-naming.IgnoreMainLikeFunctions: 'false' + llvm-qualified-auto.AddConstToQualified: 'false' + cppcoreguidelines-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted: 'false' + cppcoreguidelines-init-variables.MathHeader: '' + cert-str34-c.CharTypdefsToIgnore: '' + google-readability-function-size.StatementThreshold: '800' + llvm-else-after-return.WarnOnConditionVariables: 'false' + cert-msc51-cpp.DisallowedSeedTypes: 'time_t,std::time_t' + readability-suspicious-call-argument.DiceSimilarAbove: '70' + readability-suspicious-call-argument.Dice: 'true' + readability-suspicious-call-argument.Abbreviation: 'true' + cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor: 'true' + readability-identifier-length.IgnoredLoopCounterNames: '^[ijk_]$' + cert-dcl37-c.Invert: 'false' + cert-dcl37-c.AggressiveDependentMemberLookup: 'false' + readability-identifier-length.MinimumLoopCounterNameLength: '2' + cppcoreguidelines-avoid-do-while.IgnoreMacros: 'false' + cert-msc54-cpp.AsyncSafeFunctionSet: POSIX + cppcoreguidelines-no-malloc.Deallocations: '::free' + readability-magic-numbers.IgnoredIntegerValues: '1;2;3;4;' + readability-magic-numbers.IgnorePowersOf2IntegerValues: 'false' + portability-simd-intrinsics.Std: '' + readability-redundant-member-init.IgnoreBaseInCopyConstructors: 'false' + readability-suspicious-call-argument.JaroWinklerSimilarAbove: '85' + cert-dcl16-c.IgnoreMacros: 'true' + llvm-else-after-return.WarnOnUnfixable: 'false' + cert-msc32-c.DisallowedSeedTypes: 'time_t,std::time_t' + readability-identifier-naming.NamespaceCase: 'CamelCase' + readability-identifier-naming.UnionCase: 'CamelCase' + readability-identifier-naming.ClassCase: 'CamelCase' + readability-identifier-naming.ClassMemberCase: 'lower_case' + readability-identifier-naming.ClassMethodCase: 'lower_case' + readability-identifier-naming.StructCase: 'CamelCase' + readability-identifier-naming.MemberCase: 'lower_case' + readability-identifier-naming.MethodCase: 'lower_case' + readability-identifier-naming.FunctionCase: 'lower_case' +... + diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index b630847..3f96f40 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -1,6 +1,6 @@ name: Libdbc Pipeline -on: [push, workflow_call] +on: [push, pull_request, workflow_call, workflow_dispatch] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) @@ -9,22 +9,25 @@ env: jobs: linux-builds: name: linux ${{matrix.cxx}}, C++${{matrix.std}}, ${{matrix.build_type}} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: cxx: - - g++-13 + - g++-11 - clang++-16 build_type: [Debug, Release] std: [11] include: - - cxx: g++-13 - cc: gcc-13 + - cxx: g++-11 + cc: gcc-11 - cxx: clang++-16 cc: clang-16 llvm_version: 16 + env: + BUILD_TYPE: ${{matrix.build_type}} + steps: - uses: actions/checkout@v4 @@ -45,6 +48,9 @@ jobs: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source "$HOME/.cargo/env" + - name: Print gcc version + run: gcc --version + - name: Configure build env: CC: ${{matrix.cc}} @@ -62,21 +68,29 @@ jobs: run: cmake --build build --parallel `nproc` - name: Run tests + timeout-minutes: 3 env: CTEST_OUTPUT_ON_FAILURE: 1 run: ctest --output-on-failure --test-dir build -j `nproc` + - name: Run install + run: cmake --install build --prefix . + + windows-build: name: ${{matrix.os}}, ${{matrix.std}}, ${{matrix.build_type}}, ${{matrix.platform}} runs-on: ${{matrix.os}} strategy: fail-fast: false matrix: - os: [windows-2019, windows-2022] + os: [windows-2025, windows-2022] platform: [Win32, x64] build_type: [Debug, Release] std: [11] + env: + BUILD_TYPE: ${{matrix.build_type}} + steps: - uses: actions/checkout@v4 @@ -98,6 +112,7 @@ jobs: shell: cmd - name: Run tests + timeout-minutes: 3 env: CTEST_OUTPUT_ON_FAILURE: 1 working-directory: ${{runner.workspace}} @@ -105,11 +120,12 @@ jobs: shell: cmd macos-builds: - name: macos ${{matrix.cxx}}, C++${{matrix.std}}, ${{matrix.build_type}} - runs-on: macos-latest + name: ${{matrix.os}}, ${{matrix.cxx}}, C++${{matrix.std}}, ${{matrix.build_type}} + runs-on: ${{matrix.os}} strategy: fail-fast: false matrix: + os: [macos-15-large] cxx: - g++ - clang++ @@ -121,6 +137,9 @@ jobs: - cxx: clang++ cc: clang + env: + BUILD_TYPE: ${{matrix.build_type}} + steps: - uses: actions/checkout@v4 @@ -142,10 +161,11 @@ jobs: run: cmake --build build --parallel `sysctl -n hw.ncpu` - name: Run tests + timeout-minutes: 3 env: CTEST_OUTPUT_ON_FAILURE: 1 working-directory: ${{runner.workspace}} - run: ctest --output-on-failure --test-dir build -j `sysctl -n hw.ncpu` + run: ctest --output-on-failure --test-dir build -j `sysctl -n hw.ncpu` -V format-check: runs-on: ubuntu-latest @@ -157,13 +177,55 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 16 + sudo ./llvm.sh 17 - sudo apt update && sudo apt install -y clang-format-16 - sudo ln -sf $(which clang-format-16) $(which clang-format) + sudo apt update && sudo apt install -y clang-format-17 + sudo ln -sf $(which clang-format-17) $(which clang-format) - test "$(clang-format --version)" == "$(clang-format-16 --version)" + test "$(clang-format --version)" == "$(clang-format-17 --version)" - name: Test format with clang format run: ./scripts/fmt.sh + clang-tidy-check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install clang-tidy version + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 17 + + sudo apt update && sudo apt install -y clang-tidy-17 + sudo ln -sf $(which clang-tidy-17) $(which clang-tidy) + + test "$(clang-tidy --version)" == "$(clang-tidy-17 --version)" + + - name: Make build directory + run: cmake -Bbuild -H$GITHUB_WORKSPACE + + - name: Run clang-tidy check + run: cmake --build build --target clang-tidy-check + + check-against-minimum-cmake: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Cmake install + run: | + wget https://github.com/Kitware/CMake/releases/download/v3.16.9/cmake-3.16.9-linux-x86_64.sh + chmod +x cmake-3.16.9-linux-x86_64.sh + sudo ./cmake-3.16.9-linux-x86_64.sh --skip-license --prefix=/usr/local + cmake --version + + - name: Make build directory + run: cmake -Bbuild -H$GITHUB_WORKSPACE + + - name: Run build + run: cmake --build build + diff --git a/CMakeLists.txt b/CMakeLists.txt index e3afc99..551af92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,113 +1,125 @@ -cmake_minimum_required(VERSION 3.16) - -# Keep this on one line for release checking -project(dbc VERSION 0.2.0 DESCRIPTION "C++ DBC Parser") - -# -- PROJECT OPTIONS -- # -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) -# ---------------------- # - -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -# package -set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) -set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) -set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) -set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE) -set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_SOURCE_DIR}/README.md) -include(CPack) - -# specify the C++ standard -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED True) - -find_package(FastFloat QUIET) -if (NOT ${FastFloat_FOUND}) - include(FetchContent) - FetchContent_Declare( - FastFloat - GIT_REPOSITORY https://github.com/fastfloat/fast_float.git - GIT_TAG 1ea4f27b2aeee2859a1354a3c24cff52a116cad1 - ) - FetchContent_MakeAvailable(FastFloat) -endif() - -# add where to find the source files -list(APPEND SOURCE_FILES - ${PROJECT_SOURCE_DIR}/src/utils.cpp - ${PROJECT_SOURCE_DIR}/src/message.cpp - ${PROJECT_SOURCE_DIR}/src/signal.cpp - ${PROJECT_SOURCE_DIR}/src/dbc.cpp -) - -list(APPEND HEADER_FILES - ${PROJECT_SOURCE_DIR}/include/libdbc/dbc.hpp - ${PROJECT_SOURCE_DIR}/include/libdbc/message.hpp - ${PROJECT_SOURCE_DIR}/include/libdbc/signal.hpp - ${PROJECT_SOURCE_DIR}/include/libdbc/utils/utils.hpp - ${PROJECT_SOURCE_DIR}/include/libdbc/exceptions/error.hpp -) - -if(DBC_ENABLE_TESTS) - include(CTest) - add_subdirectory(test) -endif() - -if(DBC_GENERATE_DOCS) - add_subdirectory(doc) -endif() - -list(APPEND GCC_CLANG_COMPILE_FLAGS - -Wall -Wextra -Wpedantic - -Wconversion -Wint-in-bool-context - -Wmissing-declarations -Wmissing-field-initializers - -Werror -) - - -if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - add_compile_options(/W4 /WX) -elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - # Clang shadow warnings aren't as sensitive as gcc - add_compile_options(${GCC_CLANG_COMPILE_FLAGS} -Wshadow) -else() - add_compile_options(${GCC_CLANG_COMPILE_FLAGS}) -endif() - -add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) -target_link_libraries(${PROJECT_NAME} FastFloat::fast_float) -target_include_directories(${PROJECT_NAME} PUBLIC - $ - $ -) -target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_11) - -target_sources(${PROJECT_NAME} INTERFACE FILE_SET HEADERS - TYPE HEADERS - BASE_DIRS ${PROJECT_SOURCE_DIR}/include/libdbc - FILES ${HEADER_FILES} -) - -if(DBC_GENERATE_SINGLE_HEADER) - add_custom_target(single_header ALL - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - COMMAND ${CMAKE_SOURCE_DIR}/scripts/create_single_header.sh - ) -endif() - -## Installation -# install lib -install(TARGETS ${PROJECT_NAME} - DESTINATION ${CMAKE_INSTALL_LIBDIR}) - -# install headers -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/libdbc DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - -# Generate pkg-config file -configure_file(${PROJECT_NAME}.pc.in ${PROJECT_NAME}.pc @ONLY) -install( - FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc - DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +cmake_minimum_required(VERSION 3.16) + +# Keep this on one line for release checking +project(dbc VERSION 0.5.0 DESCRIPTION "C++ DBC Parser") + +# -- PROJECT OPTIONS -- # +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) +# ---------------------- # + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# package +set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE) +set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_SOURCE_DIR}/README.md) +include(CPack) + +# specify the C++ standard +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +find_package(FastFloat QUIET) +if (NOT ${FastFloat_FOUND}) + include(FetchContent) + FetchContent_Declare( + FastFloat + GIT_REPOSITORY https://github.com/fastfloat/fast_float.git + GIT_TAG 1ea4f27b2aeee2859a1354a3c24cff52a116cad1 + ) + FetchContent_MakeAvailable(FastFloat) +endif() + +# add where to find the source files +list(APPEND SOURCE_FILES + ${PROJECT_SOURCE_DIR}/src/utils.cpp + ${PROJECT_SOURCE_DIR}/src/message.cpp + ${PROJECT_SOURCE_DIR}/src/signal.cpp + ${PROJECT_SOURCE_DIR}/src/dbc.cpp +) + +list(APPEND HEADER_FILES + ${PROJECT_SOURCE_DIR}/include/libdbc/dbc.hpp + ${PROJECT_SOURCE_DIR}/include/libdbc/message.hpp + ${PROJECT_SOURCE_DIR}/include/libdbc/signal.hpp + ${PROJECT_SOURCE_DIR}/include/libdbc/utils/utils.hpp + ${PROJECT_SOURCE_DIR}/include/libdbc/exceptions/error.hpp +) + +if(DBC_ENABLE_TESTS) + include(CTest) + add_subdirectory(test) +endif() + +if(DBC_GENERATE_DOCS) + add_subdirectory(doc) +endif() + +list(APPEND GCC_CLANG_COMPILE_FLAGS + -Wall -Wextra -Wpedantic + -Wconversion -Wint-in-bool-context + -Wmissing-declarations -Wmissing-field-initializers + -Werror +) + + +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + add_compile_options(/W4 /WX) +elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Clang shadow warnings aren't as sensitive as gcc + add_compile_options(${GCC_CLANG_COMPILE_FLAGS} -Wshadow) +else() + add_compile_options(${GCC_CLANG_COMPILE_FLAGS}) +endif() + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) +target_link_libraries(${PROJECT_NAME} FastFloat::fast_float) +target_include_directories(${PROJECT_NAME} PUBLIC + $ + $ +) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_11) + +target_sources(${PROJECT_NAME} INTERFACE ${HEADER_FILES}) + +if(DBC_GENERATE_SINGLE_HEADER) + add_custom_target(single_header ALL + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND ${CMAKE_SOURCE_DIR}/scripts/create_single_header.sh + ) +endif() + +## Installation +# install lib +install(TARGETS ${PROJECT_NAME} + DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +# install headers +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/libdbc DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +# Generate pkg-config file +configure_file(${PROJECT_NAME}.pc.in ${PROJECT_NAME}.pc @ONLY) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + +# Clang tidy +add_custom_target(clang-tidy-check + clang-tidy -p ${CMAKE_BINARY_DIR}/compile_commands.json -warnings-as-errors=* ${SOURCE_FILES} ${HEADER_FILES} + DEPENDS ${SOURCE_FILES} ${HEADER_FILES} +) + +add_custom_target(clang-tidy-dump + clang-tidy -checks=-*,clang-analyzer-*,clang-analyzer-cplusplus*,cert-*,cppcoreguidelines-*,portability-*,readability-*,clang-diagnostic-* -dump-config -p ${CMAKE_BINARY_DIR}/compile_commands.json ${SOURCE_FILES} ${HEADER_FILES} > ../.clang-tidy + DEPENDS ${SOURCE_FILES} ${HEADER_FILES} +) + +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} +) diff --git a/include/libdbc/dbc.hpp b/include/libdbc/dbc.hpp index bff9c4e..5e0e9e4 100644 --- a/include/libdbc/dbc.hpp +++ b/include/libdbc/dbc.hpp @@ -1,55 +1,62 @@ -#ifndef __DBC_HPP__ -#define __DBC_HPP__ - -#include -#include -#include -#include - -#include - -namespace libdbc { - -class Parser { -public: - virtual ~Parser() = default; - - virtual void parse_file(const std::string& file) = 0; - -protected: -}; - -class DbcParser : public Parser { -public: - DbcParser(); - - virtual ~DbcParser() = default; - - virtual void parse_file(const std::string& file) final override; - - std::string get_version() const; - std::vector get_nodes() const; - std::vector get_messages() const; - - Message::ParseSignalsStatus parseMessage(const uint32_t id, const std::vector& data, std::vector& out_values); - -private: - std::string version; - std::vector nodes; - std::vector messages; - - const std::regex version_re; - const std::regex bit_timing_re; - const std::regex name_space_re; - const std::regex node_re; - const std::regex message_re; - const std::regex signal_re; - - void parse_dbc_header(std::istream& file_stream); - void parse_dbc_nodes(std::istream& file_stream); - void parse_dbc_messages(const std::vector& lines); -}; - -} - -#endif // __DBC_HPP__ +#ifndef DBC_HPP +#define DBC_HPP + +#include +#include +#include +#include +#include +#include + +namespace Libdbc { + +class Parser { +public: + virtual ~Parser() = default; + + virtual void parse_file(const std::string& file) = 0; + virtual void parse_file(std::istream& file) = 0; + +protected: +}; + +class DbcParser : public Parser { +public: + DbcParser(); + + void parse_file(const std::string& file_name) override; + void parse_file(std::istream& stream) override; + + std::string get_version() const; + std::vector get_nodes() const; + std::vector get_messages() const; + + Message::ParseSignalsStatus parse_message(uint32_t message_id, const std::vector& data, std::vector& out_values); + + std::vector unused_lines() const; + +private: + std::string version; + std::vector nodes; + std::vector messages; + + std::regex version_re; + std::regex bit_timing_re; + std::regex name_space_re; + std::regex node_re; + std::regex message_re; + std::regex value_re; + std::regex signal_re; + + std::vector missed_lines; + + void parse_dbc_header(std::istream& file_stream); + void parse_dbc_nodes(std::istream& file_stream); + void parse_dbc_messages(const std::vector& lines); + + static std::string get_extension(const std::string& file_name); +}; + +} + +#endif // DBC_HPP diff --git a/include/libdbc/exceptions/error.hpp b/include/libdbc/exceptions/error.hpp index 175cac7..03fd5a0 100644 --- a/include/libdbc/exceptions/error.hpp +++ b/include/libdbc/exceptions/error.hpp @@ -1,24 +1,68 @@ -#ifndef __ERROR_HPP__ -#define __ERROR_HPP__ - -#include - -namespace libdbc { - -class exception : public std::exception { -public: - const char* what() const throw() { - return "libdbc exception occurred"; - } -}; - -class validity_error : public exception { -public: - const char* what() const throw() { - return "Invalid DBC file"; - } -}; - -} // libdbc - -#endif // __ERROR_HPP__ +#ifndef ERROR_HPP +#define ERROR_HPP + +#include +#include + +namespace Libdbc { + +class Exception : public std::exception { +public: + const char* what() const throw() override { + return "libdbc exception occurred"; + } +}; + +class ValidityError : public Exception { +public: + const char* what() const throw() override { + return "Invalid DBC file"; + } +}; + +class NonDbcFileFormatError : public ValidityError { +public: + NonDbcFileFormatError(const std::string& path, const std::string& extension) { + error_msg = {"File is not of DBC format. Expected a .dbc extension. Cannot read this type of file (" + path + "). Found the extension (" + extension + + ")."}; + } + + const char* what() const throw() override { + return error_msg.c_str(); + } + +private: + std::string error_msg; +}; + +class DbcFileIsMissingVersion : public ValidityError { +public: + DbcFileIsMissingVersion(const std::string& line) { + error_msg = {"Invalid dbc file. Missing the required version header. Attempting to read line: (" + line + ")."}; + } + + const char* what() const throw() override { + return error_msg.c_str(); + } + +private: + std::string error_msg; +}; + +class DbcFileIsMissingBitTiming : public ValidityError { +public: + DbcFileIsMissingBitTiming(const std::string& line) { + error_msg = {"Invalid dbc file. Missing required bit timing in the header. Attempting to read line: (" + line + ")."}; + } + + const char* what() const throw() override { + return error_msg.c_str(); + } + +private: + std::string error_msg; +}; + +} // libdbc + +#endif // ERROR_HPP diff --git a/include/libdbc/message.hpp b/include/libdbc/message.hpp index e903fed..438dc7f 100644 --- a/include/libdbc/message.hpp +++ b/include/libdbc/message.hpp @@ -1,55 +1,49 @@ -#ifndef __MESSAGE_HPP__ -#define __MESSAGE_HPP__ - -#include -#include -#include -#include -#include - -namespace libdbc { -struct Message { - Message() = delete; - virtual ~Message() = default; - explicit Message(uint32_t id, const std::string& name, uint8_t size, const std::string& node); - - enum class ParseSignalsStatus { - Success, - ErrorMessageToLong, - ErrorBigEndian, - ErrorUnknownID, - ErrorInvalidConversion, - }; - - /*! - * \brief parseSignals - * \param data - * \param values - * \return - */ - ParseSignalsStatus parseSignals(const std::vector& data, std::vector& values) const; - - void appendSignal(const Signal& signal); - const std::vector getSignals() const; - uint32_t id() const; - uint8_t size() const; - const std::string& name() const; - void addValueDescription(const std::string& signal_name, const std::vector&); - - virtual bool operator==(const Message& rhs) const; - -private: - uint32_t m_id; - std::string m_name; - uint8_t m_size; - std::string m_node; - std::vector m_signals; - - friend std::ostream& operator<<(std::ostream& os, const Message& dt); -}; - -std::ostream& operator<<(std::ostream& out, const Message& msg); - -} - -#endif // __MESSAGE_HPP__ +#ifndef MESSAGE_HPP +#define MESSAGE_HPP + +#include +#include +#include +#include +#include + +namespace Libdbc { +struct Message { + Message() = delete; + virtual ~Message() = default; + explicit Message(uint32_t message_id, const std::string& name, uint8_t size, const std::string& node); + + enum class ParseSignalsStatus { + Success, + ErrorMessageToLong, + ErrorBigEndian, + ErrorUnknownID, + ErrorInvalidConversion, + }; + + ParseSignalsStatus parse_signals(const std::vector& data, std::vector& values) const; + + void append_signal(const Signal& signal); + std::vector get_signals() const; + uint32_t id() const; + uint8_t size() const; + const std::string& name() const; + void add_value_description(const std::string& signal_name, const std::vector&); + + virtual bool operator==(const Message& rhs) const; + +private: + uint32_t m_id; + std::string m_name; + uint8_t m_size; + std::string m_node; + std::vector m_signals; + + friend std::ostream& operator<<(std::ostream& out, const Message& msg); +}; + +std::ostream& operator<<(std::ostream& out, const Message& msg); + +} + +#endif // MESSAGE_HPP diff --git a/include/libdbc/signal.hpp b/include/libdbc/signal.hpp index c7311cb..18ed3da 100644 --- a/include/libdbc/signal.hpp +++ b/include/libdbc/signal.hpp @@ -1,54 +1,54 @@ - -#ifndef __SIGNAL_HPP__ -#define __SIGNAL_HPP__ - -#include -#include -#include -#include - -namespace libdbc { -struct Signal { - struct SignalValueDescriptions { - uint32_t value; - std::string description; - }; - - std::string name; - bool is_multiplexed; - uint32_t start_bit; - uint32_t size; - bool is_bigendian; - bool is_signed; - double factor; - double offset; - double min; - double max; - std::string unit; - std::vector receivers; - std::vector svDescriptions; - - Signal() = delete; - virtual ~Signal() = default; - explicit Signal(std::string name, - bool is_multiplexed, - uint32_t start_bit, - uint32_t size, - bool is_bigendian, - bool is_signed, - double factor, - double offset, - double min, - double max, - std::string unit, - std::vector recievers); - - virtual bool operator==(const Signal& rhs) const; - bool operator<(const Signal& rhs) const; -}; - -std::ostream& operator<<(std::ostream& out, const Signal& sig); - -} - -#endif // __SIGNAL_HPP__ + +#ifndef SIGNAL_HPP +#define SIGNAL_HPP + +#include +#include +#include +#include + +namespace Libdbc { +struct Signal { + struct ValueDescription { + uint32_t value; + std::string description; + }; + + std::string name; + bool is_multiplexed; + uint32_t start_bit; + uint32_t size; + bool is_bigendian; + bool is_signed; + double factor; + double offset; + double min; + double max; + std::string unit; + std::vector receivers; + std::vector value_descriptions; + + Signal() = delete; + virtual ~Signal() = default; + explicit Signal(std::string name, + bool is_multiplexed, + uint32_t start_bit, + uint32_t size, + bool is_bigendian, + bool is_signed, + double factor, + double offset, + double min, + double max, + std::string unit, + std::vector receivers); + + virtual bool operator==(const Signal& rhs) const; + bool operator<(const Signal& rhs) const; +}; + +std::ostream& operator<<(std::ostream& out, const Signal& sig); + +} + +#endif // SIGNAL_HPP diff --git a/include/libdbc/utils/utils.hpp b/include/libdbc/utils/utils.hpp index 21ff540..fd2fba2 100644 --- a/include/libdbc/utils/utils.hpp +++ b/include/libdbc/utils/utils.hpp @@ -1,51 +1,48 @@ - -#ifndef __UTILS_HPP__ -#define __UTILS_HPP__ - -#include -#include -#include -#include -#include -#include - -namespace utils { - -class StreamHandler { -public: - /** - * This is a safe non line ending specific get_ine function. This is to help with files - * carried over from different systems. i.e Unix file comes to Windows with LF endings - * instead of CRLF. - * - * @param stream [description] - * @param line [description] - * @return [description] - */ - static std::istream& get_line(std::istream& stream, std::string& line); - - static std::istream& get_next_non_blank_line(std::istream& stream, std::string& line); - - static std::istream& skip_to_next_blank_line(std::istream& stream, std::string& line); -}; - -class String { -public: - static std::string trim(const std::string& line); - - template - static void split(const std::string& str, Container& cont, char delim = ' ') { - std::stringstream ss(str); - std::string token; - - while (std::getline(ss, token, delim)) { - cont.push_back(token); - } - } - - static double convert_to_double(const std::string& value, double default_value = 0); -}; - -} - -#endif // __UTILS_HPP__ + +#ifndef UTILS_HPP +#define UTILS_HPP + +#include +#include +#include + +namespace Utils { + +class StreamHandler { +public: + /** + * This is a safe non line ending specific get_ine function. This is to help with files + * carried over from different systems. i.e Unix file comes to Windows with LF endings + * instead of CRLF. + * + * @param stream [description] + * @param line [description] + * @return [description] + */ + static std::istream& get_line(std::istream& stream, std::string& line); + + static std::istream& get_next_non_blank_line(std::istream& stream, std::string& line); + + static std::istream& skip_to_next_blank_line(std::istream& stream, std::string& line); +}; + +class String { +public: + static std::string trim(const std::string& line); + + template + static void split(const std::string& str, Container& cont, char delim = ' ') { + std::stringstream stream(str); + std::string token; + + while (std::getline(stream, token, delim)) { + cont.push_back(token); + } + } + + static double convert_to_double(const std::string& value, double default_value = 0); +}; + +} + +#endif // UTILS_HPP diff --git a/src/dbc.cpp b/src/dbc.cpp index 8b3dac6..89e8c24 100644 --- a/src/dbc.cpp +++ b/src/dbc.cpp @@ -1,292 +1,253 @@ -#include -#include -#include -#include - -#include - -namespace libdbc { - -const auto floatPattern = "(-?\\d+\\.?(\\d+)?)"; // Can be negative - -const auto signalIdentifierPattern = "(SG_)"; -const auto namePattern = "(\\w+)"; -const auto bitStartPattern = "(\\d+)"; // Cannot be negative -const auto lengthPattern = "(\\d+)"; // Cannot be negative -const auto byteOrderPattern = "([0-1])"; -const auto signPattern = "(\\+|\\-)"; -const auto scalePattern = "(\\d+\\.?(\\d+)?)"; // Non negative float -const auto offsetPattern = floatPattern; -const auto offsetScalePattern = std::string("\\(") + scalePattern + "\\," + offsetPattern + "\\)"; -const auto minPattern = floatPattern; -const auto maxPattern = floatPattern; -const auto minMaxPattern = std::string("\\[") + minPattern + "\\|" + maxPattern + "\\]"; -const auto unitPattern = "\"(.*)\""; // Random string -const auto receiverPattern = "([\\w\\,]+|Vector__XXX)*"; -const auto whiteSpace = "\\s"; - -enum VALToken { Identifier = 0, CANId, SignalName, Value, Description }; - -struct VALObject { - uint32_t can_id; - std::string signal_name; - std::vector vd; -}; - -static bool parseVal(const std::string& str, VALObject& obj); -bool parseVal(const std::string& str, VALObject& obj) { - obj.signal_name = ""; - obj.vd.clear(); - auto state = Identifier; - const char* a = str.data(); - Signal::SignalValueDescriptions vd; - for (;;) { - switch (state) { - case Identifier: { - if (*a != 'V') - return false; - a++; - if (*a != 'A') - return false; - a++; - if (*a != 'L') - return false; - a++; - if (*a != '_') - return false; - a++; - if (*a != ' ') - return false; - a++; // skip whitespace - state = CANId; - break; - } - case CANId: { - std::string can_id_str; - while (*a >= '0' && *a <= '9') { - can_id_str += *a; - a++; - } - if (can_id_str.empty()) - return false; - obj.can_id = static_cast(std::stoul(can_id_str)); - if (*a != ' ') - return false; - a++; // skip whitespace - state = SignalName; - break; - } - case SignalName: { - if ((*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || *a == '_') - obj.signal_name += *a; - else - return false; - a++; - while ((*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || *a == '_' || (*a >= '0' && *a <= '9')) { - obj.signal_name += *a; - a++; - } - if (*a != ' ') - return false; - a++; // skip whitespace - state = Value; - break; - } - case Value: { - std::string value_str; - while (*a >= '0' && *a <= '9') { - value_str += *a; - a++; - } - if (*a == ';') { - if (value_str.empty()) - return true; - return false; - } - if (value_str.empty()) - return false; - - if (*a != ' ') - return false; - a++; // skip whitespace - vd.value = (uint32_t)std::stoul(value_str); - state = Description; - break; - } - case Description: { - std::string desc; - if (*a != '"') - return false; - a++; - while (*a != '"' && *a != 0) { - desc += *a; - a++; - } - if (*a == 0) - return false; - a++; - if (*a != ' ') - return false; - a++; // skip whitespace - - vd.description = desc; - obj.vd.push_back(vd); - - state = Value; - break; - } - } - } - return false; -} - -DbcParser::DbcParser() - : version("") - , nodes() - , version_re("^(VERSION)\\s\"(.*)\"") - , bit_timing_re("^(BS_:)") - , name_space_re("^(NS_)\\s\\:") - , node_re("^(BU_:)\\s((?:[\\w]+?\\s?)*)") - , message_re("^(BO_)\\s(\\d+)\\s(\\w+)\\:\\s(\\d+)\\s(\\w+|Vector__XXX)") - , - // NOTE: No multiplex support yet - signal_re(std::string("^") + whiteSpace + signalIdentifierPattern + whiteSpace + namePattern + whiteSpace + "\\:" + whiteSpace + bitStartPattern + "\\|" - + lengthPattern + "\\@" + byteOrderPattern + signPattern + whiteSpace + offsetScalePattern + whiteSpace + minMaxPattern + whiteSpace + unitPattern - + whiteSpace + receiverPattern) { -} - -void DbcParser::parse_file(const std::string& file) { - std::ifstream s(file.c_str()); - std::string line; - std::vector lines; - - messages.clear(); - - parse_dbc_header(s); - - parse_dbc_nodes(s); - - while (!s.eof()) { - utils::StreamHandler::get_next_non_blank_line(s, line); - lines.push_back(line); - } - - parse_dbc_messages(lines); -} - -std::string DbcParser::get_version() const { - return version; -} - -std::vector DbcParser::get_nodes() const { - return nodes; -} - -std::vector DbcParser::get_messages() const { - return messages; -} - -Message::ParseSignalsStatus DbcParser::parseMessage(const uint32_t id, const std::vector& data, std::vector& out_values) { - for (const auto& message : messages) { - if (message.id() == id) - return message.parseSignals(data, out_values); - } - return Message::ParseSignalsStatus::ErrorUnknownID; -} - -void DbcParser::parse_dbc_header(std::istream& file_stream) { - std::string line; - std::smatch match; - - utils::StreamHandler::get_line(file_stream, line); - - if (!std::regex_search(line, match, version_re)) { - throw validity_error(); - } - - version = match.str(2); - - utils::StreamHandler::get_next_non_blank_line(file_stream, line); - - utils::StreamHandler::skip_to_next_blank_line(file_stream, line); - - utils::StreamHandler::get_next_non_blank_line(file_stream, line); - - if (!std::regex_search(line, match, bit_timing_re)) - throw validity_error(); -} - -void DbcParser::parse_dbc_nodes(std::istream& file_stream) { - std::string line; - std::smatch match; - - utils::StreamHandler::get_next_non_blank_line(file_stream, line); - - if (!std::regex_search(line, match, node_re)) - throw validity_error(); - - if (match.length() > 2) { - std::string n = match.str(2); - utils::String::split(n, nodes); - } -} - -void DbcParser::parse_dbc_messages(const std::vector& lines) { - std::smatch match; - - std::vector sv; - - VALObject obj{}; - for (const auto& line : lines) { - if (std::regex_search(line, match, message_re)) { - uint32_t id = static_cast(std::stoul(match.str(2))); - std::string name = match.str(3); - uint8_t size = static_cast(std::stoul(match.str(4))); - std::string node = match.str(5); - - Message msg(id, name, size, node); - - messages.push_back(msg); - continue; - } - - if (std::regex_search(line, match, signal_re)) { - std::string name = match.str(2); - bool is_multiplexed = false; // No support yet - uint32_t start_bit = static_cast(std::stoul(match.str(3))); - uint32_t size = static_cast(std::stoul(match.str(4))); - bool is_bigendian = (std::stoul(match.str(5)) == 0); - bool is_signed = (match.str(6) == "-"); - - double factor = utils::String::convert_to_double(match.str(7).data()); - double offset = utils::String::convert_to_double(match.str(9).data()); - double min = utils::String::convert_to_double(match.str(11).data()); - double max = utils::String::convert_to_double(match.str(13).data()); - - std::string unit = match.str(15); - - std::vector receivers; - utils::String::split(match.str(16), receivers, ','); - - Signal sig(name, is_multiplexed, start_bit, size, is_bigendian, is_signed, factor, offset, min, max, unit, receivers); - messages.back().appendSignal(sig); - continue; - } - - if (parseVal(line, obj)) { - sv.push_back(obj); - continue; - } - } - - for (const auto& signal : sv) { - for (auto& msg : messages) { - if (msg.id() == signal.can_id) { - msg.addValueDescription(signal.signal_name, signal.vd); - break; - } - } - } -} - -} +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Libdbc { + +const auto floatPattern = "(-?\\d+\\.?(\\d+)?)"; // Can be negative + +const auto signalIdentifierPattern = "(SG_)"; +const auto namePattern = "(\\w+)"; +const auto bitStartPattern = "(\\d+)"; // Cannot be negative +const auto lengthPattern = "(\\d+)"; // Cannot be negative +const auto byteOrderPattern = "([0-1])"; +const auto signPattern = "(\\+|\\-)"; +const auto scalePattern = "(\\d+\\.?(\\d+)?)"; // Non negative float +const auto offsetPattern = floatPattern; +// NOLINTNEXTLINE -- Disable warning for runtime initialization and can throw. Can't fix until newer c++ version with constexpr +const auto offsetScalePattern = std::string("\\(") + scalePattern + "\\," + offsetPattern + "\\)"; +const auto minPattern = floatPattern; +const auto maxPattern = floatPattern; +// NOLINTNEXTLINE -- Disable warning for runtime initialization and can throw. Can't fix until newer c++ version with constexpr +const auto minMaxPattern = std::string("\\[") + minPattern + "\\|" + maxPattern + "\\]"; +const auto unitPattern = "\"(.*)\""; // Random string +const auto receiverPattern = "([\\w\\,]+|Vector__XXX)*"; +const auto whiteSpace = "\\s"; + +constexpr unsigned SIGNAL_NAME_GROUP = 2; +constexpr unsigned SIGNAL_START_BIT_GROUP = 3; +constexpr unsigned SIGNAL_SIZE_GROUP = 4; +constexpr unsigned SIGNAL_ENDIAN_GROUP = 5; +constexpr unsigned SIGNAL_SIGNED_GROUP = 6; +constexpr unsigned SIGNAL_FACTOR_GROUP = 7; +constexpr unsigned SIGNAL_OFFSET_GROUP = 9; +constexpr unsigned SIGNAL_MIN_GROUP = 11; +constexpr unsigned SIGNAL_MAX_GROUP = 13; +constexpr unsigned SIGNAL_UNIT_GROUP = 15; +constexpr unsigned SIGNAL_RECIEVER_GROUP = 16; + +constexpr unsigned MESSAGE_ID_GROUP = 2; +constexpr unsigned MESSAGE_NAME_GROUP = 3; +constexpr unsigned MESSAGE_SIZE_GROUP = 4; +constexpr unsigned MESSAGE_NODE_GROUP = 5; + +struct Value { + uint32_t can_id; + std::string signal_name; + std::vector value_descriptions; +}; + +DbcParser::DbcParser() + : version_re("^(VERSION)\\s\"(.*)\"") + , bit_timing_re("^(BS_:)") + , name_space_re("^(NS_)\\s\\:") + , node_re("^(BU_:)\\s?((?:[\\w]+?\\s?)*)?") + , message_re("^(BO_)\\s(\\d+)\\s(\\w+)\\:\\s(\\d+)\\s(\\w+|Vector__XXX)") + , value_re("^(VAL_)\\s(\\d+)\\s(\\w+)((?:\\s(\\d+)\\s\"([^\"]*)\")+)\\s;$") + , + // NOTE: No multiplex support yet + signal_re(std::string("^") + whiteSpace + signalIdentifierPattern + whiteSpace + namePattern + whiteSpace + "\\:" + whiteSpace + bitStartPattern + "\\|" + + lengthPattern + "\\@" + byteOrderPattern + signPattern + whiteSpace + offsetScalePattern + whiteSpace + minMaxPattern + whiteSpace + unitPattern + + whiteSpace + receiverPattern) { +} + +void DbcParser::parse_file(std::istream& stream) { + std::string line; + std::vector lines; + + messages.clear(); + + parse_dbc_header(stream); + parse_dbc_nodes(stream); + + while (!stream.eof()) { + Utils::StreamHandler::get_next_non_blank_line(stream, line); + lines.push_back(line); + } + + parse_dbc_messages(lines); +} + +void DbcParser::parse_file(const std::string& file_name) { + auto extension = get_extension(file_name); + if (extension != ".dbc") { + throw NonDbcFileFormatError(file_name, extension); + } + + std::ifstream stream(file_name.c_str()); + + parse_file(stream); +} + +std::string DbcParser::get_extension(const std::string& file_name) { + std::size_t dot = file_name.find_last_of("."); + if (dot != std::string::npos) { + return file_name.substr(dot, file_name.size() - dot); + } + + return ""; +} + +std::string DbcParser::get_version() const { + return version; +} + +std::vector DbcParser::get_nodes() const { + return nodes; +} + +std::vector DbcParser::get_messages() const { + return messages; +} + +Message::ParseSignalsStatus DbcParser::parse_message(const uint32_t message_id, const std::vector& data, std::vector& out_values) { + for (const auto& message : messages) { + if (message.id() == message_id) { + return message.parse_signals(data, out_values); + } + } + return Message::ParseSignalsStatus::ErrorUnknownID; +} + +void DbcParser::parse_dbc_header(std::istream& file_stream) { + std::string line; + std::smatch match; + + Utils::StreamHandler::get_line(file_stream, line); + + if (!std::regex_search(line, match, version_re)) { + throw DbcFileIsMissingVersion(line); + } + + version = match.str(2); + + Utils::StreamHandler::get_next_non_blank_line(file_stream, line); + Utils::StreamHandler::skip_to_next_blank_line(file_stream, line); + Utils::StreamHandler::get_next_non_blank_line(file_stream, line); + + if (!std::regex_search(line, match, bit_timing_re)) { + throw DbcFileIsMissingBitTiming(line); + } +} + +void DbcParser::parse_dbc_nodes(std::istream& file_stream) { + std::string line; + std::smatch match; + + Utils::StreamHandler::get_next_non_blank_line(file_stream, line); + + std::regex_search(line, match, node_re); + + if (match.length() > 2) { + std::string node = match.str(2); + Utils::String::split(node, nodes); + } +} + +void DbcParser::parse_dbc_messages(const std::vector& lines) { + std::smatch match; + + std::vector signal_value; + + for (const auto& line : lines) { + if (std::regex_search(line, match, message_re)) { + uint32_t message_id = static_cast(std::stoul(match.str(MESSAGE_ID_GROUP))); + std::string name = match.str(MESSAGE_NAME_GROUP); + uint8_t size = static_cast(std::stoul(match.str(MESSAGE_SIZE_GROUP))); + std::string node = match.str(MESSAGE_NODE_GROUP); + + Message msg(message_id, name, size, node); + + messages.push_back(msg); + continue; + } + + if (std::regex_search(line, match, signal_re) && !messages.empty()) { + std::string name = match.str(SIGNAL_NAME_GROUP); + bool is_multiplexed = false; // No support yet + uint32_t start_bit = static_cast(std::stoul(match.str(SIGNAL_START_BIT_GROUP))); + uint32_t size = static_cast(std::stoul(match.str(SIGNAL_SIZE_GROUP))); + bool is_bigendian = (std::stoul(match.str(SIGNAL_ENDIAN_GROUP)) == 0); + bool is_signed = (match.str(SIGNAL_SIGNED_GROUP) == "-"); + + double factor = Utils::String::convert_to_double(match.str(SIGNAL_FACTOR_GROUP)); + double offset = Utils::String::convert_to_double(match.str(SIGNAL_OFFSET_GROUP)); + double min = Utils::String::convert_to_double(match.str(SIGNAL_MIN_GROUP)); + double max = Utils::String::convert_to_double(match.str(SIGNAL_MAX_GROUP)); + + std::string unit = match.str(SIGNAL_UNIT_GROUP); + + std::vector receivers; + Utils::String::split(match.str(SIGNAL_RECIEVER_GROUP), receivers, ','); + + Signal sig(name, is_multiplexed, start_bit, size, is_bigendian, is_signed, factor, offset, min, max, unit, receivers); + messages.back().append_signal(sig); + continue; + } + + if (std::regex_search(line, match, value_re) && !messages.empty()) { + uint32_t message_id = static_cast(std::stoul(match.str(2))); + std::string signal_name = match.str(3); + + // Loop over the rest of the descriptions + std::string rest_of_descriptions = match.str(4); + std::regex description_re("\\s(\\d+)\\s\"([^\"]*)\""); + + std::sregex_iterator desc_iter(rest_of_descriptions.begin(), rest_of_descriptions.end(), description_re); + std::sregex_iterator desc_end = std::sregex_iterator(); + + std::vector values{}; + for (std::sregex_iterator i = desc_iter; i != desc_end; ++i) { + std::smatch desc_match = *desc_iter; + uint32_t number = static_cast(std::stoul(desc_match.str(1))); + std::string text = desc_match.str(2); + + values.push_back(Signal::ValueDescription{number, text}); + ++desc_iter; + } + + Value val{message_id, signal_name, values}; + + signal_value.push_back(val); + continue; + } + + if (line.length() > 0) { + missed_lines.push_back(line); + } + } + + for (const auto& signal : signal_value) { + for (auto& msg : messages) { + if (msg.id() == signal.can_id) { + msg.add_value_description(signal.signal_name, signal.value_descriptions); + break; + } + } + } +} + +std::vector DbcParser::unused_lines() const { + return missed_lines; +} + +} diff --git a/src/message.cpp b/src/message.cpp index 443526a..ffe4526 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -1,112 +1,127 @@ -#include -#include -#include - -namespace libdbc { -Message::Message(uint32_t id, const std::string& name, uint8_t size, const std::string& node) - : m_id(id) - , m_name(name) - , m_size(size) - , m_node(node) { -} - -bool Message::operator==(const Message& rhs) const { - return (m_id == rhs.id()) && (m_name == rhs.m_name) && (m_size == rhs.m_size) && (m_node == rhs.m_node); -} - -Message::ParseSignalsStatus Message::parseSignals(const std::vector& data, std::vector& values) const { - auto size = data.size(); - if (size > 8) - return ParseSignalsStatus::ErrorMessageToLong; // not supported yet - - uint64_t data_little_endian = 0; - uint64_t data_big_endian = 0; - for (size_t i = 0; i < size; i++) { - data_little_endian |= ((uint64_t)data[i]) << i * 8; - data_big_endian = (data_big_endian << 8) | (uint64_t)data[i]; - } - - // TODO: does this also work on a big endian machine? - - const auto len = size * 8; - uint64_t v = 0; - for (const auto& signal : m_signals) { - if (signal.is_bigendian) { - uint32_t start_bit = 8 * (signal.start_bit / 8) + (7 - (signal.start_bit % 8)); // Calculation taken from python CAN - v = data_big_endian << start_bit; - v = v >> (len - signal.size); - } else - v = data_little_endian >> signal.start_bit; - - if (signal.is_signed && signal.size > 1) { - switch (signal.size) { - case 8: - values.push_back(static_cast(v) * signal.factor + signal.offset); - break; - case 16: - values.push_back(static_cast(v) * signal.factor + signal.offset); - break; - case 32: - values.push_back(static_cast(v) * signal.factor + signal.offset); - break; - case 64: - values.push_back(static_cast(v) * signal.factor + signal.offset); - break; - default: { - // 2 complement -> decimal - const int negative = (v & (1ull << (signal.size - 1))) != 0; - int64_t nativeInt; - if (negative) - nativeInt = static_cast(v | ~((1ull << signal.size) - 1)); // invert all bits above signal.size - else - nativeInt = static_cast(v & ((1ull << signal.size) - 1)); // masking - values.push_back(static_cast(nativeInt) * signal.factor + signal.offset); - break; - } - } - } else { - // use only the relevant bits - v = v & ((1 << signal.size) - 1); // masking - values.push_back(static_cast(v) * signal.factor + signal.offset); - } - } - return ParseSignalsStatus::Success; -} - -void Message::appendSignal(const Signal& signal) { - m_signals.push_back(signal); -} - -const std::vector Message::getSignals() const { - return m_signals; -} - -uint32_t Message::id() const { - return m_id; -} - -uint8_t Message::size() const { - return m_size; -} - -const std::string& Message::name() const { - return m_name; -} - -void Message::addValueDescription(const std::string& signal_name, const std::vector& vd) { - for (auto& s : m_signals) { - if (s.name.compare(signal_name) == 0) { - s.svDescriptions = vd; - return; - } - } -} - -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 << "node: " << msg.m_node << "}"; - return out; -} -} +#include +#include +#include +#include +#include +#include +#include + +namespace Libdbc { + +constexpr unsigned ONE_BYTE = 8; +constexpr unsigned TWO_BYTES = 16; +constexpr unsigned FOUR_BYTES = 32; +constexpr unsigned EIGHT_BYTES = 64; + +constexpr unsigned SEVEN_BITS = 7; + +Message::Message(uint32_t message_id, const std::string& name, uint8_t size, const std::string& node) + : m_id(message_id) + , m_name(name) + , m_size(size) + , m_node(node) { +} + +bool Message::operator==(const Message& rhs) const { + return (m_id == rhs.id()) && (m_name == rhs.m_name) && (m_size == rhs.m_size) && (m_node == rhs.m_node); +} + +Message::ParseSignalsStatus Message::parse_signals(const std::vector& data, std::vector& values) const { + auto size = data.size(); + if (size > ONE_BYTE) { + return ParseSignalsStatus::ErrorMessageToLong; // not supported yet + } + + uint64_t data_little_endian = 0; + uint64_t data_big_endian = 0; + for (std::size_t i = 0; i < size; i++) { + data_little_endian |= ((uint64_t)data[i]) << i * ONE_BYTE; + data_big_endian = (data_big_endian << ONE_BYTE) | (uint64_t)data[i]; + } + + // TODO: does this also work on a big endian machine? + + const auto len = size * 8; + uint64_t value = 0; + for (const auto& signal : m_signals) { + if (signal.is_bigendian) { + uint32_t start_bit = ONE_BYTE * (signal.start_bit / ONE_BYTE) + (SEVEN_BITS - (signal.start_bit % ONE_BYTE)); // Calculation taken from python CAN + value = data_big_endian << start_bit; + value = value >> (len - signal.size); + } else { + value = data_little_endian >> signal.start_bit; + } + + if (signal.is_signed && signal.size > 1) { + switch (signal.size) { + case ONE_BYTE: + values.push_back(static_cast(value) * signal.factor + signal.offset); + break; + case TWO_BYTES: + values.push_back(static_cast(value) * signal.factor + signal.offset); + break; + case FOUR_BYTES: + values.push_back(static_cast(value) * signal.factor + signal.offset); + break; + case EIGHT_BYTES: + values.push_back(static_cast(value) * signal.factor + signal.offset); + break; + default: { + // 2 complement -> decimal + const bool is_negative = (value & (1ULL << (signal.size - 1))) != 0; + int64_t nativeInt = 0; + if (is_negative) { + nativeInt = static_cast(value | ~((1ULL << signal.size) - 1)); // invert all bits above signal.size + } else { + nativeInt = static_cast(value & ((1ULL << signal.size) - 1)); // masking + } + values.push_back(static_cast(nativeInt) * signal.factor + signal.offset); + break; + } + } + } else { + // use only the relevant bits + value = value & ((1 << signal.size) - 1); // masking + values.push_back(static_cast(value) * signal.factor + signal.offset); + } + } + return ParseSignalsStatus::Success; +} + +void Message::append_signal(const Signal& signal) { + m_signals.push_back(signal); +} + +std::vector Message::get_signals() const { + return m_signals; +} + +uint32_t Message::id() const { + return m_id; +} + +uint8_t Message::size() const { + return m_size; +} + +const std::string& Message::name() const { + return m_name; +} + +void Message::add_value_description(const std::string& signal_name, const std::vector& value_descriptor) { + for (auto& signal : m_signals) { + if (signal.name == signal_name) { + signal.value_descriptions = value_descriptor; + return; + } + } +} + +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 << "node: " << msg.m_node << "}"; + return out; +} +} diff --git a/src/signal.cpp b/src/signal.cpp index a2ad53d..d40bf2f 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -1,54 +1,59 @@ -#include - -namespace libdbc { -Signal::Signal(std::string name, - bool is_multiplexed, - uint32_t start_bit, - uint32_t size, - bool is_bigendian, - bool is_signed, - double factor, - double offset, - double min, - double max, - std::string unit, - std::vector receivers) - : name(name) - , is_multiplexed(is_multiplexed) - , start_bit(start_bit) - , size(size) - , is_bigendian(is_bigendian) - , is_signed(is_signed) - , factor(factor) - , offset(offset) - , min(min) - , max(max) - , unit(unit) - , receivers(receivers) { -} - -bool Signal::operator==(const Signal& rhs) const { - return (this->name == rhs.name) && (this->is_multiplexed == rhs.is_multiplexed) && (this->start_bit == rhs.start_bit) && (this->size == rhs.size) - && (this->is_bigendian == rhs.is_bigendian) && (this->is_signed == rhs.is_signed) && (this->offset == rhs.offset) && (this->min == rhs.min) - && (this->max == rhs.max) && (this->unit == rhs.unit) && (this->receivers == rhs.receivers); -} - -bool Signal::operator<(const Signal& rhs) const { - return start_bit < rhs.start_bit; -} - -std::ostream& operator<<(std::ostream& out, const Signal& sig) { - out << "Signal {name: " << sig.name << ", "; - out << "Multiplexed: " << (sig.is_multiplexed ? "True" : "False") << ", "; - out << "Start bit: " << sig.start_bit << ", "; - out << "Size: " << 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 << "Unit: (" << sig.unit << "), "; - out << "receivers: "; - for (const auto& r : sig.receivers) - out << r; - return out << "}"; -} -} +#include +#include +#include +#include +#include + +namespace Libdbc { +Signal::Signal(std::string name, + bool is_multiplexed, + uint32_t start_bit, + uint32_t size, + bool is_bigendian, + bool is_signed, + double factor, + double offset, + double min, + double max, + std::string unit, + std::vector receivers) + : name(name) + , is_multiplexed(is_multiplexed) + , start_bit(start_bit) + , size(size) + , is_bigendian(is_bigendian) + , is_signed(is_signed) + , factor(factor) + , offset(offset) + , min(min) + , max(max) + , unit(unit) + , receivers(receivers) { +} + +bool Signal::operator==(const Signal& rhs) const { + return (this->name == rhs.name) && (this->is_multiplexed == rhs.is_multiplexed) && (this->start_bit == rhs.start_bit) && (this->size == rhs.size) + && (this->is_bigendian == rhs.is_bigendian) && (this->is_signed == rhs.is_signed) && (this->offset == rhs.offset) && (this->min == rhs.min) + && (this->max == rhs.max) && (this->unit == rhs.unit) && (this->receivers == rhs.receivers); +} + +bool Signal::operator<(const Signal& rhs) const { + return start_bit < rhs.start_bit; +} + +std::ostream& operator<<(std::ostream& out, const Signal& sig) { + out << "Signal {name: " << sig.name << ", "; + out << "Multiplexed: " << (sig.is_multiplexed ? "True" : "False") << ", "; + out << "Start bit: " << sig.start_bit << ", "; + out << "Size: " << 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 << "Unit: (" << sig.unit << "), "; + out << "receivers: "; + for (const auto& reciever : sig.receivers) { + out << reciever; + } + return out << "}"; +} +} diff --git a/src/utils.cpp b/src/utils.cpp index 35cff0c..eed3134 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,82 +1,82 @@ -#include -#include -#include -#include - -#include - -namespace utils { - -std::istream& StreamHandler::get_line(std::istream& stream, std::string& line) { - std::string newline; - - std::getline(stream, newline); - - // Windows CRLF (\r\n) - if (newline.size() && newline[newline.size() - 1] == '\r') { - line = newline.substr(0, newline.size() - 1); - // MacOS LF (\r) - } else if (newline.size() && newline[newline.size()] == '\r') { - line = newline.replace(newline.size(), 1, "\n"); - } else { - line = newline; - } - - return stream; -} - -std::istream& StreamHandler::get_next_non_blank_line(std::istream& stream, std::string& line) { - bool is_blank = true; - - const std::regex whitespace_re("\\s*(.*)"); - std::smatch match; - - while (is_blank) { - utils::StreamHandler::get_line(stream, line); - - std::regex_search(line, match, whitespace_re); - - if ((!line.empty() && !match.empty()) || (stream.eof())) { - if ((match.length(1) > 0) || (stream.eof())) { - is_blank = false; - } - } - } - - return stream; -} - -std::istream& StreamHandler::skip_to_next_blank_line(std::istream& stream, std::string& line) { - bool line_is_empty = false; - - const std::regex whitespace_re("\\s*(.*)"); - std::smatch match; - - while (!line_is_empty) { - utils::StreamHandler::get_line(stream, line); - - std::regex_search(line, match, whitespace_re); - - if ((match.length(1) == 0) || (stream.eof())) { - line_is_empty = true; - } - } - - return stream; -} - -std::string String::trim(const std::string& line) { - const char* WhiteSpace = " \t\v\r\n"; - std::size_t start = line.find_first_not_of(WhiteSpace); - std::size_t end = line.find_last_not_of(WhiteSpace); - return start == end ? std::string() : line.substr(start, end - start + 1); -} - -double String::convert_to_double(const std::string& value, double default_value) { - double converted_value = default_value; - fast_float::from_chars(value.data(), value.data() + value.size(), converted_value); - // converted_value = std::stod(value); - return converted_value; -} - -} // Namespace Utils +#include +#include +#include +#include +#include +#include + +namespace Utils { + +std::istream& StreamHandler::get_line(std::istream& stream, std::string& line) { + std::string newline; + + std::getline(stream, newline); + + // Windows CRLF (\r\n) + if (!newline.empty() && newline[newline.size() - 1] == '\r') { + line = newline.substr(0, newline.size() - 1); + // MacOS LF (\r) + } else if (!newline.empty() && newline[newline.size()] == '\r') { + line = newline.replace(newline.size(), 1, "\n"); + } else { + line = newline; + } + + return stream; +} + +std::istream& StreamHandler::get_next_non_blank_line(std::istream& stream, std::string& line) { + bool is_blank = true; + + const std::regex whitespace_re("\\s*(.*)"); + std::smatch match; + + while (is_blank) { + Utils::StreamHandler::get_line(stream, line); + + std::regex_search(line, match, whitespace_re); + + if ((!line.empty() && !match.empty()) || (stream.eof())) { + if ((match.length(1) > 0) || (stream.eof())) { + is_blank = false; + } + } + } + + return stream; +} + +std::istream& StreamHandler::skip_to_next_blank_line(std::istream& stream, std::string& line) { + bool line_is_empty = false; + + const std::regex whitespace_re("\\s*(.*)"); + std::smatch match; + + while (!line_is_empty) { + Utils::StreamHandler::get_line(stream, line); + + std::regex_search(line, match, whitespace_re); + + if ((match.length(1) == 0) || (stream.eof())) { + line_is_empty = true; + } + } + + return stream; +} + +std::string String::trim(const std::string& line) { + const char* WhiteSpace = " \t\v\r\n"; + std::size_t start = line.find_first_not_of(WhiteSpace); + std::size_t end = line.find_last_not_of(WhiteSpace); + return start == end ? std::string() : line.substr(start, end - start + 1); +} + +double String::convert_to_double(const std::string& value, double default_value) { + double converted_value = default_value; + // NOLINTNEXTLINE -- Trying to iterators on the value causes the test to infinitly hang on windows builds + fast_float::from_chars(value.data(), value.data() + value.size(), converted_value); + return converted_value; +} + +} // Namespace Utils diff --git a/test/dbcs/MissingVersion.dbc b/test/dbcs/MissingVersion.dbc index 0db087a..40d5967 100644 --- a/test/dbcs/MissingVersion.dbc +++ b/test/dbcs/MissingVersion.dbc @@ -1,36 +1,36 @@ -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] "" DBG \ No newline at end of file +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] "" DBG diff --git a/test/locale_testing/test_locale_main.cpp b/test/locale_testing/test_locale_main.cpp index 7336545..537a81f 100644 --- a/test/locale_testing/test_locale_main.cpp +++ b/test/locale_testing/test_locale_main.cpp @@ -31,7 +31,7 @@ class testRunListener : public Catch::EventListenerBase { CATCH_REGISTER_LISTENER(testRunListener) TEST_CASE("Should parse doubld string locale independently") { - REQUIRE(Catch::Approx(utils::String::convert_to_double("6.82")) == 6.82); + REQUIRE(Catch::Approx(Utils::String::convert_to_double("6.82")) == 6.82); } TEST_CASE("Should process message with floats locale indpendently") { @@ -42,36 +42,36 @@ TEST_CASE("Should process message with floats locale indpendently") { SG_ Sig4 : 7|16@0- (1,-10) [0|32767] "" Vector__XXX)"; const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - auto parser = libdbc::DbcParser(); + auto parser = Libdbc::DbcParser(); parser.parse_file(filename); REQUIRE(parser.get_messages().size() == 1); REQUIRE(parser.get_messages().at(0).name() == "MSG1"); - REQUIRE(parser.get_messages().at(0).getSignals().size() == 4); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 4); SECTION("Evaluating first message") { - const auto signal = parser.get_messages().at(0).getSignals().at(0); + const auto signal = parser.get_messages().at(0).get_signals().at(0); REQUIRE(signal.factor == 0.1); REQUIRE(signal.offset == 0); REQUIRE(signal.min == -3276.8); REQUIRE(signal.max == -3276.7); } SECTION("Evaluating second message") { - const auto signal = parser.get_messages().at(0).getSignals().at(1); + const auto signal = parser.get_messages().at(0).get_signals().at(1); REQUIRE(signal.factor == 0.1); REQUIRE(signal.offset == 0); REQUIRE(signal.min == -3276.8); REQUIRE(signal.max == -3276.7); } SECTION("Evaluating third message") { - const auto signal = parser.get_messages().at(0).getSignals().at(2); + const auto signal = parser.get_messages().at(0).get_signals().at(2); REQUIRE(signal.factor == 10); REQUIRE(signal.offset == 0); REQUIRE(signal.min == -3276.8); REQUIRE(signal.max == -3276.7); } SECTION("Evaluating fourth message") { - const auto signal = parser.get_messages().at(0).getSignals().at(3); + const auto signal = parser.get_messages().at(0).get_signals().at(3); REQUIRE(signal.factor == 1); REQUIRE(signal.offset == -10); REQUIRE(signal.min == 0); diff --git a/test/single_header_testing/test_single_header.cpp b/test/single_header_testing/test_single_header.cpp index d61dac6..c512d35 100644 --- a/test/single_header_testing/test_single_header.cpp +++ b/test/single_header_testing/test_single_header.cpp @@ -7,18 +7,18 @@ #include TEST_CASE("Testing dbc file loading", "[fileio]") { - auto parser = std::unique_ptr(new libdbc::DbcParser()); + auto parser = std::unique_ptr(new Libdbc::DbcParser()); SECTION("Loading a single simple dbc file", "[dbc]") { std::vector nodes = {"DBG", "DRIVER", "IO", "MOTOR", "SENSOR"}; - libdbc::Message msg(500, "IO_DEBUG", 4, "IO"); + Libdbc::Message msg(500, "IO_DEBUG", 4, "IO"); std::vector receivers{"DBG"}; - libdbc::Signal sig("IO_DEBUG_test_unsigned", false, 0, 8, false, false, 1, 0, 0, 0, "", receivers); - msg.appendSignal(sig); + Libdbc::Signal sig("IO_DEBUG_test_unsigned", false, 0, 8, false, false, 1, 0, 0, 0, "", receivers); + msg.append_signal(sig); - std::vector msgs = {msg}; + std::vector msgs = {msg}; parser->parse_file(SIMPLE_DBC_FILE); @@ -28,7 +28,7 @@ TEST_CASE("Testing dbc file loading", "[fileio]") { REQUIRE(parser->get_messages() == msgs); - REQUIRE(parser->get_messages().front().getSignals() == msg.getSignals()); + REQUIRE(parser->get_messages().front().get_signals() == msg.get_signals()); } } @@ -38,19 +38,43 @@ TEST_CASE("Testing big endian, little endian") { SG_ Sig2 : 39|16@1- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX)"; const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - auto parser = libdbc::DbcParser(); + auto parser = Libdbc::DbcParser(); parser.parse_file(filename.c_str()); REQUIRE(parser.get_messages().size() == 1); REQUIRE(parser.get_messages().at(0).name() == "MSG1"); REQUIRE(parser.get_messages().at(0).size() == 8); - REQUIRE(parser.get_messages().at(0).getSignals().size() == 2); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 2); { - const auto signal = parser.get_messages().at(0).getSignals().at(0); + const auto signal = parser.get_messages().at(0).get_signals().at(0); REQUIRE(signal.is_bigendian == true); } { - const auto signal = parser.get_messages().at(0).getSignals().at(1); + const auto signal = parser.get_messages().at(0).get_signals().at(1); + REQUIRE(signal.is_bigendian == false); + } +} + +TEST_CASE("Testing file stream mirrors the filename interface") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ Sig1 : 55|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig2 : 39|16@1- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + std::ifstream file(filename.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(file); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).size() == 8); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 2); + { + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.is_bigendian == true); + } + { + const auto signal = parser.get_messages().at(0).get_signals().at(1); REQUIRE(signal.is_bigendian == false); } } diff --git a/test/test_dbc.cpp b/test/test_dbc.cpp index 508df5c..8ae1b54 100644 --- a/test/test_dbc.cpp +++ b/test/test_dbc.cpp @@ -1,240 +1,311 @@ -#include "testing_utils/common.hpp" -#include "testing_utils/defines.hpp" -#include -#include -#include -#include -#include - -TEST_CASE("Testing dbc file loading error issues", "[fileio][error]") { - auto parser = std::unique_ptr(new libdbc::DbcParser()); - - SECTION("Loading a non dbc file should throw an error", "[error]") { - REQUIRE_THROWS_AS(parser->parse_file(TEXT_FILE), libdbc::validity_error); - } - - SECTION("Loading a dbc with bad headers throws an error", "[error]") { - REQUIRE_THROWS_AS(parser->parse_file(MISSING_VERSION_DBC_FILE), libdbc::validity_error); - } - - SECTION("Loading a dbc without the required bit timing section (BS_:)", "[error]") { - REQUIRE_THROWS_AS(parser->parse_file(MISSING_BIT_TIMING_DBC_FILE), libdbc::validity_error); - } - - SECTION("Loading a dbc with some missing namespace section tags (NS_ :)", "[error]") { - // Confusion about this type of error. it appears that the header isn't - // very well standardized for now we ignore this type of error. - CHECK_NOTHROW(parser->parse_file(MISSING_NEW_SYMBOLS_DBC_FILE)); - } - - SECTION("Verify that what() method is accessible for all exceptions", "[error]") { - auto generic_error = libdbc::exception(); - REQUIRE(std::string{generic_error.what()} == "libdbc exception occurred"); - - auto validity_check = libdbc::validity_error(); - REQUIRE(std::string{validity_check.what()} == "Invalid DBC file"); - } -} - -TEST_CASE("Testing dbc file loading", "[fileio]") { - auto parser = std::unique_ptr(new libdbc::DbcParser()); - - SECTION("Loading a single simple dbc file", "[dbc]") { - std::vector nodes = {"DBG", "DRIVER", "IO", "MOTOR", "SENSOR"}; - - libdbc::Message msg(500, "IO_DEBUG", 4, "IO"); - - std::vector receivers{"DBG"}; - libdbc::Signal sig("IO_DEBUG_test_unsigned", false, 0, 8, false, false, 1, 0, 0, 0, "", receivers); - msg.appendSignal(sig); - - std::vector msgs = {msg}; - - parser->parse_file(SIMPLE_DBC_FILE); - - REQUIRE(parser->get_version() == "1.0.0"); - - REQUIRE(parser->get_nodes() == nodes); - - REQUIRE(parser->get_messages() == msgs); - - REQUIRE(parser->get_messages().front().getSignals() == msg.getSignals()); - } -} - -TEST_CASE("Testing big endian, little endian") { - std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX - SG_ Sig1 : 55|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX - SG_ Sig2 : 39|16@1- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX)"; - const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - - auto parser = libdbc::DbcParser(); - parser.parse_file(filename.c_str()); - - REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).name() == "MSG1"); - REQUIRE(parser.get_messages().at(0).size() == 8); - REQUIRE(parser.get_messages().at(0).getSignals().size() == 2); - { - const auto signal = parser.get_messages().at(0).getSignals().at(0); - REQUIRE(signal.is_bigendian == true); - } - { - const auto signal = parser.get_messages().at(0).getSignals().at(1); - REQUIRE(signal.is_bigendian == false); - } -} - -TEST_CASE("Testing negative values") { - std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 58 Vector__XXX - SG_ Sig1 : 55|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX - SG_ Sig2 : 39|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX - SG_ Sig3 : 23|16@0- (10,0) [-3276.8|-3276.7] "C" Vector__XXX - SG_ Sig4 : 7|16@0- (1,-10) [0|32767] "" Vector__XXX)"; - const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - - auto parser = libdbc::DbcParser(); - parser.parse_file(filename.c_str()); - - REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).name() == "MSG1"); - REQUIRE(parser.get_messages().at(0).size() == 58); - REQUIRE(parser.get_messages().at(0).getSignals().size() == 4); - - SECTION("Evaluating first message") { - const auto signal = parser.get_messages().at(0).getSignals().at(0); - REQUIRE(signal.factor == 0.1); - REQUIRE(signal.offset == 0); - REQUIRE(signal.min == -3276.8); - REQUIRE(signal.max == -3276.7); - } - SECTION("Evaluating second message") { - const auto signal = parser.get_messages().at(0).getSignals().at(1); - REQUIRE(signal.factor == 0.1); - REQUIRE(signal.offset == 0); - REQUIRE(signal.min == -3276.8); - REQUIRE(signal.max == -3276.7); - } - SECTION("Evaluating third message") { - const auto signal = parser.get_messages().at(0).getSignals().at(2); - REQUIRE(signal.factor == 10); - REQUIRE(signal.offset == 0); - REQUIRE(signal.min == -3276.8); - REQUIRE(signal.max == -3276.7); - } - SECTION("Evaluating fourth message") { - const auto signal = parser.get_messages().at(0).getSignals().at(3); - REQUIRE(signal.factor == 1); - REQUIRE(signal.offset == -10); - REQUIRE(signal.min == 0); - REQUIRE(signal.max == 32767); - } -} - -TEST_CASE("Special characters in unit") { - std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 255 Vector__XXX - SG_ Speed : 0|8@1+ (1,0) [0|204] "Km/h" DEVICE1,DEVICE2,DEVICE3)"; - const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - - auto parser = libdbc::DbcParser(); - parser.parse_file(filename.c_str()); - - REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).name() == "MSG1"); - REQUIRE(parser.get_messages().at(0).size() == 255); - REQUIRE(parser.get_messages().at(0).getSignals().size() == 1); - SECTION("Checking that signal with special characters as unit is parsed correctly") { - const auto signal = parser.get_messages().at(0).getSignals().at(0); - REQUIRE(signal.unit.compare("Km/h") == 0); - } -} - -TEST_CASE("Signal Value Description") { - std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX - SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 - SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 -VAL_ 234 State1 123 "Description 1" 0 "Description 2" 90903489 "Big value and special characters &$§())!" ;)"; - const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - - auto parser = libdbc::DbcParser(); - parser.parse_file(filename.c_str()); - - REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).name() == "MSG1"); - REQUIRE(parser.get_messages().at(0).getSignals().size() == 2); - - REQUIRE(parser.get_messages().at(0).getSignals().at(0).svDescriptions.size() == 3); - REQUIRE(parser.get_messages().at(0).getSignals().at(1).svDescriptions.size() == 0); - - const auto signal = parser.get_messages().at(0).getSignals().at(0); - REQUIRE(signal.svDescriptions.at(0).value == 123); - REQUIRE(signal.svDescriptions.at(0).description == "Description 1"); - REQUIRE(signal.svDescriptions.at(1).value == 0); - REQUIRE(signal.svDescriptions.at(1).description == "Description 2"); - REQUIRE(signal.svDescriptions.at(2).value == 90903489); - REQUIRE(signal.svDescriptions.at(2).description == "Big value and special characters &$§())!"); -} - -TEST_CASE("Signal Value Description Extended CAN id") { - std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 3221225472 MSG1: 8 Vector__XXX - SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 - SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 -VAL_ 3221225472 State1 123 "Description 1" 0 "Description 2" 4000000000 "Big value and special characters &$§())!" ;)"; - const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - - auto parser = libdbc::DbcParser(); - parser.parse_file(filename.c_str()); - - REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).name() == "MSG1"); - REQUIRE(parser.get_messages().at(0).getSignals().size() == 2); - - REQUIRE(parser.get_messages().at(0).getSignals().at(0).svDescriptions.size() == 3); - REQUIRE(parser.get_messages().at(0).getSignals().at(1).svDescriptions.size() == 0); - - const auto signal = parser.get_messages().at(0).getSignals().at(0); - REQUIRE(signal.svDescriptions.at(0).value == 123); - REQUIRE(signal.svDescriptions.at(0).description == "Description 1"); - REQUIRE(signal.svDescriptions.at(1).value == 0); - REQUIRE(signal.svDescriptions.at(1).description == "Description 2"); - REQUIRE(signal.svDescriptions.at(2).value == 4000000000); - REQUIRE(signal.svDescriptions.at(2).description == "Big value and special characters &$§())!"); -} - -TEST_CASE("Signal Value Multiple VAL_") { - std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 3221225472 MSG1: 8 Vector__XXX - SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 - SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3" -BO_ 123 MSG2: 8 Vector__XXX - SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 - SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 -VAL_ 3221225472 State1 123 "Description 1" 0 "Description 2" ; -VAL_ 123 State1 123 "Description 3" 0 "Description 4" ;)"; - const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - - auto parser = libdbc::DbcParser(); - parser.parse_file(filename.c_str()); - - REQUIRE(parser.get_messages().size() == 2); - REQUIRE(parser.get_messages().at(0).name() == "MSG1"); - REQUIRE(parser.get_messages().at(1).name() == "MSG2"); - - REQUIRE(parser.get_messages().at(0).getSignals().size() == 2); - - REQUIRE(parser.get_messages().at(0).getSignals().at(0).svDescriptions.size() == 2); - REQUIRE(parser.get_messages().at(0).getSignals().at(1).svDescriptions.size() == 0); - REQUIRE(parser.get_messages().at(1).getSignals().at(0).svDescriptions.size() == 2); - REQUIRE(parser.get_messages().at(1).getSignals().at(1).svDescriptions.size() == 0); - - const auto signal = parser.get_messages().at(0).getSignals().at(0); - REQUIRE(signal.svDescriptions.at(0).value == 123); - REQUIRE(signal.svDescriptions.at(0).description == "Description 1"); - REQUIRE(signal.svDescriptions.at(1).value == 0); - REQUIRE(signal.svDescriptions.at(1).description == "Description 2"); - - const auto signal2 = parser.get_messages().at(1).getSignals().at(0); - REQUIRE(signal2.svDescriptions.at(0).value == 123); - REQUIRE(signal2.svDescriptions.at(0).description == "Description 3"); - REQUIRE(signal2.svDescriptions.at(1).value == 0); - REQUIRE(signal2.svDescriptions.at(1).description == "Description 4"); -} +#include "testing_utils/common.hpp" +#include "testing_utils/defines.hpp" +#include +#include +#include +#include +#include +#include +#include + +using Catch::Matchers::ContainsSubstring; + +TEST_CASE("Testing dbc file loading error issues", "[fileio][error]") { + auto parser = std::unique_ptr(new Libdbc::DbcParser()); + + SECTION("Loading a non dbc file should throw an error", "[error]") { + REQUIRE_THROWS_AS(parser->parse_file(TEXT_FILE), Libdbc::NonDbcFileFormatError); + REQUIRE_THROWS_WITH(parser->parse_file(TEXT_FILE), ContainsSubstring("TextFile.txt")); + } + + SECTION("Loading a dbc with missing version header throws an error (VERSION)", "[error]") { + REQUIRE_THROWS_AS(parser->parse_file(MISSING_VERSION_DBC_FILE), Libdbc::DbcFileIsMissingVersion); + REQUIRE_THROWS_WITH(parser->parse_file(MISSING_VERSION_DBC_FILE), ContainsSubstring("line: (NS_ :)")); + } + + SECTION("Loading a dbc without the required bit timing section (BS_:)", "[error]") { + REQUIRE_THROWS_AS(parser->parse_file(MISSING_BIT_TIMING_DBC_FILE), Libdbc::DbcFileIsMissingBitTiming); + REQUIRE_THROWS_WITH(parser->parse_file(MISSING_BIT_TIMING_DBC_FILE), ContainsSubstring("BU_: DBG DRIVER IO MOTOR SENSOR")); + } + + SECTION("Loading a dbc with some missing namespace section tags (NS_ :)", "[error]") { + // Confusion about this type of error. it appears that the header isn't + // very well standardized for now we ignore this type of error. + REQUIRE_NOTHROW(parser->parse_file(MISSING_NEW_SYMBOLS_DBC_FILE)); + } + + SECTION("Verify that what() method is accessible for all exceptions", "[error]") { + auto generic_error = Libdbc::Exception(); + REQUIRE(std::string{generic_error.what()} == "libdbc exception occurred"); + + auto validity_check = Libdbc::ValidityError(); + REQUIRE(std::string{validity_check.what()} == "Invalid DBC file"); + } +} + +TEST_CASE("Testing dbc file loading", "[fileio]") { + auto parser = std::unique_ptr(new Libdbc::DbcParser()); + + SECTION("Loading a single simple dbc file", "[dbc]") { + std::vector nodes = {"DBG", "DRIVER", "IO", "MOTOR", "SENSOR"}; + + Libdbc::Message msg(500, "IO_DEBUG", 4, "IO"); + + std::vector receivers{"DBG"}; + Libdbc::Signal sig("IO_DEBUG_test_unsigned", false, 0, 8, false, false, 1, 0, 0, 0, "", receivers); + msg.append_signal(sig); + + std::vector msgs = {msg}; + + parser->parse_file(SIMPLE_DBC_FILE); + + REQUIRE(parser->get_version() == "1.0.0"); + + REQUIRE(parser->get_nodes() == nodes); + + REQUIRE(parser->get_messages() == msgs); + + REQUIRE(parser->get_messages().front().get_signals() == msg.get_signals()); + } +} + +TEST_CASE("Testing big endian, little endian") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ Sig1 : 55|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig2 : 39|16@1- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).size() == 8); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 2); + { + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.is_bigendian == true); + } + { + const auto signal = parser.get_messages().at(0).get_signals().at(1); + REQUIRE(signal.is_bigendian == false); + } +} + +TEST_CASE("Testing negative values") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 58 Vector__XXX + SG_ Sig1 : 55|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig2 : 39|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig3 : 23|16@0- (10,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig4 : 7|16@0- (1,-10) [0|32767] "" Vector__XXX)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).size() == 58); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 4); + + SECTION("Evaluating first message") { + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.factor == 0.1); + REQUIRE(signal.offset == 0); + REQUIRE(signal.min == -3276.8); + REQUIRE(signal.max == -3276.7); + } + SECTION("Evaluating second message") { + const auto signal = parser.get_messages().at(0).get_signals().at(1); + REQUIRE(signal.factor == 0.1); + REQUIRE(signal.offset == 0); + REQUIRE(signal.min == -3276.8); + REQUIRE(signal.max == -3276.7); + } + SECTION("Evaluating third message") { + const auto signal = parser.get_messages().at(0).get_signals().at(2); + REQUIRE(signal.factor == 10); + REQUIRE(signal.offset == 0); + REQUIRE(signal.min == -3276.8); + REQUIRE(signal.max == -3276.7); + } + SECTION("Evaluating fourth message") { + const auto signal = parser.get_messages().at(0).get_signals().at(3); + REQUIRE(signal.factor == 1); + REQUIRE(signal.offset == -10); + REQUIRE(signal.min == 0); + REQUIRE(signal.max == 32767); + } +} + +TEST_CASE("Special characters in unit") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 255 Vector__XXX + SG_ Speed : 0|8@1+ (1,0) [0|204] "Km/h" DEVICE1,DEVICE2,DEVICE3)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).size() == 255); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 1); + SECTION("Checking that signal with special characters as unit is parsed correctly") { + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.unit.compare("Km/h") == 0); + } +} + +TEST_CASE("Signal Value Description") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 234 State1 123 "Description 1" 0 "Description 2" 90903489 "Big value and special characters &$§())!" ;)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 2); + + REQUIRE(parser.get_messages().at(0).get_signals().at(0).value_descriptions.size() == 3); + REQUIRE(parser.get_messages().at(0).get_signals().at(1).value_descriptions.size() == 0); + + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.value_descriptions.at(0).value == 123); + REQUIRE(signal.value_descriptions.at(0).description == "Description 1"); + REQUIRE(signal.value_descriptions.at(1).value == 0); + REQUIRE(signal.value_descriptions.at(1).description == "Description 2"); + REQUIRE(signal.value_descriptions.at(2).value == 90903489); + REQUIRE(signal.value_descriptions.at(2).description == "Big value and special characters &$§())!"); +} + +TEST_CASE("Signal Value Description Extended CAN id") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 3221225472 MSG1: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 3221225472 State1 123 "Description 1" 0 "Description 2" 4000000000 "Big value and special characters &$§())!" ;)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 2); + + REQUIRE(parser.get_messages().at(0).get_signals().at(0).value_descriptions.size() == 3); + REQUIRE(parser.get_messages().at(0).get_signals().at(1).value_descriptions.size() == 0); + + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.value_descriptions.at(0).value == 123); + REQUIRE(signal.value_descriptions.at(0).description == "Description 1"); + REQUIRE(signal.value_descriptions.at(1).value == 0); + REQUIRE(signal.value_descriptions.at(1).description == "Description 2"); + REQUIRE(signal.value_descriptions.at(2).value == 4000000000); + REQUIRE(signal.value_descriptions.at(2).description == "Big value and special characters &$§())!"); +} + +TEST_CASE("Signal Value Multiple VAL_") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 3221225472 MSG1: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3" +BO_ 123 MSG2: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 3221225472 State1 123 "Description 1" 0 "Description 2" ; +VAL_ 123 State1 123 "Description 3" 0 "Description 4" ;)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 2); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(1).name() == "MSG2"); + + REQUIRE(parser.get_messages().at(0).get_signals().size() == 2); + + REQUIRE(parser.get_messages().at(0).get_signals().at(0).value_descriptions.size() == 2); + REQUIRE(parser.get_messages().at(0).get_signals().at(1).value_descriptions.size() == 0); + REQUIRE(parser.get_messages().at(1).get_signals().at(0).value_descriptions.size() == 2); + REQUIRE(parser.get_messages().at(1).get_signals().at(1).value_descriptions.size() == 0); + + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.value_descriptions.at(0).value == 123); + REQUIRE(signal.value_descriptions.at(0).description == "Description 1"); + REQUIRE(signal.value_descriptions.at(1).value == 0); + REQUIRE(signal.value_descriptions.at(1).description == "Description 2"); + + const auto signal2 = parser.get_messages().at(1).get_signals().at(0); + REQUIRE(signal2.value_descriptions.at(0).value == 123); + REQUIRE(signal2.value_descriptions.at(0).description == "Description 3"); + REQUIRE(signal2.value_descriptions.at(1).value == 0); + REQUIRE(signal2.value_descriptions.at(1).description == "Description 4"); +} + +TEST_CASE("Should parse DBC with empty BU_", "[error][optional]") { + std::string contents = R"(VERSION "" + + +NS_ : + +BS_: + +BU_: + + +BO_ 293 Msg1: 2 Vector__XXX + SG_ Wert7 : 0|16@1- (1,0) [0|0] "" Vector__XXX + +BO_ 292 Msg2: 1 Vector__XXX + SG_ Wert8 : 56|8@1- (1,0) [0|0] "" Vector__XXX +)"; + const auto filename = create_temporary_dbc_with(contents.c_str()); + + auto parser = Libdbc::DbcParser(); + REQUIRE_NOTHROW(parser.parse_file(filename.c_str())); + + REQUIRE(parser.get_messages().size() == 2); + REQUIRE(parser.get_messages().at(0).name() == "Msg1"); + REQUIRE(parser.get_messages().at(1).name() == "Msg2"); +} + +TEST_CASE("Should report unused lines since we don't have tracing.", "[parsing]") { + std::string contents = R"(VERSION "" + +NS_ : + +BS_: + +BU_: + + +BO_ 293 Msg1: 2 Vector__XXX + SG_ Whitespace: | 0|16@1- (1,0) [0|0] "" Vector__XXX + SG_ Wert7 : 0|16@1- (1,0) [0|0] "" Vector__XXX + SG_ Wert8 : 0|16@1- (1,0) [0|0] "" Vector__XXX + +BO_ 292 Msg2: 1 Vector__XXX + SG_ Wert8 : 56|8@1- (1,0) [0|0] "" Vector__XXX + SB_ not a correct line + +BO_ have a issue here: +)"; + + const auto filename = create_temporary_dbc_with(contents.c_str()); + + auto parser = Libdbc::DbcParser(); + REQUIRE_NOTHROW(parser.parse_file(filename.c_str())); + + REQUIRE(parser.get_messages().size() == 2); + REQUIRE(parser.get_messages()[0].size() == 2); + REQUIRE(parser.get_messages()[1].size() == 1); + + auto unused = parser.unused_lines(); + + // We could match them all here but i think just a check that the size is sufficent. + REQUIRE(unused.size() == 3); +} diff --git a/test/test_parse_message.cpp b/test/test_parse_message.cpp index 3c3c301..08d68f3 100644 --- a/test/test_parse_message.cpp +++ b/test/test_parse_message.cpp @@ -19,12 +19,12 @@ BO_ 123 MSG2: 8 Vector__XXX )"; const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - libdbc::DbcParser parser; + Libdbc::DbcParser parser; parser.parse_file(filename.c_str()); SECTION("Evaluating unknown` message id") { std::vector out_values; - CHECK(parser.parseMessage(578, std::vector({0xFF, 0xA2}), out_values) == libdbc::Message::ParseSignalsStatus::ErrorUnknownID); + CHECK(parser.parse_message(578, std::vector({0xFF, 0xA2}), out_values) == Libdbc::Message::ParseSignalsStatus::ErrorUnknownID); } } @@ -40,12 +40,12 @@ TEST_CASE("Parse Message Big Number not aligned little endian") { )"; const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - libdbc::DbcParser parser; + Libdbc::DbcParser parser; parser.parse_file(filename); SECTION("Evaluating first message") { std::vector out_values; - CHECK(parser.parseMessage(337, std::vector({0, 4, 252, 19, 0, 0, 0, 0}), out_values) == libdbc::Message::ParseSignalsStatus::Success); + CHECK(parser.parse_message(337, std::vector({0, 4, 252, 19, 0, 0, 0, 0}), out_values) == Libdbc::Message::ParseSignalsStatus::Success); std::vector refData{2, 0, 0, 1, 0, 0, 0}; CHECK(refData.size() == 7); CHECK(out_values.size() == refData.size()); @@ -56,7 +56,7 @@ TEST_CASE("Parse Message Big Number not aligned little endian") { SECTION("Evaluating second message") { std::vector out_values; - CHECK(parser.parseMessage(337, std::vector({47, 4, 60, 29, 0, 0, 0, 0}), out_values) == libdbc::Message::ParseSignalsStatus::Success); + CHECK(parser.parse_message(337, std::vector({47, 4, 60, 29, 0, 0, 0, 0}), out_values) == Libdbc::Message::ParseSignalsStatus::Success); std::vector refData{3, 32, 0, 1, 0, 0, 47}; CHECK(refData.size() == 7); CHECK(out_values.size() == refData.size()); @@ -67,7 +67,7 @@ TEST_CASE("Parse Message Big Number not aligned little endian") { SECTION("Evaluating third message") { std::vector out_values; - CHECK(parser.parseMessage(337, std::vector({57, 4, 250, 29, 0, 0, 0, 0}), out_values) == libdbc::Message::ParseSignalsStatus::Success); + CHECK(parser.parse_message(337, std::vector({57, 4, 250, 29, 0, 0, 0, 0}), out_values) == Libdbc::Message::ParseSignalsStatus::Success); std::vector refData{3, 51, 0, 1, 0, 0, 57}; CHECK(refData.size() == 7); CHECK(out_values.size() == refData.size()); @@ -85,12 +85,12 @@ TEST_CASE("Parse Message little endian") { SG_ SOC : 16|16@1+ (0.01,0) [0|100] "%" DEVICE1)"; const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - libdbc::DbcParser parser; + Libdbc::DbcParser parser; parser.parse_file(filename); std::vector data{0x08, 0x27, 0xa3, 0x22, 0xe5, 0x1f, 0x45, 0x14}; // little endian std::vector result_values; - REQUIRE(parser.parseMessage(0x21d, data, result_values) == libdbc::Message::ParseSignalsStatus::Success); + REQUIRE(parser.parse_message(0x21d, data, result_values) == Libdbc::Message::ParseSignalsStatus::Success); REQUIRE(result_values.size() == 4); REQUIRE(Catch::Approx(result_values.at(0)) == 11.89); @@ -115,12 +115,12 @@ TEST_CASE("Parse Message big endian signed values") { SG_ Sig12 : 23|16@0+ (0.1,0) [0|6553.5] "A" Vector__XXX)"; const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - libdbc::DbcParser p; + Libdbc::DbcParser p; p.parse_file(filename.c_str()); std::vector data{13, 177, 0, 216, 251, 180, 0, 31}; // big endian std::vector result_values; - REQUIRE(p.parseMessage(545, data, result_values) == libdbc::Message::ParseSignalsStatus::Success); + REQUIRE(p.parse_message(545, data, result_values) == Libdbc::Message::ParseSignalsStatus::Success); REQUIRE(result_values.size() == 12); REQUIRE(Catch::Approx(result_values.at(0)) == 0); REQUIRE(Catch::Approx(result_values.at(1)) == 0); @@ -144,12 +144,12 @@ TEST_CASE("Parse Message with non byte aligned values") { SG_ Id_Current : 0|10@1- (1,0) [-512|512] "A" Vector__XXX)"; const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - libdbc::DbcParser p; + Libdbc::DbcParser p; p.parse_file(filename); std::vector data{131, 51, 33, 9, 33, 0, 0, 0}; std::vector result_values; - REQUIRE(p.parseMessage(403, data, result_values) == libdbc::Message::ParseSignalsStatus::Success); + REQUIRE(p.parse_message(403, data, result_values) == Libdbc::Message::ParseSignalsStatus::Success); REQUIRE(result_values.size() == 4); REQUIRE(Catch::Approx(result_values.at(0)) == 26.4); REQUIRE(Catch::Approx(result_values.at(1)) == 146); @@ -163,13 +163,49 @@ TEST_CASE("Parse Message data length < 8 unsigned") { SG_ Msg1Sig2 : 15|8@0+ (1,0) [-3276.8|-3276.7] "km/h" Vector__XXX)"; const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); - libdbc::DbcParser p; + Libdbc::DbcParser p; p.parse_file(filename); std::vector data{0x1, 0x2}; std::vector result_values; - REQUIRE(p.parseMessage(234, data, result_values) == libdbc::Message::ParseSignalsStatus::Success); + REQUIRE(p.parse_message(234, data, result_values) == Libdbc::Message::ParseSignalsStatus::Success); REQUIRE(result_values.size() == 2); REQUIRE(Catch::Approx(result_values.at(0)) == 0x1); REQUIRE(Catch::Approx(result_values.at(1)) == 0x2); } + +TEST_CASE("Parse message with BO_ on single line should fail.") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ +234 MSG1: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 234 State1 123 "Description 1" 0 "Description 2" 90903489 "Big value and special characters &$§())!")"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + Libdbc::DbcParser p; + p.parse_file(filename); + + std::vector data{0x1, 0x2}; + std::vector result_values; + REQUIRE(p.get_messages().size() == 0); + REQUIRE(p.parse_message(234, data, result_values) == Libdbc::Message::ParseSignalsStatus::ErrorUnknownID); +} + +TEST_CASE("Parse signal with SG_ on single line should fail.") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ +State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 234 State1 123 "Description 1" 0 "Description 2" 90903489 "Big value and special characters &$§())!")"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + Libdbc::DbcParser p; + p.parse_file(filename); + + std::vector data{0x1, 0x2}; + std::vector result_values; + REQUIRE(p.get_messages().size() == 1); + REQUIRE(p.parse_message(234, data, result_values) == Libdbc::Message::ParseSignalsStatus::Success); + REQUIRE(result_values.size() == 1); + REQUIRE(Catch::Approx(result_values.at(0)) == 0x1); +} diff --git a/test/test_utils.cpp b/test/test_utils.cpp index ea3d594..af4929c 100644 --- a/test/test_utils.cpp +++ b/test/test_utils.cpp @@ -1,93 +1,95 @@ -#include "testing_utils/defines.hpp" -#include -#include - -#include - -using namespace utils; - -TEST_CASE("Basic file input with safe get_line that is non line ending specific", "") { - SECTION("Verify various line ending input files") { - std::ifstream TextFile; - std::string test; - - TextFile.open(TEXT_FILE, std::ios::in); - CHECK(TextFile.is_open()); - - if (TextFile.is_open()) { - StreamHandler::get_line(TextFile, test); - REQUIRE(test == "This is a non dbc formatted file."); - StreamHandler::get_line(TextFile, test); - REQUIRE(test == ""); - StreamHandler::get_line(TextFile, test); - REQUIRE(test == "Make sure things pass with this"); - StreamHandler::get_line(TextFile, test); - REQUIRE(test == "Who knows what might happen."); - - TextFile.close(); - } - } -} - -TEST_CASE("Test line finding utility functions", "") { - std::string line; - std::string test_string = - "hello\n\ - \n\ - \n\ - \n\ -this is not blank\n\ -maybe not this one either\n\ -\n\ -Someone wrote something....\n\ - b\n\ -end"; - - std::istringstream stream(test_string); - - SECTION("Test skipping empty lines") { - StreamHandler::get_line(stream, line); - - CHECK(line == "hello"); - - StreamHandler::get_next_non_blank_line(stream, line); - REQUIRE(line == "this is not blank"); - - StreamHandler::skip_to_next_blank_line(stream, line); - REQUIRE(line == ""); - - StreamHandler::get_next_non_blank_line(stream, line); - REQUIRE(line == "Someone wrote something...."); - - StreamHandler::get_next_non_blank_line(stream, line); - REQUIRE(line == " b"); - - StreamHandler::get_next_non_blank_line(stream, line); - REQUIRE(line == "end"); - - SECTION("Test end of the files", "[edge case]") { - StreamHandler::get_next_non_blank_line(stream, line); - REQUIRE(line == ""); - - StreamHandler::skip_to_next_blank_line(stream, line); - REQUIRE(line == ""); - } - } -} - -TEST_CASE("Test the string trim feature", "[string]") { - std::string s = " there might be some white space.... "; - - REQUIRE(String::trim(s) == "there might be some white space...."); -} - -TEST_CASE("Test string split feature", "[string]") { - std::string s = "name1 name2 name3 name4 name5 "; - std::vector vs = {"name1", "name2", "name3", "name4", "name5"}; - - std::vector v; - - String::split(s, v); - - REQUIRE(v == vs); -} +#include "testing_utils/defines.hpp" +#include +#include +#include +#include + +namespace Utils { + +TEST_CASE("Basic file input with safe get_line that is non line ending specific", "") { + SECTION("Verify various line ending input files") { + std::ifstream TextFile; + std::string test; + + TextFile.open(TEXT_FILE, std::ios::in); + CHECK(TextFile.is_open()); + + if (TextFile.is_open()) { + StreamHandler::get_line(TextFile, test); + REQUIRE(test == "This is a non dbc formatted file."); + StreamHandler::get_line(TextFile, test); + REQUIRE(test == ""); + StreamHandler::get_line(TextFile, test); + REQUIRE(test == "Make sure things pass with this"); + StreamHandler::get_line(TextFile, test); + REQUIRE(test == "Who knows what might happen."); + + TextFile.close(); + } + } +} + +TEST_CASE("Test line finding utility functions", "") { + std::string line; + std::string test_string = + "hello\n\ + \n\ + \n\ + \n\ +this is not blank\n\ +maybe not this one either\n\ +\n\ +Someone wrote something....\n\ + b\n\ +end"; + + std::istringstream stream(test_string); + + SECTION("Test skipping empty lines") { + StreamHandler::get_line(stream, line); + + CHECK(line == "hello"); + + StreamHandler::get_next_non_blank_line(stream, line); + REQUIRE(line == "this is not blank"); + + StreamHandler::skip_to_next_blank_line(stream, line); + REQUIRE(line == ""); + + StreamHandler::get_next_non_blank_line(stream, line); + REQUIRE(line == "Someone wrote something...."); + + StreamHandler::get_next_non_blank_line(stream, line); + REQUIRE(line == " b"); + + StreamHandler::get_next_non_blank_line(stream, line); + REQUIRE(line == "end"); + + SECTION("Test end of the files", "[edge case]") { + StreamHandler::get_next_non_blank_line(stream, line); + REQUIRE(line == ""); + + StreamHandler::skip_to_next_blank_line(stream, line); + REQUIRE(line == ""); + } + } +} + +TEST_CASE("Test the string trim feature", "[string]") { + std::string s = " there might be some white space.... "; + + REQUIRE(String::trim(s) == "there might be some white space...."); +} + +TEST_CASE("Test string split feature", "[string]") { + std::string s = "name1 name2 name3 name4 name5 "; + std::vector vs = {"name1", "name2", "name3", "name4", "name5"}; + + std::vector v; + + String::split(s, v); + + REQUIRE(v == vs); +} + +} // Utils diff --git a/test/testing_utils/common.cpp b/test/testing_utils/common.cpp index e058054..4c3ef54 100644 --- a/test/testing_utils/common.cpp +++ b/test/testing_utils/common.cpp @@ -23,7 +23,7 @@ std::string generate_unique_filename() { int random_num = dis(gen); // Concatenate time and random number to create a unique filename - return "temp_file_" + std::to_string(milliseconds) + "_" + std::to_string(random_num) + ".txt"; + return "temp_file_" + std::to_string(milliseconds) + "_" + std::to_string(random_num) + ".dbc"; } std::string create_temporary_dbc_with(const char* contents) {