|  | 
|  | 1 | +#!/usr/bin/env python3 | 
|  | 2 | +import subprocess | 
|  | 3 | +import signal | 
|  | 4 | +import os | 
|  | 5 | +import logging | 
|  | 6 | + | 
|  | 7 | +# Set up logging | 
|  | 8 | +logging.basicConfig( | 
|  | 9 | +    level=logging.INFO, | 
|  | 10 | +    format="%(asctime)s - %(levelname)s - %(message)s", | 
|  | 11 | +    datefmt="%Y-%m-%d %H:%M:%S", | 
|  | 12 | +) | 
|  | 13 | + | 
|  | 14 | +# --- Configuration --- | 
|  | 15 | +THERMALD_CMD = ["thermald", "--no-daemon", "--loglevel=debug", "--adaptive"] | 
|  | 16 | + | 
|  | 17 | +# Patterns to search for (as bytes). | 
|  | 18 | +FAILURE_PATTERN = b"Also unable to evaluate any conditions" | 
|  | 19 | +SUCCESS_PATTERN = b"Start main loop" | 
|  | 20 | + | 
|  | 21 | +# The grep command will search for either pattern, stop after the first match (-m 1), | 
|  | 22 | +# and flush its output immediately (--line-buffered). | 
|  | 23 | +GREP_CMD = [ | 
|  | 24 | +    "grep", | 
|  | 25 | +    "--line-buffered", | 
|  | 26 | +    "-m", | 
|  | 27 | +    "1", | 
|  | 28 | +    "-e", | 
|  | 29 | +    FAILURE_PATTERN.decode("utf-8"), | 
|  | 30 | +    "-e", | 
|  | 31 | +    SUCCESS_PATTERN.decode("utf-8"), | 
|  | 32 | +] | 
|  | 33 | + | 
|  | 34 | +TIMEOUT_SECONDS = 15 | 
|  | 35 | + | 
|  | 36 | + | 
|  | 37 | +def stop_thermald_service(): | 
|  | 38 | +    """ | 
|  | 39 | +    Stop the thermald systemd service if it's running. | 
|  | 40 | +    Returns True if service was running and stopped, False if it wasn't running. | 
|  | 41 | +    """ | 
|  | 42 | +    logging.info("--- Checking thermald systemd service ---") | 
|  | 43 | + | 
|  | 44 | +    try: | 
|  | 45 | +        # Check if thermald service is active | 
|  | 46 | +        result = subprocess.run( | 
|  | 47 | +            ["systemctl", "is-active", "thermald"], capture_output=True, text=True | 
|  | 48 | +        ) | 
|  | 49 | + | 
|  | 50 | +        if result.returncode == 0 and result.stdout.strip() == "active": | 
|  | 51 | +            logging.info("Thermald service is running. Stopping it...") | 
|  | 52 | +            stop_result = subprocess.run( | 
|  | 53 | +                ["systemctl", "stop", "thermald"], capture_output=True, text=True | 
|  | 54 | +            ) | 
|  | 55 | + | 
|  | 56 | +            if stop_result.returncode == 0: | 
|  | 57 | +                logging.info("Thermald service stopped successfully.") | 
|  | 58 | +                return True | 
|  | 59 | +            else: | 
|  | 60 | +                logging.error(f"Failed to stop thermald service: {stop_result.stderr}") | 
|  | 61 | +                return False | 
|  | 62 | +        else: | 
|  | 63 | +            logging.info("Thermald service is not running.") | 
|  | 64 | +            return False | 
|  | 65 | + | 
|  | 66 | +    except Exception as e: | 
|  | 67 | +        logging.error(f"Error checking/stopping thermald service: {e}") | 
|  | 68 | +        return False | 
|  | 69 | + | 
|  | 70 | + | 
|  | 71 | +def start_thermald_service(): | 
|  | 72 | +    """ | 
|  | 73 | +    Start the thermald systemd service. | 
|  | 74 | +    """ | 
|  | 75 | +    logging.info("--- Restarting thermald systemd service ---") | 
|  | 76 | + | 
|  | 77 | +    try: | 
|  | 78 | +        result = subprocess.run( | 
|  | 79 | +            ["systemctl", "start", "thermald"], capture_output=True, text=True | 
|  | 80 | +        ) | 
|  | 81 | + | 
|  | 82 | +        if result.returncode == 0: | 
|  | 83 | +            logging.info("Thermald service restarted successfully.") | 
|  | 84 | +        else: | 
|  | 85 | +            logging.error(f"Failed to restart thermald service: {result.stderr}") | 
|  | 86 | + | 
|  | 87 | +    except Exception as e: | 
|  | 88 | +        logging.error(f"Error restarting thermald service: {e}") | 
|  | 89 | + | 
|  | 90 | + | 
|  | 91 | +def run_thermald_grep_test(): | 
|  | 92 | +    """ | 
|  | 93 | +    Test thermald's adaptive engine startup by monitoring its output. | 
|  | 94 | +
 | 
|  | 95 | +    This test launches thermald with adaptive engine and uses grep to detect | 
|  | 96 | +    which pattern appears first: | 
|  | 97 | +    - SUCCESS: "Start main loop" - adaptive engine started correctly | 
|  | 98 | +    - FAILURE: "Also unable to evaluate any conditions" - adaptive engine failed, | 
|  | 99 | +      thermald falls back to highest power profile | 
|  | 100 | +
 | 
|  | 101 | +    The test captures the first occurrence of either pattern to determine if | 
|  | 102 | +    the adaptive engine initialization was successful. | 
|  | 103 | +    """ | 
|  | 104 | +    logging.info("=== THERMALD ADAPTIVE ENGINE TEST ===") | 
|  | 105 | +    logging.info("This test verifies that thermald's adaptive engine starts correctly.") | 
|  | 106 | +    logging.info( | 
|  | 107 | +        "Monitoring thermald output for adaptive engine initialization patterns..." | 
|  | 108 | +    ) | 
|  | 109 | +    logging.info(f"Success pattern: '{SUCCESS_PATTERN.decode('utf-8')}'") | 
|  | 110 | +    logging.info(f"Failure pattern: '{FAILURE_PATTERN.decode('utf-8')}'") | 
|  | 111 | +    logging.info(f"Command: {' '.join(THERMALD_CMD)}") | 
|  | 112 | +    logging.info(f"Timeout: {TIMEOUT_SECONDS} seconds") | 
|  | 113 | +    logging.info("=" * 70) | 
|  | 114 | + | 
|  | 115 | +    if os.geteuid() != 0: | 
|  | 116 | +        logging.error("\nERROR: This script requires sudo privileges.") | 
|  | 117 | +        logging.error("Please run it with: sudo python3 test_thermald.py") | 
|  | 118 | +        raise SystemExit(1) | 
|  | 119 | + | 
|  | 120 | +    # Stop thermald service if it's running | 
|  | 121 | +    service_was_running = stop_thermald_service() | 
|  | 122 | + | 
|  | 123 | +    thermald_proc = None | 
|  | 124 | +    grep_proc = None | 
|  | 125 | +    result_message = "" | 
|  | 126 | +    exit_code = 1  # Default to failure | 
|  | 127 | + | 
|  | 128 | +    try: | 
|  | 129 | +        # 1. Start the thermald process. | 
|  | 130 | +        #    - Redirect stderr to stdout so grep sees all output. | 
|  | 131 | +        #    - preexec_fn=os.setsid creates a new process group for robust cleanup. | 
|  | 132 | +        thermald_proc = subprocess.Popen( | 
|  | 133 | +            THERMALD_CMD, | 
|  | 134 | +            stdout=subprocess.PIPE, | 
|  | 135 | +            stderr=subprocess.STDOUT, | 
|  | 136 | +            preexec_fn=os.setsid, | 
|  | 137 | +        ) | 
|  | 138 | +        logging.info( | 
|  | 139 | +            f"Started thermald with adaptive engine (PGID: {thermald_proc.pid})" | 
|  | 140 | +        ) | 
|  | 141 | + | 
|  | 142 | +        # 2. Start the grep process, piping thermald's output to its input. | 
|  | 143 | +        grep_proc = subprocess.Popen( | 
|  | 144 | +            GREP_CMD, stdin=thermald_proc.stdout, stdout=subprocess.PIPE | 
|  | 145 | +        ) | 
|  | 146 | +        logging.info(f"Started pattern monitoring (grep PID: {grep_proc.pid})") | 
|  | 147 | +        logging.info("Waiting for adaptive engine initialization patterns...") | 
|  | 148 | + | 
|  | 149 | +        # 3. Allow thermald's stdout pipe to be closed. This is VERY important. | 
|  | 150 | +        #    It prevents thermald from hanging if grep exits before thermald does. | 
|  | 151 | +        thermald_proc.stdout.close() | 
|  | 152 | + | 
|  | 153 | +        # 4. Wait for grep to finish or for the timeout to expire. | 
|  | 154 | +        #    communicate() reads the output and waits for the process. | 
|  | 155 | +        try: | 
|  | 156 | +            grep_output, _ = grep_proc.communicate(timeout=TIMEOUT_SECONDS) | 
|  | 157 | + | 
|  | 158 | +            # 5. Analyze grep's output to determine the result. | 
|  | 159 | +            if FAILURE_PATTERN in grep_output: | 
|  | 160 | +                result_message = ( | 
|  | 161 | +                    "FAILURE: Thermald adaptive engine failed to initialize.\n" | 
|  | 162 | +                    "Found failure pattern first - thermald fell back to highest power profile." | 
|  | 163 | +                ) | 
|  | 164 | +                exit_code = 1 | 
|  | 165 | +            elif SUCCESS_PATTERN in grep_output: | 
|  | 166 | +                result_message = ( | 
|  | 167 | +                    "SUCCESS: Thermald adaptive engine started correctly.\n" | 
|  | 168 | +                    "Found success pattern - adaptive engine is active." | 
|  | 169 | +                ) | 
|  | 170 | +                exit_code = 0 | 
|  | 171 | +            else: | 
|  | 172 | +                # This case means thermald exited before grep found anything. | 
|  | 173 | +                result_message = ( | 
|  | 174 | +                    "FAILURE: Thermald process exited unexpectedly.\n" | 
|  | 175 | +                    "No adaptive engine patterns detected in output." | 
|  | 176 | +                ) | 
|  | 177 | +                exit_code = 1 | 
|  | 178 | + | 
|  | 179 | +        except subprocess.TimeoutExpired: | 
|  | 180 | +            result_message = ( | 
|  | 181 | +                f"FAILURE: Test timed out after {TIMEOUT_SECONDS} seconds.\n" | 
|  | 182 | +                "Thermald may be stuck or taking too long to initialize adaptive engine." | 
|  | 183 | +            ) | 
|  | 184 | +            exit_code = 1 | 
|  | 185 | + | 
|  | 186 | +    except FileNotFoundError as e: | 
|  | 187 | +        logging.error(f"\nERROR: Command not found: '{e.filename}'. Is it installed?") | 
|  | 188 | +        raise SystemExit(1) | 
|  | 189 | +    except Exception as e: | 
|  | 190 | +        result_message = ( | 
|  | 191 | +            f"FAILURE: An unexpected error occurred during the test: {e}\n" | 
|  | 192 | +            "This may indicate system configuration issues." | 
|  | 193 | +        ) | 
|  | 194 | +        exit_code = 1 | 
|  | 195 | +    finally: | 
|  | 196 | +        # --- Graceful Cleanup --- | 
|  | 197 | +        logging.info("--- Cleaning up test processes ---") | 
|  | 198 | +        # First, ensure the grep process is gone | 
|  | 199 | +        if grep_proc and grep_proc.poll() is None: | 
|  | 200 | +            logging.info(f"Terminating leftover grep process {grep_proc.pid}...") | 
|  | 201 | +            grep_proc.terminate() | 
|  | 202 | +            grep_proc.wait(timeout=2) | 
|  | 203 | +            if grep_proc.poll() is None: | 
|  | 204 | +                grep_proc.kill() | 
|  | 205 | + | 
|  | 206 | +        # Then, clean up the main thermald process and its children | 
|  | 207 | +        if thermald_proc and thermald_proc.poll() is None: | 
|  | 208 | +            logging.info( | 
|  | 209 | +                f"Sending SIGTERM to thermald process group {thermald_proc.pid}..." | 
|  | 210 | +            ) | 
|  | 211 | +            try: | 
|  | 212 | +                os.killpg(thermald_proc.pid, signal.SIGTERM) | 
|  | 213 | +                thermald_proc.wait(timeout=5) | 
|  | 214 | +                logging.info("Thermald process group terminated gracefully.") | 
|  | 215 | +            except ProcessLookupError: | 
|  | 216 | +                logging.info("Thermald process group already gone.") | 
|  | 217 | +            except subprocess.TimeoutExpired: | 
|  | 218 | +                logging.warning( | 
|  | 219 | +                    "Thermald did not respond to SIGTERM. Sending SIGKILL..." | 
|  | 220 | +                ) | 
|  | 221 | +                os.killpg(thermald_proc.pid, signal.SIGKILL) | 
|  | 222 | +                logging.info("Thermald process group killed.") | 
|  | 223 | +        else: | 
|  | 224 | +            logging.info("Thermald process already terminated.") | 
|  | 225 | + | 
|  | 226 | +        # Restart thermald service if it was originally running | 
|  | 227 | +        if service_was_running: | 
|  | 228 | +            start_thermald_service() | 
|  | 229 | + | 
|  | 230 | +    logging.info("=" * 70) | 
|  | 231 | +    logging.info("=== THERMALD ADAPTIVE ENGINE TEST RESULT ===") | 
|  | 232 | +    logging.info(result_message) | 
|  | 233 | +    if exit_code == 0: | 
|  | 234 | +        logging.info("PASSED: Thermald adaptive engine is working correctly.") | 
|  | 235 | +    else: | 
|  | 236 | +        logging.info("FAILED: Thermald adaptive engine did not start properly.") | 
|  | 237 | +    logging.info("=" * 70) | 
|  | 238 | +    return exit_code | 
|  | 239 | + | 
|  | 240 | + | 
|  | 241 | +def main(): | 
|  | 242 | +    test_result = run_thermald_grep_test() | 
|  | 243 | +    raise SystemExit(test_result) | 
|  | 244 | + | 
|  | 245 | + | 
|  | 246 | +if __name__ == "__main__": | 
|  | 247 | +    main() | 
0 commit comments