From 5586e0b1ff4d4a7fe576d174e7f163263ab98c8b Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Fri, 22 Nov 2024 11:14:15 +0100 Subject: [PATCH] Add CI memory leak tests for liveliness --- .github/workflows/build-check.yaml | 2 +- examples/unix/c11/z_get_liveliness.c | 3 +- examples/unix/c11/z_sub_liveliness.c | 12 ++++- tests/memory_leak.py | 67 ++++++++++++++++------------ 4 files changed, 52 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build-check.yaml b/.github/workflows/build-check.yaml index be3e52d7e..70a4be197 100644 --- a/.github/workflows/build-check.yaml +++ b/.github/workflows/build-check.yaml @@ -272,7 +272,7 @@ jobs: - name: Build project and run test run: | sudo apt install -y ninja-build - CMAKE_GENERATOR=Ninja make + Z_FEATURE_UNSTABLE_API=1 Z_FEATURE_LIVELINESS=1 CMAKE_GENERATOR=Ninja make python3 ./build/tests/memory_leak.py timeout-minutes: 5 diff --git a/examples/unix/c11/z_get_liveliness.c b/examples/unix/c11/z_get_liveliness.c index cefa20175..99bfc06d4 100644 --- a/examples/unix/c11/z_get_liveliness.c +++ b/examples/unix/c11/z_get_liveliness.c @@ -99,9 +99,10 @@ int main(int argc, char **argv) { } else { printf("Received an error\n"); } + z_drop(z_move(reply)); } - z_drop(z_move(reply)); + z_drop(z_move(closure)); z_drop(z_move(handler)); z_drop(z_move(s)); return 0; diff --git a/examples/unix/c11/z_sub_liveliness.c b/examples/unix/c11/z_sub_liveliness.c index a302d10a3..36fb02fda 100644 --- a/examples/unix/c11/z_sub_liveliness.c +++ b/examples/unix/c11/z_sub_liveliness.c @@ -21,6 +21,8 @@ #if Z_FEATURE_SUBSCRIPTION == 1 && Z_FEATURE_LIVELINESS == 1 +static volatile int msg_nb = 0; + void data_handler(z_loaned_sample_t *sample, void *ctx) { (void)(ctx); z_view_string_t key_string; @@ -35,6 +37,7 @@ void data_handler(z_loaned_sample_t *sample, void *ctx) { z_string_data(z_loan(key_string))); break; } + msg_nb++; } int main(int argc, char **argv) { @@ -43,6 +46,7 @@ int main(int argc, char **argv) { char *clocator = NULL; char *llocator = NULL; bool history = false; + int n = 0; int opt; while ((opt = getopt(argc, argv, "k:e:m:l:n:h")) != -1) { @@ -62,8 +66,11 @@ int main(int argc, char **argv) { case 'h': history = true; break; + case 'n': + n = atoi(optarg); + break; case '?': - if (optopt == 'k' || optopt == 'e' || optopt == 'm' || optopt == 'l') { + if (optopt == 'k' || optopt == 'e' || optopt == 'm' || optopt == 'l' || optopt == 'n') { fprintf(stderr, "Option -%c requires an argument.\n", optopt); } else { fprintf(stderr, "Unknown option `-%c'.\n", optopt); @@ -116,6 +123,9 @@ int main(int argc, char **argv) { printf("Press CTRL-C to quit...\n"); while (1) { + if (n != 0 && msg_nb >= n) { + break; + } z_sleep_s(1); } diff --git a/tests/memory_leak.py b/tests/memory_leak.py index 34c49c9e5..2191d2295 100644 --- a/tests/memory_leak.py +++ b/tests/memory_leak.py @@ -1,22 +1,22 @@ -import argparse import os -from signal import SIGINT +import signal import subprocess import sys import time -import re # Specify the directory for the binaries DIR_EXAMPLES = "build/examples" NO_LEAK_OUTPUT = "All heap blocks were freed -- no leaks are possible" +VALGRIND_CMD = f"stdbuf -oL -eL valgrind --leak-check=full ./{DIR_EXAMPLES}/" + def failure_mode(fail_cmd): test_status = 0 # Start binary print("Start binary") - z_pub_command = f"stdbuf -oL -eL valgrind ./{DIR_EXAMPLES}/" + fail_cmd + z_pub_command = VALGRIND_CMD + fail_cmd z_pub_process = subprocess.Popen( z_pub_command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) @@ -38,9 +38,9 @@ def failure_mode(fail_cmd): def pub_and_sub(pub_cmd, sub_cmd): test_status = 0 - print("Start subscriber") + print(f"Start {sub_cmd}") # Start z_sub in the background - z_sub_command = f"stdbuf -oL -eL valgrind ./{DIR_EXAMPLES}/" + sub_cmd + z_sub_command = VALGRIND_CMD + sub_cmd z_sub_process = subprocess.Popen( z_sub_command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True @@ -48,21 +48,21 @@ def pub_and_sub(pub_cmd, sub_cmd): # Introduce a delay to ensure z_sub starts time.sleep(2) - print("Start publisher") + print(f"Start {pub_cmd}") # Start z_pub - z_pub_command = f"stdbuf -oL -eL valgrind ./{DIR_EXAMPLES}/" + pub_cmd + z_pub_command = VALGRIND_CMD + pub_cmd z_pub_process = subprocess.Popen( z_pub_command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) # Wait for z_pub to finish z_pub_process.wait() - print("Stop subscriber") + print(f"Stop {sub_cmd}") time.sleep(2) if z_sub_process.poll() is None: # send SIGINT to group z_sub_process_gid = os.getpgid(z_sub_process.pid) - os.killpg(z_sub_process_gid, SIGINT) + os.killpg(z_sub_process_gid, signal.SIGINT) # Wait for z_sub to finish z_sub_process.wait() @@ -71,18 +71,18 @@ def pub_and_sub(pub_cmd, sub_cmd): # Check output of z_pub z_pub_output = z_pub_process.stderr.read() if NO_LEAK_OUTPUT in z_pub_output: - print("z_pub output valid") + print(f"{pub_cmd} output valid") else: - print("z_pub output invalid:") + print(f"{pub_cmd} output invalid:") print(f"Received: \"{z_pub_output}\"") test_status = 1 # Check output of z_sub z_sub_output = z_sub_process.stderr.read() if NO_LEAK_OUTPUT in z_sub_output: - print("z_sub output valid") + print(f"{sub_cmd} output valid") else: - print("z_sub output invalid:") + print(f"{sub_cmd} output invalid:") print(f"Received: \"{z_sub_output}\"") test_status = 1 # Return value @@ -91,9 +91,9 @@ def pub_and_sub(pub_cmd, sub_cmd): def query_and_queryable(query_cmd, queryable_cmd): test_status = 0 - print("Start queryable") + print(f"Start {queryable_cmd}") # Start z_queryable in the background - z_queryable_command = f"stdbuf -oL -eL valgrind ./{DIR_EXAMPLES}/" + queryable_cmd + z_queryable_command = VALGRIND_CMD + queryable_cmd z_queryable_process = subprocess.Popen( z_queryable_command, shell=True, @@ -106,9 +106,9 @@ def query_and_queryable(query_cmd, queryable_cmd): # Introduce a delay to ensure z_queryable starts time.sleep(2) - print("Start query") + print(f"Start {query_cmd}") # Start z_query - z_query_command = f"stdbuf -oL -eL valgrind ./{DIR_EXAMPLES}/" + query_cmd + z_query_command = VALGRIND_CMD + query_cmd z_query_process = subprocess.Popen( z_query_command, shell=True, @@ -120,12 +120,12 @@ def query_and_queryable(query_cmd, queryable_cmd): # Wait for z_query to finish z_query_process.wait() - print("Stop queryable") + print(f"Stop {queryable_cmd}") time.sleep(2) if z_queryable_process.poll() is None: # send SIGINT to group z_quaryable_process_gid = os.getpgid(z_queryable_process.pid) - os.killpg(z_quaryable_process_gid, SIGINT) + os.killpg(z_quaryable_process_gid, signal.SIGINT) # Wait for z_queryable to finish z_queryable_process.wait() @@ -134,18 +134,18 @@ def query_and_queryable(query_cmd, queryable_cmd): # Check output of z_query z_query_output = z_query_process.stderr.read() if NO_LEAK_OUTPUT in z_query_output: - print("z_query output valid") + print(f"{query_cmd} output valid") else: - print("z_query output invalid:") + print(f"{query_cmd} output invalid:") print(f'Received: "{z_query_output}"') test_status = 1 # Check output of z_queryable z_queryable_output = z_queryable_process.stderr.read() if NO_LEAK_OUTPUT in z_queryable_output: - print("z_queryable output valid") + print(f"{queryable_cmd} output valid") else: - print("z_queryable output invalid:") + print(f"{queryable_cmd} output invalid:") print(f'Received: "{z_queryable_output}"') test_status = 1 # Return status @@ -153,25 +153,34 @@ def query_and_queryable(query_cmd, queryable_cmd): if __name__ == "__main__": + signal.signal(signal.SIGINT, signal.SIG_IGN) EXIT_STATUS = 0 # Test failure mode print("*** Failure mode ***") - if failure_mode(f'z_pub -m peer') == 1: + if failure_mode('z_pub -m peer') == 1: EXIT_STATUS = 1 # Test pub and sub examples print("*** Pub & sub test ***") - if pub_and_sub(f'z_pub -n 1', f'z_sub -n 1') == 1: + if pub_and_sub('z_pub -n 1', 'z_sub -n 1') == 1: EXIT_STATUS = 1 print("*** Pub & sub attachment test ***") - if pub_and_sub(f'z_pub_attachment -n 1', f'z_sub_attachment -n 1') == 1: + if pub_and_sub('z_pub_attachment -n 1', 'z_sub_attachment -n 1') == 1: EXIT_STATUS = 1 # Test query and queryable examples print("*** Query & queryable test ***") - if query_and_queryable(f'z_get', f'z_queryable -n 1') == 1: + if query_and_queryable('z_get', 'z_queryable -n 1') == 1: EXIT_STATUS = 1 print("*** Query & queryable attachment test ***") - if query_and_queryable(f'z_get_attachment -v Something', f'z_queryable_attachment -n 1') == 1: + if query_and_queryable('z_get_attachment -v Something', 'z_queryable_attachment -n 1') == 1: + EXIT_STATUS = 1 + # Test liveliness query + print("*** Get liveliness test ***") + if query_and_queryable('z_get_liveliness', 'z_liveliness') == 1: + EXIT_STATUS = 1 + # Test liveliness subscriber + print("*** Liveliness subscriber test ***") + if query_and_queryable('z_sub_liveliness -h -n 1', 'z_liveliness') == 1: EXIT_STATUS = 1 # Exit sys.exit(EXIT_STATUS)