Below is the file 'tests.py' from this revision. You can also download the file.

#!/usr/bin/env python2.4

# Copyright (C) 2005 Grahame Bowland <grahame@angrygoats.net>
#
# This program is made available under the GNU GPL version 2.0 or
# greater. See the accompanying file COPYING for details.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE.

#
# Unit tests for ViewMTN
#
# Aiming for reasonably full coverage, but the tests are being
# written after the application so it's slow and tedious work :-)
#

import unittest
import stat
import os

# modules we're testing..
import viewmtn
import mtn

class ConfigTest(unittest.TestCase):
    "Test that the configuration file is correct"

    def can_access(self, fname):
        self.failIf (not os.access(fname, os.R_OK), "Cannot access file: " + fname)

    def is_uri(self, uri):
        self.failIf (not isinstance(uri, str), "URIs must be instances of <str>")
        self.failIf (not uri[-1] == '/', "All URIs in config file must end in '/'")

    def is_file(self, fname):
        self.failIf (not stat.S_ISREG(os.stat(fname).st_mode), "Files in config file must be regular files: " + fname)

    def is_command(self, fname):
        self.failIf (not os.access(fname, os.X_OK), "Must be able to execute: " + fname)

    def is_directory(self, fname):
        self.failIf (not stat.S_ISDIR(os.stat(fname).st_mode), "Must be a directory: " + fname)

    #
    # describe the config file with lists of functions that should
    # succeed on the value found in the config file. for now, any
    # option listed below is required (ViewMTN doesn't really have
    # optional configuration directives, anyway..)
    #
    check_keys = [(lambda self : self.config,
                   lambda obj, key : hasattr(obj, key),
                   lambda obj, key : getattr(obj, key),
                  { 'dynamic_uri_path' :  (is_uri,),
                   'static_uri_path' : (is_uri,),
                   'running_under_apache2' : None,
                   'monotone' : (can_access, is_file, is_command),
                   'dbfile' : (can_access, is_file,),
                   'highlight_command' : (can_access, is_file, is_command),
                   'graphopts' : None,
                   'icon_theme' : None,
                   'icon_size' : None,
                  }),
                  (lambda self: self.config.graphopts,
                   lambda obj, key : obj.has_key(key),
                   lambda obj, key : obj[key],
                  { 'directory' : (can_access, is_directory),
                    'uri' : (is_uri,),
                    'dot' : (can_access, is_file, is_command),
                    'nodeopts' : None }),
                  (lambda self: self.config.graphopts['nodeopts'],
                     lambda obj, key : obj.has_key(key),
                     lambda obj, key : obj[key],
                  { 'fontname' : None,
                    'fontsize' : None,
                    'shape' : None,
                    'height' : None,
                    'spline' : None,
                    'style' : None,
                    'fillcolor' : None }) ]

    def setUp(self):
        self.config = __import__('config')

    def testRequiredKeys(self):
        "all required configuration options present"
        for val_func, has_func, get_func, keys in self.check_keys:
            to_check = val_func(self)
            for key in keys:
                self.failIf(not has_func(to_check, key),  "Required configuration option '%s' not found." % key)
                funcs = keys[key]
                if funcs == None: continue
                val = get_func(to_check, key)
                map(lambda func : func(self, val), funcs)

class MTNRegexpTests(unittest.TestCase):
    entirely_non_hex = 'NOTX'
    starts_with_hex = 'A9NOTX'
    ends_with_hex = 'NOTXA9'
    valid_revision = 'abcdEf0123456789abcdEf0123456789abcdef12'
    too_long_revision = valid_revision + '9'
    too_short_revision = valid_revision[:-1]
    non_hex_revision = valid_revision.replace('a', 'Q')
    non_hex_start_revision = 'QQ' + valid_revision
    non_hex_end_revision = valid_revision + 'QQ'

    def testHex(self):
        "Test that mtn.py regular expressions do what we think they do"
        self.assertEqual(mtn.hex_re_c.match('').groups(), ('',), 'hex_re must match empty string')
        self.assertEqual(mtn.hex_re_c.match(self.entirely_non_hex).groups(), ('',), 'hex_re must match entirely non-hex and group empty string')
        self.assertEqual(mtn.hex_re_c.match(self.starts_with_hex).groups(), ('A9',), 'hex_re must match hex followed by non-hex and group beginning hex')
        self.assertEqual(mtn.hex_re_c.match(self.ends_with_hex).groups(), ('',), 'hex_re must match non-hex followed by hex and group empty string')

        self.assertEqual(mtn.revision_re_c.match(''), None, 'revision_re must not match empty string')
        self.assertEqual(mtn.revision_re_c.match(self.valid_revision).groups(), (self.valid_revision,), 'revision_re must match valid revision')
        self.failIf(mtn.revision_re_c.match(self.too_short_revision), 'revision_re must not match too-short revision')
        self.failIf(mtn.revision_re_c.match(self.non_hex_revision), 'revision_re must not match non-hex revision')
        self.failIf(mtn.revision_re_c.match(self.non_hex_start_revision), 'revision_re must not match non-hex followed by valid revision')
        self.failIf(mtn.revision_re_c.match(self.non_hex_end_revision), 'revision_re must not match valid revision followed by non-hex')

class MtnTypeTests(unittest.TestCase):
    def testRevision(self):
        "Test the mtn.Revision class"
        self.assertEqual (mtn.Revision(''), '', 'Revision must work for empty revision, and subclass str()')
        self.assertRaises (mtn.MonotoneException, lambda : mtn.Revision(MTNRegexpTests.too_long_revision))
        self.assertRaises (mtn.MonotoneException, lambda : mtn.Revision(MTNRegexpTests.too_short_revision))
        self.assertRaises (mtn.MonotoneException, lambda : mtn.Revision(MTNRegexpTests.non_hex_revision))
        self.assertRaises (mtn.MonotoneException, lambda : mtn.Revision(MTNRegexpTests.non_hex_start_revision))
        self.assertRaises (mtn.MonotoneException, lambda : mtn.Revision(MTNRegexpTests.non_hex_end_revision))
        self.assertEqual (mtn.Revision(MTNRegexpTests.valid_revision), MTNRegexpTests.valid_revision, 'Revision must work for valid revision, and subclass str()')

    def testAuthor(self):
        "Test the mtn.Author class"
        self.assertEqual (mtn.Author(''), '', 'Author must work for empty string, and subclass str()')
        self.assertEqual (mtn.Author('Gumby'), 'Gumby', 'Author must work for non-empty string, and subclass str()')

class MtnAutomateTests(unittest.TestCase):

    def testPacketHeader(self):
        pass


if __name__ == '__main__':
    unittest.main()

###
### vi:expandtab:sw=4:ts=4
###