Skip to content

Commit

Permalink
CMake: added support for memory checks using leaks on macOS.
Browse files Browse the repository at this point in the history
  • Loading branch information
agarny authored Mar 9, 2024
2 parents 3e46957 + 954d09c commit cdf5333
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 66 deletions.
7 changes: 1 addition & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ jobs:
context: PATH=/usr/local/opt/llvm/bin:$PATH CC=/usr/local/opt/llvm/bin/clang CXX=/usr/local/opt/llvm/bin/clang++
target: code_coverage
- name: 'Memory checks'
os: ubuntu-latest
os: macos-latest
build_type: Debug
code_analysis: OFF
code_coverage: OFF
Expand Down Expand Up @@ -313,11 +313,6 @@ jobs:
- name: Install Black
if: ${{ matrix.target == 'python_check_code_formatting' }}
run: pip3 install black
- name: Install Valgrind
if: ${{ matrix.memory_checks == 'ON' }}
run: |
sudo apt update
sudo apt install valgrind
- name: Install pytest and pytest-html
if: ${{ matrix.target == 'python_unit_testing' }}
run: pip3 install pytest pytest-html
Expand Down
17 changes: 14 additions & 3 deletions cmake/environmentchecks.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,12 @@ find_program(PATCH_EXE NAMES ${PREFERRED_PATCH_NAMES} patch)
find_program(PRETTIER_EXE NAMES ${PREFERRED_PRETTIER_NAMES} prettier)
find_program(PYTEST_EXE NAMES ${PREFERRED_PYTEST_NAMES} pytest)
find_program(SPHINX_EXE NAMES ${PREFERRED_SPHINX_NAMES} sphinx-build sphinx-build2)
find_program(VALGRIND_EXE NAMES ${PREFERRED_VALGRIND_NAMES} valgrind)

if(APPLE)
find_program(LEAKS_EXE NAMES ${PREFERRED_LEAKS_NAMES} leaks)
elseif(NOT WIN32)
find_program(VALGRIND_EXE NAMES ${PREFERRED_VALGRIND_NAMES} valgrind)
endif()

# Create some aliases.

Expand Down Expand Up @@ -309,10 +314,15 @@ if(PYTHON_UNIT_TESTING_AVAILABLE AND PYTHON_EXE)
endif()
endif()

if(PYTHON_EXE AND VALGRIND_EXE)
if( (APPLE AND LEAKS_EXE)
OR (NOT WIN32 AND PYTHON_EXE AND VALGRIND_EXE))
set(MEMORY_CHECKS_AVAILABLE TRUE)
else()
set(MEMORY_CHECKS_ERROR_MESSAGE "Memory checks are requested but Python and/or Valgrind could not be found.")
if(APPLE)
set(MEMORY_CHECKS_ERROR_MESSAGE "Memory checks are requested but the leaks tool could not be found.")
else()
set(MEMORY_CHECKS_ERROR_MESSAGE "Memory checks are requested but Python and/or Valgrind could not be found.")
endif()
endif()

# Hide the CMake options that are not directly relevant to libOpenCOR.
Expand All @@ -339,6 +349,7 @@ mark_as_advanced(BLACK_EXE
EMCONFIGURE_EXE
FIND_EXE
GIT_EXE
LEAKS_EXE
LLVM_COV_EXE
LLVM_PROFDATA_EXE
NODE_EXE
Expand Down
16 changes: 8 additions & 8 deletions src/version.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,21 @@ limitations under the License.

namespace libOpenCOR {

inline constexpr unsigned int MAJOR_10 = 1048576;
inline constexpr unsigned int MAJOR_01 = 65536;
inline constexpr unsigned int MINOR_10 = 4096;
inline constexpr unsigned int MINOR_01 = 256;
inline constexpr unsigned int PATCH_10 = 16;
inline constexpr unsigned int PATCH_01 = 1;
static constexpr unsigned int MAJOR_10 = 1048576;
static constexpr unsigned int MAJOR_01 = 65536;
static constexpr unsigned int MINOR_10 = 4096;
static constexpr unsigned int MINOR_01 = 256;
static constexpr unsigned int PATCH_10 = 16;
static constexpr unsigned int PATCH_01 = 1;

inline unsigned int firstDigit(unsigned int pTwoDigitNumber)
unsigned int firstDigit(unsigned int pTwoDigitNumber)
{
static constexpr double ONE_TENTH = 0.1;

return static_cast<unsigned int>(floor(ONE_TENTH * pTwoDigitNumber));
}

inline unsigned int secondDigit(unsigned int pTwoDigitNumber)
unsigned int secondDigit(unsigned int pTwoDigitNumber)
{
static constexpr unsigned int TEN = 10;

Expand Down
4 changes: 2 additions & 2 deletions src/version.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ limitations under the License.
namespace libOpenCOR {

// clang-format off
inline constexpr unsigned int LIBOPENCOR_VERSION = @LIBOPENCOR_VERSION@;
static constexpr unsigned int LIBOPENCOR_VERSION = @LIBOPENCOR_VERSION@;
// clang-format on
inline constexpr auto LIBOPENCOR_VERSION_STRING = "@LIBOPENCOR_VERSION_STRING@";
static constexpr auto LIBOPENCOR_VERSION_STRING = "@LIBOPENCOR_VERSION_STRING@";

unsigned int firstDigit(unsigned int pTwoDigitNumber);
unsigned int secondDigit(unsigned int pTwoDigitNumber);
Expand Down
15 changes: 11 additions & 4 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,15 @@ endif()
# Run our memory checks.

if(LIBOPENCOR_MEMORY_CHECKS)
add_target(memory_checks
COMMAND ${PYTHON_EXE} ${CMAKE_CURRENT_SOURCE_DIR}/memory_checks.py ${CMAKE_CURRENT_BINARY_DIR} ${TEST_TARGETS}
DEPENDS ${TEST_TARGETS}
COMMENT "Running memory checks...")
if(APPLE)
add_target(memory_checks
COMMAND ${PYTHON_EXE} ${CMAKE_CURRENT_SOURCE_DIR}/memory_checks.py ${CMAKE_CURRENT_BINARY_DIR} ${TEST_TARGETS}
DEPENDS ${TEST_TARGETS}
COMMENT "Running memory checks...")
elseif(NOT WIN32)
add_target(memory_checks
COMMAND ${PYTHON_EXE} ${CMAKE_CURRENT_SOURCE_DIR}/memory_checks.py ${CMAKE_CURRENT_BINARY_DIR} ${TEST_TARGETS}
DEPENDS ${TEST_TARGETS}
COMMENT "Running memory checks...")
endif()
endif()
73 changes: 53 additions & 20 deletions tests/memory_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import math
import multiprocessing
import os
import platform
import shutil
import sys
import xml.dom.minidom
Expand Down Expand Up @@ -87,14 +88,34 @@ def __str__(self):

def parse_errors(output):
res = []
dom = xml.dom.minidom.parseString(output)
error = dom.getElementsByTagName("error")

for stack in error:
back_trace = BackTrace(stack)
if platform.system() == "Linux":
dom = xml.dom.minidom.parseString(output)
error = dom.getElementsByTagName("error")

if back_trace.is_leak():
res.append(back_trace)
for stack in error:
back_trace = BackTrace(stack)

if back_trace.is_leak():
res.append(back_trace)
else:
leaks_report_line = 0
lines = ""

for line in output.split("\n"):
if leaks_report_line == 0:
if line.startswith("leaks Report Version"):
leaks_report_line = 1
else:
lines += line + "\n"

leaks_report_line += 1

if leaks_report_line == 3:
if "0 leaks for 0 total leaked bytes." in line:
return []

res.append(lines)

return res

Expand All @@ -105,24 +126,28 @@ def garbage(line):
)


def memcheck(valgrind, test, test_path):
os.system(
valgrind
+ f" --tool=memcheck --child-silent-after-fork=yes --leak-check=full --xml=yes --xml-fd=3 --num-callers=50 {test_path} 1>{test}.txt 2>{test}.err 3>{test}.xml"
)
def check_memory(tool, test, test_path):
if platform.system() == "Linux":
os.system(
f"{tool} --tool=memcheck --child-silent-after-fork=yes --leak-check=full --xml=yes --xml-fd=3 --num-callers=50 {test_path} 1>{test}.txt 2>{test}.err 3>{test}.xml"
)

return "".join(list(filter(garbage, open(f"{test}.xml").readlines())))
else:
os.system(f"{tool} --quiet -atExit -- {test_path} 1>{test}.txt 2>{test}.err")

return "".join(list(filter(garbage, open(f"{test}.xml").readlines())))
return open(f"{test}.txt").read()


def run_test(valgrind, test, test_path):
def run_test(tool, test, test_path):
sys.stdout.write(f"-- Checking memory in {test} - ")

if not os.access(test_path, os.X_OK):
sys.stdout.write("not found\n")
sys.stdout.write("Not found\n")

return False

errors = parse_errors(memcheck(valgrind, test, test_path))
errors = parse_errors(check_memory(tool, test, test_path))

if len(errors) == 0:
sys.stdout.write("Success\n")
Expand All @@ -141,12 +166,20 @@ def run_test(valgrind, test, test_path):

if __name__ == "__main__":
if len(sys.argv) > 2:
valgrind = shutil.which("valgrind")
if platform.system() == "Linux":
tool = shutil.which("valgrind")

if tool == None:
sys.stderr.write("-- Valgrind could not be found.\n")

sys.exit(3)
else:
tool = shutil.which("leaks")

if valgrind == None:
sys.stderr.write("-- Valgrind could not be found.\n")
if tool == None:
sys.stderr.write("-- Leaks could not be found.\n")

sys.exit(3)
sys.exit(3)

exit_code = 0
tests_dir = sys.argv[1]
Expand All @@ -155,7 +188,7 @@ def run_test(valgrind, test, test_path):
with multiprocessing.Pool(multiprocessing.cpu_count()) as process:
results = process.starmap(
run_test,
[(valgrind, test, os.path.join(tests_dir, test)) for test in tests],
[(tool, test, os.path.join(tests_dir, test)) for test in tests],
)

successes = []
Expand Down
46 changes: 23 additions & 23 deletions tests/utils.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -33,35 +33,35 @@ limitations under the License.

namespace libOpenCOR {

inline constexpr auto RESOURCE_LOCATION = "@RESOURCE_LOCATION@";

inline constexpr auto UNKNOWN_FILE = "unknown_file.txt";
inline constexpr auto SBML_FILE = "sbml.sbml";
inline constexpr auto ERROR_SEDML_FILE = "error.sedml";
inline constexpr auto CELLML_1_X_FILE = "cellml_1_x.cellml";
inline constexpr auto SEDML_1_X_FILE = "cellml_1_x.sedml";
inline constexpr auto COMBINE_1_X_ARCHIVE = "cellml_1_x.omex";
inline constexpr auto CELLML_2_FILE = "cellml_2.cellml";
inline constexpr auto SEDML_2_FILE = "cellml_2.sedml";
inline constexpr auto COMBINE_2_ARCHIVE = "cellml_2.omex";
inline constexpr auto IRRETRIEVABLE_FILE = "irretrievable_file.txt";
static constexpr auto RESOURCE_LOCATION = "@RESOURCE_LOCATION@";

static constexpr auto UNKNOWN_FILE = "unknown_file.txt";
static constexpr auto SBML_FILE = "sbml.sbml";
static constexpr auto ERROR_SEDML_FILE = "error.sedml";
static constexpr auto CELLML_1_X_FILE = "cellml_1_x.cellml";
static constexpr auto SEDML_1_X_FILE = "cellml_1_x.sedml";
static constexpr auto COMBINE_1_X_ARCHIVE = "cellml_1_x.omex";
static constexpr auto CELLML_2_FILE = "cellml_2.cellml";
static constexpr auto SEDML_2_FILE = "cellml_2.sedml";
static constexpr auto COMBINE_2_ARCHIVE = "cellml_2.omex";
static constexpr auto IRRETRIEVABLE_FILE = "irretrievable_file.txt";

#ifdef _WIN32
inline constexpr auto LOCAL_FILE = R"(P:\some\path\file.txt)";
static constexpr auto LOCAL_FILE = R"(P:\some\path\file.txt)";
#else
inline constexpr auto LOCAL_FILE = "/some/path/file.txt";
static constexpr auto LOCAL_FILE = "/some/path/file.txt";
#endif

inline constexpr auto HTTP_REMOTE_FILE = "http://raw.githubusercontent.com/opencor/libopencor/master/tests/res/cellml_2.cellml";
inline constexpr auto REMOTE_BASE_PATH = "https://raw.githubusercontent.com/opencor/libopencor/master/tests/res";
inline constexpr auto REMOTE_FILE = "https://raw.githubusercontent.com/opencor/libopencor/master/tests/res/cellml_2.cellml";
inline constexpr auto IRRETRIEVABLE_REMOTE_FILE = "https://some.domain.com/irretrievable_file.txt";
static constexpr auto HTTP_REMOTE_FILE = "http://raw.githubusercontent.com/opencor/libopencor/master/tests/res/cellml_2.cellml";
static constexpr auto REMOTE_BASE_PATH = "https://raw.githubusercontent.com/opencor/libopencor/master/tests/res";
static constexpr auto REMOTE_FILE = "https://raw.githubusercontent.com/opencor/libopencor/master/tests/res/cellml_2.cellml";
static constexpr auto IRRETRIEVABLE_REMOTE_FILE = "https://some.domain.com/irretrievable_file.txt";

inline constexpr auto SOME_UNKNOWN_CONTENTS = "Some unknown contents...";
inline constexpr auto SOME_CELLML_CONTENTS = "@SOME_CELLML_CONTENTS_C@";
inline constexpr auto SOME_ERROR_CELLML_CONTENTS = "@SOME_ERROR_CELLML_CONTENTS_C@";
inline constexpr auto SOME_WARNING_CELLML_CONTENTS = "@SOME_WARNING_CELLML_CONTENTS_C@";
inline constexpr auto SOME_SEDML_CONTENTS = "@SOME_SEDML_CONTENTS_C@";
static constexpr auto SOME_UNKNOWN_CONTENTS = "Some unknown contents...";
static constexpr auto SOME_CELLML_CONTENTS = "@SOME_CELLML_CONTENTS_C@";
static constexpr auto SOME_ERROR_CELLML_CONTENTS = "@SOME_ERROR_CELLML_CONTENTS_C@";
static constexpr auto SOME_WARNING_CELLML_CONTENTS = "@SOME_WARNING_CELLML_CONTENTS_C@";
static constexpr auto SOME_SEDML_CONTENTS = "@SOME_SEDML_CONTENTS_C@";

typedef struct
{
Expand Down

0 comments on commit cdf5333

Please sign in to comment.