##// END OF EJS Templates
test: use a python script in `test-transaction-rollback-on-sigpipe.t`...
marmoute -
r48352:ed81f2be default
parent child Browse files
Show More
@@ -0,0 +1,176 b''
1 #!/usr/bin/env python3
2 from __future__ import print_function
3
4 import os
5 import subprocess
6 import sys
7 import threading
8 import time
9
10 # 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
11
12
13 def _timeout_factor():
14 """return the current modification to timeout"""
15 default = int(os.environ.get('HGTEST_TIMEOUT_DEFAULT', 360))
16 current = int(os.environ.get('HGTEST_TIMEOUT', default))
17 if current == 0:
18 return 1
19 return current / float(default)
20
21
22 def wait_file(path, timeout=10):
23 timeout *= _timeout_factor()
24 start = time.time()
25 while not os.path.exists(path):
26 if (time.time() - start) > timeout:
27 raise RuntimeError(b"timed out waiting for file: %s" % path)
28 time.sleep(0.01)
29
30
31 def write_file(path, content=b''):
32 with open(path, 'wb') as f:
33 f.write(content)
34
35
36 # end of mercurial.testing content
37
38 if sys.version_info[0] < 3:
39 print('SIGPIPE-HELPER: script should run with Python 3', file=sys.stderr)
40 sys.exit(255)
41
42
43 def sysbytes(s):
44 return s.encode('utf-8')
45
46
47 def sysstr(s):
48 return s.decode('latin-1')
49
50
51 piped_stdout = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC)
52 piped_stderr = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC)
53
54 stdout_writer = os.fdopen(piped_stdout[1], "rb")
55 stdout_reader = os.fdopen(piped_stdout[0], "rb")
56 stderr_writer = os.fdopen(piped_stderr[1], "rb")
57 stderr_reader = os.fdopen(piped_stderr[0], "rb")
58
59 DEBUG_FILE = os.environ.get('SIGPIPE_REMOTE_DEBUG_FILE')
60 if DEBUG_FILE is None:
61 debug_stream = sys.stderr.buffer
62 else:
63 debug_stream = open(DEBUG_FILE, 'bw', buffering=0)
64
65 SYNCFILE1 = os.environ.get('SYNCFILE1')
66 SYNCFILE2 = os.environ.get('SYNCFILE2')
67 if SYNCFILE1 is None:
68 print('SIGPIPE-HELPER: missing variable $SYNCFILE1', file=sys.stderr)
69 sys.exit(255)
70 if SYNCFILE2 is None:
71 print('SIGPIPE-HELPER: missing variable $SYNCFILE2', file=sys.stderr)
72 sys.exit(255)
73
74 debug_stream.write(b'SIGPIPE-HELPER: Starting\n')
75
76 TESTLIB_DIR = os.path.dirname(sys.argv[0])
77 WAIT_SCRIPT = os.path.join(TESTLIB_DIR, 'wait-on-file')
78
79 hooks_cmd = '%s 10 %s %s'
80 hooks_cmd %= (
81 WAIT_SCRIPT,
82 SYNCFILE2,
83 SYNCFILE1,
84 )
85
86 cmd = ['hg']
87 cmd += sys.argv[1:]
88 sub = subprocess.Popen(
89 cmd,
90 bufsize=0,
91 close_fds=True,
92 stdin=sys.stdin,
93 stdout=stdout_writer,
94 stderr=stderr_writer,
95 )
96
97 debug_stream.write(b'SIGPIPE-HELPER: Mercurial started\n')
98
99
100 shut_down = threading.Event()
101
102 close_lock = threading.Lock()
103
104
105 def _read(stream):
106 try:
107 return stream.read()
108 except ValueError:
109 # read on closed file
110 return None
111
112
113 def forward_stdout():
114 while not shut_down.is_set():
115 c = _read(stdout_reader)
116 while c is not None:
117 sys.stdout.buffer.write(c)
118 c = _read(stdout_reader)
119 time.sleep(0.001)
120 with close_lock:
121 if not stdout_reader.closed:
122 stdout_reader.close()
123 debug_stream.write(b'SIGPIPE-HELPER: stdout closed\n')
124
125
126 def forward_stderr():
127 while not shut_down.is_set():
128 c = _read(stderr_reader)
129 if c is not None:
130 sys.stderr.buffer.write(c)
131 c = _read(stderr_reader)
132 time.sleep(0.001)
133 with close_lock:
134 if not stderr_reader.closed:
135 stderr_reader.close()
136 debug_stream.write(b'SIGPIPE-HELPER: stderr closed\n')
137
138
139 stdout_thread = threading.Thread(target=forward_stdout, daemon=True)
140 stderr_thread = threading.Thread(target=forward_stderr, daemon=True)
141
142 try:
143 stdout_thread.start()
144 stderr_thread.start()
145
146 debug_stream.write(b'SIGPIPE-HELPER: Redirection in place\n')
147
148 try:
149 wait_file(sysbytes(SYNCFILE1))
150 except RuntimeError as exc:
151 msg = sysbytes(str(exc))
152 debug_stream.write(b'SIGPIPE-HELPER: wait failed: %s\n' % msg)
153 else:
154 debug_stream.write(b'SIGPIPE-HELPER: SYNCFILE1 detected\n')
155 with close_lock:
156 if not stdout_reader.closed:
157 stdout_reader.close()
158 if not stderr_reader.closed:
159 stderr_reader.close()
160 sys.stdin.close()
161 debug_stream.write(b'SIGPIPE-HELPER: pipes closed\n')
162 debug_stream.write(b'SIGPIPE-HELPER: creating SYNCFILE2\n')
163 write_file(sysbytes(SYNCFILE2))
164 finally:
165 debug_stream.write(b'SIGPIPE-HELPER: Shutting down\n')
166 shut_down.set()
167 if not sys.stdin.closed:
168 sys.stdin.close()
169 try:
170 sub.wait(timeout=30)
171 except subprocess.TimeoutExpired:
172 msg = b'SIGPIPE-HELPER: Server process failed to terminate\n'
173 debug_stream.write(msg)
174 else:
175 debug_stream.write(b'SIGPIPE-HELPER: Server process terminated\n')
176 debug_stream.write(b'SIGPIPE-HELPER: Shut down\n')
@@ -1,53 +1,30 b''
1 #require bash
2 Test that, when an hg push is interrupted and the remote side receives SIGPIPE,
1 Test that, when an hg push is interrupted and the remote side receives SIGPIPE,
3 the remote hg is able to successfully roll back the transaction.
2 the remote hg is able to successfully roll back the transaction.
4
3
5 $ hg init -q remote
4 $ hg init -q remote
6 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" -q ssh://user@dummy/`pwd`/remote local
5 $ hg clone -e "\"$PYTHON\" \"$RUNTESTDIR/dummyssh\"" -q ssh://user@dummy/`pwd`/remote local
7
6 $ SIGPIPE_REMOTE_DEBUG_FILE="$TESTTMP/DEBUGFILE"
8 $ pidfile=`pwd`/pidfile
7 $ SYNCFILE1="$TESTTMP/SYNCFILE1"
9 $ >$pidfile
8 $ SYNCFILE2="$TESTTMP/SYNCFILE2"
10
9 $ export SIGPIPE_REMOTE_DEBUG_FILE
11 $ script() {
10 $ export SYNCFILE1
12 > cat >"$1"
11 $ export SYNCFILE2
13 > chmod +x "$1"
12 $ PYTHONUNBUFFERED=1
14 > }
13 $ export PYTHONUNBUFFERED
15
14
16 On the remote end, run hg, piping stdout and stderr through processes that we
15 On the remote end, run hg, piping stdout and stderr through processes that we
17 know the PIDs of. We will later kill these to simulate an ssh client
16 know the PIDs of. We will later kill these to simulate an ssh client
18 disconnecting.
17 disconnecting.
19
18
20 $ killable_pipe=`pwd`/killable_pipe.sh
19 $ remotecmd="$RUNTESTDIR/testlib/sigpipe-remote.py"
21 $ script $killable_pipe <<EOF
22 > #!/usr/bin/env bash
23 > echo \$\$ >> $pidfile
24 > exec cat
25 > EOF
26
27 $ remotecmd=`pwd`/remotecmd.sh
28 $ script $remotecmd <<EOF
29 > #!/usr/bin/env bash
30 > hg "\$@" 1> >($killable_pipe) 2> >($killable_pipe >&2)
31 > EOF
32
20
33 In the pretxnchangegroup hook, kill the PIDs recorded above to simulate ssh
21 In the pretxnchangegroup hook, kill the PIDs recorded above to simulate ssh
34 disconnecting. Then exit nonzero, to force a transaction rollback.
22 disconnecting. Then exit nonzero, to force a transaction rollback.
35
23
36 $ hook_script=`pwd`/pretxnchangegroup.sh
37 $ script $hook_script <<EOF
38 > #!/usr/bin/env bash
39 > for pid in \$(cat $pidfile) ; do
40 > kill \$pid
41 > while kill -0 \$pid 2>/dev/null ; do
42 > sleep 0.1
43 > done
44 > done
45 > exit 1
46 > EOF
47
24
48 $ cat >remote/.hg/hgrc <<EOF
25 $ cat >remote/.hg/hgrc <<EOF
49 > [hooks]
26 > [hooks]
50 > pretxnchangegroup.00-break-things=$hook_script
27 > pretxnchangegroup.00-break-things="$RUNTESTDIR/testlib/wait-on-file" 10 "$SYNCFILE2" "$SYNCFILE1"
51 > pretxnchangegroup.01-output-things=echo "some remote output to be forward to the closed pipe"
28 > pretxnchangegroup.01-output-things=echo "some remote output to be forward to the closed pipe"
52 > EOF
29 > EOF
53
30
@@ -55,8 +32,24 b' disconnecting. Then exit nonzero, to for'
55 000000000000
32 000000000000
56 $ cd local
33 $ cd local
57 $ echo foo > foo ; hg commit -qAm "commit"
34 $ echo foo > foo ; hg commit -qAm "commit"
58 $ hg push -q -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --remotecmd $remotecmd 2>&1 | grep -v $killable_pipe
35 $ hg push -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --remotecmd "$remotecmd"
36 pushing to ssh://user@dummy/$TESTTMP/remote
37 searching for changes
38 remote: adding changesets
39 remote: adding manifests
40 remote: adding file changes
59 abort: stream ended unexpectedly (got 0 bytes, expected 4)
41 abort: stream ended unexpectedly (got 0 bytes, expected 4)
42 [255]
43 $ cat $SIGPIPE_REMOTE_DEBUG_FILE
44 SIGPIPE-HELPER: Starting
45 SIGPIPE-HELPER: Mercurial started
46 SIGPIPE-HELPER: Redirection in place
47 SIGPIPE-HELPER: SYNCFILE1 detected
48 SIGPIPE-HELPER: pipes closed
49 SIGPIPE-HELPER: creating SYNCFILE2
50 SIGPIPE-HELPER: Shutting down
51 SIGPIPE-HELPER: Server process terminated
52 SIGPIPE-HELPER: Shut down
60
53
61 The remote should be left in a good state
54 The remote should be left in a good state
62 $ hg --cwd ../remote tip -T '{node|short}\n'
55 $ hg --cwd ../remote tip -T '{node|short}\n'
General Comments 0
You need to be logged in to leave comments. Login now