[Avocado-devel] miniRFC: Call `tearDown` after `setUp` failure

Ademar Reis areis at redhat.com
Thu Mar 23 12:19:56 UTC 2017


On Thu, Mar 23, 2017 at 07:10:23AM +0100, Lukáš Doktor 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.
> > 
> > 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...

My understanding is that there are cases where the skip happens
as a decision from the test runner, such as during job replay
and/or timeout. The behavior of such a skip should be exactly the
same as when skipIf() is used.

> 
> >  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?

Right, I see no reason why not.

> 
> > 
> >  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.

Ditto.

> 
> > 
> >  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?

Agree. What I had in mind was the double ctrl+c.

Thanks.
   - Ademar

> 
> 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.
> > > 
> > 
> > 
> > 
> > 
> 




-- 
Ademar Reis
Red Hat

^[:wq!




More information about the Avocado-devel mailing list