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 |