#!/usr/bin/env python3 """Actual canonical stop/reboot handlers with deterministic side-effect doubles.""" from pathlib import Path import subprocess import tempfile ROOT = Path(__file__).resolve().parents[2] def function(path, name): source = path.read_text() start = source.index('static int ' + name + '(') return source[start:source.index('\n}', start) + 2] header = '\n'.join(line for line in (ROOT / 'src/admin_ssh_console.h').read_text().splitlines() if not line.startswith(('#include', '#pragma once'))) prelude = r''' #include #include #include #include #include #include typedef int esp_err_t; enum { ESP_OK=0, ESP_FAIL=-1, ESP_ERR_TIMEOUT=7 }; typedef struct { int unused; } user_principal_t; ''' fakes = r''' static bool remote, web; static unsigned stops, reboots, scheduled, waits, rotations, usages; static esp_err_t schedule_result, stop_result; static admin_ssh_deferred_action_type_t last_action; bool admin_ssh_console_dispatch_is_remote(void) { return remote; } bool admin_ssh_console_dispatch_is_web(void) { return remote && web; } esp_err_t admin_ssh_console_dispatch_defer(admin_ssh_deferred_action_type_t action, uint32_t argument) { assert(remote && !argument); ++scheduled; last_action=action; return schedule_result; } static const char *esp_err_to_name(esp_err_t error) { (void)error; return "fake"; } static esp_err_t web_server_stop(void) { ++stops; return stop_result; } static esp_err_t web_server_start(void) { assert(false); return ESP_FAIL; } static esp_err_t web_server_clear_counters(void) { assert(false); return ESP_FAIL; } static esp_err_t web_serial_transport_clear_counters(void) { assert(false); return ESP_FAIL; } static void esp_restart(void) { ++reboots; } static void vTaskDelay(unsigned delay) { assert(delay==100); ++waits; } #define pdMS_TO_TICKS(ms) (ms) static void print_usage(void) { ++usages; } static int web_diagnostics_command(const char *action) { assert(!strcmp(action, "show")); return 0; } static int performance_command(const char *action) { return strcmp(action, "show") ? 1 : 0; } static int show_status(void) { assert(false); return 1; } static int show_counters(void) { assert(false); return 1; } static int show_certificate(void) { assert(false); return 1; } static int rotate_certificate(void) { ++rotations; return 0; } static int reset_material(void) { assert(false); return 1; } static bool force_is_present(int argc, char **argv, int expected) { return argc == expected && !strcmp(argv[expected - 1], "--force"); } ''' tests = r''' int main(void) { char *removed[]={"web", "credentials", "show", "--force"}; for (unsigned origin=0; origin<3; ++origin) { remote=origin!=0; web=origin==2; removed[2]="show"; assert(command_web(2,removed)==1); assert(command_web(3,removed)==1); removed[2]="rotate"; assert(command_web(3,removed)==1); assert(command_web(4,removed)==1); } assert(usages==12 && !stops && !scheduled && !rotations); remote=web=false; char *diagnostics[]={"web", "diagnostics", "show"}; assert(command_web(3, diagnostics)==0 && !stops && !scheduled); char *performance[]={"web", "performance", "show", "extra"}; assert(command_web(3, performance)==0 && !stops && !scheduled); assert(command_web(4, performance)==1 && !stops && !scheduled); performance[2]="invalid"; assert(command_web(3, performance)==1 && !stops && !scheduled); char *stop[]={"web", "stop"}; remote=web=true; assert(command_web(2,stop)==0 && scheduled==1 && !stops && last_action==ADMIN_CONSOLE_DEFER_WEB_STOP); schedule_result=ESP_ERR_TIMEOUT; assert(command_web(2,stop)==1 && scheduled==2 && !stops); schedule_result=ESP_OK; web=false; /* SSH preserves its synchronous HTTPS path. */ assert(command_web(2,stop)==0 && stops==1 && scheduled==2); remote=false; assert(command_web(2,stop)==0 && stops==2 && scheduled==2); stop_result=ESP_FAIL; assert(command_web(2,stop)==1 && stops==3); remote=true; assert(command_reboot(1,NULL)==0 && scheduled==3 && !reboots && last_action==ADMIN_SSH_DEFER_REBOOT); web=true; assert(command_reboot(1,NULL)==0 && scheduled==4 && !reboots && last_action==ADMIN_SSH_DEFER_REBOOT); schedule_result=ESP_FAIL; assert(command_reboot(1,NULL)==1 && scheduled==5 && !reboots); assert(command_reboot(2,NULL)==1 && scheduled==5 && !reboots); remote=false; assert(command_reboot(1,NULL)==0 && reboots==1 && waits==1 && scheduled==5); char *rotate[]={"web", "certificate", "rotate", "--force", "extra"}; remote=web=true; schedule_result=ESP_OK; assert(command_web(4,rotate)==0 && scheduled==6 && !rotations && last_action==ADMIN_CONSOLE_DEFER_WEB_CERTIFICATE_ROTATE); schedule_result=ESP_ERR_TIMEOUT; assert(command_web(4,rotate)==1 && scheduled==7 && !rotations); assert(command_web(3,rotate)==1 && scheduled==7 && !rotations); assert(command_web(5,rotate)==1 && scheduled==7 && !rotations); web=false; assert(command_web(4,rotate)==0 && rotations==1 && scheduled==7); remote=false; assert(command_web(4,rotate)==0 && rotations==2 && scheduled==7); puts("PASS: canonical WEB stop/certificate deferred, exact force required, SSH/UART unchanged, reboot and queue failure isolation"); } ''' with tempfile.TemporaryDirectory(prefix='console-lifecycle-') as directory: tmp = Path(directory) (tmp / 'test.c').write_text(prelude + header + fakes + function(ROOT / 'src/web_console.c', 'command_web') + function(ROOT / 'src/system_console.c', 'command_reboot') + tests) subprocess.run(['cc', '-std=c11', '-Wall', '-Wextra', '-Werror', str(tmp / 'test.c'), '-o', str(tmp / 'test')], check=True, timeout=30) subprocess.run([str(tmp / 'test')], check=True, timeout=10)