Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CI memory leak tests for liveliness #811

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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/build-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion examples/unix/c11/z_get_liveliness.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 11 additions & 1 deletion examples/unix/c11/z_sub_liveliness.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down
67 changes: 38 additions & 29 deletions tests/memory_leak.py
Original file line number Diff line number Diff line change
@@ -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
)
Expand All @@ -38,31 +38,31 @@ 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
)
# 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()
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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()
Expand All @@ -134,44 +134,53 @@ 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
return test_status


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)
Loading