| 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 |
| (...skipping 806 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 817 elif isinstance(obj, list): | 817 elif isinstance(obj, list): |
| 818 return [re_encode(i) for i in obj] | 818 return [re_encode(i) for i in obj] |
| 819 elif isinstance(obj, (unicode, str)): | 819 elif isinstance(obj, (unicode, str)): |
| 820 if isinstance(obj, str): | 820 if isinstance(obj, str): |
| 821 obj = obj.decode('utf-8', 'replace') | 821 obj = obj.decode('utf-8', 'replace') |
| 822 return obj.encode('utf-8', 'replace') | 822 return obj.encode('utf-8', 'replace') |
| 823 else: | 823 else: |
| 824 return obj | 824 return obj |
| 825 | 825 |
| 826 | 826 |
| 827 def parse_args(args): | 827 def add_subparser(parser): |
| 828 """Returns parsed command line arguments.""" | 828 helpstr='Generate or check expectations by simulation.' |
| 829 parser = argparse.ArgumentParser() | 829 test_p = parser.add_parser( |
| 830 'test', |
| 831 help=helpstr, description=helpstr) |
| 830 | 832 |
| 831 def normalize_filter(filt): | 833 def normalize_filter(filt): |
| 832 if not filt: | 834 if not filt: |
| 833 parser.error('empty filters not allowed') | 835 raise argparse.ArgumentTypeError('empty filters not allowed') |
| 834 # filters missing a test_name portion imply that its a recipe prefix and we | 836 # filters missing a test_name portion imply that its a recipe prefix and we |
| 835 # should run all tests for the matching recipes. | 837 # should run all tests for the matching recipes. |
| 836 return filt if '.' in filt else filt+'*.*' | 838 return filt if '.' in filt else filt+'*.*' |
| 837 | 839 |
| 838 subp = parser.add_subparsers(dest='command') | 840 subp = test_p.add_subparsers(dest='subcommand') |
| 839 | 841 |
| 840 list_p = subp.add_parser('list', description='Print all test names') | 842 helpstr = 'Print all test names.' |
| 841 list_p.set_defaults(func=lambda opts: run_list(opts.json)) | 843 list_p = subp.add_parser( |
| 844 'list', help=helpstr, description=helpstr) |
| 845 list_p.set_defaults(subfunc=lambda opts: run_list(opts.json)) |
| 842 list_p.add_argument( | 846 list_p.add_argument( |
| 843 '--json', metavar='FILE', type=argparse.FileType('w'), | 847 '--json', metavar='FILE', type=argparse.FileType('w'), |
| 844 help='path to JSON output file') | 848 help='path to JSON output file') |
| 845 | 849 |
| 850 helpstr='Compare results of two test runs.' |
| 846 diff_p = subp.add_parser( | 851 diff_p = subp.add_parser( |
| 847 'diff', description='Compare results of two test runs') | 852 'diff', help=helpstr, description=helpstr) |
| 848 diff_p.set_defaults(func=lambda opts: run_diff( | 853 diff_p.set_defaults(subfunc=lambda opts: run_diff( |
| 849 opts.baseline, opts.actual, json_file=opts.json)) | 854 opts.baseline, opts.actual, json_file=opts.json)) |
| 850 diff_p.add_argument( | 855 diff_p.add_argument( |
| 851 '--baseline', metavar='FILE', type=argparse.FileType('r'), | 856 '--baseline', metavar='FILE', type=argparse.FileType('r'), |
| 852 required=True, | 857 required=True, |
| 853 help='path to baseline JSON file') | 858 help='path to baseline JSON file') |
| 854 diff_p.add_argument( | 859 diff_p.add_argument( |
| 855 '--actual', metavar='FILE', type=argparse.FileType('r'), | 860 '--actual', metavar='FILE', type=argparse.FileType('r'), |
| 856 required=True, | 861 required=True, |
| 857 help='path to actual JSON file') | 862 help='path to actual JSON file') |
| 858 diff_p.add_argument( | 863 diff_p.add_argument( |
| 859 '--json', metavar='FILE', type=argparse.FileType('w'), | 864 '--json', metavar='FILE', type=argparse.FileType('w'), |
| 860 help='path to JSON output file') | 865 help='path to JSON output file') |
| 861 | 866 |
| 862 run_p = subp.add_parser('run', description='Run the tests') | 867 glob_helpstr = ( |
| 863 run_p.set_defaults(func=lambda opts: run_run( | 868 'glob filter for the tests to run; ' |
| 864 opts.filter, jobs=opts.jobs, train=opts.train, json_file=opts.json)) | 869 'can be specified multiple times; ' |
| 870 'the globs have the form of ' |
| 871 '`<recipe_name_glob>[.<test_name_glob>]`. If `.<test_name_glob>` ' |
| 872 'is omitted, it is implied to be `*.*`, i.e. any recipe with this ' |
| 873 'prefix and all tests.') |
| 874 |
| 875 helpstr = 'Run the tests.' |
| 876 run_p = subp.add_parser('run', help=helpstr, description=helpstr) |
| 877 run_p.set_defaults(subfunc=lambda opts: run_run( |
| 878 opts.filter, jobs=opts.jobs, train=opts.train, json_file=opts.json)) |
| 865 run_p.add_argument( | 879 run_p.add_argument( |
| 866 '--jobs', metavar='N', type=int, | 880 '--jobs', metavar='N', type=int, |
| 867 default=multiprocessing.cpu_count(), | 881 default=multiprocessing.cpu_count(), |
| 868 help='run N jobs in parallel (default %(default)s)') | 882 help='run N jobs in parallel (default %(default)s)') |
| 869 run_p.add_argument( | 883 run_p.add_argument( |
| 870 '--json', metavar='FILE', type=argparse.FileType('w'), | 884 '--json', metavar='FILE', type=argparse.FileType('w'), |
| 871 help='path to JSON output file') | 885 help='path to JSON output file') |
| 872 run_p.add_argument( | 886 run_p.add_argument( |
| 873 '--filter', action='append', type=normalize_filter, | 887 '--filter', action='append', type=normalize_filter, |
| 874 help='glob filter for the tests to run; ' | 888 help=glob_helpstr) |
| 875 'can be specified multiple times; ' | |
| 876 'the globs have the form of ' | |
| 877 '`<recipe_name_glob>[.<test_name_glob>]`. If `.<test_name_glob>` ' | |
| 878 'is omitted, it is implied to be `*.*`, i.e. any recipe with this ' | |
| 879 'prefix and all tests.)') | |
| 880 # TODO(phajdan.jr): remove --train the switch in favor of train subcommand. | 889 # TODO(phajdan.jr): remove --train the switch in favor of train subcommand. |
| 881 run_p.add_argument( | 890 run_p.add_argument( |
| 882 '--train', action='store_true', | 891 '--train', action='store_true', |
| 883 help='re-generate recipe expectations (DEPRECATED)') | 892 help='re-generate recipe expectations (DEPRECATED)') |
| 884 | 893 |
| 885 train_p = subp.add_parser('train', description='Re-train recipe expectations') | 894 helpstr = 'Re-train recipe expectations.' |
| 886 train_p.set_defaults(func=lambda opts: run_run( | 895 train_p = subp.add_parser('train', help=helpstr, description=helpstr) |
| 887 opts.filter, jobs=opts.jobs, json_file=opts.json, train=True)) | 896 train_p.set_defaults(subfunc=lambda opts: run_run( |
| 897 opts.filter, jobs=opts.jobs, json_file=opts.json, train=True)) |
| 888 train_p.add_argument( | 898 train_p.add_argument( |
| 889 '--jobs', metavar='N', type=int, | 899 '--jobs', metavar='N', type=int, |
| 890 default=multiprocessing.cpu_count(), | 900 default=multiprocessing.cpu_count(), |
| 891 help='run N jobs in parallel (default %(default)s)') | 901 help='run N jobs in parallel (default %(default)s)') |
| 892 train_p.add_argument( | 902 train_p.add_argument( |
| 893 '--json', metavar='FILE', type=argparse.FileType('w'), | 903 '--json', metavar='FILE', type=argparse.FileType('w'), |
| 894 help='path to JSON output file') | 904 help='path to JSON output file') |
| 895 train_p.add_argument( | 905 train_p.add_argument( |
| 896 '--filter', action='append', type=normalize_filter, | 906 '--filter', action='append', type=normalize_filter, |
| 897 help='glob filter for the tests to run; ' | 907 help=glob_helpstr) |
| 898 'can be specified multiple times; ' | |
| 899 'the globs have the form of ' | |
| 900 '`<recipe_name_glob>[.<test_name_glob>]`. If `.<test_name_glob>` ' | |
| 901 'is omitted, it is implied to be `*.*`, i.e. any recipe with this ' | |
| 902 'prefix and all tests.)') | |
| 903 | 908 |
| 909 helpstr = 'Run the tests under debugger (pdb).' |
| 904 debug_p = subp.add_parser( | 910 debug_p = subp.add_parser( |
| 905 'debug', description='Run the tests under debugger (pdb)') | 911 'debug', help=helpstr, description=helpstr) |
| 906 debug_p.set_defaults(func=lambda opts: run_run( | 912 debug_p.set_defaults(subfunc=lambda opts: run_run( |
| 907 opts.filter, debug=True)) | 913 opts.filter, debug=True)) |
| 908 debug_p.add_argument( | 914 debug_p.add_argument( |
| 909 '--filter', action='append', type=normalize_filter, | 915 '--filter', action='append', type=normalize_filter, |
| 910 help='glob filter for the tests to run; ' | 916 help=glob_helpstr) |
| 911 'can be specified multiple times; ' | |
| 912 'the globs have the form of ' | |
| 913 '`<recipe_name_glob>[.<test_name_glob>]`. If `.<test_name_glob>` ' | |
| 914 'is omitted, it is implied to be `*.*`, i.e. any recipe with this ' | |
| 915 'prefix and all tests.)') | |
| 916 | |
| 917 return parser.parse_args(args) | |
| 918 | |
| 919 | |
| 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 | 917 |
| 928 def postprocess_func(_parser, args): | 918 def postprocess_func(_parser, args): |
| 929 # Auto-enable bootstrap for test command invocations (necessary to get | 919 # Auto-enable bootstrap for test command invocations (necessary to get |
| 930 # recent enough version of coverage package), unless explicitly disabled. | 920 # recent enough version of coverage package), unless explicitly disabled. |
| 931 if args.use_bootstrap is None: | 921 if args.use_bootstrap is None: |
| 932 args.use_bootstrap = True | 922 args.use_bootstrap = True |
| 933 | 923 |
| 934 test_p.set_defaults(command='test', func=main, | 924 test_p.set_defaults( |
| 935 postprocess_func=postprocess_func) | 925 command='test', func=main, |
| 926 postprocess_func=postprocess_func, |
| 927 ) |
| 936 | 928 |
| 937 | 929 |
| 938 def main(package_deps, args): | 930 def main(package_deps, args): |
| 939 """Runs simulation tests on a given repo of recipes. | 931 """Runs simulation tests on a given repo of recipes. |
| 940 | 932 |
| 941 Args: | 933 Args: |
| 942 universe_view: an UniverseView object to operate on | 934 package_deps (PackageDeps) |
| 943 raw_args: command line arguments to the 'test' command | 935 args: the parsed args (see add_subparser). |
| 944 engine_flags: recipe engine command-line flags | |
| 945 Returns: | 936 Returns: |
| 946 Exit code | 937 Exit code |
| 947 """ | 938 """ |
| 948 universe = loader.RecipeUniverse(package_deps, args.package) | 939 universe = loader.RecipeUniverse(package_deps, args.package) |
| 949 universe_view = loader.UniverseView(universe, package_deps.root_package) | 940 universe_view = loader.UniverseView(universe, package_deps.root_package) |
| 950 raw_args=args.args | |
| 951 engine_flags=args.operational_args.engine_flags | 941 engine_flags=args.operational_args.engine_flags |
| 952 | 942 |
| 953 # Prevent flakiness caused by stale pyc files. | 943 # Prevent flakiness caused by stale pyc files. |
| 954 package.cleanup_pyc(package_deps.root_package.recipes_dir) | 944 package.cleanup_pyc(package_deps.root_package.recipes_dir) |
| 955 | 945 |
| 956 global _UNIVERSE_VIEW | 946 global _UNIVERSE_VIEW |
| 957 _UNIVERSE_VIEW = universe_view | 947 _UNIVERSE_VIEW = universe_view |
| 958 global _ENGINE_FLAGS | 948 global _ENGINE_FLAGS |
| 959 _ENGINE_FLAGS = engine_flags | 949 _ENGINE_FLAGS = engine_flags |
| 960 | 950 |
| 961 args = parse_args(raw_args) | 951 return args.subfunc(args) |
| 962 return args.func(args) | |
| OLD | NEW |