tests: Refactor the Gem5Fixture to derive from UniqueFixture
authorNikos Nikoleris <nikos.nikoleris@arm.com>
Tue, 18 Jun 2019 10:50:24 +0000 (11:50 +0100)
committerNikos Nikoleris <nikos.nikoleris@arm.com>
Fri, 9 Aug 2019 14:03:21 +0000 (14:03 +0000)
Gem5Fixture is used to define a fixture for building the gem5
binary. Most tests are expected to define their own Gem5Fixture,
however, as some might depend on the same binary (e.g.,
./build/ARM/gem5.opt), they will try to re-define a fixture for the
same target. This patchset changes Gem5Fixture to derive from
UniqueFixture.

In addition, this patchset changes the way global fixtures are
discovered to work with the new Gem5Fixture class. Instead of
enumerating them when test definitions are loaded, we do so after the
tests have been filtered according to specified tags (e.g., include
opt variant, exclude fast, debug variants).

Change-Id: Ie868a7e18ef6c3271f3c8a658229657cd43997cb
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/19251
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Giacomo Travaglini <giacomo.travaglini@arm.com>
Maintainer: Giacomo Travaglini <giacomo.travaglini@arm.com>

ext/testlib/fixture.py
ext/testlib/loader.py
ext/testlib/main.py
ext/testlib/wrappers.py
tests/gem5/fixture.py

index be8924474f00683e89e3c5c8df0c747f6334a862..7af6cb289f2752c5eec2c98d2ec59202a5935069 100644 (file)
@@ -32,8 +32,6 @@ import traceback
 import helper
 import log
 
-global_fixtures = []
-
 class SkipException(Exception):
     def __init__(self, fixture, testitem):
         self.fixture = fixture
@@ -71,21 +69,11 @@ class Fixture(object):
         if name is None:
             name = self.__class__.__name__
         self.name = name
+        self._is_global = False
 
     def skip(self, testitem):
         raise SkipException(self.name, testitem.metadata)
 
-    def schedule_finalized(self, schedule):
-        '''
-        This method is called once the schedule of for tests is known.
-        To enable tests to use the same fixture defintion for each execution
-        fixtures must return a copy of themselves in this method.
-
-        :returns: a copy of this fixture which will be setup/torndown
-          when the test item this object is tied to is about to execute.
-        '''
-        return self.copy()
-
     def init(self, *args, **kwargs):
         pass
 
@@ -95,9 +83,6 @@ class Fixture(object):
     def teardown(self, testitem):
         pass
 
-    def copy(self):
-        return copy.deepcopy(self)
-
     def skip_cleanup(self):
         '''
         If this method is called, then we should make sure that nothing is
@@ -105,11 +90,8 @@ class Fixture(object):
         '''
         pass
 
+    def set_global(self):
+        self._is_global = True
 
-def globalfixture(fixture):
-    '''
-    Store the given fixture as a global fixture. Its setup() method
-    will be called before the first test is executed.
-    '''
-    global_fixtures.append(fixture)
-    return fixture
+    def is_global(self):
+        return self._is_global
index e788c33a978c9f7b492f5a133f91ced570a61dd0..8f8f60e707fc578fb58a0d47dc975fec686312ab 100644 (file)
@@ -147,7 +147,7 @@ class Loader(object):
 
     @property
     def schedule(self):
-        return wrappers.LoadedLibrary(self.suites, fixture_mod.global_fixtures)
+        return wrappers.LoadedLibrary(self.suites)
 
     def load_schedule_for_suites(self, *uids):
         files = {uid.UID.uid_to_path(id_) for id_ in uids}
@@ -155,8 +155,7 @@ class Loader(object):
             self.load_file(file_)
 
         return wrappers.LoadedLibrary(
-                [self.suite_uids[id_] for id_ in uids],
-                fixture_mod.global_fixtures)
+                [self.suite_uids[id_] for id_ in uids])
 
     def _verify_no_duplicate_suites(self, new_suites):
         new_suite_uids = self.suite_uids.copy()
index ac795473d82a94ba03ea2581bc21dfc3dcba7b7d..cbba0005f2d836003fc781ccc61974c0da2ed723 100644 (file)
@@ -254,20 +254,6 @@ def run_schedule(test_schedule, log_handler):
 
     log_handler.schedule_finalized(test_schedule)
 
-    # Iterate through all fixtures notifying them of the test schedule.
-    for suite in test_schedule:
-        copied_fixtures = []
-        for fixture in suite.fixtures:
-            copied_fixtures.append(fixture.schedule_finalized(test_schedule))
-        suite.fixtures = copied_fixtures
-
-        for test in suite:
-            copied_fixtures = []
-            for fixture in test.fixtures:
-                copied_fixtures.append(fixture.schedule_finalized(
-                        test_schedule))
-            test.fixtures = copied_fixtures
-
     log.test_log.message(terminal.separator())
     log.test_log.message('Running Tests from {} suites'
             .format(len(test_schedule.suites)), bold=True)
index 4e96f36295f1ac09d86b2f8cace3a9c727378a70..4bd22a468ec781fcf8da5ddf7fa6ac7fc6c50972 100644 (file)
@@ -1,3 +1,15 @@
+# Copyright (c) 2019 ARM Limited
+# All rights reserved
+#
+# The license below extends only to copyright in the software and shall
+# not be construed as granting a license to any other intellectual
+# property including but not limited to intellectual property relating
+# to a hardware implementation of the functionality of the software
+# licensed hereunder.  You may use the software subject to the license
+# terms below provided that you ensure that this notice is replicated
+# unmodified and in its entirety in all distributions of the software,
+# modified or unmodified, in source code or in binary form.
+#
 # Copyright (c) 2017 Mark D. Hill and David A. Wood
 # All rights reserved.
 #
@@ -179,9 +191,8 @@ class LoadedLibrary(LoadedTestable):
     Wraps a collection of all loaded test suites and
     provides utility functions for accessing fixtures.
     '''
-    def __init__(self, suites, global_fixtures):
+    def __init__(self, suites):
         LoadedTestable.__init__(self, suites)
-        self.global_fixtures = global_fixtures
 
     def _generate_metadata(self):
         return LibraryMetadata( **{
@@ -196,19 +207,13 @@ class LoadedLibrary(LoadedTestable):
         '''
         return iter(self.obj)
 
-    def all_fixture_tuples(self):
-        return itertools.chain(
-                self.global_fixtures,
-                *(suite.fixtures for suite in self.obj))
-
     def all_fixtures(self):
         '''
         :returns: an interator overall all global, suite,
           and test fixtures
         '''
         return itertools.chain(itertools.chain(
-                self.global_fixtures,
-                *(suite.fixtures for suite in self.obj)),
+            *(suite.fixtures for suite in self.obj)),
             *(self.test_fixtures(suite) for suite in self.obj)
         )
 
@@ -221,7 +226,11 @@ class LoadedLibrary(LoadedTestable):
 
     @property
     def fixtures(self):
-        return self.global_fixtures
+        global_fixtures = []
+        for fixture in self.all_fixtures():
+            if fixture.is_global():
+                global_fixtures.append(fixture)
+        return global_fixtures
 
     @property
     def uid(self):
index b04a334b0d0c1639cba84cc4f2fd394c8b587a7b..e84b89f6992ecf6c1b561a915dc69de682c5e51d 100644 (file)
@@ -43,7 +43,7 @@ import tempfile
 import shutil
 import threading
 
-from testlib.fixture import Fixture, globalfixture
+from testlib.fixture import Fixture
 from testlib.config import config, constants
 from testlib.helper import log_call, cacheresult, joinpath, absdirpath
 import testlib.log as log
@@ -112,7 +112,7 @@ class UniqueFixture(Fixture):
             self._setup(testitem)
 
 
-class SConsFixture(Fixture):
+class SConsFixture(UniqueFixture):
     '''
     Fixture will wait until all SCons targets are collected and tests are
     about to be ran, then will invocate a single instance of SCons for all
@@ -121,21 +121,18 @@ class SConsFixture(Fixture):
     :param directory: The directory which scons will -C (cd) into before
         executing. If None is provided, will choose the config base_dir.
     '''
-    def __init__(self, directory=None, target_class=None, options=[]):
-        self.directory = directory if directory else config.base_dir
-        self.target_class = target_class if target_class else SConsTarget
-        self.threads = config.threads
-        self.targets = set()
-        self.options = options
-        super(SConsFixture, self).__init__()
 
-    def setup(self, testitem):
+    def __new__(cls, target):
+        obj = super(SConsFixture, cls).__new__(cls, target)
+        return obj
+
+    def _setup(self, testitem):
         if config.skip_build:
             return
 
         command = [
             'scons', '-C', self.directory,
-            '-j', str(self.threads),
+            '-j', str(config.threads),
             '--ignore-style'
         ]
 
@@ -159,70 +156,27 @@ class SConsFixture(Fixture):
             command.extend(self.options)
         log_call(log.test_log, command)
 
-
-class SConsTarget(Fixture):
-    # The singleton scons fixture we'll use for all targets.
-    default_scons_invocation = None
-
-    def __init__(self, target, build_dir=None, invocation=None):
-        '''
-        Represents a target to be built by an 'invocation' of scons.
-
-        :param target: The target known to scons.
-
-        :param build_dir: The 'build' directory path which will be prepended
-            to the target name.
-
-        :param invocation: Represents an invocation of scons which we will
-            automatically attach this target to. If None provided, uses the
-            main 'scons' invocation.
-        '''
-
-        if build_dir is None:
-            build_dir = config.build_dir
-        self.target = os.path.join(build_dir, target)
-        super(SConsTarget, self).__init__(name=target)
-
-        if invocation is None:
-            if self.default_scons_invocation is None:
-                SConsTarget.default_scons_invocation = SConsFixture()
-                globalfixture(SConsTarget.default_scons_invocation)
-
-            invocation = self.default_scons_invocation
-        self.invocation = invocation
-
-    def schedule_finalized(self, schedule):
-        self.invocation.targets.add(self.target)
-        return Fixture.schedule_finalized(self, schedule)
-
-class Gem5Fixture(SConsTarget):
-    other_invocations = {} # stores scons invocations other than the default
-
-    def __init__(self, isa, variant, protocol=None):
+class Gem5Fixture(SConsFixture):
+    def __new__(cls, isa, variant, protocol=None):
+        target_dir = joinpath(config.build_dir, isa.upper())
         if protocol:
-            # When specifying an non-default protocol, we have to make a
-            # separate scons invocation with specific parameters. However, if
-            # more than one tests needs the same target, we need to make sure
-            # that we don't call scons too many times.
-            target_dir = isa.upper()+'-'+protocol
-            target = joinpath(target_dir, 'gem5.%s' % variant)
-            if target_dir in self.other_invocations.keys():
-                invocation = self.other_invocations[target_dir]
-            else:
-                options = ['PROTOCOL='+protocol, '--default='+isa.upper()]
-                invocation = SConsFixture(options=options)
-                globalfixture(invocation)
-                Gem5Fixture.other_invocations[target_dir] = invocation
-        else:
-            target = joinpath(isa.upper(), 'gem5.%s' % variant)
-            invocation = None # use default
-        super(Gem5Fixture, self).__init__(target, invocation=invocation)
+            target_dir += '_' + protocol
+        target = joinpath(target_dir, 'gem5.%s' % variant)
+        obj = super(Gem5Fixture, cls).__new__(cls, target)
+        return obj
 
+    def _init(self, isa, variant, protocol=None):
         self.name = constants.gem5_binary_fixture_name
+
+        self.targets = [self.target]
         self.path = self.target
-        self.isa = isa
-        self.variant = variant
+        self.directory = config.base_dir
 
+        self.options = []
+        if protocol:
+            self.options = [ '--default=' + isa.upper(),
+                             'PROTOCOL=' + protocol ]
+        self.set_global()
 
 class MakeFixture(Fixture):
     def __init__(self, directory, *args, **kwargs):