| #include "src/util.h" |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <sstream> |
| #include <thread> |
| |
| #include <application.h> |
| |
| #include "nugget_tools.h" |
| #include "nugget/app/protoapi/control.pb.h" |
| #include "nugget/app/protoapi/header.pb.h" |
| |
| #ifndef CONFIG_NO_UART |
| #include "src/lib/inc/crc_16.h" |
| #endif // CONFIG_NO_UART |
| |
| #ifdef ANDROID |
| #define FLAGS_util_use_ahdlc false |
| #define FLAGS_util_print_uart false |
| #else |
| #include "gflags/gflags.h" |
| |
| DEFINE_bool(util_use_ahdlc, false, "Use aHDLC over UART instead of SPI."); |
| DEFINE_bool(util_print_uart, false, "Print the output of citadel UART."); |
| DEFINE_string(util_verbosity, "ERROR", "One of SILENT, CRITICAL, ERROR, WARNING, or INFO."); |
| #endif // ANDROID |
| |
| using nugget::app::protoapi::APImessageID; |
| using nugget::app::protoapi::ControlRequest; |
| using nugget::app::protoapi::ControlRequestType; |
| using nugget::app::protoapi::Notice; |
| using std::chrono::duration; |
| using std::chrono::duration_cast; |
| using std::chrono::high_resolution_clock; |
| using std::chrono::microseconds; |
| |
| namespace test_harness { |
| namespace { |
| |
| int GetVerbosityFromFlag() { |
| #ifdef ANDROID |
| return TestHarness::ERROR; |
| #else |
| std::string upper_case_flag; |
| upper_case_flag.reserve(FLAGS_util_verbosity.size()); |
| std::transform(FLAGS_util_verbosity.begin(), FLAGS_util_verbosity.end(), |
| std::back_inserter(upper_case_flag), toupper); |
| |
| if (upper_case_flag == "SILENT") |
| return TestHarness::SILENT; |
| if (upper_case_flag == "CRITICAL") |
| return TestHarness::CRITICAL; |
| if (upper_case_flag == "WARNING") |
| return TestHarness::WARNING; |
| if (upper_case_flag == "INFO") |
| return TestHarness::INFO; |
| |
| // Default to ERROR. |
| return TestHarness::ERROR; |
| #endif // ANDROID |
| } |
| |
| #ifndef ANDROID |
| string find_uart(int verbosity) { |
| constexpr char dir_path[] = "/dev/"; |
| auto dir = opendir(dir_path); |
| if (!dir) { |
| return ""; |
| } |
| |
| string manual_serial_no = nugget_tools::GetCitadelUSBSerialNo(); |
| const char prefix[] = "ttyUltraTarget_"; |
| |
| string return_value = ""; |
| if (manual_serial_no.empty()) { |
| const size_t prefix_length = sizeof(prefix) / sizeof(prefix[0]) - 1; |
| while (auto listing = readdir(dir)) { |
| // The following is always true so it is not checked: |
| // sizeof(listing->d_name) >= sizeof(prefix) |
| if (std::equal(prefix, prefix + prefix_length, listing->d_name)) { |
| return_value = string(dir_path) + listing->d_name; |
| break; |
| } |
| } |
| } else { |
| return_value = string(dir_path) + prefix + manual_serial_no; |
| } |
| |
| if (verbosity >= TestHarness::VerbosityLevels::INFO) { |
| if (return_value.empty()) { |
| std::cout << "UltraDebug UART not found" << std::endl; |
| } else { |
| std::cout << "USING: " << return_value << std::endl; |
| } |
| } |
| |
| closedir(dir); |
| return return_value; |
| } |
| #endif // ANDROID |
| |
| } // namespace |
| |
| std::unique_ptr<TestHarness> TestHarness::MakeUnique() { |
| return std::unique_ptr<TestHarness>(new TestHarness()); |
| } |
| |
| TestHarness::TestHarness() : verbosity(GetVerbosityFromFlag()), |
| output_buffer(PROTO_BUFFER_MAX_LEN, 0), |
| input_buffer(PROTO_BUFFER_MAX_LEN, 0), tty_fd(-1) { |
| #ifdef CONFIG_NO_UART |
| Init(nullptr); |
| #else |
| string path = find_uart(verbosity); |
| Init(path.c_str()); |
| #endif // CONFIG_NO_UART |
| } |
| |
| TestHarness::TestHarness(const char* path) : |
| verbosity(ERROR), output_buffer(PROTO_BUFFER_MAX_LEN, 0), |
| input_buffer(PROTO_BUFFER_MAX_LEN, 0), tty_fd(-1) { |
| Init(path); |
| } |
| |
| TestHarness::~TestHarness() { |
| #ifndef CONFIG_NO_UART |
| if (verbosity >= INFO) { |
| std::cout << "CLOSING TEST HARNESS" << std::endl; |
| } |
| if (ttyState()) { |
| auto temp = tty_fd; |
| tty_fd = -1; |
| close(temp); |
| } |
| if (print_uart_worker) { |
| print_uart_worker->join(); |
| print_uart_worker = nullptr; |
| } |
| #endif // CONFIG_NO_UART |
| |
| if (client) { |
| client->Close(); |
| client = unique_ptr<nos::NuggetClientInterface >(); |
| } |
| } |
| |
| bool TestHarness::ttyState() const { |
| return tty_fd != -1; |
| } |
| |
| int TestHarness::getVerbosity() const { |
| return verbosity; |
| } |
| |
| int TestHarness::setVerbosity(int v) { |
| int temp = verbosity; |
| verbosity = v; |
| return temp; |
| } |
| |
| void TestHarness::flushConsole() { |
| #ifndef CONFIG_NO_UART |
| while (ReadLineUntilBlock().size() > 0) {} |
| #endif // CONFIG_NO_UART |
| } |
| |
| bool TestHarness::RebootNugget() { |
| return nugget_tools::RebootNugget(client.get()); |
| } |
| |
| void print_bin(std::ostream &out, uint8_t c) { |
| if (c == '\\') { |
| out << "\\\\"; |
| } else if (isprint(c)) { |
| out << c; |
| } else if (c < 16) { |
| out << "\\x0" << std::hex << (uint32_t) c; |
| } else { |
| out << "\\x" << std::hex << (uint32_t) c; |
| } |
| } |
| |
| int TestHarness::SendData(const raw_message& msg) { |
| #ifdef CONFIG_NO_UART |
| return SendSpi(msg); |
| #else |
| return FLAGS_util_use_ahdlc ? SendAhdlc(msg) : SendSpi(msg); |
| #endif // ANDROID |
| } |
| |
| #ifndef CONFIG_NO_UART |
| int TestHarness::SendAhdlc(const raw_message& msg) { |
| if (EncodeNewFrame(&encoder) != AHDLC_OK) { |
| return TRANSPORT_ERROR; |
| } |
| |
| if (EncodeAddByteToFrameBuffer(&encoder, (uint8_t) (msg.type >> 8)) |
| != AHDLC_OK || EncodeAddByteToFrameBuffer(&encoder, (uint8_t) msg.type) |
| != AHDLC_OK) { |
| return TRANSPORT_ERROR; |
| } |
| if (EncodeBuffer(&encoder, msg.data, msg.data_len) != AHDLC_OK) { |
| return TRANSPORT_ERROR; |
| } |
| |
| BlockingWrite((const char*) encoder.frame_buffer, |
| encoder.frame_info.buffer_index); |
| return NO_ERROR; |
| } |
| #endif // CONFIG_NO_UART |
| |
| int TestHarness::SendSpi(const raw_message& msg) { |
| if (!client) { |
| client = nugget_tools::MakeNuggetClient(); |
| client->Open(); |
| if (!client->IsOpen()) { |
| FatalError("Unable to connect"); |
| } |
| } |
| |
| input_buffer.resize(msg.data_len + sizeof(msg.type)); |
| input_buffer[0] = msg.type >> 8; |
| input_buffer[1] = (uint8_t) msg.type; |
| std::copy(msg.data, msg.data + msg.data_len, input_buffer.begin() + 2); |
| |
| if (verbosity >= INFO) { |
| std::cout << "SPI_TX: "; |
| for (char c : input_buffer) { |
| if (c == '\n') { |
| std::cout << "\nSPI_TX: "; |
| } else { |
| print_bin(std::cout, c); |
| } |
| } |
| std::cout << "\n"; |
| std::cout.flush(); |
| } |
| |
| output_buffer.resize(output_buffer.capacity()); |
| return client->CallApp(APP_ID_PROTOBUF, msg.type, input_buffer, |
| &output_buffer); |
| } |
| |
| int TestHarness::SendOneofProto(uint16_t type, uint16_t subtype, |
| const google::protobuf::Message& message) { |
| test_harness::raw_message msg; |
| msg.type = type; |
| int msg_size = message.ByteSize(); |
| if (msg_size + 2 > (int) PROTO_BUFFER_MAX_LEN) { |
| return OVERFLOW_ERROR; |
| } |
| msg.data[0] = subtype >> 8; |
| msg.data[1] = (uint8_t) subtype; |
| |
| msg.data_len = (uint16_t) (msg_size + 2); |
| if (!message.SerializeToArray(msg.data + 2, msg_size)) { |
| return SERIALIZE_ERROR; |
| } |
| |
| auto return_value = SendData(msg); |
| return return_value; |
| } |
| |
| int TestHarness::SendProto(uint16_t type, |
| const google::protobuf::Message& message) { |
| test_harness::raw_message msg; |
| msg.type = type; |
| int msg_size = message.ByteSize(); |
| if (msg_size > (int) (PROTO_BUFFER_MAX_LEN - 2)) { |
| return OVERFLOW_ERROR; |
| } |
| msg.data_len = (uint16_t) msg_size; |
| if (!message.SerializeToArray(msg.data, msg.data_len)) { |
| return SERIALIZE_ERROR; |
| } |
| |
| auto return_value = SendData(msg); |
| return return_value; |
| } |
| |
| #ifndef CONFIG_NO_UART |
| int TestHarness::GetAhdlc(raw_message* msg, microseconds timeout) { |
| if (verbosity >= INFO) { |
| std::cout << "RX: "; |
| } |
| size_t read_count = 0; |
| while (true) { |
| uint8_t read_value; |
| auto start = high_resolution_clock::now(); |
| while (read(tty_fd, &read_value, 1) <= 0) { |
| if (timeout >= microseconds(0) && |
| duration_cast<microseconds>(high_resolution_clock::now() - start) > |
| microseconds(timeout)) { |
| if (verbosity >= INFO) { |
| std::cout << "\n"; |
| std::cout.flush(); |
| } |
| return TIMEOUT; |
| } |
| } |
| ++read_count; |
| |
| ahdlc_op_return return_value = |
| DecodeFrameByte(&decoder, read_value); |
| |
| if (verbosity >= INFO) { |
| if (read_value == '\n') { |
| std::cout << "\nRX: "; |
| } else { |
| print_bin(std::cout, read_value); |
| } |
| std::cout.flush(); |
| } |
| |
| if (read_count > 7) { |
| if (return_value == AHDLC_COMPLETE || |
| decoder.decoder_state == DECODE_COMPLETE_BAD_CRC) { |
| if (decoder.frame_info.buffer_index < 2) { |
| if (verbosity >= ERROR) { |
| std::cout << "\n"; |
| std::cout << "UNDERFLOW ERROR\n"; |
| std::cout.flush(); |
| } |
| return TRANSPORT_ERROR; |
| } |
| |
| msg->type = (decoder.pdu_buffer[0] << 8) | decoder.pdu_buffer[1]; |
| msg->data_len = decoder.frame_info.buffer_index - 2; |
| std::copy(decoder.pdu_buffer + 2, |
| decoder.pdu_buffer + decoder.frame_info.buffer_index, |
| msg->data); |
| |
| if (verbosity >= INFO) { |
| std::cout << "\n"; |
| if (return_value == AHDLC_COMPLETE) { |
| std::cout << "GOOD CRC\n"; |
| } else { |
| std::cout << "BAD CRC\n"; |
| } |
| std::cout.flush(); |
| } |
| return NO_ERROR; |
| } else if (decoder.decoder_state == DECODE_COMPLETE_BAD_CRC) { |
| if (verbosity >= ERROR) { |
| std::cout << "\n"; |
| std::cout << "AHDLC BAD CRC\n"; |
| std::cout.flush(); |
| } |
| return TRANSPORT_ERROR; |
| } else if (decoder.frame_info.buffer_index >= PROTO_BUFFER_MAX_LEN) { |
| if (AhdlcDecoderInit(&decoder, CRC16, NULL) != AHDLC_OK) { |
| FatalError("AhdlcDecoderInit()"); |
| } |
| if (verbosity >= ERROR) { |
| std::cout << "\n"; |
| std::cout.flush(); |
| std::cout << "OVERFLOW ERROR\n"; |
| } |
| return OVERFLOW_ERROR; |
| } |
| } |
| } |
| } |
| #endif // CONFIG_NO_UART |
| |
| int TestHarness::GetSpi(raw_message* msg, microseconds timeout) { |
| if (timeout > microseconds(0)) {} // Prevent unused parameter warning. |
| if (output_buffer.size() < 2) { |
| return GENERIC_ERROR; |
| } |
| |
| if (verbosity >= INFO) { |
| std::cout << "SPI_RX: "; |
| for (char c : output_buffer) { |
| if (c == '\n') { |
| std::cout << "\nSPI_RX: "; |
| } else { |
| print_bin(std::cout, c); |
| } |
| } |
| std::cout << "\n"; |
| std::cout.flush(); |
| } |
| |
| msg->type = (output_buffer[0] << 8) | output_buffer[1]; |
| msg->data_len = output_buffer.size() - sizeof(msg->type); |
| std::copy(output_buffer.begin() + 2, output_buffer.end(), msg->data); |
| output_buffer.resize(0); |
| return NO_ERROR; |
| } |
| |
| int TestHarness::GetData(raw_message* msg, microseconds timeout) { |
| #ifdef CONFIG_NO_UART |
| return GetSpi(msg, timeout); |
| #else |
| return FLAGS_util_use_ahdlc ? GetAhdlc(msg, timeout) : GetSpi(msg, timeout); |
| #endif // CONFIG_NO_UART |
| } |
| |
| void TestHarness::Init(const char* path) { |
| if (verbosity >= INFO) { |
| std::cout << "init() start\n"; |
| std::cout.flush(); |
| } |
| |
| #ifndef CONFIG_NO_UART |
| if (FLAGS_util_use_ahdlc) { // AHDLC UART transport. |
| encoder.buffer_len = output_buffer.size(); |
| encoder.frame_buffer = output_buffer.data(); |
| if (ahdlcEncoderInit(&encoder, CRC16) != AHDLC_OK) { |
| FatalError("ahdlcEncoderInit()"); |
| } |
| |
| decoder.buffer_len = input_buffer.size(); |
| decoder.pdu_buffer = input_buffer.data(); |
| if (AhdlcDecoderInit(&decoder, CRC16, NULL) != AHDLC_OK) { |
| FatalError("AhdlcDecoderInit()"); |
| } |
| } |
| |
| // Setup UART |
| errno = 0; |
| tty_fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY); |
| if (errno != 0) { |
| perror("ERROR open()"); |
| FatalError("Cannot open debug UART to Citadel chip. Is UltraDebug board connected?"); |
| } |
| errno = 0; |
| |
| if (!isatty(tty_fd)) { |
| FatalError("Path is not a tty"); |
| } |
| |
| if (tcgetattr(tty_fd, &tty_state)) { |
| perror("ERROR tcgetattr()"); |
| FatalError(""); |
| } |
| |
| if (cfsetospeed(&tty_state, B115200) || |
| cfsetispeed(&tty_state, B115200)) { |
| perror("ERROR cfsetospeed()"); |
| FatalError(""); |
| } |
| |
| tty_state.c_cc[VMIN] = 0; |
| tty_state.c_cc[VTIME] = 0; |
| |
| tty_state.c_iflag = tty_state.c_iflag & ~(IXON | ISTRIP | INPCK | PARMRK | |
| INLCR | ICRNL | BRKINT | IGNBRK); |
| tty_state.c_iflag = 0; |
| tty_state.c_oflag = 0; |
| tty_state.c_lflag = tty_state.c_lflag & ~(ECHO | ECHONL | ICANON | IEXTEN | |
| ISIG); |
| tty_state.c_cflag = (tty_state.c_cflag & ~(CSIZE | PARENB)) | CS8; |
| |
| if (tcsetattr(tty_fd, TCSAFLUSH, &tty_state)) { |
| perror("ERROR tcsetattr()"); |
| FatalError(""); |
| } |
| #else |
| if (path) {} // Prevent the unused variable warning for path. |
| #endif // CONFIG_NO_UART |
| |
| // libnos SPI transport is initialized on first use for interoperability. |
| |
| if (verbosity >= INFO) { |
| std::cout << "init() finish\n"; |
| std::cout.flush(); |
| } |
| |
| if (FLAGS_util_print_uart) { |
| print_uart_worker = std::unique_ptr<std::thread>(new std::thread( |
| [](TestHarness* harness){ |
| if (harness->getVerbosity() >= INFO) { |
| std::cout << "Citadel UART printing enabled!\n"; |
| std::cout.flush(); |
| } |
| while(harness->ttyState()) { |
| harness->PrintUntilClosed(); |
| } |
| if (harness->getVerbosity() >= INFO) { |
| std::cout << "Citadel UART printing disabled!\n"; |
| std::cout.flush(); |
| } |
| }, this)); |
| } |
| } |
| |
| bool TestHarness::UsingSpi() const { |
| return !FLAGS_util_use_ahdlc; |
| } |
| |
| #ifndef CONFIG_NO_UART |
| bool TestHarness::SwitchFromConsoleToProtoApi() { |
| if (verbosity >= INFO) { |
| std::cout << "SwitchFromConsoleToProtoApi() start\n"; |
| std::cout.flush(); |
| } |
| |
| if (!ttyState()) { return false; } |
| |
| ReadUntil(BYTE_TIME * 1024); |
| |
| BlockingWrite("version\n", 1); |
| |
| ReadUntil(BYTE_TIME * 1024); |
| |
| BlockingWrite("\n", 1); |
| |
| while (ReadLineUntilBlock() != "> ") {} |
| |
| const char command[] = "protoapi uart on 1\n"; |
| BlockingWrite(command, sizeof(command) - 1); |
| |
| ReadUntil(BYTE_TIME * 1024); |
| |
| if (verbosity >= INFO) { |
| std::cout << "SwitchFromConsoleToProtoApi() finish\n"; |
| std::cout.flush(); |
| } |
| |
| return true; |
| } |
| |
| bool TestHarness::SwitchFromProtoApiToConsole(raw_message* out_msg) { |
| if (verbosity >= INFO) { |
| std::cout << "SwitchFromProtoApiToConsole() start\n"; |
| std::cout.flush(); |
| } |
| |
| ControlRequest controlRequest; |
| controlRequest.set_type(ControlRequestType::REVERT_TO_CONSOLE); |
| string line; |
| controlRequest.SerializeToString(&line); |
| |
| raw_message msg; |
| msg.type = APImessageID::CONTROL_REQUEST; |
| |
| std::copy(line.begin(), line.end(), msg.data); |
| msg.data_len = line.size(); |
| |
| if (SendAhdlc(msg) != error_codes::NO_ERROR) { |
| return false; |
| } |
| |
| |
| if (GetAhdlc(&msg, 4096 * BYTE_TIME) == NO_ERROR && |
| msg.type == APImessageID::NOTICE) { |
| Notice message; |
| message.ParseFromArray((char *) msg.data, msg.data_len); |
| if (verbosity >= INFO) { |
| std::cout << message.DebugString() << std::endl; |
| } |
| } else { |
| if (verbosity >= ERROR) { |
| std::cout << "Receive Error" << std::endl; |
| std::cout.flush(); |
| } |
| return false; |
| } |
| |
| ReadUntil(BYTE_TIME * 4096); |
| |
| if (verbosity >= INFO) { |
| std::cout << "SwitchFromProtoApiToConsole() finish\n"; |
| std::cout.flush(); |
| } |
| if (out_msg) { |
| *out_msg = std::move(msg); |
| } |
| return true; |
| } |
| #endif // CONFIG_NO_UART |
| |
| void TestHarness::BlockingWrite(const char* data, size_t len) { |
| if (verbosity >= INFO) { |
| std::cout << "TX: "; |
| for (size_t i = 0; i < len; ++i) { |
| uint8_t value = data[i]; |
| if (value == '\n') { |
| std::cout << "\nTX: "; |
| } else { |
| print_bin(std::cout, value); |
| } |
| } |
| std::cout << "\n"; |
| std::cout.flush(); |
| } |
| |
| size_t loc = 0; |
| while (loc < len) { |
| errno = 0; |
| int return_value = write(tty_fd, data + loc, len - loc); |
| if (verbosity >= CRITICAL && errno != 0){ |
| perror("ERROR write()"); |
| } |
| if (return_value < 0) { |
| if (errno != EWOULDBLOCK && errno != EAGAIN) { |
| FatalError("write(tty_fd,...)"); |
| } else { |
| std::this_thread::sleep_for(BYTE_TIME); |
| } |
| } else { |
| loc += return_value; |
| } |
| } |
| } |
| |
| string TestHarness::ReadLineUntilBlock() { |
| if (!ttyState()) { |
| return ""; |
| } |
| |
| string line = ""; |
| line.reserve(128); |
| char read_value = ' '; |
| std::stringstream ss; |
| |
| auto last_success = high_resolution_clock::now(); |
| while (true) { |
| errno = 0; |
| while (read_value != '\n' && read(tty_fd, &read_value, 1) > 0) { |
| last_success = high_resolution_clock::now(); |
| print_bin(ss, read_value); |
| line.append(1, read_value); |
| } |
| if (verbosity >= CRITICAL && errno != 0) { |
| perror("ERROR read()"); |
| } |
| |
| /* If there wasn't anything to read yet, or the end of line is reached |
| * there is no need to continue. */ |
| if (read_value == '\n' || line.size() == 0 || |
| duration_cast<microseconds>(high_resolution_clock::now() - |
| last_success) > 4 * BYTE_TIME) { |
| break; |
| } |
| |
| /* Wait for at least one bit time before checking read() again. */ |
| std::this_thread::sleep_for(BIT_TIME); |
| } |
| |
| if (verbosity >= INFO && line.size() > 0) { |
| std::cout << "RX: " << ss.str() <<"\n"; |
| std::cout.flush(); |
| } |
| return line; |
| } |
| |
| string TestHarness::ReadUntil(microseconds end) { |
| #ifdef CONFIG_NO_UART |
| std::this_thread::sleep_for(end); |
| return ""; |
| #else |
| if (!ttyState()) { |
| return ""; |
| } |
| |
| char read_value = ' '; |
| bool first = true; |
| std::stringstream ss; |
| |
| auto start = high_resolution_clock::now(); |
| while (duration_cast<microseconds>(high_resolution_clock::now() - |
| start) < end) { |
| errno = 0; |
| while (read(tty_fd, &read_value, 1) > 0) { |
| ss << read_value; |
| if (verbosity >= INFO) { |
| if (first) { |
| first = false; |
| std::cout << "RX: "; |
| print_bin(std::cout, read_value); |
| } else if (read_value == '\n') { |
| std::cout << "\n"; |
| std::cout.flush(); |
| std::cout << "RX: "; |
| } else { |
| print_bin(std::cout, read_value); |
| } |
| } |
| } |
| if (verbosity >= CRITICAL && errno != 0) { |
| perror("ERROR read()"); |
| } |
| |
| /* Wait for at least one bit time before checking read() again. */ |
| std::this_thread::sleep_for(BIT_TIME); |
| } |
| if (verbosity >= INFO && !first) { |
| std::cout << "\n"; |
| std::cout.flush(); |
| } |
| |
| return ss.str(); |
| #endif // CONFIG_NO_UART |
| } |
| |
| void TestHarness::PrintUntilClosed() { |
| #ifdef CONFIG_NO_UART |
| #else |
| if (!ttyState()) { |
| return; |
| } |
| |
| char read_value = ' '; |
| bool first = true; |
| std::stringstream ss("UART: "); |
| |
| while (ttyState()) { |
| errno = 0; |
| while (read(tty_fd, &read_value, 1) > 0) { |
| first = false; |
| if (read_value == '\r') |
| continue; |
| if (read_value == '\n') { |
| ss << "\n"; |
| std::cout.flush(); |
| std::cout << ss.str(); |
| std::cout.flush(); |
| ss.str(""); |
| ss << "UART: "; |
| } else { |
| print_bin(ss, read_value); |
| } |
| } |
| if (verbosity >= CRITICAL && errno != 0 && errno != EAGAIN) { |
| if (errno != EBADF) { |
| perror("ERROR read()"); |
| } |
| break; |
| } |
| |
| /* Wait for at least one bit time before checking read() again. */ |
| std::this_thread::sleep_for(BIT_TIME); |
| } |
| if (!first) { |
| ss << "\n"; |
| std::cout.flush(); |
| std::cout << ss.str(); |
| std::cout.flush(); |
| } |
| #endif // CONFIG_NO_UART |
| } |
| |
| |
| void FatalError(const string& msg) { |
| std::cerr << "FATAL ERROR: " << msg << std::endl; |
| exit(1); |
| } |
| |
| const char* error_codes_name(int code) { |
| switch (code) { |
| case error_codes::NO_ERROR: |
| return "NO_ERROR"; |
| case error_codes::GENERIC_ERROR: |
| return "GENERIC_ERROR"; |
| case error_codes::TIMEOUT: |
| return "TIMEOUT"; |
| case error_codes::TRANSPORT_ERROR: |
| return "TRANSPORT_ERROR"; |
| case error_codes::OVERFLOW_ERROR: |
| return "OVERFLOW_ERROR"; |
| case error_codes::SERIALIZE_ERROR: |
| return "SERIALIZE_ERROR"; |
| default: |
| return "unknown"; |
| } |
| } |
| |
| } // namespace test_harness |