mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-18 01:37:05 +02:00
Reject malformed, non-finite, and out-of-range progress values while preserving them as ordinary messages. Add parser regression tests for progress and QR-code output, and register them with CMake.
185 lines
8.9 KiB
C++
185 lines
8.9 KiB
C++
// DepotDownloader output: progress, QR encodings and updates across successive lines.
|
|
#include "Launcher/depot_output.h"
|
|
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
using dingosdk::launcher_gui::DepotOutput;
|
|
|
|
namespace {
|
|
|
|
int failures = 0;
|
|
|
|
void check(bool condition, const char* what) {
|
|
if (condition) return;
|
|
std::cerr << "FAIL: " << what << '\n';
|
|
++failures;
|
|
}
|
|
|
|
constexpr std::string_view announcement = "Please sign in with this QR code:";
|
|
|
|
// A deterministic 21 x 21 module pattern with a quiet zone at either side.
|
|
// These fixtures test the text transport, not whether a QR code can be scanned.
|
|
std::vector<std::string> qr_modules(bool alternate = false) {
|
|
std::vector<std::string> rows;
|
|
for (int y = 0; y < 21; ++y) {
|
|
std::string row = " ";
|
|
for (int x = 0; x < 21; ++x) row += (x + y + alternate) % 3 ? ' ' : '#';
|
|
rows.push_back(row + " ");
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
std::string qr_line(std::string_view modules, std::string_view block) {
|
|
std::string line;
|
|
for (char module : modules) {
|
|
if (module == '#') { line += block; line += block; }
|
|
else line += " ";
|
|
}
|
|
return line;
|
|
}
|
|
|
|
void feed_rows(DepotOutput& output, const std::vector<std::string>& rows, std::string_view block) {
|
|
for (const auto& row : rows) {
|
|
output.feed(qr_line(row, block));
|
|
check(!output.qr_update && !output.percent, "Collecting rows does not publish a partial QR code or progress");
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
// ------------------------------------------------ progress and retained messages
|
|
{
|
|
DepotOutput output;
|
|
check(!output.percent && !output.qr_update && output.file.empty() && output.message.empty(),
|
|
"A new parser has no output");
|
|
output.feed("\t Connecting to Steam \t");
|
|
check(output.message == "Connecting to Steam", "Ordinary messages are trimmed");
|
|
output.feed(R"(12.34% C:\Games\Skate\file.bin)");
|
|
check(output.percent && std::abs(*output.percent - 12.34f) < 0.001f && output.file == "file.bin",
|
|
"Windows progress extracts the percentage and basename");
|
|
check(output.message == "Connecting to Steam", "Progress retains the last ordinary message for failure reporting");
|
|
output.feed(" \t50.00% /games/Skate/a file with spaces.bin \t");
|
|
check(output.percent == 50.0f && output.file == "a file with spaces.bin",
|
|
"Unix paths, spaces in filenames and surrounding whitespace are supported");
|
|
output.feed("0.00% file.bin");
|
|
check(output.percent == 0.0f && output.file == "file.bin", "Zero progress and a bare filename are supported");
|
|
output.feed("100.00% /games/Skate/done.bin");
|
|
check(output.percent == 100.0f && output.file == "done.bin", "One hundred percent is supported");
|
|
output.feed(" \t ");
|
|
check(!output.percent && !output.qr_update && output.file == "done.bin" && output.message == "Connecting to Steam",
|
|
"Blank input resets transient updates and retains the last filename and message");
|
|
output.feed("Download complete");
|
|
check(!output.percent && output.message == "Download complete", "Ordinary output replaces the message after progress");
|
|
const std::string long_message = std::string(160, 'a') + "end";
|
|
output.feed(" \t" + long_message + " \t");
|
|
check(output.message == long_message.substr(0, 160), "Long messages are trimmed and limited to 160 bytes");
|
|
output.feed("Short message");
|
|
check(output.message == "Short message", "A short message replaces a truncated one");
|
|
}
|
|
|
|
// ------------------------------------------------ malformed progress regression
|
|
// The caller divides percent by 100 and passes it directly to the progress bar.
|
|
// std::stof alone accepts non-finite values, out-of-range values and numeric prefixes.
|
|
for (const char* line : {
|
|
"NaN% file.bin", "inf% file.bin", "-inf% file.bin", "-1% file.bin",
|
|
"100.01% file.bin", "999999% file.bin", "1e999% file.bin",
|
|
"12oops% file.bin", "12.3.4% file.bin", "12 % file.bin",
|
|
"% file.bin", "oops% file.bin", "12.34%", "12.34% ",
|
|
"12.34%file.bin", "12.34%\tfile.bin", "Saved 12.34% of files"}) {
|
|
DepotOutput output;
|
|
output.feed("25% previous.bin");
|
|
output.feed(line);
|
|
check(!output.percent, line);
|
|
auto expected = std::string_view(line);
|
|
while (expected.back() == ' ') expected.remove_suffix(1);
|
|
check(output.file == "previous.bin" && output.message == expected,
|
|
"Invalid progress is an ordinary message and does not replace the filename");
|
|
output.feed("75% next.bin");
|
|
check(output.percent == 75.0f && output.file == "next.bin", "Valid progress resumes after malformed input");
|
|
}
|
|
|
|
// ------------------------------------------------ supported QR encodings and dismissal
|
|
const auto rows = qr_modules();
|
|
for (std::string_view block : {std::string_view("\xE2\x96\x88"), std::string_view("\xDB"), std::string_view("?")}) {
|
|
DepotOutput output;
|
|
output.feed("10% old.bin");
|
|
output.feed(announcement);
|
|
check(!output.percent && !output.qr_update, "A QR announcement resets transient progress");
|
|
output.feed("");
|
|
output.feed(std::string(50, ' '));
|
|
check(!output.qr_update, "Leading empty rows and quiet-zone spaces do not finish a QR code");
|
|
feed_rows(output, rows, block);
|
|
output.feed(std::string(50, ' '));
|
|
check(output.qr_update && *output.qr_update == rows, "All block encodings preserve dark modules, spaces and row order");
|
|
output.feed("");
|
|
check(!output.qr_update, "Blank output after completion consumes the update without dismissing the displayed QR code");
|
|
output.feed("QR code has changed");
|
|
check(!output.qr_update, "A refresh notice keeps the displayed QR code until its replacement arrives");
|
|
output.feed("Logged in to Steam");
|
|
check(output.qr_update && output.qr_update->empty() && output.message == "Logged in to Steam",
|
|
"Ordinary output dismisses the QR code with an empty update and supplies its message");
|
|
output.feed("Depot download starting");
|
|
check(!output.qr_update && output.message == "Depot download starting", "QR dismissal is emitted only once");
|
|
}
|
|
|
|
// ------------------------------------------------ QR restarts, refreshes and later sessions
|
|
{
|
|
DepotOutput output;
|
|
output.feed(announcement);
|
|
output.feed("????");
|
|
output.feed(announcement);
|
|
const auto replacement = qr_modules(true);
|
|
for (std::size_t y = 0; y < replacement.size(); ++y) {
|
|
output.feed(qr_line(replacement[y], "?"));
|
|
if (y == 0 || y == 19) {
|
|
output.feed("");
|
|
output.feed("QR code has changed");
|
|
}
|
|
check(!output.qr_update, "Empty rows and refresh notices before 21 rows keep collection active");
|
|
}
|
|
output.feed("");
|
|
check(output.qr_update && *output.qr_update == replacement, "A repeated announcement discards previously collected rows");
|
|
output.feed("QR code has changed");
|
|
output.feed(announcement);
|
|
feed_rows(output, rows, "\xE2\x96\x88");
|
|
output.feed("");
|
|
check(output.qr_update && *output.qr_update == rows, "A refreshed QR code replaces the old one without leaking rows");
|
|
output.feed("12.34% /games/Skate/new.bin");
|
|
check(output.qr_update && output.qr_update->empty() && output.percent && output.file == "new.bin",
|
|
"Progress can dismiss a QR code and report a download in the same update");
|
|
output.feed(announcement);
|
|
feed_rows(output, replacement, "\xDB");
|
|
output.feed("");
|
|
check(output.qr_update && *output.qr_update == replacement && !output.percent,
|
|
"A later QR session starts with fresh rows and no stale progress");
|
|
}
|
|
|
|
// ------------------------------------------------ interrupted and malformed QR output
|
|
for (const char* interruption : {"Download cancelled", "??x??", "\xE2\x96"}) {
|
|
DepotOutput output;
|
|
output.feed(announcement);
|
|
output.feed("????");
|
|
output.feed(interruption);
|
|
check((!output.qr_update || output.qr_update->empty()) && output.message == interruption,
|
|
"Non-module output ends collection and is handled as an ordinary message");
|
|
output.feed("????");
|
|
check(!output.qr_update && output.message == "????", "QR-looking text is ordinary output after an interruption");
|
|
output.feed(announcement);
|
|
feed_rows(output, rows, "?");
|
|
output.feed("");
|
|
check(output.qr_update && *output.qr_update == rows, "Interrupted rows do not leak into a later QR code");
|
|
}
|
|
|
|
if (failures) {
|
|
std::cerr << failures << " DepotDownloader output check(s) failed\n";
|
|
return 1;
|
|
}
|
|
std::cout << "DepotDownloader output checks passed.\n";
|
|
return 0;
|
|
}
|