mirror of https://github.com/google/benchmark.git
escape special chars in csv and json output. (#802)
* escape special chars in csv and json output. - escape \b,\f,\n,\r,\t,\," from strings before dumping them to json or csv. - also faithfully reproduce the sign of nan in json. this fixes github issue #745. * functionalize. * split string escape functions between csv and json * Update src/csv_reporter.cc Co-Authored-By: tesch1 <tesch1@gmail.com> * Update src/json_reporter.cc Co-Authored-By: tesch1 <tesch1@gmail.com>
This commit is contained in:
parent
1d41de8463
commit
588be0446a
|
@ -37,6 +37,18 @@ std::vector<std::string> elements = {
|
|||
"error_occurred", "error_message"};
|
||||
} // namespace
|
||||
|
||||
std::string CsvEscape(const std::string & s) {
|
||||
std::string tmp;
|
||||
tmp.reserve(s.size() + 2);
|
||||
for (char c : s) {
|
||||
switch (c) {
|
||||
case '"' : tmp += "\"\""; break;
|
||||
default : tmp += c; break;
|
||||
}
|
||||
}
|
||||
return '"' + tmp + '"';
|
||||
}
|
||||
|
||||
bool CSVReporter::ReportContext(const Context& context) {
|
||||
PrintBasicContext(&GetErrorStream(), context);
|
||||
return true;
|
||||
|
@ -89,18 +101,11 @@ void CSVReporter::ReportRuns(const std::vector<Run>& reports) {
|
|||
|
||||
void CSVReporter::PrintRunData(const Run& run) {
|
||||
std::ostream& Out = GetOutputStream();
|
||||
|
||||
// Field with embedded double-quote characters must be doubled and the field
|
||||
// delimited with double-quotes.
|
||||
std::string name = run.benchmark_name();
|
||||
ReplaceAll(&name, "\"", "\"\"");
|
||||
Out << '"' << name << "\",";
|
||||
Out << CsvEscape(run.benchmark_name()) << ",";
|
||||
if (run.error_occurred) {
|
||||
Out << std::string(elements.size() - 3, ',');
|
||||
Out << "true,";
|
||||
std::string msg = run.error_message;
|
||||
ReplaceAll(&msg, "\"", "\"\"");
|
||||
Out << '"' << msg << "\"\n";
|
||||
Out << CsvEscape(run.error_message) << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -130,11 +135,7 @@ void CSVReporter::PrintRunData(const Run& run) {
|
|||
}
|
||||
Out << ",";
|
||||
if (!run.report_label.empty()) {
|
||||
// Field with embedded double-quote characters must be doubled and the field
|
||||
// delimited with double-quotes.
|
||||
std::string label = run.report_label;
|
||||
ReplaceAll(&label, "\"", "\"\"");
|
||||
Out << "\"" << label << "\"";
|
||||
Out << CsvEscape(run.report_label);
|
||||
}
|
||||
Out << ",,"; // for error_occurred and error_message
|
||||
|
||||
|
|
|
@ -32,30 +32,48 @@ namespace benchmark {
|
|||
|
||||
namespace {
|
||||
|
||||
std::string StrEscape(const std::string & s) {
|
||||
std::string tmp;
|
||||
tmp.reserve(s.size());
|
||||
for (char c : s) {
|
||||
switch (c) {
|
||||
case '\b': tmp += "\\b"; break;
|
||||
case '\f': tmp += "\\f"; break;
|
||||
case '\n': tmp += "\\n"; break;
|
||||
case '\r': tmp += "\\r"; break;
|
||||
case '\t': tmp += "\\t"; break;
|
||||
case '\\': tmp += "\\\\"; break;
|
||||
case '"' : tmp += "\\\""; break;
|
||||
default : tmp += c; break;
|
||||
}
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
std::string FormatKV(std::string const& key, std::string const& value) {
|
||||
return StrFormat("\"%s\": \"%s\"", key.c_str(), value.c_str());
|
||||
return StrFormat("\"%s\": \"%s\"", StrEscape(key).c_str(), StrEscape(value).c_str());
|
||||
}
|
||||
|
||||
std::string FormatKV(std::string const& key, const char* value) {
|
||||
return StrFormat("\"%s\": \"%s\"", key.c_str(), value);
|
||||
return StrFormat("\"%s\": \"%s\"", StrEscape(key).c_str(), StrEscape(value).c_str());
|
||||
}
|
||||
|
||||
std::string FormatKV(std::string const& key, bool value) {
|
||||
return StrFormat("\"%s\": %s", key.c_str(), value ? "true" : "false");
|
||||
return StrFormat("\"%s\": %s", StrEscape(key).c_str(), value ? "true" : "false");
|
||||
}
|
||||
|
||||
std::string FormatKV(std::string const& key, int64_t value) {
|
||||
std::stringstream ss;
|
||||
ss << '"' << key << "\": " << value;
|
||||
ss << '"' << StrEscape(key) << "\": " << value;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string FormatKV(std::string const& key, double value) {
|
||||
std::stringstream ss;
|
||||
ss << '"' << key << "\": ";
|
||||
ss << '"' << StrEscape(key) << "\": ";
|
||||
|
||||
if (std::isnan(value))
|
||||
ss << "NaN";
|
||||
ss << (value < 0 ? "-" : "") << "NaN";
|
||||
else if (std::isinf(value))
|
||||
ss << (value < 0 ? "-" : "") << "Infinity";
|
||||
else {
|
||||
|
@ -88,12 +106,7 @@ bool JSONReporter::ReportContext(const Context& context) {
|
|||
out << indent << FormatKV("host_name", context.sys_info.name) << ",\n";
|
||||
|
||||
if (Context::executable_name) {
|
||||
// windows uses backslash for its path separator,
|
||||
// which must be escaped in JSON otherwise it blows up conforming JSON
|
||||
// decoders
|
||||
std::string executable_name = Context::executable_name;
|
||||
ReplaceAll(&executable_name, "\\", "\\\\");
|
||||
out << indent << FormatKV("executable", executable_name) << ",\n";
|
||||
out << indent << FormatKV("executable", Context::executable_name) << ",\n";
|
||||
}
|
||||
|
||||
CPUInfo const& info = context.cpu_info;
|
||||
|
|
|
@ -160,15 +160,6 @@ std::string StrFormat(const char* format, ...) {
|
|||
return tmp;
|
||||
}
|
||||
|
||||
void ReplaceAll(std::string* str, const std::string& from,
|
||||
const std::string& to) {
|
||||
std::size_t start = 0;
|
||||
while ((start = str->find(from, start)) != std::string::npos) {
|
||||
str->replace(start, from.length(), to);
|
||||
start += to.length();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef BENCHMARK_STL_ANDROID_GNUSTL
|
||||
/*
|
||||
* GNU STL in Android NDK lacks support for some C++11 functions, including
|
||||
|
|
|
@ -37,9 +37,6 @@ inline std::string StrCat(Args&&... args) {
|
|||
return ss.str();
|
||||
}
|
||||
|
||||
void ReplaceAll(std::string* str, const std::string& from,
|
||||
const std::string& to);
|
||||
|
||||
#ifdef BENCHMARK_STL_ANDROID_GNUSTL
|
||||
/*
|
||||
* GNU STL in Android NDK lacks support for some C++11 functions, including
|
||||
|
|
|
@ -704,6 +704,37 @@ ADD_CASES(
|
|||
"manual_time_stddev\",%csv_report$"},
|
||||
{"^\"BM_UserStats/iterations:5/repeats:3/manual_time_\",%csv_report$"}});
|
||||
|
||||
// ========================================================================= //
|
||||
// ------------------------- Testing StrEscape JSON ------------------------ //
|
||||
// ========================================================================= //
|
||||
#if 0 // enable when csv testing code correctly handles multi-line fields
|
||||
void BM_JSON_Format(benchmark::State& state) {
|
||||
state.SkipWithError("val\b\f\n\r\t\\\"with\"es,capes");
|
||||
for (auto _ : state) {
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_JSON_Format);
|
||||
ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_JSON_Format\",$"},
|
||||
{"\"run_name\": \"BM_JSON_Format\",$", MR_Next},
|
||||
{"\"run_type\": \"iteration\",$", MR_Next},
|
||||
{"\"repetitions\": 0,$", MR_Next},
|
||||
{"\"repetition_index\": 0,$", MR_Next},
|
||||
{"\"threads\": 1,$", MR_Next},
|
||||
{"\"error_occurred\": true,$", MR_Next},
|
||||
{R"("error_message": "val\\b\\f\\n\\r\\t\\\\\\"with\\"es,capes",$)", MR_Next}});
|
||||
#endif
|
||||
// ========================================================================= //
|
||||
// -------------------------- Testing CsvEscape ---------------------------- //
|
||||
// ========================================================================= //
|
||||
|
||||
void BM_CSV_Format(benchmark::State& state) {
|
||||
state.SkipWithError("\"freedom\"");
|
||||
for (auto _ : state) {
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_CSV_Format);
|
||||
ADD_CASES(TC_CSVOut, {{"^\"BM_CSV_Format\",,,,,,,,true,\"\"\"freedom\"\"\"$"}});
|
||||
|
||||
// ========================================================================= //
|
||||
// --------------------------- TEST CASES END ------------------------------ //
|
||||
// ========================================================================= //
|
||||
|
|
Loading…
Reference in New Issue