comparison mercurial/debugcommands.py @ 36534:5faeabb07cf5

debugcommands: support for triggering push protocol The mechanism for pushing to a remote is a bit more complicated than other commands. On SSH, we wait for a positive reply from the server before we start sending the bundle payload. This commit adds a mechanism to the "command" action in `hg debugwireproto` to trigger the "push protocol" and to specify a file whose contents should be submitted as the command payload. With this new feature, we implement a handful of tests for the "unbundle" command. We try to cover various server failures and hook/output scenarios so protocol behavior is as comprehensively tested as possible. Even with so much test output, we only cover bundle1 with Python hooks. There's still a lot of test coverage that needs to be implemented. But this is certainly a good start. Because there are so many new tests, we split these tests into their own test file. In order to make output deterministic, we need to disable the doublepipe primitive. We add an option to `hg debugwireproto` to do that. Because something in the bowels of the peer does a read of stderr, we still capture read I/O from stderr. So there is test coverage of what the server emits. The tests around I/O capture some wonkiness. For example, interleaved ui.write() and ui.write_err() calls are emitted in order. However, (presumably due to buffering), print() to sys.stdout and sys.stderr aren't in order. We currently only test bundle1 because bundle2 is substantially harder to test because it is more complicated (the server responds with a stream containing a bundle2 instead of a frame). Differential Revision: https://phab.mercurial-scm.org/D2471
author Gregory Szorc <gregory.szorc@gmail.com>
date Mon, 26 Feb 2018 18:01:13 -0800
parents 097ad1079192
children 149fd142f498
comparison
equal deleted inserted replaced
36533:1a36ef7df70a 36534:5faeabb07cf5
2566 2566
2567 @command('debugwireproto', 2567 @command('debugwireproto',
2568 [ 2568 [
2569 ('', 'localssh', False, _('start an SSH server for this repo')), 2569 ('', 'localssh', False, _('start an SSH server for this repo')),
2570 ('', 'peer', '', _('construct a specific version of the peer')), 2570 ('', 'peer', '', _('construct a specific version of the peer')),
2571 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2571 ] + cmdutil.remoteopts, 2572 ] + cmdutil.remoteopts,
2572 _('[REPO]'), 2573 _('[REPO]'),
2573 optionalrepo=True) 2574 optionalrepo=True)
2574 def debugwireproto(ui, repo, **opts): 2575 def debugwireproto(ui, repo, **opts):
2575 """send wire protocol commands to a server 2576 """send wire protocol commands to a server
2584 ``--peer`` can be used to bypass the handshake protocol and construct a 2585 ``--peer`` can be used to bypass the handshake protocol and construct a
2585 peer instance using the specified class type. Valid values are ``raw``, 2586 peer instance using the specified class type. Valid values are ``raw``,
2586 ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending raw data 2587 ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending raw data
2587 payloads and don't support higher-level command actions. 2588 payloads and don't support higher-level command actions.
2588 2589
2590 ``--noreadstderr`` can be used to disable automatic reading from stderr
2591 of the peer (for SSH connections only). Disabling automatic reading of
2592 stderr is useful for making output more deterministic.
2593
2589 Commands are issued via a mini language which is specified via stdin. 2594 Commands are issued via a mini language which is specified via stdin.
2590 The language consists of individual actions to perform. An action is 2595 The language consists of individual actions to perform. An action is
2591 defined by a block. A block is defined as a line with no leading 2596 defined by a block. A block is defined as a line with no leading
2592 space followed by 0 or more lines with leading space. Blocks are 2597 space followed by 0 or more lines with leading space. Blocks are
2593 effectively a high-level command with additional metadata. 2598 effectively a high-level command with additional metadata.
2626 command listkeys 2631 command listkeys
2627 namespace bookmarks 2632 namespace bookmarks
2628 2633
2629 Values are interpreted as Python b'' literals. This allows encoding 2634 Values are interpreted as Python b'' literals. This allows encoding
2630 special byte sequences via backslash escaping. 2635 special byte sequences via backslash escaping.
2636
2637 The following arguments have special meaning:
2638
2639 ``PUSHFILE``
2640 When defined, the *push* mechanism of the peer will be used instead
2641 of the static request-response mechanism and the content of the
2642 file specified in the value of this argument will be sent as the
2643 command payload.
2644
2645 This can be used to submit a local bundle file to the remote.
2631 2646
2632 batchbegin 2647 batchbegin
2633 ---------- 2648 ----------
2634 2649
2635 Instruct the peer to begin a batched send. 2650 Instruct the peer to begin a batched send.
2710 logdata=True) 2725 logdata=True)
2711 2726
2712 # --localssh also implies the peer connection settings. 2727 # --localssh also implies the peer connection settings.
2713 2728
2714 url = 'ssh://localserver' 2729 url = 'ssh://localserver'
2730 autoreadstderr = not opts['noreadstderr']
2715 2731
2716 if opts['peer'] == 'ssh1': 2732 if opts['peer'] == 'ssh1':
2717 ui.write(_('creating ssh peer for wire protocol version 1\n')) 2733 ui.write(_('creating ssh peer for wire protocol version 1\n'))
2718 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr, 2734 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
2719 None) 2735 None, autoreadstderr=autoreadstderr)
2720 elif opts['peer'] == 'ssh2': 2736 elif opts['peer'] == 'ssh2':
2721 ui.write(_('creating ssh peer for wire protocol version 2\n')) 2737 ui.write(_('creating ssh peer for wire protocol version 2\n'))
2722 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr, 2738 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
2723 None) 2739 None, autoreadstderr=autoreadstderr)
2724 elif opts['peer'] == 'raw': 2740 elif opts['peer'] == 'raw':
2725 ui.write(_('using raw connection to peer\n')) 2741 ui.write(_('using raw connection to peer\n'))
2726 peer = None 2742 peer = None
2727 else: 2743 else:
2728 ui.write(_('creating ssh peer from handshake results\n')) 2744 ui.write(_('creating ssh peer from handshake results\n'))
2729 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr) 2745 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
2746 autoreadstderr=autoreadstderr)
2730 2747
2731 else: 2748 else:
2732 raise error.Abort(_('only --localssh is currently supported')) 2749 raise error.Abort(_('only --localssh is currently supported'))
2733 2750
2734 batchedcommands = None 2751 batchedcommands = None
2767 if batchedcommands is not None: 2784 if batchedcommands is not None:
2768 batchedcommands.append((command, args)) 2785 batchedcommands.append((command, args))
2769 continue 2786 continue
2770 2787
2771 ui.status(_('sending %s command\n') % command) 2788 ui.status(_('sending %s command\n') % command)
2772 res = peer._call(command, **args) 2789
2773 ui.status(_('response: %s\n') % util.escapedata(res)) 2790 if 'PUSHFILE' in args:
2791 with open(args['PUSHFILE'], r'rb') as fh:
2792 del args['PUSHFILE']
2793 res, output = peer._callpush(command, fh, **args)
2794 ui.status(_('result: %s\n') % util.escapedata(res))
2795 ui.status(_('remote output: %s\n') %
2796 util.escapedata(output))
2797 else:
2798 res = peer._call(command, **args)
2799 ui.status(_('response: %s\n') % util.escapedata(res))
2774 2800
2775 elif action == 'batchbegin': 2801 elif action == 'batchbegin':
2776 if batchedcommands is not None: 2802 if batchedcommands is not None:
2777 raise error.Abort(_('nested batchbegin not allowed')) 2803 raise error.Abort(_('nested batchbegin not allowed'))
2778 2804