From 81659f587effab38a5cbd4212f74805ed67ed986 Mon Sep 17 00:00:00 2001 From: Yonggang Luo Date: Tue, 9 Feb 2021 08:32:39 -0800 Subject: [PATCH] Revise tools scripts to be python3 compatible on win32 We introduce setup_stdio function to setup stdout/stderr properly. For python <-> python pipe, we always use 'utf8'/'ignore' encoding for not lost characters. For tty <-> python, we using native encoding with xmlcharrefreplace to encode, to preserve maximal information. For python <-> native program, we use naive encoding with 'ignore' to not cause error update_exclude_list with binary mode so that on win32 would not generate \r\n run-test-suite.py: Handling skiplist properly on win32 Fixes #4854 Fixes test262-harness.py complain cannot use a string pattern on a bytes-like object with running test262 with python3 For reading/writing to file, we use 'utf8' /'ignore' encoding for not lost characters. For decoding from process stdout, using native encoding with decoding error ignored for not lost data. Execute commands also ignore errors Fixes #4853 Fixes running test262-esnext failed with installed python3.9 on win32 with space in path Fixes #4852 ``` support both / \ in --test262-test-list arg On win32. python tools/run-tests.py --test262-es2015=update --test262-test-list=built-ins/decodeURI/ python tools/run-tests.py --test262-es2015=update --test262-test-list=built-ins\decodeURI\ should be both valid, currently only --test262-test-list=built-ins\decodeURI\ are valid. ``` ``` Support snapshot-tests-skiplist.txt on win32 by use os.path.normpath ``` Guard run-tests.py with timer. All run-tests.py are finished in 30 minutes in normal situation. May increase the timeout future. wait JERRY_CHECK_TIMEOUT ``` Move Windows CI to github actions Convert run-debugger-test.sh to run-debugger-test.py After this change, run-debugger-test.py could running on Win32 and OSX Define TERM colors for win32 properly ``` ``` flush stderr.write stdout.write On CI, the stderr are redirect to stdout, and if we don't flush stderr and stdout, The output from stderr/stdout would out of sync. ``` JerryScript-DCO-1.0-Signed-off-by: Yonggang Luo luoyonggang@gmail.com --- .github/workflows/gh-actions.yml | 32 ++++++++ appveyor.yml | 27 ------- jerry-debugger/jerry_client.py | 2 + tools/run-tests.py | 44 ++++++----- tools/runners/run-debugger-test.py | 99 +++++++++++++++++++++++++ tools/runners/run-debugger-test.sh | 66 ----------------- tools/runners/run-test-suite-test262.py | 18 ++--- tools/runners/run-test-suite.py | 5 +- tools/runners/run-unittests.py | 1 + tools/runners/test262-harness.py | 22 ++++-- tools/runners/util.py | 29 +++++++- tools/settings.py | 2 +- 12 files changed, 210 insertions(+), 137 deletions(-) delete mode 100644 appveyor.yml create mode 100644 tools/runners/run-debugger-test.py delete mode 100755 tools/runners/run-debugger-test.sh diff --git a/.github/workflows/gh-actions.yml b/.github/workflows/gh-actions.yml index 1aa3b300e3..43bb9c7c11 100644 --- a/.github/workflows/gh-actions.yml +++ b/.github/workflows/gh-actions.yml @@ -57,6 +57,36 @@ jobs: - run: $RUNNER -q --jerry-tests --buildoptions=--compile-flag=-m32,--cpointer-32bit=on - run: $RUNNER -q --jerry-tests --buildoptions=--compile-flag=-m32,--cpointer-32bit=on --build-debug + Win_x86-64_Conformance_Tests_ESNext: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - run: python $env:RUNNER --test262 update + + Win_x86-64_Conformance_Tests_ESNext_Debug: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - run: python $env:RUNNER --test262 update --build-debug + + Win_x86-64_Tests: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - run: python $env:RUNNER -q --jerry-tests + - run: python $env:RUNNER -q --unittests + - run: python $env:RUNNER -q --buildoption-test + - run: python $env:RUNNER -q --jerry-debugger + + Win_x86-64_Tests_Debug: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - run: python $env:RUNNER -q --jerry-tests --build-debug + - run: python $env:RUNNER -q --unittests --build-debug + - run: python $env:RUNNER -q --buildoption-test --build-debug + - run: python $env:RUNNER -q --jerry-debugger --build-debug + OSX_x86-64_Build_Correctness_Unit_Tests: runs-on: macos-13 steps: @@ -66,6 +96,7 @@ jobs: python-version: '>=3.6' - run: $RUNNER -q --jerry-tests - run: $RUNNER -q --unittests + - run: $RUNNER -q --jerry-debugger OSX_x86-64_Build_Correctness_Unit_Tests_Debug: runs-on: macos-13 @@ -76,6 +107,7 @@ jobs: python-version: '>=3.6' - run: $RUNNER -q --jerry-tests --build-debug - run: $RUNNER -q --unittests --build-debug + - run: $RUNNER -q --jerry-debugger --build-debug Linux_x86-64_Build_Option_Tests: runs-on: ubuntu-latest diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index cc215ac00d..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,27 +0,0 @@ -version: "{build}" -branches: - except: - - coverity_scan - - gh_pages -skip_tags: true - -# Build matrix setup. -image: - - Visual Studio 2017 -configuration: - - Debug - - Release -platform: - - x64 - - Win32 - -# Steps of a job. -init: - - cmake -version -before_build: - - if "%PLATFORM%"=="Win32" cmake -G"Visual Studio 15 2017" -Bbuild -H. -DJERRY_DEBUGGER=ON - - if "%PLATFORM%"=="x64" cmake -G"Visual Studio 15 2017 Win64" -Bbuild -H. -DJERRY_DEBUGGER=ON -build: - project: build\Jerry.sln - parallel: false # FIXME: This should not be needed but right now it is: msbuild generates all amalgamated files twice, at the same time in parallel builds, leading to I/O errors. - verbosity: minimal diff --git a/jerry-debugger/jerry_client.py b/jerry-debugger/jerry_client.py index ee735e5367..b531c6c704 100755 --- a/jerry-debugger/jerry_client.py +++ b/jerry-debugger/jerry_client.py @@ -326,6 +326,8 @@ def main(): break if res_type == result.PROMPT: prompt.cmdloop() + sys.stdout.flush() + sys.stderr.flush() elif res_type == result.TEXT: write(result.get_text()) continue diff --git a/tools/run-tests.py b/tools/run-tests.py index 7111c53d19..6775036698 100755 --- a/tools/run-tests.py +++ b/tools/run-tests.py @@ -20,6 +20,7 @@ import os import platform import subprocess +import threading import sys import settings @@ -31,6 +32,9 @@ OUTPUT_DIR = os.path.join(settings.PROJECT_DIR, 'build', 'tests') +# All run_check proc must finished in 15 minutes, may increase in future +JERRY_CHECK_TIMEOUT = 15 * 60 + Options = collections.namedtuple('Options', ['name', 'build_args', 'test_args', 'skip']) Options.__new__.__defaults__ = ([], [], False) @@ -196,25 +200,22 @@ def get_arguments(): BINARY_CACHE = {} -TERM_NORMAL = '\033[0m' -TERM_YELLOW = '\033[1;33m' -TERM_BLUE = '\033[1;34m' -TERM_RED = '\033[1;31m' - def report_command(cmd_type, cmd, env=None): - sys.stderr.write(f'{TERM_BLUE}{cmd_type}{TERM_NORMAL}\n') + sys.stderr.write(f'{util.TERM_BLUE}{cmd_type}{util.TERM_NORMAL}\n') if env is not None: - sys.stderr.write(''.join(f'{TERM_BLUE}{var}={val!r} \\{TERM_NORMAL}\n' + sys.stderr.write(''.join(f'{util.TERM_BLUE}{var}={val!r} \\{util.TERM_NORMAL}\n' for var, val in sorted(env.items()))) - sys.stderr.write(f"{TERM_BLUE}" + - f" \\{TERM_NORMAL}\n\t{TERM_BLUE}".join(cmd) + - f"{TERM_NORMAL}\n") + sys.stderr.write(f"{util.TERM_BLUE}" + + f" \\{util.TERM_NORMAL}\n\t{util.TERM_BLUE}".join(cmd) + + f"{util.TERM_NORMAL}\n") + sys.stderr.flush() def report_skip(job): - sys.stderr.write(f'{TERM_YELLOW}Skipping: {job.name}') + sys.stderr.write(f'{util.TERM_YELLOW}Skipping: {job.name}') if job.skip: sys.stderr.write(f' ({job.skip})') - sys.stderr.write(f'{TERM_NORMAL}\n') + sys.stderr.write(f'{util.TERM_NORMAL}\n') + sys.stderr.flush() def create_binary(job, options): build_args = job.build_args[:] @@ -245,13 +246,15 @@ def create_binary(job, options): if binary_key in BINARY_CACHE: ret, build_dir_path = BINARY_CACHE[binary_key] sys.stderr.write(f'(skipping: already built at {build_dir_path} with returncode {ret})\n') + sys.stderr.flush() return ret, build_dir_path try: subprocess.check_output(build_cmd) ret = 0 except subprocess.CalledProcessError as err: - print(err.output.decode("utf8")) + # For python <-> native program, we use default encoding with error='ignore' to not lost data + print(err.output.decode(errors="ignore")) ret = err.returncode BINARY_CACHE[binary_key] = (ret, build_dir_path) @@ -282,6 +285,7 @@ def iterate_test_runner_jobs(jobs, options): if build_dir_path in tested_paths: sys.stderr.write(f'(skipping: already tested with {build_dir_path})\n') + sys.stderr.flush() continue tested_paths.add(build_dir_path) @@ -290,6 +294,7 @@ def iterate_test_runner_jobs(jobs, options): if bin_hash in tested_hashes: sys.stderr.write(f'(skipping: already tested with equivalent {tested_hashes[bin_hash]})\n') + sys.stderr.flush() continue tested_hashes[bin_hash] = build_dir_path @@ -307,7 +312,7 @@ def run_check(runnable, env=None): env = full_env with subprocess.Popen(runnable, env=env) as proc: - proc.wait() + proc.wait(timeout=JERRY_CHECK_TIMEOUT) return proc.returncode def run_jerry_debugger_tests(options): @@ -315,7 +320,7 @@ def run_jerry_debugger_tests(options): for job in DEBUGGER_TEST_OPTIONS: ret_build, build_dir_path = create_binary(job, options) if ret_build: - print(f"\n{TERM_RED}Build failed{TERM_NORMAL}\n") + print(f"\n{util.TERM_RED}Build failed{util.TERM_NORMAL}\n") break for channel in ["websocket", "rawpacket"]: @@ -323,7 +328,7 @@ def run_jerry_debugger_tests(options): if test_file.endswith(".cmd"): test_case, _ = os.path.splitext(test_file) test_case_path = os.path.join(settings.DEBUGGER_TESTS_DIR, test_case) - test_cmd = [ + test_cmd = util.get_python_cmd_prefix() + [ settings.DEBUGGER_TEST_RUNNER_SCRIPT, get_binary_path(build_dir_path), channel, @@ -378,7 +383,7 @@ def run_test262_test_suite(options): for job in jobs: ret_build, build_dir_path = create_binary(job, options) if ret_build: - print(f"\n{TERM_RED}Build failed{TERM_NORMAL}\n") + print(f"\n{util.TERM_RED}Build failed{util.TERM_NORMAL}\n") break test_cmd = util.get_python_cmd_prefix() + [ @@ -408,7 +413,7 @@ def run_unittests(options): continue ret_build, build_dir_path = create_binary(job, options) if ret_build: - print(f"\n{TERM_RED}Build failed{TERM_NORMAL}\n") + print(f"\n{util.TERM_RED}Build failed{util.TERM_NORMAL}\n") break if sys.platform == 'win32': @@ -437,7 +442,7 @@ def run_buildoption_test(options): ret, _ = create_binary(job, options) if ret: - print(f"\n{TERM_RED}Build failed{TERM_NORMAL}\n") + print(f"\n{util.TERM_RED}Build failed{util.TERM_NORMAL}\n") break return ret @@ -445,6 +450,7 @@ def run_buildoption_test(options): Check = collections.namedtuple('Check', ['enabled', 'runner', 'arg']) def main(options): + util.setup_stdio() checks = [ Check(options.check_signed_off, run_check, [settings.SIGNED_OFF_SCRIPT] + {'tolerant': ['--tolerant'], 'gh-actions': ['--gh-actions']}.get(options.check_signed_off, [])), diff --git a/tools/runners/run-debugger-test.py b/tools/runners/run-debugger-test.py new file mode 100644 index 0000000000..3b96d98f74 --- /dev/null +++ b/tools/runners/run-debugger-test.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python + +# Copyright JS Foundation and other contributors, http://js.foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import subprocess +import sys +import time + +import util +TempFile = __import__("test262-harness").TempFile # pylint: disable=invalid-name + +class DebuggerArgs: + def __init__(self): + self.jerry = sys.argv[1] + self.channel = sys.argv[2] + self.debugger_client = sys.argv[3] + self.test_case = sys.argv[4] + + +def check_output(command_args, stdin=None, encoding=None): + try: + out = subprocess.check_output(command_args, stdin=stdin, shell=False, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as check_error: + out = check_error.output + return out.decode(encoding or 'utf-8', 'ignore') + + +def execute_debug_client(out_tmp, cmd_file_name, debug_client_args): + print(f'input debug cmd: {cmd_file_name}') + with open(cmd_file_name, 'r') as cmd_file: + out = check_output(debug_client_args, cmd_file) + out_tmp.write(out) + + +def main(args): + util.setup_stdio() + jerry_debug_server_cmd = [args.jerry] + client_args = [] + if 'client_source' in args.test_case: + jerry_debug_server_cmd += ['--start-debug-server', '--debug-channel', + args.channel, '--debugger-wait-source'] + client_args += ['--client-source'] + if 'client_source_multiple' in args.test_case: + client_args += [args.test_case + '_2.js', args.test_case + '_1.js'] + else: + client_args += [args.test_case + '.js'] + else: + jerry_debug_server_cmd += [args.test_case + '.js', '--start-debug-server', '--debug-channel', args.channel] + print(f'run debug server: {jerry_debug_server_cmd}') + proc = subprocess.Popen(jerry_debug_server_cmd, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + time.sleep(1) + + out_tmp = TempFile(prefix=os.path.basename(args.test_case), suffix='out') + git_failed = False + try: + debug_client_args = util.get_python_cmd_prefix() + debug_client_args += [args.debugger_client, '--channel', args.channel, '--non-interactive'] + debug_client_args += client_args + print(f"run debug client: {' '.join(debug_client_args)}") + execute_debug_client(out_tmp, args.test_case + '.cmd', debug_client_args) + if 'restart' in args.test_case: + continue_case = args.test_case.replace('restart', 'continue') + execute_debug_client(out_tmp, continue_case + '.cmd', debug_client_args) + out_tmp.close() + git_diff_cmd = ['git', '--no-pager', 'diff', '--ignore-space-at-eol', + '--no-index', args.test_case + '.expected', out_tmp.name] + git_out = check_output(git_diff_cmd) + if '@@' in git_out: + git_failed = True + finally: + proc.wait() + print(f'jerry out:\n{proc.stdout.read().decode('utf-8')}\nEOF') + print(f'git diff cmd: {' '.join(git_diff_cmd)}') + if git_failed: + print(f'git diff result:\n{git_out}\nEOF') + print(f'{util.TERM_RED}FAIL: {args.test_case}{util.TERM_NORMAL}') + sys.exit(1) + else: + out_tmp.dispose() + print(f'{util.TERM_GREEN}PASS: {args.test_case}{util.TERM_NORMAL}') + sys.exit(0) + + +if __name__ == "__main__": + sys.exit(main(DebuggerArgs())) diff --git a/tools/runners/run-debugger-test.sh b/tools/runners/run-debugger-test.sh deleted file mode 100755 index a3c754c768..0000000000 --- a/tools/runners/run-debugger-test.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -# Copyright JS Foundation and other contributors, http://js.foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -JERRY=$1 -CHANNEL=$2 -DEBUGGER_CLIENT=$3 -TEST_CASE=$4 -CLIENT_ARGS="" - -TERM_NORMAL='\033[0m' -TERM_RED='\033[1;31m' -TERM_GREEN='\033[1;32m' - -if [[ $TEST_CASE == *"client_source"* ]]; then - START_DEBUG_SERVER="${JERRY} --start-debug-server --debug-channel ${CHANNEL} --debugger-wait-source &" - if [[ $TEST_CASE == *"client_source_multiple"* ]]; then - CLIENT_ARGS="--client-source ${TEST_CASE}_2.js ${TEST_CASE}_1.js" - else - CLIENT_ARGS="--client-source ${TEST_CASE}.js" - fi -else - START_DEBUG_SERVER="${JERRY} ${TEST_CASE}.js --start-debug-server --debug-channel ${CHANNEL} &" -fi - -echo "$START_DEBUG_SERVER" -eval "$START_DEBUG_SERVER" -JERRY_PID=$! -sleep 1s - -RESULT_TEMP=`mktemp ${TEST_CASE}.out.XXXXXXXXXX` - -(cat "${TEST_CASE}.cmd" | ${DEBUGGER_CLIENT} --channel ${CHANNEL} --non-interactive ${CLIENT_ARGS}) >${RESULT_TEMP} 2>&1 - -if [[ $TEST_CASE == *"restart"* ]]; then - CONTINUE_CASE=$(sed "s/restart/continue/g" <<< "$TEST_CASE") - (cat "${CONTINUE_CASE}.cmd" | ${DEBUGGER_CLIENT} --channel ${CHANNEL} --non-interactive ${CLIENT_ARGS}) >>${RESULT_TEMP} 2>&1 -fi - -diff -U0 ${TEST_CASE}.expected ${RESULT_TEMP} -STATUS_CODE=$? - -rm -f ${RESULT_TEMP} - -wait $JERRY_PID -JERRY_EXIT_CODE=$? -if [ ${STATUS_CODE} -ne 0 ] || [ ${JERRY_EXIT_CODE} -gt 1 ] -then - echo -e "${TERM_RED}FAIL: ${TEST_CASE}${TERM_NORMAL}\n" -else - echo -e "${TERM_GREEN}PASS: ${TEST_CASE}${TERM_NORMAL}\n" -fi - -exit ${STATUS_CODE} diff --git a/tools/runners/run-test-suite-test262.py b/tools/runners/run-test-suite-test262.py index a03c678627..6d14c52ba9 100755 --- a/tools/runners/run-test-suite-test262.py +++ b/tools/runners/run-test-suite-test262.py @@ -22,12 +22,6 @@ import util -def get_platform_cmd_prefix(): - if sys.platform == 'win32': - return ['cmd', '/S', '/C'] - return ['python3'] - - def get_arguments(): execution_runtime = os.environ.get('RUNTIME', '') parser = argparse.ArgumentParser() @@ -91,14 +85,14 @@ def update_exclude_list(args): # Tests pass in strict-mode but fail in non-strict-mode (or vice versa) should be considered as failures passing_tests = passing_tests - failing_tests - with open(args.excludelist_path, 'r+', encoding='utf8') as exclude_file: + with open(args.excludelist_path, 'rb+') as exclude_file: lines = exclude_file.readlines() exclude_file.seek(0) exclude_file.truncate() # Skip the last line "" to be able to insert new failing tests. for line in lines[:-1]: - match = re.match(r" ", line) + match = re.match(r" ", line.decode('utf-8', 'ignore')) if match: test = match.group(1) if test in failing_tests: @@ -114,11 +108,12 @@ def update_exclude_list(args): if failing_tests: print("New failing tests added to the excludelist") for test in sorted(failing_tests): - exclude_file.write(' \n') + line_added = ' \n' + exclude_file.write(line_added.encode('utf-8')) print(" " + test) print("") - exclude_file.write('\n') + exclude_file.write('\n'.encode('utf-8')) if new_passing_tests: print("New passing tests removed from the excludelist") @@ -135,6 +130,7 @@ def update_exclude_list(args): def main(args): + util.setup_stdio() return_code = prepare_test262_test_suite(args) if return_code: return return_code @@ -154,7 +150,7 @@ def main(args): test262_harness_path = os.path.join(args.test262_harness_dir, 'test262-harness.py') - test262_command = get_platform_cmd_prefix() + \ + test262_command = util.get_python_cmd_prefix() + \ [test262_harness_path, '--command', command, '--tests', args.test_dir, diff --git a/tools/runners/run-test-suite.py b/tools/runners/run-test-suite.py index afd4477f77..60a0f4d504 100755 --- a/tools/runners/run-test-suite.py +++ b/tools/runners/run-test-suite.py @@ -55,7 +55,7 @@ def get_tests(test_dir, test_list, skip_list): for root, _, files in os.walk(test_dir): for test_file in files: if test_file.endswith('.js') or test_file.endswith('.mjs'): - tests.extend([os.path.join(root, test_file)]) + tests.extend([os.path.normpath(os.path.join(root, test_file))]) if test_list: dirname = os.path.dirname(test_list) @@ -64,6 +64,7 @@ def get_tests(test_dir, test_list, skip_list): tests.append(os.path.normpath(os.path.join(dirname, test.rstrip()))) tests.sort() + skip_list = [os.path.normpath(skip) for skip in skip_list] def filter_tests(test): for skipped in skip_list: @@ -78,6 +79,7 @@ def execute_test_command(test_cmd): kwargs = {} if sys.version_info.major >= 3: kwargs['encoding'] = 'unicode_escape' + kwargs['errors'] = 'ignore' with subprocess.Popen(test_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, **kwargs) as process: stdout, _ = process.communicate() @@ -85,6 +87,7 @@ def execute_test_command(test_cmd): def main(args): + util.setup_stdio() tests = get_tests(args.test_dir, args.test_list, args.skip_list) total = len(tests) if total == 0: diff --git a/tools/runners/run-unittests.py b/tools/runners/run-unittests.py index d54f643692..ef07d41f14 100755 --- a/tools/runners/run-unittests.py +++ b/tools/runners/run-unittests.py @@ -49,6 +49,7 @@ def get_unittests(path): def main(args): + util.setup_stdio() unittests = get_unittests(args.path) total = len(unittests) if total == 0: diff --git a/tools/runners/test262-harness.py b/tools/runners/test262-harness.py index 404e20fc20..b7d1b56c8f 100755 --- a/tools/runners/test262-harness.py +++ b/tools/runners/test262-harness.py @@ -40,6 +40,7 @@ # This code is governed by the BSD license found in the LICENSE file. +import codecs import logging import argparse import os @@ -55,6 +56,8 @@ import signal import multiprocessing +import util + ####################################################################### # based on _monkeyYaml.py ####################################################################### @@ -400,11 +403,13 @@ def open_file(self): text=self.text) def write(self, string): - os.write(self.file_desc, string.encode('utf8')) + os.write(self.file_desc, string.encode('utf8', 'ignore')) def read(self): - with open(self.name, "r", newline='', encoding='utf8') as file_desc: - return file_desc.read() + file_desc = open(self.name, 'rb') + result = file_desc.read() + file_desc.close() + return result.decode('utf8', 'ignore') def close(self): if not self.is_closed: @@ -490,7 +495,7 @@ def __init__(self, suite, name, full_path, strict_mode, command_template, module self.name = name self.full_path = full_path self.strict_mode = strict_mode - with open(self.full_path, "r", newline='', encoding='utf8') as file_desc: + with open(self.full_path, "r", newline='', encoding='utf8', errors='ignore') as file_desc: self.contents = file_desc.read() test_record = parse_test_record(self.contents, name) self.test = test_record["test"] @@ -742,7 +747,7 @@ def should_run(rel_path, tests): if not tests: return True for test in tests: - if test in rel_path: + if os.path.normpath(test) in os.path.normpath(rel_path): return True return False @@ -750,8 +755,8 @@ def get_include(self, name): if not name in self.include_cache: static = path.join(self.lib_root, name) if path.exists(static): - with open(static, encoding='utf8') as file_desc: - contents = file_desc.read() + with open(static, 'rb') as file_desc: + contents = file_desc.read().decode('utf8', 'ignore') contents = re.sub(r'\r\n', '\n', contents) self.include_cache[name] = contents + "\n" else: @@ -839,7 +844,7 @@ def run(self, command_template, tests, print_summary, full_summary, logname, job report_error("No tests to run") progress = ProgressIndicator(len(cases)) if logname: - self.logf = open(logname, "w", encoding='utf8') # pylint: disable=consider-using-with + self.logf = codecs.open(logname, "w", encoding='utf8', errors='ignore') # pylint: disable=consider-using-with if job_count == 1: for case in cases: @@ -901,6 +906,7 @@ def list_includes(self, tests): def main(): + util.setup_stdio() code = 0 parser = build_options() options = parser.parse_args() diff --git a/tools/runners/util.py b/tools/runners/util.py index 90e920491b..f93e5b7c28 100755 --- a/tools/runners/util.py +++ b/tools/runners/util.py @@ -12,13 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. +import codecs import signal import subprocess import sys -TERM_NORMAL = '\033[0m' -TERM_RED = '\033[1;31m' -TERM_GREEN = '\033[1;32m' +if sys.platform == 'win32': + TERM_NORMAL = '' + TERM_RED = '' + TERM_GREEN = '' + TERM_YELLOW = '' + TERM_BLUE = '' +else: + TERM_NORMAL = '\033[0m' + TERM_RED = '\033[1;31m' + TERM_GREEN = '\033[1;32m' + TERM_YELLOW = '\033[1;33m' + TERM_BLUE = '\033[1;34m' def set_timezone(timezone): @@ -42,6 +52,17 @@ def set_sighdl_to_reset_timezone(timezone): signal.signal(signal.SIGINT, lambda signal, frame: set_timezone_and_exit(timezone)) +def setup_stdio(): + (out_stream, err_stream) = (sys.stdout, sys.stderr) + if sys.version_info.major >= 3: + (out_stream, err_stream) = (sys.stdout.buffer, sys.stderr.buffer) + # For tty using native encoding, otherwise (pipe) use 'utf-8' + encoding = sys.stdout.encoding if sys.stdout.isatty() else 'utf-8' + # Always override it to anvoid encode error + sys.stdout = codecs.getwriter(encoding)(out_stream, 'xmlcharrefreplace') + sys.stderr = codecs.getwriter(encoding)(err_stream, 'xmlcharrefreplace') + + def print_test_summary(summary_string, total, passed, failed): print(f"\n[summary] {summary_string}\n") print(f"TOTAL: {total}") @@ -72,4 +93,4 @@ def get_platform_cmd_prefix(): def get_python_cmd_prefix(): # python script doesn't have execute permission on github actions windows runner - return get_platform_cmd_prefix() + [sys.executable or 'python'] + return [sys.executable or 'python'] diff --git a/tools/settings.py b/tools/settings.py index 4b062c4d3c..d94c786d0c 100755 --- a/tools/settings.py +++ b/tools/settings.py @@ -24,7 +24,7 @@ BUILD_SCRIPT = path.join(TOOLS_DIR, 'build.py') CPPCHECK_SCRIPT = path.join(TOOLS_DIR, 'check-cppcheck.sh') DEBUGGER_CLIENT_SCRIPT = path.join(PROJECT_DIR, 'jerry-debugger/jerry_client.py') -DEBUGGER_TEST_RUNNER_SCRIPT = path.join(TOOLS_DIR, 'runners/run-debugger-test.sh') +DEBUGGER_TEST_RUNNER_SCRIPT = path.join(TOOLS_DIR, 'runners/run-debugger-test.py') DOXYGEN_SCRIPT = path.join(TOOLS_DIR, 'check-doxygen.sh') LICENSE_SCRIPT = path.join(TOOLS_DIR, 'check-license.py') STRINGS_SCRIPT = path.join(TOOLS_DIR, 'check-strings.sh')