[Avocado-devel] miniRFC: Call `tearDown` after `setUp` failure
Amador Pahim
apahim at redhat.com
Thu Mar 23 09:18:26 UTC 2017
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 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