[Avocado-devel] miniRFC: Call `tearDown` after `setUp` failure
Amador Pahim
apahim at redhat.com
Thu Mar 23 10:07:36 UTC 2017
On Thu, Mar 23, 2017 at 11:04 AM, Lukáš Doktor <ldoktor at redhat.com> wrote:
> Dne 23.3.2017 v 10:18 Amador Pahim napsal(a):
>>
>> On Thu, Mar 23, 2017 at 7:10 AM, Lukáš Doktor <ldoktor at redhat.com> wrote:
>>>
>>> Dne 22.3.2017 v 20:00 Ademar Reis napsal(a):
>>>>
>>>>
>>>> On Wed, Mar 22, 2017 at 07:05:48PM +0100, Lukáš Doktor wrote:
>>>>>
>>>>>
>>>>> Hello guys,
>>>>>
>>>>> I remember early in development we decided to abort the execution when
>>>>> `setUp` fails without executing `tearDown`, but over the time I keep
>>>>> thinking about it and I don't think it's optimal. My main reason is to
>>>>> simplify `setUp` code, let me demonstrate it on an example.
>>>>
>>>>
>>>>
>>>> So you're basically proposing that setUp() and tearDown() are
>>>> always executed "together". In other words, if setUp() is called,
>>>> then tearDown() will also be called, even if setUp() resulted in
>>>> error, exception or cancellation.
>>
>>
>> Yep, agreed. This makes a lot of sense and will simplify our code. Let
>> me include this in the PR I'm working to adjust SKIP and CANCEL
>> behavior, since this is pretty much related.
>>
> I'd prefer merging the SKIP/CANCEL PR first as it corrects behavior
> introduced in this sprint. The new change of behavior could be brought up
> during the public sprint review as it requires modifications to tests (the
> assumption that tearDown is not executed can lead to double-failures, which
> is harmless, but could surprise people).
Ok. So, please create a card for it.
>
> Lukáš
>
>
>>>>
>>>> I think it makes perfect sense.
>>>>
>>>> The only problem I see is in the case of test interruption
>>>> (ctrl+c or timeout). I think this will have to be the only
>>>> exception. In case of interruption, we can't wait to run
>>>> tearDown().
>>>>
>>>> In summary, the behavior would be:
>>>>
>>>> normal execution
>>>> - test status = PASS | FAIL | WARN
>>>> - setUp() is run
>>>> - tearDown() is run
>>>
>>>
>>> yep
>>>
>>>>
>>>> skip (test runner decision)
>>>
>>>
>>> this one is tricky, but we can refine it when we have dep solver...
>>>
>>>> skipIf() (decorator)
>>>> - Test is completely skipped by the test runner
>>>> - Test status = SKIP
>>>> - Neither setUp() nor tearDown() are run.
>>>
>>>
>>> yep
>>>
>>>>
>>>> Uncaught error/exception
>>>> - test status = ERROR
>>>> - tearDown() is run
>>>
>>>
>>> This is the same behavior as PASS | FAIL | WARN, right?
>>>
>>>>
>>>> self.cancel()
>>>> - Can be called during any stage of the test execution.
>>>> Results in test status = CANCEL.
>>>> - tearDown() is run.
>>>
>>>
>>> Again, the same behavior as PASS | FAIL | WARN.
>>>
>>>>
>>>> Test is interrupted (ctrl+C or timeout)
>>>> - test status = INTERRUPT
>>>> - tearDown() is not run
>>>
>>>
>>> On timeout I agree, on single ctrl+c we wait till the test is over. I
>>> think
>>> it'd make sense to let the tearDown finish as well unless the double
>>> ctrl+c
>>> is pressed, what do you think?
>>>
>>> Lukáš
>>>
>>>
>>>>
>>>> Thanks.
>>>> - Ademar
>>>>
>>>>>
>>>>> Currently when you (safely) want to get a few remote servers, nfs share
>>>>> and
>>>>> local service, you have to:
>>>>>
>>>>> class MyTest(test):
>>>>> def setUp(self):
>>>>> self.nfs_share = None
>>>>> self.remote_servers = []
>>>>> self.service = None
>>>>> try:
>>>>> for addr in self.params.get("remote_servers"):
>>>>> self.remote_servers.append(login(addr))
>>>>> self.nfs_share = get_nfs_share()
>>>>> self.service = get_service()
>>>>> except:
>>>>> for server in self.remote_servers:
>>>>> server.close()
>>>>> if self.nfs_share:
>>>>> self.nfs_share.close()
>>>>> if self.service:
>>>>> self.service.stop()
>>>>> raise
>>>>>
>>>>> def tearDown(self):
>>>>> if self.nfs_share:
>>>>> self.nfs_share.close()
>>>>> for server in self.remote_servers:
>>>>> server.close()
>>>>> if self.service:
>>>>> self.service.stop()
>>>>>
>>>>>
>>>>> But if the tearDown was also executed, you'd simply write:
>>>>>
>>>>> class MyTest(test):
>>>>> def setUp(self):
>>>>> self.nfs_share = None
>>>>> self.remote_servers = []
>>>>> self.service = None
>>>>>
>>>>> for addr in self.params.get("remote_servers"):
>>>>> self.remote_servers.append(login(addr))
>>>>> self.nfs_share = get_nfs_share()
>>>>> self.service = get_service()
>>>>>
>>>>> def tearDown(self):
>>>>> if self.nfs_share:
>>>>> self.nfs_share.close()
>>>>> for server in self.remote_servers:
>>>>> server.close()
>>>>> if self.service:
>>>>> self.service.stop()
>>>>>
>>>>> As you can see the current solution requires catching exceptions and
>>>>> basically writing the tearDown twice. Yes, properly written tearDown
>>>>> could
>>>>> be manually executed from the `setUp`, but that looks a bit odd and my
>>>>> experience is that people usually just write:
>>>>>
>>>>> class MyTest(test):
>>>>> def setUp(self):
>>>>> self.remote_servers = []
>>>>> for addr in self.params.get("remote_servers"):
>>>>> self.remote_servers.append(login(addr))
>>>>> self.nfs_share = get_nfs_share()
>>>>> self.service = get_service()
>>>>>
>>>>> def tearDown(self):
>>>>> self.nfs_share.close()
>>>>> for server in self.remote_servers:
>>>>> server.close()
>>>>> self.service.stop()
>>>>>
>>>>> which usually works but when the `get_nfs_share` fails, the
>>>>> remote_servers
>>>>> are not cleaned, which might spoil the following tests (as they might
>>>>> be
>>>>> persistent, eg. via aexpect).
>>>>>
>>>>> Kind regards,
>>>>> Lukáš
>>>>>
>>>>> PS: Yes, the tearDown is unsafe as when `nfs_share.close()` fails the
>>>>> rest
>>>>> is not cleaned up. This is just a demonstration and the proper tearDown
>>>>> and
>>>>> a proper setUp for the current behavior would be way more complex.
>>>>>
>>>>
>>>>
>>>>
>>>>
>>>
>
More information about the Avocado-devel
mailing list