run-tests: add --watchman and --with-watchman flags

Summary:
Similar to chg. Add flags to run tests with watchman. This is mostly moving
features from `fsmonitor-run-tests.py`. The blacklist is converted to
`#require no-fsmonitor`.

Reviewed By: phillco

Differential Revision: D8434518

fbshipit-source-id: a8512cd71c1171e9037f36dbef195f1e6210f27e
This commit is contained in:
Jun Wu 2018-06-14 18:40:53 -07:00 committed by Facebook Github Bot
parent 108668759f
commit 64c3b8c2ab
18 changed files with 129 additions and 191 deletions

View File

@ -1,28 +0,0 @@
# Blacklist for a full testsuite run with fsmonitor enabled.
# Used by fsmonitor-run-tests.
# The following tests all fail because they either use extensions that conflict
# with fsmonitor, use subrepositories, or don't anticipate the extra file in
# the .hg directory that fsmonitor adds.
#### mainly testing eol extension
test-eol-add.t
test-eol-clone.t
test-eol-hook.t
test-eol-patch.t
test-eol-tag.t
test-eol-update.t
test-eol.t
test-eolfilename.t
#### mainly testing nested repositories
test-nested-repo.t
test-push-warn.t
test-subrepo-deep-nested-change.t
test-subrepo-recursion.t
test-subrepo.t
#### fixing these seems redundant, because these don't focus on
#### operations in the working directory or .hg
test-debugextensions.t
test-extension.t
test-help.t

View File

@ -1,147 +0,0 @@
#!/usr/bin/env python
# fsmonitor-run-tests.py - Run Mercurial tests with fsmonitor enabled
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
#
# This is a wrapper around run-tests.py that spins up an isolated instance of
# Watchman and runs the Mercurial tests against it. This ensures that the global
# version of Watchman isn't affected by anything this test does.
from __future__ import absolute_import, print_function
import argparse
import contextlib
import json
import os
import shutil
import subprocess
import sys
import tempfile
import uuid
osenvironb = getattr(os, "environb", os.environ)
if sys.version_info > (3, 5, 0):
PYTHON3 = True
xrange = range # we use xrange in one place, and we'd rather not use range
def _bytespath(p):
return p.encode("utf-8")
elif sys.version_info >= (3, 0, 0):
print(
"%s is only supported on Python 3.5+ and 2.7, not %s"
% (sys.argv[0], ".".join(str(v) for v in sys.version_info[:3]))
)
sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
else:
PYTHON3 = False
# In python 2.x, path operations are generally done using
# bytestrings by default, so we don't have to do any extra
# fiddling there. We define the wrapper functions anyway just to
# help keep code consistent between platforms.
def _bytespath(p):
return p
def getparser():
"""Obtain the argument parser used by the CLI."""
parser = argparse.ArgumentParser(
description="Run tests with fsmonitor enabled.",
epilog="Unrecognized options are passed to run-tests.py.",
)
# - keep these sorted
# - none of these options should conflict with any in run-tests.py
parser.add_argument(
"--keep-fsmonitor-tmpdir",
action="store_true",
help="keep temporary directory with fsmonitor state",
)
parser.add_argument(
"--watchman",
help="location of watchman binary (default: watchman in PATH)",
default="watchman",
)
return parser
@contextlib.contextmanager
def watchman(args):
basedir = tempfile.mkdtemp(prefix="hg-fsmonitor")
try:
# Much of this configuration is borrowed from Watchman's test harness.
cfgfile = os.path.join(basedir, "config.json")
# TODO: allow setting a config
with open(cfgfile, "w") as f:
f.write(json.dumps({}))
logfile = os.path.join(basedir, "log")
clilogfile = os.path.join(basedir, "cli-log")
if os.name == "nt":
sockfile = "\\\\.\\pipe\\watchman-test-%s" % uuid.uuid4().hex
else:
sockfile = os.path.join(basedir, "sock")
pidfile = os.path.join(basedir, "pid")
statefile = os.path.join(basedir, "state")
argv = [
args.watchman,
"--sockname",
sockfile,
"--logfile",
logfile,
"--pidfile",
pidfile,
"--statefile",
statefile,
"--foreground",
"--log-level=2", # debug logging for watchman
]
envb = osenvironb.copy()
envb[b"WATCHMAN_CONFIG_FILE"] = _bytespath(cfgfile)
with open(clilogfile, "wb") as f:
proc = subprocess.Popen(argv, env=envb, stdin=None, stdout=f, stderr=f)
try:
yield sockfile
finally:
proc.terminate()
proc.kill()
finally:
if args.keep_fsmonitor_tmpdir:
print("fsmonitor dir available at %s" % basedir)
else:
shutil.rmtree(basedir, ignore_errors=True)
def run():
parser = getparser()
args, runtestsargv = parser.parse_known_args()
with watchman(args) as sockfile:
osenvironb[b"WATCHMAN_SOCK"] = _bytespath(sockfile)
# Indicate to hghave that we're running with fsmonitor enabled.
osenvironb[b"HGFSMONITOR_TESTS"] = b"1"
runtestdir = os.path.dirname(__file__)
runtests = os.path.join(runtestdir, "run-tests.py")
blacklist = os.path.join(runtestdir, "blacklists", "fsmonitor")
runtestsargv.insert(0, runtests)
runtestsargv.extend(
["--extra-config", "extensions.fsmonitor=", "--blacklist", blacklist]
)
return subprocess.call(runtestsargv)
if __name__ == "__main__":
sys.exit(run())

View File

@ -65,6 +65,7 @@ import tempfile
import threading import threading
import time import time
import unittest import unittest
import uuid
import xml.dom.minidom as minidom import xml.dom.minidom as minidom
@ -362,7 +363,7 @@ def getparser():
harness.add_argument( harness.add_argument(
"--bisect-repo", "--bisect-repo",
metavar="bisect_repo", metavar="bisect_repo",
help=("Path of a repo to bisect. Use together with " "--known-good-rev"), help=("Path of a repo to bisect. Use together with --known-good-rev"),
) )
harness.add_argument( harness.add_argument(
"-d", "-d",
@ -448,7 +449,7 @@ def getparser():
) )
harness.add_argument( harness.add_argument(
"--tmpdir", "--tmpdir",
help="run tests in the given temporary directory" " (implies --keep-tmpdir)", help="run tests in the given temporary directory (implies --keep-tmpdir)",
) )
harness.add_argument( harness.add_argument(
"-v", "--verbose", action="store_true", help="output verbose messages" "-v", "--verbose", action="store_true", help="output verbose messages"
@ -458,6 +459,9 @@ def getparser():
hgconf.add_argument( hgconf.add_argument(
"--chg", action="store_true", help="install and use chg wrapper in place of hg" "--chg", action="store_true", help="install and use chg wrapper in place of hg"
) )
hgconf.add_argument(
"--watchman", action="store_true", help="shortcut for --with-watchman=watchman"
)
hgconf.add_argument("--compiler", help="compiler to build with") hgconf.add_argument("--compiler", help="compiler to build with")
hgconf.add_argument( hgconf.add_argument(
"--extra-config-opt", "--extra-config-opt",
@ -509,14 +513,17 @@ def getparser():
hgconf.add_argument( hgconf.add_argument(
"--with-hg", "--with-hg",
metavar="HG", metavar="HG",
help="test using specified hg script rather than a " "temporary installation", help="test using specified hg script rather than a temporary installation",
)
hgconf.add_argument(
"--with-watchman", metavar="WATCHMAN", help="test using specified watchman"
) )
# This option should be deleted once test-check-py3-compat.t and other # This option should be deleted once test-check-py3-compat.t and other
# Python 3 tests run with Python 3. # Python 3 tests run with Python 3.
hgconf.add_argument( hgconf.add_argument(
"--with-python3", "--with-python3",
metavar="PYTHON3", metavar="PYTHON3",
help="Python 3 interpreter (if running under Python 2)" " (TEMPORARY)", help="Python 3 interpreter (if running under Python 2) (TEMPORARY)",
) )
reporting = parser.add_argument_group("Results Reporting") reporting = parser.add_argument_group("Results Reporting")
@ -599,7 +606,7 @@ def parseargs(args, parser):
binpath = os.path.join(reporootdir, relpath) binpath = os.path.join(reporootdir, relpath)
if os.name != "nt" and not os.access(binpath, os.X_OK): if os.name != "nt" and not os.access(binpath, os.X_OK):
parser.error( parser.error(
"--local specified, but %r not found or " "not executable" % binpath "--local specified, but %r not found or not executable" % binpath
) )
setattr(options, attr, binpath) setattr(options, attr, binpath)
@ -618,10 +625,15 @@ def parseargs(args, parser):
"--chg does not work when --with-hg is specified " "--chg does not work when --with-hg is specified "
"(use --with-chg instead)" "(use --with-chg instead)"
) )
if options.watchman and options.with_watchman:
parser.error(
"--watchman does not work when --with-watchman is specified "
"(use --with-watchman instead)"
)
if options.color == "always" and not pygmentspresent: if options.color == "always" and not pygmentspresent:
sys.stderr.write( sys.stderr.write(
"warning: --color=always ignored because " "pygments is not installed\n" "warning: --color=always ignored because pygments is not installed\n"
) )
if options.bisect_repo and not options.known_good_rev: if options.bisect_repo and not options.known_good_rev:
@ -647,12 +659,10 @@ def parseargs(args, parser):
if options.anycoverage and options.local: if options.anycoverage and options.local:
# this needs some path mangling somewhere, I guess # this needs some path mangling somewhere, I guess
parser.error("sorry, coverage options do not work when --local " "is specified") parser.error("sorry, coverage options do not work when --local is specified")
if options.anycoverage and options.with_hg: if options.anycoverage and options.with_hg:
parser.error( parser.error("sorry, coverage options do not work when --with-hg is specified")
"sorry, coverage options do not work when --with-hg " "is specified"
)
global verbose global verbose
if options.verbose: if options.verbose:
@ -677,9 +687,7 @@ def parseargs(args, parser):
parser.error("--py3k-warnings can only be used on Python 2.7") parser.error("--py3k-warnings can only be used on Python 2.7")
if options.with_python3: if options.with_python3:
if PYTHON3: if PYTHON3:
parser.error( parser.error("--with-python3 cannot be used when executing with Python 3")
"--with-python3 cannot be used when executing with " "Python 3"
)
options.with_python3 = canonpath(options.with_python3) options.with_python3 = canonpath(options.with_python3)
# Verify Python3 executable is acceptable. # Verify Python3 executable is acceptable.
@ -697,7 +705,7 @@ def parseargs(args, parser):
vers = version.LooseVersion(out[len("Python ") :]) vers = version.LooseVersion(out[len("Python ") :])
if vers < version.LooseVersion("3.5.0"): if vers < version.LooseVersion("3.5.0"):
parser.error( parser.error(
"--with-python3 version must be 3.5.0 or greater; " "got %s" % out "--with-python3 version must be 3.5.0 or greater; got %s" % out
) )
if options.blacklist: if options.blacklist:
@ -854,6 +862,7 @@ class Test(unittest.TestCase):
slowtimeout=None, slowtimeout=None,
usechg=False, usechg=False,
useipv6=False, useipv6=False,
watchman=None,
): ):
"""Create a test from parameters. """Create a test from parameters.
@ -916,6 +925,7 @@ class Test(unittest.TestCase):
self._hgcommand = hgcommand or b"hg" self._hgcommand = hgcommand or b"hg"
self._usechg = usechg self._usechg = usechg
self._useipv6 = useipv6 self._useipv6 = useipv6
self._watchman = watchman
self._aborted = False self._aborted = False
self._daemonpids = [] self._daemonpids = []
@ -983,6 +993,49 @@ class Test(unittest.TestCase):
self._chgsockdir = os.path.join(self._threadtmp, b"%s.chgsock" % name) self._chgsockdir = os.path.join(self._threadtmp, b"%s.chgsock" % name)
os.mkdir(self._chgsockdir) os.mkdir(self._chgsockdir)
if self._watchman:
self._watchmandir = os.path.join(self._threadtmp, b"%s.watchman" % name)
os.mkdir(self._watchmandir)
cfgfile = os.path.join(self._watchmandir, b"config.json")
if os.name == "nt":
sockfile = "\\\\.\\pipe\\watchman-test-%s" % uuid.uuid4().hex
else:
sockfile = os.path.join(self._watchmandir, b"sock")
self._watchmansock = sockfile
clilogfile = os.path.join(self._watchmandir, "cli-log")
logfile = os.path.join(self._watchmandir, b"log")
pidfile = os.path.join(self._watchmandir, b"pid")
statefile = os.path.join(self._watchmandir, b"state")
with open(cfgfile, "w") as f:
f.write(json.dumps({}))
envb = osenvironb.copy()
envb[b"WATCHMAN_CONFIG_FILE"] = _bytespath(cfgfile)
envb[b"WATCHMAN_SOCK"] = _bytespath(sockfile)
argv = [
self._watchman,
"--sockname",
sockfile,
"--logfile",
logfile,
"--pidfile",
pidfile,
"--statefile",
statefile,
"--foreground",
"--log-level=2", # debug logging for watchman
]
with open(clilogfile, "wb") as f:
self._watchmanproc = subprocess.Popen(
argv, env=envb, stdin=None, stdout=f, stderr=f
)
def run(self, result): def run(self, result):
"""Run this test and report results against a TestResult instance.""" """Run this test and report results against a TestResult instance."""
# This function is extremely similar to unittest.TestCase.run(). Once # This function is extremely similar to unittest.TestCase.run(). Once
@ -1130,6 +1183,19 @@ class Test(unittest.TestCase):
# files are deleted # files are deleted
shutil.rmtree(self._chgsockdir, True) shutil.rmtree(self._chgsockdir, True)
if self._watchman:
try:
self._watchmanproc.terminate()
self._watchmanproc.kill()
if self._keeptmpdir:
log(
"Keeping watchman dir: %s\n" % self._watchmandir.decode("utf-8")
)
else:
shutil.rmtree(self._watchmandir, ignore_errors=True)
except Exception:
pass
if ( if (
(self._ret != 0 or self._out != self._refout) (self._ret != 0 or self._out != self._refout)
and not self._skipped and not self._skipped
@ -1289,6 +1355,10 @@ class Test(unittest.TestCase):
if self._usechg: if self._usechg:
env["CHGSOCKNAME"] = os.path.join(self._chgsockdir, b"server") env["CHGSOCKNAME"] = os.path.join(self._chgsockdir, b"server")
if self._watchman:
env["WATCHMAN_SOCK"] = self._watchmansock
env["HGFSMONITOR_TESTS"] = "1"
return env return env
def _createhgrc(self, path): def _createhgrc(self, path):
@ -1307,6 +1377,8 @@ class Test(unittest.TestCase):
hgrc.write( hgrc.write(
b"usercache = %s\n" % (os.path.join(self._testtmp, b".cache/lfs")) b"usercache = %s\n" % (os.path.join(self._testtmp, b".cache/lfs"))
) )
if self._watchman:
hgrc.write(b"[extensions]\nfsmonitor=\n")
hgrc.write(b"[web]\n") hgrc.write(b"[web]\n")
hgrc.write(b"address = localhost\n") hgrc.write(b"address = localhost\n")
hgrc.write(b"ipv6 = %s\n" % str(self._useipv6).encode("ascii")) hgrc.write(b"ipv6 = %s\n" % str(self._useipv6).encode("ascii"))
@ -1314,7 +1386,7 @@ class Test(unittest.TestCase):
for opt in self._extraconfigopts: for opt in self._extraconfigopts:
section, key = opt.encode("utf-8").split(b".", 1) section, key = opt.encode("utf-8").split(b".", 1)
assert b"=" in key, ( assert b"=" in key, (
"extra config opt %s must " "have an = for assignment" % opt "extra config opt %s must have an = for assignment" % opt
) )
hgrc.write(b"[%s]\n%s\n" % (section, key)) hgrc.write(b"[%s]\n%s\n" % (section, key))
@ -2024,7 +2096,7 @@ class TestResult(unittest._TextTestResult):
if self._options.interactive: if self._options.interactive:
if test.readrefout() != expected: if test.readrefout() != expected:
self.stream.write( self.stream.write(
"Reference output has changed (run again to prompt " "changes)" "Reference output has changed (run again to prompt changes)"
) )
else: else:
self.stream.write("Accept this change? [n] ") self.stream.write("Accept this change? [n] ")
@ -2745,6 +2817,14 @@ class TestRunner(object):
elif self.options.with_chg: elif self.options.with_chg:
chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg)) chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
self._hgcommand = os.path.basename(self.options.with_chg) self._hgcommand = os.path.basename(self.options.with_chg)
if self.options.with_watchman or self.options.watchman:
self._watchman = self.options.with_watchman or "watchman"
osenvironb[b"HGFSMONITOR_TESTS"] = b"1"
else:
osenvironb[b"BINDIR"] = self._bindir
self._watchman = None
if b"HGFSMONITOR_TESTS" in osenvironb:
del osenvironb[b"HGFSMONITOR_TESTS"]
osenvironb[b"BINDIR"] = self._bindir osenvironb[b"BINDIR"] = self._bindir
osenvironb[b"PYTHON"] = PYTHON osenvironb[b"PYTHON"] = PYTHON
@ -2822,6 +2902,8 @@ class TestRunner(object):
vlog("# Using HGTMP", self._hgtmp) vlog("# Using HGTMP", self._hgtmp)
vlog("# Using PATH", os.environ["PATH"]) vlog("# Using PATH", os.environ["PATH"])
vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH]) vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
if self._watchman:
vlog("# Using watchman", self._watchman)
vlog("# Writing to directory", self._outputdir) vlog("# Writing to directory", self._outputdir)
try: try:
@ -3014,6 +3096,7 @@ class TestRunner(object):
hgcommand=self._hgcommand, hgcommand=self._hgcommand,
usechg=bool(self.options.with_chg or self.options.chg), usechg=bool(self.options.with_chg or self.options.chg),
useipv6=useipv6, useipv6=useipv6,
watchman=self._watchman,
**kwds **kwds
) )
t.should_reload = True t.should_reload = True

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
$ hg debugextensions --excludedefault $ hg debugextensions --excludedefault
treedirstate (untested!) treedirstate (untested!)

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
Test adding .hgeol Test adding .hgeol
$ cat >> $HGRCPATH <<EOF $ cat >> $HGRCPATH <<EOF

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
Testing cloning with the EOL extension Testing cloning with the EOL extension
$ cat >> $HGRCPATH <<EOF $ cat >> $HGRCPATH <<EOF

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
Test the EOL hook Test the EOL hook
$ hg init main $ hg init main

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
Test EOL patching Test EOL patching
$ cat >> $HGRCPATH <<EOF $ cat >> $HGRCPATH <<EOF

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
https://bz.mercurial-scm.org/2493 https://bz.mercurial-scm.org/2493
Testing tagging with the EOL extension Testing tagging with the EOL extension

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
Test EOL update Test EOL update
$ cat >> $HGRCPATH <<EOF $ cat >> $HGRCPATH <<EOF

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
Test EOL extension Test EOL extension
$ cat >> $HGRCPATH <<EOF $ cat >> $HGRCPATH <<EOF

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
#require eol-in-paths #require eol-in-paths
https://bz.mercurial-scm.org/352 https://bz.mercurial-scm.org/352

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
Test basic extension support Test basic extension support
$ cat > foobar.py <<EOF $ cat > foobar.py <<EOF

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
Short help: Short help:
$ hg $ hg

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
$ hg init a $ hg init a
$ cd a $ cd a
$ hg init b $ hg init b

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
$ . helpers-usechg.sh $ . helpers-usechg.sh
$ hg init a $ hg init a

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
Create test repository: Create test repository:
$ hg init repo $ hg init repo

View File

@ -1,3 +1,5 @@
#require no-fsmonitor
Let commit recurse into subrepos by default to match pre-2.0 behavior: Let commit recurse into subrepos by default to match pre-2.0 behavior:
$ echo "[ui]" >> $HGRCPATH $ echo "[ui]" >> $HGRCPATH