#!/usr/bin/env python3
"""Run the real example and force one legal losing interleaving deterministically."""
from pathlib import Path
import runpy
import subprocess
import sys
from threading import Barrier

REPO = Path(__file__).resolve().parents[2]
example = REPO / 'tests/py/parallel_shared_counter.py'
ns = runpy.run_path(str(example))
unsafe = ns['increment_unsafe']
both_read = Barrier(2)


def force_lost_update(counter, mutex):
    paused = False

    def trace(frame, event, arg):
        nonlocal paused
        if not paused and frame.f_code is unsafe.__code__ and event == 'line' and 'snapshot' in frame.f_locals:
            paused = True
            # Both actual workers have read the counter, and neither has written.
            both_read.wait(timeout=5)
        return trace

    sys.settrace(trace)
    try:
        unsafe(counter, mutex)
    finally:
        sys.settrace(None)


try:
    ns['run_trial'](force_lost_update, [0], 2)
except AssertionError as error:
    assert str(error) == 'an increment was lost', error
else:
    raise AssertionError('the forced lost-update schedule did not trigger the assertion')

for initial in ([0], [-17], [8, -3, 19], [10 ** 50, 0]):
    for count in (0, 1, 2, 8):
        assert ns['valid_trial_input'](initial, count)
        ns['run_trial'](ns['increment_safe'], initial.copy(), count)
for values, count in [([], 2), ([True], 2), ([1, 'x'], 2), ([1], -1), ([1], True), ('x', 2)]:
    assert not ns['valid_trial_input'](values, count)

# The public demo runs both versions, prints rates, and catches only the expected
# unsafe assertions. Do not make the test depend on a random failure percentage.
completed = subprocess.run([sys.executable, str(example)], text=True, capture_output=True, timeout=20)
assert completed.returncode == 0, completed.stderr
assert 'increment_unsafe: 20 real-thread trials' in completed.stdout, completed.stdout
assert 'increment_safe: 20 real-thread trials\n  Lost updates: 0/20 (0%)' in completed.stdout, completed.stdout
print('Real-thread counter demo and forced lost-update assertion passed')

# Potentially nonterminating demonstrations run in disposable child processes.
completed = subprocess.run([sys.executable, str(REPO / 'tests/py/parallel_liveness.py')],
                           text=True, capture_output=True, timeout=10)
assert completed.returncode == 0, completed.stderr
assert 'Consistent lock ordering: both workers completed.' in completed.stdout, completed.stdout
assert 'Retry livelock: still waiting' in completed.stdout, completed.stdout
print('Bounded real-thread deadlock/livelock demonstration passed')
