diff --git a/.gitignore b/.gitignore index ff18ea7962e..0b87641e53b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ output/ .sqlmap_history traffic.txt -*~ \ No newline at end of file +*~ +.#* + diff --git a/extra/cloak/cloak.py b/extra/cloak/cloak.py old mode 100755 new mode 100644 diff --git a/extra/icmpsh/icmpsh-m.pl b/extra/icmpsh/icmpsh-m.pl old mode 100755 new mode 100644 diff --git a/extra/icmpsh/icmpsh.exe_ b/extra/icmpsh/icmpsh.exe_ old mode 100644 new mode 100755 diff --git a/extra/shellcodeexec/windows/shellcodeexec.x32.exe_ b/extra/shellcodeexec/windows/shellcodeexec.x32.exe_ old mode 100644 new mode 100755 diff --git a/extra/shutils/blanks.sh b/extra/shutils/blanks.sh old mode 100755 new mode 100644 diff --git a/extra/shutils/pep8.sh b/extra/shutils/pep8.sh old mode 100755 new mode 100644 diff --git a/extra/shutils/regressiontest.py b/extra/shutils/regressiontest.py old mode 100755 new mode 100644 diff --git a/lib/core/common.py b/lib/core/common.py old mode 100755 new mode 100644 diff --git a/lib/request/basic.py b/lib/request/basic.py old mode 100755 new mode 100644 diff --git a/shell/runcmd.exe_ b/shell/runcmd.exe_ old mode 100644 new mode 100755 diff --git a/sqlmap.py b/sqlmap.py old mode 100755 new mode 100644 diff --git a/sqlmapapi.py b/sqlmapapi.py old mode 100755 new mode 100644 diff --git a/test/_runner_tests.py b/test/_runner_tests.py new file mode 100644 index 00000000000..ad0ad6847b5 --- /dev/null +++ b/test/_runner_tests.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import unittest + +from runner.runner_tests.test_mountain import TestMountain +from runner.runner_tests.test_sensei import TestSensei +from runner.runner_tests.test_helper import TestHelper + +def suite(): + suite = unittest.TestSuite() + suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMountain)) + suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestSensei)) + suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestHelper)) + return suite + +if __name__ == '__main__': + res = unittest.TextTestRunner(verbosity=2).run(suite()) + sys.exit(not res.wasSuccessful()) diff --git a/test/contemplate_koans.py b/test/contemplate_koans.py new file mode 100644 index 00000000000..34e06d7bb1f --- /dev/null +++ b/test/contemplate_koans.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# +# Acknowledgment: +# +# Python Koans is a port of Ruby Koans originally written by Jim Weirich +# and Joe O'brien of Edgecase. There are some differences and tweaks specific +# to the Python language, but a great deal of it has been copied wholesale. +# So thank guys! +# + +import sys + + +if __name__ == '__main__': + if sys.version_info >= (3, 0): + print("\nThis is the Python 2 version of Python Koans, but you are " + + "running it with Python 3 or newer!\n\n" + "Did you accidentally use the wrong python script? \nTry:\n\n" + + " python contemplate_koans.py\n") + else: + if sys.version_info < (2, 7): + print("\n" + + "********************************************************\n" + + "WARNING:\n" + + "This version of Python Koans was designed for " + + "Python 2.7 or greater.\n" + + "Your version of Python is older, so you may run into " + + "problems!\n\n" + + "But lets see how far we get...\n" + + "********************************************************\n") + + from runner.mountain import Mountain + + Mountain().walk_the_path(sys.argv) diff --git a/test/koans/.#about_datatype.py b/test/koans/.#about_datatype.py new file mode 120000 index 00000000000..57f928008d2 --- /dev/null +++ b/test/koans/.#about_datatype.py @@ -0,0 +1 @@ +k@linux-896c.site.1583:1413325929 \ No newline at end of file diff --git a/test/koans/__init__.py b/test/koans/__init__.py new file mode 100644 index 00000000000..a11870b25ec --- /dev/null +++ b/test/koans/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# koans diff --git a/test/koans/about_agent.py b/test/koans/about_agent.py new file mode 100644 index 00000000000..cc0bc1ef70d --- /dev/null +++ b/test/koans/about_agent.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from runner.koan import * +# http://stackoverflow.com/questions/14509192/how-to-import-functions-from-other-projects-in-python +from os import path +import sys +sys.path.append(path.abspath("../../sqlmap")) + +from lib.core.agent import agent + +class AboutAgent(Koan): + + def test_assert_agent_name(self): + """ + test sqlmap main function + """ + self.assertEqual("Agent", agent.__class__.__name__) + + def test_assert_agent_pyloadDirect(self): + """ + test payloadDirect(self, query) + """ diff --git a/test/koans/about_data.py b/test/koans/about_data.py new file mode 100644 index 00000000000..e3bb51282ee --- /dev/null +++ b/test/koans/about_data.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from runner.koan import * +from lib.core.data import paths +from lib.core.common import setPaths +from sqlmap import modulePath + +from lib.core.data import cmdLineOptions +from lib.parse.cmdline import cmdLineParser +from lib.core.option import initOptions + +from lib.core.data import kb +from lib.core.data import conf + +from lib.core.data import logger + + +class AboutData(Koan): + + def test_paths_setPaths(self): + self.assertEqual({}, paths) + paths.SQLMAP_ROOT_PATH = modulePath() +# self.assertEqual({'SQLMAP_ROOT_PATH': u'/home/k/Develop/sqlmap'}, paths) + setPaths() + self.maxDiff = None + # self.assertDictContainsSubset({'COMMON_COLUMNS': u'/home/k/Develop/sqlmap/txt/common-columns.txt', + # 'COMMON_OUTPUTS': u'/home/k/Develop/sqlmap/txt/common-outputs.txt', + # 'COMMON_TABLES': u'/home/k/Develop/sqlmap/txt/common-tables.txt', + # 'ERRORS_XML': u'/home/k/Develop/sqlmap/xml/errors.xml', + # 'GENERIC_XML': u'/home/k/Develop/sqlmap/xml/banner/generic.xml', + # 'INJECTIONS_XML': u'/home/k/Develop/sqlmap/xml/injections.xml', + # 'LIVE_TESTS_XML': u'/home/k/Develop/sqlmap/xml/livetests.xml', + # 'MSSQL_XML': u'/home/k/Develop/sqlmap/xml/banner/mssql.xml', + # 'MYSQL_XML': u'/home/k/Develop/sqlmap/xml/banner/mysql.xml', + # 'ORACLE_XML': u'/home/k/Develop/sqlmap/xml/banner/oracle.xml', + # 'OS_SHELL_HISTORY': '/home/k/.sqlmap/os.hst', + # 'PAYLOADS_XML': u'/home/k/Develop/sqlmap/xml/payloads.xml', + # 'PGSQL_XML': u'/home/k/Develop/sqlmap/xml/banner/postgresql.xml', + # 'QUERIES_XML': u'/home/k/Develop/sqlmap/xml/queries.xml', + # 'SMALL_DICT': u'/home/k/Develop/sqlmap/txt/smalldict.txt', + # #'SQLMAP_CONFIG': u'/home/k/Develop/sqlmap/sqlmap-dieD.conf', + # 'SQLMAP_DUMP_PATH': u'/home/k/.sqlmap/output/%s/dump', + # 'SQLMAP_EXTRAS_PATH': u'/home/k/Develop/sqlmap/extra', + # 'SQLMAP_FILES_PATH': u'/home/k/.sqlmap/output/%s/files', + # 'SQLMAP_OUTPUT_PATH': u'/home/k/.sqlmap/output', + # 'SQLMAP_PROCS_PATH': u'/home/k/Develop/sqlmap/procs', + # 'SQLMAP_ROOT_PATH': u'/home/k/Develop/sqlmap', + # 'SQLMAP_SHELL_HISTORY': '/home/k/.sqlmap/sqlmap.hst', + # 'SQLMAP_SHELL_PATH': u'/home/k/Develop/sqlmap/shell', + # 'SQLMAP_TAMPER_PATH': u'/home/k/Develop/sqlmap/tamper', + # 'SQLMAP_TXT_PATH': u'/home/k/Develop/sqlmap/txt', + # 'SQLMAP_UDF_PATH': u'/home/k/Develop/sqlmap/udf', + # 'SQLMAP_WAF_PATH': u'/home/k/Develop/sqlmap/waf', + # 'SQLMAP_XML_BANNER_PATH': u'/home/k/Develop/sqlmap/xml/banner', + # 'SQLMAP_XML_PATH': u'/home/k/Develop/sqlmap/xml', + # 'SQL_KEYWORDS': u'/home/k/Develop/sqlmap/txt/keywords.txt', + # 'SQL_SHELL_HISTORY': '/home/k/.sqlmap/sql.hst', + # 'USER_AGENTS': u'/home/k/Develop/sqlmap/txt/user-agents.txt', + # 'WORDLIST': u'/home/k/Develop/sqlmap/txt/wordlist.zip'}, paths) + #self.assertEqual(u'/home/k/Develop/sqlmap/sqlmap-YplE.conf', paths.SQLMAP_CONFIG) + import os + profileOutputFile = os.path.join(paths.SQLMAP_OUTPUT_PATH, "sqlmap_profile.raw") + # self.assertEqual(u'/home/k/.sqlmap/output/sqlmap_profile.raw', profileOutputFile) + paths.SQLMAP_FILES_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "files") + # self.assertEqual(u'/home/k/.sqlmap/output/%s/files', paths.SQLMAP_FILES_PATH) + + # def test_cmdLineOptions_initOptions_h(self): + # import sys + # try: + # sys.argv = ["-h"] + # cmdLineOptions.update(cmdLineParser().__dict__) + # initOptions(cmdLineOptions) + # except SystemExit as e: + # self.assertEqual(0, e[0]) + + def test_cmdLineOptions_initOptions_xx(self): + import sys + self.assertEqual({}, cmdLineOptions) + try: + sys.argv = ["-u", "https://passport.baidu.com/v2/?reg&tpl=tb&u=http://tieba.baidu.com"] + cmdLineOptions.update(cmdLineParser().__dict__) + initOptions(cmdLineOptions) + except Exception as e: + self.assertEqual("unable to access item 'SQL_KEYWORDS'", e[0]) + logger.exception("just test logger.exception! ") + self.maxDiff = None + self.assertDictEqual({'advancedHelp': None, + 'agent': None, + 'alert': None, + 'answers': None, + 'authCred': None, + 'authPrivate': None, + 'authType': None, + 'batch': None, + 'beep': None, + 'binaryFields': None, + 'bulkFile': None, + 'charset': None, + 'checkTor': None, + 'checkWaf': None, + 'cleanup': None, + 'code': None, + 'col': None, + 'commonColumns': None, + 'commonTables': None, + 'configFile': None, + 'cookie': None, + 'cookieDel': None, + 'cpuThrottle': None, + 'crawlDepth': None, + 'csvDel': None, + 'dFile': None, + 'data': None, + 'db': None, + 'dbms': None, + 'dbmsCred': None, + 'delay': None, + 'dependencies': None, + 'direct': None, + 'disableColoring': None, + 'dnsName': None, + 'dropSetCookie': None, + 'dummy': None, + 'dumpAll': None, + 'dumpFormat': None, + 'dumpTable': None, + 'dumpWhere': None, + 'eta': None, + 'evalCode': None, + 'excludeCol': None, + 'excludeSysDbs': None, + 'extensiveFp': None, + 'firstChar': None, + 'flushSession': None, + 'forceDns': None, + 'forceSSL': None, + 'forms': None, + 'freshQueries': None, + 'getAll': None, + 'getBanner': None, + 'getColumns': None, + 'getComments': None, + 'getCount': None, + 'getCurrentDb': None, + 'getCurrentUser': None, + 'getDbs': None, + 'getHostname': None, + 'getPasswordHashes': None, + 'getPrivileges': None, + 'getRoles': None, + 'getSchema': None, + 'getTables': None, + 'getUsers': None, + 'googleDork': None, + 'googlePage': None, + 'headers': None, + 'hexConvert': None, + 'host': None, + 'hpp': None, + 'identifyWaf': None, + 'ignore401': None, + 'ignoreProxy': None, + 'invalidBignum': None, + 'invalidLogical': None, + 'invalidString': None, + 'isDba': None, + 'keepAlive': None, + 'lastChar': None, + 'level': None, + 'limitStart': None, + 'limitStop': None, + 'liveTest': None, + 'loadCookies': None, + 'logFile': None, + 'mnemonics': None, + 'mobile': None, + 'msfPath': None, + 'noCast': None, + 'noEscape': None, + 'notString': None, + 'nullConnection': None, + 'optimize': None, + 'os': None, + 'osBof': None, + 'osCmd': None, + 'osPwn': None, + 'osShell': None, + 'osSmb': None, + 'outputDir': None, + 'pageRank': None, + 'paramDel': None, + 'parseErrors': None, + 'pickledOptions': None, + 'pivotColumn': None, + 'predictOutput': None, + 'prefix': None, + 'privEsc': None, + 'profile': None, + 'proxy': None, + 'proxyCred': None, + 'proxyFile': None, + 'purgeOutput': None, + 'query': None, + 'rFile': None, + 'rParam': None, + 'randomAgent': None, + 'referer': None, + 'regAdd': None, + 'regData': None, + 'regDel': None, + 'regKey': None, + 'regRead': None, + 'regType': None, + 'regVal': None, + 'regexp': None, + 'requestFile': None, + 'retries': None, + 'risk': None, + 'runCase': None, + 'saFreq': None, + 'safUrl': None, + 'saveCmdline': None, + 'scope': None, + 'search': None, + 'secondOrder': None, + 'sessionFile': None, + 'shLib': None, + 'showVersion': None, + 'sitemapUrl': None, + 'skip': None, + 'skipUrlEncode': None, + 'smart': None, + 'smokeTest': None, + 'sqlFile': None, + 'sqlShell': None, + 'sqlmapShell': None, + 'stopFail': None, + 'string': None, + 'suffix': None, + 'tamper': None, + 'tbl': None, + 'tech': None, + 'testFilter': None, + 'testParameter': None, + 'textOnly': None, + 'threads': None, + 'timeSec': None, + 'timeout': None, + 'titles': None, + 'tmpPath': None, + 'tor': None, + 'torPort': None, + 'torType': None, + 'trafficFile': None, + 'uChar': None, + 'uCols': None, + 'uFrom': None, + 'udfInject': None, + 'updateAll': None, + 'url': u'https://passport.baidu.com/v2/?reg&tpl=tb&u=http://tieba.baidu.com', + 'user': None, + 'verbose': None, + 'wFile': None, + 'wizard': None}, cmdLineOptions) + self.assertDictEqual({'authPassword': None, + 'authUsername': None, + 'boundaries': [], + 'cj': None, + 'dbmsConnector': None, + 'dbmsHandler': None, + 'dnsServer': None, + 'dumpPath': None, + 'hashDB': None, + 'hashDBFile': None, + 'hostname': None, + 'httpHeaders': [], + 'ipv6': False, + 'multipleTargets': False, + 'outputPath': None, + 'paramDict': {}, + 'parameters': {}, + 'path': None, + 'port': None, + 'proxyList': [], + 'resultsFP': None, + 'resultsFilename': None, + 'scheme': None, + 'tests': [], + 'trafficFP': None, + 'wFileType': None}, conf) + # https://docs.python.org/2/howto/logging.html?highlight=logger#logging-howto + def test_logger(self): + self.assertEqual(['__class__', '__delattr__', '__dict__', '__doc__', + '__format__', '__getattribute__', '__hash__', '__init__', + '__module__', '__new__', '__reduce__', '__reduce_ex__', + '__repr__', '__setattr__', '__sizeof__', '__str__', + '__subclasshook__', '__weakref__', '_log', 'addFilter', + 'addHandler', 'callHandlers', 'critical', 'debug', + 'disabled', 'error', 'exception', 'fatal', 'filter', + 'filters', 'findCaller', 'getChild', 'getEffectiveLevel', + 'handle', 'handlers', 'info', 'isEnabledFor', 'level', 'log', + 'makeRecord', 'manager', 'name', 'parent', 'propagate', + 'removeFilter', 'removeHandler', 'root', 'setLevel', 'warn', + 'warning'], dir(logger)) + logger.debug("DEBUG message goes") + logger.info("INFO message goes") + self.assertEqual(False, logger == logger.root) + self.assertEqual("sqlmapLog", logger.name) + self.assertEqual(30, logger.level) + self.assertEqual(30, logger.getEffectiveLevel()) + self.assertEqual(False, logger.isEnabledFor(29)) + self.assertEqual(True, logger.isEnabledFor(31)) + self.assertEqual(1, logger.propagate) + # logging.Manager + self.assertEqual(['__class__', '__delattr__', '__dict__', '__doc__', + '__format__', '__getattribute__', '__hash__', '__init__', + '__module__', '__new__', '__reduce__', '__reduce_ex__', + '__repr__', '__setattr__', '__sizeof__', '__str__', + '__subclasshook__', '__weakref__', '_fixupChildren', + '_fixupParents', 'disable', 'emittedNoHandlerWarning', + 'getLogger', 'loggerClass', 'loggerDict', 'root', 'setLoggerClass'], + dir(logger.manager)) + self.assertEqual(True, logger.manager.loggerDict['sqlmapLog'] == logger) + self.assertEqual(30, logger.manager.loggerDict['ClientForm'].getEffectiveLevel()) + self.assertEqual(0, logger.manager.loggerDict['ClientForm'].level) + self.assertEqual(False, logger.manager.loggerDict['ClientForm'] == logger.root) + # logging.filters + self.assertEqual([], logger.filters) + # ColorizingStreamHandler + from thirdparty.ansistrm.ansistrm import ColorizingStreamHandler + self.assertEqual(['__class__', '__delattr__', '__dict__', '__doc__', + '__format__', '__getattribute__', '__hash__', '__init__', + '__module__', '__new__', '__reduce__', '__reduce_ex__', + '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', + '__weakref__', '_name', 'acquire', 'addFilter', 'close', + 'color_map','colorize', 'createLock', 'csi', 'disable_coloring', + 'emit', 'filter','filters', 'flush', 'format', 'formatter', + 'get_name', 'handle', 'handleError', 'is_tty', 'level', + 'level_map', 'lock', 'name', 'output_colorized', 'release', + 'removeFilter', 'reset', 'setFormatter', 'setLevel', + 'set_name', 'stream'], dir(logger.handlers[0])) + self.assertEqual(True, logger.handlers[0].is_tty) + # CRITICAL = 50 + # FATAL = CRITICAL + # ERROR = 40 + # WARNING = 30 --default + # WARN = WARNING + # INFO = 20 + # DEBUG = 10 + # NOTSET = 0 + # LOGGER_HANDLER.level_map[logging.getLevelName("PAYLOAD")] = (None, "cyan", False) + # LOGGER_HANDLER.level_map[logging.getLevelName("TRAFFIC OUT")] = (None, "magenta", False) + # LOGGER_HANDLER.level_map[logging.getLevelName("TRAFFIC IN")] = ("magenta", None, False) + self.assertEqual({7: ('magenta', None, False), + 40: (None, 'red', False), + 9: (None, 'cyan', False), + 10: (None, 'blue', False), + 8: (None, 'magenta', False), + 50: ('red', 'white', False), + 20: (None, 'green', False), + 30: (None, 'yellow', False)}, + logger.handlers[0].level_map) diff --git a/test/libs/__init__.py b/test/libs/__init__.py new file mode 100644 index 00000000000..421eaef9ffd --- /dev/null +++ b/test/libs/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Dummy file to support python package hierarchy diff --git a/test/libs/colorama/LICENSE-colorama b/test/libs/colorama/LICENSE-colorama new file mode 100644 index 00000000000..b7464472ebe --- /dev/null +++ b/test/libs/colorama/LICENSE-colorama @@ -0,0 +1,33 @@ +Copyright (c) 2010 Jonathan Hartley + +Released under the New BSD license (reproduced below), or alternatively you may +use this software under any OSI approved open source license such as those at +http://opensource.org/licenses/alphabetical + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name(s) of the copyright holders, nor those of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/test/libs/colorama/__init__.py b/test/libs/colorama/__init__.py new file mode 100644 index 00000000000..2d127fa8e22 --- /dev/null +++ b/test/libs/colorama/__init__.py @@ -0,0 +1,7 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from .initialise import init, deinit, reinit +from .ansi import Fore, Back, Style +from .ansitowin32 import AnsiToWin32 + +VERSION = '0.2.7' + diff --git a/test/libs/colorama/ansi.py b/test/libs/colorama/ansi.py new file mode 100644 index 00000000000..5dfe374ceb5 --- /dev/null +++ b/test/libs/colorama/ansi.py @@ -0,0 +1,50 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +''' +This module generates ANSI character codes to printing colors to terminals. +See: http://en.wikipedia.org/wiki/ANSI_escape_code +''' + +CSI = '\033[' + +def code_to_chars(code): + return CSI + str(code) + 'm' + +class AnsiCodes(object): + def __init__(self, codes): + for name in dir(codes): + if not name.startswith('_'): + value = getattr(codes, name) + setattr(self, name, code_to_chars(value)) + +class AnsiFore: + BLACK = 30 + RED = 31 + GREEN = 32 + YELLOW = 33 + BLUE = 34 + MAGENTA = 35 + CYAN = 36 + WHITE = 37 + RESET = 39 + +class AnsiBack: + BLACK = 40 + RED = 41 + GREEN = 42 + YELLOW = 43 + BLUE = 44 + MAGENTA = 45 + CYAN = 46 + WHITE = 47 + RESET = 49 + +class AnsiStyle: + BRIGHT = 1 + DIM = 2 + NORMAL = 22 + RESET_ALL = 0 + +Fore = AnsiCodes( AnsiFore ) +Back = AnsiCodes( AnsiBack ) +Style = AnsiCodes( AnsiStyle ) + diff --git a/test/libs/colorama/ansitowin32.py b/test/libs/colorama/ansitowin32.py new file mode 100644 index 00000000000..ea0a6c15f14 --- /dev/null +++ b/test/libs/colorama/ansitowin32.py @@ -0,0 +1,189 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import re +import sys + +from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style +from .winterm import WinTerm, WinColor, WinStyle +from .win32 import windll + + +if windll is not None: + winterm = WinTerm() + + +def is_a_tty(stream): + return hasattr(stream, 'isatty') and stream.isatty() + + +class StreamWrapper(object): + ''' + Wraps a stream (such as stdout), acting as a transparent proxy for all + attribute access apart from method 'write()', which is delegated to our + Converter instance. + ''' + def __init__(self, wrapped, converter): + # double-underscore everything to prevent clashes with names of + # attributes on the wrapped stream object. + self.__wrapped = wrapped + self.__convertor = converter + + def __getattr__(self, name): + return getattr(self.__wrapped, name) + + def write(self, text): + self.__convertor.write(text) + + +class AnsiToWin32(object): + ''' + Implements a 'write()' method which, on Windows, will strip ANSI character + sequences from the text, and if outputting to a tty, will convert them into + win32 function calls. + ''' + ANSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])') + + def __init__(self, wrapped, convert=None, strip=None, autoreset=False): + # The wrapped stream (normally sys.stdout or sys.stderr) + self.wrapped = wrapped + + # should we reset colors to defaults after every .write() + self.autoreset = autoreset + + # create the proxy wrapping our output stream + self.stream = StreamWrapper(wrapped, self) + + on_windows = sys.platform.startswith('win') + + # should we strip ANSI sequences from our output? + if strip is None: + strip = on_windows + self.strip = strip + + # should we should convert ANSI sequences into win32 calls? + if convert is None: + convert = on_windows and is_a_tty(wrapped) + self.convert = convert + + # dict of ansi codes to win32 functions and parameters + self.win32_calls = self.get_win32_calls() + + # are we wrapping stderr? + self.on_stderr = self.wrapped is sys.stderr + + + def should_wrap(self): + ''' + True if this class is actually needed. If false, then the output + stream will not be affected, nor will win32 calls be issued, so + wrapping stdout is not actually required. This will generally be + False on non-Windows platforms, unless optional functionality like + autoreset has been requested using kwargs to init() + ''' + return self.convert or self.strip or self.autoreset + + + def get_win32_calls(self): + if self.convert and winterm: + return { + AnsiStyle.RESET_ALL: (winterm.reset_all, ), + AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT), + AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL), + AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL), + AnsiFore.BLACK: (winterm.fore, WinColor.BLACK), + AnsiFore.RED: (winterm.fore, WinColor.RED), + AnsiFore.GREEN: (winterm.fore, WinColor.GREEN), + AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW), + AnsiFore.BLUE: (winterm.fore, WinColor.BLUE), + AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA), + AnsiFore.CYAN: (winterm.fore, WinColor.CYAN), + AnsiFore.WHITE: (winterm.fore, WinColor.GREY), + AnsiFore.RESET: (winterm.fore, ), + AnsiBack.BLACK: (winterm.back, WinColor.BLACK), + AnsiBack.RED: (winterm.back, WinColor.RED), + AnsiBack.GREEN: (winterm.back, WinColor.GREEN), + AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW), + AnsiBack.BLUE: (winterm.back, WinColor.BLUE), + AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA), + AnsiBack.CYAN: (winterm.back, WinColor.CYAN), + AnsiBack.WHITE: (winterm.back, WinColor.GREY), + AnsiBack.RESET: (winterm.back, ), + } + + + def write(self, text): + if self.strip or self.convert: + self.write_and_convert(text) + else: + self.wrapped.write(text) + self.wrapped.flush() + if self.autoreset: + self.reset_all() + + + def reset_all(self): + if self.convert: + self.call_win32('m', (0,)) + elif is_a_tty(self.wrapped): + self.wrapped.write(Style.RESET_ALL) + + + def write_and_convert(self, text): + ''' + Write the given text to our wrapped stream, stripping any ANSI + sequences from the text, and optionally converting them into win32 + calls. + ''' + cursor = 0 + for match in self.ANSI_RE.finditer(text): + start, end = match.span() + self.write_plain_text(text, cursor, start) + self.convert_ansi(*match.groups()) + cursor = end + self.write_plain_text(text, cursor, len(text)) + + + def write_plain_text(self, text, start, end): + if start < end: + self.wrapped.write(text[start:end]) + self.wrapped.flush() + + + def convert_ansi(self, paramstring, command): + if self.convert: + params = self.extract_params(paramstring) + self.call_win32(command, params) + + + def extract_params(self, paramstring): + def split(paramstring): + for p in paramstring.split(';'): + if p != '': + yield int(p) + return tuple(split(paramstring)) + + + def call_win32(self, command, params): + if params == []: + params = [0] + if command == 'm': + for param in params: + if param in self.win32_calls: + func_args = self.win32_calls[param] + func = func_args[0] + args = func_args[1:] + kwargs = dict(on_stderr=self.on_stderr) + func(*args, **kwargs) + elif command in ('H', 'f'): # set cursor position + func = winterm.set_cursor_position + func(params, on_stderr=self.on_stderr) + elif command in ('J'): + func = winterm.erase_data + func(params, on_stderr=self.on_stderr) + elif command == 'A': + if params == () or params == None: + num_rows = 1 + else: + num_rows = params[0] + func = winterm.cursor_up + func(num_rows, on_stderr=self.on_stderr) + diff --git a/test/libs/colorama/initialise.py b/test/libs/colorama/initialise.py new file mode 100644 index 00000000000..cba3676dd9c --- /dev/null +++ b/test/libs/colorama/initialise.py @@ -0,0 +1,56 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import atexit +import sys + +from .ansitowin32 import AnsiToWin32 + + +orig_stdout = sys.stdout +orig_stderr = sys.stderr + +wrapped_stdout = sys.stdout +wrapped_stderr = sys.stderr + +atexit_done = False + + +def reset_all(): + AnsiToWin32(orig_stdout).reset_all() + + +def init(autoreset=False, convert=None, strip=None, wrap=True): + + if not wrap and any([autoreset, convert, strip]): + raise ValueError('wrap=False conflicts with any other arg=True') + + global wrapped_stdout, wrapped_stderr + sys.stdout = wrapped_stdout = \ + wrap_stream(orig_stdout, convert, strip, autoreset, wrap) + sys.stderr = wrapped_stderr = \ + wrap_stream(orig_stderr, convert, strip, autoreset, wrap) + + global atexit_done + if not atexit_done: + atexit.register(reset_all) + atexit_done = True + + +def deinit(): + sys.stdout = orig_stdout + sys.stderr = orig_stderr + + +def reinit(): + sys.stdout = wrapped_stdout + sys.stderr = wrapped_stdout + + +def wrap_stream(stream, convert, strip, autoreset, wrap): + if wrap: + wrapper = AnsiToWin32(stream, + convert=convert, strip=strip, autoreset=autoreset) + if wrapper.should_wrap(): + stream = wrapper.stream + return stream + + diff --git a/test/libs/colorama/win32.py b/test/libs/colorama/win32.py new file mode 100644 index 00000000000..f4024f95ee0 --- /dev/null +++ b/test/libs/colorama/win32.py @@ -0,0 +1,134 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. + +# from winbase.h +STDOUT = -11 +STDERR = -12 + +try: + from ctypes import windll + from ctypes import wintypes +except ImportError: + windll = None + SetConsoleTextAttribute = lambda *_: None +else: + from ctypes import ( + byref, Structure, c_char, c_short, c_uint32, c_ushort, POINTER + ) + + class CONSOLE_SCREEN_BUFFER_INFO(Structure): + """struct in wincon.h.""" + _fields_ = [ + ("dwSize", wintypes._COORD), + ("dwCursorPosition", wintypes._COORD), + ("wAttributes", wintypes.WORD), + ("srWindow", wintypes.SMALL_RECT), + ("dwMaximumWindowSize", wintypes._COORD), + ] + def __str__(self): + return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( + self.dwSize.Y, self.dwSize.X + , self.dwCursorPosition.Y, self.dwCursorPosition.X + , self.wAttributes + , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right + , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X + ) + + _GetStdHandle = windll.kernel32.GetStdHandle + _GetStdHandle.argtypes = [ + wintypes.DWORD, + ] + _GetStdHandle.restype = wintypes.HANDLE + + _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo + _GetConsoleScreenBufferInfo.argtypes = [ + wintypes.HANDLE, + POINTER(CONSOLE_SCREEN_BUFFER_INFO), + ] + _GetConsoleScreenBufferInfo.restype = wintypes.BOOL + + _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute + _SetConsoleTextAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + ] + _SetConsoleTextAttribute.restype = wintypes.BOOL + + _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition + _SetConsoleCursorPosition.argtypes = [ + wintypes.HANDLE, + wintypes._COORD, + ] + _SetConsoleCursorPosition.restype = wintypes.BOOL + + _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA + _FillConsoleOutputCharacterA.argtypes = [ + wintypes.HANDLE, + c_char, + wintypes.DWORD, + wintypes._COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputCharacterA.restype = wintypes.BOOL + + _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute + _FillConsoleOutputAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + wintypes.DWORD, + wintypes._COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputAttribute.restype = wintypes.BOOL + + handles = { + STDOUT: _GetStdHandle(STDOUT), + STDERR: _GetStdHandle(STDERR), + } + + def GetConsoleScreenBufferInfo(stream_id=STDOUT): + handle = handles[stream_id] + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = _GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return csbi + + def SetConsoleTextAttribute(stream_id, attrs): + handle = handles[stream_id] + return _SetConsoleTextAttribute(handle, attrs) + + def SetConsoleCursorPosition(stream_id, position): + position = wintypes._COORD(*position) + # If the position is out of range, do nothing. + if position.Y <= 0 or position.X <= 0: + return + # Adjust for Windows' SetConsoleCursorPosition: + # 1. being 0-based, while ANSI is 1-based. + # 2. expecting (x,y), while ANSI uses (y,x). + adjusted_position = wintypes._COORD(position.Y - 1, position.X - 1) + # Adjust for viewport's scroll position + sr = GetConsoleScreenBufferInfo(STDOUT).srWindow + adjusted_position.Y += sr.Top + adjusted_position.X += sr.Left + # Resume normal processing + handle = handles[stream_id] + return _SetConsoleCursorPosition(handle, adjusted_position) + + def FillConsoleOutputCharacter(stream_id, char, length, start): + handle = handles[stream_id] + char = c_char(char) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + success = _FillConsoleOutputCharacterA( + handle, char, length, start, byref(num_written)) + return num_written.value + + def FillConsoleOutputAttribute(stream_id, attr, length, start): + ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' + handle = handles[stream_id] + attribute = wintypes.WORD(attr) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + return _FillConsoleOutputAttribute( + handle, attribute, length, start, byref(num_written)) diff --git a/test/libs/colorama/winterm.py b/test/libs/colorama/winterm.py new file mode 100644 index 00000000000..2708811541f --- /dev/null +++ b/test/libs/colorama/winterm.py @@ -0,0 +1,120 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from . import win32 + + +# from wincon.h +class WinColor(object): + BLACK = 0 + BLUE = 1 + GREEN = 2 + CYAN = 3 + RED = 4 + MAGENTA = 5 + YELLOW = 6 + GREY = 7 + +# from wincon.h +class WinStyle(object): + NORMAL = 0x00 # dim text, dim background + BRIGHT = 0x08 # bright text, dim background + + +class WinTerm(object): + + def __init__(self): + self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes + self.set_attrs(self._default) + self._default_fore = self._fore + self._default_back = self._back + self._default_style = self._style + + def get_attrs(self): + return self._fore + self._back * 16 + self._style + + def set_attrs(self, value): + self._fore = value & 7 + self._back = (value >> 4) & 7 + self._style = value & WinStyle.BRIGHT + + def reset_all(self, on_stderr=None): + self.set_attrs(self._default) + self.set_console(attrs=self._default) + + def fore(self, fore=None, on_stderr=False): + if fore is None: + fore = self._default_fore + self._fore = fore + self.set_console(on_stderr=on_stderr) + + def back(self, back=None, on_stderr=False): + if back is None: + back = self._default_back + self._back = back + self.set_console(on_stderr=on_stderr) + + def style(self, style=None, on_stderr=False): + if style is None: + style = self._default_style + self._style = style + self.set_console(on_stderr=on_stderr) + + def set_console(self, attrs=None, on_stderr=False): + if attrs is None: + attrs = self.get_attrs() + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleTextAttribute(handle, attrs) + + def get_position(self, handle): + position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition + # Because Windows coordinates are 0-based, + # and win32.SetConsoleCursorPosition expects 1-based. + position.X += 1 + position.Y += 1 + return position + + def set_cursor_position(self, position=None, on_stderr=False): + if position is None: + #I'm not currently tracking the position, so there is no default. + #position = self.get_position() + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleCursorPosition(handle, position) + + def cursor_up(self, num_rows=0, on_stderr=False): + if num_rows == 0: + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + position = self.get_position(handle) + adjusted_position = (position.Y - num_rows, position.X) + self.set_cursor_position(adjusted_position, on_stderr) + + def erase_data(self, mode=0, on_stderr=False): + # 0 (or None) should clear from the cursor to the end of the screen. + # 1 should clear from the cursor to the beginning of the screen. + # 2 should clear the entire screen. (And maybe move cursor to (1,1)?) + # + # At the moment, I only support mode 2. From looking at the API, it + # should be possible to calculate a different number of bytes to clear, + # and to do so relative to the cursor position. + if mode[0] not in (2,): + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + # here's where we'll home the cursor + coord_screen = win32.COORD(0,0) + csbi = win32.GetConsoleScreenBufferInfo(handle) + # get the number of character cells in the current buffer + dw_con_size = csbi.dwSize.X * csbi.dwSize.Y + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', dw_con_size, coord_screen) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen ); + # put the cursor at (0, 0) + win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y)) diff --git a/test/libs/mock.py b/test/libs/mock.py new file mode 100644 index 00000000000..38c446db29f --- /dev/null +++ b/test/libs/mock.py @@ -0,0 +1,271 @@ +# mock.py +# Test tools for mocking and patching. +# Copyright (C) 2007-2009 Michael Foord +# E-mail: fuzzyman AT voidspace DOT org DOT uk + +# mock 0.6.0 +# http://www.voidspace.org.uk/python/mock/ + +# Released subject to the BSD License +# Please see http://www.voidspace.org.uk/python/license.shtml + +# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml +# Comments, suggestions and bug reports welcome. + + +__all__ = ( + 'Mock', + 'patch', + 'patch_object', + 'sentinel', + 'DEFAULT' +) + +__version__ = '0.6.0 modified by Greg Malcolm' + +class SentinelObject(object): + def __init__(self, name): + self.name = name + + def __repr__(self): + return ''.format(self.name) + + +class Sentinel(object): + def __init__(self): + self._sentinels = {} + + def __getattr__(self, name): + return self._sentinels.setdefault(name, SentinelObject(name)) + + +sentinel = Sentinel() + +DEFAULT = sentinel.DEFAULT + +class OldStyleClass: + pass +ClassType = type(OldStyleClass) + +def _is_magic(name): + return '__{0!s}__'.format(name[2:-2]) == name + +def _copy(value): + if type(value) in (dict, list, tuple, set): + return type(value)(value) + return value + + +class Mock(object): + + def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, + name=None, parent=None, wraps=None): + self._parent = parent + self._name = name + if spec is not None and not isinstance(spec, list): + spec = [member for member in dir(spec) if not _is_magic(member)] + + self._methods = spec + self._children = {} + self._return_value = return_value + self.side_effect = side_effect + self._wraps = wraps + + self.reset_mock() + + + def reset_mock(self): + self.called = False + self.call_args = None + self.call_count = 0 + self.call_args_list = [] + self.method_calls = [] + for child in self._children.values(): + child.reset_mock() + if isinstance(self._return_value, Mock): + self._return_value.reset_mock() + + + def __get_return_value(self): + if self._return_value is DEFAULT: + self._return_value = Mock() + return self._return_value + + def __set_return_value(self, value): + self._return_value = value + + return_value = property(__get_return_value, __set_return_value) + + + def __call__(self, *args, **kwargs): + self.called = True + self.call_count += 1 + self.call_args = (args, kwargs) + self.call_args_list.append((args, kwargs)) + + parent = self._parent + name = self._name + while parent is not None: + parent.method_calls.append((name, args, kwargs)) + if parent._parent is None: + break + name = parent._name + '.' + name + parent = parent._parent + + ret_val = DEFAULT + if self.side_effect is not None: + if (isinstance(self.side_effect, Exception) or + isinstance(self.side_effect, (type, ClassType)) and + issubclass(self.side_effect, Exception)): + raise self.side_effect + + ret_val = self.side_effect(*args, **kwargs) + if ret_val is DEFAULT: + ret_val = self.return_value + + if self._wraps is not None and self._return_value is DEFAULT: + return self._wraps(*args, **kwargs) + if ret_val is DEFAULT: + ret_val = self.return_value + return ret_val + + + def __getattr__(self, name): + if self._methods is not None: + if name not in self._methods: + raise AttributeError("Mock object has no attribute '{0!s}'".format(name)) + elif _is_magic(name): + raise AttributeError(name) + + if name not in self._children: + wraps = None + if self._wraps is not None: + wraps = getattr(self._wraps, name) + self._children[name] = Mock(parent=self, name=name, wraps=wraps) + + return self._children[name] + + + def assert_called_with(self, *args, **kwargs): + assert self.call_args == (args, kwargs), 'Expected: {0!s}\nCalled with: {1!s}'.format((args, kwargs), self.call_args) + + +def _dot_lookup(thing, comp, import_path): + try: + return getattr(thing, comp) + except AttributeError: + __import__(import_path) + return getattr(thing, comp) + + +def _importer(target): + components = target.split('.') + import_path = components.pop(0) + thing = __import__(import_path) + + for comp in components: + import_path += ".{0!s}".format(comp) + thing = _dot_lookup(thing, comp, import_path) + return thing + + +class _patch(object): + def __init__(self, target, attribute, new, spec, create): + self.target = target + self.attribute = attribute + self.new = new + self.spec = spec + self.create = create + self.has_local = False + + + def __call__(self, func): + if hasattr(func, 'patchings'): + func.patchings.append(self) + return func + + def patched(*args, **keywargs): + # don't use a with here (backwards compatability with 2.5) + extra_args = [] + for patching in patched.patchings: + arg = patching.__enter__() + if patching.new is DEFAULT: + extra_args.append(arg) + args += tuple(extra_args) + try: + return func(*args, **keywargs) + finally: + for patching in getattr(patched, 'patchings', []): + patching.__exit__() + + patched.patchings = [self] + patched.__name__ = func.__name__ + patched.compat_co_firstlineno = getattr(func, "compat_co_firstlineno", + func.func_code.co_firstlineno) + return patched + + + def get_original(self): + target = self.target + name = self.attribute + create = self.create + + original = DEFAULT + if _has_local_attr(target, name): + try: + original = target.__dict__[name] + except AttributeError: + # for instances of classes with slots, they have no __dict__ + original = getattr(target, name) + elif not create and not hasattr(target, name): + raise AttributeError("{0!s} does not have the attribute {1!r}".format(target, name)) + return original + + + def __enter__(self): + new, spec, = self.new, self.spec + original = self.get_original() + if new is DEFAULT: + # XXXX what if original is DEFAULT - shouldn't use it as a spec + inherit = False + if spec == True: + # set spec to the object we are replacing + spec = original + if isinstance(spec, (type, ClassType)): + inherit = True + new = Mock(spec=spec) + if inherit: + new.return_value = Mock(spec=spec) + self.temp_original = original + setattr(self.target, self.attribute, new) + return new + + + def __exit__(self, *_): + if self.temp_original is not DEFAULT: + setattr(self.target, self.attribute, self.temp_original) + else: + delattr(self.target, self.attribute) + del self.temp_original + + +def patch_object(target, attribute, new=DEFAULT, spec=None, create=False): + return _patch(target, attribute, new, spec, create) + + +def patch(target, new=DEFAULT, spec=None, create=False): + try: + target, attribute = target.rsplit('.', 1) + except (TypeError, ValueError): + raise TypeError("Need a valid target to patch. You supplied: {0!r}".format(target,)) + target = _importer(target) + return _patch(target, attribute, new, spec, create) + + + +def _has_local_attr(obj, name): + try: + return name in vars(obj) + except TypeError: + # objects without a __dict__ + return hasattr(obj, name) diff --git a/test/run.bat b/test/run.bat new file mode 100755 index 00000000000..b1f70426108 --- /dev/null +++ b/test/run.bat @@ -0,0 +1,35 @@ +@echo off + +REM This is how you run it from the command line. +REM You don't actually need this script! +SET RUN_KOANS=python.exe contemplate_koans.py + +REM Set this to your python folder: +SET PYTHON_PATH=C:\Python27 + +set SCRIPT= +REM Hunt around for python +IF EXIST "python.exe" ( + SET SCRIPT=%RUN_KOANS% +) ELSE ( + IF EXIST "%PYTHON_PATH%" ( + SET SCRIPT=%PYTHON_PATH%\%RUN_KOANS% + ) ELSE ( + IF EXIST %PYTHON% SET SCRIPT=%PYTHON%\%RUN_KOANS% + ) +) + +IF NOT "" == "%SCRIPT%" ( + %SCRIPT% + pause +) ELSE ( + echo. + echo Python.exe is not in the path! + echo. + echo Fix the path and try again. + echo Or better yet, run this with the correct python path: + echo. + echo python.exe contemplate_koans.py + pause +) +:end \ No newline at end of file diff --git a/test/run.sh b/test/run.sh new file mode 100644 index 00000000000..fda73be7de7 --- /dev/null +++ b/test/run.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ -x /usr/bin/python2 ]; then + python2 contemplate_koans.py +else + python contemplate_koans.py +fi diff --git a/test/runner/__init__.py b/test/runner/__init__.py new file mode 100644 index 00000000000..655c3567db3 --- /dev/null +++ b/test/runner/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Namespace: runner + diff --git a/test/runner/helper.py b/test/runner/helper.py new file mode 100644 index 00000000000..ccc4f8db1b6 --- /dev/null +++ b/test/runner/helper.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +def cls_name(obj): + return obj.__class__.__name__ \ No newline at end of file diff --git a/test/runner/koan.py b/test/runner/koan.py new file mode 100644 index 00000000000..e9a0080bd47 --- /dev/null +++ b/test/runner/koan.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import unittest +import re + +# Starting a classname or attribute with an underscore normally implies Private scope. +# However, we are making an exception for __ and ___. + +__all__ = [ "__", "___", "____", "_____", "Koan" ] + +__ = "-=> FILL ME IN! <=-" + +class ___(Exception): + pass + +____ = "-=> TRUE OR FALSE? <=-" + +_____ = 0 + + +class Koan(unittest.TestCase): + def assertMatch(self, pattern, string, msg=None): + """ + Throw an exception if the regular expresson pattern is matched + """ + # Not part of unittest, but convenient for some koans tests + m = re.search(pattern, string) + if not m or not m.group(0): + raise self.failureException, \ + (msg or '{0!r} does not match {1!r}'.format(pattern, string)) + + def assertNoMatch(self, pattern, string, msg=None): + """ + Throw an exception if the regular expresson pattern is not matched + """ + m = re.search(pattern, string) + if m and m.group(0): + raise self.failureException, \ + (msg or '{0!r} matches {1!r}'.format(pattern, string)) diff --git a/test/runner/mockable_test_result.py b/test/runner/mockable_test_result.py new file mode 100644 index 00000000000..be1aca300c7 --- /dev/null +++ b/test/runner/mockable_test_result.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import unittest + +# Needed to stop unittest.TestResult itself getting Mocked out of existence, +# which is a problem when testing the helper classes! (It confuses the runner) + +class MockableTestResult(unittest.TestResult): + pass \ No newline at end of file diff --git a/test/runner/mountain.py b/test/runner/mountain.py new file mode 100644 index 00000000000..b988c12910f --- /dev/null +++ b/test/runner/mountain.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import unittest +import sys + +import path_to_enlightenment +from sensei import Sensei +from writeln_decorator import WritelnDecorator + +class Mountain: + def __init__(self): + self.stream = WritelnDecorator(sys.stdout) + self.tests = path_to_enlightenment.koans() + self.lesson = Sensei(self.stream) + + def walk_the_path(self, args=None): + "Run the koans tests with a custom runner output." + + if args and len(args) >=2: + args.pop(0) + test_names = ["koans." + test_name for test_name in args] + self.tests = unittest.TestLoader().loadTestsFromNames(test_names) + self.tests(self.lesson) + self.lesson.learn() + return self.lesson diff --git a/test/runner/path_to_enlightenment.py b/test/runner/path_to_enlightenment.py new file mode 100644 index 00000000000..7b9556c0b56 --- /dev/null +++ b/test/runner/path_to_enlightenment.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# The path to enlightenment starts with the following: + +import unittest + +from koans.about_agent import AboutAgent +from koans.about_data import AboutData + +def koans(): + loader = unittest.TestLoader() + suite = unittest.TestSuite() + loader.sortTestMethodsUsing = None + suite.addTests(loader.loadTestsFromTestCase(AboutAgent)) + suite.addTests(loader.loadTestsFromTestCase(AboutData)) + + return suite diff --git a/test/runner/runner_tests/__init__.py b/test/runner/runner_tests/__init__.py new file mode 100644 index 00000000000..16f619ae280 --- /dev/null +++ b/test/runner/runner_tests/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Namespace: helpers_tests diff --git a/test/runner/runner_tests/test_helper.py b/test/runner/runner_tests/test_helper.py new file mode 100644 index 00000000000..bd9da23dab6 --- /dev/null +++ b/test/runner/runner_tests/test_helper.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import unittest + +from runner import helper + +class TestHelper(unittest.TestCase): + + def test_that_get_class_name_works_with_a_string_instance(self): + self.assertEqual("str", helper.cls_name(str())) + + def test_that_get_class_name_works_with_a_4(self): + self.assertEquals("int", helper.cls_name(4)) + + def test_that_get_class_name_works_with_a_tuple(self): + self.assertEquals("tuple", helper.cls_name((3, "pie", []))) diff --git a/test/runner/runner_tests/test_mountain.py b/test/runner/runner_tests/test_mountain.py new file mode 100644 index 00000000000..0f8d08cccdf --- /dev/null +++ b/test/runner/runner_tests/test_mountain.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import unittest +from libs.mock import * + +from runner.mountain import Mountain +from runner import path_to_enlightenment + +class TestMountain(unittest.TestCase): + + def setUp(self): + path_to_enlightenment.koans = Mock() + self.mountain = Mountain() + self.mountain.stream.writeln = Mock() + + def test_it_gets_test_results(self): + self.mountain.lesson.learn = Mock() + self.mountain.walk_the_path() + self.assertTrue(self.mountain.lesson.learn.called) + diff --git a/test/runner/runner_tests/test_sensei.py b/test/runner/runner_tests/test_sensei.py new file mode 100644 index 00000000000..8149a7fac2c --- /dev/null +++ b/test/runner/runner_tests/test_sensei.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import unittest +import re + +from libs.mock import * + +from runner.sensei import Sensei +from runner.writeln_decorator import WritelnDecorator +from runner.mockable_test_result import MockableTestResult +from runner import path_to_enlightenment + +class AboutParrots: + pass +class AboutLumberjacks: + pass +class AboutTennis: + pass +class AboutTheKnightsWhoSayNi: + pass +class AboutMrGumby: + pass +class AboutMessiahs: + pass +class AboutGiantFeet: + pass +class AboutTrebuchets: + pass +class AboutFreemasons: + pass + +error_assertion_with_message = """Traceback (most recent call last): + File "/Users/Greg/hg/python_koans/koans/about_exploding_trousers.py", line 43, in test_durability + self.assertEqual("Steel","Lard", "Another fine mess you've got me into Stanley...") +AssertionError: Another fine mess you've got me into Stanley...""" + +error_assertion_equals = """ + +Traceback (most recent call last): + File "/Users/Greg/hg/python_koans/koans/about_exploding_trousers.py", line 49, in test_math + self.assertEqual(4,99) +AssertionError: 4 != 99 +""" + +error_assertion_true = """Traceback (most recent call last): + File "/Users/Greg/hg/python_koans/koans/about_armories.py", line 25, in test_weoponary + self.assertTrue("Pen" > "Sword") +AssertionError + +""" + +error_mess = """ +Traceback (most recent call last): + File "contemplate_koans.py", line 5, in + from runner.mountain import Mountain + File "/Users/Greg/hg/python_koans/runner/mountain.py", line 7, in + import path_to_enlightenment + File "/Users/Greg/hg/python_koans/runner/path_to_enlightenment.py", line 8, in + from koans import * + File "/Users/Greg/hg/python_koans/koans/about_asserts.py", line 20 + self.assertTrue(eoe"Pen" > "Sword", "nhnth") + ^ +SyntaxError: invalid syntax""" + +error_with_list = """Traceback (most recent call last): + File "/Users/Greg/hg/python_koans/koans/about_armories.py", line 84, in test_weoponary + self.assertEqual([1, 9], [1, 2]) +AssertionError: Lists differ: [1, 9] != [1, 2] + +First differing element 1: +9 +2 + +- [1, 9] +? ^ + ++ [1, 2] +? ^ + +""" + +class TestSensei(unittest.TestCase): + + def setUp(self): + self.sensei = Sensei(WritelnDecorator(sys.stdout)) + self.sensei.stream.writeln = Mock() + path_to_enlightenment.koans = Mock() + self.tests = Mock() + self.tests.countTestCases = Mock() + + def test_that_failures_are_handled_in_the_base_class(self): + MockableTestResult.addFailure = Mock() + self.sensei.addFailure(Mock(), Mock()) + self.assertTrue(MockableTestResult.addFailure.called) + + def test_that_it_successes_only_count_if_passes_are_currently_allowed(self): + self.sensei.passesCount = Mock() + MockableTestResult.addSuccess = Mock() + self.sensei.addSuccess(Mock()) + self.assertTrue(self.sensei.passesCount.called) + + def test_that_it_passes_on_add_successes_message(self): + MockableTestResult.addSuccess = Mock() + self.sensei.addSuccess(Mock()) + self.assertTrue(MockableTestResult.addSuccess.called) + + def test_that_it_increases_the_passes_on_every_success(self): + pass_count = self.sensei.pass_count + MockableTestResult.addSuccess = Mock() + self.sensei.addSuccess(Mock()) + self.assertEqual(pass_count + 1, self.sensei.pass_count) + + def test_that_nothing_is_returned_as_a_first_result_if_there_are_no_failures(self): + self.sensei.failures = [] + self.assertEqual(None, self.sensei.firstFailure()) + + def test_that_nothing_is_returned_as_sorted_result_if_there_are_no_failures(self): + self.sensei.failures = [] + self.assertEqual(None, self.sensei.sortFailures("AboutLife")) + + def test_that_nothing_is_returned_as_sorted_result_if_there_are_no_relevent_failures(self): + self.sensei.failures = [ + (AboutTheKnightsWhoSayNi(),"File 'about_the_knights_whn_say_ni.py', line 24"), + (AboutMessiahs(),"File 'about_messiahs.py', line 43"), + (AboutMessiahs(),"File 'about_messiahs.py', line 844") + ] + self.assertEqual(None, self.sensei.sortFailures("AboutLife")) + + def test_that_nothing_is_returned_as_sorted_result_if_there_are_3_shuffled_results(self): + self.sensei.failures = [ + (AboutTennis(),"File 'about_tennis.py', line 299"), + (AboutTheKnightsWhoSayNi(),"File 'about_the_knights_whn_say_ni.py', line 24"), + (AboutTennis(),"File 'about_tennis.py', line 30"), + (AboutMessiahs(),"File 'about_messiahs.py', line 43"), + (AboutTennis(),"File 'about_tennis.py', line 2"), + (AboutMrGumby(),"File 'about_mr_gumby.py', line odd"), + (AboutMessiahs(),"File 'about_messiahs.py', line 844") + ] + + expected = [ + (AboutTennis(),"File 'about_tennis.py', line 2"), + (AboutTennis(),"File 'about_tennis.py', line 30"), + (AboutTennis(),"File 'about_tennis.py', line 299") + ] + + results = self.sensei.sortFailures("AboutTennis") + self.assertEqual(3, len(results)) + self.assertEqual(2, results[0][0]) + self.assertEqual(30, results[1][0]) + self.assertEqual(299, results[2][0]) + + def test_that_it_will_choose_not_find_anything_with_non_standard_error_trace_string(self): + self.sensei.failures = [ + (AboutMrGumby(),"File 'about_mr_gumby.py', line MISSING"), + ] + self.assertEqual(None, self.sensei.sortFailures("AboutMrGumby")) + + + def test_that_it_will_choose_correct_first_result_with_lines_9_and_27(self): + self.sensei.failures = [ + (AboutTrebuchets(),"File 'about_trebuchets.py', line 27"), + (AboutTrebuchets(),"File 'about_trebuchets.py', line 9"), + (AboutTrebuchets(),"File 'about_trebuchets.py', line 73v") + ] + self.assertEqual("File 'about_trebuchets.py', line 9", self.sensei.firstFailure()[1]) + + def test_that_it_will_choose_correct_first_result_with_multiline_test_classes(self): + self.sensei.failures = [ + (AboutGiantFeet(),"File 'about_giant_feet.py', line 999"), + (AboutGiantFeet(),"File 'about_giant_feet.py', line 44"), + (AboutFreemasons(),"File 'about_freemasons.py', line 1"), + (AboutFreemasons(),"File 'about_freemasons.py', line 11") + ] + self.assertEqual("File 'about_giant_feet.py', line 44", self.sensei.firstFailure()[1]) + + def test_that_error_report_features_the_assertion_error(self): + self.sensei.scrapeAssertionError = Mock() + self.sensei.firstFailure = Mock() + self.sensei.firstFailure.return_value = (Mock(), "FAILED") + self.sensei.errorReport() + self.assertTrue(self.sensei.scrapeAssertionError.called) + + def test_that_error_report_features_a_stack_dump(self): + self.sensei.scrapeInterestingStackDump = Mock() + self.sensei.firstFailure = Mock() + self.sensei.firstFailure.return_value = (Mock(), "FAILED") + self.sensei.errorReport() + self.assertTrue(self.sensei.scrapeInterestingStackDump.called) + + def test_that_scraping_the_assertion_error_with_nothing_gives_you_a_blank_back(self): + self.assertEqual("", self.sensei.scrapeAssertionError(None)) + + def test_that_scraping_the_assertion_error_with_messaged_assert(self): + self.assertEqual(" AssertionError: Another fine mess you've got me into Stanley...", + self.sensei.scrapeAssertionError(error_assertion_with_message)) + + def test_that_scraping_the_assertion_error_with_assert_equals(self): + self.assertEqual(" AssertionError: 4 != 99", + self.sensei.scrapeAssertionError(error_assertion_equals)) + + def test_that_scraping_the_assertion_error_with_assert_true(self): + self.assertEqual(" AssertionError", + self.sensei.scrapeAssertionError(error_assertion_true)) + + def test_that_scraping_the_assertion_error_with_syntax_error(self): + self.assertEqual(" SyntaxError: invalid syntax", + self.sensei.scrapeAssertionError(error_mess)) + + def test_that_scraping_the_assertion_error_with_list_error(self): + self.assertEqual(""" AssertionError: Lists differ: [1, 9] != [1, 2] + + First differing element 1: + 9 + 2 + + - [1, 9] + ? ^ + + + [1, 2] + ? ^""", + self.sensei.scrapeAssertionError(error_with_list)) + + def test_that_scraping_a_non_existent_stack_dump_gives_you_nothing(self): + self.assertEqual("", self.sensei.scrapeInterestingStackDump(None)) + + def test_that_if_there_are_no_failures_say_the_final_zenlike_remark(self): + self.sensei.failures = None + words = self.sensei.say_something_zenlike() + + m = re.search("Spanish Inquisition", words) + self.assertTrue(m and m.group(0)) + + def test_that_if_there_are_0_successes_it_will_say_the_first_zen_of_python_koans(self): + self.sensei.pass_count = 0 + self.sensei.failures = Mock() + words = self.sensei.say_something_zenlike() + + m = re.search("Beautiful is better than ugly", words) + self.assertTrue(m and m.group(0)) + + def test_that_if_there_is_1_successes_it_will_say_the_second_zen_of_python_koans(self): + self.sensei.pass_count = 1 + self.sensei.failures = Mock() + words = self.sensei.say_something_zenlike() + + m = re.search("Explicit is better than implicit", words) + self.assertTrue(m and m.group(0)) + + def test_that_if_there_is_10_successes_it_will_say_the_sixth_zen_of_python_koans(self): + self.sensei.pass_count = 10 + self.sensei.failures = Mock() + words = self.sensei.say_something_zenlike() + + m = re.search("Sparse is better than dense", words) + self.assertTrue(m and m.group(0)) + + def test_that_if_there_is_36_successes_it_will_say_the_final_zen_of_python_koans(self): + self.sensei.pass_count = 36 + self.sensei.failures = Mock() + words = self.sensei.say_something_zenlike() + + m = re.search("Namespaces are one honking great idea", words) + self.assertTrue(m and m.group(0)) + + def test_that_if_there_is_37_successes_it_will_say_the_first_zen_of_python_koans_again(self): + self.sensei.pass_count = 37 + self.sensei.failures = Mock() + words = self.sensei.say_something_zenlike() + + m = re.search("Beautiful is better than ugly", words) + self.assertTrue(m and m.group(0)) + + def test_that_total_lessons_return_7_if_there_are_7_lessons(self): + self.sensei.filter_all_lessons = Mock() + self.sensei.filter_all_lessons.return_value = [1,2,3,4,5,6,7] + + self.assertEqual(7, self.sensei.total_lessons()) + + def test_that_total_lessons_return_0_if_all_lessons_is_none(self): + self.sensei.filter_all_lessons = Mock() + self.sensei.filter_all_lessons.return_value = None + + self.assertEqual(0, self.sensei.total_lessons()) + + def test_total_koans_return_43_if_there_are_43_test_cases(self): + self.sensei.tests.countTestCases = Mock() + self.sensei.tests.countTestCases.return_value = 43 + + self.assertEqual(43, self.sensei.total_koans()) + + def test_filter_all_lessons_will_discover_test_classes_if_none_have_been_discovered_yet(self): + self.sensei.all_lessons = 0 + self.assertTrue(len(self.sensei.filter_all_lessons()) > 10) + self.assertTrue(len(self.sensei.all_lessons) > 10) diff --git a/test/runner/sensei.py b/test/runner/sensei.py new file mode 100644 index 00000000000..5b5cd937d2b --- /dev/null +++ b/test/runner/sensei.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import unittest +import re +import sys +import os +import glob + +import helper +from mockable_test_result import MockableTestResult +from runner import path_to_enlightenment + +from libs.colorama import init, Fore, Style +init() # init colorama + +class Sensei(MockableTestResult): + def __init__(self, stream): + unittest.TestResult.__init__(self) + self.stream = stream + self.prevTestClassName = None + self.tests = path_to_enlightenment.koans() + self.pass_count = 0 + self.lesson_pass_count = 0 + self.all_lessons = None + + def startTest(self, test): + MockableTestResult.startTest(self, test) + + if helper.cls_name(test) != self.prevTestClassName: + self.prevTestClassName = helper.cls_name(test) + if not self.failures: + self.stream.writeln() + self.stream.writeln("{0}{1}Thinking {2}".format( + Fore.RESET, Style.NORMAL, helper.cls_name(test))) + if helper.cls_name(test) != 'AboutAsserts': + self.lesson_pass_count += 1 + + def addSuccess(self, test): + if self.passesCount(): + MockableTestResult.addSuccess(self, test) + self.stream.writeln( \ + " {0}{1}{2} has expanded your awareness.{3}{4}" \ + .format(Fore.GREEN, Style.BRIGHT, test._testMethodName, \ + Fore.RESET, Style.NORMAL)) + self.pass_count += 1 + + def addError(self, test, err): + # Having 1 list for errors and 1 list for failures would mess with + # the error sequence + self.addFailure(test, err) + + def passesCount(self): + return not (self.failures and helper.cls_name(self.failures[0][0]) != + self.prevTestClassName) + + def addFailure(self, test, err): + MockableTestResult.addFailure(self, test, err) + + def sortFailures(self, testClassName): + table = list() + for test, err in self.failures: + if helper.cls_name(test) == testClassName: + m = re.search("(?<= line )\d+" ,err) + if m: + tup = (int(m.group(0)), test, err) + table.append(tup) + + if table: + return sorted(table) + else: + return None + + def firstFailure(self): + if not self.failures: return None + + table = self.sortFailures(helper.cls_name(self.failures[0][0])) + + if table: + return (table[0][1], table[0][2]) + else: + return None + + def learn(self): + self.errorReport() + + self.stream.writeln("") + self.stream.writeln("") + self.stream.writeln(self.report_progress()) + if self.failures: + self.stream.writeln(self.report_remaining()) + self.stream.writeln("") + self.stream.writeln(self.say_something_zenlike()) + + if self.failures: sys.exit(-1) + self.stream.writeln( + "\n{0}**************************************************" \ + .format(Fore.RESET)) + self.stream.writeln("\n{0}That was the last one, well done!" \ + .format(Fore.MAGENTA)) + self.stream.writeln( + "\nIf you want more, take a look at about_extra_credit_task.py") + + def errorReport(self): + problem = self.firstFailure() + if not problem: return + test, err = problem + self.stream.writeln(" {0}{1}{2} has damaged your " + "karma.".format(Fore.RED, Style.BRIGHT, test._testMethodName)) + + self.stream.writeln("\n{0}{1}You have not yet reached enlightenment ..." \ + .format(Fore.RESET, Style.NORMAL)) + self.stream.writeln("{0}{1}{2}".format(Fore.RED, \ + Style.BRIGHT, self.scrapeAssertionError(err))) + self.stream.writeln("") + self.stream.writeln("{0}{1}Please meditate on the following code:" \ + .format(Fore.RESET, Style.NORMAL)) + self.stream.writeln("{0}{1}{2}{3}{4}".format(Fore.YELLOW, Style.BRIGHT, \ + self.scrapeInterestingStackDump(err), Fore.RESET, Style.NORMAL)) + + def scrapeAssertionError(self, err): + if not err: return "" + + error_text = "" + count = 0 + for line in err.splitlines(): + m = re.search("^[^^ ].*$",line) + if m and m.group(0): + count+=1 + + if count>1: + error_text += (" " + line.strip()).rstrip() + '\n' + return error_text.strip('\n') + + def scrapeInterestingStackDump(self, err): + if not err: + return "" + + lines = err.splitlines() + + sep = '@@@@@SEP@@@@@' + + stack_text = "" + for line in lines: + m = re.search("^ File .*$",line) + if m and m.group(0): + stack_text += '\n' + line + + m = re.search("^ \w(\w)+.*$",line) + if m and m.group(0): + stack_text += sep + line + + lines = stack_text.splitlines() + + stack_text = "" + for line in lines: + m = re.search("^.*[/\\\\]koans[/\\\\].*$",line) + if m and m.group(0): + stack_text += line + '\n' + + + stack_text = stack_text.replace(sep, '\n').strip('\n') + stack_text = re.sub(r'(about_\w+.py)', + r"{0}\1{1}".format(Fore.BLUE, Fore.YELLOW), stack_text) + stack_text = re.sub(r'(line \d+)', + r"{0}\1{1}".format(Fore.BLUE, Fore.YELLOW), stack_text) + return stack_text + + def report_progress(self): + return "You have completed {0} koans and " \ + "{1} lessons.".format( + self.pass_count, + self.lesson_pass_count) + + def report_remaining(self): + koans_remaining = self.total_koans() - self.pass_count + lessons_remaining = self.total_lessons() - self.lesson_pass_count + + return "You are now {0} koans and {1} lessons away from " \ + "reaching enlightenment.".format( + koans_remaining, + lessons_remaining) + + # Hat's tip to Tim Peters for the zen statements from The 'Zen + # of Python' (http://www.python.org/dev/peps/pep-0020/) + # + # Also a hat's tip to Ara T. Howard for the zen statements from his + # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html) and + # Edgecase's later permutation in the Ruby Koans + def say_something_zenlike(self): + if self.failures: + turn = self.pass_count % 37 + + zenness = ""; + if turn == 0: + zenness = "Beautiful is better than ugly." + elif turn == 1 or turn == 2: + zenness = "Explicit is better than implicit." + elif turn == 3 or turn == 4: + zenness = "Simple is better than complex." + elif turn == 5 or turn == 6: + zenness = "Complex is better than complicated." + elif turn == 7 or turn == 8: + zenness = "Flat is better than nested." + elif turn == 9 or turn == 10: + zenness = "Sparse is better than dense." + elif turn == 11 or turn == 12: + zenness = "Readability counts." + elif turn == 13 or turn == 14: + zenness = "Special cases aren't special enough to " \ + "break the rules." + elif turn == 15 or turn == 16: + zenness = "Although practicality beats purity." + elif turn == 17 or turn == 18: + zenness = "Errors should never pass silently." + elif turn == 19 or turn == 20: + zenness = "Unless explicitly silenced." + elif turn == 21 or turn == 22: + zenness = "In the face of ambiguity, refuse the " \ + "temptation to guess." + elif turn == 23 or turn == 24: + zenness = "There should be one-- and preferably only " \ + "one --obvious way to do it." + elif turn == 25 or turn == 26: + zenness = "Although that way may not be obvious at " \ + "first unless you're Dutch." + elif turn == 27 or turn == 28: + zenness = "Now is better than never." + elif turn == 29 or turn == 30: + zenness = "Although never is often better than right " \ + "now." + elif turn == 31 or turn == 32: + zenness = "If the implementation is hard to explain, " \ + "it's a bad idea." + elif turn == 33 or turn == 34: + zenness = "If the implementation is easy to explain, " \ + "it may be a good idea." + else: + zenness = "Namespaces are one honking great idea -- " \ + "let's do more of those!" + return "{0}{1}{2}{3}".format(Fore.CYAN, zenness, Fore.RESET, Style.NORMAL); + else: + return "{0}Nobody ever expects the Spanish Inquisition." \ + .format(Fore.CYAN) + + # Hopefully this will never ever happen! + return "The temple is collapsing! Run!!!" + + def total_lessons(self): + all_lessons = self.filter_all_lessons() + + if all_lessons: + return len(all_lessons) + else: + return 0 + + def total_koans(self): + return self.tests.countTestCases() + + def filter_all_lessons(self): + cur_dir = os.path.split(os.path.realpath(__file__))[0] + if not self.all_lessons: + self.all_lessons = glob.glob('{0}/../koans/about*.py'.format(cur_dir)) + self.all_lessons = filter(lambda filename: + "about_extra_credit" not in filename, + self.all_lessons) + return self.all_lessons diff --git a/test/runner/writeln_decorator.py b/test/runner/writeln_decorator.py new file mode 100644 index 00000000000..605f3f04eb6 --- /dev/null +++ b/test/runner/writeln_decorator.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import sys +import os + +# Taken from legacy python unittest +class WritelnDecorator: + """Used to decorate file-like objects with a handy 'writeln' method""" + def __init__(self,stream): + self.stream = stream + + def __getattr__(self, attr): + return getattr(self.stream,attr) + + def writeln(self, arg=None): + if arg: + self.write(arg) + self.write('\n') # text-mode streams translate to \r\n if needed + diff --git a/udf/postgresql/linux/32/8.3/lib_postgresqludf_sys.so b/udf/postgresql/linux/32/8.3/lib_postgresqludf_sys.so old mode 100755 new mode 100644 diff --git a/udf/postgresql/linux/32/8.4/lib_postgresqludf_sys.so b/udf/postgresql/linux/32/8.4/lib_postgresqludf_sys.so old mode 100755 new mode 100644 diff --git a/udf/postgresql/linux/32/9.0/lib_postgresqludf_sys.so b/udf/postgresql/linux/32/9.0/lib_postgresqludf_sys.so old mode 100755 new mode 100644 diff --git a/udf/postgresql/linux/32/9.1/lib_postgresqludf_sys.so b/udf/postgresql/linux/32/9.1/lib_postgresqludf_sys.so old mode 100755 new mode 100644 diff --git a/udf/postgresql/linux/64/8.2/lib_postgresqludf_sys.so b/udf/postgresql/linux/64/8.2/lib_postgresqludf_sys.so old mode 100755 new mode 100644 diff --git a/udf/postgresql/linux/64/8.3/lib_postgresqludf_sys.so b/udf/postgresql/linux/64/8.3/lib_postgresqludf_sys.so old mode 100755 new mode 100644 diff --git a/udf/postgresql/linux/64/8.4/lib_postgresqludf_sys.so b/udf/postgresql/linux/64/8.4/lib_postgresqludf_sys.so old mode 100755 new mode 100644 diff --git a/udf/postgresql/linux/64/9.0/lib_postgresqludf_sys.so b/udf/postgresql/linux/64/9.0/lib_postgresqludf_sys.so old mode 100755 new mode 100644 diff --git a/udf/postgresql/windows/32/8.2/lib_postgresqludf_sys.dll b/udf/postgresql/windows/32/8.2/lib_postgresqludf_sys.dll old mode 100755 new mode 100644 diff --git a/udf/postgresql/windows/32/8.3/lib_postgresqludf_sys.dll b/udf/postgresql/windows/32/8.3/lib_postgresqludf_sys.dll old mode 100755 new mode 100644 diff --git a/udf/postgresql/windows/32/8.4/lib_postgresqludf_sys.dll b/udf/postgresql/windows/32/8.4/lib_postgresqludf_sys.dll old mode 100755 new mode 100644