From Nikolaus at rath.org Sat Jun 1 23:41:44 2013 From: Nikolaus at rath.org (Nikolaus Rath) Date: Sat, 01 Jun 2013 14:41:44 -0700 Subject: [pytest-dev] how to fail on exceptions in other threads and destructors Message-ID: <87ppw54ig7.fsf@vostro.rath.org> Hello, I would like to make sure that a test fails when it results in an uncaught exception - even if the exception happens in a destructor or in a separate thread. What's the best way to do this? My idea is to modify sys.excepthook such that it keeps a list of all the exceptions encountered during a test run. Then, at the end of every test, I could check this list and raise an exception if there were any calls to sys.excepthook. However, I'm not quite sure how to do this properly without having to request an explicit fixture in every test. Is there a way to have initialization and finalization code run for every test function? And what kind of exception would I need to raise in the finalization code for py.test to recognize that it is the test that failed (rather than a bug in the finalization code)? Thanks, -Nikolaus -- ?Time flies like an arrow, fruit flies like a Banana.? PGP fingerprint: 5B93 61F8 4EA2 E279 ABF6 02CF A9AD B7F8 AE4E 425C From holger at merlinux.eu Mon Jun 3 11:42:24 2013 From: holger at merlinux.eu (holger krekel) Date: Mon, 3 Jun 2013 09:42:24 +0000 Subject: [pytest-dev] how to fail on exceptions in other threads and destructors In-Reply-To: <87ppw54ig7.fsf@vostro.rath.org> References: <87ppw54ig7.fsf@vostro.rath.org> Message-ID: <20130603094223.GT7789@merlinux.eu> Hi Nikolaus, On Sat, Jun 01, 2013 at 14:41 -0700, Nikolaus Rath wrote: > Hello, > > I would like to make sure that a test fails when it results in an > uncaught exception - even if the exception happens in a destructor or in > a separate thread. > > What's the best way to do this? > > My idea is to modify sys.excepthook such that it keeps a list of all the > exceptions encountered during a test run. Then, at the end of every > test, I could check this list and raise an exception if there were any > calls to sys.excepthook. > > However, I'm not quite sure how to do this properly without having to > request an explicit fixture in every test. Is there a way to have > initialization and finalization code run for every test function? And > what kind of exception would I need to raise in the finalization code > for py.test to recognize that it is the test that failed (rather than a > bug in the finalization code)? You could use an "autouse" fixture which gets called without naming the fixture in test/fixture function arguments. But you'd register a finalizer and this would indeed not fail the actual test but the teardown. To fail the actual test, you might look into implementing the pytest_pyfunc_call() hook. It is responsible for calling a python test function. You would want to wrap the existing hook implementation and add your exception-detection logic like this: # content of conftest.py in your testing dir import pytest @pytest.mark.tryfirst def pytest_pyfunc_call(__multicall__): ret = __multicall__.execute() # if this raises we don't care # perform extra checks and raise if there is a problem return ret # return whatever our wrapped hook impl returns The ``__multicall__`` bit is for interacting with other hook implementations so that we don't have to replicate their logic. You could also use the "pytest_runtest_call" hook if you also have doctests or other non-function types of tests. cheers, holger > > Thanks, > > -Nikolaus > > -- > ?Time flies like an arrow, fruit flies like a Banana.? > > PGP fingerprint: 5B93 61F8 4EA2 E279 ABF6 02CF A9AD B7F8 AE4E 425C > _______________________________________________ > Pytest-dev mailing list > Pytest-dev at python.org > http://mail.python.org/mailman/listinfo/pytest-dev From Nikolaus at rath.org Mon Jun 3 18:32:29 2013 From: Nikolaus at rath.org (Nikolaus Rath) Date: Mon, 03 Jun 2013 09:32:29 -0700 Subject: [pytest-dev] how to fail on exceptions in other threads and destructors In-Reply-To: <51AAA311.1010702@goucher.ca> References: <87ppw54ig7.fsf@vostro.rath.org> <51AAA311.1010702@goucher.ca> Message-ID: <51ACC51D.5000106@rath.org> On 06/01/2013 06:42 PM, Adam Goucher wrote: >> I would like to make sure that a test fails when it results in an >> uncaught exception - even if the exception happens in a destructor or in >> a separate thread. >> >> What's the best way to do this? >> >> My idea is to modify sys.excepthook such that it keeps a list of all the >> exceptions encountered during a test run. Then, at the end of every >> test, I could check this list and raise an exception if there were any >> calls to sys.excepthook. >> >> However, I'm not quite sure how to do this properly without having to >> request an explicit fixture in every test. Is there a way to have >> initialization and finalization code run for every test function? And >> what kind of exception would I need to raise in the finalization code >> for py.test to recognize that it is the test that failed (rather than a >> bug in the finalization code)? > > IIRC, I think what you want to do is look at the pytest_runtest_makereport hook. It gets called at each part of the execution flow. You would then look at call.when to determine when the thing that went wrong, went wrong. > > https://github.com/Element-34/Py.Saunter-Examples/blob/master/picpaste/webdriver/conftest.py#L34 is kinda an example. Though in my case I ignore the setup and teardown bits. That looks close to what I want to do, thanks for the link! However, I have some trouble understanding what's actually happening here: Am I correct that, despite its name, the pytest_runtest_makereport hook is called not just after the test has been teared down, but also after setup and calling? Or is it called before setup, calling and teardown? Also, what exactly is the __multicall__ argument? According to http://pytest.org/latest/plugins.html#py-test-hook-reference, pytest_runtest_makereport accepts just two (item, call) parameters...? Best, Nikolaus From Nikolaus at rath.org Mon Jun 3 19:30:49 2013 From: Nikolaus at rath.org (Nikolaus Rath) Date: Mon, 03 Jun 2013 10:30:49 -0700 Subject: [pytest-dev] how to fail on exceptions in other threads and destructors In-Reply-To: <20130603094223.GT7789@merlinux.eu> References: <87ppw54ig7.fsf@vostro.rath.org> <20130603094223.GT7789@merlinux.eu> Message-ID: <51ACD2C9.7040600@rath.org> On 06/03/2013 02:42 AM, holger krekel wrote: > Hi Nikolaus, > > On Sat, Jun 01, 2013 at 14:41 -0700, Nikolaus Rath wrote: >> Hello, >> >> I would like to make sure that a test fails when it results in an >> uncaught exception - even if the exception happens in a destructor or in >> a separate thread. >> >> What's the best way to do this? >> >> My idea is to modify sys.excepthook such that it keeps a list of all the >> exceptions encountered during a test run. Then, at the end of every >> test, I could check this list and raise an exception if there were any >> calls to sys.excepthook. >> >> However, I'm not quite sure how to do this properly without having to >> request an explicit fixture in every test. Is there a way to have >> initialization and finalization code run for every test function? And >> what kind of exception would I need to raise in the finalization code >> for py.test to recognize that it is the test that failed (rather than a >> bug in the finalization code)? > > You could use an "autouse" fixture which gets called without naming > the fixture in test/fixture function arguments. Ah, that's useful. Thanks for the pointer! > To fail the actual test, you might look into implementing the > pytest_pyfunc_call() hook. It is responsible for calling a python test > function. You would want to wrap the existing hook implementation and > add your exception-detection logic like this: > > # content of conftest.py in your testing dir > > import pytest > > @pytest.mark.tryfirst > def pytest_pyfunc_call(__multicall__): > ret = __multicall__.execute() # if this raises we don't care > # perform extra checks and raise if there is a problem > return ret # return whatever our wrapped hook impl returns > > The ``__multicall__`` bit is for interacting with other hook > implementations so that we don't have to replicate their logic. > You could also use the "pytest_runtest_call" hook if you > also have doctests or other non-function types of tests. Could you elaborate on the difference between pytest_pyfunc_call and pytest_runtest_call? The former doesn't seem to be mentioned anywhere in the docs. (Some of my tests are unittest.TestCases.) I'm also not sure about the tryfirst decorator. Why is that needed? Do I also need it when using pytest_runtest_call? As a side note, I just tried to set up something based on an autouse fixture and found that sys.excepthook is ignored both for threads and destructors. There is a workaround for threads (http://bugs.python.org/issue1230540), but __del__ seems to be a hopeless case -- I believe it prints directly to stderr from C code. So what I'll try instead is to make my extra tests check if a test resulted in any (or maybe any suspicious) output to stderr. Best, Nikolaus From Nikolaus at rath.org Tue Jun 4 07:03:27 2013 From: Nikolaus at rath.org (Nikolaus Rath) Date: Mon, 03 Jun 2013 22:03:27 -0700 Subject: [pytest-dev] Trouble accessing captured stderr Message-ID: <51AD751F.90905@rath.org> Hello, I have a problem accessing the captured stderr. I have set up the following test: $ ls mytest/ conftest.py test_one.py $ cat mytest/conftest.py import pytest @pytest.fixture(autouse=True) def add_stderr_check(request, capsys): def check_stderr(): stderr = capsys.readouterr()[1] if 'exception' in stderr.lower(): raise AssertionError('Suspicious output to stderr') request.addfinalizer(check_stderr) $ cat mytest/test_one.py import threading def test_thread(): def fail(): raise RuntimeError('Do not call me!') t = threading.Thread(target=fail) t.start() t.join() def test_del(): d = ImmortalObject() assert d.value == 2 del d class ImmortalObject: def __init__(self): self.value = 2 def __del__(self): raise RuntimeError('You cannot kill me!') As long as I call the tests individually, they behave exactly as expected: > $ py.test mytest/ -v -k test_thread > =========================================== test session starts ============================================ > platform linux -- Python 3.3.2 -- pytest-2.3.5 -- /usr/bin/python3.3 > collected 2 items > > mytest/test_one.py:3: test_thread PASSED > mytest/test_one.py:3: test_thread ERROR > > ================================================== ERRORS ================================================== > _____________________________________ ERROR at teardown of test_thread _____________________________________ > > def check_stderr(): > stderr = capsys.readouterr()[1] > if 'exception' in stderr.lower(): >> raise AssertionError('Suspicious output to stderr') > E AssertionError: Suspicious output to stderr > > mytest/conftest.py:8: AssertionError > --------------------------------------------- Captured stderr ---------------------------------------------- > Exception in thread Thread-1: > Traceback (most recent call last): > File "/usr/lib/python3.3/threading.py", line 637, in _bootstrap_inner > self.run() > File "/usr/lib/python3.3/threading.py", line 594, in run > self._target(*self._args, **self._kwargs) > File "/home/nikratio/in-progress/s3ql/mytest/test_one.py", line 5, in fail > raise RuntimeError('Do not call me!') > RuntimeError: Do not call me! > > ================================== 1 tests deselected by '-ktest_thread' =================================== > ============================= 1 passed, 1 deselected, 1 error in 0.01 seconds ============================== and: > [1] nikratio at vostro:~/in-progress/s3ql$ py.test mytest/ -v -k test_del > =========================================== test session starts ============================================ > platform linux -- Python 3.3.2 -- pytest-2.3.5 -- /usr/bin/python3.3 > collected 2 items > > mytest/test_one.py:10: test_del PASSED > mytest/test_one.py:10: test_del ERROR > > ================================================== ERRORS ================================================== > ______________________________________ ERROR at teardown of test_del _______________________________________ > > def check_stderr(): > stderr = capsys.readouterr()[1] > if 'exception' in stderr.lower(): >> raise AssertionError('Suspicious output to stderr') > E AssertionError: Suspicious output to stderr > > mytest/conftest.py:8: AssertionError > --------------------------------------------- Captured stderr ---------------------------------------------- > Exception RuntimeError: RuntimeError('You cannot kill me!',) in > ignored > ==================================== 1 tests deselected by '-ktest_del' ==================================== > ============================= 1 passed, 1 deselected, 1 error in 0.01 seconds ============================== However, if I attempt to run both tests, I get a very long and (to me) confusing error: > $ py.test mytest/ -v > =========================================== test session starts ============================================ > platform linux -- Python 3.3.2 -- pytest-2.3.5 -- /usr/bin/python3.3 > collected 2 items > > mytest/test_one.py:3: test_thread PASSED > mytest/test_one.py:3: test_thread ERROR > mytest/test_one.py:10: test_del FAILED > > ================================================== ERRORS ================================================== > _____________________________________ ERROR at teardown of test_thread _____________________________________ > > def check_stderr(): > stderr = capsys.readouterr()[1] > if 'exception' in stderr.lower(): >> raise AssertionError('Suspicious output to stderr') > E AssertionError: Suspicious output to stderr > > mytest/conftest.py:8: AssertionError > --------------------------------------------- Captured stderr ---------------------------------------------- > Exception in thread Thread-1: > Traceback (most recent call last): > File "/usr/lib/python3.3/threading.py", line 637, in _bootstrap_inner > self.run() > File "/usr/lib/python3.3/threading.py", line 594, in run > self._target(*self._args, **self._kwargs) > File "/home/nikratio/in-progress/s3ql/mytest/test_one.py", line 5, in fail > raise RuntimeError('Do not call me!') > RuntimeError: Do not call me! > > ================================================= FAILURES ================================================= > _________________________________________________ test_del _________________________________________________ > > self = > func = . at 0x7f413dfc5950>, when = 'call' > > def __init__(self, func, when): > #: context of invocation: one of "setup", "call", > #: "teardown", "memocollect" > self.when = when > self.start = time() > try: > try: >> self.result = func() > > ../../.local/lib/python3.3/site-packages/_pytest/runner.py:129: > _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ > >> return CallInfo(lambda: ihook(item=item, **kwds), when=when) > > ../../.local/lib/python3.3/site-packages/_pytest/runner.py:116: > _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ > > kwargs = {'item': } > plugins = [, <_pytest.core.PluginManager object at 0x7f413eb6dfd0>, , , ...] > > def call_matching_hooks(**kwargs): > plugins = self.config._getmatchingplugins(self.fspath) >> return hookmethod.pcall(plugins, **kwargs) > > ../../.local/lib/python3.3/site-packages/_pytest/main.py:159: > _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ > > self = > plugins = [, <_pytest.core.PluginManager object at 0x7f413eb6dfd0>, , , ...] > kwargs = {'item': } > methods = [, , >] > > def pcall(self, plugins, **kwargs): > methods = self.hookrelay._pm.listattr(self.name, plugins=plugins) >> return self._docall(methods, kwargs) > > ../../.local/lib/python3.3/site-packages/_pytest/core.py:445: > _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ > > self = > methods = [, , >] > kwargs = {'item': } > > def _docall(self, methods, kwargs): > self.trace(self.name, kwargs) > self.trace.root.indent += 1 > mc = MultiCall(methods, kwargs, firstresult=self.firstresult) > try: >> res = mc.execute() > > ../../.local/lib/python3.3/site-packages/_pytest/core.py:452: > _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ > > self = }> > > def execute(self): > while self.methods: > method = self.methods.pop() > kwargs = self.getkwargs(method) >> res = method(**kwargs) > > ../../.local/lib/python3.3/site-packages/_pytest/core.py:370: > _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ > > self = <_pytest.capture.CaptureManager object at 0x7f413eb77710>, item = > > @pytest.mark.tryfirst > def pytest_runtest_call(self, item): > self.resumecapture_item(item) >> self.activate_funcargs(item) > > ../../.local/lib/python3.3/site-packages/_pytest/capture.py:158: > _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ > > self = <_pytest.capture.CaptureManager object at 0x7f413eb77710>, pyfuncitem = > > def activate_funcargs(self, pyfuncitem): > funcargs = getattr(pyfuncitem, "funcargs", None) > if funcargs is not None: > for name, capfuncarg in funcargs.items(): > if name in ('capsys', 'capfd'): > assert not hasattr(self, '_capturing_funcarg') > self._capturing_funcarg = capfuncarg >> capfuncarg._start() > > ../../.local/lib/python3.3/site-packages/_pytest/capture.py:128: > _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ > > self = <_pytest.capture.CaptureFixture object at 0x7f413ea47f10> > > def _start(self): >> self.capture.startall() > E AttributeError: 'CaptureFixture' object has no attribute 'capture' > > ../../.local/lib/python3.3/site-packages/_pytest/capture.py:209: AttributeError > =============================== 1 failed, 1 passed, 1 error in 0.03 seconds ================================ Can someone explain what's happening here? Best, -Nikolaus -- ?Time flies like an arrow, fruit flies like a Banana.? PGP fingerprint: 5B93 61F8 4EA2 E279 ABF6 02CF A9AD B7F8 AE4E 425C From andreas at pelme.se Tue Jun 4 07:53:29 2013 From: andreas at pelme.se (Andreas Pelme) Date: Tue, 4 Jun 2013 07:53:29 +0200 Subject: [pytest-dev] Trouble accessing captured stderr In-Reply-To: <51AD751F.90905@rath.org> References: <51AD751F.90905@rath.org> Message-ID: (I somehow missed to reply to the list) On Tuesday 4 June 2013 at 07:03, Nikolaus Rath wrote: > @pytest.fixture(autouse=True) > def add_stderr_check(request, capsys): > def check_stderr(): > stderr = capsys.readouterr()[1] > if 'exception' in stderr.lower(): > raise AssertionError('Suspicious output to stderr') > request.addfinalizer(check_stderr) Hi, This might not be the answer to your question, but you should be aware of this issue: https://bitbucket.org/hpk42/pytest/issue/287/fixture-finalizer-failure-causes-other Raising exceptions in the fixture teardown will make other fixtures not being teared down, which can cause other tests to fail. Cheers Andreas From holger at merlinux.eu Tue Jun 4 21:29:06 2013 From: holger at merlinux.eu (holger krekel) Date: Tue, 4 Jun 2013 19:29:06 +0000 Subject: [pytest-dev] pytest-cache 1.0: new --ff option to first run previous failures Message-ID: <20130604192906.GZ7789@merlinux.eu> Hi, i just release pytest-cache-1.0 to pypi which allows to re-run failures from previous test runs and provides a mechanism for other plugins to store data across test session (used e.g. by pytest-pep8). See https://pypi.python.org/pypi/pytest-cache for some docs. The main news is the "--ff" option which runs all tests but first the failures from the last run. Thanks to Jack Riches for the patch. Full Changelog below. cheers, holger 1.0 ---------------------------------------------- - merged "--ff" (failedfirst) option to run all tests but run the last-failed ones first. Thanks Jack Riches. - fix issue6 - --clearcache doesn't bail out if .cache doesn't exist, thanks longlho - fix issue4: consider setup time skip of a previous failure as no longer failing - ensure --cache output is sorted From holger at merlinux.eu Wed Jun 5 09:11:19 2013 From: holger at merlinux.eu (holger krekel) Date: Wed, 5 Jun 2013 07:11:19 +0000 Subject: [pytest-dev] how to fail on exceptions in other threads and destructors In-Reply-To: <51ACC51D.5000106@rath.org> References: <87ppw54ig7.fsf@vostro.rath.org> <51AAA311.1010702@goucher.ca> <51ACC51D.5000106@rath.org> Message-ID: <20130605071119.GD7789@merlinux.eu> On Mon, Jun 03, 2013 at 09:32 -0700, Nikolaus Rath wrote: > On 06/01/2013 06:42 PM, Adam Goucher wrote: > >> I would like to make sure that a test fails when it results in an > >> uncaught exception - even if the exception happens in a destructor or in > >> a separate thread. > >> > >> What's the best way to do this? > >> > >> My idea is to modify sys.excepthook such that it keeps a list of all the > >> exceptions encountered during a test run. Then, at the end of every > >> test, I could check this list and raise an exception if there were any > >> calls to sys.excepthook. > >> > >> However, I'm not quite sure how to do this properly without having to > >> request an explicit fixture in every test. Is there a way to have > >> initialization and finalization code run for every test function? And > >> what kind of exception would I need to raise in the finalization code > >> for py.test to recognize that it is the test that failed (rather than a > >> bug in the finalization code)? > > > > IIRC, I think what you want to do is look at the pytest_runtest_makereport hook. It gets called at each part of the execution flow. You would then look at call.when to determine when the thing that went wrong, went wrong. > > > > https://github.com/Element-34/Py.Saunter-Examples/blob/master/picpaste/webdriver/conftest.py#L34 is kinda an example. Though in my case I ignore the setup and teardown bits. > > > That looks close to what I want to do, thanks for the link! > > However, I have some trouble understanding what's actually happening here: > > Am I correct that, despite its name, the pytest_runtest_makereport hook > is called not just after the test has been teared down, but also after > setup and calling? Or is it called before setup, calling and teardown? yes, pytest_runtest_makereport is called for each of the three setup/call/teardown phases of running a test. The phase is indicated in ``call.when``. > Also, what exactly is the __multicall__ argument? According to > http://pytest.org/latest/plugins.html#py-test-hook-reference, > pytest_runtest_makereport accepts just two (item, call) parameters...? "__multicall__" is missing docs because it wasn't yet part of the official API. But i consider it now part of the official API FWIW :) Any hook implementation can accept ``__multicall__`` in addition to the hook-specified parameters. ``__multicall__.execute()`` allows to call the pending hook implementations, thus allowing to wrap values. Concretely, the following pattern allows to decorate/modify values from other hooks: @pytest.mark.tryfirst # tell pytest to run this hook impl early def pytest_SOMEHOOK(..., __multicall__): result = __multicall__.execute() # execute all other hook impls # decorate/process result return result cheers, holger > > Best, > Nikolaus > _______________________________________________ > Pytest-dev mailing list > Pytest-dev at python.org > http://mail.python.org/mailman/listinfo/pytest-dev > From brianna.laugher at gmail.com Thu Jun 6 08:44:15 2013 From: brianna.laugher at gmail.com (Brianna Laugher) Date: Thu, 6 Jun 2013 16:44:15 +1000 Subject: [pytest-dev] parametrize and ids In-Reply-To: <20130529074833.GF7789@merlinux.eu> References: <20130529074833.GF7789@merlinux.eu> Message-ID: On 29 May 2013 17:48, holger krekel wrote: > agreed. One a sidenote, yesterday i introduced a briefer way to > specify argument names. Your example would start like this: > > @pytest.mark.parametrize("wx,expectedCoverage,expectedTrend", ...) > > and you can also have spaces after the commas if you prefer. > I noticed that :) looks good. I think of parametrize like specifying namedtuples so it is a good improvement. > I'd recommend to write a wrapper "@myparametrize" which generates a > pytest.mark.parametrize() instance in the end. This way you could use > and consolidate your API ideas in real life with today's pytest in real > life before aiming for pytest inclusion. > Great idea. I discussed my idea with my workmate and he immediately suggested I use a dictionary, with the key being the test name and the value being the argvalue (test data). In doing that the test order is not guaranteed, but one could use an OrderedDict I suppose if that was important. I have put up a working example here for anyone who is interested: https://gist.github.com/pfctdayelise/5719730 cheers Brianna -- They've just been waiting in a mountain for the right moment: http://modernthings.org/ -------------- next part -------------- An HTML attachment was scrubbed... URL: