# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@octobus.net>
# Date 1568448393 -7200
#      Sat Sep 14 10:06:33 2019 +0200
# Node ID f93307cb26e7c4c5aeea0e1e9c79a65a9756ea42
# Parent  023a385a33abc5ddaa4d3833b0fed1dee67a91c4
benchmark: split de discovery test in their own module

using explicite naming scheme help us to be more agile in the source code
organisation.

We extract an `utils.exchange` module in the process.

diff --git a/benchmarks/basic_commands.py b/benchmarks/basic_commands.py
--- a/benchmarks/basic_commands.py
+++ b/benchmarks/basic_commands.py
@@ -1,37 +1,15 @@
 from __future__ import print_function
 
-import errno
 import os
 import os.path
-import pipes
 import time
-import threading
-import re
-import shutil
-import subprocess
-import stat
-import sys
-import tempfile
-import urllib
-import pipes
 
 from .utils import (
-    BaseNChangesetsTestSuite,
     BaseTestSuite,
     params_as_kwargs,
     median,
-    REPOS_DIR,
-    STRIP_VARIANTS_LIST,
-    ROLES,
-    REPO_DETAILS,
-    not_compatible_with,
 )
 
-if sys.version_info[0] == 2:
-    import Queue as queue
-else:
-    import queue
-
 class TestSuite(BaseTestSuite):
 
     timeout = 120
@@ -184,191 +162,7 @@
 #         self.hg("bundle", "--base", ":(-%d)" % (changesets+1), "/tmp/bundle.bundle")
 # 
 
-class HgWeb(object):
 
-    def __init__(self):
-        super(HgWeb, self).__init__()
-        self.queue = queue.Queue()
-        self.proc = None
-        self.thread = None
-
-    def start(self, hgpath, environ):
-        config = os.path.abspath(os.path.join(
-            os.path.dirname(__file__), os.pardir, 'hgweb.config'))
-        hgweb_cmd = [
-            hgpath, 'serve', '--cwd', REPOS_DIR,
-            '-a', 'localhost', '-p', '0',
-            '--config', 'web.push_ssl=False',
-            '--config', 'web.allow_push=*',
-            '--webdir-conf', config]
-        self.proc = subprocess.Popen(hgweb_cmd, env=environ,
-                                     stdout=subprocess.PIPE)
-        # we have to read output in a thread to avoid deadlocks
-        self.thread = threading.Thread(
-            target=self._enqueue, args=(self.queue, self.proc.stdout))
-        self.thread.daemon = True
-        self.thread.start()
-        # wait the server to be started
-        statusline = self.queue.get()
-        if not statusline:
-            self.stop()
-            raise RuntimeError('hg serve has crashed')
-        return re.search(':(\d+)', statusline).groups()[0]
-
-    @staticmethod
-    def _enqueue(queue, fd):
-        while True:
-            data = fd.readline()
-            if not data:
-                break
-            queue.put(data)
-        queue.put(None)
-
-    def stop(self):
-        self.proc.kill()
-        self.proc.wait()
-        self.thread.join()
-
-
-class BaseExchangeMixin(object):
-
-    # this help recovering from failure during setup
-    _hgserve = None
-
-    def _remote_path_cmd(self, path):
-        if self.repo_type == 'local':
-            return [path]
-        elif self.repo_type == 'ssh':
-            with open('hg_wrapper', 'wb') as f:
-                f.write('#!/bin/sh\nexec env -i {} {} $*\n'.format(
-                    ' '.join(['{}={}'.format(k, pipes.quote(v))
-                              for k, v in self.environ.items()]),
-                    os.path.abspath(self.hgpath)))
-            st = os.stat('hg_wrapper')
-            os.chmod('hg_wrapper', st.st_mode | stat.S_IEXEC)
-            return [
-                '--remotecmd', os.path.abspath('hg_wrapper'),
-                'ssh://localhost/{}'.format(os.path.abspath(path))]
-        elif self.repo_type == 'http':
-            path = os.path.abspath(path)
-            repo_dir = os.path.abspath(REPOS_DIR)
-            assert path.startswith(REPOS_DIR), path
-            return ['http://localhost:{}/{}'.format(
-                self.hgport, path[len(repo_dir) + 1:])]
-        else:
-            raise NotImplementedError
-
-    def _setup_repo_type(self, repo_type):
-        """setup a hgweb server if we have to"""
-        self._hgserve = None
-        if repo_type == 'http' and self.get_asv_rev() in self.get_skip()['hgweb']:
-            raise NotImplementedError
-        self.repo_type = repo_type
-        if repo_type == 'http':
-            self._hgserve = HgWeb()
-            self.hgport = self._hgserve.start(self.hgpath, self.environ)
-
-    def _teardown_repo_type(self):
-        if self._hgserve is not None:
-            self._hgserve.stop()
-            self._hgserve = None
-
-    def _setup_revset(self, revset):
-        """If the operation target a specific revision, resolve it beforehand"""
-        if revset is not None:
-            self.rev = self.hg('identify', '-i', '-r', revset).strip()
-        else:
-            self.rev = None
-
-    def finalize_teardown(self, exc_info, current_params):
-        self._teardown_repo_type()
-
-
-class classproperty(object):
-    def __init__(self, f):
-        self.f = f
-    def __get__(self, obj, owner):
-        return self.f(owner)
-
-def setup_role(cls):
-    """install the right partial variants for the configured action"""
-    cls.params = cls.params[:]
-    cls.params[cls._partials_idx] = list(sorted(cls.role_data))
-    return cls
-
-class BaseExchangeTimeSuite(BaseExchangeMixin, BaseTestSuite):
-    # the exchange action we measure
-    role_action = None
-    # the subtypes of this action we measure
-    role_subtype = None
-
-    param_names = BaseTestSuite.param_names + [
-        'repo_type', 'strip', 'revset']
-
-    params = BaseTestSuite.params + [['local', 'ssh', 'http']]
-    _partials_idx = len(params)
-    params += [STRIP_VARIANTS_LIST]
-    params += [[None, 'tip']]
-    timeout = 1800
-
-    @classproperty
-    def role_data(cls):
-        """{"partial-key" -> {"repo-key" -> {data}} map for the current role"""
-        if cls.role_action is None:
-            return None
-        if cls.role_subtype is None:
-            return None
-        return ROLES.get(cls.role_action, {}).get(cls.role_subtype, {})
-
-    def run(self, local_repo, command, remote_repo, expected_return_code=None):
-        if not isinstance(command, (list, tuple)):
-            command = [command]
-        cmd = ['--cwd', pipes.quote(local_repo)] + command
-        cmd.extend(self._remote_path_cmd(remote_repo))
-        if self.rev:
-            cmd.extend(['-r', self.rev])
-        if expected_return_code is None:
-            expected_return_code = 1 if self.partial_key == "same" else 0
-        self.hg(*cmd, expected_return_code=expected_return_code)
-
-    @params_as_kwargs
-    def setup(self, repo, repo_type, strip, revset, **kwargs):
-        self.partial_key = strip
-        super(BaseExchangeTimeSuite, self).setup(repo, **kwargs)
-        self._setup_repo_type(repo_type)
-        self._setup_revset(revset)
-
-    @property
-    def clone_path(self):
-        return self.repo_path_from_id(self.partial_key)
-
-    def repo_path_from_id(self, partial_id):
-        """Return the absolute path for a given partial
-
-        "reference" is a special value that means the original repository.
-        """
-        if partial_id == 'reference':
-            return self.repo_path
-        suffix = urllib.quote_plus(partial_id)
-        # We need to use the repo name here because the repo doesn't contains
-        # the hash
-        partial_name = '{}-partial-{}'.format(self.repo_name, suffix)
-        return os.path.join(REPOS_DIR, 'partial-references', partial_name)
-
-    def teardown(self, *args, **kwargs):
-        self._teardown_repo_type()
-
-    def _rsync(self, src, dst):
-        cmd = [
-            'rsync',
-            '--inplace',
-            '--no-whole-file',
-            '-aH',
-            '--delete',
-            '{}/'.format(src),
-            dst,
-        ]
-        self.check_output(*cmd)
 
 
 # class ExchangeTimeSuite(BaseExchangeTimeSuite):
@@ -380,73 +174,6 @@
 #         self.run(self.repo_path, 'outgoing', self.clone_path)
 
 
-# https://bz.mercurial-scm.org/show_bug.cgi?id=5851
-# long timeout and process leak
-not_broken_hgweb = not_compatible_with(
-    revset="f0a851542a05::877185de62^",
-    filter_fn=lambda kwargs, current_version: kwargs['repo_type'] == 'http'
-)
-
-
-class BaseDiscoveryTimeSuite(BaseExchangeTimeSuite):
-    # debugdiscovery does not support revset argument
-    params = BaseTestSuite.params + [
-        ['local', 'ssh', 'http'],
-        STRIP_VARIANTS_LIST,
-        [None]]
-
-    def _track_discovery(self, *args, **kwargs):
-        data = self.role_data.get(kwargs['strip'], {})
-        data = data.get(self.repo_name)
-        if data is None:
-            raise NotImplementedError("no roles data for this partials' key")
-        source = self.repo_path_from_id(data['source'])
-        target = self.repo_path_from_id(data['target'])
-        return self.perfext('perfdiscovery', '--repository', source, target)
-
-@setup_role
-class DiscoveryIdenticalTimeSuite(BaseDiscoveryTimeSuite):
-    role_action = 'discovery'
-    role_subtype = 'identical'
-
-    @params_as_kwargs
-    @not_broken_hgweb
-    def track_identical(self, *args, **kwargs):
-        return self._track_discovery(self, *args, **kwargs)
-    track_identical.benchmark_name = 'exchange.discovery.changesets.track_identical'
-
-@setup_role
-class DiscoverySubsetTimeSuite(BaseDiscoveryTimeSuite):
-    role_action = 'discovery'
-    role_subtype = 'subset'
-
-    @params_as_kwargs
-    @not_broken_hgweb
-    def track_discovery_subset(self, *args, **kwargs):
-        return self._track_discovery(self, *args, **kwargs)
-    track_discovery_subset.benchmark_name = 'exchange.discovery.changesets.track_subset'
-
-@setup_role
-class DiscoverySupersetTimeSuite(BaseDiscoveryTimeSuite):
-    role_action = 'discovery'
-    role_subtype = 'superset'
-
-    @params_as_kwargs
-    @not_broken_hgweb
-    def track_discovery_superset(self, *args, **kwargs):
-        return self._track_discovery(self, *args, **kwargs)
-    track_discovery_superset.benchmark_name = 'exchange.discovery.changesets.track_superset'
-
-@setup_role
-class DiscoveryStandardTimeSuite(BaseDiscoveryTimeSuite):
-    role_action = 'discovery'
-    role_subtype = 'balanced'
-
-    @params_as_kwargs
-    @not_broken_hgweb
-    def track_balanced(self, *args, **kwargs):
-        return self._track_discovery(self, *args, **kwargs)
-    track_balanced.benchmark_name = 'exchange.discovery.changesets.track_balanced'
 
 # class UnbundleTimeSuite(BaseExchangeTimeSuite):
 #     params = BaseTestSuite.params + [
diff --git a/benchmarks/discovery.py b/benchmarks/discovery.py
new file mode 100644
--- /dev/null
+++ b/benchmarks/discovery.py
@@ -0,0 +1,73 @@
+from __future__ import print_function
+
+from .utils import (
+    BaseTestSuite,
+    params_as_kwargs,
+    STRIP_VARIANTS_LIST,
+)
+
+from .utils.exchange import (
+    BaseExchangeTimeSuite,
+    not_broken_hgweb,
+    setup_role,
+)
+
+class BaseDiscoveryTimeSuite(BaseExchangeTimeSuite):
+    # debugdiscovery does not support revset argument
+    params = BaseTestSuite.params + [
+        ['local', 'ssh', 'http'],
+        STRIP_VARIANTS_LIST,
+        [None]]
+
+    def _track_discovery(self, *args, **kwargs):
+        data = self.role_data.get(kwargs['strip'], {})
+        data = data.get(self.repo_name)
+        if data is None:
+            raise NotImplementedError("no roles data for this partials' key")
+        source = self.repo_path_from_id(data['source'])
+        target = self.repo_path_from_id(data['target'])
+        return self.perfext('perfdiscovery', '--repository', source, target)
+
+@setup_role
+class DiscoveryIdenticalTimeSuite(BaseDiscoveryTimeSuite):
+    role_action = 'discovery'
+    role_subtype = 'identical'
+
+    @params_as_kwargs
+    @not_broken_hgweb
+    def track_identical(self, *args, **kwargs):
+        return self._track_discovery(self, *args, **kwargs)
+    track_identical.benchmark_name = 'exchange.discovery.changesets.track_identical'
+
+@setup_role
+class DiscoverySubsetTimeSuite(BaseDiscoveryTimeSuite):
+    role_action = 'discovery'
+    role_subtype = 'subset'
+
+    @params_as_kwargs
+    @not_broken_hgweb
+    def track_discovery_subset(self, *args, **kwargs):
+        return self._track_discovery(self, *args, **kwargs)
+    track_discovery_subset.benchmark_name = 'exchange.discovery.changesets.track_subset'
+
+@setup_role
+class DiscoverySupersetTimeSuite(BaseDiscoveryTimeSuite):
+    role_action = 'discovery'
+    role_subtype = 'superset'
+
+    @params_as_kwargs
+    @not_broken_hgweb
+    def track_discovery_superset(self, *args, **kwargs):
+        return self._track_discovery(self, *args, **kwargs)
+    track_discovery_superset.benchmark_name = 'exchange.discovery.changesets.track_superset'
+
+@setup_role
+class DiscoveryStandardTimeSuite(BaseDiscoveryTimeSuite):
+    role_action = 'discovery'
+    role_subtype = 'balanced'
+
+    @params_as_kwargs
+    @not_broken_hgweb
+    def track_balanced(self, *args, **kwargs):
+        return self._track_discovery(self, *args, **kwargs)
+    track_balanced.benchmark_name = 'exchange.discovery.changesets.track_balanced'
diff --git a/benchmarks/utils/exchange.py b/benchmarks/utils/exchange.py
new file mode 100644
--- /dev/null
+++ b/benchmarks/utils/exchange.py
@@ -0,0 +1,218 @@
+from __future__ import print_function
+
+import os
+import os.path
+import pipes
+import threading
+import re
+import subprocess
+import stat
+import sys
+import urllib
+
+from . import (
+    BaseTestSuite,
+    params_as_kwargs,
+    REPOS_DIR,
+    STRIP_VARIANTS_LIST,
+    ROLES,
+    BASEDIR,
+    not_compatible_with,
+)
+
+if sys.version_info[0] == 2:
+    import Queue as queue
+else:
+    import queue
+
+class classproperty(object):
+    def __init__(self, f):
+        self.f = f
+    def __get__(self, obj, owner):
+        return self.f(owner)
+
+def setup_role(cls):
+    """install the right partial variants for the configured action"""
+    cls.params = cls.params[:]
+    cls.params[cls._partials_idx] = list(sorted(cls.role_data))
+    return cls
+
+# https://bz.mercurial-scm.org/show_bug.cgi?id=5851
+# long timeout and process leak
+not_broken_hgweb = not_compatible_with(
+    revset="f0a851542a05::877185de62^",
+    filter_fn=lambda kwargs, current_version: kwargs['repo_type'] == 'http'
+)
+
+class HgWeb(object):
+
+    def __init__(self):
+        super(HgWeb, self).__init__()
+        self.queue = queue.Queue()
+        self.proc = None
+        self.thread = None
+
+    def start(self, hgpath, environ):
+        config = os.path.abspath(os.path.join(BASEDIR, 'hgweb.config'))
+        hgweb_cmd = [
+            hgpath, 'serve', '--cwd', REPOS_DIR,
+            '-a', 'localhost', '-p', '0',
+            '--config', 'web.push_ssl=False',
+            '--config', 'web.allow_push=*',
+            '--webdir-conf', config]
+        self.proc = subprocess.Popen(hgweb_cmd, env=environ,
+                                     stdout=subprocess.PIPE)
+        # we have to read output in a thread to avoid deadlocks
+        self.thread = threading.Thread(
+            target=self._enqueue, args=(self.queue, self.proc.stdout))
+        self.thread.daemon = True
+        self.thread.start()
+        # wait the server to be started
+        statusline = self.queue.get()
+        if not statusline:
+            self.stop()
+            raise RuntimeError('hg serve has crashed')
+        return re.search(':(\d+)', statusline).groups()[0]
+
+    @staticmethod
+    def _enqueue(queue, fd):
+        while True:
+            data = fd.readline()
+            if not data:
+                break
+            queue.put(data)
+        queue.put(None)
+
+    def stop(self):
+        self.proc.kill()
+        self.proc.wait()
+        self.thread.join()
+
+
+class BaseExchangeMixin(object):
+
+    # this help recovering from failure during setup
+    _hgserve = None
+
+    def _remote_path_cmd(self, path):
+        if self.repo_type == 'local':
+            return [path]
+        elif self.repo_type == 'ssh':
+            with open('hg_wrapper', 'wb') as f:
+                f.write('#!/bin/sh\nexec env -i {} {} $*\n'.format(
+                    ' '.join(['{}={}'.format(k, pipes.quote(v))
+                              for k, v in self.environ.items()]),
+                    os.path.abspath(self.hgpath)))
+            st = os.stat('hg_wrapper')
+            os.chmod('hg_wrapper', st.st_mode | stat.S_IEXEC)
+            return [
+                '--remotecmd', os.path.abspath('hg_wrapper'),
+                'ssh://localhost/{}'.format(os.path.abspath(path))]
+        elif self.repo_type == 'http':
+            path = os.path.abspath(path)
+            repo_dir = os.path.abspath(REPOS_DIR)
+            assert path.startswith(REPOS_DIR), path
+            return ['http://localhost:{}/{}'.format(
+                self.hgport, path[len(repo_dir) + 1:])]
+        else:
+            raise NotImplementedError
+
+    def _setup_repo_type(self, repo_type):
+        """setup a hgweb server if we have to"""
+        self._hgserve = None
+        if repo_type == 'http' and self.get_asv_rev() in self.get_skip()['hgweb']:
+            raise NotImplementedError
+        self.repo_type = repo_type
+        if repo_type == 'http':
+            self._hgserve = HgWeb()
+            self.hgport = self._hgserve.start(self.hgpath, self.environ)
+
+    def _teardown_repo_type(self):
+        if self._hgserve is not None:
+            self._hgserve.stop()
+            self._hgserve = None
+
+    def _setup_revset(self, revset):
+        """If the operation target a specific revision, resolve it beforehand"""
+        if revset is not None:
+            self.rev = self.hg('identify', '-i', '-r', revset).strip()
+        else:
+            self.rev = None
+
+    def finalize_teardown(self, exc_info, current_params):
+        self._teardown_repo_type()
+
+
+class BaseExchangeTimeSuite(BaseExchangeMixin, BaseTestSuite):
+    # the exchange action we measure
+    role_action = None
+    # the subtypes of this action we measure
+    role_subtype = None
+
+    param_names = BaseTestSuite.param_names + [
+        'repo_type', 'strip', 'revset']
+
+    params = BaseTestSuite.params + [['local', 'ssh', 'http']]
+    _partials_idx = len(params)
+    params += [STRIP_VARIANTS_LIST]
+    params += [[None, 'tip']]
+    timeout = 1800
+
+    @classproperty
+    def role_data(cls):
+        """{"partial-key" -> {"repo-key" -> {data}} map for the current role"""
+        if cls.role_action is None:
+            return None
+        if cls.role_subtype is None:
+            return None
+        return ROLES.get(cls.role_action, {}).get(cls.role_subtype, {})
+
+    def run(self, local_repo, command, remote_repo, expected_return_code=None):
+        if not isinstance(command, (list, tuple)):
+            command = [command]
+        cmd = ['--cwd', pipes.quote(local_repo)] + command
+        cmd.extend(self._remote_path_cmd(remote_repo))
+        if self.rev:
+            cmd.extend(['-r', self.rev])
+        if expected_return_code is None:
+            expected_return_code = 1 if self.partial_key == "same" else 0
+        self.hg(*cmd, expected_return_code=expected_return_code)
+
+    @params_as_kwargs
+    def setup(self, repo, repo_type, strip, revset, **kwargs):
+        self.partial_key = strip
+        super(BaseExchangeTimeSuite, self).setup(repo, **kwargs)
+        self._setup_repo_type(repo_type)
+        self._setup_revset(revset)
+
+    @property
+    def clone_path(self):
+        return self.repo_path_from_id(self.partial_key)
+
+    def repo_path_from_id(self, partial_id):
+        """Return the absolute path for a given partial
+
+        "reference" is a special value that means the original repository.
+        """
+        if partial_id == 'reference':
+            return self.repo_path
+        suffix = urllib.quote_plus(partial_id)
+        # We need to use the repo name here because the repo doesn't contains
+        # the hash
+        partial_name = '{}-partial-{}'.format(self.repo_name, suffix)
+        return os.path.join(REPOS_DIR, 'partial-references', partial_name)
+
+    def teardown(self, *args, **kwargs):
+        self._teardown_repo_type()
+
+    def _rsync(self, src, dst):
+        cmd = [
+            'rsync',
+            '--inplace',
+            '--no-whole-file',
+            '-aH',
+            '--delete',
+            '{}/'.format(src),
+            dst,
+        ]
+        self.check_output(*cmd)