Repository: kmyk/online-judge-tools
Pull request #708 (2020-03-19 13:17:31)

Release/9.1.0


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab79c9c..8d8655b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,25 +1,35 @@
 # Change Log
 
-## 9.0.0 / 2019-02-29
+## 9.1.0 / 2020-03-20
+
+-   [#704](https://github.com/kmyk/online-judge-tools/pull/704) improve error logs of `oj download` when files already exist
+-   [#706](https://github.com/kmyk/online-judge-tools/pull/706) kill zombie processes caused by `--tle` option in `oj test` 
+-   [#705](https://github.com/kmyk/online-judge-tools/pull/705) make `oj submit` use Python 3 by default
+-   [#705](https://github.com/kmyk/online-judge-tools/pull/705) fix a bug of `--guess-python-interpreter` option
+-   [#703](https://github.com/kmyk/online-judge-tools/pull/703) allow CRLF as line separators in `oj test`
+-   [#702](https://github.com/kmyk/online-judge-tools/pull/702) display trailing spaces explicitly in `oj test`
+-   [#700](https://github.com/kmyk/online-judge-tools/pull/700) fix a bug of `--side-by-side` option about line numbers
+
+## 9.0.0 / 2020-02-29
 
 -   [#692](https://github.com/kmyk/online-judge-tools/pull/692) (breaking changes) remove `split-input` subcommand
 -   [#691](https://github.com/kmyk/online-judge-tools/pull/691) (breaking changes) remove support for Topcoder Marathon Match
 -   [#686](https://github.com/kmyk/online-judge-tools/pull/686) fix a bug of `--side-by-side` option
 
-## 8.0.0 / 2019-02-14
+## 8.0.0 / 2020-02-14
 
 -   [#675](https://github.com/kmyk/online-judge-tools/pull/675) snip side-by-side diff display if result is long
 -   [#676](https://github.com/kmyk/online-judge-tools/pull/676) `download` command support yukicoder token ([@beet-aizu](https://github.com/beet-aizu))
 -   [#671](https://github.com/kmyk/online-judge-tools/pull/671) support latest library checker
 -   [#665](https://github.com/kmyk/online-judge-tools/pull/665) `test` support side-by-side diff display
 
-## 7.8.0 / 2019-02-03
+## 7.8.0 / 2020-02-03
 
 -   [#665](https://github.com/kmyk/online-judge-tools/pull/665) add Sphere Online Judge support
 -   [#661](https://github.com/kmyk/online-judge-tools/pull/661) allow `submit` to guess more languages ([@eggplants](https://github.com/eggplants))
 -   [#660](https://github.com/kmyk/online-judge-tools/pull/660) prevent installation using unsupported versions of Python
 
-## 7.7.0 / 2019-01-20
+## 7.7.0 / 2020-01-20
 -   [#654](https://github.com/kmyk/online-judge-tools/pull/654) fix a bug of git pull option about Library Checker
 -   [#653](https://github.com/kmyk/online-judge-tools/pull/653) add CodeChef support
 -   [#649](https://github.com/kmyk/online-judge-tools/pull/649) allow `submit` to guess Common Lisp and Clojure files by the extension [@fukamachi](https://github.com/fukamachi)
diff --git a/onlinejudge/__about__.py b/onlinejudge/__about__.py
index 070c79b..dfd540b 100644
--- a/onlinejudge/__about__.py
+++ b/onlinejudge/__about__.py
@@ -4,6 +4,6 @@ __author__ = 'Kimiyuki Onaka'
 __email__ = 'kimiyuki95@gmail.com'
 __license__ = 'MIT License'
 __url__ = 'https://github.com/kmyk/online-judge-tools'
-__version_info__ = (9, 0, 0, 'final', 0)
+__version_info__ = (9, 1, 0, 'final', 0)
 __version__ = '.'.join(map(str, __version_info__[:3]))
 __description__ = 'Tools for online-judge services'
diff --git a/onlinejudge/_implementation/command/download.py b/onlinejudge/_implementation/command/download.py
index 2cd6a28..1838610 100644
--- a/onlinejudge/_implementation/command/download.py
+++ b/onlinejudge/_implementation/command/download.py
@@ -73,7 +73,7 @@ def download(args: 'argparse.Namespace') -> None:
             path = args.directory / format_utils.percentformat(args.format, table)  # type: pathlib.Path
             log.status('%sput: %s', ext, name)
             if not args.silent:
-                log.emit(utils.make_pretty_large_file_content(data, limit=40, head=20, tail=10, bold=True))
+                log.emit(utils.snip_large_file_content(data, limit=40, head=20, tail=10, bold=True))
             if args.dry_run:
                 continue
             if path.exists():
diff --git a/onlinejudge/_implementation/command/generate_input.py b/onlinejudge/_implementation/command/generate_input.py
index a41ec29..b733379 100644
--- a/onlinejudge/_implementation/command/generate_input.py
+++ b/onlinejudge/_implementation/command/generate_input.py
@@ -43,7 +43,7 @@ def write_result(input_data: bytes, output_data: Optional[bytes], *, input_path:
 
         if print_data:
             log.emit('input:')
-            log.emit(utils.make_pretty_large_file_content(input_data, limit=40, head=20, tail=10, bold=True))
+            log.emit(utils.snip_large_file_content(input_data, limit=40, head=20, tail=10, bold=True))
         with input_path.open('wb') as fh:
             fh.write(input_data)
         log.success('saved to: %s', input_path)
@@ -51,7 +51,7 @@ def write_result(input_data: bytes, output_data: Optional[bytes], *, input_path:
         if output_data is not None:
             if print_data:
                 log.emit('output:')
-                log.emit(utils.make_pretty_large_file_content(output_data, limit=40, head=20, tail=10, bold=True))
+                log.emit(utils.snip_large_file_content(output_data, limit=40, head=20, tail=10, bold=True))
             with output_path.open('wb') as fh:
                 fh.write(output_data)
             log.success('saved to: %s', output_path)
@@ -149,9 +149,9 @@ def try_hack_once(generator: str, command: str, hack: str, *, tle: Optional[floa
         expected = output_data.decode()
         if not simple_match(answer, expected):
             log.failure(log.red('WA'))
-            log.emit('input:\n%s', utils.make_pretty_large_file_content(input_data, limit=40, head=20, tail=10, bold=True))
-            log.emit('output:\n%s', utils.make_pretty_large_file_content(answer.encode(), limit=40, head=20, tail=10, bold=True))
-            log.emit('expected:\n%s', utils.make_pretty_large_file_content(output_data, limit=40, head=20, tail=10, bold=True))
+            log.emit('input:\n%s', utils.snip_large_file_content(input_data, limit=40, head=20, tail=10, bold=True))
+            log.emit('output:\n%s', utils.snip_large_file_content(answer.encode(), limit=40, head=20, tail=10, bold=True))
+            log.emit('expected:\n%s', utils.snip_large_file_content(output_data, limit=40, head=20, tail=10, bold=True))
             status = 'WA'
 
         if status == 'AC':
diff --git a/onlinejudge/_implementation/command/generate_output.py b/onlinejudge/_implementation/command/generate_output.py
index 1067d57..881d31f 100644
--- a/onlinejudge/_implementation/command/generate_output.py
+++ b/onlinejudge/_implementation/command/generate_output.py
@@ -45,7 +45,7 @@ def generate_output_single_case(test_name: str, test_input_path: pathlib.Path, *
             log.info('skipped.')
             return
         assert answer is not None
-        log.emit(utils.make_pretty_large_file_content(answer, limit=40, head=20, tail=10, bold=True))
+        log.emit(utils.snip_large_file_content(answer, limit=40, head=20, tail=10, bold=True))
 
         # find the destination path
         match_result = fmtutils.match_with_format(args.directory, args.format, test_input_path)  # type: Optional[Match[Any]]
diff --git a/onlinejudge/_implementation/command/submit.py b/onlinejudge/_implementation/command/submit.py
index 5950f96..8279c99 100644
--- a/onlinejudge/_implementation/command/submit.py
+++ b/onlinejudge/_implementation/command/submit.py
@@ -49,7 +49,7 @@ def submit(args: 'argparse.Namespace') -> None:
 
     # report code
     log.info('code (%d byte):', len(code))
-    log.emit(utils.make_pretty_large_file_content(code, limit=30, head=10, tail=10, bold=True))
+    log.emit(utils.snip_large_file_content(code, limit=30, head=10, tail=10, bold=True))
 
     with utils.with_cookiejar(utils.new_session_with_our_user_agent(), path=args.cookie) as sess:
         # guess or select language ids
@@ -230,9 +230,7 @@ def guess_lang_ids_of_file(filename: pathlib.Path, code: bytes, language_dict, c
             lang_ids += select('pypy', language_dict.keys())
 
         # version
-        two_found = select_words(['python', '2'], lang_ids) or select_words(['pypy', '2'], lang_ids)
-        three_found = select_words(['python', '3'], lang_ids) or select_words(['pypy', '3'], lang_ids)
-        if two_found and three_found:
+        if select_words(['python', '2'], lang_ids) and select_words(['python', '3'], lang_ids):
             log.status('both Python2 and Python3 are available for version of Python')
             if python_version in ('2', '3'):
                 versions = [int(python_version)]
@@ -251,7 +249,7 @@ def guess_lang_ids_of_file(filename: pathlib.Path, code: bytes, language_dict, c
                         versions += [version]
                 if not versions:
                     log.status('no version info in code')
-                    versions = [3]
+                    versions = [2, 3]
             log.status('use: %s', ', '.join(map(str, versions)))
 
             saved_ids = lang_ids
@@ -259,8 +257,6 @@ def guess_lang_ids_of_file(filename: pathlib.Path, code: bytes, language_dict, c
             for version in versions:
                 lang_ids += select('python%d' % version, saved_ids)
                 lang_ids += select('python %d' % version, saved_ids)
-                lang_ids += select('pypy%d' % version, saved_ids)
-                lang_ids += select('pypy %d' % version, saved_ids)
 
         lang_ids = list(set(lang_ids))
         return lang_ids
diff --git a/onlinejudge/_implementation/logging.py b/onlinejudge/_implementation/logging.py
index a2ce2c6..1e977ab 100644
--- a/onlinejudge/_implementation/logging.py
+++ b/onlinejudge/_implementation/logging.py
@@ -79,4 +79,3 @@ green = lambda s: colorama.Fore.GREEN + s + colorama.Fore.RESET
 red = lambda s: colorama.Fore.RED + s + colorama.Fore.RESET
 green_diff = lambda s: colorama.Fore.RESET + colorama.Back.GREEN + colorama.Style.BRIGHT + s + colorama.Style.NORMAL + colorama.Back.RESET + colorama.Fore.GREEN
 red_diff = lambda s: colorama.Fore.RESET + colorama.Back.RED + colorama.Style.BRIGHT + s + colorama.Style.NORMAL + colorama.Back.RESET + colorama.Fore.RED
-dim = lambda s: colorama.Style.RESET_ALL + colorama.Style.DIM + s + colorama.Style.RESET_ALL
diff --git a/onlinejudge/_implementation/main.py b/onlinejudge/_implementation/main.py
index 52232fa..4471e64 100644
--- a/onlinejudge/_implementation/main.py
+++ b/onlinejudge/_implementation/main.py
@@ -105,7 +105,7 @@ supported services:
     subparser.add_argument('--no-guess-latest', action='store_false', dest='guess_cxx_latest')
     subparser.add_argument('--guess-cxx-latest', action='store_true', help='use the lasest version for C++ (default)')
     subparser.add_argument('--guess-cxx-compiler', choices=('gcc', 'clang', 'all'), default='gcc', help='use the specified C++ compiler if both of GCC and Clang are available (default: gcc)')
-    subparser.add_argument('--guess-python-version', choices=('2', '3', 'auto', 'all'), default='auto', help='default: auto')
+    subparser.add_argument('--guess-python-version', choices=('2', '3', 'auto', 'all'), default='auto', help='shebang or modelines are used by default. write something like "#!/usr/bin/env python3". (default: auto)')
     subparser.add_argument('--guess-python-interpreter', choices=('cpython', 'pypy', 'all'), default='cpython', help='use the specified Python interpreter if both of CPython and PyPy are available (default: cpython)')
     subparser.add_argument('--format-dos2unix', action='store_true', help='replace CRLF with LF for given file')
     subparser.add_argument('--format-rstrip', action='store_true', help='remove trailing newlines from given file')
diff --git a/onlinejudge/_implementation/utils.py b/onlinejudge/_implementation/utils.py
index d0d0f7e..006527b 100644
--- a/onlinejudge/_implementation/utils.py
+++ b/onlinejudge/_implementation/utils.py
@@ -8,10 +8,8 @@ import http.cookiejar
 import json
 import os
 import posixpath
-import re
 import shlex
 import shutil
-import signal
 import subprocess
 import sys
 import tempfile
@@ -136,13 +134,8 @@ def exec_command(command_str: str, *, stdin: Optional[IO[Any]] = None, input: Op
             command = command_str.encode().decode()  # type: ignore
         begin = time.perf_counter()
 
-        # We need kill processes called from the "time" command using process groups. Without this, zombies spawn. see https://github.com/kmyk/online-judge-tools/issues/640
-        preexec_fn = None
-        if gnu_time is not None and os.name == 'posix':
-            preexec_fn = os.setsid
-
         try:
-            proc = subprocess.Popen(command, stdin=stdin, stdout=subprocess.PIPE, stderr=sys.stderr, preexec_fn=preexec_fn)
+            proc = subprocess.Popen(command, stdin=stdin, stdout=subprocess.PIPE, stderr=sys.stderr)
         except FileNotFoundError:
             log.error('No such file or directory: %s', command)
             sys.exit(1)
@@ -153,10 +146,7 @@ def exec_command(command_str: str, *, stdin: Optional[IO[Any]] = None, input: Op
         try:
             answer, _ = proc.communicate(input=input, timeout=timeout)
         except subprocess.TimeoutExpired:
-            if preexec_fn is not None:
-                os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
-            else:
-                proc.terminate()
+            proc.terminate()
 
         end = time.perf_counter()
         memory = None  # type: Optional[float]
@@ -298,60 +288,38 @@ def getter_with_load_details(name: str, type: Union[str, type]) -> Callable:
     return wrapper
 
 
-def make_pretty_large_file_content(content: bytes, limit: int, head: int, tail: int, bold: bool = False) -> str:
+def snip_large_file_content(content: bytes, limit: int, head: int, tail: int, bold: bool = False) -> str:
     assert head + tail < limit
     try:
         text = content.decode()
     except UnicodeDecodeError as e:
         return str(e)
-
-    def font(line: str) -> str:
-        if not line.endswith('\n'):
-            line += log.dim('(no trailing newline)')
-        else:
-
-            def repl(m):
-                if m.group(1) == '\r':
-                    return log.dim('\\r' + m.group(2))
-                else:
-                    return log.dim(m.group(1).replace(' ', '_').replace('\t', '\\t').replace('\r', '\\r') + '(trailing spaces)' + m.group(2))
-
-            line = re.sub(r'(\s+)(\n)$', repl, line)
-        if bold:
-            line = log.bold(line)
-        return line
-
+    font = log.bold if bold else (lambda s: s)
     char_in_line, _ = shutil.get_terminal_size()
     char_in_line = max(char_in_line, 40)  # shutil.get_terminal_size() may return too small values (e.g. (0, 0) on Circle CI) successfully (i.e. fallback is not used). see https://github.com/kmyk/online-judge-tools/pull/611
 
-    def no_snip_text() -> List[str]:
-        lines = text.splitlines(keepends=True)
-        return [''.join(map(font, lines))]
-
-    def snip_line_based() -> List[str]:
+    def snip_line_based():
         lines = text.splitlines(keepends=True)
         if len(lines) < limit:
-            return []
-        return [''.join([
-            *map(font, lines[:head]),
-            '... ({} lines) ...\n'.format(len(lines[head:-tail])),
-            *map(font, lines[-tail:]),
-        ])]
-
-    def snip_char_based() -> List[str]:
+            return font(text)
+        else:
+            return ''.join([
+                font(''.join(lines[:head])),
+                '... ({} lines) ...\n'.format(len(lines[head:-tail])),
+                font(''.join(lines[-tail:])),
+            ])
+
+    def snip_char_based():
         if len(text) < char_in_line * limit:
-            return []
-        return [''.join([
-            *map(font, text[:char_in_line * head].splitlines(keepends=True)),
-            '... ({} chars) ...'.format(len(text[char_in_line * head:-char_in_line * tail])),
-            *map(font, text[-char_in_line * tail:].splitlines(keepends=True)),
-        ])]
-
-    candidates = []  # type: List[str]
-    candidates += no_snip_text()
-    candidates += snip_line_based()
-    candidates += snip_char_based()
-    return min(candidates, key=len)
+            return font(text)
+        else:
+            return ''.join([
+                font(text[:char_in_line * head]),
+                '... ({} chars) ...'.format(len(text[char_in_line * head:-char_in_line * tail])),
+                font(text[-char_in_line * tail:]),
+            ])
+
+    return min([font(text), snip_line_based(), snip_char_based()], key=len)
 
 
 class DummySubmission(Submission):


Test patch:
diff --git a/onlinejudge/_implementation/command/test.py b/onlinejudge/_implementation/command/test.py
index 2ddac61..c075251 100644
--- a/onlinejudge/_implementation/command/test.py
+++ b/onlinejudge/_implementation/command/test.py
@@ -81,7 +81,7 @@ def compare_and_report(proc: subprocess.Popen, answer: str, memory: Optional[flo
                 log.status('$ %s', actual_command)
                 info, proc = utils.exec_command(actual_command)
                 if not silent:
-                    log.emit('judge\'s output:\n%s', utils.make_pretty_large_file_content(info['answer'] or b'', limit=40, head=20, tail=10, bold=True))
+                    log.emit('judge\'s output:\n%s', utils.snip_large_file_content(info['answer'] or b'', limit=40, head=20, tail=10, bold=True))
                 judge_result = (proc.returncode == 0)
             finally:
                 os.unlink(user_output.name)
@@ -94,21 +94,6 @@ def compare_and_report(proc: subprocess.Popen, answer: str, memory: Optional[flo
             if rstrip and a.rstrip(rstrip_targets) == b.rstrip(rstrip_targets):
                 log.warning('WA if no rstrip')
                 return True
-            if a == b.replace('\n', '\r\n'):
-                log.warning(r'WA if not replacing "\r\n" with "\n"')
-                return True
-            if rstrip and a.rstrip(rstrip_targets) == b.replace('\n', '\r\n').rstrip(rstrip_targets):
-                log.warning('WA if no rstrip')
-                log.warning(r'WA if not replacing "\r\n" with "\n"')
-                return True
-            if a.replace('\n', '\r\n') == b:
-                log.warning(r'WA if not replacing "\n" with "\r\n"')
-                return True
-            if rstrip and a.replace('\n', '\r\n').rstrip(rstrip_targets) == b.rstrip(rstrip_targets):
-                # TODO: use a smart way if you need more equality patterns
-                log.warning('WA if no rstrip')
-                log.warning(r'WA if not replacing "\n" with "\r\n"')
-                return True
             return False
 
     # prepare the function to print the input
@@ -119,7 +104,7 @@ def compare_and_report(proc: subprocess.Popen, answer: str, memory: Optional[flo
         if does_print_input and not is_input_printed:
             is_input_printed = True
             with test_input_path.open('rb') as inf:
-                log.emit('input:\n%s', utils.make_pretty_large_file_content(inf.read(), limit=40, head=20, tail=10, bold=True))
+                log.emit('input:\n%s', utils.snip_large_file_content(inf.read(), limit=40, head=20, tail=10, bold=True))
 
     # check TLE, RE or not
     status = 'AC'
@@ -150,8 +135,8 @@ def compare_and_report(proc: subprocess.Popen, answer: str, memory: Optional[flo
             print_input()
             if not silent:
                 if mode == 'simple':
-                    log.emit('output:\n%s', utils.make_pretty_large_file_content(answer.encode(), limit=40, head=20, tail=10, bold=True))
-                    log.emit('expected:\n%s', utils.make_pretty_large_file_content(expected.encode(), limit=40, head=20, tail=10, bold=True))
+                    log.emit('output:\n%s', utils.snip_large_file_content(answer.encode(), limit=40, head=20, tail=10, bold=True))
+                    log.emit('expected:\n%s', utils.snip_large_file_content(expected.encode(), limit=40, head=20, tail=10, bold=True))
                 elif mode == 'side-by-side':
                     if max(answer.count('\n'), expected.count('\n')) <= 40:
                         display_side_by_side_color(answer, expected)
@@ -162,7 +147,7 @@ def compare_and_report(proc: subprocess.Popen, answer: str, memory: Optional[flo
             status = 'WA'
     else:
         if not silent:
-            log.emit(('output:\n%s' if is_input_printed else '%s'), utils.make_pretty_large_file_content(answer.encode(), limit=40, head=20, tail=10, bold=True))
+            log.emit(('output:\n%s' if is_input_printed else '%s'), utils.snip_large_file_content(answer.encode(), limit=40, head=20, tail=10, bold=True))
     if status == 'AC':
         log.success(log.green('AC'))
 
diff --git a/tests/command_test.py b/tests/command_test.py
index dff548a..781326b 100644
--- a/tests/command_test.py
+++ b/tests/command_test.py
@@ -15,11 +15,10 @@ from onlinejudge._implementation.command import test
 
 
 class TestTest(unittest.TestCase):
-    def snippet_call_test(self, args, files, expected, verbose=True, replace_output_newline=True):
+    def snippet_call_test(self, args, files, expected, verbose=True):
         result = tests.utils.run_in_sandbox(args=(['-v'] if verbose else []) + ['test', '--json'] + args, files=files)
         self.assertTrue(result['proc'].stdout)
         data = json.loads(result['proc'].stdout.decode())
-        print(data)
         if expected is None:
             return data
         else:
@@ -32,11 +31,7 @@ class TestTest(unittest.TestCase):
                     self.assertEqual(a['testcase']['output'], b['testcase']['output'].replace('/', os.path.sep) % result['tempdir'])
                 self.assertEqual(a['exitcode'], b['exitcode'])
                 self.assertEqual(a['status'], b['status'])
-                if replace_output_newline:
-                    a_output = a['output'].replace(os.linesep, '\n')
-                else:
-                    a_output = a['output']
-                self.assertEqual(a_output, b['output'])
+                self.assertEqual(a['output'].replace(os.linesep, '\n'), b['output'])
 
     def test_call_test_simple(self):
         self.snippet_call_test(
@@ -160,11 +155,11 @@ class TestTest(unittest.TestCase):
                 },
                 {
                     'path': 'test/sample-5.in',
-                    'data': '1.0\n2.0\n'
+                    'data': '1.0\n2.0\n'.replace('\n', os.linesep)
                 },
                 {
                     'path': 'test/sample-5.out',
-                    'data': '1.0\n'
+                    'data': '1.0\n'.replace('\n', os.linesep)
                 },
             ],
             expected=[{
@@ -210,7 +205,7 @@ class TestTest(unittest.TestCase):
                     'input': '%s/test/sample-5.in',
                     'output': '%s/test/sample-5.out',
                 },
-                'output': '1.0\n2.0\n',
+                'output': '1.0\n2.0\n'.replace('\n', os.linesep),
                 'exitcode': 0,
             }],
         )
@@ -289,19 +284,19 @@ class TestTest(unittest.TestCase):
             files=[
                 {
                     'path': 'test/sample-1.in',
-                    'data': 'foo\nfoobar\n'
+                    'data': 'foo\nfoobar\n'.replace('\n', os.linesep)
                 },
                 {
                     'path': 'test/sample-1.out',
-                    'data': 'foo\nfoobar\n'
+                    'data': 'foo\nfoobar\n'.replace('\n', os.linesep)
                 },
                 {
                     'path': 'test/sample-2.in',
-                    'data': 'bar\nfoobar\n'
+                    'data': 'bar\nfoobar\n'.replace('\n', os.linesep)
                 },
                 {
                     'path': 'test/sample-2.out',
-                    'data': 'bar\nbarbar\n'
+                    'data': 'bar\nbarbar\n'.replace('\n', os.linesep)
                 },
             ],
             expected=[{
@@ -311,7 +306,7 @@ class TestTest(unittest.TestCase):
                     'input': '%s/test/sample-1.in',
                     'output': '%s/test/sample-1.out',
                 },
-                'output': 'foo\nfoobar\n',
+                'output': 'foo\nfoobar\n'.replace('\n', os.linesep),
                 'exitcode': 0,
             }, {
                 'status': 'WA',
@@ -320,7 +315,7 @@ class TestTest(unittest.TestCase):
                     'input': '%s/test/sample-2.in',
                     'output': '%s/test/sample-2.out',
                 },
-                'output': 'bar\nfoobar\n',
+                'output': 'bar\nfoobar\n'.replace('\n', os.linesep),
                 'exitcode': 0,
             }],
         )
@@ -936,84 +931,8 @@ class TestTest(unittest.TestCase):
             }],
         )
 
-    def test_call_output_uses_crlf(self):
-        data = self.snippet_call_test(
-            args=['-c', tests.utils.python_c(r"import sys; sys.stdout.buffer.write(b'foo\r\nbar\r\nbaz\r\n')")],
-            files=[
-                {
-                    'path': 'test/sample-1.in',
-                    'data': '',
-                },
-                {
-                    'path': 'test/sample-1.out',
-                    'data': 'foo\nbar\nbaz\n'
-                },
-            ],
-            expected=[{
-                'status': 'AC',
-                'testcase': {
-                    'name': 'sample-1',
-                    'input': '%s/test/sample-1.in',
-                    'output': '%s/test/sample-1.out',
-                },
-                'output': 'foo\r\nbar\r\nbaz\r\n',
-                'exitcode': 0,
-            }],
-            replace_output_newline=False,
-        )
-
-    def test_call_expected_uses_crlf(self):
-        data = self.snippet_call_test(
-            args=['-c', tests.utils.python_c(r"import sys; sys.stdout.buffer.write(b'foo\nbar\nbaz\n')")],
-            files=[
-                {
-                    'path': 'test/sample-1.in',
-                    'data': '',
-                },
-                {
-                    'path': 'test/sample-1.out',
-                    'data': 'foo\r\nbar\r\nbaz\r\n',
-                },
-            ],
-            expected=[{
-                'status': 'AC',
-                'testcase': {
-                    'name': 'sample-1',
-                    'input': '%s/test/sample-1.in',
-                    'output': '%s/test/sample-1.out',
-                },
-                'output': 'foo\nbar\nbaz\n',
-                'exitcode': 0,
-            }],
-            replace_output_newline=False,
-        )
-
-    def test_call_output_uses_both_lf_and_crlf(self):
-        data = self.snippet_call_test(
-            args=['-c', tests.utils.python_c(r"import sys; sys.stdout.buffer.write(b'foo\r\nbar\nbaz\r\n')")],
-            files=[
-                {
-                    'path': 'test/sample-1.in',
-                    'data': '',
-                },
-                {
-                    'path': 'test/sample-1.out',
-                    'data': 'foo\nbar\nbaz\n'
-                },
-            ],
-            expected=[{
-                'status': 'WA',
-                'testcase': {
-                    'name': 'sample-1',
-                    'input': '%s/test/sample-1.in',
-                    'output': '%s/test/sample-1.out',
-                },
-                'output': 'foo\r\nbar\nbaz\r\n',
-                'exitcode': 0,
-            }],
-            replace_output_newline=False,
-        )
-
+    # TODO: fix
+    @unittest.expectedFailure
     @unittest.skipIf(os.name == 'nt', "procfs is required")
     def test_call_test_check_no_zombie(self):
         marker = 'zombie-%08x' % random.randrange(2**32)
@@ -1072,45 +991,6 @@ class TestTestLog(unittest.TestCase):
         print(result['proc'].stderr.decode(), file=sys.stderr)
         self.check_log_lines(result['proc'].stderr.decode().split(os.linesep), expected_log_lines)
 
-    def test_trailing_spaces(self):
-        self.snippet_call_test(
-            args=['-c', tests.utils.python_c(r"import sys; sys.stdout.buffer.write(b'1\r\n2 \r\n3\n4  \t  \n5  \n6')")],
-            files=[
-                {
-                    'path': 'test/sample-1.in',
-                    'data': '',
-                },
-                {
-                    'path': 'test/sample-1.out',
-                    'data': '1\n2\n3\n',
-                },
-            ],
-            expected_log_lines=[
-                '[*] 1 cases found',
-                '',
-                '[*] sample-1',
-                '[x] time:',
-                '[-] WA',
-                'output:',
-                r'1\r',
-                r'2_\r(trailing spaces)',
-                r'3',
-                r'4__\t__(trailing spaces)',
-                r'5__(trailing spaces)',
-                r'6(no trailing newline)',
-                'expected:',
-                '1',
-                '2',
-                '3',
-                '',
-                '',
-                '[x] slowest:',
-                '[x] max memory:',
-                '[-] test failed: 0 AC / 1 cases',
-                '',
-            ],
-        )
-
     def test_side_by_short(self):
         self.snippet_call_test(
             args=['-m', 'side-by-side', '-c', cat(), '--no-rstrip'],
diff --git a/tests/utils.py b/tests/utils.py
index d4340b6..a8b3e8c 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -20,8 +20,8 @@ def prepare_files(files):
     for f in files:
         path = pathlib.Path(f['path'])
         path.parent.mkdir(parents=True, exist_ok=True)
-        with open(str(path), 'wb') as fh:
-            fh.write(f['data'].encode())
+        with open(str(path), 'w') as fh:
+            fh.write(f['data'])
         if f.get('executable', False):
             path.chmod(0o755)