Skip to content

Commit d0eed5d

Browse files
author
Your Name
committed
Add unit tests
1 parent 4517bc7 commit d0eed5d

5 files changed

Lines changed: 897 additions & 0 deletions

File tree

test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ if (BUILD_TESTING)
4747
if (REGISTER_TESTS)
4848
# CMAKE_MATCH_<n> usage for if (MATCHES) requires CMake 3.9
4949

50+
add_subdirectory(addon)
51+
5052
find_package(Threads REQUIRED)
5153
include(ProcessorCount)
5254
ProcessorCount(N)

test/addon/CMakeLists.txt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
if (NOT Python_Interpreter_FOUND)
2+
message(WARNING "Python interpreter not found - skipping addon tests.")
3+
return()
4+
endif()
5+
6+
set(VENV_DIR ${CMAKE_CURRENT_BINARY_DIR}/venv)
7+
if (WIN32)
8+
set(VENV_PYTHON ${VENV_DIR}/Scripts/python.exe)
9+
else()
10+
set(VENV_PYTHON ${VENV_DIR}/bin/python)
11+
endif()
12+
13+
# fixture: create a virtual environment and install the python dependencies into it
14+
add_test(NAME addon-venv-create
15+
COMMAND ${Python_EXECUTABLE} -m venv ${VENV_DIR})
16+
set_tests_properties(addon-venv-create PROPERTIES
17+
FIXTURES_SETUP addon-venv-dir)
18+
19+
add_test(NAME addon-venv-install
20+
COMMAND ${VENV_PYTHON} -m pip install -r ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt)
21+
set_tests_properties(addon-venv-install PROPERTIES
22+
FIXTURES_REQUIRED addon-venv-dir
23+
FIXTURES_SETUP addon-venv
24+
TIMEOUT 300)
25+
26+
add_test(NAME addon-cppcheckdata
27+
COMMAND ${VENV_PYTHON} -m pytest --cppcheck-binary=$<TARGET_FILE:cppcheck> ${CMAKE_CURRENT_SOURCE_DIR})
28+
set_tests_properties(addon-cppcheckdata PROPERTIES
29+
FIXTURES_REQUIRED addon-venv)

test/addon/conftest.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
"""pytest configuration for the addon tests.
2+
3+
The tests exercise addons/cppcheckdata.py against dump files that are
4+
generated on the fly with the cppcheck binary given by --cppcheck-binary.
5+
"""
6+
import os
7+
import shutil
8+
import subprocess
9+
import sys
10+
11+
import pytest
12+
13+
# Make 'import cppcheckdata' resolve to <repo>/addons/cppcheckdata.py
14+
_ADDONS_DIR = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'addons'))
15+
if _ADDONS_DIR not in sys.path:
16+
sys.path.insert(0, _ADDONS_DIR)
17+
18+
19+
def pytest_addoption(parser):
20+
parser.addoption('--cppcheck-binary',
21+
default='cppcheck',
22+
help='path to the cppcheck binary used to generate dump files '
23+
'(default: cppcheck found in PATH)')
24+
25+
26+
@pytest.fixture(scope='session')
27+
def cppcheck_binary(request):
28+
binary = request.config.getoption('--cppcheck-binary')
29+
resolved = shutil.which(binary)
30+
if resolved is None:
31+
pytest.fail("cppcheck binary '%s' not found - point --cppcheck-binary at a cppcheck executable" % binary)
32+
return os.path.abspath(resolved)
33+
34+
35+
class DumpFactory:
36+
"""Runs 'cppcheck --dump' on a source snippet and parses the result."""
37+
38+
def __init__(self, binary, tmp_path_factory):
39+
self.binary = binary
40+
self.tmp_path_factory = tmp_path_factory
41+
42+
def create(self, code, filename='test.c', extra_args=()):
43+
"""Write the code to a file, dump it and return the dump file path."""
44+
directory = self.tmp_path_factory.mktemp('cppcheckdata')
45+
path = directory / filename
46+
path.write_text(code)
47+
cmd = [self.binary, '--dump', '--quiet', str(path)] + list(extra_args)
48+
proc = subprocess.run(cmd,
49+
stdout=subprocess.PIPE,
50+
stderr=subprocess.PIPE,
51+
universal_newlines=True)
52+
assert proc.returncode == 0, \
53+
'cppcheck failed with exit code %d:\n%s\n%s' % (proc.returncode, proc.stdout, proc.stderr)
54+
return str(path) + '.dump'
55+
56+
def parse(self, code, filename='test.c', extra_args=()):
57+
"""Write the code to a file, dump it and return the parsed CppcheckData."""
58+
import cppcheckdata
59+
return cppcheckdata.parsedump(self.create(code, filename, extra_args))
60+
61+
62+
@pytest.fixture(scope='session')
63+
def dump_factory(cppcheck_binary, tmp_path_factory):
64+
return DumpFactory(cppcheck_binary, tmp_path_factory)
65+
66+
67+
SAMPLE_C = """#define ANSWER 42
68+
static int add(int a, int b)
69+
{
70+
return a + b;
71+
}
72+
73+
double half(double d)
74+
{
75+
return d / 2.0;
76+
}
77+
78+
int main(void)
79+
{
80+
int x = ANSWER;
81+
int arr[10];
82+
arr[0] = add(x, 1);
83+
int neg = -x;
84+
return arr[0] + neg;
85+
}
86+
"""
87+
88+
SAMPLE_CPP = """namespace ns {
89+
int twice(int v) { return 2 * v; }
90+
}
91+
92+
class Shape {
93+
public:
94+
virtual ~Shape() {}
95+
virtual double area() const = 0;
96+
protected:
97+
double mScale;
98+
};
99+
100+
enum Color { RED, GREEN };
101+
102+
const int limit = 5;
103+
bool flag = true;
104+
const char *msg = "hello";
105+
char ch = 'x';
106+
107+
int run()
108+
{
109+
Color color = RED;
110+
if (flag && limit > 1) {
111+
return ns::twice(21);
112+
}
113+
return static_cast<int>(color);
114+
}
115+
"""
116+
117+
MULTI_CFG_C = """typedef int myint;
118+
typedef float myfloat;
119+
#if defined(FOO) && FOO > 1
120+
int foo(void) { return 1; }
121+
#endif
122+
myint bar(void) { myint y = 3; return y; }
123+
"""
124+
125+
126+
@pytest.fixture(scope='session')
127+
def sample_data(dump_factory):
128+
"""Parsed dump of the canonical C sample."""
129+
return dump_factory.parse(SAMPLE_C, filename='sample.c')
130+
131+
132+
@pytest.fixture(scope='session')
133+
def sample_cfg(sample_data):
134+
cfgs = sample_data.configurations
135+
assert len(cfgs) == 1
136+
return cfgs[0]
137+
138+
139+
@pytest.fixture(scope='session')
140+
def sample_cpp_data(dump_factory):
141+
"""Parsed dump of the canonical C++ sample."""
142+
return dump_factory.parse(SAMPLE_CPP, filename='sample.cpp')
143+
144+
145+
@pytest.fixture(scope='session')
146+
def sample_cpp_cfg(sample_cpp_data):
147+
cfgs = sample_cpp_data.configurations
148+
assert len(cfgs) == 1
149+
return cfgs[0]
150+
151+
152+
@pytest.fixture(scope='session')
153+
def multi_cfg_data(dump_factory):
154+
"""Parsed dump of a file with two preprocessor configurations."""
155+
return dump_factory.parse(MULTI_CFG_C, filename='multi.c')

test/addon/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest

0 commit comments

Comments
 (0)