ZaStoGram_desktop/Telegram/SourceFiles/test/test_log.h
John Preston cf478d37c8 [ai] Print helper numbers in the telemetry format on request
Task: 2026/10/09/print-shared-harness-helper-numbers-in-the-telemetry-format-on-request
2026-10-09 20:43:49 +04:00

181 lines
9 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Test {
// Absolute evidence directory: TDESKTOP_TEST_EVIDENCE_DIR when set (the
// workspace test-run helper always sets it), otherwise a test_evidence
// folder in the portable working directory. Created on first use.
[[nodiscard]] QString EvidenceDir();
[[nodiscard]] QString ScreenshotsDir();
// Appends one line to <EvidenceDir()>/test_log.txt and flushes immediately,
// so evidence survives any crash or kill. Exactly one physical line per
// call, whatever |line| carries: every character Python's str.splitlines()
// breaks on - which is the grammar the external runner's readers use -
// is written as a visible \uXXXX escape, so a record whose text had a
// break stays one row rather than several, and no middle line can be
// byte-equal to the completion marker. Text with no separator is written
// byte for byte, and the escape adds no trailing whitespace.
void LogRaw(const QString &line);
void Step(const QString &text);
// A PASS and a FAIL line have the same shape: |details| is appended after
// " - " on both verdicts, and an empty |details| produces exactly
// "TEST_RESULT: <verdict>: <text>", with no separator and no empty suffix.
void Pass(const QString &text, const QString &details = QString());
void Fail(const QString &text, const QString &details = QString());
// A stage or check that did not apply: its gate read false, so nothing was
// measured. Same grammar as Pass and Fail - "TEST_RESULT: N/A: <what>" with
// " - <details>" appended when details exist - counted separately from both
// and never a failure, because a scenario's verdict stays decided by its
// failure count alone. |details| carries the gate's reason, so a reader can
// tell a deliberate skip from a missing measurement.
void Skipped(const QString &what, const QString &details = QString());
// |details| is an observation - the reading the verdict was made against -
// and it is printed whether the check holds or not, so a green log says what
// each check reached and a passing run can be audited without re-running it.
// It used to be written only on the failing branch, which is what attempt 2
// of 2026/08/26/add-wallet-refresh-readiness-helper was spent on: that
// self-test passed its observations here, printed none of them, and two of
// its acceptance criteria could not be read from its own green log.
// Text that is only true after a failure is therefore conditional at the
// call site - ok ? QString() : u"out of tolerance"_q - which leaves the
// passing line exactly "TEST_RESULT: PASS: <what>".
void Check(bool ok, const QString &what, const QString &details = QString());
void Note(const QString &text);
// The number format for telemetry an overlay prints into a scanned log,
// from
// 2026/10/08/give-overlay-telemetry-a-number-format-the-secrecy-scan-cannot-count.
// An ordinary decimal leaves each digit run between "=", "-", "." or a
// blank, so the run is bounded and can equal a short secret by
// coincidence. It then decides the scan at a "<file>|<head>|-" plain-line
// site (or at the field's name when the number is a field's whole value),
// which no computed-field declaration may exempt. This prints "-" for a
// value below zero, the integer digits, "p" and exactly |decimals|
// fraction digits (a negative |decimals| counts as 0), rounding as
// QString::number(.., 'f', ..) does: 42 -> "42p", 16.31 with 2 ->
// "16p31", -4821.75 with 2 -> "-4821p75". NaN and the infinities print
// "nan", "inf" and "-inf". Every digit run touches the "p", and an
// occurrence counts only with no letter or digit right before and after
// it, so no digit run of a formatted number is ever counted. A judge
// script reads it as float(v.replace("p", ".")).
//
// Format only numbers an overlay, or a converted helper under the request,
// measured or computed, never a fixture secret or a number read from one:
// the format hides digits from the scan, so it would hide that leak too.
// Choose |decimals| per quantity, never from a secret. A TelemetryNumber is
// a word to the phrase matcher: it ends a word run where a bare number is
// neutral. So never print one as a list index or between words copied from
// product or fixture text, and keep such text out of telemetry rows.
[[nodiscard]] QString TelemetryNumber(double value, int decimals = 0);
// How a converted shared helper prints the numbers it measures, counts or
// times. Ordinary is today's decimal ("4821", "-12"); Telemetry is
// TelemetryNumber(value) with |decimals| 0 ("4821p", "-12p"). A converted
// helper formats each row through a pure formatter taking a NumberFormat,
// and its logging path passes HelperNumberFormat().
enum class NumberFormat {
Ordinary,
Telemetry,
};
// One explicit, process-wide, one-way ask that the converted shared
// helpers print the numbers they themselves measure, count or time as
// TelemetryNumbers. It takes no argument and reads nothing, so it cannot
// depend on a secret. Call it unconditionally at the campaign's first
// stage, before any row it may scan, never under a condition and never
// from code that read a fixture secret. The first call logs exactly
// "TELEMETRY_NUMBERS: requested" (a judge splits test_log.txt at that
// line); a later call does nothing and logs nothing. Every converted row
// formatted after it prints TelemetryNumbers, one formatted before it keeps
// ordinary integers, and there is no undo: a text a helper formatted before
// the ask and a caller prints after it, such as a kept
// RoundTrip::observation or ClockedRun::refusal, stays ordinary.
//
// The converted rows: GEOMETRY: (LogGeometry); the CheckNear verdict; the
// PostPaintSampler kind=post sample and kind=stop rows; the
// RunClockedFrames kind=frame row, its "frame <k> at <ms> ms" subject, its
// gate and refusal reasons and its request refusal; the Probe window
// bounds, checkCountSince's counts and the round-trip tallies and times;
// the DiscriminatingScan examined= tally row and its refusal (the secrecy
// scan's "SECRECY: <class>" reports among them).
// Caller text and detail lists, PostPaintSampler::details(), the sampler's
// empty-window note, the scan's SECRECY_* rows and the runner's rows keep
// ordinary integers. Grammars in both formats, the caller-number rule and
// the failure route: Telegram/SourceFiles/test/README.md, the test_log.h
// (TelemetryNumber, RequestTelemetryNumbers) catalog row and "Proving no
// fixture secret reached the logs".
void RequestTelemetryNumbers();
// Ordinary until RequestTelemetryNumbers() ran, Telemetry after.
[[nodiscard]] NumberFormat HelperNumberFormat();
// QString::number(value) under Ordinary, TelemetryNumber(double(value))
// under Telemetry.
[[nodiscard]] QString HelperNumber(qint64 value, NumberFormat format);
// text.arg(value) - the integer overload, exactly as before - under
// Ordinary, and text.arg(TelemetryNumber(double(value))) under Telemetry.
// For an .arg chain that already substituted caller text: the two arg
// overloads differ on a %L<n> marker that text may carry (the integer one
// localizes it, the string one does not), so only this keeps such a chain
// byte-identical without the request.
[[nodiscard]] QString ArgNumber(
const QString &text,
qint64 value,
NumberFormat format);
// CheckNear's check text, "<what> (actual <a>, expected <e> ±<t>)", with
// its numbers in |format|. Pure, so a self-test reads both formats.
[[nodiscard]] QString CheckNearText(
int actual,
int expected,
int tolerance,
const QString &what,
NumberFormat format);
// PASS/FAIL on |actual| being within |tolerance| of |expected|, logging the
// measured values either way:
// "TEST_RESULT: PASS: <what> (actual <a>, expected <e> ±<t>)", and on FAIL
// the same with " - out of tolerance" appended. <a>, <e> and <t> print as
// ordinary integers ("actual 4826, expected 4821 ±37"), or as
// TelemetryNumbers after RequestTelemetryNumbers() ("actual 4826p, expected
// 4821p ±37p"). So under the request the caller's |actual|, |expected| and
// |tolerance| print in a format that hides digits from the secrecy scan:
// they must never be a fixture secret or a number read from one.
void CheckNear(
int actual,
int expected,
int tolerance,
const QString &what);
// LogGeometry's whole row, "GEOMETRY: <name>: x=<x> y=<y> w=<w> h=<h>",
// with its numbers in |format|. Pure, so a self-test reads both formats.
[[nodiscard]] QString GeometryText(
const QString &name,
const QRect &rect,
NumberFormat format);
// GEOMETRY: <name>: x=<x> y=<y> w=<w> h=<h>, Ordinary or Telemetry numbers.
void LogGeometry(const QString &name, const QRect &rect);
[[nodiscard]] int FailureCount();
[[nodiscard]] int SkippedCount();
// Writes the TEST_COMPLETE marker the external runner waits for, and
// records when it was written. CompletedAt() is crl::now() at that
// moment and 0 before it.
void Complete();
[[nodiscard]] crl::time CompletedAt();
} // namespace Test