tests: Accept SourceFilters as sources for GTest.

This change introduces the idea of a SourceFilter which is an object
that can filter a SourceList and which can be composed with other
SourceFilters using | and & operators. This means a filter can be
constructed ahead of time, possibly before all sources have been
discovered, and then later applied to any SourceList necessary.

This change also modifies GTest so that it accepts SourceFilters in
addition to normal source files. These filters will be applied to the
final list of all sources, and the result included in the build for
that test.

By default, gtests will build in all sources tagged with 'gtest lib'.
This change also introduces the keyword argument "skip_lib" which will
exclude those files. They can then be left out entirely, or they can be
re-included as part of a more elaborate filter. That would be useful if
someone wanted to write a unit test for, for instance, the warn, etc.
macros which rely on the gtest logging support. Those classes could
be replaced by something under the control of the unit test, while
still including the rest of the gtest library.

Change-Id: I13a846dc884b86b9fdcaf809edefd57bb4168b8e
Reviewed-on: https://gem5-review.googlesource.com/6262
Reviewed-by: Andreas Sandberg <andreas.sandberg@arm.com>
Maintainer: Andreas Sandberg <andreas.sandberg@arm.com>
diff --git a/src/SConscript b/src/SConscript
index cd42c27..7cd7116 100755
--- a/src/SConscript
+++ b/src/SConscript
@@ -30,6 +30,7 @@
 
 import array
 import bisect
+import functools
 import imp
 import marshal
 import os
@@ -62,32 +63,68 @@
 # When specifying a source file of some type, a set of tags can be
 # specified for that file.
 
+class SourceFilter(object):
+    def __init__(self, predicate):
+        self.predicate = predicate
+
+    def __or__(self, other):
+        return SourceFilter(lambda tags: self.predicate(tags) or
+                                         other.predicate(tags))
+
+    def __and__(self, other):
+        return SourceFilter(lambda tags: self.predicate(tags) and
+                                         other.predicate(tags))
+
+def with_tags_that(predicate):
+    '''Return a list of sources with tags that satisfy a predicate.'''
+    return SourceFilter(predicate)
+
+def with_any_tags(*tags):
+    '''Return a list of sources with any of the supplied tags.'''
+    return SourceFilter(lambda stags: len(set(tags) & stags) > 0)
+
+def with_all_tags(*tags):
+    '''Return a list of sources with all of the supplied tags.'''
+    return SourceFilter(lambda stags: set(tags) <= stags)
+
+def with_tag(tag):
+    '''Return a list of sources with the supplied tag.'''
+    return SourceFilter(lambda stags: tag in stags)
+
+def without_tags(*tags):
+    '''Return a list of sources without any of the supplied tags.'''
+    return SourceFilter(lambda stags: len(set(tags) & stags) == 0)
+
+def without_tag(tag):
+    '''Return a list of sources with the supplied tag.'''
+    return SourceFilter(lambda stags: tag not in stags)
+
+source_filter_factories = {
+    'with_tags_that': with_tags_that,
+    'with_any_tags': with_any_tags,
+    'with_all_tags': with_all_tags,
+    'with_tag': with_tag,
+    'without_tags': without_tags,
+    'without_tag': without_tag,
+}
+
+Export(source_filter_factories)
+
 class SourceList(list):
-    def with_tags_that(self, predicate):
-        '''Return a list of sources with tags that satisfy a predicate.'''
+    def apply_filter(self, f):
         def match(source):
-            return predicate(source.tags)
+            return f.predicate(source.tags)
         return SourceList(filter(match, self))
 
-    def with_any_tags(self, *tags):
-        '''Return a list of sources with any of the supplied tags.'''
-        return self.with_tags_that(lambda stags: len(set(tags) & stags) > 0)
+    def __getattr__(self, name):
+        func = source_filter_factories.get(name, None)
+        if not func:
+            raise AttributeError
 
-    def with_all_tags(self, *tags):
-        '''Return a list of sources with all of the supplied tags.'''
-        return self.with_tags_that(lambda stags: set(tags) <= stags)
-
-    def with_tag(self, tag):
-        '''Return a list of sources with the supplied tag.'''
-        return self.with_tags_that(lambda stags: tag in stags)
-
-    def without_tags(self, *tags):
-        '''Return a list of sources without any of the supplied tags.'''
-        return self.with_tags_that(lambda stags: len(set(tags) & stags) == 0)
-
-    def without_tag(self, tag):
-        '''Return a list of sources with the supplied tag.'''
-        return self.with_tags_that(lambda stags: tag not in stags)
+        @functools.wraps(func)
+        def wrapper(*args, **kwargs):
+            return self.apply_filter(func(*args, **kwargs))
+        return wrapper
 
 class SourceMeta(type):
     '''Meta class for source files that keeps track of all files of a
@@ -294,11 +331,14 @@
 
 class GTest(UnitTest):
     '''Create a unit test based on the google test framework.'''
-
     all = []
     def __init__(self, *args, **kwargs):
+        isFilter = lambda arg: isinstance(arg, SourceFilter)
+        self.filters = filter(isFilter, args)
+        args = filter(lambda a: not isFilter(a), args)
         super(GTest, self).__init__(*args, **kwargs)
         self.dir = Dir('.')
+        self.skip_lib = kwargs.pop('skip_lib', False)
 
 # Children should have access
 Export('Source')
@@ -1049,9 +1089,14 @@
     gtest_env = new_env.Clone()
     gtest_env.Append(LIBS=gtest_env['GTEST_LIBS'])
     gtest_env.Append(CPPFLAGS=gtest_env['GTEST_CPPFLAGS'])
+    gtestlib_sources = Source.all.with_tag('gtest lib')
     gtests = []
     for test in GTest.all:
-        test_sources = Source.all.with_tag(str(test.target))
+        test_sources = test.sources
+        if not test.skip_lib:
+            test_sources += gtestlib_sources
+        for f in test.filters:
+            test_sources += Source.all.apply_filter(f)
         test_objs = [ s.static(gtest_env) for s in test_sources ]
         gtests.append(gtest_env.Program(
             test.dir.File('%s.%s' % (test.target, label)), test_objs))