tests/testlib/sigpipe-remote.py
author Pierre-Yves David <pierre-yves.david@octobus.net>
Tue, 14 Jun 2022 11:26:18 +0200
branchstable
changeset 49344 0cc5f74ff7f0
parent 47637 d0c0d7b98014
child 48875 6000f5b25c9b
permissions -rwxr-xr-x
purge: prevent a silly crash with --confirm --files if --files is passed, there was no directory to checks and `msg` was undefined. This is now fixed and tested.

#!/usr/bin/env python3
from __future__ import print_function

import io
import os
import subprocess
import sys
import time

# we cannot use mercurial.testing as long as python2 is not dropped as the test
# will only install the mercurial module for python2 in python2 run
if sys.version_info[0] < 3:
    ver = '.'.join(str(x) for x in sys.version_info)
    exe = sys.executable
    print('SIGPIPE-HELPER: script should run with Python 3', file=sys.stderr)
    print('SIGPIPE-HELPER:   %s is running %s' % (exe, ver), file=sys.stderr)
    sys.exit(255)

if isinstance(sys.stdout.buffer, io.BufferedWriter):
    print('SIGPIPE-HELPER: script need unbuffered output', file=sys.stderr)
    sys.exit(255)

DEBUG_FILE = os.environ.get('SIGPIPE_REMOTE_DEBUG_FILE')
if DEBUG_FILE is None:
    debug_stream = sys.stderr.buffer
else:
    debug_stream = open(DEBUG_FILE, 'bw', buffering=0)

SYNCFILE1 = os.environ.get('SYNCFILE1')
SYNCFILE2 = os.environ.get('SYNCFILE2')
if SYNCFILE1 is None:
    print('SIGPIPE-HELPER: missing variable $SYNCFILE1', file=sys.stderr)
    sys.exit(255)
if SYNCFILE2 is None:
    print('SIGPIPE-HELPER: missing variable $SYNCFILE2', file=sys.stderr)
    sys.exit(255)


def _timeout_factor():
    """return the current modification to timeout"""
    default = int(os.environ.get('HGTEST_TIMEOUT_DEFAULT', 360))
    current = int(os.environ.get('HGTEST_TIMEOUT', default))
    if current == 0:
        return 1
    return current / float(default)


def wait_file(path, timeout=10):
    timeout *= _timeout_factor()
    start = time.time()
    while not os.path.exists(path):
        if (time.time() - start) > timeout:
            raise RuntimeError(b"timed out waiting for file: %s" % path)
        time.sleep(0.01)


def write_file(path, content=b''):
    with open(path, 'wb') as f:
        f.write(content)


# end of mercurial.testing content


def sysbytes(s):
    return s.encode('utf-8')


def sysstr(s):
    return s.decode('latin-1')


debug_stream.write(b'SIGPIPE-HELPER: Starting\n')

TESTLIB_DIR = os.path.dirname(sys.argv[0])
WAIT_SCRIPT = os.path.join(TESTLIB_DIR, 'wait-on-file')

hooks_cmd = '%s 10 %s %s'
hooks_cmd %= (
    WAIT_SCRIPT,
    SYNCFILE2,
    SYNCFILE1,
)

try:
    cmd = ['hg']
    cmd += sys.argv[1:]
    sub = subprocess.Popen(
        cmd,
        bufsize=0,
        close_fds=True,
        stdin=sys.stdin,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )

    basedir = os.path.dirname(sys.argv[0])
    worker = os.path.join(basedir, 'sigpipe-worker.py')

    cmd = [sys.executable, worker]

    stdout_worker = subprocess.Popen(
        cmd,
        bufsize=0,
        close_fds=True,
        stdin=sub.stdout,
        stdout=sys.stdout,
        stderr=sys.stderr,
    )

    stderr_worker = subprocess.Popen(
        cmd,
        bufsize=0,
        close_fds=True,
        stdin=sub.stderr,
        stdout=sys.stderr,
        stderr=sys.stderr,
    )
    debug_stream.write(b'SIGPIPE-HELPER: Redirection in place\n')
    os.close(sub.stdout.fileno())
    os.close(sub.stderr.fileno())
    debug_stream.write(b'SIGPIPE-HELPER: pipes closed in main\n')

    try:
        wait_file(sysbytes(SYNCFILE1))
    except RuntimeError as exc:
        msg = sysbytes(str(exc))
        debug_stream.write(b'SIGPIPE-HELPER: wait failed: %s\n' % msg)
    else:
        debug_stream.write(b'SIGPIPE-HELPER: SYNCFILE1 detected\n')
    stdout_worker.kill()
    stderr_worker.kill()
    stdout_worker.wait(10)
    stderr_worker.wait(10)
    debug_stream.write(b'SIGPIPE-HELPER: worker killed\n')

    debug_stream.write(b'SIGPIPE-HELPER: creating SYNCFILE2\n')
    write_file(sysbytes(SYNCFILE2))
finally:
    debug_stream.write(b'SIGPIPE-HELPER: Shutting down\n')
    if not sys.stdin.closed:
        sys.stdin.close()
    try:
        sub.wait(timeout=30)
    except subprocess.TimeoutExpired:
        msg = b'SIGPIPE-HELPER: Server process failed to terminate\n'
        debug_stream.write(msg)
        sub.kill()
        sub.wait()
        msg = b'SIGPIPE-HELPER: Server process killed\n'
    else:
        msg = b'SIGPIPE-HELPER: Server process terminated with status %d\n'
        msg %= sub.returncode
        debug_stream.write(msg)
    debug_stream.write(b'SIGPIPE-HELPER: Shut down\n')