112882Sspwilson2@wisc.edu# Copyright (c) 2017 Mark D. Hill and David A. Wood
212882Sspwilson2@wisc.edu# All rights reserved.
312882Sspwilson2@wisc.edu#
412882Sspwilson2@wisc.edu# Redistribution and use in source and binary forms, with or without
512882Sspwilson2@wisc.edu# modification, are permitted provided that the following conditions are
612882Sspwilson2@wisc.edu# met: redistributions of source code must retain the above copyright
712882Sspwilson2@wisc.edu# notice, this list of conditions and the following disclaimer;
812882Sspwilson2@wisc.edu# redistributions in binary form must reproduce the above copyright
912882Sspwilson2@wisc.edu# notice, this list of conditions and the following disclaimer in the
1012882Sspwilson2@wisc.edu# documentation and/or other materials provided with the distribution;
1112882Sspwilson2@wisc.edu# neither the name of the copyright holders nor the names of its
1212882Sspwilson2@wisc.edu# contributors may be used to endorse or promote products derived from
1312882Sspwilson2@wisc.edu# this software without specific prior written permission.
1412882Sspwilson2@wisc.edu#
1512882Sspwilson2@wisc.edu# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1612882Sspwilson2@wisc.edu# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1712882Sspwilson2@wisc.edu# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1812882Sspwilson2@wisc.edu# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1912882Sspwilson2@wisc.edu# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2012882Sspwilson2@wisc.edu# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2112882Sspwilson2@wisc.edu# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2212882Sspwilson2@wisc.edu# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2312882Sspwilson2@wisc.edu# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2412882Sspwilson2@wisc.edu# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2512882Sspwilson2@wisc.edu# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2612882Sspwilson2@wisc.edu#
2712882Sspwilson2@wisc.edu# Authors: Sean Wilson
2812882Sspwilson2@wisc.edu
2912882Sspwilson2@wisc.eduimport multiprocessing.dummy
3012882Sspwilson2@wisc.eduimport threading
3112882Sspwilson2@wisc.eduimport traceback
3212882Sspwilson2@wisc.edu
3312882Sspwilson2@wisc.eduimport helper
3412882Sspwilson2@wisc.eduimport state
3512882Sspwilson2@wisc.eduimport log
3612882Sspwilson2@wisc.eduimport sandbox
3712882Sspwilson2@wisc.edu
3812882Sspwilson2@wisc.edufrom state import Status, Result
3912882Sspwilson2@wisc.edufrom fixture import SkipException
4012882Sspwilson2@wisc.edu
4112882Sspwilson2@wisc.edudef compute_aggregate_result(iterable):
4212882Sspwilson2@wisc.edu    '''
4312882Sspwilson2@wisc.edu    Status of the test suite by default is:
4412882Sspwilson2@wisc.edu    * Passed if all contained tests passed
4512882Sspwilson2@wisc.edu    * Errored if any contained tests errored
4612882Sspwilson2@wisc.edu    * Failed if no tests errored, but one or more failed.
4712882Sspwilson2@wisc.edu    * Skipped if all contained tests were skipped
4812882Sspwilson2@wisc.edu    '''
4912882Sspwilson2@wisc.edu    failed = []
5012882Sspwilson2@wisc.edu    skipped = []
5112882Sspwilson2@wisc.edu    for testitem in iterable:
5212882Sspwilson2@wisc.edu        result = testitem.result
5312882Sspwilson2@wisc.edu
5412882Sspwilson2@wisc.edu        if result.value == Result.Errored:
5512882Sspwilson2@wisc.edu            return Result(result.value, result.reason)
5612882Sspwilson2@wisc.edu        elif result.value == Result.Failed:
5712882Sspwilson2@wisc.edu            failed.append(result.reason)
5812882Sspwilson2@wisc.edu        elif result.value == result.Skipped:
5912882Sspwilson2@wisc.edu            skipped.append(result.reason)
6012882Sspwilson2@wisc.edu    if failed:
6112882Sspwilson2@wisc.edu        return Result(Result.Failed, failed)
6212882Sspwilson2@wisc.edu    elif skipped:
6312882Sspwilson2@wisc.edu        return Result(Result.Skipped, skipped)
6412882Sspwilson2@wisc.edu    else:
6512882Sspwilson2@wisc.edu        return Result(Result.Passed)
6612882Sspwilson2@wisc.edu
6712882Sspwilson2@wisc.educlass TestParameters(object):
6812882Sspwilson2@wisc.edu    def __init__(self, test, suite):
6912882Sspwilson2@wisc.edu        self.test = test
7012882Sspwilson2@wisc.edu        self.suite = suite
7112882Sspwilson2@wisc.edu        self.log = log.TestLogWrapper(log.test_log, test, suite)
7212882Sspwilson2@wisc.edu
7312882Sspwilson2@wisc.edu    @helper.cacheresult
7412882Sspwilson2@wisc.edu    def _fixtures(self):
7512882Sspwilson2@wisc.edu        fixtures = {fixture.name:fixture for fixture in self.suite.fixtures}
7612882Sspwilson2@wisc.edu        for fixture in self.test.fixtures:
7712882Sspwilson2@wisc.edu            fixtures[fixture.name] = fixture
7812882Sspwilson2@wisc.edu        return fixtures
7912882Sspwilson2@wisc.edu
8012882Sspwilson2@wisc.edu    @property
8112882Sspwilson2@wisc.edu    def fixtures(self):
8212882Sspwilson2@wisc.edu        return self._fixtures()
8312882Sspwilson2@wisc.edu
8412882Sspwilson2@wisc.edu
8512882Sspwilson2@wisc.educlass RunnerPattern:
8612882Sspwilson2@wisc.edu    def __init__(self, loaded_testable):
8712882Sspwilson2@wisc.edu        self.testable = loaded_testable
8812882Sspwilson2@wisc.edu        self.builder = FixtureBuilder(self.testable.fixtures)
8912882Sspwilson2@wisc.edu
9012882Sspwilson2@wisc.edu    def handle_error(self, trace):
9112882Sspwilson2@wisc.edu        self.testable.result = Result(Result.Errored, trace)
9212882Sspwilson2@wisc.edu        self.avoid_children(trace)
9312882Sspwilson2@wisc.edu
9412882Sspwilson2@wisc.edu    def handle_skip(self, trace):
9512882Sspwilson2@wisc.edu        self.testable.result = Result(Result.Skipped, trace)
9612882Sspwilson2@wisc.edu        self.avoid_children(trace)
9712882Sspwilson2@wisc.edu
9812882Sspwilson2@wisc.edu    def avoid_children(self, reason):
9912882Sspwilson2@wisc.edu        for testable in self.testable:
10012882Sspwilson2@wisc.edu            testable.result = Result(self.testable.result.value, reason)
10112882Sspwilson2@wisc.edu            testable.status = Status.Avoided
10212882Sspwilson2@wisc.edu
10312882Sspwilson2@wisc.edu    def test(self):
10412882Sspwilson2@wisc.edu        pass
10512882Sspwilson2@wisc.edu
10612882Sspwilson2@wisc.edu    def run(self):
10712882Sspwilson2@wisc.edu        avoided = False
10812882Sspwilson2@wisc.edu        try:
10912882Sspwilson2@wisc.edu            self.testable.status = Status.Building
11012882Sspwilson2@wisc.edu            self.builder.setup(self.testable)
11112882Sspwilson2@wisc.edu        except SkipException:
11212882Sspwilson2@wisc.edu            self.handle_skip(traceback.format_exc())
11312882Sspwilson2@wisc.edu            avoided = True
11412882Sspwilson2@wisc.edu        except BrokenFixtureException:
11512882Sspwilson2@wisc.edu            self.handle_error(traceback.format_exc())
11612882Sspwilson2@wisc.edu            avoided = True
11712882Sspwilson2@wisc.edu        else:
11812882Sspwilson2@wisc.edu            self.testable.status = Status.Running
11912882Sspwilson2@wisc.edu            self.test()
12012882Sspwilson2@wisc.edu        finally:
12112882Sspwilson2@wisc.edu            self.testable.status = Status.TearingDown
12212882Sspwilson2@wisc.edu            self.builder.teardown(self.testable)
12312882Sspwilson2@wisc.edu
12412882Sspwilson2@wisc.edu        if avoided:
12512882Sspwilson2@wisc.edu            self.testable.status = Status.Avoided
12612882Sspwilson2@wisc.edu        else:
12712882Sspwilson2@wisc.edu            self.testable.status = Status.Complete
12812882Sspwilson2@wisc.edu
12912882Sspwilson2@wisc.educlass TestRunner(RunnerPattern):
13012882Sspwilson2@wisc.edu    def test(self):
13112882Sspwilson2@wisc.edu        self.sandbox_test()
13212882Sspwilson2@wisc.edu
13312882Sspwilson2@wisc.edu    def sandbox_test(self):
13412882Sspwilson2@wisc.edu        try:
13512882Sspwilson2@wisc.edu            sandbox.Sandbox(TestParameters(
13612882Sspwilson2@wisc.edu                    self.testable,
13712882Sspwilson2@wisc.edu                    self.testable.parent_suite))
13812882Sspwilson2@wisc.edu        except sandbox.SubprocessException:
13912882Sspwilson2@wisc.edu            self.testable.result = Result(Result.Failed,
14012882Sspwilson2@wisc.edu                    traceback.format_exc())
14112882Sspwilson2@wisc.edu        else:
14212882Sspwilson2@wisc.edu            self.testable.result = Result(Result.Passed)
14312882Sspwilson2@wisc.edu
14412882Sspwilson2@wisc.edu
14512882Sspwilson2@wisc.educlass SuiteRunner(RunnerPattern):
14612882Sspwilson2@wisc.edu    def test(self):
14712882Sspwilson2@wisc.edu        for test in self.testable:
14812882Sspwilson2@wisc.edu            test.runner(test).run()
14912882Sspwilson2@wisc.edu        self.testable.result = compute_aggregate_result(
15012882Sspwilson2@wisc.edu                iter(self.testable))
15112882Sspwilson2@wisc.edu
15212882Sspwilson2@wisc.edu
15312882Sspwilson2@wisc.educlass LibraryRunner(SuiteRunner):
15412882Sspwilson2@wisc.edu    pass
15512882Sspwilson2@wisc.edu
15612882Sspwilson2@wisc.edu
15712882Sspwilson2@wisc.educlass LibraryParallelRunner(RunnerPattern):
15812882Sspwilson2@wisc.edu    def set_threads(self, threads):
15912882Sspwilson2@wisc.edu        self.threads = threads
16012882Sspwilson2@wisc.edu
16112882Sspwilson2@wisc.edu    def _entrypoint(self, suite):
16212882Sspwilson2@wisc.edu        suite.runner(suite).run()
16312882Sspwilson2@wisc.edu
16412882Sspwilson2@wisc.edu    def test(self):
16512882Sspwilson2@wisc.edu        pool = multiprocessing.dummy.Pool(self.threads)
16612882Sspwilson2@wisc.edu        pool.map(lambda suite : suite.runner(suite).run(), self.testable)
16712882Sspwilson2@wisc.edu        self.testable.result = compute_aggregate_result(
16812882Sspwilson2@wisc.edu                iter(self.testable))
16912882Sspwilson2@wisc.edu
17012882Sspwilson2@wisc.edu
17112882Sspwilson2@wisc.educlass BrokenFixtureException(Exception):
17212882Sspwilson2@wisc.edu    def __init__(self, fixture, testitem, trace):
17312882Sspwilson2@wisc.edu        self.fixture = fixture
17412882Sspwilson2@wisc.edu        self.testitem = testitem
17512882Sspwilson2@wisc.edu        self.trace = trace
17612882Sspwilson2@wisc.edu
17712882Sspwilson2@wisc.edu        self.msg = ('%s\n'
17812882Sspwilson2@wisc.edu                   'Exception raised building "%s" raised SkipException'
17912882Sspwilson2@wisc.edu                   ' for "%s".' %
18012882Sspwilson2@wisc.edu                   (trace, fixture.name, testitem.name)
18112882Sspwilson2@wisc.edu        )
18212882Sspwilson2@wisc.edu        super(BrokenFixtureException, self).__init__(self.msg)
18312882Sspwilson2@wisc.edu
18412882Sspwilson2@wisc.educlass FixtureBuilder(object):
18512882Sspwilson2@wisc.edu    def __init__(self, fixtures):
18612882Sspwilson2@wisc.edu        self.fixtures = fixtures
18712882Sspwilson2@wisc.edu        self.built_fixtures = []
18812882Sspwilson2@wisc.edu
18912882Sspwilson2@wisc.edu    def setup(self, testitem):
19012882Sspwilson2@wisc.edu        for fixture in self.fixtures:
19112882Sspwilson2@wisc.edu            # Mark as built before, so if the build fails
19212882Sspwilson2@wisc.edu            # we still try to tear it down.
19312882Sspwilson2@wisc.edu            self.built_fixtures.append(fixture)
19412882Sspwilson2@wisc.edu            try:
19512882Sspwilson2@wisc.edu                fixture.setup(testitem)
19612882Sspwilson2@wisc.edu            except SkipException:
19712882Sspwilson2@wisc.edu                raise
19812882Sspwilson2@wisc.edu            except Exception as e:
19912882Sspwilson2@wisc.edu                exc = traceback.format_exc()
20012882Sspwilson2@wisc.edu                msg = 'Exception raised while setting up fixture for %s' %\
20112882Sspwilson2@wisc.edu                        testitem.uid
20212882Sspwilson2@wisc.edu                log.test_log.warn('%s\n%s' % (exc, msg))
20312882Sspwilson2@wisc.edu
20412882Sspwilson2@wisc.edu                raise BrokenFixtureException(fixture, testitem,
20512882Sspwilson2@wisc.edu                        traceback.format_exc())
20612882Sspwilson2@wisc.edu
20712882Sspwilson2@wisc.edu    def teardown(self, testitem):
20812882Sspwilson2@wisc.edu        for fixture in self.built_fixtures:
20912882Sspwilson2@wisc.edu            try:
21012882Sspwilson2@wisc.edu                fixture.teardown(testitem)
21112882Sspwilson2@wisc.edu            except Exception:
21212882Sspwilson2@wisc.edu                # Log exception but keep cleaning up.
21312882Sspwilson2@wisc.edu                exc = traceback.format_exc()
21412882Sspwilson2@wisc.edu                msg = 'Exception raised while tearing down fixture for %s' %\
21512882Sspwilson2@wisc.edu                        testitem.uid
21612882Sspwilson2@wisc.edu                log.test_log.warn('%s\n%s' % (exc, msg))
217