| """Synthetic test suite for EyeGuard 20-20-20. No webcam required.""" |
| import time, sys |
| sys.path.insert(0, '/app') |
| from eye_guard_2020 import EyeGuard2020, Status, update_eye_state |
| from eye_guard_2020 import REST_DURATION_SECONDS, SCREEN_TIME_ALERT_SECONDS, MIN_CONSECUTIVE_CLOSED_FRAMES |
|
|
|
|
| def run_state_machine(guard, ear_l, ear_r, now_override=None): |
| now = now_override if now_override is not None else time.time() |
| state = guard.state |
| ear_left = guard._smooth_ear(state.ear_history_left, ear_l) |
| ear_right = guard._smooth_ear(state.ear_history_right, ear_r) |
| state.left_eye = update_eye_state(state.left_eye, ear_left, guard.ear_open, guard.ear_closed) |
| state.right_eye = update_eye_state(state.right_eye, ear_right, guard.ear_open, guard.ear_closed) |
|
|
| screen_elapsed = now - state.screen_timer_start |
| both_eyes_closed = state.left_eye.is_closed and state.right_eye.is_closed |
| one_eye_closed = (state.left_eye.is_closed and not state.right_eye.is_closed) or \ |
| (not state.left_eye.is_closed and state.right_eye.is_closed) |
|
|
| if state.status == Status.RESTING and one_eye_closed: |
| state.spoofing_detected_at = now |
| state.spoofing_count += 1 |
| state.status = Status.SPOOFING |
| state.failed_rests += 1 |
| state.rest_start_time = None |
| return |
|
|
| if state.status == Status.ALERT and one_eye_closed: |
| state.spoofing_detected_at = now |
| state.spoofing_count += 1 |
|
|
| if state.status == Status.SCREENING: |
| if screen_elapsed >= SCREEN_TIME_ALERT_SECONDS: |
| state.status = Status.ALERT |
| elif state.status == Status.ALERT: |
| if both_eyes_closed and state.left_eye.closed_frames >= MIN_CONSECUTIVE_CLOSED_FRAMES: |
| state.status = Status.RESTING |
| state.rest_start_time = now |
| elif state.status == Status.RESTING: |
| if not both_eyes_closed: |
| state.failed_rests += 1 |
| state.status = Status.ALERT |
| state.rest_start_time = None |
| else: |
| rest_elapsed = now - state.rest_start_time |
| if rest_elapsed >= REST_DURATION_SECONDS: |
| state.status = Status.REST_COMPLETE |
| state.rest_completed_time = now |
| state.total_rests_completed += 1 |
| state.total_rest_seconds += rest_elapsed |
| state.screen_timer_start = now |
| elif state.status == Status.REST_COMPLETE: |
| if now - state.rest_completed_time > 3.0: |
| state.status = Status.SCREENING |
| elif state.status == Status.SPOOFING: |
| if now - state.spoofing_detected_at > 3.0: |
| state.status = Status.ALERT |
| elif state.status == Status.NO_FACE: |
| if screen_elapsed >= SCREEN_TIME_ALERT_SECONDS: |
| state.status = Status.ALERT |
| else: |
| state.status = Status.SCREENING |
|
|
|
|
| def print_frame(i, guard, desc): |
| le = "C" if guard.state.left_eye.is_closed else "O" |
| re = "C" if guard.state.right_eye.is_closed else "O" |
| print(f" Frame {i:3d}: Eyes=({le},{re}) Status={guard.state.status.name:<12} | {desc}") |
|
|
|
|
| def test_normal_rest(): |
| print("\n" + "=" * 60) |
| print("TEST 1: Normal rest cycle (both eyes closed)") |
| print("=" * 60) |
| guard = EyeGuard2020() |
| guard.ear_open = 0.20 |
| guard.ear_closed = 0.15 |
| t0 = 1000.0 |
| guard.state.screen_timer_start = t0 - SCREEN_TIME_ALERT_SECONDS - 1 |
| guard.state.status = Status.SCREENING |
|
|
| for i in range(5): |
| run_state_machine(guard, 0.25, 0.25, t0 + i * 0.033) |
| print_frame(i, guard, "eyes open") |
| assert guard.state.status == Status.ALERT, f"Expected ALERT, got {guard.state.status.name}" |
| print(" -> ALERT triggered correctly") |
|
|
| for i in range(5, 30): |
| run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033) |
| assert guard.state.status == Status.RESTING |
| print(" -> RESTING entered correctly") |
|
|
| for i in range(30, 650): |
| run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033) |
| if i == 35: |
| print_frame(i, guard, "resting...") |
|
|
| assert guard.state.status == Status.REST_COMPLETE |
| assert guard.state.total_rests_completed >= 1 |
| print(f" -> REST_COMPLETE! rests={guard.state.total_rests_completed}") |
| print(" PASS") |
|
|
|
|
| def test_spoofing(): |
| print("\n" + "=" * 60) |
| print("TEST 2: Spoofing detection (one eye closed)") |
| print("=" * 60) |
| guard = EyeGuard2020() |
| guard.ear_open = 0.20 |
| guard.ear_closed = 0.15 |
| t0 = 2000.0 |
| guard.state.screen_timer_start = t0 - SCREEN_TIME_ALERT_SECONDS - 1 |
| guard.state.status = Status.ALERT |
|
|
| for i in range(20): |
| run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033) |
| if i < 3: |
| print_frame(i, guard, "entering rest") |
| assert guard.state.status == Status.RESTING |
| print(" -> RESTING entered") |
|
|
| for i in range(20, 50): |
| run_state_machine(guard, 0.25, 0.10, t0 + i * 0.033) |
| if i < 23: |
| print_frame(i, guard, "SPOOFING: left eye open!") |
|
|
| assert guard.state.status == Status.SPOOFING |
| assert guard.state.spoofing_count >= 1 |
| print(f" -> SPOOFING detected! count={guard.state.spoofing_count}") |
|
|
| for i in range(50, 200): |
| run_state_machine(guard, 0.25, 0.25, t0 + i * 0.033) |
| if i == 150: |
| print_frame(i, guard, "after spoof penalty") |
| assert guard.state.status == Status.ALERT |
| print(" -> Back to ALERT after penalty") |
| print(" PASS") |
|
|
|
|
| def test_blink_filter(): |
| print("\n" + "=" * 60) |
| print("TEST 3: Blink filtering (brief closures ignored)") |
| print("=" * 60) |
| guard = EyeGuard2020() |
| guard.ear_open = 0.20 |
| guard.ear_closed = 0.15 |
| t0 = 3000.0 |
| guard.state.screen_timer_start = t0 - SCREEN_TIME_ALERT_SECONDS - 1 |
| guard.state.status = Status.ALERT |
|
|
| for i in range(5): |
| run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033) |
| print_frame(i, guard, "blink!") |
| for i in range(5, 20): |
| run_state_machine(guard, 0.25, 0.25, t0 + i * 0.033) |
| if i < 7: |
| print_frame(i, guard, "eyes open again") |
|
|
| assert guard.state.status == Status.ALERT, f"Expected ALERT, got {guard.state.status.name}" |
| print(" -> Blink correctly filtered, still in ALERT") |
|
|
| for i in range(20, 650): |
| run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033) |
| if i == 30: |
| print_frame(i, guard, "proper rest...") |
|
|
| assert guard.state.status == Status.REST_COMPLETE |
| assert guard.state.total_rests_completed >= 1 |
| print(f" -> Rest completed after blink! rests={guard.state.total_rests_completed}") |
| print(" PASS") |
|
|
|
|
| def test_interruption(): |
| print("\n" + "=" * 60) |
| print("TEST 4: Rest interruption (early opening)") |
| print("=" * 60) |
| guard = EyeGuard2020() |
| guard.ear_open = 0.20 |
| guard.ear_closed = 0.15 |
| t0 = 4000.0 |
| guard.state.screen_timer_start = t0 - SCREEN_TIME_ALERT_SECONDS - 1 |
| guard.state.status = Status.ALERT |
|
|
| for i in range(30): |
| run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033) |
| if i < 3: |
| print_frame(i, guard, "starting rest") |
| assert guard.state.status == Status.RESTING |
| print(" -> RESTING entered") |
|
|
| for i in range(30, 350): |
| run_state_machine(guard, 0.25, 0.25, t0 + i * 0.033) |
| if i == 35: |
| print_frame(i, guard, "REST INTERRUPTED!") |
|
|
| assert guard.state.status == Status.ALERT |
| assert guard.state.failed_rests >= 1 |
| print(f" -> Rest failed (interrupted). failed={guard.state.failed_rests}") |
|
|
| guard.state.status = Status.ALERT |
| for i in range(350, 1000): |
| run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033) |
| if i == 360: |
| print_frame(i, guard, "retry rest...") |
|
|
| assert guard.state.status == Status.REST_COMPLETE |
| assert guard.state.total_rests_completed >= 1 |
| print(f" -> Retry succeeded! rests={guard.state.total_rests_completed}") |
| print(" PASS") |
|
|
|
|
| def test_no_face(): |
| print("\n" + "=" * 60) |
| print("TEST 5: No face visible during rest") |
| print("=" * 60) |
| guard = EyeGuard2020() |
| guard.ear_open = 0.20 |
| guard.ear_closed = 0.15 |
| t0 = 5000.0 |
| guard.state.screen_timer_start = t0 - SCREEN_TIME_ALERT_SECONDS - 1 |
| guard.state.status = Status.ALERT |
|
|
| for i in range(20): |
| run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033) |
| if i < 3: |
| print_frame(i, guard, "entering rest") |
| assert guard.state.status == Status.RESTING |
| print(" -> RESTING entered") |
|
|
| guard.state.status = Status.NO_FACE |
| guard.state.failed_rests += 1 |
| guard.state.rest_start_time = None |
|
|
| assert guard.state.status == Status.NO_FACE |
| print(" -> NO_FACE detected, rest failed") |
| assert guard.state.failed_rests >= 1 |
| print(f" -> failed={guard.state.failed_rests}") |
| print(" PASS") |
|
|
|
|
| def test_one_eye_alert(): |
| print("\n" + "=" * 60) |
| print("TEST 6: One-eye trick during ALERT phase") |
| print("=" * 60) |
| guard = EyeGuard2020() |
| guard.ear_open = 0.20 |
| guard.ear_closed = 0.15 |
| t0 = 6000.0 |
| guard.state.screen_timer_start = t0 - SCREEN_TIME_ALERT_SECONDS - 1 |
| guard.state.status = Status.ALERT |
|
|
| for i in range(50): |
| run_state_machine(guard, 0.10, 0.25, t0 + i * 0.033) |
| if i < 3: |
| print_frame(i, guard, "one-eye trick (left closed)") |
|
|
| assert guard.state.spoofing_count >= 1 |
| print(f" -> Spoofing detected during ALERT! count={guard.state.spoofing_count}") |
| print(f" -> Final status: {guard.state.status.name}") |
| print(" PASS") |
|
|
|
|
| if __name__ == "__main__": |
| print("=" * 60) |
| print("EyeGuard 20-20-20 Synthetic Test Suite") |
| print("=" * 60) |
| test_normal_rest() |
| test_spoofing() |
| test_blink_filter() |
| test_interruption() |
| test_no_face() |
| test_one_eye_alert() |
| print("\n" + "=" * 60) |
| print("ALL TESTS PASSED!") |
| print("=" * 60) |
|
|