Revision: 424ab1283ab3
Author:   Robot Framework Developers ([email protected])
Date:     Wed Nov  9 05:50:28 2011
Log: moved statistics to model package, use model objects instead of mocks in unit tests
http://code.google.com/p/robotframework/source/detail?r=424ab1283ab3

Added:
 /src/robot/model/statistics.py
 /utest/model/test_statistics.py

=======================================
--- /dev/null
+++ /src/robot/model/statistics.py      Wed Nov  9 05:50:28 2011
@@ -0,0 +1,332 @@
+#  Copyright 2008-2011 Nokia Siemens Networks Oyj
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import re
+
+from robot import utils
+
+
+class Statistics:
+
+    def __init__(self, suite, suite_stat_level=-1, tag_stat_include=None,
+ tag_stat_exclude=None, tag_stat_combine=None, tag_doc=None,
+                 tag_stat_link=None):
+        self.tags = TagStatistics(tag_stat_include, tag_stat_exclude,
+                                  tag_stat_combine, tag_doc, tag_stat_link)
+        self.suite = SuiteStatistics(suite, self.tags, suite_stat_level)
+        self.total = TotalStatistics(self.suite)
+        self.tags.sort()
+
+    #TODO: Replace with visit
+    def serialize(self, serializer):
+        serializer.start_statistics(self)
+        self.total.serialize(serializer)
+        self.tags.serialize(serializer)
+        self.suite.serialize(serializer)
+        serializer.end_statistics(self)
+
+    def visit(self, visitor):
+        self.serialize(visitor)
+
+
+class Stat:
+
+    def __init__(self, name=''):
+        self.name = name
+        self.passed = 0
+        self.failed = 0
+
+    @property
+    def total(self):
+        return self.passed + self.failed
+
+    def add_stat(self, other):
+        self.passed += other.passed
+        self.failed += other.failed
+
+    def add_test(self, test):
+        if test.status == 'PASS':
+            self.passed += 1
+        else:
+            self.failed += 1
+
+    def fail_all(self):
+        self.failed += self.passed
+        self.passed = 0
+
+    def add_suite(self, suite):
+        for test in suite.tests:
+            if self._is_included(test):
+                self.add_test(test)
+        for suite in suite.suites:
+            self.add_stat(self._subsuite_stats(suite))
+
+    def _is_included(self, test):
+        return True
+
+    def _subsuite_stats(self, suite):
+        return suite.all_stats
+
+    def __cmp__(self, other):
+        return cmp(self.name, other.name)
+
+    def __nonzero__(self):
+        return self.failed == 0
+
+
+class CriticalStats(Stat):
+
+    def __init__(self, suite):
+        Stat.__init__(self)
+        self.add_suite(suite)
+
+    def _is_included(self, test):
+        return test.critical == 'yes'
+
+    def _subsuite_stats(self, suite):
+        return suite.critical_stats
+
+class AllStats(Stat):
+
+    def __init__(self, suite):
+        Stat.__init__(self)
+        self.add_suite(suite)
+
+
+class SuiteStat(Stat):
+    type = 'suite'
+
+    def __init__(self, suite):
+        Stat.__init__(self, suite.name)
+        self.longname = suite.longname
+        self.id = suite.id
+
+    def serialize(self, serializer):
+        serializer.suite_stat(self)
+
+
+class TagStat(Stat):
+    type = 'tag'
+
+    def __init__(self, name, doc='', links=[], critical=False,
+                 non_critical=False, combined=''):
+        Stat.__init__(self, name)
+        self.doc = doc
+        self.links = links
+        self.critical = critical
+        self.non_critical = non_critical
+        self.combined = combined
+        self.tests = []
+
+    def add_test(self, test):
+        Stat.add_test(self, test)
+        self.tests.append(test)
+
+    def __cmp__(self, other):
+        if self.critical != other.critical:
+            return cmp(other.critical, self.critical)
+        if self.non_critical != other.non_critical:
+            return cmp(other.non_critical, self.non_critical)
+        if bool(self.combined) != bool(other.combined):
+            return cmp(bool(other.combined), bool(self.combined))
+        return cmp(self.name, other.name)
+
+    def serialize(self, serializer):
+        serializer.tag_stat(self)
+
+
+class TotalStat(Stat):
+    type = 'total'
+
+    def __init__(self, name, suite_stat):
+        Stat.__init__(self, name)
+        self.passed = suite_stat.passed
+        self.failed = suite_stat.failed
+
+    def serialize(self, serializer):
+        serializer.total_stat(self)
+
+
+class SuiteStatistics:
+
+    def __init__(self, suite, tag_stats, suite_stat_level=-1):
+        self.all = SuiteStat(suite)
+        self.critical = SuiteStat(suite)
+        self.suites = []
+        self._process_suites(suite, tag_stats)
+        self._process_tests(suite, tag_stats)
+        self._suite_stat_level = suite_stat_level
+
+    def _process_suites(self, suite, tag_stats):
+        for subsuite in suite.suites:
+            substat = SuiteStatistics(subsuite, tag_stats)
+            self.suites.append(substat)
+            self.all.add_stat(substat.all)
+            self.critical.add_stat(substat.critical)
+
+    def _process_tests(self, suite, tag_stats):
+        for test in suite.tests:
+            self.all.add_test(test)
+            if test.critical == 'yes':
+                self.critical.add_test(test)
+            tag_stats.add_test(test, suite.critical)
+
+    def serialize(self, serializer):
+        serializer.start_suite_stats(self)
+        self._serialize(serializer, self._suite_stat_level)
+        serializer.end_suite_stats(self)
+
+    def _serialize(self, serializer, max_suite_level, suite_level=1):
+        self.all.serialize(serializer)
+        if max_suite_level < 0 or max_suite_level > suite_level:
+            for suite in self.suites:
+ suite._serialize(serializer, max_suite_level, suite_level+1)
+
+
+class TagStatistics:
+
+    def __init__(self, include=None, exclude=None, combine=None, docs=None,
+                 links=None):
+        self.stats = utils.NormalizedDict(ignore=['_'])
+        self._include = include or []
+        self._exclude = exclude or []
+        self._combine = combine or []
+        info = TagStatInfo(docs or [], links or [])
+        self._get_doc = info.get_doc
+        self._get_links = info.get_links
+
+    def add_test(self, test, critical):
+        self._add_tags_statistics(test, critical)
+        self._add_combined_statistics(test)
+
+    def _add_tags_statistics(self, test, critical):
+        for tag in test.tags:
+            if not self._is_included(tag):
+                continue
+            if tag not in self.stats:
+                self.stats[tag] = TagStat(tag, self._get_doc(tag),
+                                          self._get_links(tag),
+                                          critical.is_critical(tag),
+                                          critical.is_non_critical(tag))
+            self.stats[tag].add_test(test)
+
+    def _is_included(self, tag):
+        if self._include and not utils.matches_any(tag, self._include):
+            return False
+        return not utils.matches_any(tag, self._exclude)
+
+    def _add_combined_statistics(self, test):
+        for pattern, name in self._combine:
+            name = name or pattern
+            if name not in self.stats:
+                self.stats[name] = TagStat(name, self._get_doc(name),
+                                           self._get_links(name),
+                                           combined=pattern)
+            if test.is_included([pattern], []):
+                self.stats[name].add_test(test)
+
+    def serialize(self, serializer):
+        serializer.start_tag_stats(self)
+        for stat in sorted(self.stats.values()):
+            stat.serialize(serializer)
+        serializer.end_tag_stats(self)
+
+    def sort(self):
+        for stat in self.stats.values():
+            stat.tests.sort()
+
+
+class TotalStatistics:
+
+    def __init__(self, suite):
+        self.critical = TotalStat('Critical Tests', suite.critical)
+        self.all = TotalStat('All Tests', suite.all)
+
+    def serialize(self, serializer):
+        serializer.start_total_stats(self)
+        self.critical.serialize(serializer)
+        self.all.serialize(serializer)
+        serializer.end_total_stats(self)
+
+
+class TagStatInfo:
+
+    def __init__(self, docs, links):
+        self._docs = [TagStatDoc(*doc) for doc in docs]
+        self._links = [TagStatLink(*link) for link in links]
+
+    def get_doc(self, tag):
+ return ' & '.join(doc.text for doc in self._docs if doc.matches(tag))
+
+    def get_links(self, tag):
+ return [link.get_link(tag) for link in self._links if link.matches(tag)]
+
+
+class TagStatDoc:
+
+    def __init__(self, pattern, doc):
+        self.text = doc
+        self._pattern = pattern
+
+    def matches(self, tag):
+        return utils.matches(tag, self._pattern)
+
+
+class TagStatLink:
+    _match_pattern_tokenizer = re.compile('(\*|\?)')
+
+    def __init__(self, pattern, link, title):
+        self._regexp = self._get_match_regexp(pattern)
+        self._link = link
+        self._title = title.replace('_', ' ')
+
+    def matches(self, tag):
+        return self._regexp.match(tag) is not None
+
+    def get_link(self, tag):
+        match = self._regexp.match(tag)
+        if not match:
+            return None
+        link, title = self._replace_groups(self._link, self._title, match)
+        return link, title
+
+    def _replace_groups(self, link, title, match):
+        for index, group in enumerate(match.groups()):
+            placefolder = '%' + str(index+1)
+            link = link.replace(placefolder, group)
+            title = title.replace(placefolder, group)
+        return link, title
+
+    def _get_match_regexp(self, pattern):
+        regexp = []
+        open_parenthesis = False
+        for token in self._match_pattern_tokenizer.split(pattern):
+            if token == '':
+                continue
+            if token == '?':
+                if not open_parenthesis:
+                    regexp.append('(')
+                    open_parenthesis = True
+                regexp.append('.')
+                continue
+            if open_parenthesis:
+                regexp.append(')')
+                open_parenthesis = False
+            if token == '*':
+                regexp.append('(.*)')
+                continue
+            regexp.append(re.escape(token))
+        if open_parenthesis:
+            regexp.append(')')
+        return re.compile('^%s$' % ''.join(regexp), re.IGNORECASE)
=======================================
--- /dev/null
+++ /utest/model/test_statistics.py     Wed Nov  9 05:50:28 2011
@@ -0,0 +1,352 @@
+import unittest
+from robot.model.critical import Critical
+
+from robot.utils.asserts import *
+from robot.model.statistics import *
+from robot.result.model import TestSuite, TestCase
+
+
+def verify_stat(stat, name, passed, failed, critical=None, non_crit=None, id=None):
+    assert_equals(stat.name, name, 'stat.name')
+    assert_equals(stat.passed, passed)
+    assert_equals(stat.failed, failed)
+    if critical is not None:
+        assert_equals(stat.critical, critical)
+    if non_crit is not None:
+        assert_equals(stat.non_critical, non_crit)
+    if id:
+        assert_equal(stat.id, id)
+
+def verify_suite(suite, name, id, crit_pass, crit_fail, all_pass=None, all_fail=None):
+    verify_stat(suite.critical, name, crit_pass, crit_fail, id=id)
+    if all_pass is None:
+        all_pass, all_fail = crit_pass, crit_fail
+    verify_stat(suite.all, name, all_pass, all_fail, id=id)
+
+def generate_default_suite():
+    suite = TestSuite(name='Root Suite')
+    suite.set_criticality(critical_tags=['smoke'])
+    s1 = suite.suites.create(name='First Sub Suite')
+    s2 = suite.suites.create(name='Second Sub Suite')
+    s11 = s1.suites.create(name='Sub Suite 1_1')
+    s12 = s1.suites.create(name='Sub Suite 1_2')
+    s13 = s1.suites.create(name='Sub Suite 1_3')
+    s21 = s2.suites.create(name='Sub Suite 2_1')
+ s11.tests = [TestCase(status='PASS'), TestCase(status='FAIL', tags=['t1'])]
+    s12.tests = [TestCase(status='PASS', tags=['t_1','t2',]),
+                 TestCase(status='PASS', tags=['t1','smoke']),
+                 TestCase(status='FAIL', tags=['t1','t2','t3','smoke'])]
+    s13.tests = [TestCase(status='PASS', tags=['t1','t 2','smoke'])]
+    s21.tests = [TestCase(status='FAIL', tags=['t3','Smoke'])]
+    return suite
+
+
+class TestStatisticsSimple(unittest.TestCase):
+
+    def setUp(self):
+        suite = TestSuite(name='Hello')
+        suite.tests = [TestCase(status='PASS'), TestCase(status='PASS'),
+                       TestCase(status='FAIL')]
+        self.statistics = Statistics(suite)
+
+    def test_total(self):
+        verify_stat(self.statistics.total.critical, 'Critical Tests', 2, 1)
+        verify_stat(self.statistics.total.all, 'All Tests', 2, 1)
+
+    def test_suite(self):
+        verify_suite(self.statistics.suite, 'Hello', 's1', 2, 1)
+
+    def test_tags(self):
+        assert_equals(self.statistics.tags.stats, {})
+
+
+class TestStatisticsNotSoSimple(unittest.TestCase):
+
+    def setUp(self):
+        self.statistics = Statistics(generate_default_suite())
+
+    def test_total(self):
+        verify_stat(self.statistics.total.all, 'All Tests', 4, 3)
+        verify_stat(self.statistics.total.critical, 'Critical Tests', 2, 2)
+
+    def test_suite(self):
+        suite = self.statistics.suite
+        verify_suite(suite, 'Root Suite', 's1', 2, 2, 4, 3)
+        assert_equals(len(suite.suites), 2)
+        s1, s2 = suite.suites
+        verify_suite(s1, 'First Sub Suite', 's1-s1', 2, 1, 4, 2)
+        verify_suite(s2, 'Second Sub Suite', 's1-s2', 0, 1, 0, 1)
+        assert_equals(len(s1.suites), 3)
+        s11, s12, s13 = s1.suites
+        verify_suite(s11, 'Sub Suite 1_1', 's1-s1-s1', 0, 0, 1, 1)
+        verify_suite(s12, 'Sub Suite 1_2', 's1-s1-s2', 1, 1, 2, 1)
+        verify_suite(s13, 'Sub Suite 1_3', 's1-s1-s3', 1, 0, 1, 0)
+        assert_equals(len(s2.suites), 1)
+        s21 = s2.suites[0]
+        verify_suite(s21, 'Sub Suite 2_1', 's1-s2-s1', 0, 1, 0, 1)
+
+    def test_tags(self):
+        tags = self.statistics.tags
+        keys = tags.stats.keys()
+        assert_equals(len(keys), 4)
+        keys.sort()
+        assert_equals(keys, 'smoke t1 t2 t3'.split())
+        verify_stat(tags.stats['smoke'], 'smoke', 2, 2, True, False)
+        verify_stat(tags.stats['t1'], 't1', 3, 2, False, False)
+        verify_stat(tags.stats['t2'], 't2', 2, 1, False, False)
+        verify_stat(tags.stats['t3'], 't3', 0, 2, False, False)
+
+
+_incl_excl_data = [
+    ([], []),
+    ([], ['t1','t2']),
+    (['t1'], ['t1','t2']),
+    (['t1','t2'], ['t1','t2','t3','t4']),
+    (['UP'], ['t1','t2','up']),
+    (['not','not2'], ['t1','t2','t3']),
+    (['t*'], ['t1','s1','t2','t3','s2','s3']),
+    (['T*','r'], ['t1','t2','r','teeeeeeee']),
+    (['*'], ['t1','t2','s1','tag']),
+    (['t1','t2','t3','not'], ['t1','t2','t3','t4','s1','s2'])
+]
+
+
+class TestTagStatistics(unittest.TestCase):
+
+    def test_include(self):
+        for incl, tags in _incl_excl_data:
+            tagstats = TagStatistics(incl, [])
+ tagstats.add_test(TestCase(status='PASS', tags=tags), Critical())
+            exp_keys = [tag for tag in sorted(tags)
+                        if incl == [] or utils.matches_any(tag, incl)]
+            assert_equal(sorted(tagstats.stats.keys()),
+                         exp_keys, "Incls: %s " % incl)
+
+    def test_exclude(self):
+        for excl, tags in _incl_excl_data:
+            tagstats = TagStatistics([], excl)
+ tagstats.add_test(TestCase(status='PASS', tags=tags), Critical())
+            exp_keys = [tag for tag in sorted(tags)
+                        if not utils.matches_any(tag, excl)]
+            assert_equal(sorted(tagstats.stats.keys()),
+                         exp_keys, "Excls: %s" % excl)
+
+    def test_include_and_exclude(self):
+        for incl, excl, tags, exp in [
+               ([], [], ['t0','t1','t2'], ['t0','t1','t2']),
+               (['t1'], ['t2'], ['t0','t1','t2'], ['t1']),
+               (['t?'], ['t2'], ['t0','t1','t2','x'], ['t0','t1']),
+               (['t?'], ['*2'], ['t0','t1','t2','x2'], ['t0','t1']),
+               (['t1','t2'], ['t2'], ['t0','t1','t2'], ['t1']),
+               (['t1','t2','t3','not'], ['t2','t0'],
+                ['t0','t1','t2','t3','x'], ['t1','t3'] )
+              ]:
+            tagstats = TagStatistics(incl, excl)
+ tagstats.add_test(TestCase(status='PASS', tags=tags), Critical())
+            assert_equal(sorted(tagstats.stats.keys()),
+                         exp, "Incls: %s, Excls: %s" % (incl, excl))
+
+    def test_combine_with_name(self):
+        for comb_tags, expected_name in [
+                ([], '' ),
+                ([('t1&t2', 'my name')], 'my name'),
+                ([('t1NOTt3', 'Others')], 'Others'),
+                ([('1:2&2:3', 'nAme')], 'nAme'),
+                ([('3*', '')], '3*' ),
+                ([('4NOT5', 'Some new name')], 'Some new name')
+               ]:
+            stats = TagStatistics(combine=comb_tags)
+            test = TestCase()
+            stats._add_combined_statistics(test)
+            assert_equals(len(stats.stats), expected_name != '')
+            if expected_name:
+ assert_equals(stats.stats[expected_name].name, expected_name)
+
+    def test_is_combined_with_and_statements(self):
+        for comb_tags, test_tags, expected_count in [
+                ('t1', ['t1'], 1),
+                ('t1', ['t2'], 0),
+                ('t1&t2', ['t1'], 0),
+                ('t1&t2', ['t1','t2'], 1),
+                ('t1&t2', ['T1','t 2','t3'], 1),
+                ('t*', ['s','t','u'], 1),
+                ('t*', ['s','tee','t'], 1),
+                ('t*&s', ['s','tee','t'], 1),
+                ('t*&s&non', ['s','tee','t'], 0)
+               ]:
+ self._test_combined_statistics(comb_tags, test_tags, expected_count)
+
+ def _test_combined_statistics(self, comb_tags, test_tags, expected_count):
+            stats = TagStatistics(combine=[(comb_tags, 'name')])
+            test = TestCase(tags=test_tags)
+            stats._add_combined_statistics(test)
+            assert_equals(len(stats.stats['Name'].tests), expected_count,
+                          'comb: %s, test: %s' % (comb_tags, test_tags))
+
+    def test_is_combined_with_not_statements(self):
+        stats = TagStatistics()
+        for comb_tags, test_tags, expected_count in [
+                ('t1NOTt2', [], 0),
+                ('t1NOTt2', ['t1'], 1),
+                ('t1NOTt2', ['t1','t2'], 0),
+                ('t1NOTt2', ['t3'], 0),
+                ('t1NOTt2', ['t3','t2'], 0),
+                ('t*NOTt2', ['t1'], 1),
+                ('t*NOTt2', ['t'], 1),
+                ('t*NOTt2', ['TEE'], 1),
+                ('t*NOTt2', ['T2'], 0),
+                ('T*NOTT?', ['t'], 1),
+                ('T*NOTT?', ['tt'], 0),
+                ('T*NOTT?', ['ttt'], 1),
+                ('T*NOTT?', ['tt','t'], 0),
+                ('T*NOTT?', ['ttt','something'], 1),
+                ('tNOTs*NOTr', ['t'], 1),
+                ('tNOTs*NOTr', ['t','s'], 0),
+                ('tNOTs*NOTr', ['S','T'], 0),
+                ('tNOTs*NOTr', ['R','T','s'], 1),
+               ]:
+ self._test_combined_statistics(comb_tags, test_tags, expected_count)
+
+    def test_combine(self):
+        # This is more like an acceptance test than a unit test ...
+        for comb_tags, comb_matches, tests_tags, crit_tags in [
+                (['t1&t2'], [1], [['t1','t2','t3'],['t1','t3']], []),
+ (['1&2&3'], [2], [['1','2','3'],['1','2','3','4']], ['1','2']), + (['1&2','1&3'], [1,2], [['1','2','3'],['1','3'],['1']], ['1']),
+                (['t*'], [3], [['t1','x','y'],['tee','z'],['t']], ['x']),
+ (['t?&s'], [2], [['t1','s'],['tt','s','u'],['tee','s'],['s']], []), + (['t*&s','*'], [2,3], [['s','t','u'],['tee','s'],[],['x']], []),
+                (['tNOTs'], [1], [['t','u'],['t','s']], []),
+                (['tNOTs','t&s','tNOTsNOTu', 't&sNOTu'], [3,2,4,1],
+ [['t','u'],['t','s'],['s','t','u'],['t'],['t','v']], ['t']),
+                (['nonex'], [0], [['t1'],['t1,t2'],[]], [])
+               ]:
+            # 1) Create tag stats
+            tagstats = TagStatistics(combine=[(t, '') for t in comb_tags])
+            all_tags = []
+            for tags in tests_tags:
+                tagstats.add_test(TestCase(status='PASS', tags=tags),
+                                  Critical(crit_tags))
+                all_tags.extend(tags)
+            # 2) Actual values
+            names = [stat.name for stat in sorted(tagstats.stats.values())]
+            # 3) Expected values
+            exp_crit = []; exp_noncr = []; exp_comb = []
+            for tag in utils.normalize_tags(all_tags):
+                if tag in crit_tags:
+                    exp_crit.append(tag)
+                else:
+                    exp_noncr.append(tag)
+            for comb, count in zip(comb_tags, comb_matches):
+                exp_comb.append(comb)
+                try:
+ assert_equals(len(tagstats.stats[comb].tests), count, comb)
+                except KeyError:
+                    fail("No key %s. Stats: %s" % (comb, tagstats.stats))
+            exp_names = exp_crit + sorted(exp_comb) + exp_noncr
+            # 4) Verify names (match counts were already verified)
+            assert_equals(names, exp_names)
+
+    def test_through_suite(self):
+        suite = generate_default_suite()
+        suite.set_criticality(critical_tags=['smoke'])
+        statistics = Statistics(suite, -1, ['t*','smoke'], ['t3'],
+                                [('t1 & t2', ''), ('t? & smoke', ''),
+ ('t1 NOT t2', ''), ('none & t1', 'a title')])
+        stats = sorted(statistics.tags.stats.values())
+        expected = [('smoke', 4), ('a title', 0), ('t1 & t2', 3),
+ ('t1 NOT t2', 2), ('t? & smoke', 4), ('t1', 5), ('t2', 3)]
+        names = [stat.name for stat in stats]
+        exp_names = [name for name, _ in expected]
+        assert_equals(names, exp_names)
+        for name, count in expected:
+ assert_equals(len(statistics.tags.stats[name].tests), count, name)
+
+
+class TestTagStatLink(unittest.TestCase):
+
+    def test_valid_string_is_parsed_correctly(self):
+        for arg, exp in [(('Tag', 'bar/foo.html', 'foobar'),
+                          ('^Tag$', 'bar/foo.html', 'foobar')),
+                         (('hello', 'gopher://hello.world:8090/hello.html',
+                           'Hello World'),
+ ('^hello$', 'gopher://hello.world:8090/hello.html',
+                           'Hello World'))]:
+            link = TagStatLink(*arg)
+            assert_equal(exp[0], link._regexp.pattern)
+            assert_equal(exp[1], link._link)
+            assert_equal(exp[2], link._title)
+
+    def test_valid_string_containing_patterns_is_parsed_correctly(self):
+        for arg, exp_pattern in [('*', '^(.*)$'), ('f*r', '^f(.*)r$'),
+                                 ('*a*', '^(.*)a(.*)$'),  ('?', '^(.)$'),
+ ('??', '^(..)$'), ('f???ar', '^f(...)ar$'),
+                                 ('F*B?R*?', '^F(.*)B(.)R(.*)(.)$')]:
+            link = TagStatLink(arg, 'some_url', 'some_title')
+            assert_equal(exp_pattern, link._regexp.pattern)
+
+    def test_underscores_in_title_are_converted_to_spaces(self):
+        link = TagStatLink('', '', 'my_name')
+        assert_equal(link._title, 'my name')
+
+    def test_get_link_returns_correct_link_when_matches(self):
+        for arg, exp in [(('smoke', 'http://tobacco.com', 'Lung_cancer'),
+                          ('http://tobacco.com', 'Lung cancer')),
+                         (('tag', 'ftp://foo:809/bar.zap', 'Foo_in a Bar'),
+                          ('ftp://foo:809/bar.zap', 'Foo in a Bar'))]:
+            link = TagStatLink(*arg)
+            assert_equals(exp, link.get_link(arg[0]))
+
+    def test_get_link_returns_none_when_no_match(self):
+        link = TagStatLink('smoke', 'http://tobacco.com', 'Lung cancer')
+        for tag in ['foo', 'b a r', 's moke']:
+            assert_none(link.get_link(tag))
+
+    def test_pattern_matches_case_insensitively(self):
+        exp = 'http://tobacco.com', 'Lung cancer'
+        link = TagStatLink('smoke', *exp)
+        for tag in ['Smoke', 'SMOKE', 'smoke']:
+            assert_equals(exp, link.get_link(tag))
+
+    def test_pattern_matches_when_spaces(self):
+        exp = 'http://tobacco.com', 'Lung cancer'
+        link = TagStatLink('smoking kills', *exp)
+        for tag in ['Smoking Kills', 'SMOKING KILLS']:
+            assert_equals(exp, link.get_link(tag))
+
+    def test_pattern_match(self):
+        link = TagStatLink('f?o*r', 'http://foo/bar.html', 'FooBar')
+        for tag in ['foobar', 'foor', 'f_ofoobarfoobar', 'fOoBAr']:
+ assert_equal(link.get_link(tag), ('http://foo/bar.html', 'FooBar'))
+
+    def test_pattern_substitution_with_one_match(self):
+        link = TagStatLink('tag-*', 'http://tracker/?id=%1', 'Tracker')
+        for id in ['1', '23', '456']:
+            exp = ('http://tracker/?id=%s' % id, 'Tracker')
+            assert_equal(exp, link.get_link('tag-%s' % id))
+
+    def test_pattern_substitution_with_multiple_matches(self):
+        link = TagStatLink('?-*', 'http://tracker/?id=%1-%2', 'Tracker')
+        for id1, id2 in [('1', '2'), ('3', '45'), ('f', 'bar')]:
+            exp = ('http://tracker/?id=%s-%s' % (id1, id2), 'Tracker')
+            assert_equal(exp, link.get_link('%s-%s' % (id1, id2)))
+
+    def test_pattern_substitution_with_multiple_substitutions(self):
+        link = TagStatLink('?-?-*', '%3-%3-%1-%2-%3', 'Tracker')
+ assert_equal(link.get_link('a-b-XXX'), ('XXX-XXX-a-b-XXX', 'Tracker'))
+
+    def test_matches_are_ignored_in_pattern_substitution(self):
+        link = TagStatLink('?-*-*-?', '%4-%2-%2-%4', 'Tracker')
+ assert_equal(link.get_link('A-XXX-ABC-B'), ('B-XXX-XXX-B', 'Tracker'))
+
+
+class TestTagStatLinks(unittest.TestCase):
+
+    def test_tag_stat_links_with_valid_tags(self):
+        values = [('1', '2', '3'), ('tag', 'foo.html', 'bar')]
+        tag_stat_links = TagStatInfo([], values)
+        assert_equal(len(tag_stat_links._links), 2)
+
+
+if __name__ == "__main__":
+    unittest.main()

Reply via email to