flash-aminpa-V1.5.yml / Hardware flashing - Amin Pardaz Asia v1.5 (push) Failing after 6m9s
128 خطوط
5.0 KiB
Python
128 خطوط
5.0 KiB
Python
"""Restart RUTBS from U-Boot and verify the ordered boot and kernel name."""
|
|
import argparse
|
|
import fcntl
|
|
import os
|
|
import re
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import serial
|
|
|
|
|
|
STAGES = (
|
|
('U-Boot prompt', '=>', b'res\r'),
|
|
('kernel', 'Starting kernel ...', None),
|
|
('debug selection', 'Press the [1], [2], [3] or [4] key and hit [enter] to select the debug level', None),
|
|
('console activation', 'Please press Enter to activate this console.', b'\r'),
|
|
('login', 'RUTBS login:', b'root\r'),
|
|
('password', 'Password:', 'password'),
|
|
('shell', 'root@RUTBS:~#', b'uname -s\r'),
|
|
('command result', 'root@RUTBS:~#', None),
|
|
)
|
|
FAILURE = re.compile(r'Kernel panic|Login incorrect|Login failed|Authentication fail(?:ed|ure)|Access denied', re.I)
|
|
|
|
|
|
def run_test(port, password, transcript, deadline):
|
|
stage = 0
|
|
pending = ''
|
|
observed = ''
|
|
# Request a fresh prompt when the existing => predates opening the port.
|
|
remaining = deadline - time.monotonic()
|
|
if remaining <= 0:
|
|
raise RuntimeError('Overall test timeout')
|
|
port.write_timeout = min(2, remaining)
|
|
if port.write(b'\r') != 1:
|
|
raise RuntimeError('Incomplete serial write')
|
|
while time.monotonic() < deadline:
|
|
data = port.read(min(port.in_waiting or 1, 4096))
|
|
if not data:
|
|
continue
|
|
transcript.extend(data)
|
|
chunk = data.decode('utf-8', errors='replace').replace('\r', '')
|
|
pending += chunk
|
|
observed = (observed + chunk)[-8192:]
|
|
if FAILURE.search(observed):
|
|
raise RuntimeError('Boot or authentication failure detected')
|
|
while stage < len(STAGES):
|
|
name, token, action = STAGES[stage]
|
|
if stage == 0:
|
|
match = re.search(r'(?:^|\n)[^\n]*=> *$', pending)
|
|
position = pending.rfind('=>') if match else -1
|
|
else:
|
|
position = pending.find(token)
|
|
if position < 0:
|
|
break
|
|
before = pending[:position]
|
|
pending = pending[position + len(token):]
|
|
if stage == len(STAGES) - 1:
|
|
lines = [line.strip() for line in before.splitlines() if line.strip()]
|
|
if lines and lines[0] == 'uname -s':
|
|
lines.pop(0) # A terminal may echo the command.
|
|
if lines != ['Linux']:
|
|
raise RuntimeError('uname -s output was not exactly Linux')
|
|
if time.monotonic() >= deadline:
|
|
raise RuntimeError('Overall test timeout')
|
|
return
|
|
if action is not None:
|
|
# Discard pre-existing input: the next prompt must follow our write.
|
|
pending = ''
|
|
payload = password.encode('utf-8') + b'\r' if action == 'password' else action
|
|
remaining = deadline - time.monotonic()
|
|
if remaining <= 0:
|
|
raise RuntimeError('Overall test timeout')
|
|
port.write_timeout = min(2, remaining)
|
|
if port.write(payload) != len(payload):
|
|
raise RuntimeError('Incomplete serial write')
|
|
stage += 1
|
|
if len(pending) > 65536:
|
|
raise RuntimeError('Unexpected excessive serial output in current stage')
|
|
raise RuntimeError('Timeout waiting for ' + STAGES[stage][0])
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--port', default='/dev/ttyUSB0')
|
|
args = parser.parse_args()
|
|
deadline = time.monotonic() + 180
|
|
password = os.environ.get('RUTBS_PASSWORD', '')
|
|
transcript = bytearray()
|
|
result = 1
|
|
reason = 'Serial connection or configuration error'
|
|
log = Path('artifacts/boot.log')
|
|
try:
|
|
log.parent.mkdir(parents=True, exist_ok=True)
|
|
# Never put raw serial bytes on disk or stdout, even during authentication.
|
|
log.write_text('', encoding='utf-8')
|
|
if not password or any(c in password for c in '\r\n\x00'):
|
|
raise RuntimeError('RUTBS_PASSWORD is missing or invalid')
|
|
with open('/tmp/uboot-serial.lock', 'w') as lock:
|
|
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
with serial.Serial(args.port, 115200, timeout=0.1, write_timeout=2, exclusive=True) as port:
|
|
run_test(port, password, transcript, deadline)
|
|
result = 0
|
|
except RuntimeError as exc:
|
|
reason = str(exc)
|
|
except Exception:
|
|
# Serial/library exceptions can include transmitted data; do not print them.
|
|
pass
|
|
finally:
|
|
content = transcript.decode('utf-8', errors='replace').replace('\r', '')
|
|
if password:
|
|
content = content.replace(password, '[REDACTED]')
|
|
reason = reason.replace(password, '[REDACTED]')
|
|
try:
|
|
log.write_text(content, encoding='utf-8')
|
|
except OSError:
|
|
result = 1
|
|
reason = 'Could not save serial log'
|
|
if result:
|
|
print('BOOT TEST: FAIL')
|
|
print(reason)
|
|
else:
|
|
print('BOOT TEST: PASS')
|
|
return result
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main())
|