You can subscribe to this list here.
2002 |
Jan
|
Feb
|
Mar
(5) |
Apr
(5) |
May
(23) |
Jun
|
Jul
(11) |
Aug
(3) |
Sep
(1) |
Oct
(8) |
Nov
(24) |
Dec
(3) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2003 |
Jan
(17) |
Feb
(5) |
Mar
(18) |
Apr
(10) |
May
(4) |
Jun
(5) |
Jul
(67) |
Aug
(7) |
Sep
(4) |
Oct
(2) |
Nov
(4) |
Dec
(9) |
2004 |
Jan
(16) |
Feb
(4) |
Mar
(7) |
Apr
(5) |
May
(4) |
Jun
(5) |
Jul
(3) |
Aug
(3) |
Sep
(3) |
Oct
(8) |
Nov
|
Dec
|
2005 |
Jan
(5) |
Feb
(6) |
Mar
(4) |
Apr
(1) |
May
(2) |
Jun
(2) |
Jul
(1) |
Aug
|
Sep
(5) |
Oct
(1) |
Nov
|
Dec
(7) |
2006 |
Jan
(10) |
Feb
(4) |
Mar
(10) |
Apr
(8) |
May
(8) |
Jun
(14) |
Jul
(7) |
Aug
(4) |
Sep
(4) |
Oct
(24) |
Nov
(29) |
Dec
(10) |
2007 |
Jan
(5) |
Feb
(12) |
Mar
(11) |
Apr
(10) |
May
(3) |
Jun
(3) |
Jul
(15) |
Aug
(28) |
Sep
(8) |
Oct
(5) |
Nov
(8) |
Dec
(13) |
2008 |
Jan
(7) |
Feb
(11) |
Mar
(29) |
Apr
(28) |
May
(17) |
Jun
(9) |
Jul
(18) |
Aug
(7) |
Sep
(8) |
Oct
(9) |
Nov
(11) |
Dec
(53) |
2009 |
Jan
(112) |
Feb
(19) |
Mar
(46) |
Apr
(32) |
May
(90) |
Jun
(91) |
Jul
(33) |
Aug
(11) |
Sep
(16) |
Oct
(23) |
Nov
(15) |
Dec
(3) |
2010 |
Jan
(1) |
Feb
|
Mar
(37) |
Apr
(47) |
May
(66) |
Jun
(69) |
Jul
(29) |
Aug
(45) |
Sep
(23) |
Oct
(3) |
Nov
(1) |
Dec
|
2011 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
(1) |
Dec
|
2012 |
Jan
|
Feb
|
Mar
(2) |
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
2013 |
Jan
|
Feb
|
Mar
|
Apr
(1) |
May
(2) |
Jun
(1) |
Jul
(3) |
Aug
(6) |
Sep
(1) |
Oct
(7) |
Nov
(1) |
Dec
(1) |
2014 |
Jan
(1) |
Feb
|
Mar
|
Apr
|
May
(1) |
Jun
|
Jul
|
Aug
|
Sep
(1) |
Oct
|
Nov
|
Dec
(1) |
2015 |
Jan
(2) |
Feb
|
Mar
|
Apr
(1) |
May
|
Jun
|
Jul
|
Aug
(1) |
Sep
|
Oct
|
Nov
(1) |
Dec
(3) |
2016 |
Jan
(4) |
Feb
(5) |
Mar
(2) |
Apr
|
May
|
Jun
(2) |
Jul
(1) |
Aug
|
Sep
|
Oct
|
Nov
(2) |
Dec
(1) |
2017 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
(2) |
Sep
|
Oct
|
Nov
|
Dec
|
2018 |
Jan
|
Feb
(1) |
Mar
(25) |
Apr
(3) |
May
(1) |
Jun
(2) |
Jul
(2) |
Aug
|
Sep
|
Oct
|
Nov
(5) |
Dec
(1) |
2019 |
Jan
|
Feb
|
Mar
(1) |
Apr
(1) |
May
(1) |
Jun
(4) |
Jul
(3) |
Aug
|
Sep
(3) |
Oct
(6) |
Nov
(1) |
Dec
|
2020 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
(1) |
Jul
(1) |
Aug
(2) |
Sep
|
Oct
|
Nov
|
Dec
|
2022 |
Jan
|
Feb
(1) |
Mar
(1) |
Apr
(1) |
May
|
Jun
(1) |
Jul
|
Aug
|
Sep
|
Oct
|
Nov
(1) |
Dec
(1) |
2023 |
Jan
|
Feb
(1) |
Mar
(1) |
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
|
Sep
(1) |
Oct
|
Nov
|
Dec
|
2024 |
Jan
(2) |
Feb
(2) |
Mar
(5) |
Apr
(2) |
May
(1) |
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
(1) |
2025 |
Jan
(1) |
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: Fred L. D. <fd...@us...> - 2003-07-14 18:11:51
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv28022 Modified Files: branchctl Log Message: - update commented-out code - remove function that isn't used in this script Index: branchctl =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/branchctl,v retrieving revision 1.2 retrieving revision 1.3 diff -u -d -r1.2 -r1.3 --- branchctl 14 Jul 2003 16:49:30 -0000 1.2 +++ branchctl 14 Jul 2003 18:11:47 -0000 1.3 @@ -141,42 +141,13 @@ ## name = string.split(line, "/")[1] ## dirname = namekey(prefix, name) ## if os.path.isdir(dirname): -## m = load_change_info(dirname) +## m = load_entries(dirname) ## mapping.update(m) if line[0] == "/": # normal file get_entry(prefix, mapping, line, entries_fn) # else: bogus Entries line f.close() - return mapping - -def load_change_info(mapping, prefix=None): - if prefix is not None: - entries_log_fn = os.path.join(prefix, "CVS", "Entries.Log") - else: - entries_log_fn = os.path.join("CVS", "Entries.Log") - if os.path.isfile(entries_log_fn): - f = open(entries_log_fn) - while 1: - line = f.readline() - if not line: - break - if line[1:2] != ' ': - # really old version of CVS - break - entry = get_entry(prefix, mapping, line[2:], entries_log_fn) - parts = string.split(line, "/")[1:] - if line[0] == "A": - # adding a file - entry.new_revision = parts[1] - elif line[0] == "R": - # removing a file - entry.new_revision = None - f.close() - for entry in mapping.values(): - if not hasattr(entry, "new_revision"): - print 'confused about file', entry.name, '-- ignoring' - del mapping[entry.name] return mapping def load_branch_name(): |
From: Fred L. D. <fd...@us...> - 2003-07-14 17:01:36
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv14964 Modified Files: Tag: new-config-branch syncmail Log Message: Merge in many changes and refactorings based on the development of branchctl. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.16 retrieving revision 1.36.2.17 diff -u -d -r1.36.2.16 -r1.36.2.17 --- syncmail 11 Jul 2003 18:47:35 -0000 1.36.2.16 +++ syncmail 14 Jul 2003 17:01:32 -0000 1.36.2.17 @@ -39,7 +39,7 @@ --config=file Use file as the configuration file. By default, a file named - syncmail.conf is used if it exists. If the name of the + %(DEFAULT_CONFIGURATION_FILE)s is used if it exists. If the name of the configuration file is relative, it is interpreted as relative to the CVSROOT administrative directory of the repository. --config is incompatible with --no-config. @@ -131,9 +131,21 @@ fqdn = 'localhost.localdomain' return fqdn - from cStringIO import StringIO +DEFAULT_CONFIGURATION_FILE = "syncmail.conf" + +DEFAULT_CONFIGURATION = { + "context-lines": "2", + "diff-type": "unified", + "email": "true", + "from-host": "$HOSTNAME", + "smtp-server": "localhost", + "smtp-server": "localhost", + "subject-prefix": "", + "verbose": "true", + } + # Which SMTP server to do we connect to? MAILHOST = 'localhost' MAILPORT = 25 @@ -315,6 +327,9 @@ parts = string.split(line, "/") _, name, revision, timestamp, options, tagdate = parts tagdate = string.rstrip(tagdate) or None + if tagdate: + assert tagdate[0] == "T" + tagdate = tagdate[1:] key = namekey(prefix, name) try: entry = mapping[key] @@ -336,12 +351,11 @@ else: return name -def load_change_info(prefix=None): +def load_entries(prefix=None): if prefix is not None: entries_fn = os.path.join(prefix, "CVS", "Entries") else: entries_fn = os.path.join("CVS", "Entries") - entries_log_fn = entries_fn + ".Log" mapping = {} f = open(entries_fn) while 1: @@ -364,6 +378,13 @@ get_entry(prefix, mapping, line, entries_fn) # else: bogus Entries line f.close() + return mapping + +def load_change_info(mapping, prefix=None): + if prefix is not None: + entries_log_fn = os.path.join(prefix, "CVS", "Entries.Log") + else: + entries_log_fn = os.path.join("CVS", "Entries.Log") if os.path.isfile(entries_log_fn): f = open(entries_log_fn) while 1: @@ -502,35 +523,23 @@ class ConfigurationDatabase: - # The defaults set covers what we need but might not get from the - # command line or configuration file. - defaults = { - "context-lines": "2", - "diff-type": "unified", - "email": "true", - "from-host": "$HOSTNAME", - "smtp-server": "localhost", - "smtp-server": "localhost", - "subject-prefix": "", - "verbose": "true", - } - def __init__(self, filename, cmdline, args): self.args = args self.cmdline = cmdline self.config = None if filename and os.path.isfile(filename): self.config = ConfigParser() - if hasattr(cp, "readfp"): + if hasattr(self.config, "readfp"): self.config.readfp(open(filename), filename) else: # We have to use this old method for compatibility with # ancient versions of Python. self.config.read([filename]) - def get_config(self, branch): + def get_config(self, branch=None): dicts = [] if self.config: + cp = self.config if branch: dicts.append(get_section_as_dict(cp, "branch " + branch)) dicts.append(get_section_as_dict(cp, "branch")) @@ -539,9 +548,71 @@ else: dicts.append(self.cmdline) dicts = filter(None, dicts) - dicts.append(self.defaults) + dicts.append(DEFAULT_CONFIGURATION) return Options(OptionLookup(dicts, branch), self.args) +class OptionsBase: + + def __init__(self, config, args): + self.args = args + self.verbose = config.getbool('verbose') + fn = os.path.join("CVS", "Repository") + if os.path.isfile(fn): + f = open(fn) + self.repodir = string.strip(f.readline()) + f.close() + else: + self.repodir = os.curdir + + +def get_admin_file(name): + if os.environ.has_key("CVSROOT"): + p = os.path.join(os.environ["CVSROOT"], "CVSROOT", name) + return os.path.abspath(p) + else: + return os.path.join("CVSROOT", name) + +def load_configuration(args, branch=None): + cmdline, args = load_cmdline(args) + defconfig = get_admin_file(DEFAULT_CONFIGURATION_FILE) + if cmdline.has_key('config-file'): + cfgfile = get_admin_file(cmdline['config-file']) + del cmdline['config-file'] + elif os.path.isfile(defconfig): + cfgfile = defconfig + else: + cfgfile = None + return ConfigurationDatabase(cfgfile, cmdline, args) + + +class Options(OptionsBase): + + def __init__(self, config, args): + OptionsBase.__init__(self, config, args) + + self.contextlines = config.getint('context-lines') + self.diff_type = config.get('diff-type') + if self.diff_type not in ('context', 'unified'): + usage(1, "invalid diff-type specified in configuration file") + self.subject_prefix = config.get('subject-prefix') + self.replyto = config.get('reply-to') + self.fromhost = config.get('from-host') + self.email = config.getbool('email') + address = config.getaddress('smtp-server') + self.smtp_server, self.smtp_port = address + if args: + self.cvsinfo = args[0] + self.subject = self.subject_prefix + " " + self.cvsinfo + else: + self.cvsinfo = "" + self.subject = self.subject_prefix + " changes" + # The remaining args should be the email addresses + if len(args) >= 2: + self.people = string.join(args[1:], COMMASPACE) + else: + self.people = config.get("to") + + def load_cmdline(args): try: opts, args = getopt.getopt(args, @@ -584,64 +655,11 @@ set('smtp-server', arg) return cmdline, args -class Options: - def __init__(self, config, args): - self.contextlines = config.getint('context-lines') - self.diff_type = config.get('diff-type') - if self.diff_type not in ('context', 'unified'): - usage(1, "invalid diff-type specified in configuration file") - self.verbose = config.getbool('verbose') - self.subject_prefix = config.get('subject-prefix') - self.replyto = config.get('reply-to') - self.fromhost = config.get('from-host') - self.email = config.getbool('email') - address = config.getaddress('smtp-server') - self.smtp_server, self.smtp_port = address - self.args = args - if args: - self.cvsinfo = args[0] - self.subject = self.subject_prefix + " " + self.cvsinfo - else: - self.cvsinfo = "" - self.subject = self.subject_prefix + " changes" - # The remaining args should be the email addresses - if len(args) >= 2: - self.people = string.join(args[1:], COMMASPACE) - else: - self.people = config.get("to") - - fn = os.path.join("CVS", "Repository") - if os.path.isfile(fn): - f = open(fn) - self.repodir = string.strip(f.readline()) - f.close() - else: - self.repodir = os.curdir - -def get_admin_file(name): - if os.environ.has_key("CVSROOT"): - p = os.path.join(os.environ["CVSROOT"], "CVSROOT", name) - return os.path.abspath(p) - else: - return os.path.join("CVSROOT", name) -def load_configuration(args, branch=None): - cmdline, args = load_cmdline(args) - defconfig = get_admin_file("syncmail.conf") - if cmdline.has_key('config-file'): - cfgfile = get_admin_file(cmdline['config-file']) - del cmdline['config-file'] - elif os.path.isfile(defconfig): - cfgfile = defconfig - else: - cfgfile = None - db = ConfigurationDatabase(cfgfile, cmdline, args) - return db.get_config(branch) - - def main(): # load the options - config = load_configuration(sys.argv[1:], load_branch_name()) + db = load_configuration(sys.argv[1:]) + config = db.get_config(load_branch_name()) if not config.people: # Nobody to send mail to! @@ -660,7 +678,7 @@ print 'Not sending email for imported sources.' return - items = load_change_info().items() + items = load_change_info(load_entries()).items() items.sort() changes = [] for name, entry in items: |
From: Fred L. D. <fd...@us...> - 2003-07-14 17:01:36
|
Update of /cvsroot/cvs-syncmail/syncmail/tests In directory sc8-pr-cvs1:/tmp/cvs-serv14964/tests Modified Files: Tag: new-config-branch test_options.py Log Message: Merge in many changes and refactorings based on the development of branchctl. Index: test_options.py =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/tests/Attic/test_options.py,v retrieving revision 1.1.2.4 retrieving revision 1.1.2.5 diff -u -d -r1.1.2.4 -r1.1.2.5 --- test_options.py 11 Jul 2003 12:27:18 -0000 1.1.2.4 +++ test_options.py 14 Jul 2003 17:01:32 -0000 1.1.2.5 @@ -73,7 +73,7 @@ self.assertEqual(args, []) def test_load_configuration_empty(self): - config = syncmail.load_configuration([]) + config = syncmail.load_configuration([]).get_config() self.assertEqual(config.contextlines, 2) self.assertEqual(config.verbose, 1) self.assertEqual(config.smtp_server, syncmail.MAILHOST) @@ -81,18 +81,18 @@ def test_load_configuration_simple(self): config = syncmail.load_configuration( - ['-q', '--mailhost=smtp.example.com']) + ['-q', '--mailhost=smtp.example.com']).get_config() self.assertEqual(config.verbose, 0) self.assertEqual(config.smtp_server, "smtp.example.com") self.assertEqual(config.smtp_port, syncmail.MAILPORT) def test_load_configuration_smtp_address(self): config = syncmail.load_configuration( - ['--mailhost=smtp.example.com:8025']) + ['--mailhost=smtp.example.com:8025']).get_config() self.assertEqual(config.smtp_server, "smtp.example.com") self.assertEqual(config.smtp_port, 8025) - config = syncmail.load_configuration(['--mailhost=:8025']) + config = syncmail.load_configuration(['--mailhost=:8025']).get_config() self.assertEqual(config.smtp_server, syncmail.MAILHOST) self.assertEqual(config.smtp_port, 8025) |
From: Fred L. D. Jr. <fd...@ac...> - 2003-07-14 16:52:03
|
I've just added a new script to the cvs-syncmail CVS trunk: branchctl. This script controls the use of branches within a CVS repository. For each branch (including the trunk), two controls are available: - whether the branch is open for commits, and - whether commits involving that branch and any other should be permitted. Both default to true since that's the normal CVS behavior. Much of this code is based on the new-config-branch version of syncmail, and allowed portions of that to be tested in a simpler environment. The configuration file should be present in the CSVROOT administrative directory alongside the script. The configuration files have the same structure as the strawman configuration file for syncmail, but different keys are checked: open Indicates whether checkins are allowed. The value may be "true" or "false"; the default is "true". cross-branch-commits Indicates whether a single commit may span multiple branches. The implementation only checks this for a single directory at a time; commits that affect one branch in one directory and another branch in a separate directory are not (yet) detected. The value may be "true" or "false"; the default is "true". An example configuration may look like this: ---------------------------------------- [general] open = true cross-branch-commits = false [branch unmaintained-maintenance-branch] ; we don't like this branch any more open = false ---------------------------------------- This configuration data can co-exist in the configuration file for the new version of syncmail that's currently in development, so all your branch-control configuration can be in one place. The default configuration file names are different for the two scripts; I'll suggest appropriate usage would be to create a single file and name it explicitly using the --config option for each script. This script may provide a good foundation for additional forms of access control for CVS repositories as well. -Fred -- Fred L. Drake, Jr. <fdrake at acm.org> PythonLabs at Zope Corporation |
From: Fred L. D. <fd...@us...> - 2003-07-14 16:49:44
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv11396 Modified Files: branchctl Log Message: This time, actually copy in the tested, working version of the script from my test repository. Index: branchctl =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/branchctl,v retrieving revision 1.1 retrieving revision 1.2 diff -u -d -r1.1 -r1.2 --- branchctl 14 Jul 2003 16:30:00 -0000 1.1 +++ branchctl 14 Jul 2003 16:49:30 -0000 1.2 @@ -16,7 +16,7 @@ --config=file Use file as the configuration file. By default, a file named - %(DEFAULT_CONFIGURATION_FILE) is used if it exists. If the name of the + %(DEFAULT_CONFIGURATION_FILE)s is used if it exists. If the name of the configuration file is relative, it is interpreted as relative to the CVSROOT administrative directory of the repository. --config is incompatible with --no-config. @@ -43,14 +43,34 @@ import getopt import os import re +import socket import string import sys +try: + from socket import getfqdn +except ImportError: + def getfqdn(): + # Python 1.5.2 :( + hostname = socket.gethostname() + byaddr = socket.gethostbyaddr(socket.gethostbyname(hostname)) + aliases = byaddr[1] + aliases.insert(0, byaddr[0]) + aliases.insert(0, hostname) + for fqdn in aliases: + if '.' in fqdn: + break + else: + fqdn = 'localhost.localdomain' + return fqdn + + DEFAULT_CONFIGURATION_FILE = "branchctl.conf" DEFAULT_CONFIGURATION = { "verbose": "true", "open": "true", + "cross-branch-commits": "true", } @@ -77,6 +97,9 @@ parts = string.split(line, "/") _, name, revision, timestamp, options, tagdate = parts tagdate = string.rstrip(tagdate) or None + if tagdate: + assert tagdate[0] == "T" + tagdate = tagdate[1:] key = namekey(prefix, name) try: entry = mapping[key] @@ -98,12 +121,11 @@ else: return name -def load_change_info(prefix=None): +def load_entries(prefix=None): if prefix is not None: entries_fn = os.path.join(prefix, "CVS", "Entries") else: entries_fn = os.path.join("CVS", "Entries") - entries_log_fn = entries_fn + ".Log" mapping = {} f = open(entries_fn) while 1: @@ -126,6 +148,13 @@ get_entry(prefix, mapping, line, entries_fn) # else: bogus Entries line f.close() + return mapping + +def load_change_info(mapping, prefix=None): + if prefix is not None: + entries_log_fn = os.path.join(prefix, "CVS", "Entries.Log") + else: + entries_log_fn = os.path.join("CVS", "Entries.Log") if os.path.isfile(entries_log_fn): f = open(entries_log_fn) while 1: @@ -270,7 +299,7 @@ self.config = None if filename and os.path.isfile(filename): self.config = ConfigParser() - if hasattr(cp, "readfp"): + if hasattr(self.config, "readfp"): self.config.readfp(open(filename), filename) else: # We have to use this old method for compatibility with @@ -280,6 +309,7 @@ def get_config(self, branch): dicts = [] if self.config: + cp = self.config if branch: dicts.append(get_section_as_dict(cp, "branch " + branch)) dicts.append(get_section_as_dict(cp, "branch")) @@ -288,7 +318,7 @@ else: dicts.append(self.cmdline) dicts = filter(None, dicts) - dicts.append(DEFAULT_CONFIGURATION.copy()) + dicts.append(DEFAULT_CONFIGURATION) return Options(OptionLookup(dicts, branch), self.args) @@ -334,7 +364,7 @@ OptionsBase.__init__(self, config, args) self.open = config.getbool("open") - + self.cross_commits = config.getbool("cross-branch-commits") def load_cmdline(args): try: @@ -364,23 +394,36 @@ def main(): branch = load_branch_name() - configdb = load_configuration(sys.argv[1:], branch) - changes = load_change_info().values() + configdb = load_configuration(sys.argv[1:]) + changes = load_entries().values() d = {} for change in changes: - tag = changes.tagdate or branch - d[tag] = 1 - closed = 0 + tag = change.tagdate or branch + if d.has_key(tag): + d[tag].append(change) + else: + d[tag] = [change] + denied = 0 + crossed_branches = len(d) > 1 for tag in d.keys(): config = configdb.get_config(tag) + if tag: + branchname = "branch " + tag + else: + branchname = "trunk" if not config.open: - closed = 1 - if tag: - print "branch %s is closed" % `tag` - else: - print "trunk is closed" - if closed: + if not denied: + denied = 1 + print + print branchname, "is closed" + if crossed_branches and not config.cross_commits: + if not denied: + denied = 1 + print + print branchname, "does not permit cross-branch commits" + if denied: + print sys.exit(1) |
From: Fred L. D. <fd...@us...> - 2003-07-14 16:30:04
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv7352 Added Files: branchctl Log Message: Script which controls the use of branches within a CVS repository. For each branch (including the trunk), two controls are available: whether the branch is open for commits, and whether commits involving that branch and any other should be permitted. Both default to true since that's the normal CVS behavior. Much of this code is based on the new-config-branch version of syncmail, and allowed portions of that to be tested in a simpler environment. --- NEW FILE: branchctl --- #! /usr/bin/env python # # Script to control whether CVS branches are open or closed for # changes. # # This is intended to be called from the commitinfo hook. """\ Script which controls the use of branches within a CVS repository. Usage: %(PROGRAM)s [options] [<%%S> [email-addr ...]] Where options are: --config=file Use file as the configuration file. By default, a file named %(DEFAULT_CONFIGURATION_FILE) is used if it exists. If the name of the configuration file is relative, it is interpreted as relative to the CVSROOT administrative directory of the repository. --config is incompatible with --no-config. --no-config Do not use a configuration file. This can be used to disable the use of the default configuration file. --no-config is incompatible with --config. --cvsroot=<path> Use <path> as the environment variable CVSROOT. Otherwise this variable must exist in the environment. --quiet / -q Don't print as much status to stdout. --help / -h Print this text. """ # The configuration data can co-exist in the same configuration file # with the configuration for the "new-world" version of syncmail. import getopt import os import re import string import sys DEFAULT_CONFIGURATION_FILE = "branchctl.conf" DEFAULT_CONFIGURATION = { "verbose": "true", "open": "true", } def usage(code, msg=''): print __doc__ % globals() if msg: print msg sys.exit(code) class CVSEntry: def __init__(self, name, revision, timestamp, conflict, options, tagdate): self.name = name self.revision = revision self.timestamp = timestamp self.conflict = conflict self.options = options self.tagdate = tagdate def get_entry(prefix, mapping, line, filename): line = string.strip(line) parts = string.split(line, "/") _, name, revision, timestamp, options, tagdate = parts tagdate = string.rstrip(tagdate) or None key = namekey(prefix, name) try: entry = mapping[key] except KeyError: if revision == "0": revision = None if "+" in timestamp: timestamp, conflict = tuple(string.split(timestamp, "+")) else: conflict = None entry = CVSEntry(key, revision, timestamp, conflict, options, tagdate) mapping[key] = entry return entry def namekey(prefix, name): if prefix: return os.path.join(prefix, name) else: return name def load_change_info(prefix=None): if prefix is not None: entries_fn = os.path.join(prefix, "CVS", "Entries") else: entries_fn = os.path.join("CVS", "Entries") entries_log_fn = entries_fn + ".Log" mapping = {} f = open(entries_fn) while 1: line = f.readline() if not line: break ## if string.strip(line) == "D": ## continue # we could recurse down subdirs, except the Entries.Log files # we need haven't been written to the subdirs yet, so it # doesn't do us any good ## if line[0] == "D": ## name = string.split(line, "/")[1] ## dirname = namekey(prefix, name) ## if os.path.isdir(dirname): ## m = load_change_info(dirname) ## mapping.update(m) if line[0] == "/": # normal file get_entry(prefix, mapping, line, entries_fn) # else: bogus Entries line f.close() if os.path.isfile(entries_log_fn): f = open(entries_log_fn) while 1: line = f.readline() if not line: break if line[1:2] != ' ': # really old version of CVS break entry = get_entry(prefix, mapping, line[2:], entries_log_fn) parts = string.split(line, "/")[1:] if line[0] == "A": # adding a file entry.new_revision = parts[1] elif line[0] == "R": # removing a file entry.new_revision = None f.close() for entry in mapping.values(): if not hasattr(entry, "new_revision"): print 'confused about file', entry.name, '-- ignoring' del mapping[entry.name] return mapping def load_branch_name(): tag_fn = os.path.join("CVS", "Tag") if os.path.isfile(tag_fn): f = open(tag_fn) line = string.strip(f.readline()) f.close() if line[:1] == "T": return line[1:] return None TRUE_VALUES = ('true', 'on', 'enabled') FALSE_VALUES = ('false', 'off', 'disabled') class OptionLookup: def __init__(self, dicts, branch=None): self._dicts = dicts self._replace = Replacer({ "BRANCH": branch or "", "CVSROOT": os.environ.get("CVSROOT", ""), "HOSTNAME": os.environ.get("HOSTNAME") or getfqdn(), }) def get(self, option): for dict in self._dicts: v = dict.get(option) if v is not None: return self._replace(v) return None def getbool(self, option): v = self.get(option) if v is not None: v = string.lower(v) if v in TRUE_VALUES: v = 1 elif v in FALSE_VALUES: v = 0 else: raise ValueError("illegal boolean value: %s" % `v`) return v def getint(self, option): v = self.get(option) if v is not None: v = int(v) return v def getaddress(self, option): """Return (host, port) for a host:port or host string. The port, if ommitted, will be None. """ v = self.get(option) if v is None: return MAILHOST, MAILPORT elif ":" in v: h, p = tuple(string.split(v, ":")) p = int(p) return h or MAILHOST, p else: return v, MAILPORT # Support for $VARIABLE replacement. class Replacer: def __init__(self, vars): self._vars = vars rx = re.compile(r"\$([a-zA-Z][a-zA-Z_]*\b|\{[a-zA-Z][a-zA-Z_]*\})") self._search = rx.search def __call__(self, v): v, name, suffix = self._split(v) while name: v = v + self._vars.get(name, "") prefix, name, suffix = self._split(suffix) v = v + prefix return v def _split(self, s): m = self._search(s) if m is not None: name = m.group(1) if name[0] == "{": name = name[1:-1] return s[:m.start()], name, s[m.end():] else: return s, None, '' def get_section_as_dict(config, section): d = {} if config.has_section(section): for opt in config.options(section): d[opt] = config.get(section, opt, raw=1) return d from ConfigParser import ConfigParser class ConfigParser(ConfigParser): # Regular expressions for parsing section headers and options, # from the Python 2.3 version of ConfigParser. SECTCRE = re.compile( r'\[' # [ r'(?P<header>[^]]+)' # very permissive! r'\]' # ] ) # For compatibility with older versions: __SECTCRE = SECTCRE class ConfigurationDatabase: def __init__(self, filename, cmdline, args): self.args = args self.cmdline = cmdline self.config = None if filename and os.path.isfile(filename): self.config = ConfigParser() if hasattr(cp, "readfp"): self.config.readfp(open(filename), filename) else: # We have to use this old method for compatibility with # ancient versions of Python. self.config.read([filename]) def get_config(self, branch): dicts = [] if self.config: if branch: dicts.append(get_section_as_dict(cp, "branch " + branch)) dicts.append(get_section_as_dict(cp, "branch")) dicts.append(self.cmdline) dicts.append(get_section_as_dict(cp, "general")) else: dicts.append(self.cmdline) dicts = filter(None, dicts) dicts.append(DEFAULT_CONFIGURATION.copy()) return Options(OptionLookup(dicts, branch), self.args) class OptionsBase: def __init__(self, config, args): self.args = args self.verbose = config.getbool('verbose') fn = os.path.join("CVS", "Repository") if os.path.isfile(fn): f = open(fn) self.repodir = string.strip(f.readline()) f.close() else: self.repodir = os.curdir def get_admin_file(name): if os.environ.has_key("CVSROOT"): p = os.path.join(os.environ["CVSROOT"], "CVSROOT", name) return os.path.abspath(p) else: return os.path.join("CVSROOT", name) def load_configuration(args): cmdline, args = load_cmdline(args) if cmdline.has_key('config-file'): cfgfile = get_admin_file(cmdline['config-file']) del cmdline['config-file'] else: defconfig = get_admin_file(DEFAULT_CONFIGURATION_FILE) if os.path.isfile(defconfig): cfgfile = defconfig else: cfgfile = None return ConfigurationDatabase(cfgfile, cmdline, args) class Options(OptionsBase): def __init__(self, config, args): OptionsBase.__init__(self, config, args) self.open = config.getbool("open") def load_cmdline(args): try: opts, args = getopt.getopt(args, 'hq', ['cvsroot=', 'config=', 'no-config', 'help', 'quiet']) except getopt.error, msg: usage(2, msg) cmdline = {} def set(option, value, cmdline=cmdline): if cmdline.has_key(option): usage(2, "can't set option more than once") cmdline[option] = value for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt == '--cvsroot': os.environ["CVSROOT"] = arg elif opt == "--config": set('config-file', arg) elif opt == '--no-config': set('config-file', '') elif opt in ('-q', '--quiet'): set('verbose', 'false') return cmdline, args def main(): branch = load_branch_name() configdb = load_configuration(sys.argv[1:], branch) changes = load_change_info().values() d = {} for change in changes: tag = changes.tagdate or branch d[tag] = 1 closed = 0 for tag in d.keys(): config = configdb.get_config(tag) if not config.open: closed = 1 if tag: print "branch %s is closed" % `tag` else: print "trunk is closed" if closed: sys.exit(1) if __name__ == '__main__': main() |
From: Fred L. D. Jr. <fd...@ac...> - 2003-07-11 19:50:24
|
Marc Wilson writes: > Is it possible to set up syncmail so that it only sends mails for > commits done in the HEAD branch, and not for other branches in the > repository? > > This is probably more of a loginfo question which I will chase up in the > CVS docs, but I was hoping someone here might know. I think this will be possible with a complete re-construction of syncmail that I have in progress. The "new syncmail" will use a configuration file that allows settings for each branch. I posted a strawman sample configuration file to the list earlier this week: http://sourceforge.net/mailarchive/forum.php?thread_id=2742501&forum_id=8207 Please take a look at that and let me know whether it meets your requirements. I'll be glad to answer questions as well. Development is underway, but there's still plenty of space for changes. -Fred -- Fred L. Drake, Jr. <fdrake at acm.org> PythonLabs at Zope Corporation |
From: Fred L. D. <fd...@us...> - 2003-07-11 18:47:38
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv717 Modified Files: Tag: new-config-branch syncmail Log Message: - clean up code to look for files in the CVSROOT directory - look for config files named on the command line in the CVSROOT directory - remove XXX comment that is addressed by these changes Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.15 retrieving revision 1.36.2.16 diff -u -d -r1.36.2.15 -r1.36.2.16 --- syncmail 11 Jul 2003 18:39:03 -0000 1.36.2.15 +++ syncmail 11 Jul 2003 18:47:35 -0000 1.36.2.16 @@ -618,24 +618,23 @@ else: self.repodir = os.curdir -def load_configuration(args, branch=None): - cmdline, args = load_cmdline(args) +def get_admin_file(name): if os.environ.has_key("CVSROOT"): - defconfig = os.path.join(os.environ["CVSROOT"], - "CVSROOT", "syncmail.conf") + p = os.path.join(os.environ["CVSROOT"], "CVSROOT", name) + return os.path.abspath(p) else: - defconfig = os.path.join("CVSROOT", "syncmail.conf") + return os.path.join("CVSROOT", name) + +def load_configuration(args, branch=None): + cmdline, args = load_cmdline(args) + defconfig = get_admin_file("syncmail.conf") if cmdline.has_key('config-file'): - cfgfile = cmdline['config-file'] + cfgfile = get_admin_file(cmdline['config-file']) del cmdline['config-file'] elif os.path.isfile(defconfig): cfgfile = defconfig else: cfgfile = None - # XXX cfgfile is specified relative to the CVSROOT directory; we - # need to transform the path appropriately, since that won't be - # the current directory when we try to read it. In fact, a wrong - # config file may exist at the alternate location. db = ConfigurationDatabase(cfgfile, cmdline, args) return db.get_config(branch) |
From: Fred L. D. <fd...@us...> - 2003-07-11 18:39:06
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv32120 Modified Files: Tag: new-config-branch syncmail Log Message: refactor to make it easier to get the configuration data for separate branches without re-loading the configuration file Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.14 retrieving revision 1.36.2.15 diff -u -d -r1.36.2.14 -r1.36.2.15 --- syncmail 11 Jul 2003 17:51:33 -0000 1.36.2.14 +++ syncmail 11 Jul 2003 18:39:03 -0000 1.36.2.15 @@ -484,38 +484,28 @@ d[opt] = config.get(section, opt, raw=1) return d -def load_configfile(filename, cmdline, branch): - dicts = [] - if filename: - from ConfigParser import ConfigParser - class ConfigParser(ConfigParser): - # Regular expressions for parsing section headers and options, - # from the Python 2.3 version of ConfigParser. - SECTCRE = re.compile( - r'\[' # [ - r'(?P<header>[^]]+)' # very permissive! - r'\]' # ] - ) - # For compatibility with older versions: - __SECTCRE = SECTCRE - - cp = ConfigParser() - # We have to use this old method for compatibility with - # ancient versions of Python. - cp.read([filename]) - if branch: - dicts.append(get_section_as_dict(cp, "branch " + branch)) - dicts.append(get_section_as_dict(cp, "branch")) - dicts.append(cmdline) - dicts.append(get_section_as_dict(cp, "general")) - else: - dicts.append(cmdline) - dicts = filter(None, dicts) +from ConfigParser import ConfigParser +class ConfigParser(ConfigParser): + + # Regular expressions for parsing section headers and options, + # from the Python 2.3 version of ConfigParser. + + SECTCRE = re.compile( + r'\[' # [ + r'(?P<header>[^]]+)' # very permissive! + r'\]' # ] + ) + + # For compatibility with older versions: + __SECTCRE = SECTCRE + + +class ConfigurationDatabase: + # The defaults set covers what we need but might not get from the # command line or configuration file. defaults = { "context-lines": "2", - "cvsroot": "$CVSROOT", "diff-type": "unified", "email": "true", "from-host": "$HOSTNAME", @@ -524,8 +514,33 @@ "subject-prefix": "", "verbose": "true", } - dicts.append(defaults) - return OptionLookup(dicts, branch) + + def __init__(self, filename, cmdline, args): + self.args = args + self.cmdline = cmdline + self.config = None + if filename and os.path.isfile(filename): + self.config = ConfigParser() + if hasattr(cp, "readfp"): + self.config.readfp(open(filename), filename) + else: + # We have to use this old method for compatibility with + # ancient versions of Python. + self.config.read([filename]) + + def get_config(self, branch): + dicts = [] + if self.config: + if branch: + dicts.append(get_section_as_dict(cp, "branch " + branch)) + dicts.append(get_section_as_dict(cp, "branch")) + dicts.append(self.cmdline) + dicts.append(get_section_as_dict(cp, "general")) + else: + dicts.append(self.cmdline) + dicts = filter(None, dicts) + dicts.append(self.defaults) + return Options(OptionLookup(dicts, branch), self.args) def load_cmdline(args): try: @@ -546,7 +561,7 @@ if opt in ('-h', '--help'): usage(0) elif opt == '--cvsroot': - set('cvsroot', arg) + os.environ["CVSROOT"] = arg elif opt == "--config": set('config-file', arg) elif opt == '--no-config': @@ -605,17 +620,24 @@ def load_configuration(args, branch=None): cmdline, args = load_cmdline(args) + if os.environ.has_key("CVSROOT"): + defconfig = os.path.join(os.environ["CVSROOT"], + "CVSROOT", "syncmail.conf") + else: + defconfig = os.path.join("CVSROOT", "syncmail.conf") if cmdline.has_key('config-file'): cfgfile = cmdline['config-file'] del cmdline['config-file'] + elif os.path.isfile(defconfig): + cfgfile = defconfig else: - cfgfile = "syncmail.conf" - # cfgfile is specified relative to the CVSROOT directory; we need - # to transform the path appropriately, since that won't be the - # current directory when we try to read it. In fact, a wrong + cfgfile = None + # XXX cfgfile is specified relative to the CVSROOT directory; we + # need to transform the path appropriately, since that won't be + # the current directory when we try to read it. In fact, a wrong # config file may exist at the alternate location. - config = load_configfile(cfgfile, cmdline, branch) - return Options(config, args) + db = ConfigurationDatabase(cfgfile, cmdline, args) + return db.get_config(branch) def main(): |
From: Fred L. D. <fd...@us...> - 2003-07-11 18:23:53
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv29602 Modified Files: Tag: new-config-branch syncmail.conf Log Message: cvsroot will not be a setting in the configuration file Index: syncmail.conf =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/Attic/syncmail.conf,v retrieving revision 1.1.2.1 retrieving revision 1.1.2.2 diff -u -d -r1.1.2.1 -r1.1.2.2 --- syncmail.conf 10 Jul 2003 05:40:10 -0000 1.1.2.1 +++ syncmail.conf 11 Jul 2003 18:23:50 -0000 1.1.2.2 @@ -7,7 +7,6 @@ [general] ; miscellaneous -cvsroot = $CVSROOT verbose = true email = true |
From: Fred L. D. <fd...@us...> - 2003-07-11 17:51:37
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv25120 Modified Files: Tag: new-config-branch syncmail Log Message: we only need to set SECTCRE, not OPTCRE Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.13 retrieving revision 1.36.2.14 diff -u -d -r1.36.2.13 -r1.36.2.14 --- syncmail 11 Jul 2003 17:41:41 -0000 1.36.2.13 +++ syncmail 11 Jul 2003 17:51:33 -0000 1.36.2.14 @@ -496,17 +496,8 @@ r'(?P<header>[^]]+)' # very permissive! r'\]' # ] ) - OPTCRE = re.compile( - r'(?P<option>[^:=\s][^:=]*)' # very permissive! - r'\s*(?P<vi>[:=])\s*' # any number of space/tab, - # followed by separator - # (either : or =), followed - # by any # space/tab - r'(?P<value>.*)$' # everything up to eol - ) # For compatibility with older versions: __SECTCRE = SECTCRE - __OPTCRE = OPTCRE cp = ConfigParser() # We have to use this old method for compatibility with |
From: Fred L. D. <fd...@us...> - 2003-07-11 17:41:44
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv23861 Modified Files: Tag: new-config-branch syncmail Log Message: - clean up the tagdate field from the CVS/Entries file - add a note about the non-support of cross-branch commits (applies only to this branch; the trunk is fine since it doesn't care about branches) Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.12 retrieving revision 1.36.2.13 diff -u -d -r1.36.2.12 -r1.36.2.13 --- syncmail 11 Jul 2003 17:21:39 -0000 1.36.2.12 +++ syncmail 11 Jul 2003 17:41:41 -0000 1.36.2.13 @@ -314,6 +314,7 @@ line = string.strip(line) parts = string.split(line, "/") _, name, revision, timestamp, options, tagdate = parts + tagdate = string.rstrip(tagdate) or None key = namekey(prefix, name) try: entry = mapping[key] @@ -652,6 +653,10 @@ changes = [] for name, entry in items: changes.append(entry) + + # XXX Commits can span branches; we haven't allowed for that at + # all! This seriously impacts how we handle the configuration + # data; need to re-visit. if config.verbose: print 'Mailing %s...' % config.people |
From: Fred L. D. <fd...@us...> - 2003-07-11 17:21:42
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv21033 Modified Files: Tag: new-config-branch syncmail Log Message: - move loading of the repository directory into Options.__init__() - add check that we actually have email addresses to send to - update and re-wrap a comment Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.11 retrieving revision 1.36.2.12 diff -u -d -r1.36.2.11 -r1.36.2.12 --- syncmail 10 Jul 2003 22:15:00 -0000 1.36.2.11 +++ syncmail 11 Jul 2003 17:21:39 -0000 1.36.2.12 @@ -602,7 +602,14 @@ self.people = string.join(args[1:], COMMASPACE) else: self.people = config.get("to") - self.repodir = os.curdir + + fn = os.path.join("CVS", "Repository") + if os.path.isfile(fn): + f = open(fn) + self.repodir = string.strip(f.readline()) + f.close() + else: + self.repodir = os.curdir def load_configuration(args, branch=None): cmdline, args = load_cmdline(args) @@ -622,13 +629,17 @@ def main(): # load the options config = load_configuration(sys.argv[1:], load_branch_name()) - s = open(os.path.join("CVS", "Repository")).readline() - config.repodir = string.strip(s) - # args[0] is the specification containing the files that were - # modified. The argument actually must be split, with the first component - # containing the directory the checkin is being made in, relative to - # $CVSROOT, followed by the list of files that are changing. + if not config.people: + # Nobody to send mail to! + print 'No email addresses provided; exiting.' + return + + # config.cvsinfo is the specification containing the files that + # were modified. The argument actually must be split, with the + # first component containing the directory the checkin is being + # made in, relative to $CVSROOT, followed by the list of files + # that are changing. specs = string.split(config.cvsinfo) if specs[-3:] == ['-', 'Imported', 'sources']: |
From: Fred L. D. <fd...@us...> - 2003-07-11 12:27:23
|
Update of /cvsroot/cvs-syncmail/syncmail/tests In directory sc8-pr-cvs1:/tmp/cvs-serv5865 Modified Files: Tag: new-config-branch test_options.py Log Message: - make sure the usage-errors actually generated help text - add tests for explicit requests for help Index: test_options.py =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/tests/Attic/test_options.py,v retrieving revision 1.1.2.3 retrieving revision 1.1.2.4 diff -u -d -r1.1.2.3 -r1.1.2.4 --- test_options.py 11 Jul 2003 12:19:29 -0000 1.1.2.3 +++ test_options.py 11 Jul 2003 12:27:18 -0000 1.1.2.4 @@ -3,6 +3,7 @@ # These tests assume that the syncmail script is in the current directory. import os +import string import sys import unittest @@ -99,8 +100,9 @@ class UsageErrorTests(unittest.TestCase): def setUp(self): - sys.stderr = StringIO() - sys.stdout = sys.stderr + self.sio = StringIO() + sys.stderr = self.sio + sys.stdout = self.sio def tearDown(self): sys.stderr = sys.__stderr__ @@ -109,14 +111,27 @@ def test_simple_option_twice(self): self.assertRaises(SystemExit, syncmail.load_cmdline, ['-qq']) + self.failUnless(string.strip(self.sio.getvalue())) def test_competing_options(self): self.assertRaises(SystemExit, syncmail.load_cmdline, ['--config=filename', '--no-config']) + self.failUnless(string.strip(self.sio.getvalue())) def test_double_negation(self): self.assertRaises(SystemExit, syncmail.load_cmdline, ['--no-config', '--no-config']) + self.failUnless(string.strip(self.sio.getvalue())) + + def test_dash_h(self): + self.assertRaises(SystemExit, syncmail.load_cmdline, + ['-h']) + self.failUnless(string.strip(self.sio.getvalue())) + + def test_dash_dash_help(self): + self.assertRaises(SystemExit, syncmail.load_cmdline, + ['--help']) + self.failUnless(string.strip(self.sio.getvalue())) def test_suite(): |
From: Fred L. D. <fd...@us...> - 2003-07-11 12:19:33
|
Update of /cvsroot/cvs-syncmail/syncmail/tests In directory sc8-pr-cvs1:/tmp/cvs-serv4734 Modified Files: Tag: new-config-branch test_options.py Log Message: more tests of the command line handling Index: test_options.py =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/tests/Attic/test_options.py,v retrieving revision 1.1.2.2 retrieving revision 1.1.2.3 diff -u -d -r1.1.2.2 -r1.1.2.3 --- test_options.py 11 Jul 2003 05:11:00 -0000 1.1.2.2 +++ test_options.py 11 Jul 2003 12:19:29 -0000 1.1.2.3 @@ -56,6 +56,21 @@ def test_load_cmdline_empty(self): self.assertEqual(syncmail.load_cmdline([]), ({}, [])) + def test_load_cmdline_simple(self): + cmdline = ['abc foo.txt,1.2,1.3', 'me...@ex...'] + options, args = syncmail.load_cmdline(cmdline) + self.assertEqual(options, {}) + self.assertEqual(args, cmdline) + + def test_load_cmdline_with_options(self): + cmdline = ['-qC4', '-u', '--mailhost', 'mail.example.com'] + options, args = syncmail.load_cmdline(cmdline) + self.assertEqual(options, {'verbose': 'false', + 'context-lines': '4', + 'diff-type': 'unified', + 'smtp-server': 'mail.example.com'}) + self.assertEqual(args, []) + def test_load_configuration_empty(self): config = syncmail.load_configuration([]) self.assertEqual(config.contextlines, 2) |
From: Fred L. D. <fd...@us...> - 2003-07-11 05:11:03
|
Update of /cvsroot/cvs-syncmail/syncmail/tests In directory sc8-pr-cvs1:/tmp/cvs-serv17549 Modified Files: Tag: new-config-branch test_options.py Log Message: Rework the tests to use the unit testing framework. Index: test_options.py =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/tests/Attic/test_options.py,v retrieving revision 1.1.2.1 retrieving revision 1.1.2.2 diff -u -d -r1.1.2.1 -r1.1.2.2 --- test_options.py 11 Jul 2003 04:26:36 -0000 1.1.2.1 +++ test_options.py 11 Jul 2003 05:11:00 -0000 1.1.2.2 @@ -4,92 +4,110 @@ import os import sys +import unittest from cStringIO import StringIO import syncmail -def eq(a, b, msg=None): - if msg is None: - msg = "%s != %s" % (`a`, `b`) - if not a == b: - raise AssertionError(msg) +class SimpleOptionTests(unittest.TestCase): -def raises(exctype, f, *args, **kw): - try: - apply(f, args, kw) - except exctype: - pass - else: - raise AssertionError("expected exception") + def test_replacer(self): + VARS = {"FOO": "<whack!>"} -VARS = {"FOO": "<whack!>"} + # "variable" replacement + replace = syncmail.Replacer(VARS) -# "variable" replacement -replace = syncmail.Replacer(VARS) + self.assertEqual(replace("abc$FOO-def${SPLAT}"), "abc<whack!>-def") + self.assertEqual(replace("$FOO"), "<whack!>") + self.assertEqual(replace("$ FOO"), "$ FOO") + self.assertEqual(replace("${FOO}"), "<whack!>") + self.assertEqual(replace(" ${FOO} "), " <whack!> ") -eq(replace("abc$FOO-def${SPLAT}"), "abc<whack!>-def") -eq(replace("$FOO"), "<whack!>") -eq(replace("$ FOO"), "$ FOO") -eq(replace("${FOO}"), "<whack!>") -eq(replace(" ${FOO} "), " <whack!> ") + def test_OptionLookup_cascade(self): + dicts = [ + {'common': '1', 'first': 'one'}, + {'common': '2', 'second': 'two'}, + {'common': '3', 'third': 'three'}, + {'common': '4', 'fourth': 'four'}, + ] + options = syncmail.OptionLookup(dicts) + self.assertEqual(options.get('common'), '1') + self.assertEqual(options.get('first'), 'one') + self.assertEqual(options.get('second'), 'two') + self.assertEqual(options.get('third'), 'three') + self.assertEqual(options.get('fourth'), 'four') + self.assertEqual(options.get('missing'), None) + self.assertEqual(options.get('common'), '1') + self.assertEqual(options.get('third'), 'three') -# load_cmdline() -eq(syncmail.load_cmdline([]), ({}, [])) + def test_OptionLookup_replacements(self): + dicts = [ + {"foo": "bar", + "branch": "$BRANCH", + "hostname": "$HOSTNAME"}, + ] + options = syncmail.OptionLookup(dicts, "my-branch") + self.assertEqual(options.get("branch"), "my-branch") + host = os.environ.get("HOSTNAME", syncmail.getfqdn()) + self.assertEqual(options.get("hostname"), host) -sys.stderr = StringIO() -sys.stdout = sys.stderr -try: - raises(SystemExit, - syncmail.load_cmdline, ['-qq']) - raises(SystemExit, - syncmail.load_cmdline, ['--config=filename', '--no-config']) - raises(SystemExit, - syncmail.load_cmdline, ['--no-config', '--no-config']) -finally: - sys.stderr = sys.__stderr__ - sys.stdout = sys.__stdout__ + def test_load_cmdline_empty(self): + self.assertEqual(syncmail.load_cmdline([]), ({}, [])) -# load_configuration() -config = syncmail.load_configuration([]) -eq(config.contextlines, 2) -eq(config.verbose, 1) -eq(config.smtp_server, syncmail.MAILHOST) -eq(config.smtp_port, syncmail.MAILPORT) + def test_load_configuration_empty(self): + config = syncmail.load_configuration([]) + self.assertEqual(config.contextlines, 2) + self.assertEqual(config.verbose, 1) + self.assertEqual(config.smtp_server, syncmail.MAILHOST) + self.assertEqual(config.smtp_port, syncmail.MAILPORT) -config = syncmail.load_configuration(['-q', '--mailhost=smtp.example.com']) -eq(config.verbose, 0) -eq(config.smtp_server, "smtp.example.com") -eq(config.smtp_port, syncmail.MAILPORT) + def test_load_configuration_simple(self): + config = syncmail.load_configuration( + ['-q', '--mailhost=smtp.example.com']) + self.assertEqual(config.verbose, 0) + self.assertEqual(config.smtp_server, "smtp.example.com") + self.assertEqual(config.smtp_port, syncmail.MAILPORT) -config = syncmail.load_configuration(['--mailhost=smtp.example.com:8025']) -eq(config.smtp_server, "smtp.example.com") -eq(config.smtp_port, 8025) + def test_load_configuration_smtp_address(self): + config = syncmail.load_configuration( + ['--mailhost=smtp.example.com:8025']) + self.assertEqual(config.smtp_server, "smtp.example.com") + self.assertEqual(config.smtp_port, 8025) -config = syncmail.load_configuration(['--mailhost=:8025']) -eq(config.smtp_server, syncmail.MAILHOST) -eq(config.smtp_port, 8025) + config = syncmail.load_configuration(['--mailhost=:8025']) + self.assertEqual(config.smtp_server, syncmail.MAILHOST) + self.assertEqual(config.smtp_port, 8025) -# OptionLookup -dicts = [ - {'common': '1', 'first': 'one'}, - {'common': '2', 'second': 'two'}, - {'common': '3', 'third': 'three'}, - {'common': '4', 'fourth': 'four'}, - ] -options = syncmail.OptionLookup(dicts) -eq(options.get('common'), '1') -eq(options.get('first'), 'one') -eq(options.get('second'), 'two') -eq(options.get('third'), 'three') -eq(options.get('fourth'), 'four') -eq(options.get('missing'), None) -eq(options.get('common'), '1') -eq(options.get('third'), 'three') -options = syncmail.OptionLookup([{"foo": "bar", - "branch": "$BRANCH", - "hostname": "$HOSTNAME"}], "my-branch") -eq(options.get("branch"), "my-branch") -eq(options.get("hostname"), os.environ.get("HOSTNAME", syncmail.getfqdn())) +class UsageErrorTests(unittest.TestCase): + + def setUp(self): + sys.stderr = StringIO() + sys.stdout = sys.stderr + + def tearDown(self): + sys.stderr = sys.__stderr__ + sys.stdout = sys.__stdout__ + + def test_simple_option_twice(self): + self.assertRaises(SystemExit, syncmail.load_cmdline, + ['-qq']) + + def test_competing_options(self): + self.assertRaises(SystemExit, syncmail.load_cmdline, + ['--config=filename', '--no-config']) + + def test_double_negation(self): + self.assertRaises(SystemExit, syncmail.load_cmdline, + ['--no-config', '--no-config']) + + +def test_suite(): + suite = unittest.makeSuite(SimpleOptionTests) + suite.addTest(unittest.makeSuite(UsageErrorTests)) + return suite + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") |
From: Fred L. D. <fd...@us...> - 2003-07-11 05:09:17
|
Update of /cvsroot/cvs-syncmail/syncmail/tests In directory sc8-pr-cvs1:/tmp/cvs-serv17095 Modified Files: Tag: new-config-branch unittest.py Log Message: Make some confusing logic even more confusing so this can work with the Python 1.5.2 versions of isinstance() and issubclass(). Index: unittest.py =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/tests/Attic/unittest.py,v retrieving revision 1.1.2.3 retrieving revision 1.1.2.4 diff -u -d -r1.1.2.3 -r1.1.2.4 --- unittest.py 11 Jul 2003 04:59:04 -0000 1.1.2.3 +++ unittest.py 11 Jul 2003 05:09:14 -0000 1.1.2.4 @@ -486,7 +486,11 @@ import unittest if type(obj) == types.ModuleType: return self.loadTestsFromModule(obj) - elif (isinstance(obj, (type, types.ClassType)) and + elif (type(obj) is types.ClassType and + issubclass(obj, unittest.TestCase)): + return self.loadTestsFromTestCase(obj) + elif (int is type(1) and + isinstance(obj, type) and issubclass(obj, unittest.TestCase)): return self.loadTestsFromTestCase(obj) elif type(obj) == types.UnboundMethodType: |
From: Fred L. D. <fd...@us...> - 2003-07-11 04:59:07
|
Update of /cvsroot/cvs-syncmail/syncmail/tests In directory sc8-pr-cvs1:/tmp/cvs-serv15893 Modified Files: Tag: new-config-branch unittest.py Log Message: Fix typo introduced when making this module compatible with Python 1.5.2. Index: unittest.py =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/tests/Attic/unittest.py,v retrieving revision 1.1.2.2 retrieving revision 1.1.2.3 diff -u -d -r1.1.2.2 -r1.1.2.3 --- unittest.py 11 Jul 2003 04:39:29 -0000 1.1.2.2 +++ unittest.py 11 Jul 2003 04:59:04 -0000 1.1.2.3 @@ -562,7 +562,7 @@ return getattr(self.stream,attr) def writeln(self, arg=None): - if args is not None: self.write(arg) + if arg is not None: self.write(arg) self.write('\n') # text-mode streams translate to \r\n if needed |
From: Fred L. D. <fd...@us...> - 2003-07-11 04:39:32
|
Update of /cvsroot/cvs-syncmail/syncmail/tests In directory sc8-pr-cvs1:/tmp/cvs-serv14072 Modified Files: Tag: new-config-branch unittest.py Log Message: Make the module compatible with Python 1.5.2. Index: unittest.py =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/tests/Attic/unittest.py,v retrieving revision 1.1.2.1 retrieving revision 1.1.2.2 diff -u -d -r1.1.2.1 -r1.1.2.2 --- unittest.py 11 Jul 2003 04:38:12 -0000 1.1.2.1 +++ unittest.py 11 Jul 2003 04:39:29 -0000 1.1.2.2 @@ -115,7 +115,8 @@ def _exc_info_to_string(self, err): """Converts a sys.exc_info()-style tuple of values into a string.""" - return string.join(traceback.format_exception(*err), '') + lines = traceback.format_exception(err[0], err[1], err[2]) + return string.join(lines, '') def __repr__(self): return "<%s run=%i errors=%i failures=%i>" % \ @@ -276,7 +277,7 @@ unexpected exception. """ try: - callableObj(*args, **kwargs) + apply(callableObj, args, kwargs) except excClass: return else: @@ -560,8 +561,8 @@ def __getattr__(self, attr): return getattr(self.stream,attr) - def writeln(self, *args): - if args: self.write(*args) + def writeln(self, arg=None): + if args is not None: self.write(arg) self.write('\n') # text-mode streams translate to \r\n if needed |
From: Fred L. D. <fd...@us...> - 2003-07-11 04:38:16
|
Update of /cvsroot/cvs-syncmail/syncmail/tests In directory sc8-pr-cvs1:/tmp/cvs-serv13875 Added Files: Tag: new-config-branch unittest.py Log Message: Add the unittest module from Python CVS. --- NEW FILE: unittest.py --- #!/usr/bin/env python ''' Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's Smalltalk testing framework. This module contains the core framework classes that form the basis of specific test cases and suites (TestCase, TestSuite etc.), and also a text-based utility class for running the tests and reporting the results (TextTestRunner). Simple usage: import unittest class IntegerArithmenticTestCase(unittest.TestCase): def testAdd(self): ## test method names begin 'test*' self.assertEquals((1 + 2), 3) self.assertEquals(0 + 1, 1) def testMultiply(self): self.assertEquals((0 * 10), 0) self.assertEquals((5 * 8), 40) if __name__ == '__main__': unittest.main() Further information is available in the bundled documentation, and from http://pyunit.sourceforge.net/ Copyright (c) 1999, 2000, 2001 Steve Purcell This module is free software, and you may redistribute it and/or modify it under the same terms as Python itself, so long as this copyright message and disclaimer are retained in their original form. IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. ''' __author__ = "Steve Purcell" __email__ = "stephen_purcell at yahoo dot com" __version__ = "#Revision: 1.46 $"[11:-2] import time import sys import traceback import string import os import types ############################################################################## # Test framework core ############################################################################## # All classes defined herein are 'new-style' classes, allowing use of 'super()' __metaclass__ = type def _strclass(cls): return "%s.%s" % (cls.__module__, cls.__name__) class TestResult: """Holder for test result information. Test results are automatically managed by the TestCase and TestSuite classes, and do not need to be explicitly manipulated by writers of tests. Each instance holds the total number of tests run, and collections of failures and errors that occurred among those test runs. The collections contain tuples of (testcase, exceptioninfo), where exceptioninfo is the formatted traceback of the error that occurred. """ def __init__(self): self.failures = [] self.errors = [] self.testsRun = 0 self.shouldStop = 0 def startTest(self, test): "Called when the given test is about to be run" self.testsRun = self.testsRun + 1 def stopTest(self, test): "Called when the given test has been run" pass def addError(self, test, err): """Called when an error has occurred. 'err' is a tuple of values as returned by sys.exc_info(). """ self.errors.append((test, self._exc_info_to_string(err))) def addFailure(self, test, err): """Called when an error has occurred. 'err' is a tuple of values as returned by sys.exc_info().""" self.failures.append((test, self._exc_info_to_string(err))) def addSuccess(self, test): "Called when a test has completed successfully" pass def wasSuccessful(self): "Tells whether or not this result was a success" return len(self.failures) == len(self.errors) == 0 def stop(self): "Indicates that the tests should be aborted" self.shouldStop = 1 def _exc_info_to_string(self, err): """Converts a sys.exc_info()-style tuple of values into a string.""" return string.join(traceback.format_exception(*err), '') def __repr__(self): return "<%s run=%i errors=%i failures=%i>" % \ (_strclass(self.__class__), self.testsRun, len(self.errors), len(self.failures)) class TestCase: """A class whose instances are single test cases. By default, the test code itself should be placed in a method named 'runTest'. If the fixture may be used for many test cases, create as many test methods as are needed. When instantiating such a TestCase subclass, specify in the constructor arguments the name of the test method that the instance is to execute. Test authors should subclass TestCase for their own tests. Construction and deconstruction of the test's environment ('fixture') can be implemented by overriding the 'setUp' and 'tearDown' methods respectively. If it is necessary to override the __init__ method, the base class __init__ method must always be called. It is important that subclasses should not change the signature of their __init__ method, since instances of the classes are instantiated automatically by parts of the framework in order to be run. """ # This attribute determines which exception will be raised when # the instance's assertion methods fail; test methods raising this # exception will be deemed to have 'failed' rather than 'errored' failureException = AssertionError def __init__(self, methodName='runTest'): """Create an instance of the class that will use the named test method when executed. Raises a ValueError if the instance does not have a method with the specified name. """ try: self.__testMethodName = methodName testMethod = getattr(self, methodName) self.__testMethodDoc = testMethod.__doc__ except AttributeError: raise ValueError, "no such test method in %s: %s" % \ (self.__class__, methodName) def setUp(self): "Hook method for setting up the test fixture before exercising it." pass def tearDown(self): "Hook method for deconstructing the test fixture after testing it." pass def countTestCases(self): return 1 def defaultTestResult(self): return TestResult() def shortDescription(self): """Returns a one-line description of the test, or None if no description has been provided. The default implementation of this method returns the first line of the specified test method's docstring. """ doc = self.__testMethodDoc return doc and string.strip(string.split(doc, "\n")[0]) or None def id(self): return "%s.%s" % (_strclass(self.__class__), self.__testMethodName) def __str__(self): return "%s (%s)" % (self.__testMethodName, _strclass(self.__class__)) def __repr__(self): return "<%s testMethod=%s>" % \ (_strclass(self.__class__), self.__testMethodName) def run(self, result=None): return self(result) def __call__(self, result=None): if result is None: result = self.defaultTestResult() result.startTest(self) testMethod = getattr(self, self.__testMethodName) try: try: self.setUp() except KeyboardInterrupt: raise except: result.addError(self, self.__exc_info()) return ok = 0 try: testMethod() ok = 1 except self.failureException, e: result.addFailure(self, self.__exc_info()) except KeyboardInterrupt: raise except: result.addError(self, self.__exc_info()) try: self.tearDown() except KeyboardInterrupt: raise except: result.addError(self, self.__exc_info()) ok = 0 if ok: result.addSuccess(self) finally: result.stopTest(self) def debug(self): """Run the test without collecting errors in a TestResult""" self.setUp() getattr(self, self.__testMethodName)() self.tearDown() def __exc_info(self): """Return a version of sys.exc_info() with the traceback frame minimised; usually the top level of the traceback frame is not needed. """ exctype, excvalue, tb = sys.exc_info() if sys.platform[:4] == 'java': ## tracebacks look different in Jython return (exctype, excvalue, tb) newtb = tb.tb_next if newtb is None: return (exctype, excvalue, tb) return (exctype, excvalue, newtb) def fail(self, msg=None): """Fail immediately, with the given message.""" raise self.failureException, msg def failIf(self, expr, msg=None): "Fail the test if the expression is true." if expr: raise self.failureException, msg def failUnless(self, expr, msg=None): """Fail the test unless the expression is true.""" if not expr: raise self.failureException, msg def failUnlessRaises(self, excClass, callableObj, *args, **kwargs): """Fail unless an exception of class excClass is thrown by callableObj when invoked with arguments args and keyword arguments kwargs. If a different type of exception is thrown, it will not be caught, and the test case will be deemed to have suffered an error, exactly as for an unexpected exception. """ try: callableObj(*args, **kwargs) except excClass: return else: if hasattr(excClass,'__name__'): excName = excClass.__name__ else: excName = str(excClass) raise self.failureException, excName def failUnlessEqual(self, first, second, msg=None): """Fail if the two objects are unequal as determined by the '==' operator. """ if not first == second: raise self.failureException, \ (msg or '%s != %s' % (`first`, `second`)) def failIfEqual(self, first, second, msg=None): """Fail if the two objects are equal as determined by the '==' operator. """ if first == second: raise self.failureException, \ (msg or '%s == %s' % (`first`, `second`)) def failUnlessAlmostEqual(self, first, second, places=7, msg=None): """Fail if the two objects are unequal as determined by their difference rounded to the given number of decimal places (default 7) and comparing to zero. Note that decimal places (from zero) is usually not the same as significant digits (measured from the most signficant digit). """ if round(second-first, places) != 0: raise self.failureException, \ (msg or '%s != %s within %s places' % (`first`, `second`, `places` )) def failIfAlmostEqual(self, first, second, places=7, msg=None): """Fail if the two objects are equal as determined by their difference rounded to the given number of decimal places (default 7) and comparing to zero. Note that decimal places (from zero) is usually not the same as significant digits (measured from the most signficant digit). """ if round(second-first, places) == 0: raise self.failureException, \ (msg or '%s == %s within %s places' % (`first`, `second`, `places`)) assertEqual = assertEquals = failUnlessEqual assertNotEqual = assertNotEquals = failIfEqual assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual assertRaises = failUnlessRaises assert_ = failUnless class TestSuite: """A test suite is a composite test consisting of a number of TestCases. For use, create an instance of TestSuite, then add test case instances. When all tests have been added, the suite can be passed to a test runner, such as TextTestRunner. It will run the individual test cases in the order in which they were added, aggregating the results. When subclassing, do not forget to call the base class constructor. """ def __init__(self, tests=()): self._tests = [] self.addTests(tests) def __repr__(self): return "<%s tests=%s>" % (_strclass(self.__class__), self._tests) __str__ = __repr__ def countTestCases(self): cases = 0 for test in self._tests: cases = cases + test.countTestCases() return cases def addTest(self, test): self._tests.append(test) def addTests(self, tests): for test in tests: self.addTest(test) def run(self, result): return self(result) def __call__(self, result): for test in self._tests: if result.shouldStop: break test(result) return result def debug(self): """Run the tests without collecting errors in a TestResult""" for test in self._tests: test.debug() class FunctionTestCase(TestCase): """A test case that wraps a test function. This is useful for slipping pre-existing test functions into the PyUnit framework. Optionally, set-up and tidy-up functions can be supplied. As with TestCase, the tidy-up ('tearDown') function will always be called if the set-up ('setUp') function ran successfully. """ def __init__(self, testFunc, setUp=None, tearDown=None, description=None): TestCase.__init__(self) self.__setUpFunc = setUp self.__tearDownFunc = tearDown self.__testFunc = testFunc self.__description = description def setUp(self): if self.__setUpFunc is not None: self.__setUpFunc() def tearDown(self): if self.__tearDownFunc is not None: self.__tearDownFunc() def runTest(self): self.__testFunc() def id(self): return self.__testFunc.__name__ def __str__(self): return "%s (%s)" % (_strclass(self.__class__), self.__testFunc.__name__) def __repr__(self): return "<%s testFunc=%s>" % (_strclass(self.__class__), self.__testFunc) def shortDescription(self): if self.__description is not None: return self.__description doc = self.__testFunc.__doc__ return doc and string.strip(string.split(doc, "\n")[0]) or None ############################################################################## # Locating and loading tests ############################################################################## class TestLoader: """This class is responsible for loading tests according to various criteria and returning them wrapped in a Test """ testMethodPrefix = 'test' sortTestMethodsUsing = cmp suiteClass = TestSuite def loadTestsFromTestCase(self, testCaseClass): """Return a suite of all tests cases contained in testCaseClass""" return self.suiteClass(map(testCaseClass, self.getTestCaseNames(testCaseClass))) def loadTestsFromModule(self, module): """Return a suite of all tests cases contained in the given module""" tests = [] for name in dir(module): obj = getattr(module, name) if (isinstance(obj, (type, types.ClassType)) and issubclass(obj, TestCase)): tests.append(self.loadTestsFromTestCase(obj)) return self.suiteClass(tests) def loadTestsFromName(self, name, module=None): """Return a suite of all tests cases given a string specifier. The name may resolve either to a module, a test case class, a test method within a test case class, or a callable object which returns a TestCase or TestSuite instance. The method optionally resolves the names relative to a given module. """ parts = string.split(name, '.') if module is None: if not parts: raise ValueError, "incomplete test name: %s" % name else: parts_copy = parts[:] while parts_copy: try: module = __import__(string.join(parts_copy,'.')) break except ImportError: del parts_copy[-1] if not parts_copy: raise parts = parts[1:] obj = module for part in parts: obj = getattr(obj, part) import unittest if type(obj) == types.ModuleType: return self.loadTestsFromModule(obj) elif (isinstance(obj, (type, types.ClassType)) and issubclass(obj, unittest.TestCase)): return self.loadTestsFromTestCase(obj) elif type(obj) == types.UnboundMethodType: return obj.im_class(obj.__name__) elif callable(obj): test = obj() if not isinstance(test, unittest.TestCase) and \ not isinstance(test, unittest.TestSuite): raise ValueError, \ "calling %s returned %s, not a test" % (obj,test) return test else: raise ValueError, "don't know how to make test from: %s" % obj def loadTestsFromNames(self, names, module=None): """Return a suite of all tests cases found using the given sequence of string specifiers. See 'loadTestsFromName()'. """ suites = [] for name in names: suites.append(self.loadTestsFromName(name, module)) return self.suiteClass(suites) def getTestCaseNames(self, testCaseClass): """Return a sorted sequence of method names found within testCaseClass """ testFnNames = filter(lambda n,p=self.testMethodPrefix: n[:len(p)] == p, dir(testCaseClass)) for baseclass in testCaseClass.__bases__: for testFnName in self.getTestCaseNames(baseclass): if testFnName not in testFnNames: # handle overridden methods testFnNames.append(testFnName) if self.sortTestMethodsUsing: testFnNames.sort(self.sortTestMethodsUsing) return testFnNames defaultTestLoader = TestLoader() ############################################################################## # Patches for old functions: these functions should be considered obsolete ############################################################################## def _makeLoader(prefix, sortUsing, suiteClass=None): loader = TestLoader() loader.sortTestMethodsUsing = sortUsing loader.testMethodPrefix = prefix if suiteClass: loader.suiteClass = suiteClass return loader def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp): return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass) def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, suiteClass=TestSuite): return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass) def findTestCases(module, prefix='test', sortUsing=cmp, suiteClass=TestSuite): return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module) ############################################################################## # Text UI ############################################################################## class _WritelnDecorator: """Used to decorate file-like objects with a handy 'writeln' method""" def __init__(self,stream): self.stream = stream def __getattr__(self, attr): return getattr(self.stream,attr) def writeln(self, *args): if args: self.write(*args) self.write('\n') # text-mode streams translate to \r\n if needed class _TextTestResult(TestResult): """A test result class that can print formatted text results to a stream. Used by TextTestRunner. """ separator1 = '=' * 70 separator2 = '-' * 70 def __init__(self, stream, descriptions, verbosity): TestResult.__init__(self) self.stream = stream self.showAll = verbosity > 1 self.dots = verbosity == 1 self.descriptions = descriptions def getDescription(self, test): if self.descriptions: return test.shortDescription() or str(test) else: return str(test) def startTest(self, test): TestResult.startTest(self, test) if self.showAll: self.stream.write(self.getDescription(test)) self.stream.write(" ... ") def addSuccess(self, test): TestResult.addSuccess(self, test) if self.showAll: self.stream.writeln("ok") elif self.dots: self.stream.write('.') def addError(self, test, err): TestResult.addError(self, test, err) if self.showAll: self.stream.writeln("ERROR") elif self.dots: self.stream.write('E') def addFailure(self, test, err): TestResult.addFailure(self, test, err) if self.showAll: self.stream.writeln("FAIL") elif self.dots: self.stream.write('F') def printErrors(self): if self.dots or self.showAll: self.stream.writeln() self.printErrorList('ERROR', self.errors) self.printErrorList('FAIL', self.failures) def printErrorList(self, flavour, errors): for test, err in errors: self.stream.writeln(self.separator1) self.stream.writeln("%s: %s" % (flavour,self.getDescription(test))) self.stream.writeln(self.separator2) self.stream.writeln("%s" % err) class TextTestRunner: """A test runner class that displays results in textual form. It prints out the names of tests as they are run, errors as they occur, and a summary of the results at the end of the test run. """ def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1): self.stream = _WritelnDecorator(stream) self.descriptions = descriptions self.verbosity = verbosity def _makeResult(self): return _TextTestResult(self.stream, self.descriptions, self.verbosity) def run(self, test): "Run the given test case or test suite." result = self._makeResult() startTime = time.time() test(result) stopTime = time.time() timeTaken = float(stopTime - startTime) result.printErrors() self.stream.writeln(result.separator2) run = result.testsRun self.stream.writeln("Ran %d test%s in %.3fs" % (run, run != 1 and "s" or "", timeTaken)) self.stream.writeln() if not result.wasSuccessful(): self.stream.write("FAILED (") failed, errored = map(len, (result.failures, result.errors)) if failed: self.stream.write("failures=%d" % failed) if errored: if failed: self.stream.write(", ") self.stream.write("errors=%d" % errored) self.stream.writeln(")") else: self.stream.writeln("OK") return result ############################################################################## # Facilities for running tests from the command line ############################################################################## class TestProgram: """A command-line program that runs a set of tests; this is primarily for making test modules conveniently executable. """ USAGE = """\ Usage: %(progName)s [options] [test] [...] Options: -h, --help Show this message -v, --verbose Verbose output -q, --quiet Minimal output Examples: %(progName)s - run default set of tests %(progName)s MyTestSuite - run suite 'MyTestSuite' %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething %(progName)s MyTestCase - run all 'test*' test methods in MyTestCase """ def __init__(self, module='__main__', defaultTest=None, argv=None, testRunner=None, testLoader=defaultTestLoader): if type(module) == type(''): self.module = __import__(module) for part in string.split(module,'.')[1:]: self.module = getattr(self.module, part) else: self.module = module if argv is None: argv = sys.argv self.verbosity = 1 self.defaultTest = defaultTest self.testRunner = testRunner self.testLoader = testLoader self.progName = os.path.basename(argv[0]) self.parseArgs(argv) self.runTests() def usageExit(self, msg=None): if msg: print msg print self.USAGE % self.__dict__ sys.exit(2) def parseArgs(self, argv): import getopt try: options, args = getopt.getopt(argv[1:], 'hHvq', ['help','verbose','quiet']) for opt, value in options: if opt in ('-h','-H','--help'): self.usageExit() if opt in ('-q','--quiet'): self.verbosity = 0 if opt in ('-v','--verbose'): self.verbosity = 2 if len(args) == 0 and self.defaultTest is None: self.test = self.testLoader.loadTestsFromModule(self.module) return if len(args) > 0: self.testNames = args else: self.testNames = (self.defaultTest,) self.createTests() except getopt.error, msg: self.usageExit(msg) def createTests(self): self.test = self.testLoader.loadTestsFromNames(self.testNames, self.module) def runTests(self): if self.testRunner is None: self.testRunner = TextTestRunner(verbosity=self.verbosity) result = self.testRunner.run(self.test) sys.exit(not result.wasSuccessful()) main = TestProgram ############################################################################## # Executing this module from the command line ############################################################################## if __name__ == "__main__": main(module=None) |
From: Fred L. D. <fd...@us...> - 2003-07-11 04:27:39
|
Update of /cvsroot/cvs-syncmail/CVSROOT In directory sc8-pr-cvs1:/tmp/cvs-serv12427 Modified Files: syncmail Log Message: Comment out the extra diagnostics for now. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/CVSROOT/syncmail,v retrieving revision 1.21 retrieving revision 1.22 diff -u -d -r1.21 -r1.22 --- syncmail 10 Jul 2003 22:02:22 -0000 1.21 +++ syncmail 11 Jul 2003 04:27:36 -0000 1.22 @@ -441,10 +441,10 @@ return if verbose: - print 'Python version', sys.version - os.system("type python1.6; type python2;" - " type python2.0; type python2.1; type python2.2;" - " type rcsdiff; type rlog") +## print 'Python version', sys.version +## os.system("type python1.6; type python2;" +## " type python2.0; type python2.1; type python2.2;" +## " type rcsdiff; type rlog") print 'Mailing %s...' % string.join(people, COMMASPACE) print 'Generating notification message...' blast_mail(subject, people, changes.values(), |
From: Fred L. D. <fd...@us...> - 2003-07-11 04:26:38
|
Update of /cvsroot/cvs-syncmail/syncmail/tests In directory sc8-pr-cvs1:/tmp/cvs-serv12297/tests Added Files: Tag: new-config-branch syncmail.py test_options.py Log Message: Move the tests to a subdir, and make them use a proxy module that deals with loading code from the syncmail script. This provides better namespace isolation between syncmail and the tests. --- NEW FILE: test_options.py --- """Tests for the helper functions in syncmail.""" # These tests assume that the syncmail script is in the current directory. import os import sys from cStringIO import StringIO import syncmail def eq(a, b, msg=None): if msg is None: msg = "%s != %s" % (`a`, `b`) if not a == b: raise AssertionError(msg) def raises(exctype, f, *args, **kw): try: apply(f, args, kw) except exctype: pass else: raise AssertionError("expected exception") VARS = {"FOO": "<whack!>"} # "variable" replacement replace = syncmail.Replacer(VARS) eq(replace("abc$FOO-def${SPLAT}"), "abc<whack!>-def") eq(replace("$FOO"), "<whack!>") eq(replace("$ FOO"), "$ FOO") eq(replace("${FOO}"), "<whack!>") eq(replace(" ${FOO} "), " <whack!> ") # load_cmdline() eq(syncmail.load_cmdline([]), ({}, [])) sys.stderr = StringIO() sys.stdout = sys.stderr try: raises(SystemExit, syncmail.load_cmdline, ['-qq']) raises(SystemExit, syncmail.load_cmdline, ['--config=filename', '--no-config']) raises(SystemExit, syncmail.load_cmdline, ['--no-config', '--no-config']) finally: sys.stderr = sys.__stderr__ sys.stdout = sys.__stdout__ # load_configuration() config = syncmail.load_configuration([]) eq(config.contextlines, 2) eq(config.verbose, 1) eq(config.smtp_server, syncmail.MAILHOST) eq(config.smtp_port, syncmail.MAILPORT) config = syncmail.load_configuration(['-q', '--mailhost=smtp.example.com']) eq(config.verbose, 0) eq(config.smtp_server, "smtp.example.com") eq(config.smtp_port, syncmail.MAILPORT) config = syncmail.load_configuration(['--mailhost=smtp.example.com:8025']) eq(config.smtp_server, "smtp.example.com") eq(config.smtp_port, 8025) config = syncmail.load_configuration(['--mailhost=:8025']) eq(config.smtp_server, syncmail.MAILHOST) eq(config.smtp_port, 8025) # OptionLookup dicts = [ {'common': '1', 'first': 'one'}, {'common': '2', 'second': 'two'}, {'common': '3', 'third': 'three'}, {'common': '4', 'fourth': 'four'}, ] options = syncmail.OptionLookup(dicts) eq(options.get('common'), '1') eq(options.get('first'), 'one') eq(options.get('second'), 'two') eq(options.get('third'), 'three') eq(options.get('fourth'), 'four') eq(options.get('missing'), None) eq(options.get('common'), '1') eq(options.get('third'), 'three') options = syncmail.OptionLookup([{"foo": "bar", "branch": "$BRANCH", "hostname": "$HOSTNAME"}], "my-branch") eq(options.get("branch"), "my-branch") eq(options.get("hostname"), os.environ.get("HOSTNAME", syncmail.getfqdn())) --- NEW FILE: syncmail.py --- """Module which loads the syncmail script as a module.""" import os __here = os.path.dirname(os.path.abspath(__file__)) __script = os.path.join(os.path.dirname(__here), "syncmail") del os del __here execfile(__script) del __script |
From: Fred L. D. <fd...@us...> - 2003-07-11 04:26:38
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv12297 Removed Files: Tag: new-config-branch tests.py Log Message: Move the tests to a subdir, and make them use a proxy module that deals with loading code from the syncmail script. This provides better namespace isolation between syncmail and the tests. --- tests.py DELETED --- |
From: Fred L. D. <fd...@us...> - 2003-07-11 04:23:07
|
Update of /cvsroot/cvs-syncmail/CVSROOT In directory sc8-pr-cvs1:/tmp/cvs-serv11923 Added Files: cvsignore Log Message: Tell CVS to ignore Python byte code files. --- NEW FILE: cvsignore --- *.pyc *.pyo |
From: Fred L. D. <fd...@us...> - 2003-07-11 04:20:17
|
Update of /cvsroot/cvs-syncmail/syncmail/tests In directory sc8-pr-cvs1:/tmp/cvs-serv11669/tests Log Message: Directory /cvsroot/cvs-syncmail/syncmail/tests added to the repository --> Using per-directory sticky tag `new-config-branch' |