tests/test-worker.t
author Gregory Szorc <gregory.szorc@gmail.com>
Wed, 12 Sep 2018 11:02:16 -0700
changeset 39703 bfeab472e3c0
parent 36220 4f0439981a8a
child 41535 bad59bbd9bec
permissions -rw-r--r--
localrepo: create new function for instantiating a local repo object Today, there is a single local repository class - localrepository. Its __init__ is responsible for loading the .hg/requires file and taking different actions depending on what is present. In addition, extensions may define a "reposetup" function that monkeypatches constructed repository instances, often by implementing a derived type and changing the __class__ of the repo instance. Work around alternate storage backends and partial clone has made it clear to me that shoehorning all this logic into __init__ and operating on an existing instance is too convoluted. For example, localrepository assumes revlog storage and swapping in non-revlog storage requires overriding e.g. file() to return something that isn't a revlog. I've authored various patches that either: a) teach various methods (like file()) about different states and taking the appropriate code path at run-time b) create methods/attributes/callables used for instantiating things and populating these in __init__ "a" incurs run-time performance penalties and makes code more complicated since various functions have a bunch of "if storage is X" branches. "b" makes localrepository quickly explode in complexity. My plan for tackling this problem is to make the local repository type more dynamic. Instead of a static localrepository class/type that supports all of the local repository configurations (revlogs vs other, revlogs with ellipsis, revlog v1 versus revlog v2, etc), we'll dynamically construct a type providing the implementations that are needed for the repository on disk, derived from the .hg/requires file and configuration options. The constructed repository type will be specialized and methods won't need to be taught about different implementations nor overloaded. We may also leverage this functionality for building types that don't implement all attributes. For example, the "intents" feature allows commands to declare that they are read only. By dynamically constructing a repository type, we could return a repository instance with no attributes related to mutating the repository. This could include things like a "changelog" property implementation that doesn't check whether it needs to invalidate the hidden revisions set on every access. This commit establishes a function for building a local repository instance. Future commits will start moving functionality from localrepository.__init__ to this function. Then we'll start dynamically changing the returned type depending on options that are present. This change may seem radical. But it should be fully compatible with the reposetup() model - at least for now. Differential Revision: https://phab.mercurial-scm.org/D4563

Test UI worker interaction

  $ cat > t.py <<EOF
  > from __future__ import absolute_import, print_function
  > import time
  > from mercurial import (
  >     error,
  >     registrar,
  >     ui as uimod,
  >     worker,
  > )
  > def abort(ui, args):
  >     if args[0] == 0:
  >         # by first worker for test stability
  >         raise error.Abort(b'known exception')
  >     return runme(ui, [])
  > def exc(ui, args):
  >     if args[0] == 0:
  >         # by first worker for test stability
  >         raise Exception('unknown exception')
  >     return runme(ui, [])
  > def runme(ui, args):
  >     for arg in args:
  >         ui.status(b'run\n')
  >         yield 1, arg
  >     time.sleep(0.1) # easier to trigger killworkers code path
  > functable = {
  >     b'abort': abort,
  >     b'exc': exc,
  >     b'runme': runme,
  > }
  > cmdtable = {}
  > command = registrar.command(cmdtable)
  > @command(b'test', [], b'hg test [COST] [FUNC]')
  > def t(ui, repo, cost=1.0, func=b'runme'):
  >     cost = float(cost)
  >     func = functable[func]
  >     ui.status(b'start\n')
  >     runs = worker.worker(ui, cost, func, (ui,), range(8))
  >     for n, i in runs:
  >         pass
  >     ui.status(b'done\n')
  > EOF
  $ abspath=`pwd`/t.py
  $ hg init

Run tests with worker enable by forcing a heigh cost

  $ hg --config "extensions.t=$abspath" test 100000.0
  start
  run
  run
  run
  run
  run
  run
  run
  run
  done

Run tests without worker by forcing a low cost

  $ hg --config "extensions.t=$abspath" test 0.0000001
  start
  run
  run
  run
  run
  run
  run
  run
  run
  done

#if no-windows

Known exception should be caught, but printed if --traceback is enabled

  $ hg --config "extensions.t=$abspath" --config 'worker.numcpus=8' \
  > test 100000.0 abort 2>&1
  start
  abort: known exception
  [255]

  $ hg --config "extensions.t=$abspath" --config 'worker.numcpus=8' \
  > test 100000.0 abort --traceback 2>&1 | egrep '^(SystemExit|Abort)'
  Abort: known exception
  SystemExit: 255

Traceback must be printed for unknown exceptions

  $ hg --config "extensions.t=$abspath" --config 'worker.numcpus=8' \
  > test 100000.0 exc 2>&1 | grep '^Exception'
  Exception: unknown exception

Workers should not do cleanups in all cases

  $ cat > $TESTTMP/detectcleanup.py <<EOF
  > from __future__ import absolute_import
  > import atexit
  > import os
  > import time
  > oldfork = os.fork
  > count = 0
  > parentpid = os.getpid()
  > def delayedfork():
  >     global count
  >     count += 1
  >     pid = oldfork()
  >     # make it easier to test SIGTERM hitting other workers when they have
  >     # not set up error handling yet.
  >     if count > 1 and pid == 0:
  >         time.sleep(0.1)
  >     return pid
  > os.fork = delayedfork
  > def cleanup():
  >     if os.getpid() != parentpid:
  >         os.write(1, 'should never happen\n')
  > atexit.register(cleanup)
  > EOF

  $ hg --config "extensions.t=$abspath" --config worker.numcpus=8 --config \
  > "extensions.d=$TESTTMP/detectcleanup.py" test 100000 abort
  start
  abort: known exception
  [255]

#endif