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 |