588 lines
20 KiB
C++
588 lines
20 KiB
C++
// Copyright (c) 2015 Artyom Beilis (Tonkikh)
|
|
// Copyright (c) 2020 - 2021 Alexander Grund
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// https://www.boost.org/LICENSE_1_0.txt
|
|
|
|
#ifndef _SCL_SECURE_NO_WARNINGS
|
|
// Call to 'std::copy_n' with parameters that may be unsafe
|
|
#define _SCL_SECURE_NO_WARNINGS
|
|
#endif
|
|
#include <nowide/iostream.hpp>
|
|
|
|
#include <nowide/convert.hpp>
|
|
#include <nowide/utf/utf.hpp>
|
|
#include "../src/console_buffer.hpp"
|
|
#include "file_test_helpers.hpp"
|
|
#include "test.hpp"
|
|
#include "test_sets.hpp"
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <limits>
|
|
#include <queue>
|
|
#include <string>
|
|
|
|
namespace nw = nowide;
|
|
|
|
const std::string outputString =
|
|
// German umlauts (aou with 2 dots), cyrillic small m, greek small nu
|
|
"Basic letters: \xc3\xa4-\xc3\xb6-\xc3\xbc-\xd0\xbc-\xce\xbd\n"
|
|
"East Asian Letters: \xe5\x92\x8c-\xe5\xb9\xb3\n"
|
|
"Non-BMP letter: \xf0\x9d\x84\x9e\n" // musical symbol g clef
|
|
"Invalid UTF-8: `\xFF' `\xd7\xFF' `\xe5\xFF\x8c' `\xf0\x9d\x84\xFF' \n"
|
|
"\n";
|
|
|
|
const bool usesNowideRdBufIn = nw::cin.rdbuf() != std::cin.rdbuf();
|
|
const bool usesNowideRdBufOut = nw::cout.rdbuf() != std::cout.rdbuf();
|
|
|
|
#ifndef NOWIDE_TEST_INTERACTIVE
|
|
class mock_output_buffer final : public nw::detail::console_output_buffer_base
|
|
{
|
|
public:
|
|
std::wstring output;
|
|
bool succeed = true;
|
|
|
|
protected:
|
|
bool do_write(const wchar_t* buffer, std::size_t num_chars_to_write, std::size_t& num_chars_written) override
|
|
{
|
|
if(succeed)
|
|
{
|
|
output.insert(output.end(), buffer, buffer + num_chars_to_write);
|
|
num_chars_written = num_chars_to_write;
|
|
return true;
|
|
} else
|
|
{
|
|
num_chars_written = 0;
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
|
|
class mock_input_buffer final : public nw::detail::console_input_buffer_base
|
|
{
|
|
public:
|
|
std::queue<std::wstring> inputs;
|
|
|
|
protected:
|
|
bool do_read(wchar_t* buffer, std::size_t num_chars_to_read, std::size_t& num_chars_read) override
|
|
{
|
|
if(inputs.empty())
|
|
return false;
|
|
std::wstring& input = inputs.front();
|
|
num_chars_read = std::min(num_chars_to_read, input.size());
|
|
std::copy_n(input.begin(), num_chars_read, buffer);
|
|
input.erase(input.begin(), input.begin() + num_chars_read);
|
|
if(input.empty())
|
|
inputs.pop();
|
|
return true;
|
|
}
|
|
};
|
|
|
|
/// Scoped change of a streams rdbuf
|
|
struct scoped_rdbuf_change
|
|
{
|
|
std::ios& stream;
|
|
std::streambuf* orig_buf;
|
|
scoped_rdbuf_change(std::ios& stream, std::streambuf* new_buf) : stream(stream), orig_buf(stream.rdbuf(new_buf))
|
|
{}
|
|
~scoped_rdbuf_change()
|
|
{
|
|
stream.rdbuf(orig_buf);
|
|
}
|
|
};
|
|
|
|
// Macros to be used to avoid littering the code with #ifndef checks
|
|
/// Install a mock buffer into the given stream, when compiling as non-interactive
|
|
#define INSTALL_MOCK_BUF(STREAM, BUF_TYPE) \
|
|
BUF_TYPE mock_buf; \
|
|
scoped_rdbuf_change _(nw::STREAM, &mock_buf)
|
|
/// Run the given cmd(s) only when compiling as non-interactive
|
|
#define RUN_MOCKED(what) what
|
|
#else
|
|
#define INSTALL_MOCK_BUF(STREAM, BUF_TYPE)
|
|
#define RUN_MOCKED(what)
|
|
#endif
|
|
/// Assert the given condition/code only when compiling as non-interactive
|
|
#define TEST_MOCKED(what) RUN_MOCKED(TEST(what))
|
|
|
|
bool is_valid_UTF8(const std::string& s)
|
|
{
|
|
using namespace nowide::utf;
|
|
for(std::string::const_iterator it = s.begin(); it != s.end();)
|
|
{
|
|
code_point c = utf_traits<char>::decode(it, s.end());
|
|
if(!is_valid_codepoint(c))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string create_random_one_line_string(std::size_t num_chars)
|
|
{
|
|
std::string result = nw::test::create_random_data(num_chars, nowide::test::data_type::text);
|
|
// Make sure it is a single line
|
|
std::replace(result.begin(), result.end(), '\n', 'a');
|
|
return result;
|
|
}
|
|
|
|
void test_is_valid_UTF8()
|
|
{
|
|
// Sanity check of the test function
|
|
TEST(is_valid_UTF8("")); // Empty string is valid by definition
|
|
TEST(is_valid_UTF8(create_random_one_line_string(100))); // ASCII string is valid
|
|
TEST(is_valid_UTF8(roundtrip_tests[5].utf8)); // UTF-8 string is valid
|
|
TEST(!is_valid_UTF8(invalid_utf8_tests[0].utf8)); // Detect invalid
|
|
}
|
|
|
|
void test_tie()
|
|
{
|
|
TEST(nw::cin.tie() == &nw::cout);
|
|
TEST(nw::cerr.tie() == &nw::cout);
|
|
TEST((nw::cerr.flags() & std::ios_base::unitbuf) != 0);
|
|
TEST(nw::clog.tie() == nullptr);
|
|
}
|
|
|
|
void test_putback_and_get()
|
|
{
|
|
// If we are using the standard rdbuf we can only put back 1 char
|
|
// This should always work
|
|
int maxval = 15000;
|
|
for(int i = 0; i < maxval; i++)
|
|
{
|
|
char c = i % 96 + ' ';
|
|
TEST(nw::cin.putback(c));
|
|
int ci = i % 96 + ' ';
|
|
TEST_EQ(nw::cin.get(), ci);
|
|
}
|
|
|
|
INSTALL_MOCK_BUF(cin, mock_input_buffer);
|
|
if(usesNowideRdBufIn RUN_MOCKED(|| true))
|
|
{
|
|
// Test with a few small values and around power-of-2 values as buffer size doubles.
|
|
// Finally test a large value
|
|
for(const int num_putback_chars : {1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 1000})
|
|
{
|
|
const auto getChar = [&](int i) { return (i + num_putback_chars) % 96 + ' '; };
|
|
for(int i = 0; i < num_putback_chars; i++)
|
|
{
|
|
const char c = static_cast<char>(getChar(i));
|
|
TEST(nw::cin.putback(c));
|
|
}
|
|
for(int i = num_putback_chars - 1; i >= 0; i--)
|
|
{
|
|
const int c = getChar(i);
|
|
TEST_EQ(nw::cin.get(), c);
|
|
}
|
|
// Check unget (all chars)
|
|
for(int i = 0; i < num_putback_chars; i++)
|
|
TEST(nw::cin.unget());
|
|
TEST(!nw::cin.unget());
|
|
nw::cin.clear();
|
|
for(int i = num_putback_chars - 1; i >= 0; i--)
|
|
{
|
|
const int c = getChar(i);
|
|
TEST_EQ(nw::cin.get(), c);
|
|
}
|
|
}
|
|
#ifndef NOWIDE_TEST_INTERACTIVE
|
|
// Put back 1 char, then get the rest from "real" input
|
|
nw::cin.putback('T');
|
|
mock_buf.inputs.push(L"est\r\n");
|
|
std::string test;
|
|
TEST(nw::cin >> test);
|
|
TEST_EQ(test, "Test");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void test_cout()
|
|
{
|
|
INSTALL_MOCK_BUF(cout, mock_output_buffer);
|
|
TEST(nw::cout);
|
|
TEST(nw::cout << outputString << std::endl);
|
|
TEST_MOCKED(mock_buf.output == nw::widen(outputString + "\n"));
|
|
#ifndef NOWIDE_TEST_INTERACTIVE
|
|
// Pretend the actual write to console fails
|
|
mock_buf.output.clear();
|
|
mock_buf.succeed = false;
|
|
TEST(!(nw::cout << "Fail this" << std::endl));
|
|
TEST(mock_buf.output.empty());
|
|
#endif
|
|
}
|
|
|
|
void test_cout_single_char()
|
|
{
|
|
INSTALL_MOCK_BUF(cout, mock_output_buffer);
|
|
for(const char s : outputString)
|
|
TEST(nw::cout << s << std::flush);
|
|
TEST(nw::cout);
|
|
TEST_MOCKED(mock_buf.output == nw::widen(outputString));
|
|
}
|
|
|
|
void test_cerr()
|
|
{
|
|
INSTALL_MOCK_BUF(cerr, mock_output_buffer);
|
|
TEST(nw::cerr);
|
|
TEST(nw::cerr << outputString << std::endl);
|
|
TEST_MOCKED(mock_buf.output == nw::widen(outputString + "\n"));
|
|
// Output to cerr is unbuffered and should be flushed for every single output
|
|
RUN_MOCKED(mock_buf.output.clear());
|
|
TEST_MOCKED(nw::cerr << "a");
|
|
TEST_MOCKED(mock_buf.output == nw::widen("a"));
|
|
TEST_MOCKED(nw::cerr << "Hello World");
|
|
TEST_MOCKED(mock_buf.output == nw::widen("aHello World"));
|
|
}
|
|
|
|
void test_cerr_single_char()
|
|
{
|
|
INSTALL_MOCK_BUF(cerr, mock_output_buffer);
|
|
for(const char s : outputString)
|
|
TEST(nw::cerr << s << std::flush);
|
|
TEST(nw::cerr);
|
|
TEST_MOCKED(mock_buf.output == nw::widen(outputString));
|
|
}
|
|
|
|
void test_cin()
|
|
{
|
|
RUN_MOCKED(const std::wstring lineBreak = L"\r\n"; const std::wstring space = L" ");
|
|
INSTALL_MOCK_BUF(cin, mock_input_buffer);
|
|
nw::cout << "Input 2 strings, e.g. 'Hello World<ENTER>'" << std::endl;
|
|
static_assert(array_size(roundtrip_tests) > 7, "!");
|
|
RUN_MOCKED(mock_buf.inputs.push(roundtrip_tests[6].wide + space + roundtrip_tests[7].wide + lineBreak));
|
|
std::string v1, v2;
|
|
nw::cin >> v1 >> v2;
|
|
TEST(nw::cin);
|
|
TEST(is_valid_UTF8(v1));
|
|
TEST(is_valid_UTF8(v2));
|
|
TEST(nw::cout << "First: " << v1 << std::endl);
|
|
TEST(nw::cout << "Second: " << v2 << std::endl);
|
|
TEST_MOCKED(v1 == roundtrip_tests[6].utf8);
|
|
TEST_MOCKED(v2 == roundtrip_tests[7].utf8);
|
|
|
|
// Check sync
|
|
nw::cout << "Input 2 strings, e.g. 'Two more<ENTER>'" << std::endl;
|
|
RUN_MOCKED(mock_buf.inputs.push(L"First_String\u00F1" + space + L"Second_String_Ignored" + lineBreak));
|
|
TEST(nw::cin >> v1);
|
|
nw::cin.sync();
|
|
nw::cout << "The 2nd string should have been ignored. Input 1 more + [ENTER]" << std::endl;
|
|
RUN_MOCKED(mock_buf.inputs.push(L"Third_\xDC01_String" + lineBreak)); // Note: Invalid UTF-16
|
|
TEST(nw::cin >> v2);
|
|
TEST(!v2.empty());
|
|
nw::cout << "First: " << v1 << std::endl;
|
|
nw::cout << "Second: " << v2 << std::endl;
|
|
TEST_MOCKED(v1 == "First_String\xc3\xb1");
|
|
TEST_MOCKED(v2 == "Third_\xEF\xBF\xBD_String");
|
|
}
|
|
|
|
void test_cin_getline()
|
|
{
|
|
INSTALL_MOCK_BUF(cin, mock_input_buffer);
|
|
std::string value;
|
|
for(int i = 0; i < 10; i++)
|
|
{
|
|
nw::cout << "Input a line of text or simply press ENTER to exit, e.g. 'Hello World to you!<ENTER>'"
|
|
<< std::endl;
|
|
// Add a longish string which eventually exceeds the buffer size of the console_buffer
|
|
RUN_MOCKED(const std::string expected = (i == 9) ? "" : create_random_one_line_string(i * 211 + 13));
|
|
// Convert to wstring and push (we can do this as the chars are ASCII)
|
|
RUN_MOCKED(mock_buf.inputs.push(std::wstring(expected.begin(), expected.end()) + L"\r\n"));
|
|
TEST(std::getline(nw::cin, value));
|
|
if(value.empty())
|
|
{
|
|
TEST_MOCKED(i == 9);
|
|
nw::cout << "END\n";
|
|
break;
|
|
}
|
|
TEST_MOCKED(i != 9);
|
|
// It should not include the CR
|
|
TEST(value.back() != '\r');
|
|
nw::cout << i << ": " << value << std::endl;
|
|
TEST_MOCKED(value == expected);
|
|
}
|
|
#ifndef NOWIDE_TEST_INTERACTIVE
|
|
TEST(!std::getline(nw::cin, value));
|
|
TEST(nw::cin.eof());
|
|
TEST(value.empty());
|
|
nw::cin.clear();
|
|
// Check that incomplete chars at the buffer border are handled correctly by creating a very long string
|
|
std::wstring input;
|
|
constexpr std::size_t buffer_size = 1024; // From console_buffer_base
|
|
input.reserve(buffer_size * 4);
|
|
input.append(buffer_size - 2, L'a'); // 2 chars before end of buffer
|
|
input.append(L"\U0001033C"); // 2 UTF-16 chars --> Buffer full
|
|
input.append(buffer_size - 1, L'a'); // Just before end of buffer
|
|
input.append(L"\U0001033C"); // over buffer boundary
|
|
input.append(L"\U0001033Ca"); // in new buffer and align again
|
|
// Fill up with largest code point
|
|
for(std::size_t i = 0; i < buffer_size; i++)
|
|
input.append(L"\U0010FFFF");
|
|
mock_buf.inputs.push(input + L"\r\n");
|
|
TEST(std::getline(nw::cin, value));
|
|
TEST_EQ(value, nw::narrow(input));
|
|
#endif
|
|
}
|
|
|
|
void test_ctrl_z_is_eof()
|
|
{
|
|
INSTALL_MOCK_BUF(cin, mock_input_buffer);
|
|
std::string value;
|
|
nw::cout << "Input a line of text and then press CTRL+Z, e.g. 'Hello World!<ENTER><CTRL+Z><ENTER>'" << std::endl;
|
|
RUN_MOCKED(mock_buf.inputs.push(L"Hello World!\r\n"));
|
|
RUN_MOCKED(mock_buf.inputs.push(L"\x1a\r\n"));
|
|
RUN_MOCKED(mock_buf.inputs.push(L"Reached after clear()\r\n"));
|
|
TEST(std::getline(nw::cin, value));
|
|
// It should not include the CR
|
|
TEST(!value.empty());
|
|
TEST(value.back() != '\r');
|
|
nw::cout << "Value: " << value << std::endl;
|
|
TEST_MOCKED(value == "Hello World!");
|
|
TEST(!std::getline(nw::cin, value));
|
|
TEST(nw::cin.eof());
|
|
nw::cin.clear();
|
|
nw::cout << "clear() called, input another line, e.g. 'Hi there!<ENTER>'" << std::endl;
|
|
TEST(std::getline(nw::cin, value));
|
|
// It should not include the CR
|
|
TEST(!value.empty());
|
|
TEST(value.back() != '\r');
|
|
nw::cout << "Value: " << value << std::endl;
|
|
TEST_MOCKED(value == "Reached after clear()");
|
|
#ifndef NOWIDE_TEST_INTERACTIVE
|
|
// CTRL+Z anywhere else but at the start of a line does not matter
|
|
nw::cout << "CTRL+Z Test:";
|
|
for(int i = 1; i <= 1100; i++)
|
|
{
|
|
nw::cout << '.' << std::flush; // Progress indicator
|
|
const std::string expected = create_random_one_line_string(i) + "\x1a";
|
|
mock_buf.inputs.push(std::wstring(expected.begin(), expected.end()) + L"\r\n");
|
|
TEST(std::getline(nw::cin, value));
|
|
TEST_EQ(value, expected);
|
|
}
|
|
nw::cout << std::endl;
|
|
#endif
|
|
}
|
|
|
|
#ifndef NOWIDE_TEST_INTERACTIVE
|
|
#ifdef NOWIDE_WINDOWS
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include <windows.h>
|
|
|
|
/// Test class swapping the original in/out handles for a buffer which
|
|
/// can be filled (for stdin) or read (for stdout/stderr)
|
|
class RedirectStdio
|
|
{
|
|
DWORD handleType;
|
|
HANDLE h, oldHandle;
|
|
|
|
public:
|
|
RedirectStdio(DWORD handleType) : handleType(handleType), oldHandle(GetStdHandle(handleType))
|
|
{
|
|
if(handleType == STD_INPUT_HANDLE)
|
|
{
|
|
h = CreateFile("CONIN$",
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
nullptr,
|
|
OPEN_EXISTING,
|
|
0,
|
|
0);
|
|
} else
|
|
{
|
|
h = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
nullptr,
|
|
CONSOLE_TEXTMODE_BUFFER,
|
|
nullptr);
|
|
}
|
|
TEST(h != INVALID_HANDLE_VALUE);
|
|
TEST(SetStdHandle(handleType, h));
|
|
if(handleType == STD_INPUT_HANDLE)
|
|
TEST(SetConsoleMode(h, ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_EXTENDED_FLAGS));
|
|
else
|
|
TEST(SetConsoleActiveScreenBuffer(h));
|
|
}
|
|
~RedirectStdio()
|
|
{
|
|
SetStdHandle(handleType, oldHandle);
|
|
if(handleType != STD_INPUT_HANDLE)
|
|
SetConsoleActiveScreenBuffer(oldHandle);
|
|
CloseHandle(h);
|
|
}
|
|
|
|
std::wstring getBufferData()
|
|
{
|
|
CONSOLE_SCREEN_BUFFER_INFO info;
|
|
TEST(GetConsoleScreenBufferInfo(h, &info));
|
|
TEST(info.dwSize.X > 0 && info.dwSize.Y > 0);
|
|
std::cout << "Mock console buffer size: " << info.dwSize.X << "x" << info.dwSize.Y << "\n";
|
|
|
|
std::wstring result;
|
|
std::vector<wchar_t> buffer(info.dwSize.X);
|
|
const auto isSpace = [](const wchar_t c) { return c != L' '; };
|
|
for(COORD readPos{}; readPos.Y < info.dwSize.Y; ++readPos.Y)
|
|
{
|
|
DWORD dwRead, bufferSize = static_cast<DWORD>(buffer.size());
|
|
TEST(ReadConsoleOutputCharacterW(h, buffer.data(), bufferSize, readPos, &dwRead));
|
|
const auto itEnd = std::find_if(buffer.rbegin() + (buffer.size() - dwRead), buffer.rend(), isSpace);
|
|
if(itEnd == buffer.rend())
|
|
break;
|
|
result.append(buffer.begin(), itEnd.base());
|
|
result.push_back('\n');
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void setBufferData(const std::wstring& data)
|
|
{
|
|
std::vector<INPUT_RECORD> buffer;
|
|
buffer.reserve(data.size() * 2 + 2);
|
|
for(const auto c : data)
|
|
{
|
|
INPUT_RECORD ev;
|
|
ev.EventType = KEY_EVENT;
|
|
ev.Event.KeyEvent.bKeyDown = TRUE;
|
|
ev.Event.KeyEvent.dwControlKeyState = 0;
|
|
ev.Event.KeyEvent.wRepeatCount = 1;
|
|
if(c == '\n')
|
|
{
|
|
ev.Event.KeyEvent.uChar.UnicodeChar = '\r';
|
|
ev.Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
|
|
} else
|
|
{
|
|
ev.Event.KeyEvent.uChar.UnicodeChar = c;
|
|
ev.Event.KeyEvent.wVirtualKeyCode = VkKeyScanW(c);
|
|
}
|
|
ev.Event.KeyEvent.wVirtualScanCode =
|
|
static_cast<WORD>(MapVirtualKeyW(ev.Event.KeyEvent.wVirtualKeyCode, MAPVK_VK_TO_VSC));
|
|
buffer.push_back(ev);
|
|
ev.Event.KeyEvent.bKeyDown = FALSE;
|
|
buffer.push_back(ev);
|
|
}
|
|
DWORD dwWritten;
|
|
TEST(WriteConsoleInputW(h, buffer.data(), static_cast<DWORD>(buffer.size()), &dwWritten));
|
|
TEST_EQ(dwWritten, static_cast<DWORD>(buffer.size()));
|
|
}
|
|
};
|
|
|
|
void test_console()
|
|
{
|
|
#ifndef NOWIDE_DISABLE_CIN_TEST
|
|
std::cout << "Test cin console: " << std::flush;
|
|
{
|
|
RedirectStdio stdinHandle(STD_INPUT_HANDLE);
|
|
std::cout << "stdin redirected, " << std::flush;
|
|
// Recreate to react on redirected streams
|
|
decltype(nw::cin) cin(nullptr);
|
|
std::cout << "cin recreated " << std::flush;
|
|
TEST(cin.rdbuf() != std::cin.rdbuf());
|
|
std::cout << "and validated" << std::endl;
|
|
const std::string testStringIn1 = "Hello std in ";
|
|
const std::string testStringIn2 = "\xc3\xa4 - \xc3\xb6 - \xc3\xbc - \xd0\xbc - \xce\xbd";
|
|
std::cout << "Setting mock buffer data" << std::endl;
|
|
stdinHandle.setBufferData(nw::widen(testStringIn1 + "\n" + testStringIn2 + "\n"));
|
|
std::cout << "Done" << std::endl;
|
|
std::string line;
|
|
TEST(std::getline(cin, line));
|
|
std::cout << "ASCII line read" << std::endl;
|
|
TEST_EQ(line, testStringIn1);
|
|
TEST(std::getline(cin, line));
|
|
std::cout << "UTF-8 line read" << std::endl;
|
|
TEST_EQ(line, testStringIn2);
|
|
}
|
|
#endif
|
|
std::cout << "Test cout console" << std::endl;
|
|
{
|
|
RedirectStdio stdoutHandle(STD_OUTPUT_HANDLE);
|
|
decltype(nw::cout) cout(true, nullptr);
|
|
TEST(cout.rdbuf() != std::cout.rdbuf());
|
|
|
|
const std::string testString = "Hello std out\n\xc3\xa4-\xc3\xb6-\xc3\xbc\n";
|
|
cout << testString << std::flush;
|
|
|
|
const auto data = stdoutHandle.getBufferData();
|
|
TEST_EQ(data, nw::widen(testString));
|
|
}
|
|
std::cout << "Test cerr console" << std::endl;
|
|
{
|
|
RedirectStdio stderrHandle(STD_ERROR_HANDLE);
|
|
|
|
decltype(nw::cerr) cerr(false, nullptr);
|
|
TEST(cerr.rdbuf() != std::cerr.rdbuf());
|
|
|
|
const std::string testString = "Hello std err\n\xc3\xa4-\xc3\xb6-\xc3\xbc\n";
|
|
cerr << testString << std::flush;
|
|
|
|
const auto data = stderrHandle.getBufferData();
|
|
TEST_EQ(data, nw::widen(testString));
|
|
}
|
|
std::cout << "Console tests done" << std::endl;
|
|
}
|
|
|
|
#else
|
|
void test_console()
|
|
{}
|
|
#endif
|
|
#endif
|
|
|
|
// coverity[root_function]
|
|
void test_main(int argc, char** argv, char**)
|
|
{
|
|
// LCOV_EXCL_START
|
|
if(usesNowideRdBufIn)
|
|
nw::cout << "Using Nowide input buffer\n";
|
|
else
|
|
nw::cout << "NOT using Nowide input buffer\n";
|
|
if(usesNowideRdBufOut)
|
|
nw::cout << "Using Nowide output buffer\n"; // LCOV_EXCL_LINE
|
|
else
|
|
nw::cout << "NOT using Nowide output buffer\n";
|
|
// LCOV_EXCL_STOP
|
|
|
|
const std::string arg = (argc == 1) ? "" : argv[1];
|
|
if(arg == "passthrough") // Read string from cin and write to cout
|
|
{
|
|
// Check that input and output are not using the nowide filebufs as expected when the consoles are redirected
|
|
TEST(!usesNowideRdBufIn);
|
|
TEST(!usesNowideRdBufOut);
|
|
std::string s;
|
|
TEST(std::getline(nw::cin, s));
|
|
TEST(nw::cout << s);
|
|
return;
|
|
}
|
|
|
|
#ifdef NOWIDE_TEST_INTERACTIVE
|
|
nw::cout << "Output different chars:" << std::endl; // LCOV_EXCL_LINE
|
|
test_cout(); // LCOV_EXCL_LINE
|
|
nw::cout << "Same again:" << std::endl; // LCOV_EXCL_LINE
|
|
test_cout_single_char(); // LCOV_EXCL_LINE
|
|
|
|
nw::cout << "Same 2 outputs but to stderr:" << std::endl; // LCOV_EXCL_LINE
|
|
test_cerr(); // LCOV_EXCL_LINE
|
|
test_cerr_single_char(); // LCOV_EXCL_LINE
|
|
|
|
nw::cout << "Basic cin tests:" << std::endl; // LCOV_EXCL_LINE
|
|
test_cin(); // LCOV_EXCL_LINE
|
|
|
|
nw::cout << "getline test:" << std::endl; // LCOV_EXCL_LINE
|
|
// Clear newline from last test
|
|
nw::cin.ignore(std::numeric_limits<int>::max(), '\n'); // LCOV_EXCL_LINE
|
|
test_cin_getline(); // LCOV_EXCL_LINE
|
|
|
|
nw::cout << "CTRL+Z test:" << std::endl; // LCOV_EXCL_LINE
|
|
test_ctrl_z_is_eof(); // LCOV_EXCL_LINE
|
|
#else
|
|
test_is_valid_UTF8();
|
|
test_tie();
|
|
test_putback_and_get();
|
|
test_cout();
|
|
test_cout_single_char();
|
|
test_cerr();
|
|
test_cerr_single_char();
|
|
test_cin();
|
|
test_cin_getline();
|
|
test_ctrl_z_is_eof();
|
|
test_console();
|
|
#endif // NOWIDE_TEST_INTERACTIVE
|
|
}
|