google-apputils-0.4.0/0000750003611500116100000000000012176034015015435 5ustar dborowitzeng00000000000000google-apputils-0.4.0/ez_setup.py0000750003611500116100000002024512176032264017657 0ustar dborowitzeng00000000000000#!/usr/bin/env python """Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import sys DEFAULT_VERSION = "0.6c11" DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] md5_data = { 'setuptools-0.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090', 'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4', 'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7', 'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5', 'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de', 'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b', 'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2', 'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086', 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03', 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a', 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6', 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a', } import sys, os try: from hashlib import md5 except ImportError: from md5 import md5 def _validate_md5(egg_name, data): if egg_name in md5_data: digest = md5(data).hexdigest() if digest != md5_data[egg_name]: print >>sys.stderr, ( "md5 validation of %s failed! (Possible download problem?)" % egg_name ) sys.exit(2) return data def use_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, download_delay=15 ): """Automatically find/download setuptools and make it available on sys.path `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where setuptools will be downloaded, if it is not already available. If `download_delay` is specified, it should be the number of seconds that will be paused before initiating a download, should one be required. If an older version of setuptools is installed, this routine will print a message to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling script. """ was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules def do_download(): egg = download_setuptools(version, download_base, to_dir, download_delay) sys.path.insert(0, egg) import setuptools; setuptools.bootstrap_install_from = egg try: import pkg_resources except ImportError: return do_download() try: pkg_resources.require("setuptools>="+version); return except pkg_resources.VersionConflict, e: if was_imported: print >>sys.stderr, ( "The required version of setuptools (>=%s) is not available, and\n" "can't be installed while this script is running. Please install\n" " a more recent version first, using 'easy_install -U setuptools'." "\n\n(Currently using %r)" ) % (version, e.args[0]) sys.exit(2) except pkg_resources.DistributionNotFound: pass del pkg_resources, sys.modules['pkg_resources'] # reload ok return do_download() def download_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay = 15 ): """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. """ import urllib2, shutil egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) url = download_base + egg_name saveto = os.path.join(to_dir, egg_name) src = dst = None if not os.path.exists(saveto): # Avoid repeated downloads try: from distutils import log if delay: log.warn(""" --------------------------------------------------------------------------- This script requires setuptools version %s to run (even to display help). I will attempt to download it for you (from %s), but you may need to enable firewall access for this script first. I will start the download in %d seconds. (Note: if this machine does not have network access, please obtain the file %s and place it in this directory before rerunning this script.) ---------------------------------------------------------------------------""", version, download_base, delay, url ); from time import sleep; sleep(delay) log.warn("Downloading %s", url) src = urllib2.urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. data = _validate_md5(egg_name, src.read()) dst = open(saveto,"wb"); dst.write(data) finally: if src: src.close() if dst: dst.close() return os.path.realpath(saveto) def main(argv, version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" try: import setuptools except ImportError: egg = None try: egg = download_setuptools(version, delay=0) sys.path.insert(0,egg) from setuptools.command.easy_install import main return main(list(argv)+[egg]) # we're done here finally: if egg and os.path.exists(egg): os.unlink(egg) else: if setuptools.__version__ == '0.0.1': print >>sys.stderr, ( "You have an obsolete version of setuptools installed. Please\n" "remove it from your system entirely before rerunning this script." ) sys.exit(2) req = "setuptools>="+version import pkg_resources try: pkg_resources.require(req) except pkg_resources.VersionConflict: try: from setuptools.command.easy_install import main except ImportError: from easy_install import main main(list(argv)+[download_setuptools(delay=0)]) sys.exit(0) # try to force an exit else: if argv: from setuptools.command.easy_install import main main(argv) else: print "Setuptools version",version,"or greater has been installed." print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' def update_md5(filenames): """Update our built-in md5 registry""" import re for name in filenames: base = os.path.basename(name) f = open(name,'rb') md5_data[base] = md5(f.read()).hexdigest() f.close() data = [" %r: %r,\n" % it for it in md5_data.items()] data.sort() repl = "".join(data) import inspect srcfile = inspect.getsourcefile(sys.modules[__name__]) f = open(srcfile, 'rb'); src = f.read(); f.close() match = re.search("\nmd5_data = {\n([^}]+)}", src) if not match: print >>sys.stderr, "Internal error!" sys.exit(2) src = src[:match.start(1)] + repl + src[match.end(1):] f = open(srcfile,'w') f.write(src) f.close() if __name__=='__main__': if len(sys.argv)>2 and sys.argv[1]=='--md5update': update_md5(sys.argv[2:]) else: main(sys.argv[1:]) google-apputils-0.4.0/tests/0000750003611500116100000000000012176034015016577 5ustar dborowitzeng00000000000000google-apputils-0.4.0/tests/resources_test.py0000640003611500116100000000262712176032264022236 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2010 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tests for the resources module.""" __author__ = 'dborowitz@google.com (Dave Borowitz)' from google.apputils import basetest from google.apputils import file_util from google.apputils import resources PREFIX = __name__ + ':data/' class ResourcesTest(basetest.TestCase): def _CheckTestData(self, func): self.assertEqual('test file a contents\n', func(PREFIX + 'a')) self.assertEqual('test file b contents\n', func(PREFIX + 'b')) def testGetResource(self): self._CheckTestData(resources.GetResource) def testGetResourceAsFile(self): self._CheckTestData(lambda n: resources.GetResourceAsFile(n).read()) def testGetResourceFilename(self): self._CheckTestData( lambda n: file_util.Read(resources.GetResourceFilename(n))) if __name__ == '__main__': basetest.main() google-apputils-0.4.0/tests/data/0000750003611500116100000000000012176034015017510 5ustar dborowitzeng00000000000000google-apputils-0.4.0/tests/data/b0000640003611500116100000000002512176032264017656 0ustar dborowitzeng00000000000000test file b contents google-apputils-0.4.0/tests/data/a0000640003611500116100000000002512176032264017655 0ustar dborowitzeng00000000000000test file a contents google-apputils-0.4.0/tests/__init__.py0000640003611500116100000000002612176032264020713 0ustar dborowitzeng00000000000000#!/usr/bin/env python google-apputils-0.4.0/tests/basetest_test.py0000750003611500116100000017577112176032264022053 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2010 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tests for base google test functionality.""" __author__ = 'dborowitz@google.com (Dave Borowitz)' import os import re import string import sys import unittest import gflags as flags from google.apputils import basetest PY_VERSION_2 = sys.version_info[0] == 2 FLAGS = flags.FLAGS flags.DEFINE_integer('testid', 0, 'Which test to run') class GoogleTestBaseUnitTest(basetest.TestCase): def setUp(self): self._orig_test_diff = os.environ.pop('TEST_DIFF', None) self.data1_file = os.path.join(FLAGS.test_tmpdir, 'provided_1.dat') self.data2_file = os.path.join(FLAGS.test_tmpdir, 'provided_2.dat') def tearDown(self): if self._orig_test_diff is not None: os.environ['TEST_DIFF'] = self._orig_test_diff def testCapturing(self): basetest.CaptureTestStdout() basetest.CaptureTestStderr() # print two lines to stdout sys.stdout.write('This goes to captured.out\n') sys.stdout.write('This goes to captured.out\n') sys.stderr.write('This goes to captured.err\n') stdout_filename = os.path.join(FLAGS.test_tmpdir, 'stdout.diff') stdout_file = open(stdout_filename, 'wb') stdout_file.write(b'This goes to captured.out\n' b'This goes to captured.out\n') stdout_file.close() basetest.DiffTestStdout(stdout_filename) # After DiffTestStdout(), the standard output is no longer captured # and is written to the screen. Standard error is still captured. sys.stdout.write('This goes to stdout screen 1/2\n') sys.stderr.write('This goes to captured.err\n') # After CaptureTestStdout(), both standard output and standard error # are captured. basetest.CaptureTestStdout() sys.stdout.write('This goes to captured.out\n') sys.stderr.write('This goes to captured.err\n') stderr_filename = os.path.join(FLAGS.test_tmpdir, 'stderr.diff') stderr_file = open(stderr_filename, 'wb') stderr_file.write(b'This goes to captured.err\n' b'This goes to captured.err\n' b'This goes to captured.err\n') stderr_file.close() # After DiffTestStderr(), the standard error is no longer captured and # is written to the screen. Standard output is still captured. basetest.DiffTestStderr(stderr_filename) sys.stdout.write('This goes to captured.out\n') sys.stderr.write('This goes to stderr screen 2/2\n') basetest.DiffTestStdout(stdout_filename) basetest.CaptureTestStdout() sys.stdout.write('Correct Output\n') stdout_filename = os.path.join(FLAGS.test_tmpdir, 'stdout.diff') stdout_file = open(stdout_filename, 'wb') stdout_file.write(b'Incorrect Output\n') stdout_file.close() self.assertRaises(basetest.OutputDifferedError, basetest.DiffTestStdout, stdout_filename) def test_Diff_SameData(self): """Tests for the internal _Diff method.""" basetest._WriteTestData('a\nb\n', self.data1_file) basetest._WriteTestData('a\nb\n', self.data2_file) # This must not raise an exception: basetest._Diff(self.data1_file, self.data2_file) @unittest.skipIf(not os.path.exists('/usr/bin/diff'), 'requires /usr/bin/diff') def test_Diff_SameData_ExternalDiff(self): """Test the internal _Diff method when TEST_DIFF is in the env.""" os.environ['TEST_DIFF'] = '/usr/bin/diff' basetest._WriteTestData('b\n', self.data1_file) basetest._WriteTestData('b\n', self.data2_file) # This must not raise an exception: basetest._Diff(self.data1_file, self.data2_file) @unittest.skipIf(not os.path.exists('/usr/bin/diff'), 'requires /usr/bin/diff') def test_Diff_MissingFile_ExternalDiff(self): """Test the internal _Diff method on TEST_DIFF error.""" os.environ['TEST_DIFF'] = '/usr/bin/diff' basetest._WriteTestData('a\n', self.data1_file) if os.path.exists(self.data2_file): os.unlink(self.data2_file) # Be 100% sure this does not exist. # This depends on /usr/bin/diff returning an exit code greater than 1 # when an input file is missing. It has had this behavior forever. with self.assertRaises(basetest.DiffFailureError) as error_context: basetest._Diff(self.data1_file, self.data2_file) def test_Diff_MissingExternalDiff(self): """Test the internal _Diff when TEST_DIFF program is non-existant.""" os.environ['TEST_DIFF'] = self.data1_file if os.path.exists(self.data1_file): os.unlink(self.data1_file) # Be 100% sure this does not exist with self.assertRaises(basetest.DiffFailureError) as error_context: basetest._Diff(self.data2_file, self.data2_file) def test_Diff_Exception(self): """Test that _Diff includes the delta in the error msg.""" basetest._WriteTestData(b'01: text A\n02: text B\n03: C', self.data1_file) basetest._WriteTestData(b'01: text A\n02: zzzzzz\n03: C', self.data2_file) with self.assertRaises(basetest.OutputDifferedError) as error_context: basetest._Diff(self.data1_file, self.data2_file) # Check that both filenames and some semblance of a unified diff # are present in the exception error message. diff_error_message = str(error_context.exception) self.assertIn('provided_1', diff_error_message) self.assertIn('provided_2', diff_error_message) self.assertIn('@@', diff_error_message) self.assertIn('02: text B', diff_error_message) @unittest.skipIf(not os.path.exists('/usr/bin/diff'), 'requires /usr/bin/diff') def test_Diff_Exception_ExternalDiff(self): """Test that _Diff executes TEST_DIFF when supplied and there are diffs.""" os.environ['TEST_DIFF'] = '/usr/bin/diff' basetest._WriteTestData(b'01: text A\n02: text B\n03: C', self.data1_file) basetest._WriteTestData(b'01: text A\n02: zzzzzz\n03: C', self.data2_file) with self.assertRaises(basetest.OutputDifferedError) as error_context: basetest._Diff(self.data1_file, self.data2_file) # Check that both filenames and the TEST_DIFF command # are present in the exception error message. diff_error_message = str(error_context.exception) self.assertIn('/usr/bin/diff', diff_error_message) self.assertIn('provided_1', diff_error_message) self.assertIn('provided_2', diff_error_message) def testDiffTestStrings(self): basetest.DiffTestStrings('a', 'a') with self.assertRaises(basetest.OutputDifferedError): basetest.DiffTestStrings( '-2: a message\n-2: another message\n', '-2: a message\n-2: another message \n') self.assertRaises(basetest.DiffFailureError, basetest.DiffTestStringFile, 'a message', 'txt.a message not existant file here') self.assertRaises(basetest.OutputDifferedError, basetest.DiffTestStringFile, 'message', os.devnull) def testFlags(self): if FLAGS.testid == 1: self.assertEqual(FLAGS.test_random_seed, 301) self.assert_(FLAGS.test_tmpdir.startswith('/')) self.assert_(os.access(FLAGS.test_tmpdir, os.W_OK)) elif FLAGS.testid == 2: self.assertEqual(FLAGS.test_random_seed, 321) self.assertEqual(FLAGS.test_srcdir, 'cba') self.assertEqual(FLAGS.test_tmpdir, 'fed') elif FLAGS.testid == 3: self.assertEqual(FLAGS.test_random_seed, 123) self.assertEqual(FLAGS.test_srcdir, 'abc') self.assertEqual(FLAGS.test_tmpdir, 'def') elif FLAGS.testid == 4: self.assertEqual(FLAGS.test_random_seed, 123) self.assertEqual(FLAGS.test_srcdir, 'abc') self.assertEqual(FLAGS.test_tmpdir, 'def') def testAssertIn(self): animals = {'monkey': 'banana', 'cow': 'grass', 'seal': 'fish'} self.assertIn('a', 'abc') self.assertIn(2, [1, 2, 3]) self.assertIn('monkey', animals) self.assertNotIn('d', 'abc') self.assertNotIn(0, [1, 2, 3]) self.assertNotIn('otter', animals) self.assertRaises(AssertionError, self.assertIn, 'x', 'abc') self.assertRaises(AssertionError, self.assertIn, 4, [1, 2, 3]) self.assertRaises(AssertionError, self.assertIn, 'elephant', animals) self.assertRaises(AssertionError, self.assertNotIn, 'c', 'abc') self.assertRaises(AssertionError, self.assertNotIn, 1, [1, 2, 3]) self.assertRaises(AssertionError, self.assertNotIn, 'cow', animals) def testAssertEqual(self): if FLAGS.testid != 5: return self.assertListEqual([], []) self.assertTupleEqual((), ()) self.assertSequenceEqual([], ()) a = [0, 'a', []] b = [] self.assertRaises(basetest.TestCase.failureException, self.assertListEqual, a, b) self.assertRaises(basetest.TestCase.failureException, self.assertListEqual, tuple(a), tuple(b)) self.assertRaises(basetest.TestCase.failureException, self.assertSequenceEqual, a, tuple(b)) b.extend(a) self.assertListEqual(a, b) self.assertTupleEqual(tuple(a), tuple(b)) self.assertSequenceEqual(a, tuple(b)) self.assertSequenceEqual(tuple(a), b) self.assertRaises(AssertionError, self.assertListEqual, a, tuple(b)) self.assertRaises(AssertionError, self.assertTupleEqual, tuple(a), b) self.assertRaises(AssertionError, self.assertListEqual, None, b) self.assertRaises(AssertionError, self.assertTupleEqual, None, tuple(b)) self.assertRaises(AssertionError, self.assertSequenceEqual, None, tuple(b)) self.assertRaises(AssertionError, self.assertListEqual, 1, 1) self.assertRaises(AssertionError, self.assertTupleEqual, 1, 1) self.assertRaises(AssertionError, self.assertSequenceEqual, 1, 1) self.assertSameElements([1, 2, 3], [3, 2, 1]) self.assertSameElements([1, 2] + [3] * 100, [1] * 100 + [2, 3]) self.assertSameElements(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo']) self.assertRaises(AssertionError, self.assertSameElements, [10], [10, 11]) self.assertRaises(AssertionError, self.assertSameElements, [10, 11], [10]) # Test that sequences of unhashable objects can be tested for sameness: self.assertSameElements([[1, 2], [3, 4]], [[3, 4], [1, 2]]) if PY_VERSION_2: # dict's are no longer valid for < comparison in Python 3 making them # unsortable (yay, sanity!). But we need to preserve this old behavior # when running under Python 2. self.assertSameElements([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}]) self.assertRaises(AssertionError, self.assertSameElements, [[1]], [[2]]) def testAssertDictEqual(self): self.assertDictEqual({}, {}) c = {'x': 1} d = {} self.assertRaises(basetest.TestCase.failureException, self.assertDictEqual, c, d) d.update(c) self.assertDictEqual(c, d) d['x'] = 0 self.assertRaises(basetest.TestCase.failureException, self.assertDictEqual, c, d, 'These are unequal') self.assertRaises(AssertionError, self.assertDictEqual, None, d) self.assertRaises(AssertionError, self.assertDictEqual, [], d) self.assertRaises(AssertionError, self.assertDictEqual, 1, 1) try: # Ensure we use equality as the sole measure of elements, not type, since # that is consistent with dict equality. self.assertDictEqual({1: 1L, 2: 2}, {1: 1, 2: 3}) except AssertionError, e: self.assertMultiLineEqual('{1: 1L, 2: 2} != {1: 1, 2: 3}\n' 'repr() of differing entries:\n2: 2 != 3\n', str(e)) try: self.assertDictEqual({}, {'x': 1}) except AssertionError, e: self.assertMultiLineEqual("{} != {'x': 1}\n" "Missing entries:\n'x': 1\n", str(e)) else: self.fail('Expecting AssertionError') try: self.assertDictEqual({}, {'x': 1}, 'a message') except AssertionError, e: self.assertIn('a message', str(e)) else: self.fail('Expecting AssertionError') expected = {'a': 1, 'b': 2, 'c': 3} seen = {'a': 2, 'c': 3, 'd': 4} try: self.assertDictEqual(expected, seen) except AssertionError, e: self.assertMultiLineEqual("""\ {'a': 1, 'b': 2, 'c': 3} != {'a': 2, 'c': 3, 'd': 4} Unexpected, but present entries: 'b': 2 repr() of differing entries: 'a': 1 != 2 Missing entries: 'd': 4 """, str(e)) else: self.fail('Expecting AssertionError') self.assertRaises(AssertionError, self.assertDictEqual, (1, 2), {}) self.assertRaises(AssertionError, self.assertDictEqual, {}, (1, 2)) # Ensure deterministic output of keys in dictionaries whose sort order # doesn't match the lexical ordering of repr -- this is most Python objects, # which are keyed by memory address. class Obj(object): def __init__(self, name): self.name = name def __repr__(self): return self.name try: self.assertDictEqual( {'a': Obj('A'), Obj('b'): Obj('B'), Obj('c'): Obj('C')}, {'a': Obj('A'), Obj('d'): Obj('D'), Obj('e'): Obj('E')}) except AssertionError, e: # Do as best we can not to be misleading when objects have the same repr # but aren't equal. self.assertMultiLineEqual("""\ {'a': A, b: B, c: C} != {'a': A, d: D, e: E} Unexpected, but present entries: b: B c: C repr() of differing entries: 'a': A != A Missing entries: d: D e: E """, str(e)) else: self.fail('Expecting AssertionError') # Confirm that safe_repr, not repr, is being used. class RaisesOnRepr(object): def __repr__(self): return 1/0 try: self.assertDictEqual( {RaisesOnRepr(): RaisesOnRepr()}, {RaisesOnRepr(): RaisesOnRepr()} ) self.fail('Expected dicts not to match') except AssertionError as e: # Depending on the testing environment, the object may get a __main__ # prefix or a basetest_test prefix, so strip that for comparison. error_msg = re.sub( r'( at 0x[^>]+)|__main__\.|basetest_test\.', '', str(e)) self.assertEquals("""\ {: } != \ {: } Unexpected, but present entries: : Missing entries: : """, error_msg) # Confirm that safe_repr, not repr, is being used. class RaisesOnLt(object): def __lt__(self): raise TypeError('Object is unordered.') def __repr__(self): return '' try: self.assertDictEqual( {RaisesOnLt(): RaisesOnLt()}, {RaisesOnLt(): RaisesOnLt()}) except AssertionError as e: self.assertIn('Unexpected, but present entries:\n other.x except AttributeError: return NotImplemented def __ge__(self, other): try: return self.x >= other.x except AttributeError: return NotImplemented if PY_VERSION_2: self.assertTotallyOrdered( [None], # None should come before everything else. [1], # Integers sort earlier. [A(1, 'a')], [A(2, 'b')], # 2 is after 1. [A(3, 'c'), A(3, 'd')], # The second argument is irrelevant. [A(4, 'z')], ['foo']) # Strings sort last. else: # Python 3 does not define ordering across different types. self.assertTotallyOrdered( [A(1, 'a')], [A(2, 'b')], # 2 is after 1. [A(3, 'c'), A(3, 'd')], # The second argument is irrelevant. [A(4, 'z')]) # Invalid. self.assertRaises(AssertionError, self.assertTotallyOrdered, [2], [1]) self.assertRaises(AssertionError, self.assertTotallyOrdered, [2], [1], [3]) self.assertRaises(AssertionError, self.assertTotallyOrdered, [1, 2]) def testShortDescriptionWithoutDocstring(self): self.assertEquals( self.shortDescription(), ('testShortDescriptionWithoutDocstring ' '(%s.GoogleTestBaseUnitTest)' % __name__)) def testShortDescriptionWithOneLineDocstring(self): """Tests shortDescription() for a method with a docstring.""" self.assertEquals( self.shortDescription(), ('testShortDescriptionWithOneLineDocstring ' '(%s.GoogleTestBaseUnitTest)\n' 'Tests shortDescription() for a method with a docstring.' % __name__)) def testShortDescriptionWithMultiLineDocstring(self): """Tests shortDescription() for a method with a longer docstring. This method ensures that only the first line of a docstring is returned used in the short description, no matter how long the whole thing is. """ self.assertEquals( self.shortDescription(), ('testShortDescriptionWithMultiLineDocstring ' '(%s.GoogleTestBaseUnitTest)\n' 'Tests shortDescription() for a method with a longer docstring.' % __name__)) def testRecordedProperties(self): """Tests that a test can record a property and then retrieve it.""" self.recordProperty('test_property', 'test_value') self.assertEquals(self.getRecordedProperties(), {'test_property': 'test_value'}) def testAssertUrlEqualSame(self): self.assertUrlEqual('http://a', 'http://a') self.assertUrlEqual('http://a/path/test', 'http://a/path/test') self.assertUrlEqual('#fragment', '#fragment') self.assertUrlEqual('http://a/?q=1', 'http://a/?q=1') self.assertUrlEqual('http://a/?q=1&v=5', 'http://a/?v=5&q=1') self.assertUrlEqual('/logs?v=1&a=2&t=labels&f=path%3A%22foo%22', '/logs?a=2&f=path%3A%22foo%22&v=1&t=labels') self.assertUrlEqual('http://a/path;p1', 'http://a/path;p1') self.assertUrlEqual('http://a/path;p2;p3;p1', 'http://a/path;p1;p2;p3') self.assertUrlEqual('sip:alice@atlanta.com;maddr=239.255.255.1;ttl=15', 'sip:alice@atlanta.com;ttl=15;maddr=239.255.255.1') def testAssertUrlEqualDifferent(self): self.assertRaises(AssertionError, self.assertUrlEqual, 'http://a', 'http://b') self.assertRaises(AssertionError, self.assertUrlEqual, 'http://a/x', 'http://a:8080/x') self.assertRaises(AssertionError, self.assertUrlEqual, 'http://a/x', 'http://a/y') self.assertRaises(AssertionError, self.assertUrlEqual, 'http://a/?q=2', 'http://a/?q=1') self.assertRaises(AssertionError, self.assertUrlEqual, 'http://a/?q=1&v=5', 'http://a/?v=2&q=1') self.assertRaises(AssertionError, self.assertUrlEqual, 'http://a', 'sip://b') self.assertRaises(AssertionError, self.assertUrlEqual, 'http://a#g', 'sip://a#f') self.assertRaises(AssertionError, self.assertUrlEqual, 'http://a/path;p1;p3;p1', 'http://a/path;p1;p2;p3') def testSameStructure_same(self): self.assertSameStructure(0, 0) self.assertSameStructure(1, 1) self.assertSameStructure('', '') self.assertSameStructure('hello', 'hello', msg='This Should not fail') self.assertSameStructure(set(), set()) self.assertSameStructure(set([1, 2]), set([1, 2])) self.assertSameStructure([], []) self.assertSameStructure(['a'], ['a']) self.assertSameStructure({}, {}) self.assertSameStructure({'one': 1}, {'one': 1}) # int and long should always be treated as the same type. self.assertSameStructure({3L: 3}, {3: 3L}) def testSameStructure_different(self): # Different type self.assertRaisesWithRegexpMatch( AssertionError, r"a is a <(type|class) 'int'> but b is a <(type|class) 'str'>", self.assertSameStructure, 0, 'hello') self.assertRaisesWithRegexpMatch( AssertionError, r"a is a <(type|class) 'int'> but b is a <(type|class) 'list'>", self.assertSameStructure, 0, []) self.assertRaisesWithRegexpMatch( AssertionError, r"a is a <(type|class) 'int'> but b is a <(type|class) 'float'>", self.assertSameStructure, 2, 2.0) # Different scalar values self.assertRaisesWithLiteralMatch( AssertionError, 'a is 0 but b is 1', self.assertSameStructure, 0, 1) self.assertRaisesWithLiteralMatch( AssertionError, "a is 'hello' but b is 'goodbye': This was expected", self.assertSameStructure, 'hello', 'goodbye', msg='This was expected') # Different sets are treated without structure self.assertRaisesWithRegexpMatch( AssertionError, r'AA is (set\(\[1\]\)|\{1\}) but BB is set\((\[\])?\)', self.assertSameStructure, set([1]), set(), aname='AA', bname='BB') # Different lists self.assertRaisesWithLiteralMatch( AssertionError, 'a has [2] but b does not', self.assertSameStructure, ['x', 'y', 'z'], ['x', 'y']) self.assertRaisesWithLiteralMatch( AssertionError, 'a lacks [2] but b has it', self.assertSameStructure, ['x', 'y'], ['x', 'y', 'z']) self.assertRaisesWithLiteralMatch( AssertionError, "a[2] is 'z' but b[2] is 'Z'", self.assertSameStructure, ['x', 'y', 'z'], ['x', 'y', 'Z']) # Different dicts self.assertRaisesWithLiteralMatch( AssertionError, "a has ['two'] but b does not", self.assertSameStructure, {'one': 1, 'two': 2}, {'one': 1}) self.assertRaisesWithLiteralMatch( AssertionError, "a lacks ['two'] but b has it", self.assertSameStructure, {'one': 1}, {'one': 1, 'two': 2}) self.assertRaisesWithLiteralMatch( AssertionError, "a['two'] is 2 but b['two'] is 3", self.assertSameStructure, {'one': 1, 'two': 2}, {'one': 1, 'two': 3}) # Deep key generation self.assertRaisesWithLiteralMatch( AssertionError, "a[0][0]['x']['y']['z'][0] is 1 but b[0][0]['x']['y']['z'][0] is 2", self.assertSameStructure, [[{'x': {'y': {'z': [1]}}}]], [[{'x': {'y': {'z': [2]}}}]]) # Multiple problems self.assertRaisesWithLiteralMatch( AssertionError, 'a[0] is 1 but b[0] is 3; a[1] is 2 but b[1] is 4', self.assertSameStructure, [1, 2], [3, 4]) self.assertRaisesWithRegexpMatch( AssertionError, re.compile(r"^a\[0] is 'a' but b\[0] is 'A'; .*" r"a\[18] is 's' but b\[18] is 'S'; \.\.\.$"), self.assertSameStructure, list(string.ascii_lowercase), list(string.ascii_uppercase)) def testAssertJsonEqualSame(self): self.assertJsonEqual('{"success": true}', '{"success": true}') self.assertJsonEqual('{"success": true}', '{"success":true}') self.assertJsonEqual('true', 'true') self.assertJsonEqual('null', 'null') self.assertJsonEqual('false', 'false') self.assertJsonEqual('34', '34') self.assertJsonEqual('[1, 2, 3]', '[1,2,3]', msg='please PASS') self.assertJsonEqual('{"sequence": [1, 2, 3], "float": 23.42}', '{"float": 23.42, "sequence": [1,2,3]}') self.assertJsonEqual('{"nest": {"spam": "eggs"}, "float": 23.42}', '{"float": 23.42, "nest": {"spam":"eggs"}}') def testAssertJsonEqualDifferent(self): with self.assertRaises(AssertionError): self.assertJsonEqual('{"success": true}', '{"success": false}') with self.assertRaises(AssertionError): self.assertJsonEqual('{"success": false}', '{"Success": false}') with self.assertRaises(AssertionError): self.assertJsonEqual('false', 'true') with self.assertRaises(AssertionError) as error_context: self.assertJsonEqual('null', '0', msg='I demand FAILURE') self.assertIn('I demand FAILURE', error_context.exception.args[0]) self.assertIn('None', error_context.exception.args[0]) with self.assertRaises(AssertionError): self.assertJsonEqual('[1, 0, 3]', '[1,2,3]') with self.assertRaises(AssertionError): self.assertJsonEqual('{"sequence": [1, 2, 3], "float": 23.42}', '{"float": 23.42, "sequence": [1,0,3]}') with self.assertRaises(AssertionError): self.assertJsonEqual('{"nest": {"spam": "eggs"}, "float": 23.42}', '{"float": 23.42, "nest": {"Spam":"beans"}}') def testAssertJsonEqualBadJson(self): with self.assertRaises(ValueError) as error_context: self.assertJsonEqual("alhg'2;#", '{"a": true}') self.assertIn('first', error_context.exception.args[0]) self.assertIn('alhg', error_context.exception.args[0]) with self.assertRaises(ValueError) as error_context: self.assertJsonEqual('{"a": true}', "alhg'2;#") self.assertIn('second', error_context.exception.args[0]) self.assertIn('alhg', error_context.exception.args[0]) with self.assertRaises(ValueError) as error_context: self.assertJsonEqual('', '') class GetCommandStderrTestCase(basetest.TestCase): def setUp(self): self.original_environ = os.environ.copy() def tearDown(self): os.environ = self.original_environ def testReturnStatus(self): expected = 255 observed = ( basetest.GetCommandStderr( ['/usr/bin/perl', '-e', 'die "FAIL";'], None)[0]) self.assertEqual(expected, observed) # TODO(dborowitz): Tests for more functionality that do not deal with # PYTHON_RUNFILES. class EqualityAssertionTest(basetest.TestCase): """This test verifies that basetest.failIfEqual actually tests __ne__. If a user class implements __eq__, unittest.failUnlessEqual will call it via first == second. However, failIfEqual also calls first == second. This means that while the caller may believe their __ne__ method is being tested, it is not. """ class NeverEqual(object): """Objects of this class behave like NaNs.""" def __eq__(self, unused_other): return False def __ne__(self, unused_other): return False class AllSame(object): """All objects of this class compare as equal.""" def __eq__(self, unused_other): return True def __ne__(self, unused_other): return False class EqualityTestsWithEq(object): """Performs all equality and inequality tests with __eq__.""" def __init__(self, value): self._value = value def __eq__(self, other): return self._value == other._value def __ne__(self, other): return not self.__eq__(other) class EqualityTestsWithNe(object): """Performs all equality and inequality tests with __ne__.""" def __init__(self, value): self._value = value def __eq__(self, other): return not self.__ne__(other) def __ne__(self, other): return self._value != other._value class EqualityTestsWithCmp(object): def __init__(self, value): self._value = value def __cmp__(self, other): return cmp(self._value, other._value) class EqualityTestsWithLtEq(object): def __init__(self, value): self._value = value def __eq__(self, other): return self._value == other._value def __lt__(self, other): return self._value < other._value def testAllComparisonsFail(self): i1 = self.NeverEqual() i2 = self.NeverEqual() self.assertFalse(i1 == i2) self.assertFalse(i1 != i2) # Compare two distinct objects self.assertFalse(i1 is i2) self.assertRaises(AssertionError, self.assertEqual, i1, i2) self.assertRaises(AssertionError, self.assertEquals, i1, i2) self.assertRaises(AssertionError, self.failUnlessEqual, i1, i2) self.assertRaises(AssertionError, self.assertNotEqual, i1, i2) self.assertRaises(AssertionError, self.assertNotEquals, i1, i2) self.assertRaises(AssertionError, self.failIfEqual, i1, i2) # A NeverEqual object should not compare equal to itself either. i2 = i1 self.assertTrue(i1 is i2) self.assertFalse(i1 == i2) self.assertFalse(i1 != i2) self.assertRaises(AssertionError, self.assertEqual, i1, i2) self.assertRaises(AssertionError, self.assertEquals, i1, i2) self.assertRaises(AssertionError, self.failUnlessEqual, i1, i2) self.assertRaises(AssertionError, self.assertNotEqual, i1, i2) self.assertRaises(AssertionError, self.assertNotEquals, i1, i2) self.assertRaises(AssertionError, self.failIfEqual, i1, i2) def testAllComparisonsSucceed(self): a = self.AllSame() b = self.AllSame() self.assertFalse(a is b) self.assertTrue(a == b) self.assertFalse(a != b) self.assertEqual(a, b) self.assertEquals(a, b) self.failUnlessEqual(a, b) self.assertRaises(AssertionError, self.assertNotEqual, a, b) self.assertRaises(AssertionError, self.assertNotEquals, a, b) self.assertRaises(AssertionError, self.failIfEqual, a, b) def _PerformAppleAppleOrangeChecks(self, same_a, same_b, different): """Perform consistency checks with two apples and an orange. The two apples should always compare as being the same (and inequality checks should fail). The orange should always compare as being different to each of the apples. Args: same_a: the first apple same_b: the second apple different: the orange """ self.assertTrue(same_a == same_b) self.assertFalse(same_a != same_b) self.assertEqual(same_a, same_b) self.assertEquals(same_a, same_b) self.failUnlessEqual(same_a, same_b) if PY_VERSION_2: # Python 3 removes the global cmp function self.assertEqual(0, cmp(same_a, same_b)) self.assertFalse(same_a == different) self.assertTrue(same_a != different) self.assertNotEqual(same_a, different) self.assertNotEquals(same_a, different) self.failIfEqual(same_a, different) if PY_VERSION_2: self.assertNotEqual(0, cmp(same_a, different)) self.assertFalse(same_b == different) self.assertTrue(same_b != different) self.assertNotEqual(same_b, different) self.assertNotEquals(same_b, different) self.failIfEqual(same_b, different) if PY_VERSION_2: self.assertNotEqual(0, cmp(same_b, different)) def testComparisonWithEq(self): same_a = self.EqualityTestsWithEq(42) same_b = self.EqualityTestsWithEq(42) different = self.EqualityTestsWithEq(1769) self._PerformAppleAppleOrangeChecks(same_a, same_b, different) def testComparisonWithNe(self): same_a = self.EqualityTestsWithNe(42) same_b = self.EqualityTestsWithNe(42) different = self.EqualityTestsWithNe(1769) self._PerformAppleAppleOrangeChecks(same_a, same_b, different) def testComparisonWithCmpOrLtEq(self): if PY_VERSION_2: # In Python 3; the __cmp__ method is no longer special. cmp_or_lteq_class = self.EqualityTestsWithCmp else: cmp_or_lteq_class = self.EqualityTestsWithLtEq same_a = cmp_or_lteq_class(42) same_b = cmp_or_lteq_class(42) different = cmp_or_lteq_class(1769) self._PerformAppleAppleOrangeChecks(same_a, same_b, different) class GoogleTestBasePy24UnitTest(basetest.TestCase): def RunTestCaseTests(self, test_case_class): test_loader = test_case_class._test_loader test_result = unittest.TestResult() test_loader.loadTestsFromTestCase(test_case_class).run(test_result) return test_result def testSetUpCalledForEachTestMethod(self): self.RunTestCaseTests(SetUpSpy) self.assertEquals(2, SetUpSpy.set_up_counter) def testSetUpTestCaseCalledExactlyOnce(self): self.RunTestCaseTests(SetUpTestCaseSpy) self.assertEquals(1, SetUpTestCaseSpy.set_up_test_case_counter) def testTearDownCalledForEachTestMethod(self): self.RunTestCaseTests(TearDownSpy) self.assertEquals(2, TearDownSpy.tear_down_counter) def testTearDownTestCaseCalledExactlyOnce(self): self.RunTestCaseTests(TearDownTestCaseSpy) self.assertEquals(1, TearDownTestCaseSpy.tear_down_test_case_counter) def testDefaultSetUpTestCaseExists(self): self.assertTrue( hasattr(OnlyBeforeAfterTestCaseMetaDefinedSpy, 'setUpTestCase')) def testDefaultTearDownTestCaseExists(self): self.assertTrue( hasattr(OnlyBeforeAfterTestCaseMetaDefinedSpy, 'tearDownTestCase')) def testBeforeAfterWithInheritanceAndOverridesA(self): """Test that things work when there's subclassing and overrides.""" InheritanceSpyBaseClass.ClearLog() self.RunTestCaseTests(InheritanceSpySubClassA) expected_calls = [ 'sub setUpTestCase', 'base setUp', 'base StubTest0', 'sub tearDown', 'base setUp', 'sub StubTest1', 'sub tearDown', 'base tearDownTestCase'] self.assertSequenceEqual(expected_calls, InheritanceSpyBaseClass.calls_made) def testBeforeAfterWithInheritanceAndOverridesB(self): """Complementary to the other test, test39A.""" InheritanceSpyBaseClass.ClearLog() self.RunTestCaseTests(InheritanceSpySubClassB) expected_calls = [ 'base setUpTestCase', 'sub setUp', 'sub StubTest0', 'base tearDown', 'sub setUp', 'sub StubTest1', 'base tearDown', 'sub setUp', 'sub StubTest2', 'base tearDown', 'sub tearDownTestCase'] self.assertSequenceEqual(expected_calls, InheritanceSpyBaseClass.calls_made) def testVerifyTearDownOrder(self): """Verify that tearDownTestCase gets called at the correct time.""" TearDownOrderWatcherBaseClass.ClearLog() self.RunTestCaseTests(TearDownOrderWatcherSubClass) expected_calls = [ 'TearDownOrderWatcherBaseClass.setUpTestCase', 'TearDownOrderWatcherSubClass.setUp Start', 'TearDownOrderWatcherBaseClass.setUp', 'TearDownOrderWatcherSubClass.setUp Finish', 'testAAAA', 'TearDownOrderWatcherSubClass.tearDown Start', 'TearDownOrderWatcherBaseClass.tearDown', 'TearDownOrderWatcherSubClass.tearDown Finish', 'TearDownOrderWatcherBaseClass.tearDownTestCase'] self.assertSequenceEqual(expected_calls, TearDownOrderWatcherBaseClass.calls_made) def test_arguments_added_by_decorator(self): """Arguments added to test methods by decorators (ex: mock.patch) should be able to pass through BeforeAfterTestCase meta.""" def add_arguments(**kargs): def wrapper(cls): test_main = cls.testMain cls.testMain = lambda self: test_main(self, **kargs) return cls return wrapper @add_arguments(named='blah') class TestClass(basetest.TestCase): __metaclass__ = basetest.BeforeAfterTestCaseMeta def testMain(self, named): self.assertEqual('blah', named) result = self.RunTestCaseTests(TestClass) self.assertTrue(result.wasSuccessful()) class StubPrefixedTestMethodsTestCase(basetest.TestCase): _test_loader = unittest.TestLoader() _test_loader.testMethodPrefix = 'StubTest' def StubTest0(self): pass def StubTest1(self): pass class SetUpSpy(StubPrefixedTestMethodsTestCase): __metaclass__ = basetest.BeforeAfterTestCaseMeta set_up_counter = 0 def setUp(self): StubPrefixedTestMethodsTestCase.setUp(self) SetUpSpy.set_up_counter += 1 class SetUpTestCaseSpy(StubPrefixedTestMethodsTestCase): __metaclass__ = basetest.BeforeAfterTestCaseMeta set_up_test_case_counter = 0 def setUpTestCase(self): StubPrefixedTestMethodsTestCase.setUpTestCase(self) SetUpTestCaseSpy.set_up_test_case_counter += 1 class TearDownSpy(StubPrefixedTestMethodsTestCase): __metaclass__ = basetest.BeforeAfterTestCaseMeta tear_down_counter = 0 def tearDown(self): TearDownSpy.tear_down_counter += 1 StubPrefixedTestMethodsTestCase.tearDown(self) class TearDownTestCaseSpy(StubPrefixedTestMethodsTestCase): __metaclass__ = basetest.BeforeAfterTestCaseMeta tear_down_test_case_counter = 0 def tearDownTestCase(self): TearDownTestCaseSpy.tear_down_test_case_counter += 1 StubPrefixedTestMethodsTestCase.tearDownTestCase(self) class OnlyBeforeAfterTestCaseMetaDefinedSpy(basetest.TestCase): __metaclass__ = basetest.BeforeAfterTestCaseMeta # Here we define 3 classes: a base class and two subclasses inheriting from it. # The base class has implementations for all four setUp / tearDown methods, and # the two subclasses override complementary subsets of them: one does # setUpTestCase and tearDown, the other setUp and tearDownTestCase. This way we # can check that overriding each method works, and that they don't have to be # overriden in matching pairs. # We use an even number of test methods in one test (2 in A) and an odd number # in the other (3 in B) intentionally. These failed in different ways with the # old code. With an even number of test methods, the old code calls # tearDownTestCase early; with an odd number of test methods tearDownTestCase # would not be called. The older implementation calls tearDownTestCase when its # counter of tests remaining reached zero. When double counting, if there were # 2 test methods, it'd hit 0 after executing only 1 of them. If there were 3, # it would go from 3 to 1 to -1, skipping 0 entirely. class InheritanceSpyBaseClass(basetest.TestCase): __metaclass__ = basetest.BeforeAfterTestCaseMeta calls_made = [] @staticmethod def Log(call): InheritanceSpyBaseClass.calls_made.append(call) @staticmethod def ClearLog(): InheritanceSpyBaseClass.calls_made = [] def setUpTestCase(self): self.Log('base setUpTestCase') def tearDownTestCase(self): self.Log('base tearDownTestCase') def setUp(self): self.Log('base setUp') def tearDown(self): self.Log('base tearDown') def StubTest0(self): self.Log('base StubTest0') class InheritanceSpySubClassA(InheritanceSpyBaseClass): _test_loader = unittest.TestLoader() _test_loader.testMethodPrefix = 'StubTest' def StubTest1(self): self.Log('sub StubTest1') def setUpTestCase(self): self.Log('sub setUpTestCase') def tearDown(self): self.Log('sub tearDown') class InheritanceSpySubClassB(InheritanceSpyBaseClass): _test_loader = unittest.TestLoader() _test_loader.testMethodPrefix = 'StubTest' # Intentionally mask StubTest0 from the base class def StubTest0(self): self.Log('sub StubTest0') def StubTest1(self): self.Log('sub StubTest1') def StubTest2(self): self.Log('sub StubTest2') def setUp(self): self.Log('sub setUp') def tearDownTestCase(self): self.Log('sub tearDownTestCase') # We define another pair of base/subclass here to verify that tearDown # and tearDownTestCase always happen in the correct order. class TearDownOrderWatcherBaseClass(basetest.TestCase): __metaclass__ = basetest.BeforeAfterTestCaseMeta calls_made = [] @staticmethod def Log(call): TearDownOrderWatcherBaseClass.calls_made.append(call) @staticmethod def ClearLog(): TearDownOrderWatcherBaseClass.calls_made = [] def __init__(self, *args, **kwds): super(TearDownOrderWatcherBaseClass, self).__init__(*args, **kwds) def setUpTestCase(self): self.Log('TearDownOrderWatcherBaseClass.setUpTestCase') def tearDownTestCase(self): self.Log('TearDownOrderWatcherBaseClass.tearDownTestCase') def setUp(self): self.Log('TearDownOrderWatcherBaseClass.setUp') def tearDown(self): self.Log('TearDownOrderWatcherBaseClass.tearDown') class TearDownOrderWatcherSubClass(TearDownOrderWatcherBaseClass): def setUp(self): self.Log('TearDownOrderWatcherSubClass.setUp Start') super(TearDownOrderWatcherSubClass, self).setUp() self.Log('TearDownOrderWatcherSubClass.setUp Finish') def tearDown(self): self.Log('TearDownOrderWatcherSubClass.tearDown Start') super(TearDownOrderWatcherSubClass, self).tearDown() self.Log('TearDownOrderWatcherSubClass.tearDown Finish') def testAAAA(self): self.Log('testAAAA') self.assertTrue(True) # We define a multiply inheriting metaclass and an instance class to verify that # multiple inheritance for metaclasses. class OtherMetaClass(type): def __init__(cls, name, bases, dict): super(OtherMetaClass, cls).__init__(name, bases, dict) cls.other_meta_called = True class MultipleMetaBeforeAfter(basetest.BeforeAfterTestCaseMeta, OtherMetaClass): """Allow classes to support BeforeAfterTestCase and another metaclass. Order matters here since we want to make sure BeforeAfterTestCaseMeta passes through correctly to the other metaclass. """ class MultipleMetaInstanceClass(basetest.TestCase): __metaclass__ = MultipleMetaBeforeAfter def testOtherMetaInitCalled(self): self.assertTrue(hasattr(MultipleMetaInstanceClass, 'other_meta_called')) class AssertSequenceStartsWithTest(basetest.TestCase): def setUp(self): self.a = [5, 'foo', {'c': 'd'}, None] def testEmptySequenceStartsWithEmptyPrefix(self): self.assertSequenceStartsWith([], ()) def testSequencePrefixIsAnEmptyList(self): self.assertSequenceStartsWith([[]], ([], 'foo')) def testRaiseIfEmptyPrefixWithNonEmptyWhole(self): self.assertRaisesWithRegexpMatch( AssertionError, 'Prefix length is 0 but whole length is %d: %s' % ( len(self.a), '\[5, \'foo\', \{\'c\': \'d\'\}, None\]'), self.assertSequenceStartsWith, [], self.a) def testSingleElementPrefix(self): self.assertSequenceStartsWith([5], self.a) def testTwoElementPrefix(self): self.assertSequenceStartsWith((5, 'foo'), self.a) def testPrefixIsFullSequence(self): self.assertSequenceStartsWith([5, 'foo', {'c': 'd'}, None], self.a) def testStringPrefix(self): self.assertSequenceStartsWith('abc', 'abc123') def testConvertNonSequencePrefixToSequenceAndTryAgain(self): self.assertSequenceStartsWith(5, self.a) def testWholeNotASequence(self): msg = ('For whole: len\(5\) is not supported, it appears to be type: ' '<(type|class) \'int\'>') self.assertRaisesWithRegexpMatch(AssertionError, msg, self.assertSequenceStartsWith, self.a, 5) def testRaiseIfSequenceDoesNotStartWithPrefix(self): msg = ('prefix: \[\'foo\', \{\'c\': \'d\'\}\] not found at start of whole: ' '\[5, \'foo\', \{\'c\': \'d\'\}, None\].') self.assertRaisesWithRegexpMatch( AssertionError, msg, self.assertSequenceStartsWith, ['foo', {'c': 'd'}], self.a) def testRaiseIfTypesArNotSupported(self): self.assertRaisesWithRegexpMatch( TypeError, 'unhashable type', self.assertSequenceStartsWith, {'a': 1, 2: 'b'}, {'a': 1, 2: 'b', 'c': '3'}) class InitNotNecessaryForAssertsTest(basetest.TestCase): '''TestCase assertions should work even if __init__ wasn't correctly called. This is a hack, see comment in basetest.TestCase._getAssertEqualityFunc. We know that not calling __init__ of a superclass is a bad thing, but people keep doing them, and this (even if a little bit dirty) saves them from shooting themselves in the foot. ''' def testSubclass(self): class Subclass(basetest.TestCase): def __init__(self): pass Subclass().assertEquals({}, {}) def testMultipleInheritance(self): class Foo(object): def __init__(self, *args, **kwargs): pass class Subclass(Foo, basetest.TestCase): pass Subclass().assertEquals({}, {}) if __name__ == '__main__': basetest.main() google-apputils-0.4.0/tests/datelib_unittest.py0000640003611500116100000002016612176032264022526 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2002 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unittest for datelib.py module.""" import datetime import random import time import pytz from google.apputils import basetest from google.apputils import datelib class TimestampUnitTest(basetest.TestCase): seed = 1979 def testTzAwareSuccession(self): a = datelib.Timestamp.now() b = datelib.Timestamp.utcnow() self.assertLessEqual(a, b) def testTzRandomConversion(self): random.seed(self.seed) for unused_i in xrange(100): stz = pytz.timezone(random.choice(pytz.all_timezones)) a = datelib.Timestamp.FromString('2008-04-12T10:00:00', stz) b = a for unused_j in xrange(100): b = b.astimezone(pytz.timezone(random.choice(pytz.all_timezones))) self.assertEqual(a, b) random.seed() def testMicroTimestampConversion(self): """Test that f1(f2(a)) == a.""" def IsEq(x): self.assertEqual( x, datelib.Timestamp.FromMicroTimestamp(x).AsMicroTimestamp()) IsEq(0) IsEq(datelib.MAXIMUM_MICROSECOND_TIMESTAMP) random.seed(self.seed) for _ in xrange(100): IsEq(random.randint(0, datelib.MAXIMUM_MICROSECOND_TIMESTAMP)) def testMicroTimestampKnown(self): self.assertEqual(0, datelib.Timestamp.FromString( '1970-01-01T00:00:00', pytz.UTC).AsMicroTimestamp()) self.assertEqual( datelib.MAXIMUM_MICROSECOND_TIMESTAMP, datelib.MAXIMUM_MICROSECOND_TIMESTAMP_AS_TS.AsMicroTimestamp()) def testMicroTimestampOrdering(self): """Test that cmp(a, b) == cmp(f1(a), f1(b)).""" def IsEq(a, b): self.assertEqual( cmp(a, b), cmp(datelib.Timestamp.FromMicroTimestamp(a), datelib.Timestamp.FromMicroTimestamp(b))) random.seed(self.seed) for unused_i in xrange(100): IsEq( random.randint(0, datelib.MAXIMUM_MICROSECOND_TIMESTAMP), random.randint(0, datelib.MAXIMUM_MICROSECOND_TIMESTAMP)) def testCombine(self): for tz in (datelib.UTC, datelib.US_PACIFIC): self.assertEqual( datelib.Timestamp(1970, 1, 1, 0, 0, 0, 0, tz), datelib.Timestamp.combine( datelib.datetime.date(1970, 1, 1), datelib.datetime.time(0, 0, 0), tz)) self.assertEqual( datelib.Timestamp(9998, 12, 31, 23, 59, 59, 999999, tz), datelib.Timestamp.combine( datelib.datetime.date(9998, 12, 31), datelib.datetime.time(23, 59, 59, 999999), tz)) def testFromString1(self): for string_zero in ( '1970-01-01 00:00:00', '19700101T000000', '1970-01-01T00:00:00' ): for testtz in (datelib.UTC, datelib.US_PACIFIC): self.assertEqual( datelib.Timestamp.FromString(string_zero, testtz), datelib.Timestamp(1970, 1, 1, 0, 0, 0, 0, testtz)) self.assertEqual( datelib.Timestamp.FromString( '1970-01-01T00:00:00+0000', datelib.US_PACIFIC), datelib.Timestamp(1970, 1, 1, 0, 0, 0, 0, datelib.UTC)) startdate = datelib.Timestamp(2009, 1, 1, 3, 0, 0, 0, datelib.US_PACIFIC) for day in xrange(1, 366): self.assertEqual( datelib.Timestamp.FromString(startdate.isoformat()), startdate, 'FromString works for day %d since 2009-01-01' % day) startdate += datelib.datetime.timedelta(days=1) def testFromString2(self): """Test correctness of parsing the local time in a given timezone. The result shall always be the same as tz.localize(naive_time). """ baseday = datelib.datetime.date(2009, 1, 1).toordinal() for day_offset in xrange(0, 365): day = datelib.datetime.date.fromordinal(baseday + day_offset) naive_day = datelib.datetime.datetime.combine( day, datelib.datetime.time(0, 45, 9)) naive_day_str = naive_day.strftime('%Y-%m-%dT%H:%M:%S') self.assertEqual( datelib.US_PACIFIC.localize(naive_day), datelib.Timestamp.FromString(naive_day_str, tz=datelib.US_PACIFIC), 'FromString localizes time incorrectly') def testFromStringInterval(self): expected_date = datetime.datetime.utcnow() - datetime.timedelta(days=1) expected_s = time.mktime(expected_date.utctimetuple()) actual_date = datelib.Timestamp.FromString('1d') actual_s = time.mktime(actual_date.timetuple()) diff_seconds = actual_s - expected_s self.assertBetween(diff_seconds, 0, 1) self.assertRaises( datelib.TimeParseError, datelib.Timestamp.FromString, 'wat') def _EpochToDatetime(t, tz=None): if tz is not None: return datelib.datetime.datetime.fromtimestamp(t, tz) else: return datelib.datetime.datetime.utcfromtimestamp(t) class DatetimeConversionUnitTest(basetest.TestCase): def setUp(self): self.pst = pytz.timezone('US/Pacific') self.utc = pytz.utc self.now = time.time() def testDatetimeToUTCMicros(self): self.assertEqual( 0, datelib.DatetimeToUTCMicros(_EpochToDatetime(0))) self.assertEqual( 1001 * long(datelib._MICROSECONDS_PER_SECOND), datelib.DatetimeToUTCMicros(_EpochToDatetime(1001))) self.assertEqual(long(self.now * datelib._MICROSECONDS_PER_SECOND), datelib.DatetimeToUTCMicros(_EpochToDatetime(self.now))) # tzinfo shouldn't change the result self.assertEqual( 0, datelib.DatetimeToUTCMicros(_EpochToDatetime(0, tz=self.pst))) def testDatetimeToUTCMillis(self): self.assertEqual( 0, datelib.DatetimeToUTCMillis(_EpochToDatetime(0))) self.assertEqual( 1001 * 1000L, datelib.DatetimeToUTCMillis(_EpochToDatetime(1001))) self.assertEqual(long(self.now * 1000), datelib.DatetimeToUTCMillis(_EpochToDatetime(self.now))) # tzinfo shouldn't change the result self.assertEqual( 0, datelib.DatetimeToUTCMillis(_EpochToDatetime(0, tz=self.pst))) def testUTCMicrosToDatetime(self): self.assertEqual(_EpochToDatetime(0), datelib.UTCMicrosToDatetime(0)) self.assertEqual(_EpochToDatetime(1.000001), datelib.UTCMicrosToDatetime(1000001)) self.assertEqual(_EpochToDatetime(self.now), datelib.UTCMicrosToDatetime( long(self.now * datelib._MICROSECONDS_PER_SECOND))) # Check timezone-aware comparisons self.assertEqual(_EpochToDatetime(0, self.pst), datelib.UTCMicrosToDatetime(0, tz=self.pst)) self.assertEqual(_EpochToDatetime(0, self.pst), datelib.UTCMicrosToDatetime(0, tz=self.utc)) def testUTCMillisToDatetime(self): self.assertEqual(_EpochToDatetime(0), datelib.UTCMillisToDatetime(0)) self.assertEqual(_EpochToDatetime(1.001), datelib.UTCMillisToDatetime(1001)) t = time.time() dt = _EpochToDatetime(t) # truncate sub-milli time dt -= datelib.datetime.timedelta(microseconds=dt.microsecond % 1000) self.assertEqual(dt, datelib.UTCMillisToDatetime(long(t * 1000))) # Check timezone-aware comparisons self.assertEqual(_EpochToDatetime(0, self.pst), datelib.UTCMillisToDatetime(0, tz=self.pst)) self.assertEqual(_EpochToDatetime(0, self.pst), datelib.UTCMillisToDatetime(0, tz=self.utc)) class MicrosecondsToSecondsUnitTest(basetest.TestCase): def testConversionFromMicrosecondsToSeconds(self): self.assertEqual(0.0, datelib.MicrosecondsToSeconds(0)) self.assertEqual(7.0, datelib.MicrosecondsToSeconds(7000000)) self.assertEqual(1.234567, datelib.MicrosecondsToSeconds(1234567)) self.assertEqual(12345654321.123456, datelib.MicrosecondsToSeconds(12345654321123456)) if __name__ == '__main__': basetest.main() google-apputils-0.4.0/tests/shellutil_unittest.py0000750003611500116100000000370512176032264023131 0ustar dborowitzeng00000000000000#!/usr/bin/env python # This code must be source compatible with Python 2.4 through 3.3. # # Copyright 2003 Google Inc. All Rights Reserved. """Unittest for shellutil module.""" import os # Use unittest instead of basetest to avoid bootstrap issues / circular deps. import unittest from google.apputils import shellutil # Running windows? win32 = (os.name == 'nt') class ShellUtilUnitTest(unittest.TestCase): def testShellEscapeList(self): # TODO(user): Actually run some shell commands and test the # shell escaping works properly. # Empty list words = [] self.assertEqual(shellutil.ShellEscapeList(words), '') # Empty string words = [''] self.assertEqual(shellutil.ShellEscapeList(words), "''") # Single word words = ['foo'] self.assertEqual(shellutil.ShellEscapeList(words), "'foo'") # Single word with single quote words = ["foo'bar"] expected = """ 'foo'"'"'bar' """.strip() self.assertEqual(shellutil.ShellEscapeList(words), expected) # .. double quote words = ['foo"bar'] expected = """ 'foo"bar' """.strip() self.assertEqual(shellutil.ShellEscapeList(words), expected) # Multiple words words = ['foo', 'bar'] self.assertEqual(shellutil.ShellEscapeList(words), "'foo' 'bar'") # Words with spaces words = ['foo', 'bar', "foo'' ''bar"] expected = """ 'foo' 'bar' 'foo'"'"''"'"' '"'"''"'"'bar' """.strip() self.assertEqual(shellutil.ShellEscapeList(words), expected) # Now I'm just being mean words = ['foo', 'bar', """ ""'"'" """.strip()] expected = """ 'foo' 'bar' '""'"'"'"'"'"'"' """.strip() self.assertEqual(shellutil.ShellEscapeList(words), expected) def testShellifyStatus(self): if not win32: self.assertEqual(shellutil.ShellifyStatus(0), 0) self.assertEqual(shellutil.ShellifyStatus(1), 129) self.assertEqual(shellutil.ShellifyStatus(1 * 256), 1) if __name__ == '__main__': unittest.main() google-apputils-0.4.0/tests/file_util_test.py0000640003611500116100000003362012176032264022175 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2007 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unittest for common file utilities.""" import __builtin__ import errno import os import posix import pwd import shutil import stat import tempfile import mox from google.apputils import basetest from google.apputils import file_util import gflags as flags FLAGS = flags.FLAGS # pylint is dumb about mox: # pylint: disable=no-member class FileUtilTest(basetest.TestCase): def testHomeDir(self): self.assertEqual(file_util.HomeDir(), pwd.getpwuid(os.geteuid()).pw_dir) self.assertEqual(file_util.HomeDir(0), pwd.getpwuid(0).pw_dir) self.assertEqual(file_util.HomeDir('root'), pwd.getpwnam('root').pw_dir) class FileUtilTempdirTest(basetest.TestCase): def setUp(self): self.temp_dir = tempfile.mkdtemp() self.file_path = self.temp_dir + 'sample.txt' self.sample_contents = 'Random text: aldmkfhjwoem103u74.' # To avoid confusion in the mode tests. self.prev_umask = posix.umask(0) def tearDown(self): shutil.rmtree(self.temp_dir) posix.umask(self.prev_umask) def testWriteOverwrite(self): file_util.Write(self.file_path, 'original contents') file_util.Write(self.file_path, self.sample_contents) with open(self.file_path) as fp: self.assertEquals(fp.read(), self.sample_contents) def testWriteExclusive(self): file_util.Write(self.file_path, 'original contents') self.assertRaises(OSError, file_util.Write, self.file_path, self.sample_contents, overwrite_existing=False) def testWriteMode(self): mode = 0744 file_util.Write(self.file_path, self.sample_contents, mode=mode) s = os.stat(self.file_path) self.assertEqual(stat.S_IMODE(s.st_mode), mode) def testAtomicWriteSuccessful(self): file_util.AtomicWrite(self.file_path, self.sample_contents) with open(self.file_path) as fp: self.assertEquals(fp.read(), self.sample_contents) def testAtomicWriteMode(self): mode = 0745 file_util.AtomicWrite(self.file_path, self.sample_contents, mode=mode) s = os.stat(self.file_path) self.assertEqual(stat.S_IMODE(s.st_mode), mode) class FileUtilMoxTestBase(basetest.TestCase): def setUp(self): self.mox = mox.Mox() self.sample_contents = 'Contents of the file' self.file_path = '/path/to/some/file' self.fd = 'a file descriptor' def tearDown(self): # In case a test fails before it gets to the unset line. self.mox.UnsetStubs() class FileUtilMoxTest(FileUtilMoxTestBase): def testListDirPath(self): self.mox.StubOutWithMock(os, 'listdir') dir_contents = ['file1', 'file2', 'file3', 'directory1', 'file4', 'directory2'] os.listdir('/path/to/some/directory').AndReturn(dir_contents) self.mox.ReplayAll() self.assertListEqual(file_util.ListDirPath('/path/to/some/directory'), ['%s/%s' % ('/path/to/some/directory', entry) for entry in dir_contents]) self.mox.VerifyAll() def testSuccessfulRead(self): file_handle = self.mox.CreateMockAnything() self.mox.StubOutWithMock(__builtin__, 'open', use_mock_anything=True) open(self.file_path).AndReturn(file_handle) file_handle.__enter__().AndReturn(file_handle) file_handle.read().AndReturn(self.sample_contents) file_handle.__exit__(None, None, None) self.mox.ReplayAll() try: self.assertEquals(file_util.Read(self.file_path), self.sample_contents) self.mox.VerifyAll() finally: # Because we mock out the built-in open() function, which the unittest # library depends on, we need to make sure we revert it before leaving the # test, otherwise any test failures will cause further internal failures # and yield no meaningful error output. self.mox.ResetAll() self.mox.UnsetStubs() def testWriteGroup(self): self.mox.StubOutWithMock(os, 'open') self.mox.StubOutWithMock(os, 'write') self.mox.StubOutWithMock(os, 'close') self.mox.StubOutWithMock(os, 'chown') gid = 'new gid' os.open(self.file_path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, 0666).AndReturn(self.fd) os.write(self.fd, self.sample_contents) os.close(self.fd) os.chown(self.file_path, -1, gid) self.mox.ReplayAll() file_util.Write(self.file_path, self.sample_contents, gid=gid) self.mox.VerifyAll() class AtomicWriteMoxTest(FileUtilMoxTestBase): def setUp(self): super(AtomicWriteMoxTest, self).setUp() self.mox.StubOutWithMock(tempfile, 'mkstemp') self.mox.StubOutWithMock(os, 'write') self.mox.StubOutWithMock(os, 'close') self.mox.StubOutWithMock(os, 'chmod') self.mox.StubOutWithMock(os, 'rename') self.mox.StubOutWithMock(os, 'remove') self.mode = 'new permissions' self.gid = 'new gid' self.temp_filename = '/some/temp/file' self.os_error = OSError('A problem renaming!') tempfile.mkstemp(dir='/path/to/some').AndReturn( (self.fd, self.temp_filename)) os.write(self.fd, self.sample_contents) os.close(self.fd) os.chmod(self.temp_filename, self.mode) def tearDown(self): self.mox.UnsetStubs() def testAtomicWriteGroup(self): self.mox.StubOutWithMock(os, 'chown') os.chown(self.temp_filename, -1, self.gid) os.rename(self.temp_filename, self.file_path) self.mox.ReplayAll() file_util.AtomicWrite(self.file_path, self.sample_contents, mode=self.mode, gid=self.gid) self.mox.VerifyAll() def testAtomicWriteGroupError(self): self.mox.StubOutWithMock(os, 'chown') os.chown(self.temp_filename, -1, self.gid).AndRaise(self.os_error) os.remove(self.temp_filename) self.mox.ReplayAll() self.assertRaises(OSError, file_util.AtomicWrite, self.file_path, self.sample_contents, mode=self.mode, gid=self.gid) self.mox.VerifyAll() def testRenamingError(self): os.rename(self.temp_filename, self.file_path).AndRaise(self.os_error) os.remove(self.temp_filename) self.mox.ReplayAll() self.assertRaises(OSError, file_util.AtomicWrite, self.file_path, self.sample_contents, mode=self.mode) self.mox.VerifyAll() def testRenamingErrorWithRemoveError(self): extra_error = OSError('A problem removing!') os.rename(self.temp_filename, self.file_path).AndRaise(self.os_error) os.remove(self.temp_filename).AndRaise(extra_error) self.mox.ReplayAll() try: file_util.AtomicWrite(self.file_path, self.sample_contents, mode=self.mode) except OSError as e: self.assertEquals(str(e), 'A problem renaming!. Additional errors cleaning up: ' 'A problem removing!') else: raise self.failureException('OSError not raised by AtomicWrite') self.mox.VerifyAll() class TemporaryFilesMoxTest(FileUtilMoxTestBase): def testTemporaryFileWithContents(self): contents = 'Inspiration!' with file_util.TemporaryFileWithContents(contents) as temporary_file: filename = temporary_file.name contents_read = open(temporary_file.name).read() self.assertEqual(contents_read, contents) # Ensure that the file does not exist. self.assertFalse(os.path.exists(filename)) class MkDirsMoxTest(FileUtilMoxTestBase): # pylint is dumb about mox: # pylint: disable=maybe-no-member def setUp(self): super(MkDirsMoxTest, self).setUp() self.mox.StubOutWithMock(os, 'mkdir') self.mox.StubOutWithMock(os, 'chmod') self.mox.StubOutWithMock(os.path, 'isdir') self.dir_tree = ['/path', 'to', 'some', 'directory'] def tearDown(self): self.mox.UnsetStubs() def testNoErrorsAbsoluteOneDir(self): # record, replay os.mkdir('/foo') self.mox.ReplayAll() # test, verify file_util.MkDirs('/foo') self.mox.VerifyAll() def testNoErrorsAbsoluteOneDirWithForceMode(self): # record, replay os.mkdir('/foo') os.chmod('/foo', 0707) self.mox.ReplayAll() # test, verify file_util.MkDirs('/foo', force_mode=0707) self.mox.VerifyAll() def testNoErrorsExistingDirWithForceMode(self): exist_error = OSError(errno.EEXIST, 'This string not used') # record, replay os.mkdir('/foo').AndRaise(exist_error) # no chmod is called since the dir exists os.path.isdir('/foo').AndReturn(True) self.mox.ReplayAll() # test, verify file_util.MkDirs('/foo', force_mode=0707) self.mox.VerifyAll() def testNoErrorsAbsoluteSlashDot(self): # record, replay os.mkdir('/foo') self.mox.ReplayAll() # test, verify file_util.MkDirs('/foo/.') self.mox.VerifyAll() def testNoErrorsAbsoluteExcessiveSlashDot(self): """See that normpath removes irrelevant .'s in the path.""" # record, replay os.mkdir('/foo') os.mkdir('/foo/bar') self.mox.ReplayAll() # test, verify file_util.MkDirs('/./foo/./././bar/.') self.mox.VerifyAll() def testNoErrorsAbsoluteTwoDirs(self): # record, replay os.mkdir('/foo') os.mkdir('/foo/bar') self.mox.ReplayAll() # test, verify file_util.MkDirs('/foo/bar') self.mox.VerifyAll() def testNoErrorsPartialTwoDirsWithForceMode(self): exist_error = OSError(errno.EEXIST, 'This string not used') # record, replay os.mkdir('/foo').AndRaise(exist_error) # /foo exists os.path.isdir('/foo').AndReturn(True) os.mkdir('/foo/bar') # bar does not os.chmod('/foo/bar', 0707) self.mox.ReplayAll() # test, verify file_util.MkDirs('/foo/bar', force_mode=0707) self.mox.VerifyAll() def testNoErrorsRelativeOneDir(self): # record, replay os.mkdir('foo') self.mox.ReplayAll() # test, verify file_util.MkDirs('foo') self.mox.VerifyAll() def testNoErrorsRelativeTwoDirs(self): # record, replay os.mkdir('foo') os.mkdir('foo/bar') self.mox.ReplayAll() # test, verify file_util.MkDirs('foo/bar') self.mox.VerifyAll() def testDirectoriesExist(self): exist_error = OSError(errno.EEXIST, 'This string not used') # record, replay for i in range(len(self.dir_tree)): path = os.path.join(*self.dir_tree[:i+1]) os.mkdir(path).AndRaise(exist_error) os.path.isdir(path).AndReturn(True) self.mox.ReplayAll() # test, verify file_util.MkDirs(os.path.join(*self.dir_tree)) self.mox.VerifyAll() def testFileInsteadOfDirectory(self): exist_error = OSError(errno.EEXIST, 'This string not used') path = self.dir_tree[0] # record, replay os.mkdir(path).AndRaise(exist_error) os.path.isdir(path).AndReturn(False) self.mox.ReplayAll() # test, verify self.assertRaises(OSError, file_util.MkDirs, os.path.join(*self.dir_tree)) self.mox.VerifyAll() def testNonExistsError(self): non_exist_error = OSError(errno.ETIMEDOUT, 'This string not used') path = self.dir_tree[0] # record, replay os.mkdir(path).AndRaise(non_exist_error) self.mox.ReplayAll() # test, verify self.assertRaises(OSError, file_util.MkDirs, os.path.join(*self.dir_tree)) self.mox.VerifyAll() class RmDirsTestCase(mox.MoxTestBase): def testRmDirs(self): test_sandbox = os.path.join(FLAGS.test_tmpdir, 'test-rm-dirs') test_dir = os.path.join(test_sandbox, 'test', 'dir') os.makedirs(test_sandbox) with open(os.path.join(test_sandbox, 'file'), 'w'): pass os.makedirs(test_dir) with open(os.path.join(test_dir, 'file'), 'w'): pass file_util.RmDirs(test_dir) self.assertFalse(os.path.exists(os.path.join(test_sandbox, 'test'))) self.assertTrue(os.path.exists(os.path.join(test_sandbox, 'file'))) shutil.rmtree(test_sandbox) def testRmDirsForNonExistingDirectory(self): self.mox.StubOutWithMock(os, 'rmdir') os.rmdir('path/to') os.rmdir('path') self.mox.StubOutWithMock(shutil, 'rmtree') shutil.rmtree('path/to/directory').AndRaise( OSError(errno.ENOENT, "No such file or directory 'path/to/directory'")) self.mox.ReplayAll() file_util.RmDirs('path/to/directory') self.mox.VerifyAll() def testRmDirsForNonExistingParentDirectory(self): self.mox.StubOutWithMock(os, 'rmdir') os.rmdir('path/to').AndRaise( OSError(errno.ENOENT, "No such file or directory 'path/to'")) os.rmdir('path') self.mox.StubOutWithMock(shutil, 'rmtree') shutil.rmtree('path/to/directory').AndRaise( OSError(errno.ENOENT, "No such file or directory 'path/to/directory'")) self.mox.ReplayAll() file_util.RmDirs('path/to/directory') self.mox.VerifyAll() def testRmDirsForNotEmptyDirectory(self): self.mox.StubOutWithMock(os, 'rmdir') os.rmdir('path/to').AndRaise( OSError(errno.ENOTEMPTY, 'Directory not empty', 'path/to')) self.mox.StubOutWithMock(shutil, 'rmtree') shutil.rmtree('path/to/directory') self.mox.ReplayAll() file_util.RmDirs('path/to/directory') self.mox.VerifyAll() def testRmDirsForPermissionDeniedOnParentDirectory(self): self.mox.StubOutWithMock(os, 'rmdir') os.rmdir('path/to').AndRaise( OSError(errno.EACCES, 'Permission denied', 'path/to')) self.mox.StubOutWithMock(shutil, 'rmtree') shutil.rmtree('path/to/directory') self.mox.ReplayAll() file_util.RmDirs('path/to/directory') self.mox.VerifyAll() def testRmDirsWithSimplePath(self): self.mox.StubOutWithMock(shutil, 'rmtree') shutil.rmtree('directory') self.mox.ReplayAll() file_util.RmDirs('directory') self.mox.VerifyAll() if __name__ == '__main__': basetest.main() google-apputils-0.4.0/tests/stopwatch_unittest.py0000640003611500116100000001101412176032264023126 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2006 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tests for the stopwatch module.""" __author__ = 'dbentley@google.com (Dan Bentley)' from google.apputils import basetest import gflags as flags from google.apputils import stopwatch FLAGS = flags.FLAGS class StubTime(object): """Simple stub replacement for the time module. Only useful for relative calculations, since it always starts at 0. """ # These method names match the standard library time module. def __init__(self): self._counter = 0 def time(self): """Get the time for this time object. A call is always guaranteed to be greater than the previous one. Returns: A monotonically increasing time. """ self._counter += 0.0001 return self._counter def sleep(self, time): """Simulate sleeping for the specified number of seconds.""" self._counter += time class StopwatchUnitTest(basetest.TestCase): """Stopwatch tests. These tests are tricky because timing is difficult. Therefore, we test the structure of the results but not the results themselves for fear it would lead to intermittent but persistent failures. """ def setUp(self): self.time = StubTime() stopwatch.time = self.time def testResults(self): sw = stopwatch.StopWatch() sw.start() sw.stop() results = sw.results() self.assertListEqual([r[0] for r in results], ['total']) results = sw.results(verbose=1) self.assertListEqual([r[0] for r in results], ['overhead', 'total']) # test tally part of results. sw.start('ron') sw.stop('ron') sw.start('ron') sw.stop('ron') results = sw.results() results = sw.results(verbose=1) for r in results: if r[0] == 'ron': assert r[2] == 2 def testSeveralTimes(self): sw = stopwatch.StopWatch() sw.start() sw.start('a') sw.start('b') self.time.sleep(1) sw.stop('b') sw.stop('a') sw.stop() results = sw.results(verbose=1) self.assertListEqual([r[0] for r in results], ['a', 'b', 'overhead', 'total']) # Make sure overhead is positive self.assertEqual(results[2][1] > 0, 1) def testNoStopOthers(self): sw = stopwatch.StopWatch() sw.start() sw.start('a') sw.start('b', stop_others=0) self.time.sleep(1) sw.stop('b') sw.stop('a') sw.stop() #overhead should be negative, because we ran two timers simultaneously #It is possible that this could fail in outlandish circumstances. #If this is a problem in practice, increase the value of the call to #time.sleep until it passes consistently. #Or, consider finding a platform where the two calls sw.start() and #sw.start('a') happen within 1 second. results = sw.results(verbose=1) self.assertEqual(results[2][1] < 0, 1) def testStopNonExistentTimer(self): sw = stopwatch.StopWatch() self.assertRaises(RuntimeError, sw.stop) self.assertRaises(RuntimeError, sw.stop, 'foo') def testResultsDoesntCrashWhenUnstarted(self): sw = stopwatch.StopWatch() sw.results() def testResultsDoesntCrashWhenUnstopped(self): sw = stopwatch.StopWatch() sw.start() sw.results() def testTimerValue(self): sw = stopwatch.StopWatch() self.assertAlmostEqual(0, sw.timervalue('a'), 2) sw.start('a') self.assertAlmostEqual(0, sw.timervalue('a'), 2) self.time.sleep(1) self.assertAlmostEqual(1, sw.timervalue('a'), 2) sw.stop('a') self.assertAlmostEqual(1, sw.timervalue('a'), 2) sw.start('a') self.time.sleep(1) self.assertAlmostEqual(2, sw.timervalue('a'), 2) sw.stop('a') self.assertAlmostEqual(2, sw.timervalue('a'), 2) def testResultsDoesntReset(self): sw = stopwatch.StopWatch() sw.start() self.time.sleep(1) sw.start('a') self.time.sleep(1) sw.stop('a') sw.stop() res1 = sw.results(verbose=True) res2 = sw.results(verbose=True) self.assertListEqual(res1, res2) if __name__ == '__main__': basetest.main() google-apputils-0.4.0/tests/appcommands_unittest.sh0000750003611500116100000002301712176032264023406 0ustar dborowitzeng00000000000000#! /bin/bash # Copyright 2007 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Author: mboerger@google.com PYTHON=$(which python) function die { echo "$1" exit 1 } IMPORTS="from google.apputils import app from google.apputils import appcommands import gflags as flags" # This should exit with error code because no main defined $PYTHON -c "${IMPORTS} appcommands.Run()" >/dev/null 2>&1 && \ die "Test 1 failed" # Standard use. This should exit successfully $PYTHON -c "${IMPORTS} import sys def test(argv): return 0 def main(argv): appcommands.AddCmdFunc('test', test) appcommands.Run() sys.exit(1)" test || \ die "Test 2 failed" # Even with no return from Cmds Run() does not return $PYTHON -c "${IMPORTS} import sys def test(argv): return def main(argv): appcommands.AddCmdFunc('test', test) appcommands.Run() sys.exit(1)" test || \ die "Test 3 failed" # Standard use with returning an error code. $PYTHON -c "${IMPORTS} import sys def test(argv): return 1 def main(argv): appcommands.AddCmdFunc('test', test) appcommands.Run() sys.exit(0)" test && \ die "Test 4 failed" # Executing two commands in single mode does not work (execute only first) $PYTHON -c "${IMPORTS} def test1(argv): return 0 def test2(argv): return 1 def main(argv): appcommands.AddCmdFunc('test1', test1) appcommands.AddCmdFunc('test2', test2) appcommands.Run()" test1 test2 || \ die "Test 5 failed" # Registering a command twice does not work. $PYTHON -c "${IMPORTS} def test1(argv): return 0 def main(argv): appcommands.AddCmdFunc('test', test1) appcommands.AddCmdFunc('test', test1) appcommands.Run()" test >/dev/null 2>&1 && \ die "Test 6 failed" # Executing help, returns non zero return code (1), then check result RES=`$PYTHON -c "${IMPORTS} def test1(argv): '''Help1''' return 0 def test2(argv): '''Help2''' return 0 def main(argv): appcommands.AddCmdFunc('test1', test1) appcommands.AddCmdFunc('test2', test2) appcommands.Run()" help` && die "Test 7 failed" echo "${RES}" | grep -q "USAGE: " || die "Test 8 failed" echo "${RES}" | sed -ne '/following commands:/,/.*/p' | \ grep -q "help, test1, test2" || die "Test 9 failed" echo "${RES}" | grep -q -E "(^| )test1[ \t]+Help1($| )" || die "Test 10 failed" echo "${RES}" | grep -q -E "(^| )test2[ \t]+Help2($| )" || die "Test 11 failed" # Executing help for command, returns non zero return code (1), then check result RES=`$PYTHON -c "${IMPORTS} def test1(argv): '''Help1''' return 0 def test2(argv): '''Help2''' return 0 def main(argv): appcommands.AddCmdFunc('test1', test1) appcommands.AddCmdFunc('test2', test2) appcommands.Run()" help test2` && die "Test 12 failed" echo "${RES}" | grep -q "USAGE: " || die "Test 13 failed" echo "${RES}" | grep -q -E "(^| )Any of the following commands:" && die "Test 14 failed" echo "${RES}" | grep -q -E "(^| )test1[ \t]+" && die "Test 15 failed" echo "${RES}" | grep -q -E "(^| )test2[ \t]+Help2($| )" || die "Test 16 failed" # Returning False succeeds $PYTHON -c "${IMPORTS} def test(argv): return False def main(argv): appcommands.AddCmdFunc('test', test) appcommands.Run()" test || die "Test 17 failed" # Returning True fails $PYTHON -c "${IMPORTS} def test(argv): return True def main(argv): appcommands.AddCmdFunc('test', test) appcommands.Run()" test && die "Test 18 failed" # Registering using AddCmd instead of AddCmdFunc, should be the normal case $PYTHON -c "${IMPORTS} class test(appcommands.Cmd): def Run(self, argv): return 0 def main(argv): appcommands.AddCmd('test', test) appcommands.Run()" test || die "Test 19 failed" # Registering using AddCmd instead of AddCmdFunc, now fail $PYTHON -c "${IMPORTS} class test(appcommands.Cmd): def Run(self, argv): return 1 def main(argv): appcommands.AddCmd('test', test) appcommands.Run()" test && die "Test 20 failed" TEST=./appcommands_example.py if test -s "${TEST}.py"; then TEST="${TEST}.py" elif test ! -s "${TEST}"; then die "Could not locate ${TEST}" fi # Success $PYTHON $TEST test1 >/dev/null 2>&1 || die "Test 21 failed" $PYTHON $TEST test1|grep -q 'Command1' 2>&1 || die "Test 22 failed" $PYTHON $TEST test2|grep -q 'Command2' 2>&1 || die "Test 23 failed" $PYTHON $TEST test3|grep -q 'Command3' 2>&1 || die "Test 24 failed" # Success, --nofail1 belongs to test1 $PYTHON $TEST test1 --nofail1 >/dev/null 2>&1 || die "Test 25 failed" # Failure, --fail1 $PYTHON $TEST test1 --fail1 >/dev/null 2>&1 && die "Test 26 failed" # Failure, --nofail1 does not belong to test2 $PYTHON $TEST test2 --nofail1 >/dev/null 2>&1 && die "Test 27 failed" # Failure, --nofail1 must appear after its command $PYTHON $TEST --nofail1 test1 >/dev/null 2>&1 && die "Test 28 failed" # Failure, explicit from --fail2 $PYTHON $TEST test2 --fail2 >/dev/null 2>&1 && die "Test 29 failed" # Success, --hint before command, foo shown with test1 $PYTHON $TEST --hint 'XYZ' test1|grep -q "Hint1:'XYZ'" || die "Test 30 failed" # Success, --hint before command, foo shown with test1 $PYTHON $TEST test1 --hint 'XYZ'|grep -q "Hint1:'XYZ'" || die "Test 31 failed" # Test for standard --help $PYTHON $TEST --help|grep -q "following commands:" && die "Test 32 failed" $PYTHON $TEST help|grep -q "following commands:" || die "Test 33 failed" # No help after command $PYTHON $TEST test1 --help|grep -q "following commands:" && die "Test 34 failed" $PYTHON $TEST test1 --help 'XYZ'|grep -q "Hint1:'XYZ'" && die "Test 35 failed" # Help specific to command: $PYTHON $TEST --help test1|grep -q "following commands:" && die "Test 36 failed" $PYTHON $TEST --help test1|grep -q "test1 *Help for test1" && die "Test 37 failed" $PYTHON $TEST help test1|grep -q "following commands:" && die "Test 38 failed" $PYTHON $TEST help test1|grep -q "test1, testalias1, testalias2" || die\ "Test 39 failed" $PYTHON $TEST help testalias1|grep -q "[-]-foo" || die\ "Test 40 failed" $PYTHON $TEST help testalias2|grep -q "[-]-foo" || die\ "Test 41 failed" $PYTHON $TEST help test4|grep -q "^ *Help for test4" || die "Test 42 failed" $PYTHON $TEST help testalias3|grep -q "^ *Help for test4" || die\ "Test 43 failed" # Help for cmds with all_command_help. $PYTHON $TEST help|grep -q "test1 *Help for test1" && die "Test 44 failed" $PYTHON $TEST help test1|grep -q "Help for test1" || die "Test 45 failed" $PYTHON $TEST help|grep -q "test4 *Help for test4" && die "Test 44 failed" $PYTHON $TEST help test4|grep -q "Help for test4" || die "Test 45 failed" # Success, --hint before command, foo shown with test1 $PYTHON $TEST --hint 'XYZ' --help|grep -q "following commands:" && die "Test 46 failed" $PYTHON $TEST --hint 'XYZ' --help|grep -q "XYZ" && die "Test 47 failed" $PYTHON $TEST --hint 'XYZ' --help|grep -q "This tool shows how" || die "Test 48 failed" $PYTHON $TEST --hint 'XYZ' help|grep -q "following commands:" || die "Test 49 failed" $PYTHON $TEST --hint 'XYZ' help|grep -q "XYZ" && die "Test 50 failed" $PYTHON $TEST --hint 'XYZ' help|grep -q "This tool shows how" || die "Test 51 failed" # A command name with an letters, numbers, or an underscore is fine $PYTHON -c "${IMPORTS} def test(argv): return 0 def main(argv): appcommands.AddCmdFunc('test', test) appcommands.AddCmdFunc('test_foo', test) appcommands.AddCmdFunc('a123', test) appcommands.Run()" test || die "Test 52 failed" # A command name that starts with a non-alphanumeric characters is not ok $PYTHON -c "${IMPORTS} def test(argv): return 0 def main(argv): appcommands.AddCmdFunc('123', test) appcommands.Run()" 123 >/dev/null 2>&1 && die "Test 53 failed" # A command name that contains other characters is not ok $PYTHON -c "${IMPORTS} def test(argv): return 0 def main(argv): appcommands.AddCmdFunc('test+1', test) appcommands.Run()" "test+1" >/dev/null 2>&1 && die "Test 54 failed" # If a command raises app.UsageError, usage is printed. RES=`$PYTHON -c "${IMPORTS} def test(argv): '''Help1''' raise app.UsageError('Ha-ha') def main(argv): appcommands.AddCmdFunc('test', test) appcommands.Run()" test` && die "Test 55 failed" echo "${RES}" | grep -q "USAGE: " || die "Test 56 failed" echo "${RES}" | grep -q -E "(^| )test[ \t]+Help1($| )" || die "Test 57 failed" echo "${RES}" | grep -q -E "(^| )Ha-ha($| )" || die "Test 58 failed" $PYTHON -c "${IMPORTS} class Test(appcommands.Cmd): def Run(self, argv): return 0 def test(*args, **kwargs): return Test(*args, **kwargs) def main(argv): appcommands.AddCmd('test', test) appcommands.Run()" test || die "Test 62 failed" # Success, default command set and correctly run. RES=`$PYTHON -c "${IMPORTS} class test(appcommands.Cmd): def Run(self, argv): print 'test running correctly' return 0 def main(argv): appcommands.AddCmd('test', test) appcommands.SetDefaultCommand('test') appcommands.Run()"` || die "Test 63 failed" echo "${RES}" | grep -q "test running correctly" || die "Test 64 failed" # Failure, default command set but missing. $PYTHON -c "${IMPORTS} class test(appcommands.Cmd): def Run(self, argv): print 'test running correctly' return 0 def main(argv): appcommands.AddCmd('test', test) appcommands.SetDefaultCommand('missing') appcommands.Run()" >/dev/null 2>&1 && die "Test 65 failed" echo "PASS" google-apputils-0.4.0/tests/humanize_test.py0000640003611500116100000003245512176032264022046 0ustar dborowitzeng00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """Test for google.apputils.humanize.""" import datetime from google.apputils import basetest from google.apputils import humanize class HumanizeTest(basetest.TestCase): def testCommas(self): self.assertEqual('0', humanize.Commas(0)) self.assertEqual('100', humanize.Commas(100)) self.assertEqual('1,000', humanize.Commas(1000)) self.assertEqual('10,000', humanize.Commas(10000)) self.assertEqual('1,000,000', humanize.Commas(1e6)) self.assertEqual('-1,000,000', humanize.Commas(-1e6)) def testPlural(self): self.assertEqual('0 objects', humanize.Plural(0, 'object')) self.assertEqual('1 object', humanize.Plural(1, 'object')) self.assertEqual('-1 objects', humanize.Plural(-1, 'object')) self.assertEqual('42 objects', humanize.Plural(42, 'object')) self.assertEqual('42 cats', humanize.Plural(42, 'cat')) self.assertEqual('42 glasses', humanize.Plural(42, 'glass')) self.assertEqual('42 potatoes', humanize.Plural(42, 'potato')) self.assertEqual('42 cherries', humanize.Plural(42, 'cherry')) self.assertEqual('42 monkeys', humanize.Plural(42, 'monkey')) self.assertEqual('42 oxen', humanize.Plural(42, 'ox', 'oxen')) self.assertEqual('42 indices', humanize.Plural(42, 'index')) self.assertEqual( '42 attorneys general', humanize.Plural(42, 'attorney general', 'attorneys general')) def testPluralWord(self): self.assertEqual('vaxen', humanize.PluralWord(2, 'vax', plural='vaxen')) self.assertEqual('cores', humanize.PluralWord(2, 'core')) self.assertEqual('group', humanize.PluralWord(1, 'group')) self.assertEqual('cells', humanize.PluralWord(0, 'cell')) self.assertEqual('degree', humanize.PluralWord(1.0, 'degree')) self.assertEqual('helloes', humanize.PluralWord(3.14, 'hello')) def testWordSeries(self): self.assertEqual('', humanize.WordSeries([])) self.assertEqual('foo', humanize.WordSeries(['foo'])) self.assertEqual('foo and bar', humanize.WordSeries(['foo', 'bar'])) self.assertEqual( 'foo, bar, and baz', humanize.WordSeries(['foo', 'bar', 'baz'])) self.assertEqual( 'foo, bar, or baz', humanize.WordSeries(['foo', 'bar', 'baz'], conjunction='or')) def testAddIndefiniteArticle(self): self.assertEqual('a thing', humanize.AddIndefiniteArticle('thing')) self.assertEqual('an object', humanize.AddIndefiniteArticle('object')) self.assertEqual('a Porsche', humanize.AddIndefiniteArticle('Porsche')) self.assertEqual('an Audi', humanize.AddIndefiniteArticle('Audi')) def testDecimalPrefix(self): self.assertEqual('0 m', humanize.DecimalPrefix(0, 'm')) self.assertEqual('1 km', humanize.DecimalPrefix(1000, 'm')) self.assertEqual('-1 km', humanize.DecimalPrefix(-1000, 'm')) self.assertEqual('10 Gbps', humanize.DecimalPrefix(10e9, 'bps')) self.assertEqual('6000 Yg', humanize.DecimalPrefix(6e27, 'g')) self.assertEqual('12.1 km', humanize.DecimalPrefix(12100, 'm', precision=3)) self.assertEqual('12 km', humanize.DecimalPrefix(12100, 'm', precision=2)) self.assertEqual('1.15 km', humanize.DecimalPrefix(1150, 'm', precision=3)) self.assertEqual('-1.15 km', humanize.DecimalPrefix(-1150, 'm', precision=3)) self.assertEqual('1.1 s', humanize.DecimalPrefix(1.12, 's', precision=2)) self.assertEqual('-1.1 s', humanize.DecimalPrefix(-1.12, 's', precision=2)) self.assertEqual('nan bps', humanize.DecimalPrefix(float('nan'), 'bps')) self.assertEqual('inf bps', humanize.DecimalPrefix(float('inf'), 'bps')) self.assertEqual('-inf bps', humanize.DecimalPrefix(float('-inf'), 'bps')) self.assertEqual('-4 mm', humanize.DecimalPrefix(-0.004, 'm', min_scale=None)) self.assertEqual('0 m', humanize.DecimalPrefix(0, 'm', min_scale=None)) self.assertEqual( u'1 µs', humanize.DecimalPrefix(0.0000013, 's', min_scale=None)) self.assertEqual('3 km', humanize.DecimalPrefix(3000, 'm', min_scale=None)) self.assertEqual( '5000 TB', humanize.DecimalPrefix(5e15, 'B', max_scale=4)) self.assertEqual( '5 mSWE', humanize.DecimalPrefix(0.005, 'SWE', min_scale=None)) self.assertEqual( '0.0005 ms', humanize.DecimalPrefix(5e-7, 's', min_scale=-1, precision=2)) def testBinaryPrefix(self): self.assertEqual('0 B', humanize.BinaryPrefix(0, 'B')) self.assertEqual('1000 B', humanize.BinaryPrefix(1000, 'B')) self.assertEqual('1 KiB', humanize.BinaryPrefix(1024, 'B')) self.assertEqual('64 GiB', humanize.BinaryPrefix(2**36, 'B')) self.assertEqual('65536 Yibit', humanize.BinaryPrefix(2**96, 'bit')) self.assertEqual('1.25 KiB', humanize.BinaryPrefix(1280, 'B', precision=3)) self.assertEqual('1.2 KiB', humanize.BinaryPrefix(1280, 'B', precision=2)) def testScale(self): self.assertEqual((12.1, 'km'), humanize.DecimalScale(12100, 'm')) self.assertEqual((1.15, 'Mm'), humanize.DecimalScale(1150000, 'm')) self.assertEqual((450, 'mSWE'), humanize.DecimalScale(0.45, 'SWE', min_scale=None)) self.assertEqual( (250, u'µm'), humanize.DecimalScale(1.0 / (4 * 1000), 'm', min_scale=None)) value, unit = humanize.BinaryScale(200000000000, 'B') self.assertAlmostEqual(value, 186.26, 2) self.assertEqual(unit, 'GiB') value, unit = humanize.BinaryScale(3000000000000, 'B') self.assertAlmostEqual(value, 2.728, 3) self.assertEqual(unit, 'TiB') def testPrettyFraction(self): # No rounded integer part self.assertEqual(u'½', humanize.PrettyFraction(0.5)) # Roundeded integer + fraction self.assertEqual(u'6⅔', humanize.PrettyFraction(20.0 / 3.0)) # Rounded integer, no fraction self.assertEqual(u'2', humanize.PrettyFraction(2.00001)) # No rounded integer, no fraction self.assertEqual(u'0', humanize.PrettyFraction(0.001)) # Round up self.assertEqual(u'1', humanize.PrettyFraction(0.99)) # No round up, edge case self.assertEqual(u'⅞', humanize.PrettyFraction(0.9)) # Negative fraction self.assertEqual(u'-⅕', humanize.PrettyFraction(-0.2)) # Negative close to zero (should not be -0) self.assertEqual(u'0', humanize.PrettyFraction(-0.001)) # Smallest fraction that should round down. self.assertEqual(u'0', humanize.PrettyFraction(1.0 / 16.0)) # Largest fraction should round up. self.assertEqual(u'1', humanize.PrettyFraction(15.0 / 16.0)) # Integer zero. self.assertEqual(u'0', humanize.PrettyFraction(0)) # Check that division yields fraction self.assertEqual(u'⅘', humanize.PrettyFraction(4.0 / 5.0)) # Custom spacer. self.assertEqual(u'2 ½', humanize.PrettyFraction(2.5, spacer=' ')) def testDuration(self): self.assertEqual('2h', humanize.Duration(7200)) self.assertEqual('5d 13h 47m 12s', humanize.Duration(481632)) self.assertEqual('0s', humanize.Duration(0)) self.assertEqual('59s', humanize.Duration(59)) self.assertEqual('1m', humanize.Duration(60)) self.assertEqual('1m 1s', humanize.Duration(61)) self.assertEqual('1h 1s', humanize.Duration(3601)) self.assertEqual('2h-2s', humanize.Duration(7202, separator='-')) def testLargeDuration(self): # The maximum seconds and days that can be stored in a datetime.timedelta # object, as seconds. max_days is equal to MAX_DELTA_DAYS in Python's # Modules/datetimemodule.c, converted to seconds. max_seconds = 3600 * 24 - 1 max_days = 999999999 * 24 * 60 * 60 self.assertEqual('999999999d', humanize.Duration(max_days)) self.assertEqual('999999999d 23h 59m 59s', humanize.Duration(max_days + max_seconds)) self.assertEqual('>=999999999d 23h 59m 60s', humanize.Duration(max_days + max_seconds + 1)) def testTimeDelta(self): self.assertEqual('0s', humanize.TimeDelta(datetime.timedelta())) self.assertEqual('2h', humanize.TimeDelta(datetime.timedelta(hours=2))) self.assertEqual('1m', humanize.TimeDelta(datetime.timedelta(minutes=1))) self.assertEqual('5d', humanize.TimeDelta(datetime.timedelta(days=5))) self.assertEqual('1.25s', humanize.TimeDelta( datetime.timedelta(seconds=1, microseconds=250000))) self.assertEqual('1.5s', humanize.TimeDelta(datetime.timedelta(seconds=1.5))) self.assertEqual('4d 10h 5m 12.25s', humanize.TimeDelta( datetime.timedelta(days=4, hours=10, minutes=5, seconds=12, microseconds=250000))) class NaturalSortKeyChunkingTest(basetest.TestCase): def testChunkifySingleChars(self): self.assertListEqual( humanize.NaturalSortKey('a1b2c3'), ['a', 1, 'b', 2, 'c', 3]) def testChunkifyMultiChars(self): self.assertListEqual( humanize.NaturalSortKey('aa11bb22cc33'), ['aa', 11, 'bb', 22, 'cc', 33]) def testChunkifyComplex(self): self.assertListEqual( humanize.NaturalSortKey('one 11 -- two 44'), ['one ', 11, ' -- two ', 44]) class NaturalSortKeysortTest(basetest.TestCase): def testNaturalSortKeySimpleWords(self): self.test = ['pair', 'banana', 'apple'] self.good = ['apple', 'banana', 'pair'] self.test.sort(key=humanize.NaturalSortKey) self.assertListEqual(self.test, self.good) def testNaturalSortKeySimpleNums(self): self.test = ['3333', '2222', '9999', '0000'] self.good = ['0000', '2222', '3333', '9999'] self.test.sort(key=humanize.NaturalSortKey) self.assertListEqual(self.test, self.good) def testNaturalSortKeySimpleDigits(self): self.test = ['8', '3', '2'] self.good = ['2', '3', '8'] self.test.sort(key=humanize.NaturalSortKey) self.assertListEqual(self.test, self.good) def testVersionStrings(self): self.test = ['1.2', '0.9', '1.1a2', '1.1a', '1', '1.2.1', '0.9.1'] self.good = ['0.9', '0.9.1', '1', '1.1a', '1.1a2', '1.2', '1.2.1'] self.test.sort(key=humanize.NaturalSortKey) self.assertListEqual(self.test, self.good) def testNaturalSortKeySimpleNumLong(self): self.test = ['11', '9', '1', '200', '19', '20', '900'] self.good = ['1', '9', '11', '19', '20', '200', '900'] self.test.sort(key=humanize.NaturalSortKey) self.assertListEqual(self.test, self.good) def testNaturalSortKeyAlNum(self): self.test = ['x10', 'x9', 'x1', 'x11'] self.good = ['x1', 'x9', 'x10', 'x11'] self.test.sort(key=humanize.NaturalSortKey) self.assertListEqual(self.test, self.good) def testNaturalSortKeyNumAlNum(self): self.test = ['4x10', '4x9', '4x11', '5yy4', '3x1', '2x11'] self.good = ['2x11', '3x1', '4x9', '4x10', '4x11', '5yy4'] self.test.sort(key=humanize.NaturalSortKey) self.assertListEqual(self.test, self.good) def testNaturalSortKeyAlNumAl(self): self.test = ['a9c', 'a4b', 'a10c', 'a1c', 'c10c', 'c10a', 'c9a'] self.good = ['a1c', 'a4b', 'a9c', 'a10c', 'c9a', 'c10a', 'c10c'] self.test.sort(key=humanize.NaturalSortKey) self.assertListEqual(self.test, self.good) class NaturalSortKeyBigTest(basetest.TestCase): def testBig(self): self.test = [ '1000X Radonius Maximus', '10X Radonius', '200X Radonius', '20X Radonius', '20X Radonius Prime', '30X Radonius', '40X Radonius', 'Allegia 50 Clasteron', 'Allegia 500 Clasteron', 'Allegia 51 Clasteron', 'Allegia 51B Clasteron', 'Allegia 52 Clasteron', 'Allegia 60 Clasteron', 'Alpha 100', 'Alpha 2', 'Alpha 200', 'Alpha 2A', 'Alpha 2A-8000', 'Alpha 2A-900', 'Callisto Morphamax', 'Callisto Morphamax 500', 'Callisto Morphamax 5000', 'Callisto Morphamax 600', 'Callisto Morphamax 700', 'Callisto Morphamax 7000', 'Callisto Morphamax 7000 SE', 'Callisto Morphamax 7000 SE2', 'QRS-60 Intrinsia Machine', 'QRS-60F Intrinsia Machine', 'QRS-62 Intrinsia Machine', 'QRS-62F Intrinsia Machine', 'Xiph Xlater 10000', 'Xiph Xlater 2000', 'Xiph Xlater 300', 'Xiph Xlater 40', 'Xiph Xlater 5', 'Xiph Xlater 50', 'Xiph Xlater 500', 'Xiph Xlater 5000', 'Xiph Xlater 58'] self.good = [ '10X Radonius', '20X Radonius', '20X Radonius Prime', '30X Radonius', '40X Radonius', '200X Radonius', '1000X Radonius Maximus', 'Allegia 50 Clasteron', 'Allegia 51 Clasteron', 'Allegia 51B Clasteron', 'Allegia 52 Clasteron', 'Allegia 60 Clasteron', 'Allegia 500 Clasteron', 'Alpha 2', 'Alpha 2A', 'Alpha 2A-900', 'Alpha 2A-8000', 'Alpha 100', 'Alpha 200', 'Callisto Morphamax', 'Callisto Morphamax 500', 'Callisto Morphamax 600', 'Callisto Morphamax 700', 'Callisto Morphamax 5000', 'Callisto Morphamax 7000', 'Callisto Morphamax 7000 SE', 'Callisto Morphamax 7000 SE2', 'QRS-60 Intrinsia Machine', 'QRS-60F Intrinsia Machine', 'QRS-62 Intrinsia Machine', 'QRS-62F Intrinsia Machine', 'Xiph Xlater 5', 'Xiph Xlater 40', 'Xiph Xlater 50', 'Xiph Xlater 58', 'Xiph Xlater 300', 'Xiph Xlater 500', 'Xiph Xlater 2000', 'Xiph Xlater 5000', 'Xiph Xlater 10000', ] self.test.sort(key=humanize.NaturalSortKey) self.assertListEqual(self.test, self.good) if __name__ == '__main__': basetest.main() google-apputils-0.4.0/tests/basetest_sh_test.sh0000750003611500116100000000364412176032264022514 0ustar dborowitzeng00000000000000#! /bin/bash # Copyright 2003 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Author: Douglas Greiman # Owner: unittest-team@google.com EXE=./basetest_test.py function die { echo "$1" exit $2 } # Create directories for use function MaybeMkdir { for dir in $@; do if [ ! -d "$dir" ] ; then mkdir "$dir" || die "Unable to create $dir" fi done } # TODO(dborowitz): Clean these up if we die. MaybeMkdir abc cba def fed ghi jkl # Test assertListEqual, assertDictEqual, and assertSameElements $EXE --testid=5 || die "Test 5 failed" $? # Test assertAlmostEqual and assertNotAlmostEqual $EXE --testid=6 || die "Test 6 failed" $? # Invoke with no env vars and no flags ( unset TEST_RANDOM_SEED unset TEST_SRCDIR unset TEST_TMPDIR $EXE --testid=1 ) || die "Test 1 failed" $? # Invoke with env vars but no flags ( export TEST_RANDOM_SEED=321 export TEST_SRCDIR=cba export TEST_TMPDIR=fed $EXE --testid=2 ) || die "Test 2 failed" $? # Invoke with no env vars and all flags ( unset TEST_RANDOM_SEED unset TEST_SRCDIR unset TEST_TMPDIR $EXE --testid=3 --test_random_seed=123 --test_srcdir=abc --test_tmpdir=def ) || die "Test 3 failed" $? # Invoke with env vars and all flags ( export TEST_RANDOM_SEED=321 export TEST_SRCDIR=cba export TEST_TMPDIR=fed $EXE --testid=4 --test_random_seed=123 --test_srcdir=abc --test_tmpdir=def ) || die "Test 4 failed" $? # Cleanup rm -r abc cba def fed ghi jkl echo "Pass" google-apputils-0.4.0/tests/app_test.py0000640003611500116100000000177512176032264021007 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2008 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tests for app.py .""" import os import shutil import socket import sys import mox from google.apputils import basetest from google.apputils import app import gflags as flags FLAGS = flags.FLAGS class TestFunctions(basetest.TestCase): def testInstallExceptionHandler(self): self.assertRaises(TypeError, app.InstallExceptionHandler, 1) if __name__ == '__main__': basetest.main() google-apputils-0.4.0/tests/app_unittest.sh0000750003611500116100000001465112176032264021670 0ustar dborowitzeng00000000000000#! /bin/bash # Copyright 2003 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Author: Douglas Greiman PYTHON=$(which python) function die { echo "$1" exit 1 } APP_PACKAGE="google.apputils" # This should exit with error code because no main defined $PYTHON -c "from ${APP_PACKAGE} import app; app.run()" 2>/dev/null && \ die "Test 1 failed" # Standard use. This should exit successfully $PYTHON -c "from ${APP_PACKAGE} import app a = 0 def main(argv): global a a = 1 app.run() assert a == 1" || \ die "Test 2 failed" # app.run called in exec block, script read from -c string. Should succeed. $PYTHON -c "from ${APP_PACKAGE} import app a = 0 s=''' def main(argv): global a a = 1 app.run() ''' exec s assert a == 1" || \ die "Test 4 failed" # app.run called in exec block, script read from file. Should succeed. PYFILE=$TEST_TMPDIR/tmp.py cat >$PYFILE </dev/null || \ die "Test 11 failed" # Test that the usage() function works $PYTHON -c "from ${APP_PACKAGE} import app def main(argv): app.usage() app.run() " 2>&1 | egrep '^ --' >/dev/null || \ die "Test 12 failed" # Test that shorthelp doesn't give flags in this case. $PYTHON -c "from ${APP_PACKAGE} import app def main(argv): app.usage(shorthelp=1) app.run() " 2>&1 | grep '^ --' >/dev/null && \ die "Test 13 failed" # Test writeto_stdout. $PYTHON -c "from ${APP_PACKAGE} import app def main(argv): app.usage(shorthelp=1, writeto_stdout=1) app.run() " | grep 'USAGE' >/dev/null || \ die "Test 14 failed" # Test detailed_error $PYTHON -c "from ${APP_PACKAGE} import app def main(argv): app.usage(shorthelp=1, writeto_stdout=1, detailed_error='BAZBAZ') app.run() " 2>&1 | grep 'BAZBAZ' >/dev/null || \ die "Test 15 failed" # Test exitcode $PYTHON -c "from ${APP_PACKAGE} import app def main(argv): app.usage(writeto_stdout=1, exitcode=1) app.run() " >/dev/null if [ "$?" -ne "1" ]; then die "Test 16 failed" fi # Test --help (this could use wrapping which is tested elsewhere) $PYTHON -c "from ${APP_PACKAGE} import app def main(argv): print 'FAIL' app.run() " 2>&1 --help | grep 'USAGE: -c \[flags\]' >/dev/null || \ die "Test 17 failed" # Test --help does not wrap for __main__.__doc__ $PYTHON -c "from ${APP_PACKAGE} import app import sys def main(argv): print 'FAIL' doc = [] for i in xrange(10): doc.append(str(i)) doc.append('12345678 ') sys.modules['__main__'].__doc__ = ''.join(doc) app.run() " 2>&1 --help | grep '712345678 812345678' >/dev/null || \ die "Test 18 failed" # Test --help with forced wrap for __main__.__doc__ $PYTHON -c "from ${APP_PACKAGE} import app import sys def main(argv): print 'FAIL' doc = [] for i in xrange(10): doc.append(str(i)) doc.append('12345678 ') sys.modules['__main__'].__doc__ = ''.join(doc) app.SetEnableHelpWrapping() app.run() " 2>&1 --help | grep '712345678 812345678' >/dev/null && \ die "Test 19 failed" # Test UsageError $PYTHON -c "from ${APP_PACKAGE} import app def main(argv): raise app.UsageError('You made a usage error') app.run() " 2>&1 | grep "You made a usage error" >/dev/null || \ die "Test 20 failed" # Test UsageError exit code $PYTHON -c "from ${APP_PACKAGE} import app def main(argv): raise app.UsageError('You made a usage error', exitcode=64) app.run() " > /dev/null 2>&1 if [ "$?" -ne "64" ]; then die "Test 21 failed" fi # Test catching top-level exceptions. We should get the exception name on # stderr. ./app_test_helper.py \ --raise_exception 2>&1 | grep -q 'MyException' || die "Test 23 failed" # Test exception handlers are called have_handler_output=$TEST_TMPDIR/handler.txt $PYTHON -c "from ${APP_PACKAGE} import app def main(argv): raise ValueError('look for me') class TestExceptionHandler(app.ExceptionHandler): def __init__(self, msg): self.msg = msg def Handle(self, exc): print '%s %s' % (self.msg, exc) app.InstallExceptionHandler(TestExceptionHandler('first')) app.InstallExceptionHandler(TestExceptionHandler('second')) app.run() " > $have_handler_output 2>&1 grep -q "first look for me" $have_handler_output || die "Test 24 failed" grep -q "second look for me" $have_handler_output || die "Test 25 failed" no_handler_output=$TEST_TMPDIR/no_handler.txt # Test exception handlers are not called for "normal" exits for exc in "SystemExit(1)" "app.UsageError('foo')"; do $PYTHON -c "from ${APP_PACKAGE} import app def main(argv): raise $exc class TestExceptionHandler(app.ExceptionHandler): def Handle(self, exc): print 'handler was called' app.InstallExceptionHandler(TestExceptionHandler()) app.run() " > $no_handler_output 2>&1 grep -q "handler was called" $no_handler_output && die "Test 26 ($exc) failed" done # Test --help expands docstring. $PYTHON -c " '''USAGE: %s [flags]''' from ${APP_PACKAGE} import app def main(argv): print 'FAIL' app.run() " --help 2>&1 | fgrep 'USAGE: -c [flags]' >/dev/null || die "Test 27 failed" # Test --help expands docstring. $PYTHON -c " '''USAGE: %s --fmt=\"%%s\" --fraction=50%%''' from ${APP_PACKAGE} import app def main(argv): print 'FAIL' app.run() " --help 2>&1 | fgrep 'USAGE: -c --fmt="%s" --fraction=50%' >/dev/null || die "Test 28 failed" # Test --help expands docstring. $PYTHON -c " '''>%s|%%s|%%%s|%%%%s|%%%%%s<''' from ${APP_PACKAGE} import app def main(argv): print 'FAIL' app.run() " --help 2>&1 | fgrep '>-c|%s|%-c|%%s|%%-c<' >/dev/null || die "Test 29 failed" # Test bad docstring. $PYTHON -c " '''>%@<''' from ${APP_PACKAGE} import app def main(argv): print 'FAIL' app.run() " --help 2>&1 | fgrep '>%@<' >/dev/null || die "Test 30 failed" readonly HELP_PROG=" from ${APP_PACKAGE} import app def main(argv): print 'HI' app.run() " echo "PASS" google-apputils-0.4.0/tests/sh_test.py0000640003611500116100000000237212176032264020633 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2010 Google Inc. All Rights Reserved. """Tests for google.apputils. In addition to the test modules under this package, we have a special TestCase that runs the tests that are shell scripts. """ # TODO(dborowitz): It may be useful to generalize this and provide it to users # who want to run their own sh_tests. import os import subprocess import sys from google.apputils import basetest import gflags FLAGS = gflags.FLAGS class ShellScriptTests(basetest.TestCase): """TestCase that runs the various *test.sh scripts.""" def RunTestScript(self, script_name): tests_path = os.path.dirname(__file__) sh_test_path = os.path.realpath(os.path.join(tests_path, script_name)) env = { # Setuptools puts dependency eggs in our path, so propagate that. 'PYTHONPATH': os.pathsep.join(sys.path), 'TEST_TMPDIR': FLAGS.test_tmpdir, } p = subprocess.Popen(sh_test_path, cwd=tests_path, env=env) self.assertEqual(0, p.wait()) def testBaseTest(self): self.RunTestScript('basetest_sh_test.sh') def testApp(self): self.RunTestScript('app_unittest.sh') def testAppCommands(self): self.RunTestScript('appcommands_unittest.sh') if __name__ == '__main__': basetest.main() google-apputils-0.4.0/tests/appcommands_example.py0000750003611500116100000000663712176032264023211 0ustar dborowitzeng00000000000000#!/usr/bin/env python # # Copyright 2007 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Test tool to demonstrate appcommands.py usage. This tool shows how to use appcommands.py. """ from google.apputils import appcommands import gflags as flags FLAGS = flags.FLAGS flags.DEFINE_string('hint', '', 'Global hint to show in commands') # Name taken from app.py class Test1(appcommands.Cmd): """Help for test1.""" def __init__(self, name, flag_values, **kargs): """Init and register flags specific to command.""" appcommands.Cmd.__init__(self, name, flag_values=flag_values, **kargs) # self._all_commands_help allows you to define a different message to be # displayed when all commands are displayed vs. the single command. self._all_commands_help = '' # Flag --fail1 is specific to this command flags.DEFINE_boolean('fail1', False, 'Make test1 fail', flag_values=flag_values) flags.DEFINE_string('foo', '', 'Param foo', flag_values=flag_values) flags.DEFINE_string('bar', '', 'Param bar', flag_values=flag_values) flags.DEFINE_integer('intfoo', 0, 'Integer foo', flag_values=flag_values) def Run(self, argv): """Output 'Command1' and flag info. Args: argv: Remaining command line arguments after parsing flags and command Returns: Value of flag fail1 """ print 'Command1' if FLAGS.hint: print "Hint1:'%s'" % FLAGS.hint print "Foo1:'%s'" % FLAGS.foo print "Bar1:'%s'" % FLAGS.bar return FLAGS.fail1 * 1 class Test2(appcommands.Cmd): """Help for test2.""" def __init__(self, name, flag_values): """Init and register flags specific to command.""" appcommands.Cmd.__init__(self, name, flag_values) flags.DEFINE_boolean('fail2', False, 'Make test2 fail', flag_values=flag_values) flags.DEFINE_string('foo', '', 'Param foo', flag_values=flag_values) flags.DEFINE_string('bar', '', 'Param bar', flag_values=flag_values) def Run(self, argv): """Output 'Command2' and flag info. Args: argv: Remaining command line arguments after parsing flags and command Returns: Value of flag fail2 """ print 'Command2' if FLAGS.hint: print "Hint2:'%s'" % FLAGS.hint print "Foo2:'%s'" % FLAGS.foo print "Bar2:'%s'" % FLAGS.bar return FLAGS.fail2 * 1 def Test3(unused_argv): """Help for test3.""" print 'Command3' def Test4(unused_argv): """Help for test4.""" print 'Command4' def main(unused_argv): """Register the commands.""" appcommands.AddCmd('test1', Test1, command_aliases=['testalias1', 'testalias2']) appcommands.AddCmd('test2', Test2) appcommands.AddCmdFunc('test3', Test3) appcommands.AddCmdFunc('test4', Test4, command_aliases=['testalias3'], all_commands_help='') if __name__ == '__main__': appcommands.Run() google-apputils-0.4.0/tests/app_test_helper.py0000750003611500116100000000172512176032264022343 0ustar dborowitzeng00000000000000#!/usr/bin/env python # # Copyright 2005 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Helper script used by app_unittest.sh""" import sys import gflags as flags from google.apputils import app FLAGS = flags.FLAGS flags.DEFINE_boolean("raise_exception", False, "throw MyException from main") class MyException(Exception): pass def main(args): if FLAGS.raise_exception: raise MyException if __name__ == '__main__': app.run() google-apputils-0.4.0/setup.py0000640003611500116100000000541512176032264017161 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2010 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys import ez_setup ez_setup.use_setuptools() from setuptools import setup, find_packages from setuptools.command import test REQUIRE = [ "python-dateutil>=1.4,<2", "python-gflags>=1.4", "pytz>=2010", ] TEST_REQUIRE = ["mox>=0.5"] if sys.version_info[:2] < (2, 7): # unittest2 is a backport of Python 2.7's unittest. TEST_REQUIRE.append("unittest2>=0.5.1") # Mild hackery to get around the fact that we want to include a # GoogleTest as one of the cmdclasses for our package, but we # can't reference it until our package is installed. We simply # make a wrapper class that actually creates objects of the # appropriate class at runtime. class GoogleTestWrapper(test.test, object): test_dir = None def __new__(cls, *args, **kwds): from google.apputils import setup_command dist = setup_command.GoogleTest(*args, **kwds) dist.test_dir = GoogleTestWrapper.test_dir return dist setup( name="google-apputils", version="0.4.0", packages=find_packages(exclude=["tests"]), namespace_packages=["google"], entry_points={ "distutils.commands": [ "google_test = google.apputils.setup_command:GoogleTest", ], "distutils.setup_keywords": [ ("google_test_dir = google.apputils.setup_command" ":ValidateGoogleTestDir"), ], }, install_requires=REQUIRE, tests_require=REQUIRE + TEST_REQUIRE, # The entry_points above allow other projects to understand the # google_test command and test_dir option by specifying # setup_requires("google-apputils"). However, those entry_points only get # registered when this project is installed, and we need to run Google-style # tests for this project before it is installed. So we need to manually set # up the command and option mappings, for this project only, and we use # a wrapper class that exists before the install happens. cmdclass={"google_test": GoogleTestWrapper}, command_options={"google_test": {"test_dir": ("setup.py", "tests")}}, author="Google Inc.", author_email="opensource@google.com", url="http://code.google.com/p/google-apputils-python", ) google-apputils-0.4.0/LICENSE0000640003611500116100000002613612176032264016457 0ustar dborowitzeng00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. google-apputils-0.4.0/PKG-INFO0000640003611500116100000000036712176034015016541 0ustar dborowitzeng00000000000000Metadata-Version: 1.0 Name: google-apputils Version: 0.4.0 Summary: UNKNOWN Home-page: http://code.google.com/p/google-apputils-python Author: Google Inc. Author-email: opensource@google.com License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN google-apputils-0.4.0/google/0000750003611500116100000000000012176034015016711 5ustar dborowitzeng00000000000000google-apputils-0.4.0/google/__init__.py0000640003611500116100000000030112176032264021021 0ustar dborowitzeng00000000000000#!/usr/bin/env python try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: from pkgutil import extend_path __path__ = extend_path(__path__, __name__) google-apputils-0.4.0/google/apputils/0000750003611500116100000000000012176034015020552 5ustar dborowitzeng00000000000000google-apputils-0.4.0/google/apputils/run_script_module.py0000640003611500116100000001453512176032264024676 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2010 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Script for running Google-style applications. Unlike normal scripts run through setuptools console_script entry points, Google-style applications must be run as top-level scripts. Given an already-imported module, users can use the RunScriptModule function to set up the appropriate executable environment to spawn a new Python process to run the module as a script. To use this technique in your project, first create a module called e.g. stubs.py with contents like: from google.apputils import run_script_module def RunMyScript(): import my.script run_script_module.RunScriptModule(my.script) def RunMyOtherScript(): import my.other_script run_script_module.RunScriptModule(my.other_script) Then, set up entry points in your setup.py that point to the functions in your stubs module: setup( ... entry_points = { 'console_scripts': [ 'my_script = my.stubs:RunMyScript', 'my_other_script = my.stubs.RunMyOtherScript', ], }, ) When your project is installed, setuptools will generate minimal wrapper scripts to call your stub functions, which in turn execv your script modules. That's it! """ __author__ = 'dborowitz@google.com (Dave Borowitz)' import os import re import sys def FindEnv(progname): """Find the program in the system path. Args: progname: The name of the program. Returns: The full pathname of the program. Raises: AssertionError: if the program was not found. """ for path in os.environ['PATH'].split(':'): fullname = os.path.join(path, progname) if os.access(fullname, os.X_OK): return fullname raise AssertionError( "Could not find an executable named '%s' in the system path" % progname) def GetPdbArgs(python): """Try to get the path to pdb.py and return it in a list. Args: python: The full path to a Python executable. Returns: A list of strings. If a relevant pdb.py was found, this will be ['/path/to/pdb.py']; if not, return ['-m', 'pdb'] and hope for the best. (This latter technique will fail for Python 2.2.) """ # Usually, python is /usr/bin/pythonxx and pdb is /usr/lib/pythonxx/pdb.py components = python.split('/') if len(components) >= 2: pdb_path = '/'.join(components[0:-2] + ['lib'] + components[-1:] + ['pdb.py']) if os.access(pdb_path, os.R_OK): return [pdb_path] # No pdb module found in the python path, default to -m pdb return ['-m', 'pdb'] def StripDelimiters(s, beg, end): if s[0] == beg: assert s[-1] == end return (s[1:-1], True) else: return (s, False) def StripQuotes(s): (s, stripped) = StripDelimiters(s, '"', '"') if not stripped: (s, stripped) = StripDelimiters(s, "'", "'") return s def PrintOurUsage(): """Print usage for the stub script.""" print 'Stub script %s (auto-generated). Options:' % sys.argv[0] print ('--helpstub ' 'Show help for stub script.') print ('--debug_binary ' 'Run python under debugger specified by --debugger.') print ('--debugger= ' "Debugger for --debug_binary. Default: 'gdb --args'.") print ('--debug_script ' 'Run wrapped script with python debugger module (pdb).') print ('--show_command_and_exit ' 'Print command which would be executed and exit.') print ('These options must appear first in the command line, all others will ' 'be passed to the wrapped script.') def RunScriptModule(module): """Run a module as a script. Locates the module's file and runs it in the current interpreter, or optionally a debugger. Args: module: The module object to run. """ args = sys.argv[1:] debug_binary = False debugger = 'gdb --args' debug_script = False show_command_and_exit = False while args: if args[0] == '--helpstub': PrintOurUsage() sys.exit(0) if args[0] == '--debug_binary': debug_binary = True args = args[1:] continue if args[0] == '--debug_script': debug_script = True args = args[1:] continue if args[0] == '--show_command_and_exit': show_command_and_exit = True args = args[1:] continue matchobj = re.match('--debugger=(.+)', args[0]) if matchobj is not None: debugger = StripQuotes(matchobj.group(1)) args = args[1:] continue break # Now look for my main python source file # TODO(dborowitz): This will fail if the module was zipimported, which means # no egg depending on this script runner can be zip_safe. main_filename = module.__file__ assert os.path.exists(main_filename), ('Cannot exec() %r: file not found.' % main_filename) assert os.access(main_filename, os.R_OK), ('Cannot exec() %r: file not' ' readable.' % main_filename) args = [main_filename] + args if debug_binary: debugger_args = debugger.split() program = debugger_args[0] # If pathname is not absolute, determine full path using PATH if not os.path.isabs(program): program = FindEnv(program) python_path = sys.executable command_vec = [python_path] if debug_script: command_vec.extend(GetPdbArgs(python_path)) args = [program] + debugger_args[1:] + command_vec + args elif debug_script: args = [sys.executable] + GetPdbArgs(program) + args else: program = sys.executable args = [sys.executable] + args if show_command_and_exit: print 'program: "%s"' % program print 'args:', args sys.exit(0) try: sys.stdout.flush() os.execv(program, args) except EnvironmentError as e: if not getattr(e, 'filename', None): e.filename = program # Add info to error message raise google-apputils-0.4.0/google/apputils/shellutil.py0000640003611500116100000000235212176032264023140 0ustar dborowitzeng00000000000000#!/usr/bin/env python # This code must be source compatible with Python 2.4 through 3.3. # # Copyright 2003 Google Inc. All Rights Reserved. """Utility functions for dealing with command interpreters.""" import os # Running windows? win32 = (os.name == 'nt') def ShellEscapeList(words): """Turn a list of words into a shell-safe string. Args: words: A list of words, e.g. for a command. Returns: A string of shell-quoted and space-separated words. """ if win32: return ' '.join(words) s = '' for word in words: # Single quote word, and replace each ' in word with '"'"' s += "'" + word.replace("'", "'\"'\"'") + "' " return s[:-1] def ShellifyStatus(status): """Translate from a wait() exit status to a command shell exit status.""" if not win32: if os.WIFEXITED(status): # decode and return exit status status = os.WEXITSTATUS(status) else: # On Unix, the wait() produces a 16 bit return code. Unix shells # lossily compress this to an 8 bit value, using the formula below. # Shell status code < 128 means the process exited normally, status # code >= 128 means the process died because of a signal. status = 128 + os.WTERMSIG(status) return status google-apputils-0.4.0/google/apputils/__init__.py0000640003611500116100000000030112176032264022662 0ustar dborowitzeng00000000000000#!/usr/bin/env python try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: from pkgutil import extend_path __path__ = extend_path(__path__, __name__) google-apputils-0.4.0/google/apputils/file_util.py0000640003611500116100000001527112176032264023113 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2007 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Simple file system utilities.""" __author__ = ('elaforge@google.com (Evan LaForge)', 'matthewb@google.com (Matthew Blecker)') import contextlib import errno import os import pwd import shutil import stat import tempfile class PasswdError(Exception): """Exception class for errors loading a password from a file.""" def ListDirPath(dir_name): """Like os.listdir with prepended dir_name, which is often more convenient.""" return [os.path.join(dir_name, fn) for fn in os.listdir(dir_name)] def Read(filename): """Read entire contents of file with name 'filename'.""" with open(filename) as fp: return fp.read() def Write(filename, contents, overwrite_existing=True, mode=0666, gid=None): """Create a file 'filename' with 'contents', with the mode given in 'mode'. The 'mode' is modified by the umask, as in open(2). If 'overwrite_existing' is False, the file will be opened in O_EXCL mode. An optional gid can be specified. Args: filename: str; the name of the file contents: str; the data to write to the file overwrite_existing: bool; whether or not to allow the write if the file already exists mode: int; permissions with which to create the file (default is 0666 octal) gid: int; group id with which to create the file """ flags = os.O_WRONLY | os.O_TRUNC | os.O_CREAT if not overwrite_existing: flags |= os.O_EXCL fd = os.open(filename, flags, mode) try: os.write(fd, contents) finally: os.close(fd) if gid is not None: os.chown(filename, -1, gid) def AtomicWrite(filename, contents, mode=0666, gid=None): """Create a file 'filename' with 'contents' atomically. As in Write, 'mode' is modified by the umask. This creates and moves a temporary file, and errors doing the above will be propagated normally, though it will try to clean up the temporary file in that case. This is very similar to the prodlib function with the same name. An optional gid can be specified. Args: filename: str; the name of the file contents: str; the data to write to the file mode: int; permissions with which to create the file (default is 0666 octal) gid: int; group id with which to create the file """ fd, tmp_filename = tempfile.mkstemp(dir=os.path.dirname(filename)) try: os.write(fd, contents) finally: os.close(fd) try: os.chmod(tmp_filename, mode) if gid is not None: os.chown(tmp_filename, -1, gid) os.rename(tmp_filename, filename) except OSError, exc: try: os.remove(tmp_filename) except OSError, e: exc = OSError('%s. Additional errors cleaning up: %s' % (exc, e)) raise exc @contextlib.contextmanager def TemporaryFileWithContents(contents, **kw): """A contextmanager that writes out a string to a file on disk. This is useful whenever you need to call a function or command that expects a file on disk with some contents that you have in memory. The context manager abstracts the writing, flushing, and deletion of the temporary file. This is a common idiom that boils down to a single with statement. Note: if you need a temporary file-like object for calling an internal function, you should use a StringIO as a file-like object and not this. Temporary files should be avoided unless you need a file name or contents in a file on disk to be read by some other function or program. Args: contents: a string with the contents to write to the file. **kw: Optional arguments passed on to tempfile.NamedTemporaryFile. Yields: The temporary file object, opened in 'w' mode. """ temporary_file = tempfile.NamedTemporaryFile(**kw) temporary_file.write(contents) temporary_file.flush() yield temporary_file temporary_file.close() def MkDirs(directory, force_mode=None): """Makes a directory including its parent directories. This function is equivalent to os.makedirs() but it avoids a race condition that os.makedirs() has. The race is between os.mkdir() and os.path.exists() which fail with errors when run in parallel. Args: directory: str; the directory to make force_mode: optional octal, chmod dir to get rid of umask interaction Raises: Whatever os.mkdir() raises when it fails for any reason EXCLUDING "dir already exists". If a directory already exists, it does not raise anything. This behaviour is different than os.makedirs() """ name = os.path.normpath(directory) dirs = name.split(os.path.sep) for i in range(0, len(dirs)): path = os.path.sep.join(dirs[:i+1]) try: if path: os.mkdir(path) # only chmod if we created if force_mode is not None: os.chmod(path, force_mode) except OSError, exc: if not (exc.errno == errno.EEXIST and os.path.isdir(path)): raise def RmDirs(dir_name): """Removes dir_name and every non-empty directory in dir_name. Unlike os.removedirs and shutil.rmtree, this function doesn't raise an error if the directory does not exist. Args: dir_name: Directory to be removed. """ try: shutil.rmtree(dir_name) except OSError, err: if err.errno != errno.ENOENT: raise try: parent_directory = os.path.dirname(dir_name) while parent_directory: try: os.rmdir(parent_directory) except OSError, err: if err.errno != errno.ENOENT: raise parent_directory = os.path.dirname(parent_directory) except OSError, err: if err.errno not in (errno.EACCES, errno.ENOTEMPTY): raise def HomeDir(user=None): """Find the home directory of a user. Args: user: int, str, or None - the uid or login of the user to query for, or None (the default) to query for the current process' effective user Returns: str - the user's home directory Raises: TypeError: if user is not int, str, or None. """ if user is None: pw_struct = pwd.getpwuid(os.geteuid()) elif isinstance(user, int): pw_struct = pwd.getpwuid(user) elif isinstance(user, str): pw_struct = pwd.getpwnam(user) else: raise TypeError('user must be None or an instance of int or str') return pw_struct.pw_dir google-apputils-0.4.0/google/apputils/humanize.py0000640003611500116100000003167012176032264022760 0ustar dborowitzeng00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright 2008 Google Inc. All Rights Reserved. """Lightweight routines for producing more friendly output. Usage examples: 'New messages: %s' % humanize.Commas(star_count) -> 'New messages: 58,192' 'Found %s.' % humanize.Plural(error_count, 'error') -> 'Found 2 errors.' 'Found %s.' % humanize.Plural(error_count, 'ox', 'oxen') -> 'Found 2 oxen.' 'Copied at %s.' % humanize.DecimalPrefix(rate, 'bps', precision=3) -> 'Copied at 42.6 Mbps.' 'Free RAM: %s' % humanize.BinaryPrefix(bytes_free, 'B') -> 'Free RAM: 742 MiB' 'Finished all tasks in %s.' % humanize.Duration(elapsed_time) -> 'Finished all tasks in 34m 5s.' These libraries are not a substitute for full localization. If you need localization, then you will have to think about translating strings, formatting numbers in different ways, and so on. Use ICU if your application is user-facing. Use these libraries if your application is an English-only internal tool, and you are tired of seeing "1 results" or "3450134804 bytes used". Compare humanize.*Prefix() to C++ utilites HumanReadableNumBytes and HumanReadableInt in strings/human_readable.h. """ import datetime import math import re SIBILANT_ENDINGS = frozenset(['sh', 'ss', 'tch', 'ax', 'ix', 'ex']) DIGIT_SPLITTER = re.compile(r'\d+|\D+').findall # These are included because they are common technical terms. SPECIAL_PLURALS = { 'index': 'indices', 'matrix': 'matrices', 'vertex': 'vertices', } VOWELS = frozenset('AEIOUaeiou') # In Python 2.6, int(float('nan')) intentionally raises a TypeError. This code # attempts to futureproof us against that eventual upgrade. try: _IsNan = math.isnan except AttributeError: def _IsNan(x): return type(x) is float and x != x def Commas(value): """Formats an integer with thousands-separating commas. Args: value: An integer. Returns: A string. """ if value < 0: sign = '-' value = -value else: sign = '' result = [] while value >= 1000: result.append('%03d' % (value % 1000)) value /= 1000 result.append('%d' % value) return sign + ','.join(reversed(result)) def Plural(quantity, singular, plural=None): """Formats an integer and a string into a single pluralized string. Args: quantity: An integer. singular: A string, the singular form of a noun. plural: A string, the plural form. If not specified, then simple English rules of regular pluralization will be used. Returns: A string. """ return '%d %s' % (quantity, PluralWord(quantity, singular, plural)) def PluralWord(quantity, singular, plural=None): """Builds the plural of an English word. Args: quantity: An integer. singular: A string, the singular form of a noun. plural: A string, the plural form. If not specified, then simple English rules of regular pluralization will be used. Returns: the plural form of the word. """ if quantity == 1: return singular if plural: return plural if singular in SPECIAL_PLURALS: return SPECIAL_PLURALS[singular] # We need to guess what the English plural might be. Keep this # function simple! It doesn't need to know about every possiblity; # only regular rules and the most common special cases. # # Reference: http://en.wikipedia.org/wiki/English_plural for ending in SIBILANT_ENDINGS: if singular.endswith(ending): return '%ses' % singular if singular.endswith('o') and singular[-2:-1] not in VOWELS: return '%ses' % singular if singular.endswith('y') and singular[-2:-1] not in VOWELS: return '%sies' % singular[:-1] return '%ss' % singular def WordSeries(words, conjunction='and'): """Convert a list of words to an English-language word series. Args: words: A list of word strings. conjunction: A coordinating conjunction. Returns: A single string containing all the words in the list separated by commas, the coordinating conjunction, and a serial comma, as appropriate. """ num_words = len(words) if num_words == 0: return '' elif num_words == 1: return words[0] elif num_words == 2: return (' %s ' % conjunction).join(words) else: return '%s, %s %s' % (', '.join(words[:-1]), conjunction, words[-1]) def AddIndefiniteArticle(noun): """Formats a noun with an appropriate indefinite article. Args: noun: A string representing a noun. Returns: A string containing noun prefixed with an indefinite article, e.g., "a thing" or "an object". """ if not noun: raise ValueError('argument must be a word: {!r}'.format(noun)) if noun[0] in VOWELS: return 'an ' + noun else: return 'a ' + noun def DecimalPrefix(quantity, unit, precision=1, min_scale=0, max_scale=None): """Formats an integer and a unit into a string, using decimal prefixes. The unit will be prefixed with an appropriate multiplier such that the formatted integer is less than 1,000 (as long as the raw integer is less than 10**27). For example: DecimalPrefix(576012, 'bps') -> '576 kbps' DecimalPrefix(1574215, 'bps', 2) -> '1.6 Mbps' Only the SI prefixes which are powers of 10**3 will be used, so DecimalPrefix(100, 'thread') is '100 thread', not '1 hthread'. See also: BinaryPrefix() Args: quantity: A number. unit: A string. precision: An integer, the minimum number of digits to display. min_scale: minimum power of 1000 to scale to, (None = unbounded). max_scale: maximum power of 1000 to scale to, (None = unbounded). Returns: A string. """ return _Prefix(quantity, unit, precision, DecimalScale, min_scale=min_scale, max_scale=max_scale) def BinaryPrefix(quantity, unit, precision=1): """Formats an integer and a unit into a string, using binary prefixes. The unit will be prefixed with an appropriate multiplier such that the formatted integer is less than 1,024 (as long as the raw integer is less than 2**90). For example: BinaryPrefix(576012, 'B') -> '562 KiB' See also: DecimalPrefix() Args: quantity: A number. unit: A string. precision: An integer, the minimum number of digits to display. Returns: A string. """ return _Prefix(quantity, unit, precision, BinaryScale) def _Prefix(quantity, unit, precision, scale_callable, **args): """Formats an integer and a unit into a string. Args: quantity: A number. unit: A string. precision: An integer, the minimum number of digits to display. scale_callable: A callable, scales the number and units. **args: named arguments passed to scale_callable. Returns: A string. """ if not quantity: return '0 %s' % unit if quantity in [float('inf'), float('-inf')] or _IsNan(quantity): return '%f %s' % (quantity, unit) scaled_quantity, scaled_unit = scale_callable(quantity, unit, **args) print_pattern = '%%.%df %%s' % max(0, (precision - int( math.log(abs(scaled_quantity), 10)) - 1)) return print_pattern % (scaled_quantity, scaled_unit) def DecimalScale(quantity, unit, min_scale=0, max_scale=None): """Get the scaled value and decimal prefixed unit in a tupple. DecimalScale(576012, 'bps') -> (576.012, 'kbps') DecimalScale(1574215, 'bps') -> (1.574215, 'Mbps') Args: quantity: A number. unit: A string. min_scale: minimum power of 1000 to normalize to (None = unbounded) max_scale: maximum power of 1000 to normalize to (None = unbounded) Returns: A tuple of a scaled quantity (float) and BinaryPrefix for the units (string). """ if min_scale is None or min_scale < 0: negative_powers = ('m', u'µ', 'n', 'p', 'f', 'a', 'z', 'y') if min_scale is not None: negative_powers = tuple(negative_powers[0:-min_scale]) else: negative_powers = None if max_scale is None or max_scale > 0: powers = ('k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') if max_scale is not None: powers = tuple(powers[0:max_scale]) return _Scale(quantity, unit, 1000, powers, negative_powers) def BinaryScale(quantity, unit): """Get the scaled value and binary prefixed unit in a tupple. BinaryPrefix(576012, 'B') -> (562.51171875, 'KiB') Args: quantity: A number. unit: A string. Returns: A tuple of a scaled quantity (float) and BinaryPrefix for the units (string). """ return _Scale(quantity, unit, 1024, ('Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')) def _Scale(quantity, unit, multiplier, prefixes, inverse_prefixes=None): """Returns the formatted quantity and unit into a tuple. Args: quantity: A number. unit: A string multiplier: An integer, the ratio between prefixes. prefixes: A sequence of strings. inverse_prefixes: Prefixes to use for negative powers, If empty or None, no scaling is done for fractions. Returns: A tuple containing the raw scaled quantity and the prefixed unit. """ if not quantity: return 0, unit if quantity in [float('inf'), float('-inf')] or _IsNan(quantity): return quantity, unit power = int(math.log(abs(quantity), multiplier)) if abs(quantity) > 1 or not inverse_prefixes: if power < 1: return quantity, unit power = min(power, len(prefixes)) prefix = prefixes[power - 1] value = float(quantity) / multiplier ** power else: power = min(-power + 1, len(inverse_prefixes)) prefix = inverse_prefixes[power - 1] value = quantity * multiplier ** power return value, prefix + unit # Contains the fractions where the full range [1/n ... (n - 1) / n] # is defined in Unicode. FRACTIONS = { 3: (None, u'⅓', u'⅔', None), 5: (None, u'⅕', u'⅖', u'⅗', u'⅘', None), 8: (None, u'⅛', u'¼', u'⅜', u'½', u'⅝', u'¾', u'⅞', None), } FRACTION_ROUND_DOWN = 1.0 / (max(FRACTIONS.keys()) * 2.0) FRACTION_ROUND_UP = 1.0 - FRACTION_ROUND_DOWN def PrettyFraction(number, spacer=''): """Convert a number into a string that might include a unicode fraction. This method returns the integer representation followed by the closest fraction of a denominator 2, 3, 4, 5 or 8. For instance, 0.33 will be converted to 1/3. The resulting representation should be less than 1/16 off. Args: number: a python number spacer: an optional string to insert between the integer and the fraction default is an empty string. Returns: a unicode string representing the number. """ # We do not want small negative numbers to display as -0. if number < -FRACTION_ROUND_DOWN: return u'-%s' % PrettyFraction(-number) number = abs(number) rounded = int(number) fract = number - rounded if fract >= FRACTION_ROUND_UP: return str(rounded + 1) errors_fractions = [] for denominator, fraction_elements in FRACTIONS.items(): numerator = int(round(denominator * fract)) error = abs(fract - (float(numerator) / float(denominator))) errors_fractions.append((error, fraction_elements[numerator])) unused_error, fraction_text = min(errors_fractions) if rounded and fraction_text: return u'%d%s%s' % (rounded, spacer, fraction_text) if rounded: return str(rounded) if fraction_text: return fraction_text return u'0' def Duration(duration, separator=' '): """Formats a nonnegative number of seconds into a human-readable string. Args: duration: A float duration in seconds. separator: A string separator between days, hours, minutes and seconds. Returns: Formatted string like '5d 12h 30m 45s'. """ try: delta = datetime.timedelta(seconds=duration) except OverflowError: return '>=' + TimeDelta(datetime.timedelta.max) return TimeDelta(delta, separator=separator) def TimeDelta(delta, separator=' '): """Format a datetime.timedelta into a human-readable string. Args: delta: The datetime.timedelta to format. separator: A string separator between days, hours, minutes and seconds. Returns: Formatted string like '5d 12h 30m 45s'. """ parts = [] seconds = delta.seconds if delta.days: parts.append('%dd' % delta.days) if seconds >= 3600: parts.append('%dh' % (seconds // 3600)) seconds %= 3600 if seconds >= 60: parts.append('%dm' % (seconds // 60)) seconds %= 60 seconds += delta.microseconds / 1e6 if seconds or not parts: parts.append('%gs' % seconds) return separator.join(parts) def NaturalSortKey(data): """Key function for "natural sort" ordering. This key function results in a lexigraph sort. For example: - ['1, '3', '20'] (not ['1', '20', '3']). - ['Model 9', 'Model 70 SE', 'Model 70 SE2'] (not ['Model 70 SE', 'Model 70 SE2', 'Model 9']). Usage: new_list = sorted(old_list, key=humanize.NaturalSortKey) or list_sort_in_place.sort(key=humanize.NaturalSortKey) Based on code by Steven Bazyl . Args: data: str, The key being compared in a sort. Returns: A list which is comparable to other lists for the purpose of sorting. """ segments = DIGIT_SPLITTER(data) for i, value in enumerate(segments): if value.isdigit(): segments[i] = int(value) return segments google-apputils-0.4.0/google/apputils/basetest.py0000750003611500116100000016017612176032264022760 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2010 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Base functionality for google tests. This module contains base classes and high-level functions for Google-style tests. """ __author__ = 'dborowitz@google.com (Dave Borowitz)' import collections import difflib import getpass import itertools import json import os import re import subprocess import sys import tempfile import types import urlparse try: import faulthandler except ImportError: # //testing/pybase:pybase can't have deps on any extension modules as it # is used by code that is executed in such a way it cannot import them. :( # We use faulthandler if it is available (either via a user declared dep # or from the Python 3.3+ standard library). faulthandler = None # unittest2 is a backport of Python 2.7's unittest for Python 2.6, so # we don't need it if we are running 2.7 or newer. if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest from google.apputils import app import gflags as flags from google.apputils import shellutil FLAGS = flags.FLAGS # ---------------------------------------------------------------------- # Internal functions to extract default flag values from environment. # ---------------------------------------------------------------------- def _GetDefaultTestRandomSeed(): random_seed = 301 value = os.environ.get('TEST_RANDOM_SEED', '') try: random_seed = int(value) except ValueError: pass return random_seed def _GetDefaultTestTmpdir(): tmpdir = os.environ.get('TEST_TMPDIR', '') if not tmpdir: tmpdir = os.path.join(tempfile.gettempdir(), 'google_apputils_basetest') return tmpdir flags.DEFINE_integer('test_random_seed', _GetDefaultTestRandomSeed(), 'Random seed for testing. Some test frameworks may ' 'change the default value of this flag between runs, so ' 'it is not appropriate for seeding probabilistic tests.', allow_override=1) flags.DEFINE_string('test_srcdir', os.environ.get('TEST_SRCDIR', ''), 'Root of directory tree where source files live', allow_override=1) flags.DEFINE_string('test_tmpdir', _GetDefaultTestTmpdir(), 'Directory for temporary testing files', allow_override=1) class BeforeAfterTestCaseMeta(type): """Adds setUpTestCase() and tearDownTestCase() methods. These may be needed for setup and teardown of shared fixtures usually because such fixtures are expensive to setup and teardown (eg Perforce clients). When using such fixtures, care should be taken to keep each test as independent as possible (eg via the use of sandboxes). Example: class MyTestCase(basetest.TestCase): __metaclass__ = basetest.BeforeAfterTestCaseMeta @classmethod def setUpTestCase(cls): cls._resource = foo.ReallyExpensiveResource() @classmethod def tearDownTestCase(cls): cls._resource.Destroy() def testSomething(self): self._resource.Something() ... """ _test_loader = unittest.defaultTestLoader def __init__(cls, name, bases, dict): super(BeforeAfterTestCaseMeta, cls).__init__(name, bases, dict) # Notes from mtklein # This code can be tricky to think about. Here are a few things to remember # as you read through it. # When inheritance is involved, this __init__ is called once on each class # in the inheritance chain when that class is defined. In a typical # scenario where a BaseClass inheriting from TestCase declares the # __metaclass__ and SubClass inherits from BaseClass, __init__ will be first # called with cls=BaseClass when BaseClass is defined, and then called later # with cls=SubClass when SubClass is defined. # To know when to call setUpTestCase and tearDownTestCase, this class wraps # the setUp, tearDown, and test* methods in a TestClass. We'd like to only # wrap those methods in the leaves of the inheritance tree, but we can't # know when we're a leaf at wrapping time. So instead we wrap all the # setUp, tearDown, and test* methods, but code them so that we only do the # counting we want at the leaves, which we *can* detect when we've got an # actual instance to look at --- i.e. self, when a method is running. # Because we're wrapping at every level of inheritance, some methods get # wrapped multiple times down the inheritance chain; if SubClass were to # inherit, say, setUp or testFoo from BaseClass, that method would be # wrapped twice, first by BaseClass then by SubClass. That's OK, because we # ensure that the extra code we inject with these wrappers is idempotent. # test_names are the test methods this class can see. test_names = set(cls._test_loader.getTestCaseNames(cls)) # Each class keeps a set of the tests it still has to run. When it's empty, # we know we should call tearDownTestCase. For now, it holds the sentinel # value of None, acting as a indication that we need to call setUpTestCase, # which fills in the actual tests to run. cls.__tests_to_run = None # These calls go through and monkeypatch various methods, in no particular # order. BeforeAfterTestCaseMeta.SetSetUpAttr(cls, test_names) BeforeAfterTestCaseMeta.SetTearDownAttr(cls) BeforeAfterTestCaseMeta.SetTestMethodAttrs(cls, test_names) BeforeAfterTestCaseMeta.SetBeforeAfterTestCaseAttr() # Just a little utility function to help with monkey-patching. @staticmethod def SetMethod(cls, method_name, replacement): """Like setattr, but also preserves name, doc, and module metadata.""" original = getattr(cls, method_name) replacement.__name__ = original.__name__ replacement.__doc__ = original.__doc__ replacement.__module__ = original.__module__ setattr(cls, method_name, replacement) @staticmethod def SetSetUpAttr(cls, test_names): """Wraps setUp() with per-class setUp() functionality.""" # Remember that SetSetUpAttr is eventually called on each class in the # inheritance chain. This line can be subtle because of inheritance. Say # we've got BaseClass that defines setUp, and SubClass inheriting from it # that doesn't define setUp. This method will run twice, and both times # cls_setUp will be BaseClass.setUp. This is one of the tricky cases where # setUp will be wrapped multiple times. cls_setUp = cls.setUp # We create a new setUp method that first checks to see if we need to run # setUpTestCase (looking for the __tests_to_run==None flag), and then runs # the original setUp method. def setUp(self): """Function that will encapsulate and replace cls.setUp().""" # This line is unassuming but crucial to making this whole system work. # It sets leaf to the class of the instance we're currently testing. That # is, leaf is going to be a leaf class. It's not necessarily the same # class as the parameter cls that's being passed in. For example, in the # case above where setUp is in BaseClass, when we instantiate a SubClass # and call setUp, we need leaf to be pointing at the class SubClass. leaf = self.__class__ # The reason we want to do this is that it makes sure setUpTestCase is # only run once, not once for each class down the inheritance chain. When # multiply-wrapped, this extra code is called multiple times. In the # running example: # # 1) cls=BaseClass: replace BaseClass' setUp with a wrapped setUp # 2) cls=SubClass: set SubClass.setUp to what it thinks was its original # setUp --- the wrapped setUp from 1) # # So it's double-wrapped, but that's OK. When we actually call setUp from # an instance, we're calling the double-wrapped method. It sees # __tests_to_run is None and fills that in. Then it calls what it thinks # was its original setUp, the singly-wrapped setUp from BaseClass. The # singly-wrapped setUp *skips* the if-statement, as it sees # leaf.__tests_to_run is not None now. It just runs the real, original # setUp(). # test_names is passed in from __init__, and holds all the test cases that # cls can see. In the BaseClass call, that's probably the empty set, and # for SubClass it'd have your test methods. if leaf.__tests_to_run is None: leaf.__tests_to_run = set(test_names) self.setUpTestCase() cls_setUp(self) # Monkeypatch our new setUp method into the place of the original. BeforeAfterTestCaseMeta.SetMethod(cls, 'setUp', setUp) @staticmethod def SetTearDownAttr(cls): """Wraps tearDown() with per-class tearDown() functionality.""" # This is analagous to SetSetUpAttr, except of course it's patching tearDown # to run tearDownTestCase when there are no more tests to run. All the same # hairy logic applies. cls_tearDown = cls.tearDown def tearDown(self): """Function that will encapsulate and replace cls.tearDown().""" cls_tearDown(self) leaf = self.__class__ # We need to make sure that tearDownTestCase is only run when # we're executing this in the leaf class, so we need the # explicit leaf == cls check below. if (leaf.__tests_to_run is not None and not leaf.__tests_to_run and leaf == cls): leaf.__tests_to_run = None self.tearDownTestCase() BeforeAfterTestCaseMeta.SetMethod(cls, 'tearDown', tearDown) @staticmethod def SetTestMethodAttrs(cls, test_names): """Makes each test method first remove itself from the remaining set.""" # This makes each test case remove itself from the set of remaining tests. # You might think that this belongs more logically in tearDown, and I'd # agree except that tearDown doesn't know what test case it's tearing down! # Instead we have the test method itself remove itself before attempting the # test. # Note that having the test remove itself after running doesn't work, as we # never get to 'after running' for tests that fail. # Like setUp and tearDown, the test case could conceivably be wrapped # twice... but as noted it's an implausible situation to have an actual test # defined in a base class. Just in case, we take the same precaution by # looking in only the leaf class' set of __tests_to_run, and using discard() # instead of remove() to make the operation idempotent. # The closure here makes sure that each new test() function remembers its # own values of cls_test and test_name. Without this, they'd all point to # the values from the last iteration of the loop, causing some arbitrary # test method to run multiple times and the others never. :( def test_closure(cls_test, test_name): def test(self, *args, **kargs): leaf = self.__class__ leaf.__tests_to_run.discard(test_name) return cls_test(self, *args, **kargs) return test for test_name in test_names: cls_test = getattr(cls, test_name) BeforeAfterTestCaseMeta.SetMethod( cls, test_name, test_closure(cls_test, test_name)) @staticmethod def SetBeforeAfterTestCaseAttr(): # This just makes sure every TestCase has a setUpTestCase or # tearDownTestCase, so that you can safely define only one or neither of # them if you want. TestCase.setUpTestCase = lambda self: None TestCase.tearDownTestCase = lambda self: None class TestCase(unittest.TestCase): """Extension of unittest.TestCase providing more powerful assertions.""" maxDiff = 80 * 20 def __init__(self, methodName='runTest'): super(TestCase, self).__init__(methodName) self.__recorded_properties = {} def shortDescription(self): """Format both the test method name and the first line of its docstring. If no docstring is given, only returns the method name. This method overrides unittest.TestCase.shortDescription(), which only returns the first line of the docstring, obscuring the name of the test upon failure. Returns: desc: A short description of a test method. """ desc = str(self) # NOTE: super() is used here instead of directly invoking # unittest.TestCase.shortDescription(self), because of the # following line that occurs later on: # unittest.TestCase = TestCase # Because of this, direct invocation of what we think is the # superclass will actually cause infinite recursion. doc_first_line = super(TestCase, self).shortDescription() if doc_first_line is not None: desc = '\n'.join((desc, doc_first_line)) return desc def assertSequenceStartsWith(self, prefix, whole, msg=None): """An equality assertion for the beginning of ordered sequences. If prefix is an empty sequence, it will raise an error unless whole is also an empty sequence. If prefix is not a sequence, it will raise an error if the first element of whole does not match. Args: prefix: A sequence expected at the beginning of the whole parameter. whole: The sequence in which to look for prefix. msg: Optional message to append on failure. """ try: prefix_len = len(prefix) except (TypeError, NotImplementedError): prefix = [prefix] prefix_len = 1 try: whole_len = len(whole) except (TypeError, NotImplementedError): self.fail('For whole: len(%s) is not supported, it appears to be type: ' '%s' % (whole, type(whole))) assert prefix_len <= whole_len, ( 'Prefix length (%d) is longer than whole length (%d).' % (prefix_len, whole_len)) if not prefix_len and whole_len: self.fail('Prefix length is 0 but whole length is %d: %s' % (len(whole), whole)) try: self.assertSequenceEqual(prefix, whole[:prefix_len], msg) except AssertionError: self.fail(msg or 'prefix: %s not found at start of whole: %s.' % (prefix, whole)) def assertContainsSubset(self, expected_subset, actual_set, msg=None): """Checks whether actual iterable is a superset of expected iterable.""" missing = set(expected_subset) - set(actual_set) if not missing: return missing_msg = 'Missing elements %s\nExpected: %s\nActual: %s' % ( missing, expected_subset, actual_set) if msg: msg += ': %s' % missing_msg else: msg = missing_msg self.fail(msg) # TODO(user): Provide an assertItemsEqual method when our super class # does not provide one. That method went away in Python 3.2 (renamed # to assertCountEqual, or is that different? investigate). def assertSameElements(self, expected_seq, actual_seq, msg=None): """Assert that two sequences have the same elements (in any order). This method, unlike assertItemsEqual, doesn't care about any duplicates in the expected and actual sequences. >> assertSameElements([1, 1, 1, 0, 0, 0], [0, 1]) # Doesn't raise an AssertionError If possible, you should use assertItemsEqual instead of assertSameElements. Args: expected_seq: A sequence containing elements we are expecting. actual_seq: The sequence that we are testing. msg: The message to be printed if the test fails. """ # `unittest2.TestCase` used to have assertSameElements, but it was # removed in favor of assertItemsEqual. As there's a unit test # that explicitly checks this behavior, I am leaving this method # alone. try: expected = dict([(element, None) for element in expected_seq]) actual = dict([(element, None) for element in actual_seq]) missing = [element for element in expected if element not in actual] unexpected = [element for element in actual if element not in expected] missing.sort() unexpected.sort() except TypeError: # Fall back to slower list-compare if any of the objects are # not hashable. expected = list(expected_seq) actual = list(actual_seq) expected.sort() actual.sort() missing, unexpected = _SortedListDifference(expected, actual) errors = [] if missing: errors.append('Expected, but missing:\n %r\n' % missing) if unexpected: errors.append('Unexpected, but present:\n %r\n' % unexpected) if errors: self.fail(msg or ''.join(errors)) # unittest2.TestCase.assertMulitilineEqual works very similarly, but it # has a different error format. However, I find this slightly more readable. def assertMultiLineEqual(self, first, second, msg=None): """Assert that two multi-line strings are equal.""" assert isinstance(first, types.StringTypes), ( 'First argument is not a string: %r' % (first,)) assert isinstance(second, types.StringTypes), ( 'Second argument is not a string: %r' % (second,)) if first == second: return if msg: failure_message = [msg, ':\n'] else: failure_message = ['\n'] for line in difflib.ndiff(first.splitlines(True), second.splitlines(True)): failure_message.append(line) if not line.endswith('\n'): failure_message.append('\n') raise self.failureException(''.join(failure_message)) def assertBetween(self, value, minv, maxv, msg=None): """Asserts that value is between minv and maxv (inclusive).""" if msg is None: msg = '"%r" unexpectedly not between "%r" and "%r"' % (value, minv, maxv) self.assert_(minv <= value, msg) self.assert_(maxv >= value, msg) def assertRegexMatch(self, actual_str, regexes, message=None): """Asserts that at least one regex in regexes matches str. If possible you should use assertRegexpMatches, which is a simpler version of this method. assertRegexpMatches takes a single regular expression (a string or re compiled object) instead of a list. Notes: 1. This function uses substring matching, i.e. the matching succeeds if *any* substring of the error message matches *any* regex in the list. This is more convenient for the user than full-string matching. 2. If regexes is the empty list, the matching will always fail. 3. Use regexes=[''] for a regex that will always pass. 4. '.' matches any single character *except* the newline. To match any character, use '(.|\n)'. 5. '^' matches the beginning of each line, not just the beginning of the string. Similarly, '$' matches the end of each line. 6. An exception will be thrown if regexes contains an invalid regex. Args: actual_str: The string we try to match with the items in regexes. regexes: The regular expressions we want to match against str. See "Notes" above for detailed notes on how this is interpreted. message: The message to be printed if the test fails. """ if isinstance(regexes, basestring): self.fail('regexes is a string; use assertRegexpMatches instead.') if not regexes: self.fail('No regexes specified.') regex_type = type(regexes[0]) for regex in regexes[1:]: if type(regex) is not regex_type: self.fail('regexes list must all be the same type.') if regex_type is bytes and isinstance(actual_str, unicode): regexes = [regex.decode('utf-8') for regex in regexes] regex_type = unicode elif regex_type is unicode and isinstance(actual_str, bytes): regexes = [regex.encode('utf-8') for regex in regexes] regex_type = bytes if regex_type is unicode: regex = u'(?:%s)' % u')|(?:'.join(regexes) elif regex_type is bytes: regex = b'(?:' + (b')|(?:'.join(regexes)) + b')' else: self.fail('Only know how to deal with unicode str or bytes regexes.') if not re.search(regex, actual_str, re.MULTILINE): self.fail(message or ('"%s" does not contain any of these ' 'regexes: %s.' % (actual_str, regexes))) def assertCommandSucceeds(self, command, regexes=(b'',), env=None, close_fds=True): """Asserts that a shell command succeeds (i.e. exits with code 0). Args: command: List or string representing the command to run. regexes: List of regular expression byte strings that match success. env: Dictionary of environment variable settings. close_fds: Whether or not to close all open fd's in the child after forking. """ (ret_code, err) = GetCommandStderr(command, env, close_fds) # Accommodate code which listed their output regexes w/o the b'' prefix by # converting them to bytes for the user. if isinstance(regexes[0], unicode): regexes = [regex.encode('utf-8') for regex in regexes] command_string = GetCommandString(command) self.assertEqual( ret_code, 0, 'Running command\n' '%s failed with error code %s and message\n' '%s' % ( _QuoteLongString(command_string), ret_code, _QuoteLongString(err))) self.assertRegexMatch( err, regexes, message=( 'Running command\n' '%s failed with error code %s and message\n' '%s which matches no regex in %s' % ( _QuoteLongString(command_string), ret_code, _QuoteLongString(err), regexes))) def assertCommandFails(self, command, regexes, env=None, close_fds=True): """Asserts a shell command fails and the error matches a regex in a list. Args: command: List or string representing the command to run. regexes: the list of regular expression strings. env: Dictionary of environment variable settings. close_fds: Whether or not to close all open fd's in the child after forking. """ (ret_code, err) = GetCommandStderr(command, env, close_fds) # Accommodate code which listed their output regexes w/o the b'' prefix by # converting them to bytes for the user. if isinstance(regexes[0], unicode): regexes = [regex.encode('utf-8') for regex in regexes] command_string = GetCommandString(command) self.assertNotEqual( ret_code, 0, 'The following command succeeded while expected to fail:\n%s' % _QuoteLongString(command_string)) self.assertRegexMatch( err, regexes, message=( 'Running command\n' '%s failed with error code %s and message\n' '%s which matches no regex in %s' % ( _QuoteLongString(command_string), ret_code, _QuoteLongString(err), regexes))) def assertRaisesWithPredicateMatch(self, expected_exception, predicate, callable_obj, *args, **kwargs): """Asserts that exception is thrown and predicate(exception) is true. Args: expected_exception: Exception class expected to be raised. predicate: Function of one argument that inspects the passed-in exception and returns True (success) or False (please fail the test). callable_obj: Function to be called. args: Extra args. kwargs: Extra keyword args. """ try: callable_obj(*args, **kwargs) except expected_exception as err: self.assert_(predicate(err), '%r does not match predicate %r' % (err, predicate)) else: self.fail(expected_exception.__name__ + ' not raised') def assertRaisesWithLiteralMatch(self, expected_exception, expected_exception_message, callable_obj, *args, **kwargs): """Asserts that the message in a raised exception equals the given string. Unlike assertRaisesWithRegexpMatch this method takes a literal string, not a regular expression. Args: expected_exception: Exception class expected to be raised. expected_exception_message: String message expected in the raised exception. For a raise exception e, expected_exception_message must equal str(e). callable_obj: Function to be called. args: Extra args. kwargs: Extra kwargs. """ try: callable_obj(*args, **kwargs) except expected_exception as err: actual_exception_message = str(err) self.assert_(expected_exception_message == actual_exception_message, 'Exception message does not match.\n' 'Expected: %r\n' 'Actual: %r' % (expected_exception_message, actual_exception_message)) else: self.fail(expected_exception.__name__ + ' not raised') def assertRaisesWithRegexpMatch(self, expected_exception, expected_regexp, callable_obj, *args, **kwargs): """Asserts that the message in a raised exception matches the given regexp. This is just a wrapper around assertRaisesRegexp. Please use assertRaisesRegexp instead of assertRaisesWithRegexpMatch. Args: expected_exception: Exception class expected to be raised. expected_regexp: Regexp (re pattern object or string) expected to be found in error message. callable_obj: Function to be called. args: Extra args. kwargs: Extra keyword args. """ # TODO(user): this is a good candidate for a global # search-and-replace. self.assertRaisesRegexp( expected_exception, expected_regexp, callable_obj, *args, **kwargs) def assertContainsInOrder(self, strings, target): """Asserts that the strings provided are found in the target in order. This may be useful for checking HTML output. Args: strings: A list of strings, such as [ 'fox', 'dog' ] target: A target string in which to look for the strings, such as 'The quick brown fox jumped over the lazy dog'. """ if not isinstance(strings, list): strings = [strings] current_index = 0 last_string = None for string in strings: index = target.find(str(string), current_index) if index == -1 and current_index == 0: self.fail("Did not find '%s' in '%s'" % (string, target)) elif index == -1: self.fail("Did not find '%s' after '%s' in '%s'" % (string, last_string, target)) last_string = string current_index = index def assertTotallyOrdered(self, *groups): """Asserts that total ordering has been implemented correctly. For example, say you have a class A that compares only on its attribute x. Comparators other than __lt__ are omitted for brevity. class A(object): def __init__(self, x, y): self.x = xio self.y = y def __hash__(self): return hash(self.x) def __lt__(self, other): try: return self.x < other.x except AttributeError: return NotImplemented assertTotallyOrdered will check that instances can be ordered correctly. For example, self.assertTotallyOrdered( [None], # None should come before everything else. [1], # Integers sort earlier. ['foo'], # As do strings. [A(1, 'a')], [A(2, 'b')], # 2 is after 1. [A(2, 'c'), A(2, 'd')], # The second argument is irrelevant. [A(3, 'z')]) Args: groups: A list of groups of elements. Each group of elements is a list of objects that are equal. The elements in each group must be less than the elements in the group after it. For example, these groups are totally ordered: [None], [1], [2, 2], [3]. """ def CheckOrder(small, big): """Ensures small is ordered before big.""" self.assertFalse(small == big, '%r unexpectedly equals %r' % (small, big)) self.assertTrue(small != big, '%r unexpectedly equals %r' % (small, big)) self.assertLess(small, big) self.assertFalse(big < small, '%r unexpectedly less than %r' % (big, small)) self.assertLessEqual(small, big) self.assertFalse(big <= small, '%r unexpectedly less than or equal to %r' % (big, small)) self.assertGreater(big, small) self.assertFalse(small > big, '%r unexpectedly greater than %r' % (small, big)) self.assertGreaterEqual(big, small) self.assertFalse(small >= big, '%r unexpectedly greater than or equal to %r' % (small, big)) def CheckEqual(a, b): """Ensures that a and b are equal.""" self.assertEqual(a, b) self.assertFalse(a != b, '%r unexpectedly equals %r' % (a, b)) self.assertEqual(hash(a), hash(b), 'hash %d of %r unexpectedly not equal to hash %d of %r' % (hash(a), a, hash(b), b)) self.assertFalse(a < b, '%r unexpectedly less than %r' % (a, b)) self.assertFalse(b < a, '%r unexpectedly less than %r' % (b, a)) self.assertLessEqual(a, b) self.assertLessEqual(b, a) self.assertFalse(a > b, '%r unexpectedly greater than %r' % (a, b)) self.assertFalse(b > a, '%r unexpectedly greater than %r' % (b, a)) self.assertGreaterEqual(a, b) self.assertGreaterEqual(b, a) # For every combination of elements, check the order of every pair of # elements. for elements in itertools.product(*groups): elements = list(elements) for index, small in enumerate(elements[:-1]): for big in elements[index + 1:]: CheckOrder(small, big) # Check that every element in each group is equal. for group in groups: for a in group: CheckEqual(a, a) for a, b in itertools.product(group, group): CheckEqual(a, b) def assertDictEqual(self, a, b, msg=None): """Raises AssertionError if a and b are not equal dictionaries. Args: a: A dict, the expected value. b: A dict, the actual value. msg: An optional str, the associated message. Raises: AssertionError: if the dictionaries are not equal. """ self.assertIsInstance(a, dict, 'First argument is not a dictionary') self.assertIsInstance(b, dict, 'Second argument is not a dictionary') def Sorted(iterable): try: return sorted(iterable) # In 3.3, unordered objects are possible. except TypeError: return list(iterable) if a == b: return a_items = Sorted(a.iteritems()) b_items = Sorted(b.iteritems()) unexpected = [] missing = [] different = [] safe_repr = unittest.util.safe_repr def Repr(dikt): """Deterministic repr for dict.""" # Sort the entries based on their repr, not based on their sort order, # which will be non-deterministic across executions, for many types. entries = sorted((safe_repr(k), safe_repr(v)) for k, v in dikt.iteritems()) return '{%s}' % (', '.join('%s: %s' % pair for pair in entries)) message = ['%s != %s%s' % (Repr(a), Repr(b), ' (%s)' % msg if msg else '')] # The standard library default output confounds lexical difference with # value difference; treat them separately. for a_key, a_value in a_items: if a_key not in b: unexpected.append((a_key, a_value)) elif a_value != b[a_key]: different.append((a_key, a_value, b[a_key])) if unexpected: message.append( 'Unexpected, but present entries:\n%s' % ''.join( '%s: %s\n' % (safe_repr(k), safe_repr(v)) for k, v in unexpected)) if different: message.append( 'repr() of differing entries:\n%s' % ''.join( '%s: %s != %s\n' % (safe_repr(k), safe_repr(a_value), safe_repr(b_value)) for k, a_value, b_value in different)) for b_key, b_value in b_items: if b_key not in a: missing.append((b_key, b_value)) if missing: message.append( 'Missing entries:\n%s' % ''.join( ('%s: %s\n' % (safe_repr(k), safe_repr(v)) for k, v in missing))) raise self.failureException('\n'.join(message)) def assertUrlEqual(self, a, b): """Asserts that urls are equal, ignoring ordering of query params.""" parsed_a = urlparse.urlparse(a) parsed_b = urlparse.urlparse(b) self.assertEqual(parsed_a.scheme, parsed_b.scheme) self.assertEqual(parsed_a.netloc, parsed_b.netloc) self.assertEqual(parsed_a.path, parsed_b.path) self.assertEqual(parsed_a.fragment, parsed_b.fragment) self.assertEqual(sorted(parsed_a.params.split(';')), sorted(parsed_b.params.split(';'))) self.assertDictEqual(urlparse.parse_qs(parsed_a.query), urlparse.parse_qs(parsed_b.query)) def assertSameStructure(self, a, b, aname='a', bname='b', msg=None): """Asserts that two values contain the same structural content. The two arguments should be data trees consisting of trees of dicts and lists. They will be deeply compared by walking into the contents of dicts and lists; other items will be compared using the == operator. If the two structures differ in content, the failure message will indicate the location within the structures where the first difference is found. This may be helpful when comparing large structures. Args: a: The first structure to compare. b: The second structure to compare. aname: Variable name to use for the first structure in assertion messages. bname: Variable name to use for the second structure. msg: Additional text to include in the failure message. """ # Accumulate all the problems found so we can report all of them at once # rather than just stopping at the first problems = [] _WalkStructureForProblems(a, b, aname, bname, problems) # Avoid spamming the user toooo much max_problems_to_show = self.maxDiff // 80 if len(problems) > max_problems_to_show: problems = problems[0:max_problems_to_show-1] + ['...'] if problems: failure_message = '; '.join(problems) if msg: failure_message += (': ' + msg) self.fail(failure_message) def assertJsonEqual(self, first, second, msg=None): """Asserts that the JSON objects defined in two strings are equal. A summary of the differences will be included in the failure message using assertSameStructure. Args: first: A string contining JSON to decode and compare to second. second: A string contining JSON to decode and compare to first. msg: Additional text to include in the failure message. """ try: first_structured = json.loads(first) except ValueError as e: raise ValueError('could not decode first JSON value %s: %s' % (first, e)) try: second_structured = json.loads(second) except ValueError as e: raise ValueError('could not decode second JSON value %s: %s' % (second, e)) self.assertSameStructure(first_structured, second_structured, aname='first', bname='second', msg=msg) def getRecordedProperties(self): """Return any properties that the user has recorded.""" return self.__recorded_properties def recordProperty(self, property_name, property_value): """Record an arbitrary property for later use. Args: property_name: str, name of property to record; must be a valid XML attribute name property_value: value of property; must be valid XML attribute value """ self.__recorded_properties[property_name] = property_value def _getAssertEqualityFunc(self, first, second): try: return super(TestCase, self)._getAssertEqualityFunc(first, second) except AttributeError: # This happens if unittest2.TestCase.__init__ was never run. It # usually means that somebody created a subclass just for the # assertions and has overriden __init__. "assertTrue" is a safe # value that will not make __init__ raise a ValueError (this is # a bit hacky). test_method = getattr(self, '_testMethodName', 'assertTrue') super(TestCase, self).__init__(test_method) return super(TestCase, self)._getAssertEqualityFunc(first, second) # This is not really needed here, but some unrelated code calls this # function. # TODO(user): sort it out. def _SortedListDifference(expected, actual): """Finds elements in only one or the other of two, sorted input lists. Returns a two-element tuple of lists. The first list contains those elements in the "expected" list but not in the "actual" list, and the second contains those elements in the "actual" list but not in the "expected" list. Duplicate elements in either input list are ignored. Args: expected: The list we expected. actual: The list we actualy got. Returns: (missing, unexpected) missing: items in expected that are not in actual. unexpected: items in actual that are not in expected. """ i = j = 0 missing = [] unexpected = [] while True: try: e = expected[i] a = actual[j] if e < a: missing.append(e) i += 1 while expected[i] == e: i += 1 elif e > a: unexpected.append(a) j += 1 while actual[j] == a: j += 1 else: i += 1 try: while expected[i] == e: i += 1 finally: j += 1 while actual[j] == a: j += 1 except IndexError: missing.extend(expected[i:]) unexpected.extend(actual[j:]) break return missing, unexpected # ---------------------------------------------------------------------- # Functions to compare the actual output of a test to the expected # (golden) output. # # Note: We could just replace the sys.stdout and sys.stderr objects, # but we actually redirect the underlying file objects so that if the # Python script execs any subprocess, their output will also be # redirected. # # Usage: # basetest.CaptureTestStdout() # ... do something ... # basetest.DiffTestStdout("... path to golden file ...") # ---------------------------------------------------------------------- class CapturedStream(object): """A temporarily redirected output stream.""" def __init__(self, stream, filename): self._stream = stream self._fd = stream.fileno() self._filename = filename # Keep original stream for later self._uncaptured_fd = os.dup(self._fd) # Open file to save stream to cap_fd = os.open(self._filename, os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0600) # Send stream to this file self._stream.flush() os.dup2(cap_fd, self._fd) os.close(cap_fd) def RestartCapture(self): """Resume capturing output to a file (after calling StopCapture).""" # Original stream fd assert self._uncaptured_fd # Append stream to file cap_fd = os.open(self._filename, os.O_CREAT | os.O_APPEND | os.O_WRONLY, 0600) # Send stream to this file self._stream.flush() os.dup2(cap_fd, self._fd) os.close(cap_fd) def StopCapture(self): """Remove output redirection.""" self._stream.flush() os.dup2(self._uncaptured_fd, self._fd) def filename(self): return self._filename def __del__(self): self.StopCapture() os.close(self._uncaptured_fd) del self._uncaptured_fd _captured_streams = {} def _CaptureTestOutput(stream, filename): """Redirect an output stream to a file. Args: stream: Should be sys.stdout or sys.stderr. filename: File where output should be stored. """ assert not _captured_streams.has_key(stream) _captured_streams[stream] = CapturedStream(stream, filename) def _DiffTestOutput(stream, golden_filename): """Compare ouput of redirected stream to contents of golden file. Args: stream: Should be sys.stdout or sys.stderr. golden_filename: Absolute path to golden file. """ assert _captured_streams.has_key(stream) cap = _captured_streams[stream] for cap_stream in _captured_streams.itervalues(): cap_stream.StopCapture() try: _Diff(cap.filename(), golden_filename) finally: # remove the current stream del _captured_streams[stream] # restore other stream capture for cap_stream in _captured_streams.itervalues(): cap_stream.RestartCapture() # We want to emit exactly one notice to stderr telling the user where to look # for their stdout or stderr that may have been consumed to aid debugging. _notified_test_output_path = '' def _MaybeNotifyAboutTestOutput(outdir): global _notified_test_output_path if _notified_test_output_path != outdir: _notified_test_output_path = outdir sys.stderr.write('\nNOTE: Some tests capturing output into: %s\n' % outdir) # TODO(user): Make CaptureTest* be usable as context managers to easily stop # capturing at the appropriate time to make debugging failures much easier. # Public interface def CaptureTestStdout(outfile=''): """Capture the stdout stream to a file until StopCapturing() is called.""" if not outfile: outfile = os.path.join(FLAGS.test_tmpdir, 'captured.out') outdir = FLAGS.test_tmpdir else: outdir = os.path.dirname(outfile) _MaybeNotifyAboutTestOutput(outdir) _CaptureTestOutput(sys.stdout, outfile) def CaptureTestStderr(outfile=''): """Capture the stderr stream to a file until StopCapturing() is called.""" if not outfile: outfile = os.path.join(FLAGS.test_tmpdir, 'captured.err') outdir = FLAGS.test_tmpdir else: outdir = os.path.dirname(outfile) _MaybeNotifyAboutTestOutput(outdir) _CaptureTestOutput(sys.stderr, outfile) def DiffTestStdout(golden): _DiffTestOutput(sys.stdout, golden) def DiffTestStderr(golden): _DiffTestOutput(sys.stderr, golden) def StopCapturing(): """Stop capturing redirected output. Debugging sucks if you forget!""" while _captured_streams: _, cap_stream = _captured_streams.popitem() cap_stream.StopCapture() del cap_stream def _WriteTestData(data, filename): """Write data into file named filename.""" fd = os.open(filename, os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0o600) if not isinstance(data, (bytes, bytearray)): data = data.encode('utf-8') os.write(fd, data) os.close(fd) _INT_TYPES = (int, long) # Sadly there is no types.IntTypes defined for us. def _WalkStructureForProblems(a, b, aname, bname, problem_list): """The recursive comparison behind assertSameStructure.""" if type(a) != type(b) and not ( isinstance(a, _INT_TYPES) and isinstance(b, _INT_TYPES)): # We do not distinguish between int and long types as 99.99% of Python 2 # code should never care. They collapse into a single type in Python 3. problem_list.append('%s is a %r but %s is a %r' % (aname, type(a), bname, type(b))) # If they have different types there's no point continuing return if isinstance(a, collections.Mapping): for k in a: if k in b: _WalkStructureForProblems(a[k], b[k], '%s[%r]' % (aname, k), '%s[%r]' % (bname, k), problem_list) else: problem_list.append('%s has [%r] but %s does not' % (aname, k, bname)) for k in b: if k not in a: problem_list.append('%s lacks [%r] but %s has it' % (aname, k, bname)) # Strings are Sequences but we'll just do those with regular != elif isinstance(a, collections.Sequence) and not isinstance(a, basestring): minlen = min(len(a), len(b)) for i in xrange(minlen): _WalkStructureForProblems(a[i], b[i], '%s[%d]' % (aname, i), '%s[%d]' % (bname, i), problem_list) for i in xrange(minlen, len(a)): problem_list.append('%s has [%i] but %s does not' % (aname, i, bname)) for i in xrange(minlen, len(b)): problem_list.append('%s lacks [%i] but %s has it' % (aname, i, bname)) else: if a != b: problem_list.append('%s is %r but %s is %r' % (aname, a, bname, b)) class OutputDifferedError(AssertionError): pass class DiffFailureError(Exception): pass def _DiffViaExternalProgram(lhs, rhs, external_diff): """Compare two files using an external program; raise if it reports error.""" # The behavior of this function matches the old _Diff() method behavior # when a TEST_DIFF environment variable was set. A few old things at # Google depended on that functionality. command = [external_diff, lhs, rhs] try: subprocess.check_output(command, close_fds=True, stderr=subprocess.STDOUT) return True # No diffs. except subprocess.CalledProcessError as error: failure_output = error.output if error.returncode == 1: raise OutputDifferedError('\nRunning %s\n%s\nTest output differed from' ' golden file\n' % (command, failure_output)) except EnvironmentError as error: failure_output = str(error) # Running the program failed in some way that wasn't a diff. raise DiffFailureError('\nRunning %s\n%s\nFailure diffing test output' ' with golden file\n' % (command, failure_output)) def _Diff(lhs, rhs): """Given two pathnames, compare two files. Raise if they differ.""" # Some people rely on being able to specify TEST_DIFF in the environment to # have tests use their own diff wrapper for use when updating golden data. external_diff = os.environ.get('TEST_DIFF') if external_diff: return _DiffViaExternalProgram(lhs, rhs, external_diff) try: with open(lhs, 'rt') as lhs_f: with open(rhs, 'rt') as rhs_f: diff_text = ''.join( difflib.unified_diff(lhs_f.readlines(), rhs_f.readlines())) if not diff_text: return True raise OutputDifferedError('\nComparing %s and %s\nTest output differed ' 'from golden file:\n%s' % (lhs, rhs, diff_text)) except EnvironmentError as error: # Unable to read the files. raise DiffFailureError('\nComparing %s and %s\nFailure diffing test output ' 'with golden file: %s\n' % (lhs, rhs, error)) def DiffTestStringFile(data, golden): """Diff data agains a golden file.""" data_file = os.path.join(FLAGS.test_tmpdir, 'provided.dat') _WriteTestData(data, data_file) _Diff(data_file, golden) def DiffTestStrings(data1, data2): """Diff two strings.""" diff_text = ''.join( difflib.unified_diff(data1.splitlines(True), data2.splitlines(True))) if not diff_text: return raise OutputDifferedError('\nTest strings differed:\n%s' % diff_text) def DiffTestFiles(testgen, golden): _Diff(testgen, golden) def GetCommandString(command): """Returns an escaped string that can be used as a shell command. Args: command: List or string representing the command to run. Returns: A string suitable for use as a shell command. """ if isinstance(command, types.StringTypes): return command else: return shellutil.ShellEscapeList(command) def GetCommandStderr(command, env=None, close_fds=True): """Runs the given shell command and returns a tuple. Args: command: List or string representing the command to run. env: Dictionary of environment variable settings. close_fds: Whether or not to close all open fd's in the child after forking. Returns: Tuple of (exit status, text printed to stdout and stderr by the command). """ if env is None: env = {} # Forge needs PYTHON_RUNFILES in order to find the runfiles directory when a # Python executable is run by a Python test. Pass this through from the # parent environment if not explicitly defined. if os.environ.get('PYTHON_RUNFILES') and not env.get('PYTHON_RUNFILES'): env['PYTHON_RUNFILES'] = os.environ['PYTHON_RUNFILES'] use_shell = isinstance(command, types.StringTypes) process = subprocess.Popen( command, close_fds=close_fds, env=env, shell=use_shell, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) output = process.communicate()[0] exit_status = process.wait() return (exit_status, output) def _QuoteLongString(s): """Quotes a potentially multi-line string to make the start and end obvious. Args: s: A string. Returns: The quoted string. """ if isinstance(s, (bytes, bytearray)): try: s = s.decode('utf-8') except UnicodeDecodeError: s = str(s) return ('8<-----------\n' + s + '\n' + '----------->8\n') class TestProgramManualRun(unittest.TestProgram): """A TestProgram which runs the tests manually.""" def runTests(self, do_run=False): """Run the tests.""" if do_run: unittest.TestProgram.runTests(self) def main(*args, **kwargs): """Executes a set of Python unit tests. Usually this function is called without arguments, so the unittest.TestProgram instance will get created with the default settings, so it will run all test methods of all TestCase classes in the __main__ module. Args: args: Positional arguments passed through to unittest.TestProgram.__init__. kwargs: Keyword arguments passed through to unittest.TestProgram.__init__. """ _RunInApp(RunTests, args, kwargs) def _IsInAppMain(): """Returns True iff app.main or app.really_start is active.""" f = sys._getframe().f_back app_dict = app.__dict__ while f: if f.f_globals is app_dict and f.f_code.co_name in ('run', 'really_start'): return True f = f.f_back return False class SavedFlag(object): """Helper class for saving and restoring a flag value.""" def __init__(self, flag): self.flag = flag self.value = flag.value self.present = flag.present def RestoreFlag(self): self.flag.value = self.value self.flag.present = self.present def _RunInApp(function, args, kwargs): """Executes a set of Python unit tests, ensuring app.really_start. Most users should call basetest.main() instead of _RunInApp. _RunInApp calculates argv to be the command-line arguments of this program (without the flags), sets the default of FLAGS.alsologtostderr to True, then it calls function(argv, args, kwargs), making sure that `function' will get called within app.run() or app.really_start(). _RunInApp does this by checking whether it is called by either app.run() or app.really_start(), or by calling app.really_start() explicitly. The reason why app.really_start has to be ensured is to make sure that flags are parsed and stripped properly, and other initializations done by the app module are also carried out, no matter if basetest.run() is called from within or outside app.run(). If _RunInApp is called from within app.run(), then it will reparse sys.argv and pass the result without command-line flags into the argv argument of `function'. The reason why this parsing is needed is that __main__.main() calls basetest.main() without passing its argv. So the only way _RunInApp could get to know the argv without the flags is that it reparses sys.argv. _RunInApp changes the default of FLAGS.alsologtostderr to True so that the test program's stderr will contain all the log messages unless otherwise specified on the command-line. This overrides any explicit assignment to FLAGS.alsologtostderr by the test program prior to the call to _RunInApp() (e.g. in __main__.main). Please note that _RunInApp (and the function it calls) is allowed to make changes to kwargs. Args: function: basetest.RunTests or a similar function. It will be called as function(argv, args, kwargs) where argv is a list containing the elements of sys.argv without the command-line flags. args: Positional arguments passed through to unittest.TestProgram.__init__. kwargs: Keyword arguments passed through to unittest.TestProgram.__init__. """ if faulthandler: try: faulthandler.enable() except Exception as e: sys.stderr.write('faulthandler.enable() failed %r; ignoring.\n' % e) if _IsInAppMain(): # Save command-line flags so the side effects of FLAGS(sys.argv) can be # undone. saved_flags = dict((f.name, SavedFlag(f)) for f in FLAGS.FlagDict().itervalues()) # Here we'd like to change the default of alsologtostderr from False to # True, so the test programs's stderr will contain all the log messages. # The desired effect is that if --alsologtostderr is not specified in # the command-line, and __main__.main doesn't set FLAGS.logtostderr # before calling us (basetest.main), then our changed default takes # effect and alsologtostderr becomes True. # # However, we cannot achive this exact effect, because here we cannot # distinguish these situations: # # A. main.__main__ has changed it to False, it hasn't been specified on # the command-line, and the default was kept as False. We should keep # it as False. # # B. main.__main__ hasn't changed it, it hasn't been specified on the # command-line, and the default was kept as False. We should change # it to True here. # # As a workaround, we assume that main.__main__ never changes # FLAGS.alsologstderr to False, thus the value of the flag is determined # by its default unless the command-line overrides it. We want to change # the default to True, and we do it by setting the flag value to True, and # letting the command-line override it in FLAGS(sys.argv) below by not # restoring it in saved_flag.RestoreFlag(). if 'alsologtostderr' in saved_flags: FLAGS.alsologtostderr = True del saved_flags['alsologtostderr'] # The call FLAGS(sys.argv) parses sys.argv, returns the arguments # without the flags, and -- as a side effect -- modifies flag values in # FLAGS. We don't want the side effect, because we don't want to # override flag changes the program did (e.g. in __main__.main) # after the command-line has been parsed. So we have the for loop below # to change back flags to their old values. argv = FLAGS(sys.argv) for saved_flag in saved_flags.itervalues(): saved_flag.RestoreFlag() function(argv, args, kwargs) else: # Send logging to stderr. Use --alsologtostderr instead of --logtostderr # in case tests are reading their own logs. if 'alsologtostderr' in FLAGS: FLAGS.SetDefault('alsologtostderr', True) def Main(argv): function(argv, args, kwargs) app.really_start(main=Main) def RunTests(argv, args, kwargs): """Executes a set of Python unit tests within app.really_start. Most users should call basetest.main() instead of RunTests. Please note that RunTests should be called from app.really_start (which is called from app.run()). Calling basetest.main() would ensure that. Please note that RunTests is allowed to make changes to kwargs. Args: argv: sys.argv with the command-line flags removed from the front, i.e. the argv with which app.run() has called __main__.main. args: Positional arguments passed through to unittest.TestProgram.__init__. kwargs: Keyword arguments passed through to unittest.TestProgram.__init__. """ test_runner = kwargs.get('testRunner') # Make sure tmpdir exists if not os.path.isdir(FLAGS.test_tmpdir): os.makedirs(FLAGS.test_tmpdir) # Run main module setup, if it exists main_mod = sys.modules['__main__'] if hasattr(main_mod, 'setUp') and callable(main_mod.setUp): main_mod.setUp() # Let unittest.TestProgram.__init__ called by # TestProgramManualRun.__init__ do its own argv parsing, e.g. for '-v', # on argv, which is sys.argv without the command-line flags. kwargs.setdefault('argv', argv) try: result = None test_program = TestProgramManualRun(*args, **kwargs) if test_runner: test_program.testRunner = test_runner else: test_program.testRunner = unittest.TextTestRunner( verbosity=test_program.verbosity) result = test_program.testRunner.run(test_program.test) finally: # Run main module teardown, if it exists if hasattr(main_mod, 'tearDown') and callable(main_mod.tearDown): main_mod.tearDown() sys.exit(not result.wasSuccessful()) google-apputils-0.4.0/google/apputils/app.py0000640003611500116100000002640412176032264021717 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2003 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Generic entry point for Google applications. To use this module, simply define a 'main' function with a single 'argv' argument and add the following to the end of your source file: if __name__ == '__main__': app.run() TODO(user): Remove silly main-detection logic, and force all clients of this module to check __name__ explicitly. Fix all current clients that don't check __name__. """ import errno import os import pdb import socket import stat import struct import sys import time import traceback import gflags as flags FLAGS = flags.FLAGS flags.DEFINE_boolean('run_with_pdb', 0, 'Set to true for PDB debug mode') flags.DEFINE_boolean('pdb_post_mortem', 0, 'Set to true to handle uncaught exceptions with PDB ' 'post mortem.') flags.DEFINE_boolean('run_with_profiling', 0, 'Set to true for profiling the script. ' 'Execution will be slower, and the output format might ' 'change over time.') flags.DEFINE_string('profile_file', None, 'Dump profile information to a file (for python -m ' 'pstats). Implies --run_with_profiling.') flags.DEFINE_boolean('use_cprofile_for_profiling', True, 'Use cProfile instead of the profile module for ' 'profiling. This has no effect unless ' '--run_with_profiling is set.') # If main() exits via an abnormal exception, call into these # handlers before exiting. EXCEPTION_HANDLERS = [] help_text_wrap = False # Whether to enable TextWrap in help output class Error(Exception): pass class UsageError(Error): """The arguments supplied by the user are invalid. Raise this when the arguments supplied are invalid from the point of view of the application. For example when two mutually exclusive flags have been supplied or when there are not enough non-flag arguments. It is distinct from flags.FlagsError which covers the lower level of parsing and validating individual flags. """ def __init__(self, message, exitcode=1): Error.__init__(self, message) self.exitcode = exitcode class HelpFlag(flags.BooleanFlag): """Special boolean flag that displays usage and raises SystemExit.""" NAME = 'help' def __init__(self): flags.BooleanFlag.__init__(self, self.NAME, 0, 'show this help', short_name='?', allow_override=1) def Parse(self, arg): if arg: usage(shorthelp=1, writeto_stdout=1) # Advertise --helpfull on stdout, since usage() was on stdout. print print 'Try --helpfull to get a list of all flags.' sys.exit(1) class HelpshortFlag(HelpFlag): """--helpshort is an alias for --help.""" NAME = 'helpshort' class HelpfullFlag(flags.BooleanFlag): """Display help for flags in this module and all dependent modules.""" def __init__(self): flags.BooleanFlag.__init__(self, 'helpfull', 0, 'show full help', allow_override=1) def Parse(self, arg): if arg: usage(writeto_stdout=1) sys.exit(1) class HelpXMLFlag(flags.BooleanFlag): """Similar to HelpfullFlag, but generates output in XML format.""" def __init__(self): flags.BooleanFlag.__init__(self, 'helpxml', False, 'like --helpfull, but generates XML output', allow_override=1) def Parse(self, arg): if arg: flags.FLAGS.WriteHelpInXMLFormat(sys.stdout) sys.exit(1) class BuildDataFlag(flags.BooleanFlag): """Boolean flag that writes build data to stdout and exits.""" def __init__(self): flags.BooleanFlag.__init__(self, 'show_build_data', 0, 'show build data and exit') def Parse(self, arg): if arg: sys.stdout.write(build_data.BuildData()) sys.exit(0) def parse_flags_with_usage(args): """Try parsing the flags, printing usage and exiting if unparseable.""" try: argv = FLAGS(args) return argv except flags.FlagsError, error: sys.stderr.write('FATAL Flags parsing error: %s\n' % error) sys.stderr.write('Pass --helpshort or --helpfull to see help on flags.\n') sys.exit(1) _define_help_flags_called = False def DefineHelpFlags(): """Register help flags. Idempotent.""" # Use a global to ensure idempotence. # pylint: disable=global-statement global _define_help_flags_called if not _define_help_flags_called: flags.DEFINE_flag(HelpFlag()) flags.DEFINE_flag(HelpshortFlag()) # alias for --help flags.DEFINE_flag(HelpfullFlag()) flags.DEFINE_flag(HelpXMLFlag()) flags.DEFINE_flag(BuildDataFlag()) _define_help_flags_called = True def RegisterAndParseFlagsWithUsage(): """Register help flags, parse arguments and show usage if appropriate. Returns: remaining arguments after flags parsing """ DefineHelpFlags() argv = parse_flags_with_usage(sys.argv) return argv def really_start(main=None): """Initializes flag values, and calls main with non-flag arguments. Only non-flag arguments are passed to main(). The return value of main() is used as the exit status. Args: main: Main function to run with the list of non-flag arguments, or None so that sys.modules['__main__'].main is to be used. """ argv = RegisterAndParseFlagsWithUsage() if main is None: main = sys.modules['__main__'].main try: if FLAGS.run_with_pdb: sys.exit(pdb.runcall(main, argv)) else: if FLAGS.run_with_profiling or FLAGS.profile_file: # Avoid import overhead since most apps (including performance-sensitive # ones) won't be run with profiling. import atexit if FLAGS.use_cprofile_for_profiling: import cProfile as profile else: import profile profiler = profile.Profile() if FLAGS.profile_file: atexit.register(profiler.dump_stats, FLAGS.profile_file) else: atexit.register(profiler.print_stats) retval = profiler.runcall(main, argv) sys.exit(retval) else: sys.exit(main(argv)) except UsageError, error: usage(shorthelp=1, detailed_error=error, exitcode=error.exitcode) except: if FLAGS.pdb_post_mortem: traceback.print_exc() pdb.post_mortem() raise def run(): """Begin executing the program. - Parses command line flags with the flag module. - If there are any errors, print usage(). - Calls main() with the remaining arguments. - If main() raises a UsageError, print usage and the error message. """ return _actual_start() def _actual_start(): """Another layer in the starting stack.""" # Get raw traceback tb = None try: raise ZeroDivisionError('') except ZeroDivisionError: tb = sys.exc_info()[2] assert tb # Look at previous stack frame's previous stack frame (previous # frame is run() or start(); the frame before that should be the # frame of the original caller, which should be __main__ or appcommands prev_prev_frame = tb.tb_frame.f_back.f_back if not prev_prev_frame: return prev_prev_name = prev_prev_frame.f_globals.get('__name__', None) if (prev_prev_name != '__main__' and not prev_prev_name.endswith('.appcommands')): return # just in case there's non-trivial stuff happening in __main__ del tb if hasattr(sys, 'exc_clear'): sys.exc_clear() # This functionality is gone in Python 3. try: really_start() except SystemExit, e: raise except Exception, e: # Call any installed exception handlers which may, for example, # log to a file or send email. for handler in EXCEPTION_HANDLERS: try: if handler.Wants(e): handler.Handle(e) except: # We don't want to stop for exceptions in the exception handlers but # we shouldn't hide them either. sys.stderr.write(traceback.format_exc()) raise # All handlers have had their chance, now die as we would have normally. raise def usage(shorthelp=0, writeto_stdout=0, detailed_error=None, exitcode=None): """Write __main__'s docstring to stderr with some help text. Args: shorthelp: print only flags from this module, rather than all flags. writeto_stdout: write help message to stdout, rather than to stderr. detailed_error: additional detail about why usage info was presented. exitcode: if set, exit with this status code after writing help. """ if writeto_stdout: stdfile = sys.stdout else: stdfile = sys.stderr doc = sys.modules['__main__'].__doc__ if not doc: doc = '\nUSAGE: %s [flags]\n' % sys.argv[0] doc = flags.TextWrap(doc, indent=' ', firstline_indent='') else: # Replace all '%s' with sys.argv[0], and all '%%' with '%'. num_specifiers = doc.count('%') - 2 * doc.count('%%') try: doc %= (sys.argv[0],) * num_specifiers except (OverflowError, TypeError, ValueError): # Just display the docstring as-is. pass if help_text_wrap: doc = flags.TextWrap(flags.DocToHelp(doc)) if shorthelp: flag_str = FLAGS.MainModuleHelp() else: flag_str = str(FLAGS) try: stdfile.write(doc) if flag_str: stdfile.write('\nflags:\n') stdfile.write(flag_str) stdfile.write('\n') if detailed_error is not None: stdfile.write('\n%s\n' % detailed_error) except IOError, e: # We avoid printing a huge backtrace if we get EPIPE, because # "foo.par --help | less" is a frequent use case. if e.errno != errno.EPIPE: raise if exitcode is not None: sys.exit(exitcode) class ExceptionHandler(object): """Base exception handler from which other may inherit.""" def Wants(self, unused_exc): """Check if this exception handler want to handle this exception. Args: unused_exc: Exception, the current exception Returns: boolean This base handler wants to handle all exceptions, override this method if you want to be more selective. """ return True def Handle(self, exc): """Do something with the current exception. Args: exc: Exception, the current exception This method must be overridden. """ raise NotImplementedError() def InstallExceptionHandler(handler): """Install an exception handler. Args: handler: an object conforming to the interface defined in ExceptionHandler Raises: TypeError: handler was not of the correct type All installed exception handlers will be called if main() exits via an abnormal exception, i.e. not one of SystemExit, KeyboardInterrupt, FlagsError or UsageError. """ if not isinstance(handler, ExceptionHandler): raise TypeError('handler of type %s does not inherit from ExceptionHandler' % type(handler)) EXCEPTION_HANDLERS.append(handler) google-apputils-0.4.0/google/apputils/setup_command.py0000640003611500116100000001211112176032264023763 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2010 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Setuptools extension for running Google-style Python tests. Google-style Python tests differ from normal Python tests in that each test module is intended to be executed as an independent script. In particular, the test fixture code in basetest.main() that executes module-wide setUp() and tearDown() depends on __main__ being the module under test. This conflicts with the usual setuptools test style, which uses a single TestSuite to run all of a package's tests. This package provides a new setuptools command, google_test, that runs all of the google-style tests found in a specified directory. NOTE: This works by overriding sys.modules['__main__'] with the module under test, but still runs tests in the same process. Thus it will *not* work if your tests depend on any of the following: - Per-process (as opposed to per-module) initialization. - Any entry point that is not basetest.main(). To use the google_test command in your project, do something like the following: In setup.py: setup( name = "mypackage", ... setup_requires = ["google-apputils>=0.2"], google_test_dir = "tests", ) Run: $ python setup.py google_test """ from distutils import errors import imp import os import re import shlex import sys import traceback from setuptools.command import test def ValidateGoogleTestDir(unused_dist, unused_attr, value): """Validate that the test directory is a directory.""" if not os.path.isdir(value): raise errors.DistutilsSetupError('%s is not a directory' % value) class GoogleTest(test.test): """Command to run Google-style tests after in-place build.""" description = 'run Google-style tests after in-place build' _DEFAULT_PATTERN = r'_(?:unit|reg)?test\.py$' user_options = [ ('test-dir=', 'd', 'Look for test modules in specified directory.'), ('test-module-pattern=', 'p', ('Pattern for matching test modules. Defaults to %r. ' 'Only source files (*.py) will be considered, even if more files match ' 'this pattern.' % _DEFAULT_PATTERN)), ('test-args=', 'a', ('Arguments to pass to basetest.main(). May only make sense if ' 'test_module_pattern matches exactly one test.')), ] def initialize_options(self): self.test_dir = None self.test_module_pattern = self._DEFAULT_PATTERN self.test_args = '' # Set to a dummy value, since we don't call the superclass methods for # options parsing. self.test_suite = True def finalize_options(self): if self.test_dir is None: if self.distribution.google_test_dir: self.test_dir = self.distribution.google_test_dir else: raise errors.DistutilsOptionError('No test directory specified') self.test_module_pattern = re.compile(self.test_module_pattern) self.test_args = shlex.split(self.test_args) def _RunTestModule(self, module_path): """Run a module as a test module given its path. Args: module_path: The path to the module to test; must end in '.py'. Returns: True if the tests in this module pass, False if not or if an error occurs. """ path, filename = os.path.split(module_path) old_argv = sys.argv[:] old_path = sys.path[:] old_modules = sys.modules.copy() # Make relative imports in test modules work with our mangled sys.path. sys.path.insert(0, path) module_name = filename.replace('.py', '') import_tuple = imp.find_module(module_name, [path]) module = imp.load_module(module_name, *import_tuple) sys.modules['__main__'] = module sys.argv = [module.__file__] + self.test_args # Late import since this must be run with the project's sys.path. import basetest try: try: sys.stderr.write('Testing %s\n' % module_name) basetest.main() # basetest.main() should always call sys.exit, so this is very bad. return False except SystemExit as e: returncode, = e.args return not returncode except: traceback.print_exc() return False finally: sys.argv[:] = old_argv sys.path[:] = old_path sys.modules.clear() sys.modules.update(old_modules) def run_tests(self): ok = True for path, _, filenames in os.walk(self.test_dir): for filename in filenames: if not filename.endswith('.py'): continue file_path = os.path.join(path, filename) if self.test_module_pattern.search(file_path): ok &= self._RunTestModule(file_path) sys.exit(int(not ok)) google-apputils-0.4.0/google/apputils/appcommands.py0000750003611500116100000007140212176032264023441 0ustar dborowitzeng00000000000000#!/usr/bin/env python # # Copyright 2007 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """This module is the base for programs that provide multiple commands. This provides command line tools that have a few shared global flags, followed by a command name, followed by command specific flags, then by arguments. That is: tool [--global_flags] command [--command_flags] [args] The module is built on top of app.py and 'overrides' a bit of it. However the interface is mostly the same. The main difference is that your main is supposed to register commands and return without further execution of the commands; pre checking is of course welcome! Also your global initialization should call appcommands.Run() rather than app.run(). To register commands use AddCmd() or AddCmdFunc(). AddCmd() is used for commands that derive from class Cmd and the AddCmdFunc() is used to wrap simple functions. This module itself registers the command 'help' that allows users to retrieve help for all or specific commands. 'help' is the default command executed if no command is expressed, unless a different default command is set with SetDefaultCommand. Example: from mx import DateTime class CmdDate(appcommands.Cmd): \"\"\"This docstring contains the help for the date command.\"\"\" def Run(self, argv): print DateTime.now() def main(argv): appcommands.AddCmd('date', CmdDate, command_aliases=['data_now']) if __name__ == '__main__': appcommands.Run() In the above example the name of the registered command on the command line is 'date'. Thus, to get the date you would execute: tool date The above example also added the command alias 'data_now' which allows to replace 'tool date' with 'tool data_now'. To get a list of available commands run: tool help For help with a specific command, you would execute: tool help date For help on flags run one of the following: tool --help Note that 'tool --help' gives you information on global flags, just like for applications that do not use appcommand. Likewise 'tool --helpshort' and the other help-flags from app.py are also available. The above example also demonstrates that you only have to call appcommands.Run() and register your commands in main() to initialize your program with appcommands (and app). Handling of flags: Flags can be registered just as with any other google tool using flags.py. In addition you can also provide command specific flags. To do so simply add flags registering code into the __init__ function of your Cmd classes passing parameter flag_values to any flags registering calls. These flags will get copied to the global flag list, so that once the command is detected they behave just like any other flag. That means these flags won't be available for other commands. Note that it is possible to register flags with more than one command. Getting help: This module activates formatting and wrapping to help output. That is the main difference to help created from app.py. So just as with app.py, appcommands.py will create help from the main modules main __doc__. But it adds the new 'help' command that allows you to get a list of all available commands. Each command's help will be followed by the registered command specific flags along with their defaults and help. After help for all commands there will also be a list of all registered global flags with their defaults and help. The text for the command's help can best be supplied by overwriting the __doc__ property of the Cmd classes for commands registered with AddCmd() or the __doc__ property of command functions registered AddCmdFunc(). Inner working: This module interacts with app.py by replacing its inner start dispatcher. The replacement version basically does the same, registering help flags, checking whether help flags were present, and calling the main module's main function. However unlike app.py, this module epxpects main() to only register commands and then to return. After having all commands registered appcommands.py will then parse the remaining arguments for any registered command. If one is found it will get executed. Otherwise a short usage info will be displayed. Each provided command must be an instance of Cmd. If commands get registered from global functions using AddCmdFunc() then the helper class _FunctionalCmd will be used in the registering process. """ import os import pdb import sys import traceback from google.apputils import app import gflags as flags FLAGS = flags.FLAGS # module exceptions: class AppCommandsError(Exception): """The base class for all flags errors.""" pass _cmd_argv = None # remaining arguments with index 0 = sys.argv[0] _cmd_list = {} # list of commands index by name (_Cmd instances) _cmd_alias_list = {} # list of command_names index by command_alias _cmd_default = 'help' # command to execute if none explicitly given def GetAppBasename(): """Returns the friendly basename of this application.""" return os.path.basename(sys.argv[0]) def ShortHelpAndExit(message=None): """Display optional message, followed by a note on how to get help, then exit. Args: message: optional message to display """ sys.stdout.flush() if message is not None: sys.stderr.write('%s\n' % message) sys.stderr.write("Run '%s help' to get help\n" % GetAppBasename()) sys.exit(1) def GetCommandList(): """Return list of registered commands.""" # pylint: disable=global-variable-not-assigned global _cmd_list return _cmd_list def GetCommandAliasList(): """Return list of registered command aliases.""" # pylint: disable=global-variable-not-assigned global _cmd_alias_list return _cmd_alias_list def GetFullCommandList(): """Return list of registered commands, including aliases.""" all_cmds = dict(GetCommandList()) for cmd_alias, cmd_name in GetCommandAliasList().iteritems(): all_cmds[cmd_alias] = all_cmds.get(cmd_name) return all_cmds def GetCommandByName(name): """Get the command or None if name is not a registered command. Args: name: name of command to look for Returns: Cmd instance holding the command or None """ return GetCommandList().get(GetCommandAliasList().get(name)) def GetCommandArgv(): """Return list of remaining args.""" return _cmd_argv def GetMaxCommandLength(): """Returns the length of the longest registered command.""" return max([len(cmd_name) for cmd_name in GetCommandList()]) class Cmd(object): """Abstract class describing and implementing a command. When creating code for a command, at least you have to derive this class and override method Run(). The other methods of this class might be overridden as well. Check their documentation for details. If the command needs any specific flags, use __init__ for registration. """ def __init__(self, name, flag_values, command_aliases=None): """Initialize and check whether self is actually a Cmd instance. This can be used to register command specific flags. If you do so remember that you have to provide the 'flag_values=flag_values' parameter to any flags.DEFINE_*() call. Args: name: Name of the command flag_values: FlagValues() instance that needs to be passed as flag_values parameter to any flags registering call. command_aliases: A list of command aliases that the command can be run as. Raises: AppCommandsError: if self is Cmd (Cmd is abstract) """ self._command_name = name self._command_aliases = command_aliases self._command_flags = flag_values self._all_commands_help = None if type(self) is Cmd: raise AppCommandsError('Cmd is abstract and cannot be instantiated') def Run(self, argv): """Execute the command. Must be provided by the implementing class. Args: argv: Remaining command line arguments after parsing flags and command (that is a copy of sys.argv at the time of the function call with all parsed flags removed). Returns: 0 for success, anything else for failure (must return with integer). Alternatively you may return None (or not use a return statement at all). Raises: AppCommandsError: Always as in must be overwritten """ raise AppCommandsError('%s.%s.Run() is not implemented' % ( type(self).__module__, type(self).__name__)) def CommandRun(self, argv): """Execute the command with given arguments. First register and parse additional flags. Then run the command. Returns: Command return value. Args: argv: Remaining command line arguments after parsing command and flags (that is a copy of sys.argv at the time of the function call with all parsed flags removed). """ # Register flags global when run normally FLAGS.AppendFlagValues(self._command_flags) # Prepare flags parsing, to redirect help, to show help for command orig_app_usage = app.usage def ReplacementAppUsage(shorthelp=0, writeto_stdout=1, detailed_error=None, exitcode=None): AppcommandsUsage(shorthelp, writeto_stdout, detailed_error, exitcode=1, show_cmd=self._command_name, show_global_flags=True) app.usage = ReplacementAppUsage # Parse flags and restore app.usage afterwards try: try: argv = ParseFlagsWithUsage(argv) # Run command ret = self.Run(argv) if ret is None: ret = 0 else: assert isinstance(ret, int) return ret except app.UsageError, error: app.usage(shorthelp=1, detailed_error=error, exitcode=error.exitcode) except: if FLAGS.pdb_post_mortem: traceback.print_exc() pdb.post_mortem() raise finally: # Restore app.usage and remove this command's flags from the global flags. app.usage = orig_app_usage for flag_name in self._command_flags.FlagDict(): delattr(FLAGS, flag_name) def CommandGetHelp(self, unused_argv, cmd_names=None): """Get help string for command. Args: unused_argv: Remaining command line flags and arguments after parsing command (that is a copy of sys.argv at the time of the function call with all parsed flags removed); unused in this default implementation, but may be used in subclasses. cmd_names: Complete list of commands for which help is being shown at the same time. This is used to determine whether to return _all_commands_help, or the command's docstring. (_all_commands_help is used, if not None, when help is being shown for more than one command, otherwise the command's docstring is used.) Returns: Help string, one of the following (by order): - Result of the registered 'help' function (if any) - Doc string of the Cmd class (if any) - Default fallback string """ if (type(cmd_names) is list and len(cmd_names) > 1 and self._all_commands_help is not None): return flags.DocToHelp(self._all_commands_help) elif self.__doc__: return flags.DocToHelp(self.__doc__) else: return 'No help available' def CommandGetAliases(self): """Get aliases for command. Returns: aliases: list of aliases for the command. """ return self._command_aliases class _FunctionalCmd(Cmd): """Class to wrap functions as CMD instances. Args: cmd_func: command function """ def __init__(self, name, flag_values, cmd_func, all_commands_help=None, **kargs): """Create a functional command. Args: name: Name of command flag_values: FlagValues() instance that needs to be passed as flag_values parameter to any flags registering call. cmd_func: Function to call when command is to be executed. """ Cmd.__init__(self, name, flag_values, **kargs) self._all_commands_help = all_commands_help self._cmd_func = cmd_func def CommandGetHelp(self, unused_argv, cmd_names=None): """Get help for command. Args: unused_argv: Remaining command line flags and arguments after parsing command (that is a copy of sys.argv at the time of the function call with all parsed flags removed); unused in this implementation. cmd_names: By default, if help is being shown for more than one command, and this command defines _all_commands_help, then _all_commands_help will be displayed instead of the class doc. cmd_names is used to determine the number of commands being displayed and if only a single command is display then the class doc is returned. Returns: __doc__ property for command function or a message stating there is no help. """ if (type(cmd_names) is list and len(cmd_names) > 1 and self._all_commands_help is not None): return flags.DocToHelp(self._all_commands_help) if self._cmd_func.__doc__ is not None: return flags.DocToHelp(self._cmd_func.__doc__) else: return 'No help available' def Run(self, argv): """Execute the command with given arguments. Args: argv: Remaining command line flags and arguments after parsing command (that is a copy of sys.argv at the time of the function call with all parsed flags removed). Returns: Command return value. """ return self._cmd_func(argv) def _AddCmdInstance(command_name, cmd, command_aliases=None): """Add a command from a Cmd instance. Args: command_name: name of the command which will be used in argument parsing cmd: Cmd instance to register command_aliases: A list of command aliases that the command can be run as. Raises: AppCommandsError: is command is already registered OR cmd is not a subclass of Cmd AppCommandsError: if name is already registered OR name is not a string OR name is too short OR name does not start with a letter OR name contains any non alphanumeric characters besides '_'. """ # Update global command list. # pylint: disable=global-variable-not-assigned global _cmd_list global _cmd_alias_list if not issubclass(cmd.__class__, Cmd): raise AppCommandsError('Command must be an instance of commands.Cmd') for name in [command_name] + (command_aliases or []): _CheckCmdName(name) _cmd_alias_list[name] = command_name _cmd_list[command_name] = cmd def _CheckCmdName(name_or_alias): """Only allow strings for command names and aliases (reject unicode as well). Args: name_or_alias: properly formatted string name or alias. Raises: AppCommandsError: is command is already registered OR cmd is not a subclass of Cmd AppCommandsError: if name is already registered OR name is not a string OR name is too short OR name does not start with a letter OR name contains any non alphanumeric characters besides '_'. """ if name_or_alias in GetCommandAliasList(): raise AppCommandsError("Command or Alias '%s' already defined" % name_or_alias) if not isinstance(name_or_alias, str) or len(name_or_alias) <= 1: raise AppCommandsError("Command '%s' not a string or too short" % str(name_or_alias)) if not name_or_alias[0].isalpha(): raise AppCommandsError("Command '%s' does not start with a letter" % name_or_alias) if [c for c in name_or_alias if not (c.isalnum() or c == '_')]: raise AppCommandsError("Command '%s' contains non alphanumeric characters" % name_or_alias) def AddCmd(command_name, cmd_factory, **kargs): """Add a command from a Cmd subclass or factory. Args: command_name: name of the command which will be used in argument parsing cmd_factory: A callable whose arguments match those of Cmd.__init__ and returns a Cmd. In the simplest case this is just a subclass of Cmd. command_aliases: A list of command aliases that the command can be run as. Raises: AppCommandsError: if calling cmd_factory does not return an instance of Cmd. """ cmd = cmd_factory(command_name, flags.FlagValues(), **kargs) if not isinstance(cmd, Cmd): raise AppCommandsError('Command must be an instance of commands.Cmd') _AddCmdInstance(command_name, cmd, **kargs) def AddCmdFunc(command_name, cmd_func, command_aliases=None, all_commands_help=None): """Add a new command to the list of registered commands. Args: command_name: name of the command which will be used in argument parsing cmd_func: command function, this function received the remaining arguments as its only parameter. It is supposed to do the command work and then return with the command result that is being used as the shell exit code. command_aliases: A list of command aliases that the command can be run as. all_commands_help: Help message to be displayed in place of func.__doc__ when all commands are displayed. """ _AddCmdInstance(command_name, _FunctionalCmd(command_name, flags.FlagValues(), cmd_func, command_aliases=command_aliases, all_commands_help=all_commands_help), command_aliases) class _CmdHelp(Cmd): """Standard help command. Allows to provide help for all or specific commands. """ def Run(self, argv): """Execute help command. If an argument is given and that argument is a registered command name, then help specific to that command is being displayed. If the command is unknown then a fatal error will be displayed. If no argument is present then help for all commands will be presented. If a specific command help is being generated, the list of commands is temporarily replaced with one containing only that command. Thus the call to usage() will only show help for that command. Otherwise call usage() will show help for all registered commands as it sees all commands. Args: argv: Remaining command line flags and arguments after parsing command (that is a copy of sys.argv at the time of the function call with all parsed flags removed). So argv[0] is the program and argv[1] will be the first argument to the call. For instance 'tool.py help command' will result in argv containing ('tool.py', 'command'). In this case the list of commands is searched for 'command'. Returns: 1 for failure """ if len(argv) > 1 and argv[1] in GetFullCommandList(): show_cmd = argv[1] else: show_cmd = None AppcommandsUsage(shorthelp=0, writeto_stdout=1, detailed_error=None, exitcode=1, show_cmd=show_cmd, show_global_flags=False) def CommandGetHelp(self, unused_argv, cmd_names=None): """Returns: Help for command.""" cmd_help = ('Help for all or selected command:\n' '\t%(prog)s help []\n\n' 'To retrieve help with global flags:\n' '\t%(prog)s --help\n\n' 'To retrieve help with flags only from the main module:\n' '\t%(prog)s --helpshort []\n\n' % {'prog': GetAppBasename()}) return flags.DocToHelp(cmd_help) def GetSynopsis(): """Get synopsis for program. Returns: Synopsis including program basename. """ return '%s [--global_flags] [--command_flags] [args]' % ( GetAppBasename()) def _UsageFooter(detailed_error, cmd_names): """Output a footer at the end of usage or help output. Args: detailed_error: additional detail about why usage info was presented. cmd_names: list of command names for which help was shown or None. Returns: Generated footer that contains 'Run..' messages if appropriate. """ footer = [] if not cmd_names or len(cmd_names) == 1: footer.append("Run '%s help' to see the list of available commands." % GetAppBasename()) if not cmd_names or len(cmd_names) == len(GetCommandList()): footer.append("Run '%s help ' to get help for ." % GetAppBasename()) if detailed_error is not None: if footer: footer.append('') footer.append('%s' % detailed_error) return '\n'.join(footer) def AppcommandsUsage(shorthelp=0, writeto_stdout=0, detailed_error=None, exitcode=None, show_cmd=None, show_global_flags=False): """Output usage or help information. Extracts the __doc__ string from the __main__ module and writes it to stderr. If that string contains a '%s' then that is replaced by the command pathname. Otherwise a default usage string is being generated. The output varies depending on the following: - FLAGS.help - FLAGS.helpshort - show_cmd - show_global_flags Args: shorthelp: print only command and main module flags, rather than all. writeto_stdout: write help message to stdout, rather than to stderr. detailed_error: additional details about why usage info was presented. exitcode: if set, exit with this status code after writing help. show_cmd: show help for this command only (name of command). show_global_flags: show help for global flags. """ if writeto_stdout: stdfile = sys.stdout else: stdfile = sys.stderr prefix = ''.rjust(GetMaxCommandLength() + 2) # Deal with header, containing general tool documentation doc = sys.modules['__main__'].__doc__ if doc: help_msg = flags.DocToHelp(doc.replace('%s', sys.argv[0])) stdfile.write(flags.TextWrap(help_msg, flags.GetHelpWidth())) stdfile.write('\n\n\n') if not doc or doc.find('%s') == -1: synopsis = 'USAGE: ' + GetSynopsis() stdfile.write(flags.TextWrap(synopsis, flags.GetHelpWidth(), ' ', '')) stdfile.write('\n\n\n') # Special case just 'help' registered, that means run as 'tool --help'. if len(GetCommandList()) == 1: cmd_names = [] else: # Show list of commands if show_cmd is None or show_cmd == 'help': cmd_names = GetCommandList().keys() cmd_names.sort() stdfile.write('Any of the following commands:\n') doc = ', '.join(cmd_names) stdfile.write(flags.TextWrap(doc, flags.GetHelpWidth(), ' ')) stdfile.write('\n\n\n') # Prepare list of commands to show help for if show_cmd is not None: cmd_names = [show_cmd] # show only one command elif FLAGS.help or FLAGS.helpshort or shorthelp: cmd_names = [] else: cmd_names = GetCommandList().keys() # show all commands cmd_names.sort() # Show the command help (none, one specific, or all) for name in cmd_names: command = GetCommandByName(name) try: cmd_help = command.CommandGetHelp(GetCommandArgv(), cmd_names=cmd_names) except Exception as error: cmd_help = "Internal error for command '%s': %s." % (name, str(error)) cmd_help = cmd_help.strip() all_names = ', '.join([name] + (command.CommandGetAliases() or [])) if len(all_names) + 1 >= len(prefix) or not cmd_help: # If command/alias list would reach over help block-indent # start the help block on a new line. stdfile.write(flags.TextWrap(all_names, flags.GetHelpWidth())) stdfile.write('\n') prefix1 = prefix else: prefix1 = all_names.ljust(GetMaxCommandLength() + 2) if cmd_help: stdfile.write(flags.TextWrap(cmd_help, flags.GetHelpWidth(), prefix, prefix1)) stdfile.write('\n\n') else: stdfile.write('\n') # When showing help for exactly one command we show its flags if len(cmd_names) == 1: # Need to register flags for command prior to be able to use them. # We do not register them globally so that they do not reappear. # pylint: disable=protected-access cmd_flags = command._command_flags if cmd_flags.RegisteredFlags(): stdfile.write('%sFlags for %s:\n' % (prefix, name)) stdfile.write(cmd_flags.GetHelp(prefix+' ')) stdfile.write('\n\n') stdfile.write('\n') # Now show global flags as asked for if show_global_flags: stdfile.write('Global flags:\n') if shorthelp: stdfile.write(FLAGS.MainModuleHelp()) else: stdfile.write(FLAGS.GetHelp()) stdfile.write('\n') else: stdfile.write("Run '%s --help' to get help for global flags." % GetAppBasename()) stdfile.write('\n%s\n' % _UsageFooter(detailed_error, cmd_names)) if exitcode is not None: sys.exit(exitcode) def ParseFlagsWithUsage(argv): """Parse the flags, exiting (after printing usage) if they are unparseable. Args: argv: command line arguments Returns: remaining command line arguments after parsing flags """ # Update the global commands. # pylint: disable=global-statement global _cmd_argv try: _cmd_argv = FLAGS(argv) return _cmd_argv except flags.FlagsError, error: ShortHelpAndExit('FATAL Flags parsing error: %s' % error) def GetCommand(command_required): """Get the command or return None (or issue an error) if there is none. Args: command_required: whether to issue an error if no command is present Returns: command or None, if command_required is True then return value is a valid command or the program will exit. The program also exits if a command was specified but that command does not exist. """ # Update the global commands. # pylint: disable=global-statement global _cmd_argv _cmd_argv = ParseFlagsWithUsage(_cmd_argv) if len(_cmd_argv) < 2: if command_required: ShortHelpAndExit('FATAL Command expected but none given') return None command = GetCommandByName(_cmd_argv[1]) if command is None: ShortHelpAndExit("FATAL Command '%s' unknown" % _cmd_argv[1]) del _cmd_argv[1] return command def SetDefaultCommand(default_command): """Change the default command to execute if none is explicitly given. Args: default_command: str, the name of the command to execute by default. """ # pylint: disable=global-statement,g-bad-name global _cmd_default _cmd_default = default_command def _CommandsStart(unused_argv): """Main initialization. Calls __main__.main(), and then the command indicated by the first non-flag argument, or 'help' if no argument was given. (The command to execute if no flag is given can be changed via SetDefaultCommand). Only non-flag arguments are passed to main(). If main does not call sys.exit, the return value of the command is used as the exit status. """ # The following is supposed to return after registering additional commands try: sys.modules['__main__'].main(GetCommandArgv()) # If sys.exit was called, return with error code. except SystemExit, e: sys.exit(e.code) except Exception, error: traceback.print_exc() # Print a backtrace to stderr. ShortHelpAndExit('\nFATAL error in main: %s' % error) if len(GetCommandArgv()) > 1: command = GetCommand(command_required=True) else: command = GetCommandByName(_cmd_default) if command is None: ShortHelpAndExit("FATAL Command '%s' unknown" % _cmd_default) sys.exit(command.CommandRun(GetCommandArgv())) def Run(): """This must be called from __main__ modules main, instead of app.run(). app.run will base its actions on its stacktrace. Returns: app.run() """ app.parse_flags_with_usage = ParseFlagsWithUsage original_really_start = app.really_start def InterceptReallyStart(): original_really_start(main=_CommandsStart) app.really_start = InterceptReallyStart app.usage = _ReplacementAppUsage return app.run() # Always register 'help' command AddCmd('help', _CmdHelp) def _ReplacementAppUsage(shorthelp=0, writeto_stdout=0, detailed_error=None, exitcode=None): AppcommandsUsage(shorthelp, writeto_stdout, detailed_error, exitcode=exitcode, show_cmd=None, show_global_flags=True) if __name__ == '__main__': Run() google-apputils-0.4.0/google/apputils/stopwatch.py0000640003611500116100000001430512176032264023150 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2005 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A useful class for digesting, on a high-level, where time in a program goes. Usage: sw = StopWatch() sw.start() sw.start('foo') foo() sw.stop('foo') args = overhead_code() sw.start('bar') bar(args) sw.stop('bar') sw.dump() If you start a new timer when one is already running, then the other one will stop running, and restart when you stop this timer. This behavior is very useful for when you want to try timing for a subcall without remembering what is already running. For instance: sw.start('all_this') do_some_stuff() sw.start('just_that') small_but_expensive_function() sw.stop('just_that') cleanup_code() sw.stop('all_this') In this case, the output will be what you want: the time spent in small_but_expensive function will show up in the timer for just_that and not all_this. """ import StringIO import time __owner__ = 'dbentley@google.com (Dan Bentley)' class StopWatch(object): """Class encapsulating a timer; see above for example usage. Instance variables: timers: map of stopwatch name -> time for each currently running stopwatch, where time is seconds from the epoch of when this stopwatch was started. accum: map of stopwatch name -> accumulated time, in seconds, it has already been run for. stopped: map of timer name -> list of timer names that are blocking it. counters: map of timer name -> number of times it has been started. """ def __init__(self): self.timers = {} self.accum = {} self.stopped = {} self.counters = {} def start(self, timer='total', stop_others=True): """Start a timer. Args: timer: str; name of the timer to start, defaults to the overall timer. stop_others: bool; if True, stop all other running timers. If False, then you can have time that is spent inside more than one timer and there's a good chance that the overhead measured will be negative. """ if stop_others: stopped = [] for other in list(self.timers): if not other == 'total': self.stop(other) stopped.append(other) self.stopped[timer] = stopped self.counters[timer] = self.counters.get(timer, 0) + 1 self.timers[timer] = time.time() def stop(self, timer='total'): """Stop a running timer. This includes restarting anything that was stopped on behalf of this timer. Args: timer: str; name of the timer to stop, defaults to the overall timer. Raises: RuntimeError: if timer refers to a timer that was never started. """ if timer not in self.timers: raise RuntimeError( 'Tried to stop timer that was never started: %s' % timer) self.accum[timer] = self.timervalue(timer) del self.timers[timer] for stopped in self.stopped.get(timer, []): self.start(stopped, stop_others=0) def timervalue(self, timer='total', now=None): """Return the value seen by this timer so far. If the timer is stopped, this will be the accumulated time it has seen. If the timer is running, this will be the time it has seen up to now. If the timer has never been started, this will be zero. Args: timer: str; the name of the timer to report on. now: long; if provided, the time to use for 'now' for running timers. """ if not now: now = time.time() if timer in self.timers: # Timer is running now. return self.accum.get(timer, 0.0) + (now - self.timers[timer]) elif timer in self.accum: # Timer is stopped. return self.accum[timer] else: # Timer is never started. return 0.0 def overhead(self, now=None): """Calculate the overhead. Args: now: (optional) time to use as the current time. Returns: The overhead, that is, time spent in total but not in any sub timer. This may be negative if time was counted in two sub timers. Avoid this by always using stop_others. """ total = self.timervalue('total', now) if total == 0.0: return 0.0 all_timers = sum(self.accum.itervalues()) return total - (all_timers - total) def results(self, verbose=False): """Get the results of this stopwatch. Args: verbose: bool; if True, show all times; otherwise, show only the total. Returns: A list of tuples showing the output of this stopwatch, of the form (name, value, num_starts) for each timer. Note that if the total timer is not used, non-verbose results will be the empty list. """ now = time.time() all_names = self.accum.keys() names = [] if 'total' in all_names: all_names.remove('total') all_names.sort() if verbose: names = all_names results = [(name, self.timervalue(name, now=now), self.counters[name]) for name in names] if verbose: results.append(('overhead', self.overhead(now=now), 1)) if 'total' in self.accum or 'total' in self.timers: results.append(('total', self.timervalue('total', now=now), self.counters['total'])) return results def dump(self, verbose=False): """Describes where time in this stopwatch was spent. Args: verbose: bool; if True, show all timers; otherwise, show only the total. Returns: A string describing the stopwatch. """ output = StringIO.StringIO() results = self.results(verbose=verbose) maxlength = max([len(result[0]) for result in results]) for result in results: output.write('%*s: %6.2fs\n' % (maxlength, result[0], result[1])) return output.getvalue() # Create a stopwatch to be publicly used. sw = StopWatch() google-apputils-0.4.0/google/apputils/datelib.py0000640003611500116100000003606512176032264022547 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2002 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Set of classes and functions for dealing with dates and timestamps. The BaseTimestamp and Timestamp are timezone-aware wrappers around Python datetime.datetime class. """ import calendar import copy import datetime import re import sys import time import types import warnings from dateutil import parser import pytz _MICROSECONDS_PER_SECOND = 1000000 _MICROSECONDS_PER_SECOND_F = float(_MICROSECONDS_PER_SECOND) def SecondsToMicroseconds(seconds): """Convert seconds to microseconds. Args: seconds: number Returns: microseconds """ return seconds * _MICROSECONDS_PER_SECOND def MicrosecondsToSeconds(microseconds): """Convert microseconds to seconds. Args: microseconds: A number representing some duration of time measured in microseconds. Returns: A number representing the same duration of time measured in seconds. """ return microseconds / _MICROSECONDS_PER_SECOND_F def _GetCurrentTimeMicros(): """Get the current time in microseconds, in UTC. Returns: The number of microseconds since the epoch. """ return int(SecondsToMicroseconds(time.time())) def GetSecondsSinceEpoch(time_tuple): """Convert time_tuple (in UTC) to seconds (also in UTC). Args: time_tuple: tuple with at least 6 items. Returns: seconds. """ return calendar.timegm(time_tuple[:6] + (0, 0, 0)) def GetTimeMicros(time_tuple): """Get a time in microseconds. Arguments: time_tuple: A (year, month, day, hour, minute, second) tuple (the python time tuple format) in the UTC time zone. Returns: The number of microseconds since the epoch represented by the input tuple. """ return int(SecondsToMicroseconds(GetSecondsSinceEpoch(time_tuple))) def DatetimeToUTCMicros(date): """Converts a datetime object to microseconds since the epoch in UTC. Args: date: A datetime to convert. Returns: The number of microseconds since the epoch, in UTC, represented by the input datetime. """ # Using this guide: http://wiki.python.org/moin/WorkingWithTime # And this conversion guide: http://docs.python.org/library/time.html # Turn the date parameter into a tuple (struct_time) that can then be # manipulated into a long value of seconds. During the conversion from # struct_time to long, the source date in UTC, and so it follows that the # correct transformation is calendar.timegm() micros = calendar.timegm(date.utctimetuple()) * _MICROSECONDS_PER_SECOND return micros + date.microsecond def DatetimeToUTCMillis(date): """Converts a datetime object to milliseconds since the epoch in UTC. Args: date: A datetime to convert. Returns: The number of milliseconds since the epoch, in UTC, represented by the input datetime. """ return DatetimeToUTCMicros(date) / 1000 def UTCMicrosToDatetime(micros, tz=None): """Converts a microsecond epoch time to a datetime object. Args: micros: A UTC time, expressed in microseconds since the epoch. tz: The desired tzinfo for the datetime object. If None, the datetime will be naive. Returns: The datetime represented by the input value. """ # The conversion from micros to seconds for input into the # utcfromtimestamp function needs to be done as a float to make sure # we dont lose the sub-second resolution of the input time. dt = datetime.datetime.utcfromtimestamp( micros / _MICROSECONDS_PER_SECOND_F) if tz is not None: dt = tz.fromutc(dt) return dt def UTCMillisToDatetime(millis, tz=None): """Converts a millisecond epoch time to a datetime object. Args: millis: A UTC time, expressed in milliseconds since the epoch. tz: The desired tzinfo for the datetime object. If None, the datetime will be naive. Returns: The datetime represented by the input value. """ return UTCMicrosToDatetime(millis * 1000, tz) UTC = pytz.UTC US_PACIFIC = pytz.timezone('US/Pacific') class TimestampError(ValueError): """Generic timestamp-related error.""" pass class TimezoneNotSpecifiedError(TimestampError): """This error is raised when timezone is not specified.""" pass class TimeParseError(TimestampError): """This error is raised when we can't parse the input.""" pass # TODO(user): this class needs to handle daylight better class LocalTimezoneClass(datetime.tzinfo): """This class defines local timezone.""" ZERO = datetime.timedelta(0) HOUR = datetime.timedelta(hours=1) STDOFFSET = datetime.timedelta(seconds=-time.timezone) if time.daylight: DSTOFFSET = datetime.timedelta(seconds=-time.altzone) else: DSTOFFSET = STDOFFSET DSTDIFF = DSTOFFSET - STDOFFSET def utcoffset(self, dt): """datetime -> minutes east of UTC (negative for west of UTC).""" if self._isdst(dt): return self.DSTOFFSET else: return self.STDOFFSET def dst(self, dt): """datetime -> DST offset in minutes east of UTC.""" if self._isdst(dt): return self.DSTDIFF else: return self.ZERO def tzname(self, dt): """datetime -> string name of time zone.""" return time.tzname[self._isdst(dt)] def _isdst(self, dt): """Return true if given datetime is within local DST.""" tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.weekday(), 0, -1) stamp = time.mktime(tt) tt = time.localtime(stamp) return tt.tm_isdst > 0 def __repr__(self): """Return string ''.""" return '' def localize(self, dt, unused_is_dst=False): """Convert naive time to local time.""" if dt.tzinfo is not None: raise ValueError('Not naive datetime (tzinfo is already set)') return dt.replace(tzinfo=self) def normalize(self, dt, unused_is_dst=False): """Correct the timezone information on the given datetime.""" if dt.tzinfo is None: raise ValueError('Naive time - no tzinfo set') return dt.replace(tzinfo=self) LocalTimezone = LocalTimezoneClass() class BaseTimestamp(datetime.datetime): """Our kind of wrapper over datetime.datetime. The objects produced by methods now, today, fromtimestamp, utcnow, utcfromtimestamp are timezone-aware (with correct timezone). We also overload __add__ and __sub__ method, to fix the result of arithmetic operations. """ LocalTimezone = LocalTimezone @classmethod def AddLocalTimezone(cls, obj): """If obj is naive, add local timezone to it.""" if not obj.tzinfo: return obj.replace(tzinfo=cls.LocalTimezone) return obj @classmethod def Localize(cls, obj): """If obj is naive, localize it to cls.LocalTimezone.""" if not obj.tzinfo: return cls.LocalTimezone.localize(obj) return obj def __add__(self, *args, **kwargs): """x.__add__(y) <==> x+y.""" r = super(BaseTimestamp, self).__add__(*args, **kwargs) return type(self)(r.year, r.month, r.day, r.hour, r.minute, r.second, r.microsecond, r.tzinfo) def __sub__(self, *args, **kwargs): """x.__add__(y) <==> x-y.""" r = super(BaseTimestamp, self).__sub__(*args, **kwargs) if isinstance(r, datetime.datetime): return type(self)(r.year, r.month, r.day, r.hour, r.minute, r.second, r.microsecond, r.tzinfo) return r @classmethod def now(cls, *args, **kwargs): """Get a timestamp corresponding to right now. Args: args: Positional arguments to pass to datetime.datetime.now(). kwargs: Keyword arguments to pass to datetime.datetime.now(). If tz is not specified, local timezone is assumed. Returns: A new BaseTimestamp with tz's local day and time. """ return cls.AddLocalTimezone( super(BaseTimestamp, cls).now(*args, **kwargs)) @classmethod def today(cls): """Current BaseTimestamp. Same as self.__class__.fromtimestamp(time.time()). Returns: New self.__class__. """ return cls.AddLocalTimezone(super(BaseTimestamp, cls).today()) @classmethod def fromtimestamp(cls, *args, **kwargs): """Get a new localized timestamp from a POSIX timestamp. Args: args: Positional arguments to pass to datetime.datetime.fromtimestamp(). kwargs: Keyword arguments to pass to datetime.datetime.fromtimestamp(). If tz is not specified, local timezone is assumed. Returns: A new BaseTimestamp with tz's local day and time. """ return cls.Localize( super(BaseTimestamp, cls).fromtimestamp(*args, **kwargs)) @classmethod def utcnow(cls): """Return a new BaseTimestamp representing UTC day and time.""" return super(BaseTimestamp, cls).utcnow().replace(tzinfo=pytz.utc) @classmethod def utcfromtimestamp(cls, *args, **kwargs): """timestamp -> UTC datetime from a POSIX timestamp (like time.time()).""" return super(BaseTimestamp, cls).utcfromtimestamp( *args, **kwargs).replace(tzinfo=pytz.utc) @classmethod def strptime(cls, date_string, format, tz=None): """Parse date_string according to format and construct BaseTimestamp. Args: date_string: string passed to time.strptime. format: format string passed to time.strptime. tz: if not specified, local timezone assumed. Returns: New BaseTimestamp. """ if tz is None: return cls.Localize(cls(*(time.strptime(date_string, format)[:6]))) return tz.localize(cls(*(time.strptime(date_string, format)[:6]))) def astimezone(self, *args, **kwargs): """tz -> convert to time in new timezone tz.""" r = super(BaseTimestamp, self).astimezone(*args, **kwargs) return type(self)(r.year, r.month, r.day, r.hour, r.minute, r.second, r.microsecond, r.tzinfo) @classmethod def FromMicroTimestamp(cls, ts): """Create new Timestamp object from microsecond UTC timestamp value. Args: ts: integer microsecond UTC timestamp Returns: New cls() """ return cls.utcfromtimestamp(ts/_MICROSECONDS_PER_SECOND_F) def AsSecondsSinceEpoch(self): """Return number of seconds since epoch (timestamp in seconds).""" return GetSecondsSinceEpoch(self.utctimetuple()) def AsMicroTimestamp(self): """Return microsecond timestamp constructed from this object.""" return (SecondsToMicroseconds(self.AsSecondsSinceEpoch()) + self.microsecond) @classmethod def combine(cls, datepart, timepart, tz=None): """Combine date and time into timestamp, timezone-aware. Args: datepart: datetime.date timepart: datetime.time tz: timezone or None Returns: timestamp object """ result = super(BaseTimestamp, cls).combine(datepart, timepart) if tz: result = tz.localize(result) return result # Conversions from interval suffixes to number of seconds. # (m => 60s, d => 86400s, etc) _INTERVAL_CONV_DICT = {'s': 1} _INTERVAL_CONV_DICT['m'] = 60 * _INTERVAL_CONV_DICT['s'] _INTERVAL_CONV_DICT['h'] = 60 * _INTERVAL_CONV_DICT['m'] _INTERVAL_CONV_DICT['d'] = 24 * _INTERVAL_CONV_DICT['h'] _INTERVAL_CONV_DICT['D'] = _INTERVAL_CONV_DICT['d'] _INTERVAL_CONV_DICT['w'] = 7 * _INTERVAL_CONV_DICT['d'] _INTERVAL_CONV_DICT['W'] = _INTERVAL_CONV_DICT['w'] _INTERVAL_CONV_DICT['M'] = 30 * _INTERVAL_CONV_DICT['d'] _INTERVAL_CONV_DICT['Y'] = 365 * _INTERVAL_CONV_DICT['d'] _INTERVAL_REGEXP = re.compile('^([0-9]+)([%s])?' % ''.join(_INTERVAL_CONV_DICT)) def ConvertIntervalToSeconds(interval): """Convert a formatted string representing an interval into seconds. Args: interval: String to interpret as an interval. A basic interval looks like "". Complex intervals consisting of a chain of basic intervals are also allowed. Returns: An integer representing the number of seconds represented by the interval string, or None if the interval string could not be decoded. """ total = 0 while interval: match = _INTERVAL_REGEXP.match(interval) if not match: return None try: num = int(match.group(1)) except ValueError: return None suffix = match.group(2) if suffix: multiplier = _INTERVAL_CONV_DICT.get(suffix) if not multiplier: return None num *= multiplier total += num interval = interval[match.end(0):] return total class Timestamp(BaseTimestamp): """This subclass contains methods to parse W3C and interval date spec. The interval date specification is in the form "1D", where "D" can be "s"econds "m"inutes "h"ours "D"ays "W"eeks "M"onths "Y"ears. """ INTERVAL_CONV_DICT = _INTERVAL_CONV_DICT INTERVAL_REGEXP = _INTERVAL_REGEXP @classmethod def _StringToTime(cls, timestring, tz=None): """Use dateutil.parser to convert string into timestamp. dateutil.parser understands ISO8601 which is really handy. Args: timestring: string with datetime tz: optional timezone, if timezone is omitted from timestring. Returns: New Timestamp or None if unable to parse the timestring. """ try: r = parser.parse(timestring) except ValueError: return None if not r.tzinfo: r = (tz or cls.LocalTimezone).localize(r) result = cls(r.year, r.month, r.day, r.hour, r.minute, r.second, r.microsecond, r.tzinfo) return result @classmethod def _IntStringToInterval(cls, timestring): """Parse interval date specification and create a timedelta object. Args: timestring: string interval. Returns: A datetime.timedelta representing the specified interval or None if unable to parse the timestring. """ seconds = ConvertIntervalToSeconds(timestring) return datetime.timedelta(seconds=seconds) if seconds else None @classmethod def FromString(cls, value, tz=None): """Create a Timestamp from a string. Args: value: String interval or datetime. e.g. "2013-01-05 13:00:00" or "1d" tz: optional timezone, if timezone is omitted from timestring. Returns: A new Timestamp. Raises: TimeParseError if unable to parse value. """ result = cls._StringToTime(value, tz=tz) if result: return result result = cls._IntStringToInterval(value) if result: return cls.utcnow() - result raise TimeParseError(value) # What's written below is a clear python bug. I mean, okay, I can apply # negative timezone to it and end result will be inconversible. MAXIMUM_PYTHON_TIMESTAMP = Timestamp( 9999, 12, 31, 23, 59, 59, 999999, UTC) # This is also a bug. It is called 32bit time_t. I hate it. # This is fixed in 2.5, btw. MAXIMUM_MICROSECOND_TIMESTAMP = 0x80000000 * _MICROSECONDS_PER_SECOND - 1 MAXIMUM_MICROSECOND_TIMESTAMP_AS_TS = Timestamp(2038, 1, 19, 3, 14, 7, 999999) google-apputils-0.4.0/google/apputils/debug.py0000640003611500116100000000352612176032264022225 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2004 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # This code must be source compatible with Python 2.6 through 3.3. """Import this module to add a hook to call pdb on uncaught exceptions. To enable this, do the following in your top-level application: import google.apputils.debug and then in your main(): google.apputils.debug.Init() Then run your program with --pdb. """ import sys import gflags as flags flags.DEFINE_boolean('pdb', 0, 'Drop into pdb on uncaught exceptions') old_excepthook = None def _DebugHandler(exc_class, value, tb): if not flags.FLAGS.pdb or hasattr(sys, 'ps1') or not sys.stderr.isatty(): # we aren't in interactive mode or we don't have a tty-like # device, so we call the default hook old_excepthook(exc_class, value, tb) else: # Don't impose import overhead on apps that never raise an exception. import traceback import pdb # we are in interactive mode, print the exception... traceback.print_exception(exc_class, value, tb) sys.stdout.write('\n') # ...then start the debugger in post-mortem mode. pdb.pm() def Init(): # Must back up old excepthook. global old_excepthook # pylint: disable=global-statement if old_excepthook is None: old_excepthook = sys.excepthook sys.excepthook = _DebugHandler google-apputils-0.4.0/google/apputils/resources.py0000640003611500116100000000412112176032264023141 0ustar dborowitzeng00000000000000#!/usr/bin/env python # Copyright 2010 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Wrapper around setuptools' pkg_resources with more Google-like names. This module is not very useful on its own, but many Google open-source projects are used to a different naming scheme, and this module makes the transition easier. """ __author__ = 'dborowitz@google.com (Dave Borowitz)' import atexit import pkg_resources def _Call(func, name): """Call a pkg_resources function. Args: func: A function from pkg_resources that takes the arguments (package_or_requirement, resource_name); for more info, see http://peak.telecommunity.com/DevCenter/PkgResources name: A name of the form 'module.name:path/to/resource'; this should generally be built from __name__ in the calling module. Returns: The result of calling the function on the split resource name. """ pkg_name, resource_name = name.split(':', 1) return func(pkg_name, resource_name) def GetResource(name): """Get a resource as a string; see _Call.""" return _Call(pkg_resources.resource_string, name) def GetResourceAsFile(name): """Get a resource as a file-like object; see _Call.""" return _Call(pkg_resources.resource_stream, name) _extracted_files = False def GetResourceFilename(name): """Get a filename for a resource; see _Call.""" global _extracted_files # pylint: disable=global-statement if not _extracted_files: atexit.register(pkg_resources.cleanup_resources) _extracted_files = True return _Call(pkg_resources.resource_filename, name) google-apputils-0.4.0/setup.cfg0000640003611500116100000000007312176034015017257 0ustar dborowitzeng00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 google-apputils-0.4.0/google_apputils.egg-info/0000750003611500116100000000000012176034015022324 5ustar dborowitzeng00000000000000google-apputils-0.4.0/google_apputils.egg-info/requires.txt0000640003611500116100000000006512176034015024726 0ustar dborowitzeng00000000000000python-dateutil>=1.4,<2 python-gflags>=1.4 pytz>=2010google-apputils-0.4.0/google_apputils.egg-info/dependency_links.txt0000640003611500116100000000000112176034015026373 0ustar dborowitzeng00000000000000 google-apputils-0.4.0/google_apputils.egg-info/entry_points.txt0000640003611500116100000000025712176034015025627 0ustar dborowitzeng00000000000000[distutils.setup_keywords] google_test_dir = google.apputils.setup_command:ValidateGoogleTestDir [distutils.commands] google_test = google.apputils.setup_command:GoogleTest google-apputils-0.4.0/google_apputils.egg-info/SOURCES.txt0000640003611500116100000000211512176034015024210 0ustar dborowitzeng00000000000000LICENSE README ez_setup.py setup.py google/__init__.py google/apputils/__init__.py google/apputils/app.py google/apputils/appcommands.py google/apputils/basetest.py google/apputils/datelib.py google/apputils/debug.py google/apputils/file_util.py google/apputils/humanize.py google/apputils/resources.py google/apputils/run_script_module.py google/apputils/setup_command.py google/apputils/shellutil.py google/apputils/stopwatch.py google_apputils.egg-info/PKG-INFO google_apputils.egg-info/SOURCES.txt google_apputils.egg-info/dependency_links.txt google_apputils.egg-info/entry_points.txt google_apputils.egg-info/namespace_packages.txt google_apputils.egg-info/requires.txt google_apputils.egg-info/top_level.txt tests/__init__.py tests/app_test.py tests/app_test_helper.py tests/app_unittest.sh tests/appcommands_example.py tests/appcommands_unittest.sh tests/basetest_sh_test.sh tests/basetest_test.py tests/datelib_unittest.py tests/file_util_test.py tests/humanize_test.py tests/resources_test.py tests/sh_test.py tests/shellutil_unittest.py tests/stopwatch_unittest.py tests/data/a tests/data/bgoogle-apputils-0.4.0/google_apputils.egg-info/namespace_packages.txt0000640003611500116100000000000712176034015026655 0ustar dborowitzeng00000000000000google google-apputils-0.4.0/google_apputils.egg-info/top_level.txt0000640003611500116100000000000712176034015025054 0ustar dborowitzeng00000000000000google google-apputils-0.4.0/google_apputils.egg-info/PKG-INFO0000640003611500116100000000036712176034015023430 0ustar dborowitzeng00000000000000Metadata-Version: 1.0 Name: google-apputils Version: 0.4.0 Summary: UNKNOWN Home-page: http://code.google.com/p/google-apputils-python Author: Google Inc. Author-email: opensource@google.com License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN google-apputils-0.4.0/README0000640003611500116100000000556012176032264016330 0ustar dborowitzeng00000000000000Google Application Utilities for Python ======================================= This project is a small collection of utilities for building Python applications. It includes some of the same set of utilities used to build and run internal Python apps at Google. Features: * Simple application startup integrated with python-gflags. * Subcommands for command-line applications. * Option to drop into pdb on uncaught exceptions. * Helper functions for dealing with files. * High-level profiling tools. * Timezone-aware wrappers for datetime.datetime classes. * Improved TestCase with the same methods as unittest2, plus helpful flags for test startup. * google_test setuptools command for running tests. * Helper module for creating application stubs. Installation ============ To install the package, simply run: python setup.py install Google-Style Tests ================== Google-style tests (those run with basetest.main()) differ from setuptools-style tests in that test modules are designed to be run as __main__. Setting up your project to use Google-style tests is easy: 1. Create one or more test modules named '*_test.py' in a directory. Each test module should have a main block that runs basetest.main(): # In tests/my_test.py from google.apputils import basetest class MyTest(basetest.TestCase): def testSomething(self): self.assertTrue('my test') if __name__ == '__main__': basetest.main() 2. Add a setup requirement on google-apputils and set the test_dir option: # In setup.py setup( ... setup_requires = ['google-apputils>=0.2'], test_dir = 'tests', ) 3. Run your tests: python setup.py google_test Google-Style Stub Scripts ========================= Google-style binaries (run with app.run()) are intended to be executed directly at the top level, so you should not use a setuptools console_script entry point to point at your main(). You can use distutils-style scripts if you want. Another alternative is to use google.apputils.run_script_module, which is a handy wrapper to execute a module directly as if it were a script: 1. Create a module like 'stubs.py' in your project: # In my/stubs.py from google.apputils import run_script_module def RunMyScript(): import my.script run_script_module.RunScriptModule(my.script) def RunMyOtherScript(): import my.other_script run_script_module.RunScriptModule(my.other_script) 2. Set up entry points in setup.py that point to the functions in your stubs module: # In setup.py setup( ... entry_points = { 'console_scripts': [ 'my_script = my.stubs:RunMyScript', 'my_other_script = my.stubs.RunMyOtherScript', ], }, ) There are also useful flags you can pass to your scripts to help you debug your binaries; run your binary with --helpstub to see the full list.