| OLD | NEW |
| 1 # coding: utf-8 |
| 2 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
| 3 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
| 4 |
| 1 """Helper for building, testing, and linting coverage.py. | 5 """Helper for building, testing, and linting coverage.py. |
| 2 | 6 |
| 3 To get portability, all these operations are written in Python here instead | 7 To get portability, all these operations are written in Python here instead |
| 4 of in shell scripts, batch files, or Makefiles. | 8 of in shell scripts, batch files, or Makefiles. |
| 5 | 9 |
| 6 """ | 10 """ |
| 7 | 11 |
| 12 import contextlib |
| 8 import fnmatch | 13 import fnmatch |
| 9 import glob | 14 import glob |
| 10 import inspect | 15 import inspect |
| 11 import os | 16 import os |
| 12 import platform | 17 import platform |
| 13 import socket | |
| 14 import sys | 18 import sys |
| 19 import textwrap |
| 20 import warnings |
| 15 import zipfile | 21 import zipfile |
| 16 | 22 |
| 17 | 23 |
| 24 # We want to see all warnings while we are running tests. But we also need to |
| 25 # disable warnings for some of the more complex setting up of tests. |
| 26 warnings.simplefilter("default") |
| 27 |
| 28 |
| 29 @contextlib.contextmanager |
| 30 def ignore_warnings(): |
| 31 """Context manager to ignore warning within the with statement.""" |
| 32 with warnings.catch_warnings(): |
| 33 warnings.simplefilter("ignore") |
| 34 yield |
| 35 |
| 36 |
| 18 # Functions named do_* are executable from the command line: do_blah is run | 37 # Functions named do_* are executable from the command line: do_blah is run |
| 19 # by "python igor.py blah". | 38 # by "python igor.py blah". |
| 20 | 39 |
| 21 | 40 |
| 41 def do_show_env(): |
| 42 """Show the environment variables.""" |
| 43 print("Environment:") |
| 44 for env in sorted(os.environ): |
| 45 print(" %s = %r" % (env, os.environ[env])) |
| 46 |
| 47 |
| 22 def do_remove_extension(): | 48 def do_remove_extension(): |
| 23 """Remove the compiled C extension, no matter what its name.""" | 49 """Remove the compiled C extension, no matter what its name.""" |
| 24 | 50 |
| 25 so_patterns = """ | 51 so_patterns = """ |
| 26 tracer.so | 52 tracer.so |
| 27 tracer.*.so | 53 tracer.*.so |
| 28 tracer.pyd | 54 tracer.pyd |
| 55 tracer.*.pyd |
| 29 """.split() | 56 """.split() |
| 30 | 57 |
| 31 for pattern in so_patterns: | 58 for pattern in so_patterns: |
| 32 pattern = os.path.join("coverage", pattern) | 59 pattern = os.path.join("coverage", pattern) |
| 33 for filename in glob.glob(pattern): | 60 for filename in glob.glob(pattern): |
| 34 try: | 61 try: |
| 35 os.remove(filename) | 62 os.remove(filename) |
| 36 except OSError: | 63 except OSError: |
| 37 pass | 64 pass |
| 38 | 65 |
| 39 def run_tests(tracer, *nose_args): | 66 |
| 40 """The actual running of tests.""" | 67 def label_for_tracer(tracer): |
| 41 import nose.core | 68 """Get the label for these tests.""" |
| 42 if tracer == "py": | 69 if tracer == "py": |
| 43 label = "with Python tracer" | 70 label = "with Python tracer" |
| 44 else: | 71 else: |
| 45 label = "with C tracer" | 72 label = "with C tracer" |
| 46 if os.environ.get("COVERAGE_NO_EXTENSION"): | 73 |
| 47 print("Skipping tests, no C extension in this environment") | 74 return label |
| 48 return | 75 |
| 49 print_banner(label) | 76 |
| 50 os.environ["COVERAGE_TEST_TRACER"] = tracer | 77 def should_skip(tracer): |
| 78 """Is there a reason to skip these tests?""" |
| 79 if tracer == "py": |
| 80 skipper = os.environ.get("COVERAGE_NO_PYTRACER") |
| 81 else: |
| 82 skipper = ( |
| 83 os.environ.get("COVERAGE_NO_EXTENSION") or |
| 84 os.environ.get("COVERAGE_NO_CTRACER") |
| 85 ) |
| 86 |
| 87 if skipper: |
| 88 msg = "Skipping tests " + label_for_tracer(tracer) |
| 89 if len(skipper) > 1: |
| 90 msg += ": " + skipper |
| 91 else: |
| 92 msg = "" |
| 93 |
| 94 return msg |
| 95 |
| 96 |
| 97 def run_tests(tracer, *nose_args): |
| 98 """The actual running of tests.""" |
| 99 with ignore_warnings(): |
| 100 import nose.core |
| 101 |
| 102 if 'COVERAGE_TESTING' not in os.environ: |
| 103 os.environ['COVERAGE_TESTING'] = "True" |
| 104 print_banner(label_for_tracer(tracer)) |
| 51 nose_args = ["nosetests"] + list(nose_args) | 105 nose_args = ["nosetests"] + list(nose_args) |
| 52 nose.core.main(argv=nose_args) | 106 nose.core.main(argv=nose_args) |
| 53 | 107 |
| 108 |
| 54 def run_tests_with_coverage(tracer, *nose_args): | 109 def run_tests_with_coverage(tracer, *nose_args): |
| 55 """Run tests, but with coverage.""" | 110 """Run tests, but with coverage.""" |
| 56 import coverage | |
| 57 | 111 |
| 112 # Need to define this early enough that the first import of env.py sees it. |
| 113 os.environ['COVERAGE_TESTING'] = "True" |
| 58 os.environ['COVERAGE_PROCESS_START'] = os.path.abspath('metacov.ini') | 114 os.environ['COVERAGE_PROCESS_START'] = os.path.abspath('metacov.ini') |
| 59 os.environ['COVERAGE_HOME'] = os.getcwd() | 115 os.environ['COVERAGE_HOME'] = os.getcwd() |
| 60 | 116 |
| 61 # Create the .pth file that will let us measure coverage in sub-processes. | 117 # Create the .pth file that will let us measure coverage in sub-processes. |
| 118 # The .pth file seems to have to be alphabetically after easy-install.pth |
| 119 # or the sys.path entries aren't created right? |
| 62 import nose | 120 import nose |
| 63 pth_dir = os.path.dirname(os.path.dirname(nose.__file__)) | 121 pth_dir = os.path.dirname(os.path.dirname(nose.__file__)) |
| 64 pth_path = os.path.join(pth_dir, "covcov.pth") | 122 pth_path = os.path.join(pth_dir, "zzz_metacov.pth") |
| 65 pth_file = open(pth_path, "w") | 123 with open(pth_path, "w") as pth_file: |
| 66 try: | |
| 67 pth_file.write("import coverage; coverage.process_startup()\n") | 124 pth_file.write("import coverage; coverage.process_startup()\n") |
| 68 finally: | |
| 69 pth_file.close() | |
| 70 | 125 |
| 126 # Make names for the data files that keep all the test runs distinct. |
| 127 impl = platform.python_implementation().lower() |
| 71 version = "%s%s" % sys.version_info[:2] | 128 version = "%s%s" % sys.version_info[:2] |
| 72 suffix = "%s_%s_%s" % (version, tracer, socket.gethostname()) | 129 if '__pypy__' in sys.builtin_module_names: |
| 130 version += "_%s%s" % sys.pypy_version_info[:2] |
| 131 suffix = "%s%s_%s_%s" % (impl, version, tracer, platform.platform()) |
| 73 | 132 |
| 74 cov = coverage.coverage(config_file="metacov.ini", data_suffix=suffix) | 133 os.environ['COVERAGE_METAFILE'] = os.path.abspath(".metacov."+suffix) |
| 75 # Cheap trick: the coverage code itself is excluded from measurement, but | 134 |
| 76 # if we clobber the cover_prefix in the coverage object, we can defeat the | 135 import coverage |
| 77 # self-detection. | 136 cov = coverage.Coverage(config_file="metacov.ini", data_suffix=False) |
| 137 # Cheap trick: the coverage.py code itself is excluded from measurement, |
| 138 # but if we clobber the cover_prefix in the coverage object, we can defeat |
| 139 # the self-detection. |
| 78 cov.cover_prefix = "Please measure coverage.py!" | 140 cov.cover_prefix = "Please measure coverage.py!" |
| 79 cov.erase() | 141 cov._warn_unimported_source = False |
| 80 cov.start() | 142 cov.start() |
| 81 | 143 |
| 82 try: | 144 try: |
| 83 # Re-import coverage to get it coverage tested! I don't understand all | 145 # Re-import coverage to get it coverage tested! I don't understand all |
| 84 # the mechanics here, but if I don't carry over the imported modules | 146 # the mechanics here, but if I don't carry over the imported modules |
| 85 # (in covmods), then things go haywire (os == None, eventually). | 147 # (in covmods), then things go haywire (os == None, eventually). |
| 86 covmods = {} | 148 covmods = {} |
| 87 covdir = os.path.split(coverage.__file__)[0] | 149 covdir = os.path.split(coverage.__file__)[0] |
| 88 # We have to make a list since we'll be deleting in the loop. | 150 # We have to make a list since we'll be deleting in the loop. |
| 89 modules = list(sys.modules.items()) | 151 modules = list(sys.modules.items()) |
| 90 for name, mod in modules: | 152 for name, mod in modules: |
| 91 if name.startswith('coverage'): | 153 if name.startswith('coverage'): |
| 92 if getattr(mod, '__file__', "??").startswith(covdir): | 154 if getattr(mod, '__file__', "??").startswith(covdir): |
| 93 covmods[name] = mod | 155 covmods[name] = mod |
| 94 del sys.modules[name] | 156 del sys.modules[name] |
| 95 import coverage # don't warn about re-import: pylint: disable=W0404 | 157 import coverage # pylint: disable=reimported |
| 96 sys.modules.update(covmods) | 158 sys.modules.update(covmods) |
| 97 | 159 |
| 98 # Run nosetests, with the arguments from our command line. | 160 # Run nosetests, with the arguments from our command line. |
| 99 try: | 161 try: |
| 100 run_tests(tracer, *nose_args) | 162 run_tests(tracer, *nose_args) |
| 101 except SystemExit: | 163 except SystemExit: |
| 102 # nose3 seems to raise SystemExit, not sure why? | 164 # nose3 seems to raise SystemExit, not sure why? |
| 103 pass | 165 pass |
| 104 finally: | 166 finally: |
| 105 cov.stop() | 167 cov.stop() |
| 106 os.remove(pth_path) | 168 os.remove(pth_path) |
| 107 | 169 |
| 170 cov.combine() |
| 108 cov.save() | 171 cov.save() |
| 109 | 172 |
| 173 |
| 110 def do_combine_html(): | 174 def do_combine_html(): |
| 111 """Combine data from a meta-coverage run, and make the HTML report.""" | 175 """Combine data from a meta-coverage run, and make the HTML and XML reports.
""" |
| 112 import coverage | 176 import coverage |
| 113 os.environ['COVERAGE_HOME'] = os.getcwd() | 177 os.environ['COVERAGE_HOME'] = os.getcwd() |
| 114 cov = coverage.coverage(config_file="metacov.ini") | 178 os.environ['COVERAGE_METAFILE'] = os.path.abspath(".metacov") |
| 179 cov = coverage.Coverage(config_file="metacov.ini") |
| 115 cov.load() | 180 cov.load() |
| 116 cov.combine() | 181 cov.combine() |
| 117 cov.save() | 182 cov.save() |
| 118 cov.html_report() | 183 cov.html_report() |
| 184 cov.xml_report() |
| 185 |
| 119 | 186 |
| 120 def do_test_with_tracer(tracer, *noseargs): | 187 def do_test_with_tracer(tracer, *noseargs): |
| 121 """Run nosetests with a particular tracer.""" | 188 """Run nosetests with a particular tracer.""" |
| 189 # If we should skip these tests, skip them. |
| 190 skip_msg = should_skip(tracer) |
| 191 if skip_msg: |
| 192 print(skip_msg) |
| 193 return |
| 194 |
| 195 os.environ["COVERAGE_TEST_TRACER"] = tracer |
| 122 if os.environ.get("COVERAGE_COVERAGE", ""): | 196 if os.environ.get("COVERAGE_COVERAGE", ""): |
| 123 return run_tests_with_coverage(tracer, *noseargs) | 197 return run_tests_with_coverage(tracer, *noseargs) |
| 124 else: | 198 else: |
| 125 return run_tests(tracer, *noseargs) | 199 return run_tests(tracer, *noseargs) |
| 126 | 200 |
| 201 |
| 127 def do_zip_mods(): | 202 def do_zip_mods(): |
| 128 """Build the zipmods.zip file.""" | 203 """Build the zipmods.zip file.""" |
| 129 zf = zipfile.ZipFile("tests/zipmods.zip", "w") | 204 zf = zipfile.ZipFile("tests/zipmods.zip", "w") |
| 205 |
| 206 # Take one file from disk. |
| 130 zf.write("tests/covmodzip1.py", "covmodzip1.py") | 207 zf.write("tests/covmodzip1.py", "covmodzip1.py") |
| 208 |
| 209 # The others will be various encodings. |
| 210 source = textwrap.dedent(u"""\ |
| 211 # coding: {encoding} |
| 212 text = u"{text}" |
| 213 ords = {ords} |
| 214 assert [ord(c) for c in text] == ords |
| 215 print(u"All OK with {encoding}") |
| 216 """) |
| 217 details = [ |
| 218 (u'utf8', u'ⓗⓔⓛⓛⓞ, ⓦⓞⓡⓛⓓ'), |
| 219 (u'gb2312', u'你好,世界'), |
| 220 (u'hebrew', u'שלום, עולם'), |
| 221 (u'shift_jis', u'こんにちは世界'), |
| 222 ] |
| 223 for encoding, text in details: |
| 224 filename = 'encoded_{0}.py'.format(encoding) |
| 225 ords = [ord(c) for c in text] |
| 226 source_text = source.format(encoding=encoding, text=text, ords=ords) |
| 227 zf.writestr(filename, source_text.encode(encoding)) |
| 228 |
| 131 zf.close() | 229 zf.close() |
| 132 | 230 |
| 231 |
| 133 def do_install_egg(): | 232 def do_install_egg(): |
| 134 """Install the egg1 egg for tests.""" | 233 """Install the egg1 egg for tests.""" |
| 135 # I am pretty certain there are easier ways to install eggs... | 234 # I am pretty certain there are easier ways to install eggs... |
| 136 # pylint: disable=F0401,E0611,E1101 | 235 # pylint: disable=import-error,no-name-in-module |
| 137 import distutils.core | |
| 138 cur_dir = os.getcwd() | 236 cur_dir = os.getcwd() |
| 139 os.chdir("tests/eggsrc") | 237 os.chdir("tests/eggsrc") |
| 140 distutils.core.run_setup("setup.py", ["--quiet", "bdist_egg"]) | 238 with ignore_warnings(): |
| 141 egg = glob.glob("dist/*.egg")[0] | 239 import distutils.core |
| 142 distutils.core.run_setup( | 240 distutils.core.run_setup("setup.py", ["--quiet", "bdist_egg"]) |
| 143 "setup.py", ["--quiet", "easy_install", "--no-deps", "--zip-ok", egg] | 241 egg = glob.glob("dist/*.egg")[0] |
| 144 ) | 242 distutils.core.run_setup( |
| 243 "setup.py", ["--quiet", "easy_install", "--no-deps", "--zip-ok", egg
] |
| 244 ) |
| 145 os.chdir(cur_dir) | 245 os.chdir(cur_dir) |
| 146 | 246 |
| 247 |
| 147 def do_check_eol(): | 248 def do_check_eol(): |
| 148 """Check files for incorrect newlines and trailing whitespace.""" | 249 """Check files for incorrect newlines and trailing whitespace.""" |
| 149 | 250 |
| 150 ignore_dirs = [ | 251 ignore_dirs = [ |
| 151 '.svn', '.hg', '.tox', '.tox_kits', 'coverage.egg-info', | 252 '.svn', '.hg', '.git', |
| 152 '_build', 'covtestegg1.egg-info', | 253 '.tox*', |
| 254 '*.egg-info', |
| 255 '_build', |
| 153 ] | 256 ] |
| 154 checked = set([]) | 257 checked = set() |
| 155 | 258 |
| 156 def check_file(fname, crlf=True, trail_white=True): | 259 def check_file(fname, crlf=True, trail_white=True): |
| 157 """Check a single file for whitespace abuse.""" | 260 """Check a single file for whitespace abuse.""" |
| 158 fname = os.path.relpath(fname) | 261 fname = os.path.relpath(fname) |
| 159 if fname in checked: | 262 if fname in checked: |
| 160 return | 263 return |
| 161 checked.add(fname) | 264 checked.add(fname) |
| 162 | 265 |
| 163 line = None | 266 line = None |
| 164 for n, line in enumerate(open(fname, "rb")): | 267 with open(fname, "rb") as f: |
| 165 if crlf: | 268 for n, line in enumerate(f, start=1): |
| 166 if "\r" in line: | 269 if crlf: |
| 167 print("%s@%d: CR found" % (fname, n+1)) | 270 if "\r" in line: |
| 168 return | 271 print("%s@%d: CR found" % (fname, n)) |
| 169 if trail_white: | 272 return |
| 170 line = line[:-1] | 273 if trail_white: |
| 171 if not crlf: | 274 line = line[:-1] |
| 172 line = line.rstrip('\r') | 275 if not crlf: |
| 173 if line.rstrip() != line: | 276 line = line.rstrip('\r') |
| 174 print("%s@%d: trailing whitespace found" % (fname, n+1)) | 277 if line.rstrip() != line: |
| 175 return | 278 print("%s@%d: trailing whitespace found" % (fname, n)) |
| 279 return |
| 176 | 280 |
| 177 if line is not None and not line.strip(): | 281 if line is not None and not line.strip(): |
| 178 print("%s: final blank line" % (fname,)) | 282 print("%s: final blank line" % (fname,)) |
| 179 | 283 |
| 180 def check_files(root, patterns, **kwargs): | 284 def check_files(root, patterns, **kwargs): |
| 181 """Check a number of files for whitespace abuse.""" | 285 """Check a number of files for whitespace abuse.""" |
| 182 for root, dirs, files in os.walk(root): | 286 for root, dirs, files in os.walk(root): |
| 183 for f in files: | 287 for f in files: |
| 184 fname = os.path.join(root, f) | 288 fname = os.path.join(root, f) |
| 185 for p in patterns: | 289 for p in patterns: |
| 186 if fnmatch.fnmatch(fname, p): | 290 if fnmatch.fnmatch(fname, p): |
| 187 check_file(fname, **kwargs) | 291 check_file(fname, **kwargs) |
| 188 break | 292 break |
| 189 for dir_name in ignore_dirs: | 293 for ignore_dir in ignore_dirs: |
| 190 if dir_name in dirs: | 294 ignored = [] |
| 295 for dir_name in dirs: |
| 296 if fnmatch.fnmatch(dir_name, ignore_dir): |
| 297 ignored.append(dir_name) |
| 298 for dir_name in ignored: |
| 191 dirs.remove(dir_name) | 299 dirs.remove(dir_name) |
| 192 | 300 |
| 193 check_files("coverage", ["*.py", "*.c"]) | 301 check_files("coverage", ["*.py"]) |
| 302 check_files("coverage/ctracer", ["*.c", "*.h"]) |
| 194 check_files("coverage/htmlfiles", ["*.html", "*.css", "*.js"]) | 303 check_files("coverage/htmlfiles", ["*.html", "*.css", "*.js"]) |
| 195 check_file("tests/farm/html/src/bom.py", crlf=False) | 304 check_file("tests/farm/html/src/bom.py", crlf=False) |
| 196 check_files("tests", ["*.py"]) | 305 check_files("tests", ["*.py"]) |
| 197 check_files("tests", ["*,cover"], trail_white=False) | 306 check_files("tests", ["*,cover"], trail_white=False) |
| 198 check_files("tests/js", ["*.js", "*.html"]) | 307 check_files("tests/js", ["*.js", "*.html"]) |
| 199 check_file("setup.py") | 308 check_file("setup.py") |
| 200 check_file("igor.py") | 309 check_file("igor.py") |
| 201 check_file("Makefile") | 310 check_file("Makefile") |
| 202 check_file(".hgignore") | 311 check_file(".hgignore") |
| 203 check_file(".travis.yml") | 312 check_file(".travis.yml") |
| 204 check_files("doc", ["*.rst"]) | 313 check_files(".", ["*.rst", "*.txt"]) |
| 205 check_files(".", ["*.txt"]) | 314 check_files(".", ["*.pip"]) |
| 206 | 315 |
| 207 | 316 |
| 208 def print_banner(label): | 317 def print_banner(label): |
| 209 """Print the version of Python.""" | 318 """Print the version of Python.""" |
| 210 try: | 319 try: |
| 211 impl = platform.python_implementation() | 320 impl = platform.python_implementation() |
| 212 except AttributeError: | 321 except AttributeError: |
| 213 impl = "Python" | 322 impl = "Python" |
| 214 | 323 |
| 215 version = platform.python_version() | 324 version = platform.python_version() |
| 216 | 325 |
| 217 if '__pypy__' in sys.builtin_module_names: | 326 if '__pypy__' in sys.builtin_module_names: |
| 218 pypy_version = sys.pypy_version_info # pylint: disable=E1101 | 327 version += " (pypy %s)" % ".".join(str(v) for v in sys.pypy_version_info
) |
| 219 version += " (pypy %s)" % ".".join([str(v) for v in pypy_version]) | |
| 220 | 328 |
| 221 print('=== %s %s %s (%s) ===' % (impl, version, label, sys.executable)) | 329 which_python = os.path.relpath(sys.executable) |
| 330 print('=== %s %s %s (%s) ===' % (impl, version, label, which_python)) |
| 331 sys.stdout.flush() |
| 222 | 332 |
| 223 | 333 |
| 224 def do_help(): | 334 def do_help(): |
| 225 """List the available commands""" | 335 """List the available commands""" |
| 226 items = list(globals().items()) | 336 items = list(globals().items()) |
| 227 items.sort() | 337 items.sort() |
| 228 for name, value in items: | 338 for name, value in items: |
| 229 if name.startswith('do_'): | 339 if name.startswith('do_'): |
| 230 print("%-20s%s" % (name[3:], value.__doc__)) | 340 print("%-20s%s" % (name[3:], value.__doc__)) |
| 231 | 341 |
| 232 | 342 |
| 343 def analyze_args(function): |
| 344 """What kind of args does `function` expect? |
| 345 |
| 346 Returns: |
| 347 star, num_pos: |
| 348 star(boolean): Does `function` accept *args? |
| 349 num_args(int): How many positional arguments does `function` have? |
| 350 """ |
| 351 try: |
| 352 getargspec = inspect.getfullargspec |
| 353 except AttributeError: |
| 354 getargspec = inspect.getargspec |
| 355 argspec = getargspec(function) |
| 356 return bool(argspec[1]), len(argspec[0]) |
| 357 |
| 358 |
| 233 def main(args): | 359 def main(args): |
| 234 """Main command-line execution for igor. | 360 """Main command-line execution for igor. |
| 235 | 361 |
| 236 Verbs are taken from the command line, and extra words taken as directed | 362 Verbs are taken from the command line, and extra words taken as directed |
| 237 by the arguments needed by the handler. | 363 by the arguments needed by the handler. |
| 238 | 364 |
| 239 """ | 365 """ |
| 240 while args: | 366 while args: |
| 241 verb = args.pop(0) | 367 verb = args.pop(0) |
| 242 handler = globals().get('do_'+verb) | 368 handler = globals().get('do_'+verb) |
| 243 if handler is None: | 369 if handler is None: |
| 244 print("*** No handler for %r" % verb) | 370 print("*** No handler for %r" % verb) |
| 245 return 1 | 371 return 1 |
| 246 argspec = inspect.getargspec(handler) | 372 star, num_args = analyze_args(handler) |
| 247 if argspec[1]: | 373 if star: |
| 248 # Handler has *args, give it all the rest of the command line. | 374 # Handler has *args, give it all the rest of the command line. |
| 249 handler_args = args | 375 handler_args = args |
| 250 args = [] | 376 args = [] |
| 251 else: | 377 else: |
| 252 # Handler has specific arguments, give it only what it needs. | 378 # Handler has specific arguments, give it only what it needs. |
| 253 num_args = len(argspec[0]) | |
| 254 handler_args = args[:num_args] | 379 handler_args = args[:num_args] |
| 255 args = args[num_args:] | 380 args = args[num_args:] |
| 256 ret = handler(*handler_args) | 381 ret = handler(*handler_args) |
| 257 # If a handler returns a failure-like value, stop. | 382 # If a handler returns a failure-like value, stop. |
| 258 if ret: | 383 if ret: |
| 259 return ret | 384 return ret |
| 260 | 385 |
| 386 |
| 261 if __name__ == '__main__': | 387 if __name__ == '__main__': |
| 262 sys.exit(main(sys.argv[1:])) | 388 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |