#!/usr/bin/env python -u import os import sys import getopt import argparse import re import logging import json logger = logging.getLogger() handler = logging.StreamHandler(sys.stdout) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) logger.setLevel(logging.INFO) logger.addHandler(handler) def iter_dir_recusive(path, callback): if os.path.isfile(path) and os.path.splitext(path)[1] == '.js': logger.info("Processing {} ...".format(path)) callback(path) else: for root, dirs, files in os.walk(path): for name in files: iter_dir_recusive(os.path.join(root, name), callback) logger.info("Done.") class JSCTestModifier(object): variables = [ "$hostOs", "$model", "$architecture" ] def __init__(self, test_pathes, conditions={}, match="all"): self._conditions = conditions self._test_pathes = test_pathes self._match = match self._skip_line_postfix = "# added by mark-jsc-stress-test.py" def skip(self): for path in self._test_pathes: logger.info("Mark {} skip".format(path)) iter_dir_recusive(path, lambda file_path: self._skip_test_file(file_path)) def enable(self): for path in self._test_pathes: logger.info("Mark {} enable".format(path)) iter_dir_recusive(path, lambda file_path: self._enable_test_file(file_path)) def _generate_condition_op(self, value): op = "==" if ("!" == value[0]): op = "!=" value = value[1:] return op, value # Condition grammer is like !A or B or C and D # Translate it to ruby: $hostOs != A or $hostOs == B or $hostOs == C and $hostOs == D def _parse_condition(self, variable, condition): res = [] values = [] for word in re.split(r'\s+', condition): if word == "or" or word == "and": value = " ".join(values).strip() values = [] res.append('{} {} "{}"'.format(variable, *self._generate_condition_op(value))) res.append(word) else: values.append(word) if values: value = " ".join(values).strip() res.append('{} {} "{}"'.format(variable, *self._generate_condition_op(value))) return " ".join(res) def _generate_skip_annotation_line(self): skip_line_prefix = "//@ skip if" skip_conditions = [] skip_line = "{} {} {}" supported_variables = [variable for variable in JSCTestModifier.variables if variable in self._conditions] condition_template = "{}" if len(supported_variables) == 1 else "({})" for variable in supported_variables: skip_conditions.append(condition_template.format(self._parse_condition(variable, self._conditions[variable]))) if not skip_conditions: # No conditions, always skip skip_conditions = ["true"] if self._match == "any": skip_line = skip_line.format(skip_line_prefix, " or ".join(skip_conditions), self._skip_line_postfix) elif self._match == "all": skip_line = skip_line.format(skip_line_prefix, " and ".join(skip_conditions), self._skip_line_postfix) return skip_line # This can only remove the skip annotation generated by this script def _enable_test_file(self, test_file): with open(test_file, 'r+') as f: lines = f.readlines() f.seek(0) for line in lines: if not self._skip_line_postfix in line: f.write(line) f.truncate() def _skip_test_file(self, test_file): # remove the exisiting skip line, so that we can apply the new one self._enable_test_file(test_file) skip_line = self._generate_skip_annotation_line() with open(test_file, 'r+') as f: original_content = f.read() f.seek(0) f.write("{}\n{}".format(skip_line, original_content)) opensource_root = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../") jsc_test_search_path = [ os.path.join(opensource_root, "JSTests"), os.path.join(opensource_root, "LayoutTests") ] def main(): parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(dest="action") file_list_help = "Files/directories list; use ',' to separate each item. Example: a.js, b.js, c.js Use '-' if you are using --jsc-json-output argument" parser_enable = subparsers.add_parser("enable", help="Enable the tests which are marked as skipped by this script") parser_enable.add_argument("files", help=file_list_help) parser_enable.add_argument("--jsc-json-output", help="Pass the json output of run-javascriptcore-tests to unskip all failed tests") parser_skip = subparsers.add_parser("skip", help="Insert skip condition to given files/directories") parser_skip.add_argument("files", help=file_list_help) parser_skip.add_argument("--jsc-json-output", help="Pass the json output of run-javascriptcore-tests to skip all failed tests") parser_skip.add_argument("--platform", "--host-os", help="Skip if host os matches given value, Examples: 'windows or linux' '!windows and !linux'") parser_skip.add_argument("--model", help="Skip if hardware model matches given value, Examples: 'Apple Watch Series 3 or Apple Watch Series 4' '!Apple Watch Series 3 and !Apple Watch Series 4'") parser_skip.add_argument("--architecture", help="Skip if architecture matches given value, Examples: 'arm or x86' '!arm and !x86'") parser_skip.add_argument("--match", default="all", help="Match all or any above conditions") args = vars(parser.parse_args()) conditions = {} files = [] if not args["files"] and not args["--log-file"]: logger.error("Please speicify a list of file, or use --log-file to give a JSC test log") return 1 if args["files"] and not args["files"] == "-": files += args["files"].split(",") if args["jsc_json_output"]: with open(args["jsc_json_output"]) as f: jsc_json_output = json.load(f) failures = jsc_json_output["stressTestFailures"] failure_test_files_set = set() for failure_test in failures: path_parts = failure_test.split(os.path.sep) if 'yaml' in path_parts[0]: failure_test_path = os.path.join(*path_parts[1:]) else: failure_test_path = failure_test failure_test_file = os.path.splitext(failure_test_path)[0] for search_path in jsc_test_search_path: whole_path = os.path.join(search_path, failure_test_file) if failure_test_file not in failure_test_files_set and os.path.isfile(whole_path): files.append(whole_path) failure_test_files_set.add(failure_test_file) if "host_os" in args and args["host_os"]: conditions["$hostOs"] = args["host_os"] if "platform" in args and args["platform"]: conditions["$hostOs"] = args["platform"] if "architecture" in args and args["architecture"]: conditions["$architecture"] = args["architecture"] if "model" in args and args["model"]: conditions["$model"] = args["model"] modifer = JSCTestModifier(files, conditions, args["match"] if "match" in args else None) getattr(modifer, args["action"])() return 0 if __name__ == '__main__': sys.exit(main())