ZaStoGram_desktop/Telegram/SourceFiles/test/test_log.cpp
loop-uh 0fd5d76a67 Merge upstream v7.3.1
Слияние с tdesktop от 64ca5475f2 до тега v7.3.1 (7.2.10 beta, 7.3, 7.3.1).

- Версия 7.3.1 (7003001), BetaChannel 0, комментарий ZaStoGram сохранён.
- Обновлятор: оригинал убрал пакеты v1 (RSA), а наш Packer и Forgejo
  выпускают именно их. v1 оставлен как UnpackUpdateV1 рядом с v2 и
  использует новые проверенные распаковку и извлечение файлов.
- mtproto: исправления импорта авторизации из оригинала перенесены в нашу
  структуру RequestRegistry; пути include новых файлов кошелька
  приведены к нашей раскладке mtproto/.
- prepare.py: стадии оригинала (ucrt64, ffmpeg 8, openssl 3.5, libheif,
  Rust, wallet-engine) с нашими skip-debug и patches_extra.
- lib_ui, lib_lottie: форки слиты с версиями из v7.3.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:43:47 +03:00

286 lines
6.6 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
*/
#include "test/test_log.h"
#ifdef _DEBUG
#include "settings.h"
#include <cmath>
namespace Test {
namespace {
auto FailuresCount = 0;
auto SkippedCountValue = 0;
auto CompletedAtValue = crl::time(0);
auto TelemetryNumbersValue = false;
[[nodiscard]] QString EnsuredDir(const QString &path) {
QDir().mkpath(path);
return path.endsWith('/') ? path : (path + '/');
}
// Exactly the characters Python's str.splitlines() breaks on, which is the
// line grammar both readers of test_log.txt use - parse_test_log and
// log_marks_complete in the external runner. CR and LF are listed
// individually, so a CRLF pair needs no pair state and no lookahead: it is
// simply its two code points, each matched on its own. All ten are single
// UTF-16 code units in the BMP, so matching per character is total and a
// surrogate half can never be mistaken for one of them.
constexpr auto kLineSeparators = std::array<ushort, 10>{
0x000A, 0x000B, 0x000C, 0x000D,
0x001C, 0x001D, 0x001E,
0x0085, 0x2028, 0x2029,
};
[[nodiscard]] bool IsLineSeparator(QChar ch) {
return ranges::contains(kLineSeparators, ch.unicode());
}
// Makes one write one physical line: each separator is replaced by a visible
// \uXXXX token naming its code point, so a record whose text carried a break
// stays one row and stays readable past the break. Every token ends in a hex
// digit, never in whitespace, so a reader's line.rstrip() == "TEST_COMPLETE"
// and Qt's Windows QIODevice::Text translation - which scans for '\n' alone -
// are both undisturbed. The escape character is deliberately not doubled, so
// separator-free text comes back unchanged, byte for byte, and an author's
// own backslash is never rewritten.
[[nodiscard]] QString OneLine(const QString &line) {
if (ranges::none_of(line, IsLineSeparator)) {
return line;
}
auto result = QString();
result.reserve(line.size() + 8);
for (const auto ch : line) {
if (IsLineSeparator(ch)) {
result.append(u"\\u%1"_q.arg(
QString::number(uint(ch.unicode()), 16)
.toUpper()
.rightJustified(4, QChar('0'))));
} else {
result.append(ch);
}
}
return result;
}
} // namespace
QString EvidenceDir() {
static const auto result = [] {
const auto value = qEnvironmentVariable("TDESKTOP_TEST_EVIDENCE_DIR");
const auto path = value.isEmpty()
? (cWorkingDir() + u"test_evidence"_q)
: value;
return EnsuredDir(QFileInfo(path).absoluteFilePath());
}();
return result;
}
QString ScreenshotsDir() {
static const auto result = EnsuredDir(EvidenceDir() + u"screenshots"_q);
return result;
}
void LogRaw(const QString &line) {
auto file = QFile(EvidenceDir() + u"test_log.txt"_q);
if (!file.open(QIODevice::Append | QIODevice::Text)) {
return;
}
file.write((OneLine(line) + u"\n"_q).toUtf8());
file.flush();
}
void Step(const QString &text) {
LogRaw(u"TEST_STEP: %1"_q.arg(text));
}
void Pass(const QString &text, const QString &details) {
LogRaw(details.isEmpty()
? u"TEST_RESULT: PASS: %1"_q.arg(text)
: u"TEST_RESULT: PASS: %1 - %2"_q.arg(text, details));
}
void Fail(const QString &text, const QString &details) {
++FailuresCount;
LogRaw(details.isEmpty()
? u"TEST_RESULT: FAIL: %1"_q.arg(text)
: u"TEST_RESULT: FAIL: %1 - %2"_q.arg(text, details));
}
void Skipped(const QString &text, const QString &details) {
++SkippedCountValue;
LogRaw(details.isEmpty()
? u"TEST_RESULT: N/A: %1"_q.arg(text)
: u"TEST_RESULT: N/A: %1 - %2"_q.arg(text, details));
}
void Check(bool ok, const QString &what, const QString &details) {
if (ok) {
Pass(what, details);
} else {
Fail(what, details);
}
}
void Note(const QString &text) {
LogRaw(u"NOTE: %1"_q.arg(text));
}
QString TelemetryNumber(double value, int decimals) {
if (std::isnan(value)) {
return u"nan"_q;
}
const auto sign = (value < 0.) ? u"-"_q : QString();
if (std::isinf(value)) {
return sign + u"inf"_q;
}
const auto places = std::max(decimals, 0);
auto digits = QString::number(std::abs(value), 'f', places);
return sign + (places
? digits.replace(QChar('.'), QChar('p'))
: (digits + QChar('p')));
}
void RequestTelemetryNumbers() {
if (TelemetryNumbersValue) {
return;
}
TelemetryNumbersValue = true;
LogRaw(u"TELEMETRY_NUMBERS: requested"_q);
}
NumberFormat HelperNumberFormat() {
return TelemetryNumbersValue
? NumberFormat::Telemetry
: NumberFormat::Ordinary;
}
QString HelperNumber(qint64 value, NumberFormat format) {
return (format == NumberFormat::Telemetry)
? TelemetryNumber(double(value))
: QString::number(value);
}
QString ArgNumber(const QString &text, qint64 value, NumberFormat format) {
return (format == NumberFormat::Telemetry)
? text.arg(TelemetryNumber(double(value)))
: text.arg(value);
}
QString CheckNearText(
int actual,
int expected,
int tolerance,
const QString &what,
NumberFormat format) {
return u"%1 (actual %2, expected %3 ±%4)"_q.arg(
what,
HelperNumber(actual, format),
HelperNumber(expected, format),
HelperNumber(tolerance, format));
}
void CheckNear(
int actual,
int expected,
int tolerance,
const QString &what) {
const auto ok = (std::abs(actual - expected) <= tolerance);
Check(
ok,
CheckNearText(actual, expected, tolerance, what, HelperNumberFormat()),
ok ? QString() : u"out of tolerance"_q);
}
QString GeometryText(
const QString &name,
const QRect &rect,
NumberFormat format) {
return u"GEOMETRY: %1: x=%2 y=%3 w=%4 h=%5"_q.arg(
name,
HelperNumber(rect.x(), format),
HelperNumber(rect.y(), format),
HelperNumber(rect.width(), format),
HelperNumber(rect.height(), format));
}
void LogGeometry(const QString &name, const QRect &rect) {
LogRaw(GeometryText(name, rect, HelperNumberFormat()));
}
int FailureCount() {
return FailuresCount;
}
int SkippedCount() {
return SkippedCountValue;
}
void Complete() {
CompletedAtValue = crl::now();
LogRaw(u"TEST_COMPLETE"_q);
}
crl::time CompletedAt() {
return CompletedAtValue;
}
} // namespace Test
#else // _DEBUG
namespace Test {
QString EvidenceDir() {
return {};
}
QString ScreenshotsDir() {
return {};
}
void LogRaw(const QString &) {
}
void Step(const QString &) {
}
void Pass(const QString &) {
}
void Fail(const QString &, const QString &) {
}
void Check(bool, const QString &, const QString &) {
}
void Note(const QString &) {
}
void CheckNear(int, int, int, const QString &) {
}
void LogGeometry(const QString &, const QRect &) {
}
int FailureCount() {
return 0;
}
void Complete() {
}
crl::time CompletedAt() {
return 0;
}
} // namespace Test
#endif // _DEBUG