Release v0.2.0 with incident diagnostics and in-app log viewer
This commit is contained in:
+94
-1
@@ -1,5 +1,7 @@
|
||||
#include "app.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <commctrl.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -14,6 +16,12 @@ const std::vector<std::pair<std::wstring, std::wstring>> punctuationCases{
|
||||
{L"ljvf: ", L"дома: "}, {L"ljvf; ", L"дома; "}, {L"#ljvf%; ", L"#дома%; "}};
|
||||
ULONGLONG deadline{};
|
||||
std::wstring sentinel = L"KSwitcher integration clipboard sentinel";
|
||||
std::string diagnosticLog()
|
||||
{
|
||||
std::ifstream file(ks::dataDirectory() / L"kswitcher.log");
|
||||
return {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
|
||||
}
|
||||
size_t diagnosticStart{};
|
||||
void require(bool ok, const char *name)
|
||||
{
|
||||
if (!ok)
|
||||
@@ -109,10 +117,28 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
||||
require(text() == L"OLDпривет", "recent-input conversion preserves adjacent older text");
|
||||
prepare(L"OLDabcdef", false);
|
||||
record(L"ghbdtn");
|
||||
diagnosticStart = diagnosticLog().size();
|
||||
app->busy = true;
|
||||
app->transform(ks::Direct + ru);
|
||||
app->transform(ks::Direct + ru);
|
||||
app->busy = false;
|
||||
app->transform(ks::Direct + ru);
|
||||
break;
|
||||
case 6:
|
||||
require(text() == L"OLDabcdef", "recent-input mismatch refuses replacement");
|
||||
{
|
||||
auto diagnostics = diagnosticLog();
|
||||
diagnostics = diagnostics.substr(std::min(diagnosticStart, diagnostics.size()));
|
||||
require(diagnostics.find("reason=busy") != std::string::npos,
|
||||
"busy conversion attempt is logged");
|
||||
require(diagnostics.find("repeat=3") != std::string::npos,
|
||||
"three repeated conversion attempts are logged");
|
||||
require(diagnostics.find("reason=recent-verification-mismatch") != std::string::npos,
|
||||
"failed recent-word verification has explicit outcome");
|
||||
require(diagnostics.find("copied=\"abcdef\"") != std::string::npos &&
|
||||
diagnostics.find("physicalKeys=\"ghbdtn\"") != std::string::npos,
|
||||
"conversion diagnostics contain actual text and physical input for reproduction");
|
||||
}
|
||||
app->settings.snippets = {{L";addr", L"LINE1\r\n{cursor}LINE2"}};
|
||||
prepare(L"prefix ;addr", false);
|
||||
record(L";addr");
|
||||
@@ -237,7 +263,7 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
||||
return DefWindowProcW(w, msg, wp, lp);
|
||||
}
|
||||
} // namespace
|
||||
int main()
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
auto root = std::filesystem::temp_directory_path() /
|
||||
@@ -249,6 +275,73 @@ int main()
|
||||
app = &instance;
|
||||
app->settings.history = false;
|
||||
app->settings.switchLayout = false;
|
||||
if (argc > 1 && std::string(argv[1]) == "--logs-only")
|
||||
{
|
||||
ks::showLogs(instance);
|
||||
HWND viewer = instance.logWindow;
|
||||
HWND rows = GetDlgItem(viewer, 602);
|
||||
require(rows && (GetWindowLongPtrW(rows, GWL_STYLE) & LVS_TYPEMASK) == LVS_REPORT,
|
||||
"log viewer uses report ListView");
|
||||
require(Header_GetItemCount(ListView_GetHeader(rows)) == 4, "log viewer has four columns");
|
||||
require(GetDlgItem(viewer, IDCANCEL) != rows, "log rows and Close have different IDs");
|
||||
SetFocus(rows);
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(IDCANCEL, EN_SETFOCUS), (LPARAM)rows);
|
||||
NMHDR clicked{rows, 602, NM_CLICK};
|
||||
SendMessageW(viewer, WM_NOTIFY, 602, (LPARAM)&clicked);
|
||||
require(IsWindow(viewer), "focus and click notifications do not close log viewer");
|
||||
ks::showLogs(instance);
|
||||
require(instance.logWindow == viewer, "opening logs reuses the existing window");
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(603, BN_CLICKED), (LPARAM)GetDlgItem(viewer, 603));
|
||||
require(IsWindow(viewer) && ListView_GetItemCount(rows) > 0, "refresh populates log rows");
|
||||
SendDlgItemMessageW(viewer, 601, CB_SETCURSEL, 1, 0);
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(601, CBN_SELCHANGE), (LPARAM)GetDlgItem(viewer, 601));
|
||||
require(IsWindow(viewer) && ListView_GetItemCount(rows) > 0, "previous log or empty state is displayed");
|
||||
if (argc > 2 && std::string(argv[2]) == "--clear-test")
|
||||
{
|
||||
require(std::filesystem::path(ks::executable()).parent_path().filename() == L"log-clear-test",
|
||||
"clear test runs in isolated executable directory");
|
||||
const auto logRoot = ks::dataDirectory();
|
||||
require(logRoot == std::filesystem::path(ks::executable()).parent_path() / L"data",
|
||||
"clear test uses isolated portable data");
|
||||
std::ofstream(logRoot / L"kswitcher.log") << "current-marker\n";
|
||||
std::ofstream(logRoot / L"kswitcher.log.1") << "previous-marker\n";
|
||||
ks::diagnosticEvent(L"buffered-before-clear");
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(606, BN_CLICKED), (LPARAM)GetDlgItem(viewer, 606));
|
||||
require(!std::filesystem::exists(logRoot / L"kswitcher.log.1") &&
|
||||
ks::readDiagnosticLog(false).find(L"current-marker") != std::wstring::npos,
|
||||
"Clear log removes selected previous log only");
|
||||
SendDlgItemMessageW(viewer, 601, CB_SETCURSEL, 0, 0);
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(606, BN_CLICKED), (LPARAM)GetDlgItem(viewer, 606));
|
||||
require(!std::filesystem::exists(logRoot / L"kswitcher.log") && IsWindow(viewer),
|
||||
"Clear log clears current file without closing viewer");
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(606, BN_CLICKED), (LPARAM)GetDlgItem(viewer, 606));
|
||||
ks::diagnosticEvent(L"new-problem", true);
|
||||
auto newLog = ks::readDiagnosticLog(false);
|
||||
require(newLog.find(L"new-problem") != std::wstring::npos &&
|
||||
newLog.find(L"buffered-before-clear") == std::wstring::npos,
|
||||
"logging resumes after clear without stale context");
|
||||
}
|
||||
auto savedClipboard = ks::readClipboard(viewer);
|
||||
instance.window = viewer;
|
||||
try
|
||||
{
|
||||
ListView_SetItemState(rows, 0, LVIS_SELECTED, LVIS_SELECTED);
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(605, BN_CLICKED), (LPARAM)GetDlgItem(viewer, 605));
|
||||
require(ks::readClipboard(viewer).text().find(L'\t') != std::wstring::npos,
|
||||
"selected log row copies as tab-separated columns");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ks::writeClipboard(viewer, savedClipboard);
|
||||
throw;
|
||||
}
|
||||
ks::writeClipboard(viewer, savedClipboard);
|
||||
instance.window = nullptr;
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(IDCANCEL, BN_CLICKED), (LPARAM)GetDlgItem(viewer, IDCANCEL));
|
||||
require(!IsWindow(viewer) && !instance.logWindow, "Close button closes and releases viewer");
|
||||
CoUninitialize();
|
||||
return 0;
|
||||
}
|
||||
for (int i = 0; i < (int)app->layouts.size(); ++i)
|
||||
{
|
||||
auto language = LOWORD((uintptr_t)app->layouts[i].handle);
|
||||
|
||||
@@ -44,7 +44,37 @@ int main(int argc, char **argv)
|
||||
}
|
||||
try
|
||||
{
|
||||
require(ks::diagnosticText(L"word") == L"\"word\" units=4 truncated=0",
|
||||
"diagnostic word preserved");
|
||||
require(ks::diagnosticText(L"\"\\\n\r\t") ==
|
||||
L"\"\\\"\\\\\\u000a\\u000d\\u0009\" units=5 truncated=0",
|
||||
"diagnostic control characters cannot create log lines");
|
||||
require(ks::diagnosticText(L"\u0457\u200b") == L"\"\\u0457\\u200b\" units=2 truncated=0",
|
||||
"diagnostic unicode and invisible characters preserved exactly");
|
||||
require(ks::diagnosticText(std::wstring(513, L'a')).find(L"units=513 truncated=1") != std::wstring::npos,
|
||||
"large diagnostic selections are explicitly truncated");
|
||||
STARTUPINFOW startup{sizeof(startup)};
|
||||
ks::DiagnosticBuffer diagnostics;
|
||||
require(diagnostics.record(L"successful-attempt", false, 100).empty(),
|
||||
"successful diagnostics stay in memory");
|
||||
auto incident = diagnostics.record(L"failed-attempt", true, 200);
|
||||
require(incident.find(L"successful-attempt") != std::wstring::npos &&
|
||||
incident.find(L"failed-attempt") != std::wstring::npos,
|
||||
"incident includes preceding successful context");
|
||||
require(diagnostics.record(L"next-failure", true, 300).find(L"successful-attempt") == std::wstring::npos,
|
||||
"flushed context is not duplicated");
|
||||
diagnostics.record(L"expired-context", false, 400);
|
||||
require(diagnostics.record(L"later-failure", true, 30500).find(L"expired-context") == std::wstring::npos,
|
||||
"context expires after thirty seconds");
|
||||
diagnostics.record(L"oldest-context", false, 30600);
|
||||
for (int i = 0; i < 40; ++i)
|
||||
diagnostics.record(L"bounded-context", false, 30600 + i);
|
||||
require(diagnostics.record(L"bounded-failure", true, 30700).find(L"oldest-context") == std::wstring::npos,
|
||||
"context buffer has a fixed size");
|
||||
diagnostics.record(L"disabled-context", false, 30800);
|
||||
diagnostics.clear();
|
||||
require(diagnostics.record(L"new-failure", true, 30900).find(L"disabled-context") == std::wstring::npos,
|
||||
"clearing diagnostics discards sensitive context");
|
||||
startup.dwFlags = STARTF_USESHOWWINDOW;
|
||||
startup.wShowWindow = SW_HIDE;
|
||||
PROCESS_INFORMATION child{};
|
||||
@@ -346,6 +376,7 @@ int main(int argc, char **argv)
|
||||
require(history.entries().size() == 2, "a new screenshot is retained");
|
||||
}
|
||||
ks::Settings settings;
|
||||
settings.writeLog = false;
|
||||
settings.elevatedAutostart = true;
|
||||
settings.autoCorrect = true;
|
||||
settings.autoMaxRank = 10000;
|
||||
@@ -358,6 +389,9 @@ int main(int argc, char **argv)
|
||||
settings.snippets = {{L";address", L"Line 1\r\n{cursor}Line 2"}};
|
||||
ks::saveSettings(temp / L"settings.json", settings);
|
||||
auto restored = ks::loadSettings(temp / L"settings.json", {});
|
||||
require(!restored.writeLog, "logging setting persists when disabled");
|
||||
require(ks::loadSettings(temp / L"defaults.json", {}).writeLog,
|
||||
"logging defaults on for existing diagnostic installations");
|
||||
require(restored.snippets[0].text == settings.snippets[0].text, "settings snippet roundtrip");
|
||||
require(restored.cycle == settings.cycle, "layout order roundtrip");
|
||||
require(restored.elevatedAutostart, "elevated autostart setting roundtrip");
|
||||
|
||||
Reference in New Issue
Block a user