Skip to content

Commit adc565d

Browse files
committed
Add multithreading tests
1 parent 2648332 commit adc565d

2 files changed

Lines changed: 236 additions & 3 deletions

File tree

usr/lib/python3/dist-packages/privleap/tests/run_test.py

Lines changed: 213 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import socket
3030
import time
3131
import signal
32+
from threading import Thread
33+
import re
3234
from pathlib import Path
3335
from typing import NoReturn, Tuple, IO, TypeAlias
3436
from collections.abc import Callable
@@ -2443,6 +2445,196 @@ def privleapd_config_reload_with_kick_test(bogus: str) -> bool:
24432445
return assert_success
24442446

24452447

2448+
def privleapd_multithreading_test_monitor() -> None:
2449+
"""
2450+
Monitors the stderr from privleapd for unexpected lines, printing errors
2451+
and setting a flag if an unexpected line is detected.
2452+
"""
2453+
2454+
assert PlTestGlobal.privleapd_proc is not None
2455+
assert PlTestGlobal.privleapd_proc.stderr is not None
2456+
linebuf: str = ""
2457+
while True:
2458+
if PlTestGlobal.multithreading_test_monitor_stop:
2459+
break
2460+
while "\n" in linebuf:
2461+
linebuf_parts: list[str] = linebuf.split("\n", maxsplit=1)
2462+
linebuf = linebuf_parts[1]
2463+
line: str = linebuf_parts[0]
2464+
line = re.sub(
2465+
"privleaptest(one|two|three|four|five|six|seven|eight|"
2466+
+ "nine|ten)",
2467+
"XXX_USERNAME_XXX",
2468+
line
2469+
)
2470+
if line not in PlTestData.multithreading_test_set:
2471+
logging.error(
2472+
"Unexpected stderr line from privleapd: '%s'", line
2473+
)
2474+
PlTestGlobal.multithreading_test_unexpected_stderr = True
2475+
else:
2476+
logging.info("Valid line '%s' received from privleapd", line)
2477+
2478+
try:
2479+
linebuf += PlTestGlobal.privleapd_proc.stderr.read()
2480+
except Exception:
2481+
pass
2482+
time.sleep(0.1)
2483+
2484+
2485+
# pylint: disable=consider-using-with
2486+
# Rationale:
2487+
# consider-using-with: Not suitable for the parallel process running
2488+
# mechanism being used here.
2489+
def privleapd_multithreading_test_worker(
2490+
mode: str, user_name: str
2491+
) -> subprocess.Popen[bytes]:
2492+
"""
2493+
Calls leapctl or leaprun to do an action asynchronously.
2494+
"""
2495+
2496+
return_proc: subprocess.Popen[bytes]
2497+
match mode:
2498+
case "create":
2499+
return_proc = subprocess.Popen(
2500+
["leapctl", "--create", user_name],
2501+
stdin=subprocess.PIPE,
2502+
stdout=subprocess.PIPE,
2503+
)
2504+
case "run":
2505+
return_proc = subprocess.Popen(
2506+
["sudo", "-u", user_name, "leaprun", "test-act-anyone"],
2507+
stdin=subprocess.PIPE,
2508+
stdout=subprocess.PIPE,
2509+
)
2510+
case "destroy":
2511+
return_proc = subprocess.Popen(
2512+
["leapctl", "--destroy", user_name],
2513+
stdin=subprocess.PIPE,
2514+
stdout=subprocess.PIPE,
2515+
)
2516+
return return_proc
2517+
2518+
2519+
# pylint: disable=too-many-branches
2520+
# Rationale:
2521+
# too-many-branches: Using less branches isn't practical, splitting this up
2522+
# would only make it harder to read.
2523+
def privleapd_multithreading_test(bogus: str) -> bool:
2524+
"""
2525+
Exercises privleapd's multithreading code.
2526+
"""
2527+
2528+
if bogus != "":
2529+
return False
2530+
discard_privleapd_stderr()
2531+
2532+
proc_list: list[subprocess.Popen[bytes]] = []
2533+
first_user_name_block = [
2534+
"privleaptestone",
2535+
"privleaptesttwo",
2536+
"privleaptestthree",
2537+
"privleaptestfour",
2538+
"privleaptestfive",
2539+
]
2540+
second_user_name_block = [
2541+
"privleaptestsix",
2542+
"privleaptestseven",
2543+
"privleaptesteight",
2544+
"privleaptestnine",
2545+
"privleaptestten",
2546+
]
2547+
monitor_thread: Thread = Thread(
2548+
target=privleapd_multithreading_test_monitor,
2549+
)
2550+
monitor_thread.start()
2551+
2552+
for user_name in first_user_name_block:
2553+
proc_list.append(
2554+
privleapd_multithreading_test_worker("create", user_name)
2555+
)
2556+
for proc in proc_list:
2557+
proc.communicate()
2558+
proc_list.clear()
2559+
2560+
for loop_idx in range(0, 20):
2561+
for first_block_user_name, second_block_user_name in zip(
2562+
first_user_name_block, second_user_name_block
2563+
):
2564+
proc_list.append(
2565+
privleapd_multithreading_test_worker(
2566+
"run", first_block_user_name
2567+
)
2568+
)
2569+
proc_list.append(
2570+
privleapd_multithreading_test_worker(
2571+
"create", second_block_user_name
2572+
)
2573+
)
2574+
for proc in proc_list:
2575+
proc.communicate()
2576+
proc_list.clear()
2577+
logging.info(
2578+
f"Multithreading test, part 1 of iteration {loop_idx + 1} done"
2579+
)
2580+
2581+
for first_block_user_name, second_block_user_name in zip(
2582+
first_user_name_block, second_user_name_block
2583+
):
2584+
proc_list.append(
2585+
privleapd_multithreading_test_worker(
2586+
"destroy", first_block_user_name
2587+
)
2588+
)
2589+
proc_list.append(
2590+
privleapd_multithreading_test_worker(
2591+
"run", second_block_user_name
2592+
)
2593+
)
2594+
for proc in proc_list:
2595+
proc.communicate()
2596+
proc_list.clear()
2597+
logging.info(
2598+
f"Multithreading test, part 2 of iteration {loop_idx + 1} done"
2599+
)
2600+
2601+
for first_block_user_name, second_block_user_name in zip(
2602+
first_user_name_block, second_user_name_block
2603+
):
2604+
proc_list.append(
2605+
privleapd_multithreading_test_worker(
2606+
"create", first_block_user_name
2607+
)
2608+
)
2609+
proc_list.append(
2610+
privleapd_multithreading_test_worker(
2611+
"destroy", second_block_user_name
2612+
)
2613+
)
2614+
for proc in proc_list:
2615+
proc.communicate()
2616+
proc_list.clear()
2617+
logging.info(
2618+
f"Multithreading test, part 3 of iteration {loop_idx + 1} done"
2619+
)
2620+
2621+
for user_name in first_user_name_block:
2622+
proc_list.append(
2623+
privleapd_multithreading_test_worker("destroy", user_name)
2624+
)
2625+
for proc in proc_list:
2626+
proc.communicate()
2627+
proc_list.clear()
2628+
2629+
time.sleep(0.5)
2630+
PlTestGlobal.multithreading_test_monitor_stop = True
2631+
time.sleep(0.5)
2632+
2633+
if PlTestGlobal.multithreading_test_unexpected_stderr:
2634+
return False
2635+
return True
2636+
2637+
24462638
def privleapd_assert_command(
24472639
command_data: list[str],
24482640
exit_code: int,
@@ -3318,6 +3510,12 @@ def run_privleapd_tests() -> None:
33183510
"/test-act-interrupt",
33193511
"Remove flag file left by test-act-interrupt",
33203512
)
3513+
# ---
3514+
privleapd_assert_function(
3515+
privleapd_multithreading_test,
3516+
"",
3517+
"Test privleapd multithreading code",
3518+
)
33213519
privleapd_assert_command(
33223520
["leapctl", "--create", "privleaptestone"],
33233521
exit_code=0,
@@ -3395,9 +3593,21 @@ def main() -> NoReturn:
33953593
print_test_header()
33963594
ensure_running_as_root()
33973595
stop_privleapd_service()
3398-
setup_test_account("privleaptestone", Path("/home/privleaptestone"))
3399-
setup_test_account("privleaptesttwo", Path("/home/privleaptesttwo"))
3400-
setup_test_account("privleaptestthree", Path("/home/privleaptestthree"))
3596+
for setup_account_name in [
3597+
"privleaptestone",
3598+
"privleaptesttwo",
3599+
"privleaptestthree",
3600+
"privleaptestfour",
3601+
"privleaptestfive",
3602+
"privleaptestsix",
3603+
"privleaptestseven",
3604+
"privleaptesteight",
3605+
"privleaptestnine",
3606+
"privleaptestten",
3607+
]:
3608+
setup_test_account(
3609+
setup_account_name, Path(f"/home/{setup_account_name}")
3610+
)
34013611
displace_old_privleap_config()
34023612
write_privleap_test_config()
34033613

usr/lib/python3/dist-packages/privleap/tests/run_test_util.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class PlTestGlobal:
5151
privleapd_running: bool = False
5252
no_service_handling = False
5353
all_asserts_passed = True
54+
multithreading_test_unexpected_stderr = False
55+
multithreading_test_monitor_stop = False
5456

5557

5658
SelectInfo = Tuple[list[IO[bytes]], list[IO[bytes]], list[IO[bytes]]]
@@ -674,6 +676,10 @@ class PlTestData:
674676
Command=fn() { touch /test-act-interrupt; }; trap fn TERM; sleep infinity & wait $!
675677
AuthorizedUsers=privleaptestone
676678
679+
[action:test-act-anyone]
680+
Command=echo 'test-act-anyone'
681+
AuthorizedUsers=privleaptestone,privleaptesttwo,privleaptestthree,privleaptestfour,privleaptestfive,privleaptestsix,privleaptestseven,privleaptesteight,privleaptestnine,privleaptestten
682+
677683
[expected-disallowed-users]
678684
User=irc
679685
User=news
@@ -1415,3 +1421,20 @@ class PlTestData:
14151421
"parse_config_files: INFO: Config directory "
14161422
+ "'/usr/local/etc/privleap/conf.d' does not exist, skipping.\n"
14171423
]
1424+
multithreading_test_set: set[str] = set(
1425+
[
1426+
"handle_control_create_msg: INFO: Handled CREATE message for "
1427+
+ "account 'XXX_USERNAME_XXX', socket created",
1428+
"destroy_comm_socket: INFO: Successfully destroyed comm socket "
1429+
+ "for account 'XXX_USERNAME_XXX'",
1430+
"handle_control_destroy_msg: INFO: Handled DESTROY message for "
1431+
+ "account 'XXX_USERNAME_XXX', socket destroyed",
1432+
"auth_signal_request: INFO: Action run request: Account "
1433+
+ "'XXX_USERNAME_XXX' is authorized to run action "
1434+
+ "'test-act-anyone'",
1435+
"handle_signal_message: INFO: Triggered action 'test-act-anyone' "
1436+
+ "for account 'XXX_USERNAME_XXX'",
1437+
"send_action_results: INFO: Action 'test-act-anyone' requested "
1438+
+ "by account 'XXX_USERNAME_XXX' completed"
1439+
]
1440+
)

0 commit comments

Comments
 (0)