From d7f8a5ff675803a1c278215ceda4087395c0fda5 Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Fri, 25 Oct 2024 13:18:22 +0200 Subject: [PATCH] Fix Windows line endings and add tests. --- src/primitive_core.cc | 20 +++++++- tests/null-string-test.toit | 25 ++++++++++ tests/stdout-stderr-test-compiler.toit | 65 ++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 tests/null-string-test.toit create mode 100644 tests/stdout-stderr-test-compiler.toit diff --git a/src/primitive_core.cc b/src/primitive_core.cc index 7162a912c..4f68d2b83 100644 --- a/src/primitive_core.cc +++ b/src/primitive_core.cc @@ -73,6 +73,18 @@ namespace toit { MODULE_IMPLEMENTATION(core, MODULE_CORE) #if defined(TOIT_WINDOWS) +#include +#include + +static void replace_line_endings(const uint8_t* bytes, size_t length, std::vector& buffer) { + for (size_t i = 0; i < length; ++i) { + if (bytes[i] == '\n' && (i == 0 || bytes[i - 1] != '\r')) { + buffer.push_back('\r'); // Add '\r' before '\n' if not already preceded by it. + } + buffer.push_back(bytes[i]); + } +} + static Object* write_on_std(const uint8_t* bytes, size_t length, bool is_stdout, bool newline, Process* process) { HANDLE console = GetStdHandle(is_stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); if (console == INVALID_HANDLE_VALUE) { @@ -82,17 +94,21 @@ static Object* write_on_std(const uint8_t* bytes, size_t length, bool is_stdout, DWORD written; DWORD mode; + // Prepare buffer with replaced line endings. + std::vector buffer; + replace_line_endings(bytes, length, buffer); + // Check if the handle is a console handle. if (GetConsoleMode(console, &mode)) { // Write to the console. - WriteConsoleA(console, bytes, (DWORD)length, &written, NULL); + WriteConsoleA(console, buffer.data(), (DWORD)buffer.size(), &written, NULL); if (newline) { WriteConsoleA(console, "\r\n", 2, &written, NULL); } } else { // Handle redirection case. - WriteFile(console, bytes, (DWORD)length, &written, NULL); + WriteFile(console, buffer.data(), (DWORD)buffer.size(), &written, NULL); if (newline) { WriteFile(console, "\r\n", 2, &written, NULL); diff --git a/tests/null-string-test.toit b/tests/null-string-test.toit new file mode 100644 index 000000000..82d99fc55 --- /dev/null +++ b/tests/null-string-test.toit @@ -0,0 +1,25 @@ +// Copyright (C) 2024 Toitware ApS. +// Use of this source code is governed by a Zero-Clause BSD license that can +// be found in the tests/LICENSE file. + +import expect show * +import host.directory +import host.file + +main: + null-string := "foo\0bar" + test: directory.mkdir null-string + test: directory.mkdtemp null-string + test: file.is-directory null-string + test: file.is-file null-string + e := catch: file.Stream.for-read null-string + expect (e.starts-with "INVALID_ARGUMENT") + e = catch: file.Stream.for-write null-string + expect (e.starts-with "INVALID_ARGUMENT") + + // However, print works. + print null-string + print-on-stderr_ null-string + +test [block]: + expect-throw "INVALID_ARGUMENT" block diff --git a/tests/stdout-stderr-test-compiler.toit b/tests/stdout-stderr-test-compiler.toit new file mode 100644 index 000000000..ca6a40ef9 --- /dev/null +++ b/tests/stdout-stderr-test-compiler.toit @@ -0,0 +1,65 @@ +// Copyright (C) 2024 Toitware ApS. +// Use of this source code is governed by a Zero-Clause BSD license that can +// be found in the tests/LICENSE file. + +import expect show * +import host.pipe +import io +import system + +// Marked as compiler test so we get the toit.run path. +main args: + if args.size == 3 and args[0] == "RUN_TEST": + is-stdout := args[1] == "STDOUT" + test-case := int.parse args[2] + run-spawned --is-stdout=is-stdout test-case + return + + toit-run := args[0] + run-tests toit-run + +TESTS ::= [ + "", + "foo", + "foo\nbar", +] + +run-spawned test-case/int --is-stdout/bool -> none: + if is-stdout: + print_ TESTS[test-case] + else: + print-on-stderr_ TESTS[test-case] + +run args --stdout/bool=false --stderr/bool=false -> string: + pipes := pipe.fork + true // use_path + pipe.PIPE-INHERITED // stdin + stdout ? pipe.PIPE-CREATED : pipe.PIPE-INHERITED // stdout + stderr ? pipe.PIPE-CREATED : pipe.PIPE-INHERITED // stderr + args[0] + args + + out-pipe := stdout ? pipes[1] : pipes[2] + pid := pipes[3] + + reader := io.Reader.adapt out-pipe + reader.buffer-all + output := reader.read-string (reader.buffered-size) + + exit-value := pipe.wait-for pid + exit-code := pipe.exit-code exit-value + + if exit-code != 0: throw "Program didn't exit with 0." + return output + +run-tests toit-run: + this-file := system.program-path + expect (this-file.ends-with ".toit") + for i := 0; i < TESTS.size; i++: + test/string := TESTS[i] + stdout-output := run [toit-run, this-file, "RUN_TEST", "STDOUT", "$i"] --stdout + stderr-output := run [toit-run, this-file, "RUN_TEST", "STDERR", "$i"] --stderr + expect-equals stdout-output stderr-output + + expected := "$test\n".replace --all "\n" system.LINE-TERMINATOR + expect-equals expected stdout-output