Skip to content

Commit

Permalink
Add a simple tool to test rendering functionality (#15091)
Browse files Browse the repository at this point in the history
This tool augments `vttest` by adding some things that are specific to
us (like non-VT console attributes), and some things `vttest` is
seemingly too old for (like emojis). I'm planning to add more "pages"
of tests to the application in the future, whenever the need arises.
  • Loading branch information
lhecker committed Apr 3, 2023
1 parent 7ddd98d commit 0d38d17
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/actions/spelling/excludes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$
^src/tools/lnkd/lnkd\.bat$
^src/tools/pixels/pixels\.bat$
^src/tools/RenderingTests/main.cpp$
^src/tools/texttests/fira\.txt$
^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$
^src/types/ut_types/UtilsTests.cpp$
Expand Down
29 changes: 29 additions & 0 deletions OpenConsole.sln
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MidiAudio", "src\audio\midi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TerminalStress", "src\tools\TerminalStress\TerminalStress.csproj", "{613CCB57-5FA9-48EF-80D0-6B1E319E20C4}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RenderingTests", "src\tools\RenderingTests\RenderingTests.vcxproj", "{37C995E0-2349-4154-8E77-4A52C0C7F46D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
AuditMode|Any CPU = AuditMode|Any CPU
Expand Down Expand Up @@ -2770,6 +2772,32 @@ Global
{613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Release|x64.ActiveCfg = Release|Any CPU
{613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Release|x64.Build.0 = Release|Any CPU
{613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Release|x86.ActiveCfg = Release|Any CPU
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.AuditMode|ARM.ActiveCfg = AuditMode|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.AuditMode|ARM64.ActiveCfg = Release|ARM64
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.AuditMode|x64.ActiveCfg = Release|x64
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.AuditMode|x86.ActiveCfg = Release|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|Any CPU.ActiveCfg = Debug|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|ARM.ActiveCfg = Debug|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|ARM64.Build.0 = Debug|ARM64
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|x64.ActiveCfg = Debug|x64
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|x64.Build.0 = Debug|x64
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|x86.ActiveCfg = Debug|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|x86.Build.0 = Debug|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|x64.ActiveCfg = Fuzzing|x64
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|Any CPU.ActiveCfg = Release|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|ARM.ActiveCfg = Release|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|ARM64.ActiveCfg = Release|ARM64
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|ARM64.Build.0 = Release|ARM64
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|x64.ActiveCfg = Release|x64
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|x64.Build.0 = Release|x64
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|x86.ActiveCfg = Release|Win32
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -2875,6 +2903,7 @@ Global
{40BD8415-DD93-4200-8D82-498DDDC08CC8} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
{3C67784E-1453-49C2-9660-483E2CC7F7AD} = {40BD8415-DD93-4200-8D82-498DDDC08CC8}
{613CCB57-5FA9-48EF-80D0-6B1E319E20C4} = {A10C4720-DCA4-4640-9749-67F4314F527C}
{37C995E0-2349-4154-8E77-4A52C0C7F46D} = {A10C4720-DCA4-4640-9749-67F4314F527C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
Expand Down
26 changes: 26 additions & 0 deletions src/tools/RenderingTests/RenderingTests.vcxproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{37c995e0-2349-4154-8e77-4a52c0c7f46d}</ProjectGuid>
<RootNamespace>RenderingTests</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(SolutionDir)\src\common.build.pre.props" />
<Import Project="$(SolutionDir)\src\common.nugetversions.props" />
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
</ItemGroup>
<Import Project="$(SolutionDir)\src\common.build.post.props" />
<Import Project="$(SolutionDir)\src\common.nugetversions.targets" />
</Project>
4 changes: 4 additions & 0 deletions src/tools/RenderingTests/RenderingTests.vcxproj.filters
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>
259 changes: 259 additions & 0 deletions src/tools/RenderingTests/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <conio.h>

#include <array>
#include <cassert>

// Another variant of "defer" for C++.
namespace
{
namespace detail
{

template<typename F>
class scope_guard
{
public:
scope_guard(F f) noexcept :
func(std::move(f))
{
}

~scope_guard()
{
func();
}

scope_guard(const scope_guard&) = delete;
scope_guard(scope_guard&& rhs) = delete;
scope_guard& operator=(const scope_guard&) = delete;
scope_guard& operator=(scope_guard&&) = delete;

private:
F func;
};

enum class scope_guard_helper
{
};

template<typename F>
scope_guard<F> operator+(scope_guard_helper /*unused*/, F&& fn)
{
return scope_guard<F>(std::forward<F>(fn));
}

} // namespace detail

// The extra indirection is necessary to prevent __LINE__ to be treated literally.
#define _DEFER_CONCAT_IMPL(a, b) a##b
#define _DEFER_CONCAT(a, b) _DEFER_CONCAT_IMPL(a, b)
#define defer const auto _DEFER_CONCAT(_defer_, __LINE__) = ::detail::scope_guard_helper() + [&]()
}

static void printUTF16(const wchar_t* str)
{
WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), str, static_cast<DWORD>(wcslen(str)), nullptr, nullptr);
}

// wprintf() in the uCRT prints every single wchar_t individually and thus breaks surrogate
// pairs apart which Windows Terminal treats as invalid input and replaces it with U+FFFD.
static void printfUTF16(_In_z_ _Printf_format_string_ wchar_t const* const format, ...)
{
std::array<wchar_t, 128> buffer;

va_list args;
va_start(args, format);
const auto length = _vsnwprintf_s(buffer.data(), buffer.size(), _TRUNCATE, format, args);
va_end(args);

assert(length >= 0);
WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), buffer.data(), length, nullptr, nullptr);
}

static void wait()
{
printUTF16(L"\x1B[9999;1HPress any key to continue...");
_getch();
}

static void clear()
{
printUTF16(
L"\x1B[H" // move cursor to 0,0
L"\x1B[2J" // clear screen
);
}

int main()
{
const auto outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD consoleMode = ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT;
GetConsoleMode(outputHandle, &consoleMode);
SetConsoleMode(outputHandle, consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN);
defer
{
SetConsoleMode(outputHandle, consoleMode);
};

printUTF16(
L"\x1b[?1049h" // enable alternative screen buffer
);
defer
{
printUTF16(
L"\x1b[?1049l" // disable alternative screen buffer
);
};

{
struct ConsoleAttributeTest
{
const wchar_t* text = nullptr;
WORD attribute = 0;
};
static constexpr ConsoleAttributeTest consoleAttributeTests[]{
{ L"Console attributes:", 0 },
#define MAKE_TEST_FOR_ATTRIBUTE(attr) { L## #attr, attr }
MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_HORIZONTAL),
MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_LVERTICAL),
MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_RVERTICAL),
MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_REVERSE_VIDEO),
MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_UNDERSCORE),
#undef MAKE_TEST_FOR_ATTRIBUTE
{ L"all gridlines", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_UNDERSCORE },
{ L"all attributes", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_REVERSE_VIDEO | COMMON_LVB_UNDERSCORE },
};

SHORT row = 2;
for (const auto& t : consoleAttributeTests)
{
const auto length = static_cast<DWORD>(wcslen(t.text));
printfUTF16(L"\x1B[%d;5H%s", row + 1, t.text);

WORD attributes[32];
std::fill_n(&attributes[0], length, static_cast<WORD>(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | t.attribute));

DWORD numberOfAttrsWritten;
WriteConsoleOutputAttribute(outputHandle, attributes, length, { 4, row }, &numberOfAttrsWritten);

row += 2;
}

struct VTAttributeTest
{
const wchar_t* text = nullptr;
int sgr = 0;
};
static constexpr VTAttributeTest vtAttributeTests[]{
{ L"ANSI escape SGR:", 0 },
{ L"italic", 3 },
{ L"underline", 4 },
{ L"reverse", 7 },
{ L"strikethrough", 9 },
{ L"double underline", 21 },
{ L"overlined", 53 },
};

row = 3;
for (const auto& t : vtAttributeTests)
{
printfUTF16(L"\x1B[%d;45H\x1b[%dm%s\x1b[m", row, t.sgr, t.text);
row += 2;
}

printfUTF16(L"\x1B[%d;45H\x1b]8;;https://example.com\x1b\\hyperlink\x1b]8;;\x1b\\", row);

wait();
clear();
}

{
printUTF16(
L"\x1B[3;5HDECDWL Double Width \U0001FAE0 A\u0353\u0353 B\u036F\u036F"
L"\x1B[4;5H\x1b#6DECDWL Double Width \U0001FAE0 A\u0353\u0353 B\u036F\u036F"
L"\x1B[8;5HDECDHL Double Height \U0001F642\U0001F6C1 A\u0353\u0353 B\u036F\u036F X\u0353\u0353 Y\u036F\u036F"
L"\x1B[9;5H\x1b#3DECDHL Double Height Top \U0001F642 A\u0353\u0353 B\u036F\u036F"
L"\x1B[10;5H\x1b#4DECDHL Double Height Bottom \U0001F6C1 X\u0353\u0353 Y\u036F\u036F");

wait();
clear();
}

{
defer
{
// Setting an empty DRCS gets us back to the regular font.
printUTF16(L"\x1bP1;1;2{ @\x1b\\");
};

constexpr auto width = 14;
const auto glyph =
"W W "
"W W "
"W W W "
"W W W "
"W W W "
"W W W TTTTTTT"
" W W T "
" T "
" T "
" T "
" T "
" T ";

// Convert the above visual glyph to sixels
wchar_t rows[2][width];
for (int r = 0; r < 2; ++r)
{
const auto glyphData = &glyph[r * width * 6];

for (int x = 0; x < width; ++x)
{
unsigned int accumulator = 0;
for (int y = 5; y >= 0; --y)
{
const auto isSet = glyphData[y * width + x] != ' ';
accumulator <<= 1;
accumulator |= static_cast<unsigned int>(isSet);
}

rows[r][x] = static_cast<wchar_t>(L'?' + accumulator);
}
}

// DECDLD - Dynamically Redefinable Character Sets
printfUTF16(
// * Pfn | font number | 1 |
// * Pcn | starting character | 3 | = ASCII 0x23 "#"
// * Pe | erase control | 2 | erase all
// Pcmw | character matrix width | %d | `width` pixels
// Pw | font width | 0 | 80 columns
// Pt | text or full cell | 0 | text
// Pcmh | character matrix height | 0 | 12 pixels
// Pcss | character set size | 0 | 94
// * Dscs | character set name | " @" | unregistered soft set
L"\x1bP1;3;2;%d{ @%.15s/%.15s\x1b\\",
width,
rows[0],
rows[1]);

#define DRCS_SEQUENCE L"\x1b( @#\x1b(A"
printUTF16(
L"\x1B[3;5HDECDLD and DRCS test - it should show \"WT\" in a single cell"
L"\x1B[5;5HRegular: " DRCS_SEQUENCE L""
L"\x1B[7;3H\x1b#6DECDWL: " DRCS_SEQUENCE L""
L"\x1B[9;3H\x1b#3DECDHL: " DRCS_SEQUENCE L""
L"\x1B[10;3H\x1b#4DECDHL: " DRCS_SEQUENCE L""
// We map soft fonts into the private use area starting at U+EF20. This test ensures
// that we correctly map actual fallback glyphs mixed into the DRCS glyphs.
L"\x1B[12;5HUnicode Fallback: \uE000\uE001" DRCS_SEQUENCE L"\uE003\uE004");
#undef DRCS_SEQUENCE

wait();
}

return 0;
}

0 comments on commit 0d38d17

Please sign in to comment.