Demonstrating how to use formatData to allow a custom formatter to accumulate data (such as a list of messages to generate JSON) across the entire test run.
#define FCF_TEST_IMPLEMENTATION
#include <fcfTest/test.hpp>
#include <iostream>
#include <vector>
struct Message {
unsigned int code;
std::string message;
};
struct Messages {
typedef std::vector<Message> Items;
bool defaultStream; // flag indicating if this is the default stream
Items items; // message history
};
// Function to convert message history to json
std::string toJson(const Messages& a_messages){
auto escape = [](const std::string& a_source) {
std::string result;
result.reserve(a_source.size());
for (char c : a_source) {
switch (c) {
case '"': result += "\\\""; break;
case '\\': result += "\\\\"; break;
case '\n': result += "\\n"; break;
case '\r': result += "\\r"; break;
case '\t': result += "\\t"; break;
case '\x1b': result += "\\u001b"; break;
default: result += c; break;
}
}
return result;
};
std::string result;
result += "[";
for(const Message& message : a_messages.items) {
if (result.length() > 1){
result += ",";
}
result += "\n { \"code\": " + std::to_string(message.code) + ", \"message\": \"" + escape(message.message) + "\"}";
}
result += "\n]\n";
return result;
}
FCF_TEST_DEFINE("FormatDataDemo", "Logger", "AccumulatorTest") {
fcf::NTest::log() << "First log" << std::endl;
fcf::NTest::log() << "Second log" << std::endl;
FCF_TEST(true);
}
int main(int a_argc, char* a_argv[]) {
fcf::NTest::Logger& logger = fcf::NTest::logger();
// Setup Format with a dataFactory to initialize our state
fcf::NTest::Logger::Format format;
format.name = "my-json";
// Set the default factory for user data for our formatter
format.dataFactory = []( fcf::NTest::Logger&, fcf::NTest::Logger::OutputTarget&) {
return fcf::NTest::SharedPtrAny::make<Messages>(
Messages{false, {}}
);
};
// Define a custom formatter that will generate JSON output
format.handler = []( fcf::NTest::Logger&, fcf::NTest::Logger::MessageContext& a_context) {
// Get user data associated with this formatter and output stream
Messages* messages = a_context.data->cast<Messages>();
// save the message to our container
messages->items.push_back({ a_context.category, a_context.message });
if (a_context.category == fcf::NTest::LMC_ROOT_START){
// If this is the main output stream, output the test start message
if (messages->defaultStream) {
a_context.message = "Testing has started. Please wait for it to complete.\n\n";
// Reset the system message flag so that data
// in a_context.message is sent to the output stream
a_context.system = false;
}
} else if (a_context.category == fcf::NTest::LMC_ROOT_END){
// Process the message about the completion of all tests.
// Generate JSON with information about all messages
a_context.message = toJson(*messages);
// Reset the system message flag so that data
// in a_context.message is sent to the output stream
a_context.system = false;
// Clear history upon completion of testing
messages->items.clear();
} else {
// For all other messages, we set
// the system flag to true so that data is not output
a_context.system = true;
}
};
// Register the format
logger.appendFormat(format);
// Set the target to use this format
fcf::NTest::Logger::OutputTarget target;
target.name = "default";
target.stream = &std::cout;
target.format = "my-json";
// Set custom data for our formatter in the current output stream.
// Since data will be set, the default factory will not be called.
target.formatData["my-json"] = fcf::NTest::SharedPtrAny::make<Messages>(
Messages{true, {}}
);
logger.targets({target});
bool error = false;
fcf::NTest::cmdRun(a_argc, a_argv, fcf::NTest::CRM_RUN, &error);
return error ? 1 : 0;
}
Output:
Testing has started. Please wait for it to complete.
[
{ "code": 2147549185, "message": ""},
{ "code": 2147614721, "message": ""},
{ "code": 131074, "message": "Performing the test: \"FormatDataDemo\" -> \"Logger\" -> \"AccumulatorTest\" ...\n"},
{ "code": 524288, "message": " > First log\n"},
{ "code": 524288, "message": " > Second log\n"},
{ "code": 262145, "message": " \u001b[1;32m[SUCCESS]\u001b[0m Test completed successfully (0.000`015`739 sec)\n"},
{ "code": 2147614723, "message": ""},
{ "code": 65544, "message": "\n"},
{ "code": 65539, "message": "\u001b[1;32m[SUCCESS]\u001b[0m All tests were completed.\n"},
{ "code": 65541, "message": "Tests: 1 passed, 0 failed, 0 skipped, 1 total\n"},
{ "code": 65542, "message": "Duration: 0.000`015`739 sec\n"},
{ "code": 2147549186, "message": ""}
]
If you run the application with file output:
app --test-file-my-json "output.json"
The application will create the file output.json
[
{ "code": 2147549185, "message": ""},
{ "code": 2147614721, "message": ""},
{ "code": 131074, "message": "Performing the test: \"FormatDataDemo\" -> \"Logger\" -> \"AccumulatorTest\" ...\n"},
{ "code": 524288, "message": " > First log\n"},
{ "code": 524288, "message": " > Second log\n"},
{ "code": 262145, "message": " \u001b[1;32m[SUCCESS]\u001b[0m Test completed successfully (0.000`015`739 sec)\n"},
{ "code": 2147614723, "message": ""},
{ "code": 65544, "message": "\n"},
{ "code": 65539, "message": "\u001b[1;32m[SUCCESS]\u001b[0m All tests were completed.\n"},
{ "code": 65541, "message": "Tests: 1 passed, 0 failed, 0 skipped, 1 total\n"},
{ "code": 65542, "message": "Duration: 0.000`015`739 sec\n"},
{ "code": 2147549186, "message": ""}
]
Note that the greeting is absent in this file, because the data for the formatter is created by the fcf::NTest::Logger::FormatSettings::dataFactory factory.