diff options
-rw-r--r-- | CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/color.cc | 84 | ||||
-rw-r--r-- | src/color.h | 71 | ||||
-rw-r--r-- | src/config.h.in | 3 | ||||
-rw-r--r-- | src/source-error-handler.cc | 16 | ||||
-rw-r--r-- | src/source-error-handler.h | 6 | ||||
-rw-r--r-- | test/README.md | 1 | ||||
-rw-r--r-- | test/parse/force-color.txt | 14 | ||||
-rwxr-xr-x | test/run-tests.py | 19 |
9 files changed, 208 insertions, 11 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ec0896f..1ae2b326 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,10 @@ check_symbol_exists(snprintf "stdio.h" HAVE_SNPRINTF) check_symbol_exists(sysconf "unistd.h" HAVE_SYSCONF) check_symbol_exists(strcasecmp "strings.h" HAVE_STRCASECMP) +if (WIN32) + check_symbol_exists(ENABLE_VIRTUAL_TERMINAL_PROCESSING "windows.h" HAVE_WIN32_VT100) +endif () + if (EMSCRIPTEN) set(SIZEOF_SSIZE_T 4) set(SIZEOF_SIZE_T 4) @@ -254,6 +258,7 @@ add_library(libwabt STATIC src/resolve-names.cc src/binary.cc + src/color.cc src/common.cc src/config.cc src/literal.cc diff --git a/src/color.cc b/src/color.cc new file mode 100644 index 00000000..f1657728 --- /dev/null +++ b/src/color.cc @@ -0,0 +1,84 @@ +/* + * Copyright 2017 WebAssembly Community Group participants + * + * 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. + */ + +#include "color.h" + +#include <cstdlib> + +#include "common.h" + +#if _WIN32 +#include <io.h> +#include <windows.h> +#elif HAVE_UNISTD_H +#include <unistd.h> +#endif + +namespace wabt { + +Color::Color(FILE* file, bool enabled) : file_(file) { + enabled_ = enabled && SupportsColor(file_); +} + +// static +bool Color::SupportsColor(FILE* file) { + char* force = getenv("FORCE_COLOR"); + if (force) { + return atoi(force) != 0; + } + +#if _WIN32 + + { +#if HAVE_WIN32_VT100 + HANDLE handle; + if (file == stdout) { + handle = GetStdHandle(STD_OUTPUT_HANDLE); + } else if (file == stderr) { + handle = GetStdHandle(STD_ERROR_HANDLE); + } else { + return false; + } + DWORD mode; + if (!_isatty(_fileno(file)) || !GetConsoleMode(handle, mode) || + !SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { + return false; + } + return true; +#else + // TODO(binji): Support older Windows by using SetConsoleTextAttribute? + return false; +#endif + } + +#elif HAVE_UNISTD_H + + return isatty(fileno(file)); + +#else + + return false; + +#endif +} + +void Color::WriteCode(const char* code) const { + if (enabled_) { + fputs(code, file_); + } +} + +} // namespace wabt diff --git a/src/color.h b/src/color.h new file mode 100644 index 00000000..ba64e5d2 --- /dev/null +++ b/src/color.h @@ -0,0 +1,71 @@ +/* + * Copyright 2017 WebAssembly Community Group participants + * + * 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. + */ + +#ifndef WABT_COLOR_H_ +#define WABT_COLOR_H_ + +#include <cstdio> + +namespace wabt { + +#define WABT_FOREACH_COLOR_CODE(V) \ + V(Default, "\x1b[0m") \ + V(Bold, "\x1b[1m") \ + V(NoBold, "\x1b[22m") \ + V(Black, "\x1b[30m") \ + V(Red, "\x1b[31m") \ + V(Green, "\x1b[32m") \ + V(Yellow, "\x1b[33m") \ + V(Blue, "\x1b[34m") \ + V(Magenta, "\x1b[35m") \ + V(Cyan, "\x1b[36m") \ + V(White, "\x1b[37m") + +class Color { + public: + Color(FILE*, bool enabled = true); + + // Write the given color to the file, if enabled. +#define WABT_COLOR(Name, code) \ + void Name() const { WriteCode(Name##Code()); } + WABT_FOREACH_COLOR_CODE(WABT_COLOR) +#undef WABT_COLOR + + // Get the color code as a string, if enabled. +#define WABT_COLOR(Name, code) \ + const char* Maybe##Name##Code() const { return enabled_ ? Name##Code() : ""; } + WABT_FOREACH_COLOR_CODE(WABT_COLOR) +#undef WABT_COLOR + + // Get the color code as a string. +#define WABT_COLOR(Name, code) \ + static const char* Name##Code() { return code; } + WABT_FOREACH_COLOR_CODE(WABT_COLOR) +#undef WABT_COLOR + + private: + static bool SupportsColor(FILE*); + void WriteCode(const char*) const; + + FILE* file_; + bool enabled_; +}; + +#undef WABT_FOREACH_COLOR_CODE + +} // namespace wabt + +#endif // WABT_COLOR_H_ diff --git a/src/config.h.in b/src/config.h.in index b2dc04b4..cb6af598 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -37,6 +37,9 @@ /* Whether strcasecmp is defined by strings.h */ #cmakedefine01 HAVE_STRCASECMP +/* Whether ENABLE_VIRTUAL_TERMINAL_PROCESSING is defined by windows.h */ +#cmakedefine01 HAVE_WIN32_VT100 + #cmakedefine01 COMPILER_IS_CLANG #cmakedefine01 COMPILER_IS_GNU #cmakedefine01 COMPILER_IS_MSVC diff --git a/src/source-error-handler.cc b/src/source-error-handler.cc index acd3641f..7424b973 100644 --- a/src/source-error-handler.cc +++ b/src/source-error-handler.cc @@ -24,6 +24,7 @@ SourceErrorHandler::SourceErrorHandler(Location::Type location_type) : location_type_(location_type) {} std::string SourceErrorHandler::DefaultErrorMessage( + const Color& color, const Location* loc, const std::string& error, const std::string& source_line, @@ -31,12 +32,14 @@ std::string SourceErrorHandler::DefaultErrorMessage( int indent) { std::string indent_str(indent, ' '); std::string result = indent_str; + result += color.MaybeBoldCode(); if (location_type_ == Location::Type::Text) { result += string_printf("%s:%d:%d: ", loc->filename, loc->line, loc->first_column); } else { result += string_printf("%s:%" PRIzd ": ", loc->filename, loc->offset); } + result += color.MaybeDefaultCode(); result += error; result += '\n'; result += indent_str; @@ -50,7 +53,10 @@ std::string SourceErrorHandler::DefaultErrorMessage( num_carets = std::min(num_carets, source_line.size() - num_spaces); num_carets = std::max<size_t>(num_carets, 1); result.append(num_spaces, ' '); + result += color.MaybeBoldCode(); + result += color.MaybeGreenCode(); result.append(num_carets, '^'); + result += color.MaybeDefaultCode(); result += '\n'; } return result; @@ -69,7 +75,8 @@ SourceErrorHandlerFile::SourceErrorHandlerFile(FILE* file, file_(file), header_(header), print_header_(print_header), - source_line_max_length_(source_line_max_length) {} + source_line_max_length_(source_line_max_length), + color_(file) {} bool SourceErrorHandlerFile::OnError(const Location* loc, const std::string& error, @@ -77,7 +84,7 @@ bool SourceErrorHandlerFile::OnError(const Location* loc, size_t source_line_column_offset) { PrintErrorHeader(); int indent = header_.empty() ? 0 : 2; - std::string message = DefaultErrorMessage(loc, error, source_line, + std::string message = DefaultErrorMessage(color_, loc, error, source_line, source_line_column_offset, indent); fwrite(message.data(), 1, message.size(), file_); return true; @@ -105,13 +112,14 @@ SourceErrorHandlerBuffer::SourceErrorHandlerBuffer( size_t source_line_max_length, Location::Type location_type) : SourceErrorHandler(location_type), - source_line_max_length_(source_line_max_length) {} + source_line_max_length_(source_line_max_length), + color_(nullptr, false) {} bool SourceErrorHandlerBuffer::OnError(const Location* loc, const std::string& error, const std::string& source_line, size_t source_line_column_offset) { - buffer_ += DefaultErrorMessage(loc, error, source_line, + buffer_ += DefaultErrorMessage(color_, loc, error, source_line, source_line_column_offset, 0); return true; } diff --git a/src/source-error-handler.h b/src/source-error-handler.h index 45e049b0..5e09dc26 100644 --- a/src/source-error-handler.h +++ b/src/source-error-handler.h @@ -19,6 +19,7 @@ #include <string> +#include "color.h" #include "common.h" namespace wabt { @@ -38,7 +39,8 @@ class SourceErrorHandler { // OnError will be called with with source_line trimmed to this length. virtual size_t source_line_max_length() const = 0; - std::string DefaultErrorMessage(const Location*, + std::string DefaultErrorMessage(const Color&, + const Location*, const std::string& error, const std::string& source_line, size_t source_line_column_offset, @@ -93,6 +95,7 @@ class SourceErrorHandlerFile : public SourceErrorHandler { std::string header_; PrintHeader print_header_; size_t source_line_max_length_; + Color color_; }; class SourceErrorHandlerBuffer : public SourceErrorHandler { @@ -114,6 +117,7 @@ class SourceErrorHandlerBuffer : public SourceErrorHandler { private: size_t source_line_max_length_; std::string buffer_; + Color color_; }; } // namespace wabt diff --git a/test/README.md b/test/README.md index 64c452e0..e03508f4 100644 --- a/test/README.md +++ b/test/README.md @@ -103,6 +103,7 @@ The currently supported list of keys: - `EXE`: the executable to run, defaults to out/wast2wasm - `STDIN_FILE`: the file to use for STDIN instead of the contents of this file. - `FLAGS`: additional flags to pass to the executable +- `ENV`: environment variables to set, separated by spaces - `ERROR`: the expected return value from the executable, defaults to 0 - `SLOW`: if defined, this test's timeout is doubled. - `SKIP`: if defined, this test is not run. You can use the value as a comment. diff --git a/test/parse/force-color.txt b/test/parse/force-color.txt new file mode 100644 index 00000000..9ea7d587 --- /dev/null +++ b/test/parse/force-color.txt @@ -0,0 +1,14 @@ +;;; ERROR: 1 +;;; TOOL: wast2wasm +;;; ENV: FORCE_COLOR=1 +(module + (func badname (param i32) (result badtype) + drop)) +(;; STDERR ;;; +[1mout/test/parse/force-color.txt:5:9: [0munexpected token "badname" + (func badname (param i32) (result badtype) + [1m[32m^^^^^^^[0m +[1mout/test/parse/force-color.txt:5:37: [0munexpected token "badtype" + (func badname (param i32) (result badtype) + [1m[32m^^^^^^^[0m +;;; STDERR ;;) diff --git a/test/run-tests.py b/test/run-tests.py index 44de0525..eb459d19 100755 --- a/test/run-tests.py +++ b/test/run-tests.py @@ -171,7 +171,7 @@ class Cell(object): return self.value[0] -def RunCommandWithTimeout(command, cwd, timeout, console_out=False): +def RunCommandWithTimeout(command, cwd, timeout, console_out=False, env=None): process = None is_timeout = Cell(False) @@ -196,10 +196,10 @@ def RunCommandWithTimeout(command, cwd, timeout, console_out=False): # http://stackoverflow.com/a/10012262: subprocess with a timeout # http://stackoverflow.com/a/22582602: kill subprocess and children - process = subprocess.Popen(command, cwd=cwd, stdout=None if console_out - else subprocess.PIPE, stderr=None if console_out - else subprocess.PIPE, universal_newlines=True, - **kwargs) + process = subprocess.Popen(command, cwd=cwd, env=env, + stdout=None if console_out else subprocess.PIPE, + stderr=None if console_out else subprocess.PIPE, + universal_newlines=True, **kwargs) timer = threading.Timer(timeout, KillProcess) try: timer.start() @@ -231,6 +231,7 @@ class TestInfo(object): self.tool = 'wast2wasm' self.exe = '%(wast2wasm)s' self.flags = [] + self.env = {} self.last_cmd = '' self.expected_error = 0 self.slow = False @@ -250,6 +251,7 @@ class TestInfo(object): result.flags = ['--bindir', '%(bindir)s', '-v', '-o', '%(out_dir)s'] if fold_exprs: result.flags.append('--fold-exprs') + result.env = self.env result.expected_error = 0 result.slow = self.slow result.skip = self.skip @@ -311,6 +313,9 @@ class TestInfo(object): self.tool = value for tool_key, tool_value in TOOLS[value].items(): self.ParseDirective(tool_key, tool_value) + elif key == 'ENV': + # Pattern: FOO=1 BAR=stuff + self.env = dict(x.split('=') for x in value.split()) else: raise Error('Unknown directive: %s' % key) @@ -545,6 +550,8 @@ def RunTest(info, options, variables, verbose_level=0): variables = dict(variables) cwd = REPO_ROOT_DIR + env = dict(os.environ) + env.update(info.env) gen_input_path = info.CreateInputFile() rel_gen_input_path = os.path.relpath(gen_input_path, cwd) @@ -562,7 +569,7 @@ def RunTest(info, options, variables, verbose_level=0): print(' '.join(cmd)) try: - return RunCommandWithTimeout(cmd, cwd, timeout, verbose_level > 0) + return RunCommandWithTimeout(cmd, cwd, timeout, verbose_level > 0, env) except (Error, KeyboardInterrupt) as e: return e |