view compat.py @ 90:1fb319ec33af

Skimming mode added (-k/--skim option) In skimming mode, as soon as a single test case within a test group is failed, the remaining test cases in the same group are skipped. Bug fix and simply a bit of refactoring: TestCase.has_iofiles and TestCase.has_ansfile are now defined (the meaning should be clear from the names).
author Oleg Oshmyan <chortos@inbox.lv>
date Mon, 28 Feb 2011 15:32:22 +0000
parents cd347cfca272
children e17ae4ccbc58 92f76baebcc6
line wrap: on
line source

# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>

# A compatibility layer for Python 2.5+. This is what lets test.py
# run on all versions of Python starting with 2.5, including Python 3.

# A few notes regarding some compatibility-driven peculiarities
# in the use of the language that can be seen in all modules:
#
# * Except statements never specify target; instead, when needed,
#   the exception is taken from sys.exc_info(). Blame the incompatible
#   syntaxes of the except clause in Python 2.5 and Python 3 and the lack
#   of preprocessor macros in Python of any version ;P.
#
# * Keyword-only parameters are never used, even for parameters
#   that should never be given in as arguments. The reason is
#   the laziness of some Python developers who have failed to finish
#   implementing them in Python 2 even though they had several years
#   of time and multiple version releases to sneak them in.
#
# * Abstract classes are only implemented for Python 2.6 and 2.7.
#   ABC's require the abc module and the specification of metaclasses,
#   but in Python 2.5, the abc module does not exist, while in Python 3,
#   metaclasses are specified using a syntax totally incompatible
#   with Python 2 and not usable conditionally via exec() and such
#   because it is a detail of the syntax of the class statement itself.

# Some code was adapted from Python 2.7.1 and its documentation.
# This code is clearly marked as such in preceding comments and is
# covered by copyright as follows:
#
# Copyright (c) 2001-2010 Python Software Foundation; all rights reserved.
#
# The code is used according to the PSF License Agreement
# for Python 2.7.1, whose full text is available from your local
# installation of Python (enter 'license()' in the interactive
# interpreter) or from the Web at the following URL:
#
# http://docs.python.org/2.7.1/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python

try:
	import builtins
except ImportError:
	import __builtin__ as builtins

pseudobuiltins = ('say', 'basestring', 'range', 'map', 'zip', 'filter', 'next',
                  'items', 'keys', 'values', 'zip_longest', 'callable', 'ceil')
__all__ = pseudobuiltins + ('ABCMeta', 'abstractmethod', 'CompatBuiltins')

try:
	# Python 3
	exec('say = print')
except SyntaxError:
	try:
		# Python 2.6/2.7
		# An alternative is exec('from __future__ import print_function; say = print');
		# if problems arise with the current line, one should try replacing it
		# with this one with the future import before abandoning the idea altogether
		say = getattr(builtins, 'print')
	except Exception:
		# Python 2.5
		import sys
		# This should fully emulate the print function of Python 2.6 in Python 2.3+
		# The error messages are taken from Python 2.6
		# The name bindings at the bottom of this file are in effect
		def saytypeerror(value, name):
			return TypeError(' '.join((name, 'must be None, str or unicode, not', type(value).__name__)))
		def say(*values, **kwargs):
			sep  = kwargs.pop('sep' , None)
			end  = kwargs.pop('end' , None)
			file = kwargs.pop('file', None)
			if kwargs: raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.popitem()[0])
			if sep  is None: sep  = ' '
			if end  is None: end  = '\n'
			if file is None: file = sys.stdout
			if not isinstance(sep, basestring): raise saytypeerror(sep, 'sep')
			if not isinstance(end, basestring): raise saytypeerror(end, 'end')
			file.write(sep.join(map(str, values)) + end)

try:
	from os.path import relpath
except ImportError:
	# Python 2.5
	import os.path as _path
	
	# Adapted from Python 2.7.1
	
	if hasattr(_path, 'splitunc'):
		def _abspath_split(path):
			abs = _path.abspath(_path.normpath(path))
			prefix, rest = _path.splitunc(abs)
			is_unc = bool(prefix)
			if not is_unc:
				prefix, rest = _path.splitdrive(abs)
			return is_unc, prefix, [x for x in rest.split(_path.sep) if x]
	else:
		def _abspath_split(path):
			prefix, rest = _path.splitdrive(_path.abspath(_path.normpath(path)))
			return False, prefix, [x for x in rest.split(_path.sep) if x]
	
	def relpath(path, start=_path.curdir):
		"""Return a relative version of a path"""
		
		if not path:
			raise ValueError("no path specified")
		
		start_is_unc, start_prefix, start_list = _abspath_split(start)
		path_is_unc, path_prefix, path_list = _abspath_split(path)
		
		if path_is_unc ^ start_is_unc:
			raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)"
	                                                    		% (path, start))
		if path_prefix.lower() != start_prefix.lower():
			if path_is_unc:
				raise ValueError("path is on UNC root %s, start on UNC root %s"
		                                    		% (path_prefix, start_prefix))
			else:
				raise ValueError("path is on drive %s, start on drive %s"
		                                    		% (path_prefix, start_prefix))
		# Work out how much of the filepath is shared by start and path.
		i = 0
		for e1, e2 in zip(start_list, path_list):
			if e1.lower() != e2.lower():
				break
			i += 1
		
		rel_list = [_path.pardir] * (len(start_list)-i) + path_list[i:]
		if not rel_list:
			return _path.curdir
		return _path.join(*rel_list)
	
	_path.relpath = relpath

def import_urllib():
	try:
		# Python 3
		import urllib.request
		return urllib.request, lambda url: urllib.request.urlopen(url).read().decode('ascii')
	except ImportError:
		# Python 2
		import urllib
		return urllib, lambda url: urllib.urlopen(url).read()

try:
	from abc import ABCMeta, abstractmethod
except ImportError:
	ABCMeta, abstractmethod = None, lambda x: x

try:
	basestring = basestring
except NameError:
	basestring = str

# xrange is set to support simple testconf.py's written for test.py 1.x
try:
	xrange = range = xrange
except NameError:
	xrange = range = range

try:
	callable = callable
except NameError:
	from collections import Callable
	callable = lambda obj: isinstance(obj, Callable)

try:
	next = next
except NameError:
	next = lambda obj: obj.next()

try:
	from itertools import imap as map
except ImportError:
	map = map

try:
	from itertools import izip as zip
except ImportError:
	zip = zip

try:
	from itertools import ifilter as filter
except ImportError:
	filter = filter

try:
	items = dict.iteritems
except AttributeError:
	items = dict.items

try:
	keys = dict.iterkeys
except AttributeError:
	keys = dict.keys

try:
	values = dict.itervalues
except AttributeError:
	values = dict.values

from math import ceil
if not isinstance(ceil(0), int):
	def ceil(x):
		y = int(x)
		if y < x: y += 1
		return y

try:
	# Python 3
	from itertools import zip_longest
except ImportError:
	try:
		# Python 2.6/2.7
		from itertools import izip_longest as zip_longest
	except ImportError:
		# Python 2.5
		from itertools import chain, repeat
		# Adapted from the documentation of itertools.izip_longest
		def zip_longest(*args, **kwargs):
			fillvalue = kwargs.get('fillvalue')
			def sentinel(counter=([fillvalue]*(len(args)-1)).pop):
				yield counter()
			fillers = repeat(fillvalue)
			iters = [chain(it, sentinel(), fillers) for it in args]
			try:
				for tup in zip(*iters):
					yield tup
			except IndexError:
				pass

# Automatically import * from this module into testconf.py's
class CompatBuiltins(object):
	__slots__ = 'originals'
	globals = globals()
	def __enter__(self):
		self.originals = {}
		for name in pseudobuiltins:
			try:
				self.originals[name] = getattr(builtins, name)
			except AttributeError:
				pass
			setattr(builtins, name, self.globals[name])
		return self
	def __exit__(self, exc_type, exc_val, exc_tb):
		for name in self.originals:
			setattr(builtins, name, self.originals[name])