Commit 42fdaecc authored by Jason Madden's avatar Jason Madden

Add pypy3.7 to Appveyor.

Fixes #1798.

Add test_signal for all versions.

Give up and disable test_signal on PyPy/Win. It hangs without running any tests.

Disable test__threading_vs_settrace on PyPy3/Appveyor. Somehow it fails to launch the script for unknown reasons.
parent 9338965a
...@@ -41,6 +41,13 @@ environment: ...@@ -41,6 +41,13 @@ environment:
# a later point release. # a later point release.
# 64-bit # 64-bit
- PYTHON: "C:\\pypy3.7-v7.3.7-win64"
PYTHON_ID: "pypy3"
PYTHON_EXE: pypy3w
PYTHON_VERSION: "3.7.x"
PYTHON_ARCH: "64"
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
- PYTHON: "C:\\pypy2.7-v7.3.6-win64" - PYTHON: "C:\\pypy2.7-v7.3.6-win64"
PYTHON_ID: "pypy" PYTHON_ID: "pypy"
PYTHON_EXE: pypy PYTHON_EXE: pypy
...@@ -168,6 +175,12 @@ install: ...@@ -168,6 +175,12 @@ install:
} }
7z x -y "${env:PYTMP}\pypy2-v7.3.6-win64.zip" -oC:\ | Out-Null; 7z x -y "${env:PYTMP}\pypy2-v7.3.6-win64.zip" -oC:\ | Out-Null;
} }
elseif ("${env:PYTHON_ID}" -eq "pypy3") {
if (!(Test-Path "${env:PYTMP}\pypy3.7-v7.3.7-win64.zip")) {
(New-Object Net.WebClient).DownloadFile("https://downloads.python.org/pypy/pypy3.7-v7.3.7-win64.zip", "${env:PYTMP}\pypy3.7-v7.3.7-win64.zip");
}
7z x -y "${env:PYTMP}\pypy3.7-v7.3.7-win64.zip" -oC:\ | Out-Null;
}
elseif (-not(Test-Path($env:PYTHON))) { elseif (-not(Test-Path($env:PYTHON))) {
& appveyor\install.ps1; & appveyor\install.ps1;
} }
...@@ -198,9 +211,8 @@ cache: ...@@ -198,9 +211,8 @@ cache:
build_script: build_script:
# Build the compiled extension # Build the compiled extension
# Try to get some things that don't wind up in the pip cache as - "%CMD_IN_ENV% %PYEXE% -m pip install -U wheel"
# built wheels if they're built during an isolated build. - "%CMD_IN_ENV% %PYEXE% -m pip install -U setuptools"
- "%CMD_IN_ENV% %PYEXE% -m pip install -U wheel cython setuptools cffi"
- if not "%GWHEEL_ONLY%"=="true" %PYEXE% -m pip install -U -e .[test] - if not "%GWHEEL_ONLY%"=="true" %PYEXE% -m pip install -U -e .[test]
test_script: test_script:
...@@ -209,6 +221,7 @@ test_script: ...@@ -209,6 +221,7 @@ test_script:
- if not "%GWHEEL_ONLY%"=="true" %PYEXE% -c "import gevent.core; print(gevent.core.loop)" - if not "%GWHEEL_ONLY%"=="true" %PYEXE% -c "import gevent.core; print(gevent.core.loop)"
- if not "%GWHEEL_ONLY%"=="true" %PYEXE% -c "import gevent; print(gevent.config.settings['resolver'].get_options())" - if not "%GWHEEL_ONLY%"=="true" %PYEXE% -c "import gevent; print(gevent.config.settings['resolver'].get_options())"
- if not "%GWHEEL_ONLY%"=="true" %PYEXE% -c "from gevent._compat import get_clock_info; print(get_clock_info('perf_counter'))" - if not "%GWHEEL_ONLY%"=="true" %PYEXE% -c "from gevent._compat import get_clock_info; print(get_clock_info('perf_counter'))"
- if not "%GWHEEL_ONLY%"=="true" %PYEXE% -mgevent.tests.known_failures
- if not "%GWHEEL_ONLY%"=="true" %PYEXE% -mgevent.tests --second-chance --config known_failures.py - if not "%GWHEEL_ONLY%"=="true" %PYEXE% -mgevent.tests --second-chance --config known_failures.py
after_test: after_test:
......
Windows: Test and provide binary wheels for PyPy3.7.
Note that there may be issues with subprocesses, signals, and it may
be slow.
...@@ -26,8 +26,8 @@ library and will install the `cffi`_ library by default on Windows. ...@@ -26,8 +26,8 @@ library and will install the `cffi`_ library by default on Windows.
The cffi library will become the default on all platforms in a future The cffi library will become the default on all platforms in a future
release of gevent. release of gevent.
This version of gevent also runs on PyPy 7.0 or above. On PyPy, there This version of gevent also runs on PyPy 7.3.7 (7.3.6 for PyPy2) or
are no external dependencies. above. On PyPy, there are no external dependencies.
gevent is tested on Windows, macOS, and Linux, and should run on most gevent is tested on Windows, macOS, and Linux, and should run on most
other Unix-like operating systems (e.g., FreeBSD, Solaris, etc.) other Unix-like operating systems (e.g., FreeBSD, Solaris, etc.)
...@@ -35,7 +35,9 @@ other Unix-like operating systems (e.g., FreeBSD, Solaris, etc.) ...@@ -35,7 +35,9 @@ other Unix-like operating systems (e.g., FreeBSD, Solaris, etc.)
.. note:: .. note::
Windows is supported as a tier 2, "best effort," platform. It is Windows is supported as a tier 2, "best effort," platform. It is
suitable for development, but not recommended for production. suitable for development, but not recommended for production. In
particular, PyPy3 on Windows may have issues, especially with
subprocesses.
On Windows using the deprecated libev backend, gevent is On Windows using the deprecated libev backend, gevent is
limited to a maximum of 1024 open sockets due to limited to a maximum of 1024 open sockets due to
......
...@@ -180,9 +180,15 @@ disabled_tests = [ ...@@ -180,9 +180,15 @@ disabled_tests = [
# (unless signal handler raises an error) maybe it should? # (unless signal handler raises an error) maybe it should?
'test_signal.WakeupSignalTests.test_wakeup_fd_during', 'test_signal.WakeupSignalTests.test_wakeup_fd_during',
# these rely on os.read raising EINTR which never happens with gevent.os.read
'test_signal.SiginterruptTest.test_without_siginterrupt', 'test_signal.SiginterruptTest.test_without_siginterrupt',
'test_signal.SiginterruptTest.test_siginterrupt_on', 'test_signal.SiginterruptTest.test_siginterrupt_on',
# these rely on os.read raising EINTR which never happens with gevent.os.read 'test_signal.SiginterruptTest.test_siginterrupt_off',
# This one takes forever and relies on threading details
'test_signal.StressTest.test_stress_modifying_handlers',
# This uses an external file, and launches it. This means that it's not
# actually testing gevent because there's no monkey-patch.
'test_signal.PosixTests.test_interprocess_signal',
'test_subprocess.ProcessTestCase.test_leak_fast_process_del_killed', 'test_subprocess.ProcessTestCase.test_leak_fast_process_del_killed',
'test_subprocess.ProcessTestCase.test_zombie_fast_process_del', 'test_subprocess.ProcessTestCase.test_zombie_fast_process_del',
...@@ -680,6 +686,34 @@ if PYPY3 and TRAVIS: ...@@ -680,6 +686,34 @@ if PYPY3 and TRAVIS:
'test_socket.InheritanceTest.test_SOCK_CLOEXEC', 'test_socket.InheritanceTest.test_SOCK_CLOEXEC',
] ]
if PYPY3 and WIN:
disabled_tests += [
# test_httpservers.CGIHTTPServerTestCase all seem to hang.
# There seem to be some general subprocess issues. This is
# ignored entirely from known_failures.py
# This produces:
#
# OSError: [Errno 10014] The system detected an invalid
# pointer address in attempting to use a pointer argument in
# a call
#
# When calling socket.socket(fileno=fd) when we actually
# call ``self._socket =self._gevent_sock_class()``.
'test_socket.GeneralModuleTests.test_socket_fileno',
# This doesn't respect the scope properly
#
# self.assertEqual(sockaddr, ('ff02::1de:c0:face:8d', 1234, 0, ifindex))
# AssertionError: Tuples differ: ('ff02::1de:c0:face:8d%42', 1234, 0, 42) != ('ff02::1de:c0:face:8d', 1234, 0, 42
#
'test_socket.GeneralModuleTests.test_getaddrinfo_ipv6_scopeid_numeric',
# self.assertEqual(newsock.get_inheritable(), False)
# AssertionError: True != False
'test_socket.InheritanceTest.test_dup',
]
def _make_run_with_original(mod_name, func_name): def _make_run_with_original(mod_name, func_name):
@contextlib.contextmanager @contextlib.contextmanager
def with_orig(): def with_orig():
...@@ -1412,6 +1446,8 @@ if PY310: ...@@ -1412,6 +1446,8 @@ if PY310:
# We don't currently implement pipesize. # We don't currently implement pipesize.
'test_subprocess.ProcessTestCase.test_pipesize_default', 'test_subprocess.ProcessTestCase.test_pipesize_default',
'test_subprocess.ProcessTestCase.test_pipesizes', 'test_subprocess.ProcessTestCase.test_pipesizes',
# Unknown
'test_signal.SiginterruptTest.test_siginterrupt_off',
] ]
if TRAVIS: if TRAVIS:
......
...@@ -808,15 +808,39 @@ def main(): ...@@ -808,15 +808,39 @@ def main():
import argparse import argparse
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--ignore') parser.add_argument('--ignore')
parser.add_argument('--discover', action='store_true') parser.add_argument(
parser.add_argument('--full', action='store_true') '--discover', action='store_true',
parser.add_argument('--config', default='known_failures.py') help="Only print the tests found."
parser.add_argument("--coverage", action="store_true") )
parser.add_argument("--quiet", action="store_true", default=True) parser.add_argument(
'--config', default='known_failures.py',
help="The path to the config file containing "
"FAILING_TESTS, IGNORED_TESTS and RUN_ALONE. "
"Defaults to %(default)s."
)
parser.add_argument(
"--coverage", action="store_true",
help="Enable coverage recording with coverage.py."
)
# TODO: Quiet and verbose should be mutually exclusive
parser.add_argument(
"--quiet", action="store_true", default=True,
help="Be quiet. Defaults to %(default)s. Also the "
"GEVENTTEST_QUIET environment variable."
)
parser.add_argument("--verbose", action="store_false", dest='quiet') parser.add_argument("--verbose", action="store_false", dest='quiet')
parser.add_argument("--debug", action="store_true", default=False)
parser.add_argument("--package", default="gevent.tests") parser.add_argument(
"--debug", action="store_true", default=False,
help="Enable debug settings. If the GEVENT_DEBUG environment variable is not set, "
"this sets it to 'debug'. This can also enable PYTHONTRACEMALLOC and the debug PYTHONMALLOC "
"allocators, if not already set. Defaults to %(default)s."
)
parser.add_argument(
"--package", default="gevent.tests",
help="Load tests from the given package. Defaults to %(default)s."
)
parser.add_argument( parser.add_argument(
"--processes", "-j", default=DEFAULT_NWORKERS, type=int, "--processes", "-j", default=DEFAULT_NWORKERS, type=int,
help="Use up to the given number of parallel processes to execute tests. " help="Use up to the given number of parallel processes to execute tests. "
......
...@@ -80,6 +80,7 @@ class _AttrCondition(ConstantCondition): ...@@ -80,6 +80,7 @@ class _AttrCondition(ConstantCondition):
ConstantCondition.__init__(self, getattr(sysinfo, name), name) ConstantCondition.__init__(self, getattr(sysinfo, name), name)
PYPY = _AttrCondition('PYPY') PYPY = _AttrCondition('PYPY')
PYPY3 = _AttrCondition('PYPY3')
PY3 = _AttrCondition('PY3') PY3 = _AttrCondition('PY3')
PY2 = _AttrCondition('PY2') PY2 = _AttrCondition('PY2')
OSX = _AttrCondition('OSX') OSX = _AttrCondition('OSX')
...@@ -160,12 +161,18 @@ class Multi(object): ...@@ -160,12 +161,18 @@ class Multi(object):
def __init__(self): def __init__(self):
self._conds = [] self._conds = []
def flaky(self, reason='', when=True): def flaky(self, reason='', when=True, ignore_coverage=NEVER, run_alone=NEVER):
self._conds.append(Flaky(reason, when)) self._conds.append(
Flaky(
reason, when=when,
ignore_coverage=ignore_coverage,
run_alone=run_alone,
)
)
return self return self
def ignored(self, reason='', when=True): def ignored(self, reason='', when=True):
self._conds.append(Ignored(reason, when)) self._conds.append(Ignored(reason, when=when))
return self return self
def __set_name__(self, owner, name): def __set_name__(self, owner, name):
...@@ -279,13 +286,19 @@ class Definitions(DefinitionsBase): ...@@ -279,13 +286,19 @@ class Definitions(DefinitionsBase):
test__backdoor = Flaky(when=LEAKTEST | PYPY) test__backdoor = Flaky(when=LEAKTEST | PYPY)
test__socket_errors = Flaky(when=LEAKTEST) test__socket_errors = Flaky(when=LEAKTEST)
test_signal = Flaky( test_signal = Multi().flaky(
"On Travis, this very frequently fails due to timing", "On Travis, this very frequently fails due to timing",
when=TRAVIS & LEAKTEST, when=TRAVIS & LEAKTEST,
# Partial workaround for the _testcapi issue on PyPy, # Partial workaround for the _testcapi issue on PyPy,
# but also because signal delivery can sometimes be slow, and this # but also because signal delivery can sometimes be slow, and this
# spawn processes of its own # spawn processes of its own
run_alone=APPVEYOR, run_alone=APPVEYOR,
).ignored(
"""
This fails to run a single test. It looks like just importing the module
can hang. All I see is the output from patch_all()
""",
when=APPVEYOR & PYPY3
) )
test__monkey_sigchld_2 = Ignored( test__monkey_sigchld_2 = Ignored(
...@@ -303,10 +316,22 @@ class Definitions(DefinitionsBase): ...@@ -303,10 +316,22 @@ class Definitions(DefinitionsBase):
allocate SSL Context objects, either in Python 2.7 or 3.6. allocate SSL Context objects, either in Python 2.7 or 3.6.
There must be some library incompatibility. No point even There must be some library incompatibility. No point even
running them. XXX: Remember to turn this back on. running them. XXX: Remember to turn this back on.
On Windows, with PyPy3.7 7.3.7, there seem to be all kind of certificate
errors.
""", """,
when=PYPY & TRAVIS when=(PYPY & TRAVIS) | (PYPY3 & WIN)
) )
test_httpservers = Ignored(
"""
All the CGI tests hang. There appear to be subprocess problems.
""",
when=PYPY3 & WIN
)
test__pywsgi = Ignored( test__pywsgi = Ignored(
""" """
XXX: Re-enable this when we can investigate more. This has XXX: Re-enable this when we can investigate more. This has
...@@ -319,16 +344,23 @@ class Definitions(DefinitionsBase): ...@@ -319,16 +344,23 @@ class Definitions(DefinitionsBase):
On Appveyor 3.8.0, for some reason this takes *way* too long, about 100s, which On Appveyor 3.8.0, for some reason this takes *way* too long, about 100s, which
often goes just over the default timeout of 100s. This makes no sense. often goes just over the default timeout of 100s. This makes no sense.
But it also takes nearly that long in 3.7. 3.6 and earlier are much faster. But it also takes nearly that long in 3.7. 3.6 and earlier are much faster.
It also takes just over 100s on PyPy 3.7.
""", """,
when=(PYPY & TRAVIS & LIBUV) | PY380_EXACTLY, when=(PYPY & TRAVIS & LIBUV) | PY380_EXACTLY,
# https://bitbucket.org/pypy/pypy/issues/2769/systemerror-unexpected-internal-exception # https://bitbucket.org/pypy/pypy/issues/2769/systemerror-unexpected-internal-exception
run_alone=(CI & LEAKTEST & PY3) | (PYPY & LIBUV), run_alone=(CI & LEAKTEST & PY3) | (PYPY & LIBUV),
# This often takes much longer on PyPy on CI.
options={'timeout': (CI & PYPY, 180)},
) )
test_subprocess = Flaky( test_subprocess = Multi().flaky(
"Unknown, can't reproduce locally; times out one test", "Unknown, can't reproduce locally; times out one test",
when=PYPY & PY3 & TRAVIS, when=PYPY & PY3 & TRAVIS,
ignore_coverage=ALWAYS, ignore_coverage=ALWAYS,
).ignored(
"Tests don't even start before the process times out.",
when=PYPY3 & WIN
) )
test__threadpool = Ignored( test__threadpool = Ignored(
...@@ -388,7 +420,7 @@ class Definitions(DefinitionsBase): ...@@ -388,7 +420,7 @@ class Definitions(DefinitionsBase):
off. off.
""", """,
when=COVERAGE, when=COVERAGE,
ignore_coverage=ALWAYS ignore_coverage=ALWAYS,
) )
test__hub_join_timeout = Ignored( test__hub_join_timeout = Ignored(
...@@ -417,7 +449,7 @@ class Definitions(DefinitionsBase): ...@@ -417,7 +449,7 @@ class Definitions(DefinitionsBase):
""" """
On a heavily loaded box, these can all take upwards of 200s. On a heavily loaded box, these can all take upwards of 200s.
""", """,
when=CI & LEAKTEST when=(CI & LEAKTEST) | (PYPY3 & APPVEYOR)
) )
test_socket = RunAlone( test_socket = RunAlone(
......
...@@ -33,13 +33,16 @@ else: ...@@ -33,13 +33,16 @@ else:
from gevent.testing.sysinfo import CFFI_BACKEND from gevent.testing.sysinfo import CFFI_BACKEND
from gevent.testing.sysinfo import RUN_COVERAGE from gevent.testing.sysinfo import RUN_COVERAGE
from gevent.testing.sysinfo import WIN from gevent.testing.sysinfo import WIN
from gevent.testing.sysinfo import PYPY3
class Test(unittest.TestCase): class Test(unittest.TestCase):
@unittest.skipIf(CFFI_BACKEND and RUN_COVERAGE, @unittest.skipIf(
"Interferes with the timing") (CFFI_BACKEND and RUN_COVERAGE) or (PYPY3 and WIN),
"Interferes with the timing; times out waiting for the child")
def test_hang(self): def test_hang(self):
# XXX: Why does PyPy3 on Win fail to kill the child? (This was before we switched
# to pypy3w; perhaps that makes a difference?)
if WIN: if WIN:
from subprocess import CREATE_NEW_PROCESS_GROUP from subprocess import CREATE_NEW_PROCESS_GROUP
kwargs = {'creationflags': CREATE_NEW_PROCESS_GROUP} kwargs = {'creationflags': CREATE_NEW_PROCESS_GROUP}
...@@ -63,7 +66,7 @@ else: ...@@ -63,7 +66,7 @@ else:
p.send_signal(signal_to_send) p.send_signal(signal_to_send)
# Wait a few seconds for child process to die. Sometimes signal delivery is delayed # Wait a few seconds for child process to die. Sometimes signal delivery is delayed
# or even swallowed by Python, so send the signal a few more times if necessary # or even swallowed by Python, so send the signal a few more times if necessary
wait_seconds = 15.0 wait_seconds = 25.0
now = time.time() now = time.time()
midtime = now + (wait_seconds / 2.0) midtime = now + (wait_seconds / 2.0)
endtime = time.time() + wait_seconds endtime = time.time() + wait_seconds
......
...@@ -158,6 +158,11 @@ class TestCase(greentest.TestCase): ...@@ -158,6 +158,11 @@ class TestCase(greentest.TestCase):
if greentest.OSX: if greentest.OSX:
# A kernel bug in OS X sometimes results in this # A kernel bug in OS X sometimes results in this
LOCAL_CONN_REFUSED_ERRORS = (errno.EPROTOTYPE,) LOCAL_CONN_REFUSED_ERRORS = (errno.EPROTOTYPE,)
elif greentest.WIN and greentest.PYPY3:
# We see WinError 10049: The requested address is not valid
# which is not one of the errors we get anywhere else.
# Not sure which errno constant this is?
LOCAL_CONN_REFUSED_ERRORS = (10049,)
def assertConnectionRefused(self, in_proc_server=True): def assertConnectionRefused(self, in_proc_server=True):
try: try:
......
...@@ -14,7 +14,7 @@ import sys, os, threading, time ...@@ -14,7 +14,7 @@ import sys, os, threading, time
# A deadlock-killer, to prevent the # A deadlock-killer, to prevent the
# testsuite to hang forever # testsuite to hang forever
def killer(): def killer():
time.sleep(0.1) time.sleep(0.2)
sys.stdout.write('..program blocked; aborting!') sys.stdout.write('..program blocked; aborting!')
sys.stdout.flush() sys.stdout.flush()
os._exit(2) os._exit(2)
...@@ -137,7 +137,15 @@ class TestTrace(unittest.TestCase): ...@@ -137,7 +137,15 @@ class TestTrace(unittest.TestCase):
self.assertTrue(isinstance(e, LoopExit)) self.assertTrue(isinstance(e, LoopExit))
def run_script(self, more_args=()): def run_script(self, more_args=()):
args = [sys.executable, "-c", script] if (
greentest.PYPY3
and greentest.RUNNING_ON_APPVEYOR
and sys.version_info[:2] == (3, 7)
):
# Somehow launching the subprocess fails with exit code 1, and
# produces no output. It's not clear why.
self.skipTest("Known to hang on AppVeyor")
args = [sys.executable, "-u", "-c", script]
args.extend(more_args) args.extend(more_args)
rc = subprocess.call(args) rc = subprocess.call(args)
self.assertNotEqual(rc, 2, "interpreter was blocked") self.assertNotEqual(rc, 2, "interpreter was blocked")
......
import os
import signal
import subprocess
import sys
import time
import unittest
from test import support
class SIGUSR1Exception(Exception):
pass
class InterProcessSignalTests(unittest.TestCase):
def setUp(self):
self.got_signals = {'SIGHUP': 0, 'SIGUSR1': 0, 'SIGALRM': 0}
def sighup_handler(self, signum, frame):
self.got_signals['SIGHUP'] += 1
def sigusr1_handler(self, signum, frame):
self.got_signals['SIGUSR1'] += 1
raise SIGUSR1Exception
def wait_signal(self, child, signame):
if child is not None:
# This wait should be interrupted by exc_class
# (if set)
child.wait()
timeout = support.SHORT_TIMEOUT
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
if self.got_signals[signame]:
return
signal.pause()
self.fail('signal %s not received after %s seconds'
% (signame, timeout))
def subprocess_send_signal(self, pid, signame):
code = 'import os, signal; os.kill(%s, signal.%s)' % (pid, signame)
args = [sys.executable, '-I', '-c', code]
return subprocess.Popen(args)
def test_interprocess_signal(self):
# Install handlers. This function runs in a sub-process, so we
# don't worry about re-setting the default handlers.
signal.signal(signal.SIGHUP, self.sighup_handler)
signal.signal(signal.SIGUSR1, self.sigusr1_handler)
signal.signal(signal.SIGUSR2, signal.SIG_IGN)
signal.signal(signal.SIGALRM, signal.default_int_handler)
# Let the sub-processes know who to send signals to.
pid = str(os.getpid())
with self.subprocess_send_signal(pid, "SIGHUP") as child:
self.wait_signal(child, 'SIGHUP')
self.assertEqual(self.got_signals, {'SIGHUP': 1, 'SIGUSR1': 0,
'SIGALRM': 0})
with self.assertRaises(SIGUSR1Exception):
with self.subprocess_send_signal(pid, "SIGUSR1") as child:
self.wait_signal(child, 'SIGUSR1')
self.assertEqual(self.got_signals, {'SIGHUP': 1, 'SIGUSR1': 1,
'SIGALRM': 0})
with self.subprocess_send_signal(pid, "SIGUSR2") as child:
# Nothing should happen: SIGUSR2 is ignored
child.wait()
try:
with self.assertRaises(KeyboardInterrupt):
signal.alarm(1)
self.wait_signal(None, 'SIGALRM')
self.assertEqual(self.got_signals, {'SIGHUP': 1, 'SIGUSR1': 1,
'SIGALRM': 0})
finally:
signal.alarm(0)
if __name__ == "__main__":
unittest.main()
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment