Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/coverage-generate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
node-version: 24
- name: Install dependencies
run: |
sudo apt install -y build-essential doxygen graphviz
sudo apt install -y build-essential cppcheck doxygen graphviz
npm install -g jsonld-cli
npm --prefix scripts ci
- name: Install Rust static analysis tools
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ jobs:
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install -y build-essential doxygen graphviz
sudo apt install -y build-essential cppcheck doxygen graphviz
- name: Install macOS packages
if: runner.os == 'macOS'
run: |
brew update
brew install doxygen gcc go graphviz
brew install cppcheck doxygen gcc go graphviz
- name: Install Rust static analysis tools
run: |
rustup component add clippy
Expand Down
3 changes: 1 addition & 2 deletions src/shacl2code/lang/templates/cpp/objectset.cpp.j2
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ void SHACLObjectSet::add(Ref<SHACLObject> const &ref) {
}
auto p = ref.obj();

if (mObjects.find(p) == mObjects.end()) {
mObjects.insert(p);
if (mObjects.insert(p).second) {
addIndex(p);
}
}
Expand Down
34 changes: 18 additions & 16 deletions src/shacl2code/lang/templates/cpp/walk.hpp.j2
Original file line number Diff line number Diff line change
Expand Up @@ -31,32 +31,34 @@ template <class C> class Ref;
*/
struct EXPORT WalkCallback {
/// Callback when a string property is walked
std::function<void(ObjectPath &, std::optional<std::reference_wrapper<std::string const>> const
&)> const &stringProp = [](auto &, auto const &) {};
std::function<void(ObjectPath &,
std::optional<std::reference_wrapper<std::string const>> const &)>
stringProp = [](auto &, auto const &) {};

/// Callback when an integer property is walked
std::function<void(ObjectPath const &, std::optional<int>)> const &integerProp =
[](auto &, auto const &) {};
std::function<void(ObjectPath const &, std::optional<int>)> integerProp = [](auto &,
auto const &) {};

/// Callback when a boolean property is walked
std::function<void(ObjectPath &, std::optional<bool>)> const &booleanProp = [](auto const &,
auto const &) {};
std::function<void(ObjectPath &, std::optional<bool>)> booleanProp = [](auto const &,
auto const &) {};

/// Callback when a double property is walked
std::function<void(ObjectPath &, std::optional<double>)> const &doubleProp = [](auto &,
auto const &) {};
std::function<void(ObjectPath &, std::optional<double>)> doubleProp = [](auto &, auto const &) {};

/// Callback when a DateTime property is walked
std::function<void(ObjectPath &, std::optional<std::reference_wrapper<DateTime const>> const
&)> const &dateTimeProp = [](auto &, auto const &) {};
std::function<void(ObjectPath &, std::optional<std::reference_wrapper<DateTime const>> const &)>
dateTimeProp = [](auto &, auto const &) {};

/// Callback when an Enum property is walked
std::function<void(ObjectPath &, std::optional<std::reference_wrapper<std::string const>> const
&)> const &enumProp = [](auto &, auto const &) {};
std::function<void(ObjectPath &,
std::optional<std::reference_wrapper<std::string const>> const &)>
enumProp = [](auto &, auto const &) {};

/// Callback when an Any URI property is walked
std::function<void(ObjectPath &, std::optional<std::reference_wrapper<std::string const>> const
&)> const &anyURIProp = [](auto &, auto const &) {};
std::function<void(ObjectPath &,
std::optional<std::reference_wrapper<std::string const>> const &)>
anyURIProp = [](auto &, auto const &) {};

/**
* @brief Callback when an object is walked
Expand All @@ -65,8 +67,8 @@ struct EXPORT WalkCallback {
* (that is, all its properties walked), or false if not
*/
std::function<bool(ObjectPath &,
std::optional<std::reference_wrapper<Ref<SHACLObject> const>> const &)> const
&refProp = [](auto &, auto const &) { return false; };
std::optional<std::reference_wrapper<Ref<SHACLObject> const>> const &)>
refProp = [](auto &, auto const &) { return false; };
};

/* {{ api_def_end }} */
Expand Down
107 changes: 64 additions & 43 deletions tests/test_cpp.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#
# Copyright (c) 2024 Joshua Watt
#
# SPDX-FileContributor: Joshua Watt
# SPDX-FileContributor: Arthit Suriyawongkul
# SPDX-FileCopyrightText: 2024 Joshua Watt
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: MIT

import json
Expand Down Expand Up @@ -275,7 +279,7 @@ def f(code_fragment, *, progress=Progress.RUNS, static=False):
yield f


@pytest.mark.parametrize(
CPP_MODEL_TESTS = (
"args,basename",
[
(
Expand All @@ -288,25 +292,32 @@ def f(code_fragment, *, progress=Progress.RUNS, static=False):
),
],
)


def _generate_cpp(tmp_path, args, basename):
subprocess.run(
[
"shacl2code",
"generate",
]
+ args
+ [
"cpp",
"--output",
tmp_path / basename,
"--version=0.0.1",
],
check=True,
)


@pytest.mark.parametrize(*CPP_MODEL_TESTS)
class TestOutput:
def test_trailing_whitespace(self, tmp_path, args, basename):
"""
Tests that the generated file does not have trailing whitespace
"""
subprocess.run(
[
"shacl2code",
"generate",
]
+ args
+ [
"cpp",
"--output",
tmp_path / basename,
"--version=0.0.1",
],
check=True,
)
_generate_cpp(tmp_path, args, basename)

for fn in tmp_path.iterdir():
with fn.open("r") as f:
Expand All @@ -320,20 +331,7 @@ def test_tabs(self, tmp_path, args, basename):
"""
Tests that the output file doesn't contain tabs
"""
subprocess.run(
[
"shacl2code",
"generate",
]
+ args
+ [
"cpp",
"--output",
tmp_path / basename,
"--version=0.0.1",
],
check=True,
)
_generate_cpp(tmp_path, args, basename)

for fn in tmp_path.iterdir():
if fn.name == "Makefile":
Expand All @@ -346,20 +344,7 @@ def test_tabs(self, tmp_path, args, basename):
), f"{fn}: Line {lineno + 1} has tabs: {line!r}"

def test_output_compile(self, tmp_path, args, basename):
subprocess.run(
[
"shacl2code",
"generate",
]
+ args
+ [
"cpp",
"--output",
tmp_path / basename,
"--version=0.0.1",
],
check=True,
)
_generate_cpp(tmp_path, args, basename)

make_args = [
"make",
Expand All @@ -383,6 +368,42 @@ def test_output_compile(self, tmp_path, args, basename):
)


@pytest.mark.parametrize(*CPP_MODEL_TESTS)
class TestStaticAnalysis:
"""
Static analysis checks for the generated C++ code
"""

def test_cppcheck(self, tmp_path, args, basename):
"""
cppcheck static analysis.

Excludes the bundled "-jsonld" source, not generated code.

duplInheritedMember is suppressed: every generated class
deliberately redeclares its own static Type descriptor.
"""
_generate_cpp(tmp_path, args, basename)

sources = sorted(
p
for p in tmp_path.iterdir()
if p.suffix in (".c", ".cpp", ".h", ".hpp") and "-jsonld" not in p.name
)

subprocess.run(
[
"cppcheck",
"--enable=performance,portability,warning",
"--error-exitcode=1",
"--suppress=duplInheritedMember",
]
+ sources,
check=True,
cwd=tmp_path,
)


def test_compile(compile_test):
compile_test("")

Expand Down
Loading