| OLD | NEW |
| 1 # Copyright 2017 The LUCI Authors. All rights reserved. | 1 # Copyright 2017 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 from __future__ import print_function | 5 from __future__ import print_function |
| 6 | 6 |
| 7 import bdb | 7 import bdb |
| 8 import cStringIO | 8 import cStringIO |
| 9 import contextlib | 9 import contextlib |
| 10 import copy | 10 import copy |
| 11 import coverage | |
| 12 import datetime | 11 import datetime |
| 13 import difflib | 12 import difflib |
| 14 import fnmatch | 13 import fnmatch |
| 15 import functools | 14 import functools |
| 16 import json | 15 import json |
| 17 import multiprocessing | 16 import multiprocessing |
| 18 import os | 17 import os |
| 19 import pdb | 18 import pdb |
| 20 import pprint | 19 import pprint |
| 21 import re | 20 import re |
| 22 import shutil | 21 import shutil |
| 23 import signal | 22 import signal |
| 24 import sys | 23 import sys |
| 25 import tempfile | 24 import tempfile |
| 26 import traceback | 25 import traceback |
| 27 | 26 |
| 28 from google.protobuf import json_format | |
| 29 | |
| 30 from . import checker | 27 from . import checker |
| 31 from . import config_types | 28 from . import config_types |
| 32 from . import loader | 29 from . import loader |
| 30 from . import package |
| 33 from . import run | 31 from . import run |
| 34 from . import step_runner | 32 from . import step_runner |
| 35 from . import stream | 33 from . import stream |
| 34 |
| 35 from . import env |
| 36 |
| 37 from google.protobuf import json_format |
| 38 |
| 36 from . import test_result_pb2 | 39 from . import test_result_pb2 |
| 37 | 40 |
| 38 from . import env | 41 from . import env |
| 39 | 42 |
| 40 import argparse # this is vendored | 43 import argparse # this is vendored |
| 41 | 44 |
| 42 | 45 |
| 43 # These variables must be set in the dynamic scope of the functions in this | 46 # These variables must be set in the dynamic scope of the functions in this |
| 44 # file. We do this instead of passing because they're not picklable, and | 47 # file. We do this instead of passing because they're not picklable, and |
| 45 # that's required by multiprocessing. | 48 # that's required by multiprocessing. |
| (...skipping 15 matching lines...) Expand all Loading... |
| 61 | 64 |
| 62 | 65 |
| 63 class PostProcessError(ValueError): | 66 class PostProcessError(ValueError): |
| 64 """Exception raised when any of the post-process hooks fails.""" | 67 """Exception raised when any of the post-process hooks fails.""" |
| 65 pass | 68 pass |
| 66 | 69 |
| 67 | 70 |
| 68 @contextlib.contextmanager | 71 @contextlib.contextmanager |
| 69 def coverage_context(include=None, enable=True): | 72 def coverage_context(include=None, enable=True): |
| 70 """Context manager that records coverage data.""" | 73 """Context manager that records coverage data.""" |
| 74 # TODO(iannucci): once we're always bootstrapping, move this to the top. |
| 75 import coverage |
| 71 c = coverage.coverage(config_file=False, include=include) | 76 c = coverage.coverage(config_file=False, include=include) |
| 72 | 77 |
| 73 if not enable: | 78 if not enable: |
| 74 yield c | 79 yield c |
| 75 return | 80 return |
| 76 | 81 |
| 77 # Sometimes our strict include lists will result in a run | 82 # Sometimes our strict include lists will result in a run |
| 78 # not adding any coverage info. That's okay, avoid output spam. | 83 # not adding any coverage info. That's okay, avoid output spam. |
| 79 c._warn_no_data = False | 84 c._warn_no_data = False |
| 80 | 85 |
| (...skipping 257 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 338 v['name'] = k | 343 v['name'] = k |
| 339 raw_expectations = rslt | 344 raw_expectations = rslt |
| 340 | 345 |
| 341 # empty means drop expectation | 346 # empty means drop expectation |
| 342 result_data = raw_expectations.values() if raw_expectations else None | 347 result_data = raw_expectations.values() if raw_expectations else None |
| 343 return (result_data, failed_checks, coverage_data) | 348 return (result_data, failed_checks, coverage_data) |
| 344 | 349 |
| 345 | 350 |
| 346 def get_tests(test_filter=None): | 351 def get_tests(test_filter=None): |
| 347 """Returns a list of tests for current recipe package.""" | 352 """Returns a list of tests for current recipe package.""" |
| 353 # TODO(iannucci): once we're always bootstrapping, move this to the top. |
| 354 import coverage |
| 355 |
| 348 tests = [] | 356 tests = [] |
| 349 coverage_data = coverage.CoverageData() | 357 coverage_data = coverage.CoverageData() |
| 350 | 358 |
| 351 all_modules = set(_UNIVERSE_VIEW.loop_over_recipe_modules()) | 359 all_modules = set(_UNIVERSE_VIEW.loop_over_recipe_modules()) |
| 352 covered_modules = set() | 360 covered_modules = set() |
| 353 | 361 |
| 354 base_covers = [] | 362 base_covers = [] |
| 355 | 363 |
| 356 coverage_include = os.path.join(_UNIVERSE_VIEW.module_dir, '*', '*.py') | 364 coverage_include = os.path.join(_UNIVERSE_VIEW.module_dir, '*', '*.py') |
| 357 for module in all_modules: | 365 for module in all_modules: |
| (...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 537 # Exclude recipe engine files from simulation test coverage. Simulation tests | 545 # Exclude recipe engine files from simulation test coverage. Simulation tests |
| 538 # should cover "user space" recipe code (recipes and modules), not the engine. | 546 # should cover "user space" recipe code (recipes and modules), not the engine. |
| 539 # The engine is covered by unit tests, not simulation tests. | 547 # The engine is covered by unit tests, not simulation tests. |
| 540 omit.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '*')) | 548 omit.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '*')) |
| 541 | 549 |
| 542 return omit | 550 return omit |
| 543 | 551 |
| 544 | 552 |
| 545 def report_coverage_version(): | 553 def report_coverage_version(): |
| 546 """Prints info about coverage module (for debugging).""" | 554 """Prints info about coverage module (for debugging).""" |
| 555 # TODO(iannucci): once we're always bootstrapping, move this to the top. |
| 556 import coverage |
| 557 |
| 547 print('Using coverage %s from %r' % (coverage.__version__, coverage.__file__)) | 558 print('Using coverage %s from %r' % (coverage.__version__, coverage.__file__)) |
| 548 | 559 |
| 549 | 560 |
| 550 @contextlib.contextmanager | 561 @contextlib.contextmanager |
| 551 def scoped_override(obj, attr, override): | 562 def scoped_override(obj, attr, override): |
| 552 """Sets |obj|.|attr| to |override| in scope of the context manager.""" | 563 """Sets |obj|.|attr| to |override| in scope of the context manager.""" |
| 553 orig = getattr(obj, attr) | 564 orig = getattr(obj, attr) |
| 554 setattr(obj, attr, override) | 565 setattr(obj, attr, override) |
| 555 yield | 566 yield |
| 556 setattr(obj, attr, orig) | 567 setattr(obj, attr, orig) |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 604 continue | 615 continue |
| 605 full_subentry = os.path.join(full_entry, subentry) | 616 full_subentry = os.path.join(full_entry, subentry) |
| 606 collected_expectations.add(full_subentry) | 617 collected_expectations.add(full_subentry) |
| 607 elif entry.endswith('.json'): | 618 elif entry.endswith('.json'): |
| 608 collected_expectations.add(full_entry) | 619 collected_expectations.add(full_entry) |
| 609 return collected_expectations | 620 return collected_expectations |
| 610 | 621 |
| 611 | 622 |
| 612 def run_run(test_filter, jobs=None, debug=False, train=False, json_file=None): | 623 def run_run(test_filter, jobs=None, debug=False, train=False, json_file=None): |
| 613 """Implementation of the 'run' command.""" | 624 """Implementation of the 'run' command.""" |
| 625 # TODO(iannucci): once we're always bootstrapping, move this to the top. |
| 626 import coverage |
| 627 |
| 614 start_time = datetime.datetime.now() | 628 start_time = datetime.datetime.now() |
| 615 | 629 |
| 616 report_coverage_version() | 630 report_coverage_version() |
| 617 | 631 |
| 618 rc = 0 | 632 rc = 0 |
| 619 results_proto = test_result_pb2.TestResult() | 633 results_proto = test_result_pb2.TestResult() |
| 620 results_proto.version = 1 | 634 results_proto.version = 1 |
| 621 results_proto.valid = True | 635 results_proto.valid = True |
| 622 | 636 |
| 623 tests, coverage_data, uncovered_modules = get_tests(test_filter) | 637 tests, coverage_data, uncovered_modules = get_tests(test_filter) |
| (...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 896 help='glob filter for the tests to run; ' | 910 help='glob filter for the tests to run; ' |
| 897 'can be specified multiple times; ' | 911 'can be specified multiple times; ' |
| 898 'the globs have the form of ' | 912 'the globs have the form of ' |
| 899 '`<recipe_name_glob>[.<test_name_glob>]`. If `.<test_name_glob>` ' | 913 '`<recipe_name_glob>[.<test_name_glob>]`. If `.<test_name_glob>` ' |
| 900 'is omitted, it is implied to be `*.*`, i.e. any recipe with this ' | 914 'is omitted, it is implied to be `*.*`, i.e. any recipe with this ' |
| 901 'prefix and all tests.)') | 915 'prefix and all tests.)') |
| 902 | 916 |
| 903 return parser.parse_args(args) | 917 return parser.parse_args(args) |
| 904 | 918 |
| 905 | 919 |
| 906 def main(universe_view, raw_args, engine_flags): | 920 def add_subparser(parser): |
| 921 # TODO(iannucci): add actual subparsers here, make main argparse parser to do |
| 922 # full commandline parsing to allow --help to work correctly. |
| 923 test_p = parser.add_parser( |
| 924 'test', |
| 925 description='Generate or check expectations by simulation') |
| 926 test_p.add_argument('args', nargs=argparse.REMAINDER) |
| 927 |
| 928 def postprocess_func(_parser, args): |
| 929 # Auto-enable bootstrap for test command invocations (necessary to get |
| 930 # recent enough version of coverage package), unless explicitly disabled. |
| 931 if args.use_bootstrap is None: |
| 932 args.use_bootstrap = True |
| 933 |
| 934 test_p.set_defaults(command='test', func=main, |
| 935 postprocess_func=postprocess_func) |
| 936 |
| 937 |
| 938 def main(package_deps, args): |
| 907 """Runs simulation tests on a given repo of recipes. | 939 """Runs simulation tests on a given repo of recipes. |
| 908 | 940 |
| 909 Args: | 941 Args: |
| 910 universe_view: an UniverseView object to operate on | 942 universe_view: an UniverseView object to operate on |
| 911 raw_args: command line arguments to the 'test' command | 943 raw_args: command line arguments to the 'test' command |
| 912 engine_flags: recipe engine command-line flags | 944 engine_flags: recipe engine command-line flags |
| 913 Returns: | 945 Returns: |
| 914 Exit code | 946 Exit code |
| 915 """ | 947 """ |
| 948 universe = loader.RecipeUniverse(package_deps, args.package) |
| 949 universe_view = loader.UniverseView(universe, package_deps.root_package) |
| 950 raw_args=args.args |
| 951 engine_flags=args.operational_args.engine_flags |
| 952 |
| 953 # Prevent flakiness caused by stale pyc files. |
| 954 package.cleanup_pyc(package_deps.root_package.recipes_dir) |
| 955 |
| 916 global _UNIVERSE_VIEW | 956 global _UNIVERSE_VIEW |
| 917 _UNIVERSE_VIEW = universe_view | 957 _UNIVERSE_VIEW = universe_view |
| 918 global _ENGINE_FLAGS | 958 global _ENGINE_FLAGS |
| 919 _ENGINE_FLAGS = engine_flags | 959 _ENGINE_FLAGS = engine_flags |
| 920 | 960 |
| 921 args = parse_args(raw_args) | 961 args = parse_args(raw_args) |
| 922 return args.func(args) | 962 return args.func(args) |
| OLD | NEW |