Revision: 2518
Author: jprantan
Date: Tue Feb 23 05:28:30 2010
Log: Added initial implementation for NOT for include and exclude. Issue 472.
http://code.google.com/p/robotframework/source/detail?r=2518

Modified:
 /trunk/src/robot/common/model.py
 /trunk/utest/common/test_baseobjects.py

=======================================
--- /trunk/src/robot/common/model.py    Sun Apr 19 13:26:54 2009
+++ /trunk/src/robot/common/model.py    Tue Feb 23 05:28:30 2010
@@ -30,7 +30,7 @@
         self.teardown = None
         self.status = 'NOT_RUN'
         self.message = ''
-
+
     def __getattr__(self, name):
         if name == 'htmldoc':
             return utils.html_escape(self.doc, formatting=True)
@@ -58,7 +58,7 @@
             self.message = message
         else:
             self.message += '\n\nAlso ' + message[0].lower() + message[1:]
-
+
     def __str__(self):
         return self.name

@@ -80,13 +80,13 @@
         self.all_stats = Stat()
         if parent:
             parent.suites.append(self)
-
+
     def set_name(self, name):
         if name:
             self.name = name
         elif not self.parent and self.name == '':  # MultiSourceSuite
             self.name = ' & '.join([suite.name for suite in self.suites])
-
+
     def set_critical_tags(self, critical, non_critical):
         if critical is not None or non_critical is not None:
             self.critical.set(critical, non_critical)
@@ -98,11 +98,11 @@
             suite._set_critical_tags(critical)
         for test in self.tests:
             test.set_criticality(critical)
-
+
     def set_doc(self, doc):
         if doc is not None:
             self.doc = doc
-
+
     def set_metadata(self, metalist):
         for metastr in metalist:
             try:
@@ -110,7 +110,7 @@
             except ValueError:
                 name, value = metastr, ''
self.metadata[utils.printable_name(name.replace('_',' '))] = value
-
+
     def get_metadata(self, html=False):
         names = self.metadata.keys()
         names.sort()
@@ -133,7 +133,7 @@
         if not html:
             return '%s\n\n%s' % (self.message, stat_msg)
return '%s<br /><br />%s' % (utils.html_escape(self.message), stat_msg)
-
+
     def get_stat_message(self, html=False):
         ctotal, cend, cpass, cfail = self._get_counts(self.critical_stats)
         atotal, aend, apass, afail = self._get_counts(self.all_stats)
@@ -152,7 +152,7 @@
         total = stat.passed + stat.failed
         ending = utils.plural_or_not(total)
         return total, ending, stat.passed, stat.failed
-
+
     def set_status(self):
         """Sets status and statistics based on subsuite and test statuses.

@@ -197,7 +197,7 @@
                 test.tags = utils.normalize_tags(test.tags + tags)
             for suite in self.suites:
                 suite.set_tags(tags)
-
+
def filter(self, suites=None, tests=None, includes=None, excludes=None):
         self.filter_by_names(suites, tests)
         self.filter_by_tags(includes, excludes)
@@ -218,13 +218,13 @@
         else:
             self.tests = []
         return self.suites or self.tests
-
+
     def _filter_suite_names(self, suites):
         try:
             return [ self._filter_suite_name(p, s) for p, s in suites ]
         except StopIteration:
             return []
-
+
     def _filter_suite_name(self, parent, suite):
         if utils.matches(self.name, suite[0], ignore=['_']):
             if len(suite) == 1:
@@ -243,7 +243,7 @@
         else:
             msg = 'test cases %s in suites %s.' % (tests, suites)
         raise DataError("Suite '%s' contains no %s" % (self.name, msg))
-
+
     def filter_by_tags(self, includes=None, excludes=None):
         if not (includes or excludes):
             return
@@ -251,7 +251,7 @@
         if not excludes: excludes = []
         if not self._filter_by_tags(includes, excludes):
             self._raise_no_tests_filtered_by_tags(includes, excludes)
-
+
     def _filter_by_tags(self, incls, excls):
         self.suites = [ suite for suite in self.suites
                         if suite._filter_by_tags(incls, excls) ]
@@ -270,7 +270,7 @@
         if excl:
             msg += 'excludes %s ' % excl
         raise DataError(msg + 'contains no test cases.')
-
+
     def set_runmode(self, runmode):
         runmode = runmode.upper()
         if runmode == 'EXITONFAILURE':
@@ -286,7 +286,7 @@
             return
         for suite in self.suites:
             suite.set_runmode(runmode)
-
+
     def set_options(self, settings):
         self.set_tags(settings['SetTag'])
         self.filter(settings['SuiteNames'], settings['TestNames'],
@@ -337,16 +337,54 @@

     def is_included(self, incl_tags, excl_tags):
         """Returns True if this test case is included but not excluded.
-
+
If no 'incl_tags' are given all tests are considered to be included.
         """
-        included = not incl_tags or self._contains_any_tag(incl_tags)
-        excluded = self._contains_any_tag(excl_tags)
+        included = not incl_tags or self._matches_tag_rules(incl_tags)
+        excluded = self._matches_tag_rules(excl_tags)
         return included and not excluded

+    def _matches_tag_rules(self, tag_rules):
+        """Returns True if tag_rules matches self.tags
+
+ Matching equals supporting AND, & and NOT boolean operators and simple + pattern matching. NOT is 'or' operation meaning if any of the NOTs is
+        matching, False is returned.
+        """
+        if not tag_rules:
+            return False
+        for rule in tag_rules:
+            if not self._matches_tag_rule(rule):
+                return False
+        return True
+
+    def _matches_tag_rule(self, tag_rule):
+ that_should_be, that_should_not_be = self._split_boolean_operators(tag_rule)
+        if self._contains_any_tag(that_should_not_be):
+            return False
+        if self._contains_tag(that_should_be):
+            return True
+        return False
+
+    def _split_boolean_operators(self, tag_rule):
+        if not tag_rule:
+            return self._empty_split()
+        if 'NOT' in tag_rule:
+            return self._split_nots(tag_rule)
+        return tag_rule, []
+
+    def _empty_split(self):
+        return '', []
+
+    def _split_nots(self, tag_rule):
+        parts = [ utils.normalize(t) for t in tag_rule.split('NOT') ]
+        if '' not in parts:
+            return parts[0], parts[1:]
+        return self._empty_split()
+
     def _contains_any_tag(self, tags):
"""Returns True if any of the given tags matches a tag from self.tags.
-
+
         Note that one tag may be ANDed combination of multiple tags (e.g.
         tag1&tag2) and then all of them must match some tag from selg.tags.
         """
@@ -354,10 +392,10 @@
             if self._contains_tag(tag):
                 return True
         return False
-
+
     def _contains_tag(self, tag):
         """Returns True if given tag matches any tag from self.tags.
-
+
         Note that given tag may be ANDed combination of multiple tags (e.g.
         tag1&tag2) and then all of them must match some tag from selg.tags.
         """
@@ -365,7 +403,7 @@
             if not utils.any_matches(self.tags, item):
                 return False
         return True
-
+
     def __cmp__(self, other):
         if self.status != other.status:
             return self.status == 'FAIL' and -1 or 1
@@ -375,7 +413,7 @@
             return cmp(self.longname, other.longname)
         except AttributeError:
             return cmp(self.name, other.name)
-
+
     def serialize(self, serializer):
         serializer.start_test(self)
         if self.setup is not None:
@@ -391,19 +429,19 @@
             return split_level + 1
         return split_level

-
+
 class _Critical:
-
+
     def __init__(self, tags=None, nons=None):
         self.set(tags, nons)

     def set(self, tags, nons):
         self.tags = utils.normalize_tags(utils.to_list(tags))
         self.nons = utils.normalize_tags(utils.to_list(nons))
-
+
     def is_critical(self, tag):
         return utils.matches_any(tag, self.tags)
-
+
     def is_non_critical(self, tag):
         return utils.matches_any(tag, self.nons)

=======================================
--- /trunk/utest/common/test_baseobjects.py     Mon Apr  6 05:28:47 2009
+++ /trunk/utest/common/test_baseobjects.py     Tue Feb 23 05:28:30 2010
@@ -1,7 +1,8 @@
 import unittest
 import sys

-from robot.utils.asserts import assert_equals, assert_raises_with_msg
+from robot.utils.asserts import assert_equals, assert_raises_with_msg,\
+    assert_true, assert_false
 from robot import utils
 from robot.errors import DataError

@@ -194,5 +195,73 @@
         self._test(['?a?3'], ['tag*'], 'no')


+class TC(BaseTestCase):
+
+    def __init__(self, tags=[]):
+        self.tags = tags
+
+class TestFilterByTags(unittest.TestCase):
+
+    def setUp(self):
+        self._tag1 = TC(['tag1'])
+        self._tags12 = TC(['tag1', 'tag2'])
+        self._tags123 = TC(['tag1', 'tag2', 'tag3'])
+
+    def test_no_tags_no_incl_no_excl(self):
+        assert_true(TC().is_included([], []))
+
+    def test_tags_no_incl_no_excl(self):
+        assert_true(self._tags12.is_included([], []))
+
+    def test_simple_include(self):
+        assert_true(self._tag1.is_included(['tag1'], []))
+        assert_false(self._tag1.is_included(['tag2'], []))
+
+    def test_simple_exclude(self):
+        assert_false(self._tag1.is_included([], ['tag1']))
+        assert_true(self._tag1.is_included([], ['tag2']))
+
+    def test_include_and_exclude(self):
+        assert_false(self._tags12.is_included(['tag1'], ['tag2']))
+
+    def test_include_with_and(self):
+        assert_true(self._tags12.is_included(['tag1&tag2'], []))
+        assert_false(self._tags12.is_included(['tag1&tag3'], []))
+
+    def test_exclude_with_and(self):
+        assert_false(self._tags12.is_included([], ['tag1&tag2']))
+        assert_true(self._tags12.is_included([], ['tag1&tag3']))
+
+    def test_include_with_not(self):
+        assert_false(self._tags12.is_included(['tag1NOTtag2'], []))
+        assert_true(self._tags12.is_included(['tag1NOTtag3'], []))
+
+    def test_exclude_with_not(self):
+        assert_true(self._tags12.is_included([], ['tag1NOTtag2']))
+        assert_false(self._tags12.is_included([], ['tag1NOTtag3']))
+
+    def test_include_with_multiple_nots(self):
+        assert_false(self._tags123.is_included(['tag1NOTtag2NOTtag3'], []))
+        assert_false(self._tags123.is_included(['tag1NOTtag4NOTtag2'], []))
+        assert_true(self._tags123.is_included(['tag1NOTtag4NOTtag5'], []))
+
+    def test_exclude_with_multiple_nots(self):
+        assert_true(self._tags123.is_included([], ['tag1NOTtag2NOTtag3']))
+        assert_true(self._tags123.is_included([], ['tag1NOTtag4NOTtag2']))
+        assert_false(self._tags123.is_included([], ['tag1NOTtag4NOTtag5']))
+
+    def test_include_with_multiple_nots_and_ands(self):
+ assert_true(self._tag1.is_included(['tag1NOTtag2&tag3NOTtag4&tag5'], [])) + assert_true(TC(['tag1', 'tag2', 'tag4']).is_included(['tag1NOTtag2&tag3NOTtag4&tag5'], [])) + assert_false(TC(['tag1', 'tag2', 'tag3']).is_included(['tag1NOTtag2&tag3NOTtag4&tag5'], [])) + assert_false(TC(['tag1', 'tag4', 'tag5']).is_included(['tag1NOTtag2&tag3NOTtag4&tag5'], [])) + assert_false(TC(['tag1', 'tag4']).is_included(['tag1NOTtag2NOTtag3NOTtag4NOTtag5'], []))
+
+    def test_invalid(self):
+        for invalid in [ 'NOT', 'NOTNOT', 'xNOTNOTy', 'NOTa', 'bNOT',
+                         'NOTaNOTb', 'aNOTbNOT' ]:
+            assert_false(self._tag1.is_included([invalid], []))
+            assert_true(self._tag1.is_included(['tag1'], [invalid]))
+
 if __name__ == "__main__":
     unittest.main()

Reply via email to