Skip to content

Commit

Permalink
Display multi-line error messages with github action
Browse files Browse the repository at this point in the history
  • Loading branch information
Qrox committed Dec 31, 2020
1 parent 8bced72 commit 493f6d4
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 27 deletions.
2 changes: 1 addition & 1 deletion build-scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ num_jobs=3
function run_tests
{
# --min-duration shows timing lines for tests with a duration of at least that many seconds.
$WINE "$@" --min-duration 0.2 --use-colour yes --rng-seed time $EXTRA_TEST_OPTS
$WINE "$@" --min-duration 0.2 --use-colour yes --rng-seed time --error-format=github-action $EXTRA_TEST_OPTS
}

# We might need binaries installed via pip, so ensure that our personal bin dir is on the PATH
Expand Down
16 changes: 0 additions & 16 deletions build-scripts/problem-matchers/json.json

This file was deleted.

1 change: 0 additions & 1 deletion build-scripts/requirements.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ function just_json
# (See https://github.com/actions/toolkit/blob/master/docs/problem-matchers.md)
echo "::add-matcher::build-scripts/problem-matchers/catch2.json"
echo "::add-matcher::build-scripts/problem-matchers/debugmsg.json"
echo "::add-matcher::build-scripts/problem-matchers/json.json"

if which travis_retry &>/dev/null
then
Expand Down
6 changes: 5 additions & 1 deletion data/json/achievements.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
"id": "achievement_kill_zombie",
"type": "achievement",
"name": "One down, billions to go\u2026",
"requirements": [ { "event_statistic": "num_avatar_zombie_kills", "is": ">=", "target": 1 } ]
"requirements": [ { "event_statistic": "num_avatar_zombie_kills", "is": ">=", "target": 1 } ],
"foo": 0,
"bar": 1,
"baz": 2,
"qux": 3
},
{
"id": "achievement_kill_cyborg",
Expand Down
1 change: 1 addition & 0 deletions src/cached_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ bool use_tiles;
test_mode_spilling_action_t test_mode_spilling_action = test_mode_spilling_action_t::spill_all;
bool direct3d_mode;
bool pixel_minimap_option;
error_log_format_t error_log_format = error_log_format_t::human_readable;
12 changes: 12 additions & 0 deletions src/cached_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,16 @@ extern test_mode_spilling_action_t test_mode_spilling_action;

extern bool direct3d_mode;

enum class error_log_format_t {
human_readable,
// Output error messages in github action command format (currently json only)
// See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
github_action,
};
#ifndef CATA_IN_TOOL
extern error_log_format_t error_log_format;
#else
constexpr error_log_format_t error_log_format = error_log_format_t::human_readable;
#endif

#endif // CATA_SRC_CACHED_OPTIONS_H
129 changes: 121 additions & 8 deletions src/json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "cached_options.h"
#include "cata_utility.h"
#include "debug.h"
#include "output.h"
#include "string_formatter.h"

// JSON parsing and serialization tools for Cataclysm-DDA.
Expand Down Expand Up @@ -1650,16 +1651,111 @@ bool JsonIn::read( JsonDeserializer &j, bool throw_on_error )
}
}

/**
* Get the normal form of a relative path. Does not work on absolute paths.
* Slash and backslash are both treated as path separators and replaced with
* slash. Trailing slashes are always removed.
*/
static std::string normalize_relative_path( const std::string &path )
{
if( path.empty() ) {
// normal form of an empty path is an empty path
return path;
}
std::vector<std::string> names;
for( size_t name_start = 0; name_start < path.size(); ) {
const size_t name_end = std::min( path.find_first_of( "\\/", name_start ),
path.size() );
if( name_start < name_end ) {
const std::string name = path.substr( name_start, name_end - name_start );
if( name == "." ) {
// do nothing
} else if( name == ".." ) {
if( names.empty() || names.back() == ".." ) {
names.emplace_back( name );
} else {
names.pop_back();
}
} else {
names.emplace_back( name );
}
}
name_start = std::min( path.find_first_not_of( "\\/", name_end ),
path.size() );
}
if( names.empty() ) {
return ".";
} else {
std::string normpath;
for( auto it = names.begin(); it != names.end(); ++it ) {
if( it != names.begin() ) {
normpath += "/";
}
normpath += *it;
}
return normpath;
}
}

/**
* Escape special chars in github action command properties.
* See https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts
*/
static std::string escape_property( std::string str )
{
switch( error_log_format ) {
case error_log_format_t::human_readable:
break;
case error_log_format_t::github_action:
replace_substring( str, "%", "%25", true );
replace_substring( str, "\r", "%0D", true );
replace_substring( str, "\n", "%0A", true );
replace_substring( str, ":", "%3A", true );
replace_substring( str, ",", "%2C", true );
break;
}
return str;
}

/**
* Escape special chars in github action command messages.
* See https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts
*/
static std::string escape_data( std::string str )
{
switch( error_log_format ) {
case error_log_format_t::human_readable:
break;
case error_log_format_t::github_action:
replace_substring( str, "%", "%25", true );
replace_substring( str, "\r", "%0D", true );
replace_substring( str, "\n", "%0A", true );
break;
}
return str;
}

/* error display */

// WARNING: for occasional use only.
std::string JsonIn::line_number( int offset_modifier )
{
const std::string &name = path ? *path : "<unknown source file>";
const std::string name = escape_property( path ? normalize_relative_path( *path )
: "<unknown source file>" );
if( stream && stream->eof() ) {
return name + ":EOF";
switch( error_log_format ) {
case error_log_format_t::human_readable:
return name + ":EOF";
case error_log_format_t::github_action:
return "file=" + name + ",line=EOF";
}
} else if( !stream || stream->fail() ) {
return name + ":???";
switch( error_log_format ) {
case error_log_format_t::human_readable:
return name + ":???";
case error_log_format_t::github_action:
return "file=" + name + ",line=???";
}
} // else stream is fine
int pos = tell();
int line = 1;
Expand Down Expand Up @@ -1687,24 +1783,41 @@ std::string JsonIn::line_number( int offset_modifier )
}
seek( pos );
std::stringstream ret;
ret << name << ":" << line << ":" << offset;
switch( error_log_format ) {
case error_log_format_t::human_readable:
ret << name << ":" << line << ":" << offset;
break;
case error_log_format_t::github_action:
ret.imbue( std::locale::classic() );
ret << "file=" << name << ",line=" << line << ",col=" << offset;
break;
}
return ret.str();
}

void JsonIn::error( const std::string &message, int offset )
{
std::ostringstream err;
err << "Json error: " << line_number( offset ) << ": " << message;
std::ostringstream err_header;
switch( error_log_format ) {
case error_log_format_t::human_readable:
err_header << "Json error: " << line_number( offset ) << ": ";
break;
case error_log_format_t::github_action:
err_header << "::error " << line_number( offset ) << "::";
break;
}
// if we can't get more info from the stream don't try
if( !stream->good() ) {
throw JsonError( err.str() );
throw JsonError( err_header.str() + escape_data( message ) );
}
// Seek to eof after throwing to avoid continue reading from the incorrect
// location. The calling code of json error methods is supposed to restore
// the stream location if it wishes to recover from the error.
on_out_of_scope seek_to_eof( [this]() {
stream->seekg( 0, std::istream::end );
} );
std::ostringstream err;
err << message;
// also print surrounding few lines of context, if not too large
err << "\n\n";
stream->seekg( offset, std::istream::cur );
Expand Down Expand Up @@ -1774,7 +1887,7 @@ void JsonIn::error( const std::string &message, int offset )
if( !msg.empty() && msg.back() != '\n' ) {
msg.push_back( '\n' );
}
throw JsonError( msg );
throw JsonError( err_header.str() + escape_data( msg ) );
}

void JsonIn::string_error( const std::string &message, const int offset )
Expand Down
10 changes: 10 additions & 0 deletions tests/json_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include <vector>

#include "mutation.h"
#include "cached_options.h"
#include "cata_utility.h"
#include "colony.h"
#include "string_formatter.h"
#include "type_id.h"
Expand Down Expand Up @@ -255,6 +257,8 @@ TEST_CASE( "translation_text_style_check", "[json][translation]" )
{
// this test case is mainly for checking the format of debug messages.
// the text style check itself is tested in the lit test of clang-tidy.
restore_on_out_of_scope<error_log_format_t> restore_error_log_format( error_log_format );
error_log_format = error_log_format_t::human_readable;

// string, ascii
test_translation_text_style_check(
Expand Down Expand Up @@ -400,6 +404,9 @@ TEST_CASE( "translation_text_style_check", "[json][translation]" )

TEST_CASE( "translation_text_style_check_error_recovery", "[json][translation]" )
{
restore_on_out_of_scope<error_log_format_t> restore_error_log_format( error_log_format );
error_log_format = error_log_format_t::human_readable;

SECTION( "string" ) {
const std::string json =
R"([)" "\n"
Expand Down Expand Up @@ -499,6 +506,9 @@ static void test_string_error_throws_matches( Matcher &&matcher, const std::stri

TEST_CASE( "jsonin_get_string", "[json]" )
{
restore_on_out_of_scope<error_log_format_t> restore_error_log_format( error_log_format );
error_log_format = error_log_format_t::human_readable;

// read plain text
test_get_string( "foo", R"("foo")" );
// ignore starting spaces
Expand Down
13 changes: 13 additions & 0 deletions tests/test_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,16 @@ int main( int argc, const char *argv[] )

std::string user_dir = extract_user_dir( arg_vec );

std::string error_fmt = extract_argument( arg_vec, "--error-format=" );
if( error_fmt == "github-action" ) {
error_log_format = error_log_format_t::github_action;
} else if( error_fmt == "human-readable" || error_fmt.empty() ) {
error_log_format = error_log_format_t::human_readable;
} else {
printf( "Unknown format %s", error_fmt.c_str() );
return EXIT_FAILURE;
}

// Note: this must not be invoked before all DDA-specific flags are stripped from arg_vec!
int result = session.applyCommandLine( arg_vec.size(), &arg_vec[0] );
if( result != 0 || session.configData().showHelp ) {
Expand All @@ -294,6 +304,9 @@ int main( int argc, const char *argv[] )
printf( " -D, --drop-world Don't save the world on test failure.\n" );
printf( " --option_overrides=n:v[,…] Name-value pairs of game options for tests.\n" );
printf( " (overrides config/options.json values)\n" );
printf( " --error-format=<value> Format of error messages. Possible values are:\n" );
printf( " human-readable (default)\n" );
printf( " github-action\n" );
return result;
}

Expand Down

0 comments on commit 493f6d4

Please sign in to comment.