mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-08 02:53:25 -08:00
Breakpad on all platforms (no crashpad packaging); remove the Windows MiniDumpWithFullMemory filter and the installer WER LocalDumps full-dump registration. QLockFile per-process session tracking replaces the Windows-only .dmp scan, placed after parser.process and the instance hand-off so help/version exits and handed-off secondaries never leak a lock. CI: Release-only DSN bake from SENTRY_DSN secret, tag-only sentry-cli symbol upload (org cockatrice, project native). Tests: 533/533 ctest green, format clean, code gate PASS.
427 lines
19 KiB
C++
427 lines
19 KiB
C++
#include "client/crash/crash_reporter.h"
|
|
|
|
#include <QByteArray>
|
|
#include <QCoreApplication>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QLockFile>
|
|
#include <QString>
|
|
#include <QSysInfo>
|
|
#include <QTemporaryDir>
|
|
#include <QTextStream>
|
|
#include <gtest/gtest.h>
|
|
#include <libcockatrice/settings/personal_settings.h>
|
|
#include <string>
|
|
|
|
namespace
|
|
{
|
|
|
|
TEST(CrashReporterTest, SanitizeLogLineStripsAnsiColorCodes)
|
|
{
|
|
ASSERT_EQ(CrashReporter::sanitizeLogLine(QStringLiteral("\033[32mhello\033[0m")), QStringLiteral("hello"));
|
|
ASSERT_EQ(CrashReporter::sanitizeLogLine(QStringLiteral("\033[1;31mfatal\033[0m")), QStringLiteral("fatal"));
|
|
ASSERT_EQ(CrashReporter::sanitizeLogLine(QStringLiteral("plain line")), QStringLiteral("plain line"));
|
|
}
|
|
|
|
TEST(CrashReporterTest, RedactCockatriceUrlsKeepsSchemeAndHostOnly)
|
|
{
|
|
ASSERT_EQ(
|
|
CrashReporter::redactCockatriceUrls(QStringLiteral("open cockatrice://opendeck?token=secret&user=me now")),
|
|
QStringLiteral("open cockatrice://opendeck now"));
|
|
}
|
|
|
|
TEST(CrashReporterTest, RedactCockatriceUrlsIsIdempotent)
|
|
{
|
|
// A secret-free URL must not match again after redaction (regression: this hung forever).
|
|
ASSERT_EQ(CrashReporter::redactCockatriceUrls(QStringLiteral("open cockatrice://opendeck now")),
|
|
QStringLiteral("open cockatrice://opendeck now"));
|
|
}
|
|
|
|
TEST(CrashReporterTest, RedactCockatriceUrlsLeavesOtherTextAlone)
|
|
{
|
|
ASSERT_EQ(CrashReporter::redactCockatriceUrls(QStringLiteral("https://cockatrice.github.io/?token=secret")),
|
|
QStringLiteral("https://cockatrice.github.io/?token=secret"));
|
|
ASSERT_EQ(CrashReporter::sanitizeLogLine(QStringLiteral("")), QStringLiteral(""));
|
|
}
|
|
|
|
TEST(CrashReporterTest, InitSkippedWithoutDsn)
|
|
{
|
|
CrashReporter reporter;
|
|
reporter.init(QString(), QStringLiteral("3.1.2"), QDir::tempPath() + QStringLiteral("/cockatrice-crash-test"),
|
|
PersonalSettings::CrashReportingMode::Ask);
|
|
ASSERT_FALSE(reporter.isReady());
|
|
reporter.shutdown();
|
|
ASSERT_FALSE(reporter.isReady());
|
|
}
|
|
|
|
TEST(CrashReporterTest, InitSkippedWhenNeverSend)
|
|
{
|
|
CrashReporter reporter;
|
|
reporter.init(QStringLiteral("https://key@sentry.io/1"), QStringLiteral("3.1.2"),
|
|
QDir::tempPath() + QStringLiteral("/cockatrice-crash-test"),
|
|
PersonalSettings::CrashReportingMode::NeverSend);
|
|
ASSERT_FALSE(reporter.isReady());
|
|
reporter.shutdown();
|
|
}
|
|
|
|
// The init() tests above compile to no-ops when the SDK is off, so they pass
|
|
// regardless of the gating; the gating decision itself is covered here, in both
|
|
// build modes, through the SDK-independent helper init() consults.
|
|
TEST(CrashReporterTest, ReportingEnabledGating)
|
|
{
|
|
using Mode = PersonalSettings::CrashReportingMode;
|
|
const QString dsn = QStringLiteral("https://key@sentry.io/1");
|
|
ASSERT_FALSE(CrashReporter::isReportingEnabled(QString(), Mode::Ask));
|
|
ASSERT_FALSE(CrashReporter::isReportingEnabled(QString(), Mode::AlwaysSend));
|
|
ASSERT_FALSE(CrashReporter::isReportingEnabled(QString(), Mode::NeverSend));
|
|
ASSERT_TRUE(CrashReporter::isReportingEnabled(dsn, Mode::Ask));
|
|
ASSERT_TRUE(CrashReporter::isReportingEnabled(dsn, Mode::AlwaysSend));
|
|
ASSERT_FALSE(CrashReporter::isReportingEnabled(dsn, Mode::NeverSend));
|
|
}
|
|
|
|
TEST(CrashReporterTest, IsDsnWellFormedAcceptsWhatTheBuildAcceptsAndRejectsUiLinks)
|
|
{
|
|
ASSERT_TRUE(CrashReporter::isDsnWellFormed(QStringLiteral("https://abc123@o0.ingest.sentry.io/1234567")));
|
|
ASSERT_TRUE(CrashReporter::isDsnWellFormed(QStringLiteral("http://key@localhost:9000/42")));
|
|
// Unanchored like cmake/CrashReporting.cmake's MATCHES, so a DSN the build baked is never
|
|
// rejected here: a trailing path still matches.
|
|
ASSERT_TRUE(CrashReporter::isDsnWellFormed(QStringLiteral("https://key@host.example/7/extra")));
|
|
// The project-settings URL is the mistake a tester actually makes.
|
|
ASSERT_FALSE(CrashReporter::isDsnWellFormed(
|
|
QStringLiteral("https://sentry.io/settings/cockatrice/projects/cockatrice-desktop/keys/")));
|
|
ASSERT_FALSE(CrashReporter::isDsnWellFormed(QStringLiteral("https://key@host.example/not-a-number")));
|
|
ASSERT_FALSE(CrashReporter::isDsnWellFormed(QStringLiteral("o0.ingest.sentry.io/1234567")));
|
|
ASSERT_FALSE(CrashReporter::isDsnWellFormed(QString()));
|
|
}
|
|
|
|
TEST(CrashReporterTest, DescribeDsnNamesHostAndProjectWithoutTheKey)
|
|
{
|
|
const QString described =
|
|
CrashReporter::describeDsn(QStringLiteral("https://supersecretkey@o0.ingest.sentry.io/1234567"));
|
|
ASSERT_EQ(described, QStringLiteral("o0.ingest.sentry.io (project 1234567)"));
|
|
ASSERT_FALSE(described.contains(QStringLiteral("supersecretkey")));
|
|
ASSERT_TRUE(CrashReporter::describeDsn(QStringLiteral("not a dsn")).isEmpty());
|
|
ASSERT_TRUE(CrashReporter::describeDsn(QString()).isEmpty());
|
|
}
|
|
|
|
TEST(CrashReporterTest, DescribeOriginMismatchReportsOnlyRealDestinationChanges)
|
|
{
|
|
const QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
const QString envelopePath = dir.filePath(QStringLiteral("crash.envelope"));
|
|
QFile file(envelopePath);
|
|
ASSERT_TRUE(file.open(QIODevice::WriteOnly));
|
|
file.write(QByteArrayLiteral("{\"event_id\":\"x\",\"dsn\":\"https://keyA@hostA.example/111\"}\n"));
|
|
file.write(QByteArrayLiteral("{\"type\":\"event\",\"length\":2}\n{}\n"));
|
|
file.close();
|
|
|
|
const QString described =
|
|
CrashReporter::describeOriginMismatch(envelopePath, QStringLiteral("https://keyB@hostB.example/222"));
|
|
ASSERT_FALSE(described.isEmpty());
|
|
ASSERT_TRUE(described.contains(QStringLiteral("hostA.example")));
|
|
ASSERT_TRUE(described.contains(QStringLiteral("hostB.example")));
|
|
ASSERT_FALSE(described.contains(QStringLiteral("keyA")));
|
|
ASSERT_FALSE(described.contains(QStringLiteral("keyB")));
|
|
|
|
// A different key for the SAME host and project is not a routing change and must stay quiet.
|
|
ASSERT_TRUE(
|
|
CrashReporter::describeOriginMismatch(envelopePath, QStringLiteral("https://otherkey@hostA.example/111"))
|
|
.isEmpty());
|
|
// An unreadable envelope, a header without a dsn, or no current dsn are all silent.
|
|
ASSERT_TRUE(CrashReporter::describeOriginMismatch(dir.filePath(QStringLiteral("absent.envelope")),
|
|
QStringLiteral("https://keyB@hostB.example/222"))
|
|
.isEmpty());
|
|
ASSERT_TRUE(CrashReporter::describeOriginMismatch(envelopePath, QString()).isEmpty());
|
|
const QString headerlessPath = dir.filePath(QStringLiteral("no-dsn.envelope"));
|
|
QFile headerless(headerlessPath);
|
|
ASSERT_TRUE(headerless.open(QIODevice::WriteOnly));
|
|
headerless.write(QByteArrayLiteral("{\"event_id\":\"y\"}\n"));
|
|
headerless.close();
|
|
ASSERT_TRUE(CrashReporter::describeOriginMismatch(headerlessPath, QStringLiteral("https://keyB@hostB.example/222"))
|
|
.isEmpty());
|
|
}
|
|
|
|
TEST(CrashReporterTest, ShutdownWithoutInitIsSafe)
|
|
{
|
|
CrashReporter reporter;
|
|
ASSERT_FALSE(reporter.isReady());
|
|
reporter.shutdown();
|
|
ASSERT_FALSE(reporter.isReady());
|
|
}
|
|
|
|
TEST(CrashReporterTest, ConsentAndSendWithoutDsnAreSafeNoOps)
|
|
{
|
|
CrashReporter reporter;
|
|
reporter.init(QString(), QStringLiteral("3.1.2"), QDir::tempPath() + QStringLiteral("/cockatrice-crash-test"),
|
|
PersonalSettings::CrashReportingMode::Ask);
|
|
ASSERT_FALSE(reporter.isReady());
|
|
reporter.giveConsent();
|
|
reporter.revokeConsent();
|
|
ASSERT_FALSE(reporter.isReady());
|
|
ASSERT_FALSE(reporter.sendReport(QStringLiteral("preview")));
|
|
ASSERT_FALSE(reporter.sendReport(QString()));
|
|
reporter.shutdown();
|
|
ASSERT_FALSE(reporter.isReady());
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
QByteArray readFileBytes(const QString &path)
|
|
{
|
|
QFile file(path);
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
return {};
|
|
}
|
|
return file.readAll();
|
|
}
|
|
|
|
bool writeFileBytes(const QString &path, const QByteArray &content)
|
|
{
|
|
QFile file(path);
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
|
return false;
|
|
}
|
|
return file.write(content) == content.size();
|
|
}
|
|
|
|
QString uuidOf(char marker)
|
|
{
|
|
// 36 chars, 4 hyphens — structurally a UUID, content irrelevant for these tests.
|
|
return QStringLiteral("00000000-0000-4000-8000-00000000000%1").arg(marker);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(CrashReporterTest, AppendAttachmentAppendsWellFormedItemAfterExistingContent)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
const QString path = dir.filePath(QStringLiteral("event.envelope"));
|
|
// Existing envelope content deliberately WITHOUT a trailing newline.
|
|
const QByteArray original = QByteArrayLiteral("{\"event_id\":\"x\"}\n{\"type\":\"event\",\"length\":2}\n{}");
|
|
ASSERT_TRUE(writeFileBytes(path, original));
|
|
|
|
ASSERT_TRUE(
|
|
CrashReporter::appendAttachmentToEnvelope(path, QStringLiteral("crash-log.txt"), QByteArrayLiteral("hello")));
|
|
const QByteArray expected = original + '\n' +
|
|
QByteArrayLiteral("{\"type\":\"attachment\",\"length\":5,\"attachment_length\":5,"
|
|
"\"filename\":\"crash-log.txt\",\"content_type\":\"text/plain\"}\n") +
|
|
QByteArrayLiteral("hello");
|
|
ASSERT_EQ(readFileBytes(path), expected);
|
|
}
|
|
|
|
TEST(CrashReporterTest, AppendAttachmentOnEmptyFileHasNoLeadingNewline)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
const QString path = dir.filePath(QStringLiteral("event.envelope"));
|
|
ASSERT_TRUE(writeFileBytes(path, QByteArray()));
|
|
|
|
ASSERT_TRUE(
|
|
CrashReporter::appendAttachmentToEnvelope(path, QStringLiteral("user-note.txt"), QByteArrayLiteral("note")));
|
|
const QByteArray expected = QByteArrayLiteral("{\"type\":\"attachment\",\"length\":4,\"attachment_length\":4,"
|
|
"\"filename\":\"user-note.txt\",\"content_type\":\"text/plain\"}\n") +
|
|
QByteArrayLiteral("note");
|
|
ASSERT_EQ(readFileBytes(path), expected);
|
|
}
|
|
|
|
TEST(CrashReporterTest, AppendAttachmentRejectsMissingFile)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
ASSERT_FALSE(CrashReporter::appendAttachmentToEnvelope(dir.filePath(QStringLiteral("missing.envelope")),
|
|
QStringLiteral("crash-log.txt"), QByteArrayLiteral("x")));
|
|
}
|
|
|
|
TEST(CrashReporterTest, AppendAttachmentLengthIsByteAccurate)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
const QString path = dir.filePath(QStringLiteral("event.envelope"));
|
|
ASSERT_TRUE(writeFileBytes(path, QByteArrayLiteral("{}")));
|
|
|
|
const QByteArray utf8Payload = QString::fromUtf8("héllo").toUtf8();
|
|
ASSERT_EQ(utf8Payload.size(), 6); // 5 characters, "é" is 2 bytes
|
|
ASSERT_TRUE(CrashReporter::appendAttachmentToEnvelope(path, QStringLiteral("crash-log.txt"), utf8Payload));
|
|
const QByteArray content = readFileBytes(path);
|
|
ASSERT_TRUE(content.contains(QByteArrayLiteral("\"length\":6")));
|
|
ASSERT_TRUE(content.contains(QByteArrayLiteral("\"attachment_length\":6")));
|
|
ASSERT_TRUE(content.endsWith(utf8Payload));
|
|
}
|
|
|
|
// The SDK's envelope re-parser frames each item's payload by its `length` header; a
|
|
// missing or wrong length ends the payload at the first newline, so the remaining log
|
|
// lines get parsed as item headers and the whole envelope is rejected (and deleted).
|
|
TEST(CrashReporterTest, AppendAttachmentKeepsMultiLinePayloadIntact)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
const QString path = dir.filePath(QStringLiteral("event.envelope"));
|
|
ASSERT_TRUE(writeFileBytes(path, QByteArrayLiteral("{}")));
|
|
|
|
const QByteArray payload = QByteArrayLiteral("first line\nsecond line\nthird line");
|
|
ASSERT_TRUE(CrashReporter::appendAttachmentToEnvelope(path, QStringLiteral("crash-log.txt"), payload));
|
|
const QByteArray content = readFileBytes(path);
|
|
ASSERT_TRUE(content.contains("\"length\":" + QByteArray::number(payload.size())));
|
|
ASSERT_TRUE(content.contains("\"attachment_length\":" + QByteArray::number(payload.size())));
|
|
ASSERT_TRUE(content.endsWith(payload));
|
|
}
|
|
|
|
TEST(CrashReporterTest, PendingEnvelopePathsMatchesSdkNamingInCacheAndRunDirs)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
const QString dbPath = dir.path();
|
|
ASSERT_TRUE(QDir().mkpath(dbPath + QStringLiteral("/cache")));
|
|
ASSERT_TRUE(QDir().mkpath(dbPath + QStringLiteral("/aaaa.run")));
|
|
ASSERT_TRUE(QDir().mkpath(dbPath + QStringLiteral("/other")));
|
|
|
|
const QString retryName = QStringLiteral("1700000000000-0-") + uuidOf('a') + QStringLiteral(".envelope");
|
|
const QString bareName = uuidOf('b') + QStringLiteral(".envelope");
|
|
// Valid: retry-style cache file + bare run-dir file.
|
|
ASSERT_TRUE(writeFileBytes(dbPath + QStringLiteral("/cache/") + retryName, QByteArrayLiteral("{}")));
|
|
ASSERT_TRUE(writeFileBytes(dbPath + QStringLiteral("/aaaa.run/") + bareName, QByteArrayLiteral("{}")));
|
|
// Rejected: junk names and the SDK's own marker file.
|
|
ASSERT_TRUE(writeFileBytes(dbPath + QStringLiteral("/cache/") + uuidOf('c') + QStringLiteral(".envelope.bak"),
|
|
QByteArrayLiteral("{}")));
|
|
ASSERT_TRUE(writeFileBytes(dbPath + QStringLiteral("/cache/") + QStringLiteral("random.envelope"),
|
|
QByteArrayLiteral("{}")));
|
|
ASSERT_TRUE(writeFileBytes(dbPath + QStringLiteral("/aaaa.run/") + QStringLiteral("__sentry-crash.envelope"),
|
|
QByteArrayLiteral("{}")));
|
|
ASSERT_TRUE(writeFileBytes(dbPath + QStringLiteral("/other/") + bareName, QByteArrayLiteral("{}")));
|
|
|
|
const QStringList found = CrashReporter::pendingEnvelopePaths(dbPath);
|
|
ASSERT_EQ(found.size(), 2);
|
|
ASSERT_TRUE(found.contains(dbPath + QStringLiteral("/cache/") + retryName));
|
|
ASSERT_TRUE(found.contains(dbPath + QStringLiteral("/aaaa.run/") + bareName));
|
|
}
|
|
|
|
TEST(CrashReporterTest, PendingEnvelopePathsWithoutDatabaseIsEmpty)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
ASSERT_TRUE(CrashReporter::pendingEnvelopePaths(dir.filePath(QStringLiteral("no-such-db"))).isEmpty());
|
|
ASSERT_TRUE(CrashReporter::pendingEnvelopePaths(QString()).isEmpty());
|
|
}
|
|
|
|
TEST(CrashReporterTest, AttachFoldsLogAndNoteIntoPendingEnvelopes)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
const QString dbPath = dir.path();
|
|
ASSERT_TRUE(QDir().mkpath(dbPath + QStringLiteral("/cache")));
|
|
const QString envelopePath = dbPath + QStringLiteral("/cache/") + uuidOf('d') + QStringLiteral(".envelope");
|
|
ASSERT_TRUE(writeFileBytes(envelopePath, QByteArrayLiteral("{\"event_id\":\"x\"}\n{}\n")));
|
|
|
|
CrashReporter reporter;
|
|
// Empty DSN: no SDK, but init() still records the database path the dialog uses.
|
|
reporter.init(QString(), QStringLiteral("3.1.2"), dbPath, PersonalSettings::CrashReportingMode::Ask);
|
|
ASSERT_EQ(reporter.attachToPendingCrashEnvelopes(QStringLiteral("log line"), QStringLiteral("my note")), 1);
|
|
|
|
const QByteArray content = readFileBytes(envelopePath);
|
|
ASSERT_TRUE(content.startsWith(QByteArrayLiteral("{\"event_id\":\"x\"}\n{}\n")));
|
|
ASSERT_TRUE(content.contains(QByteArrayLiteral("\"filename\":\"crash-log.txt\"")));
|
|
ASSERT_TRUE(content.contains(QByteArrayLiteral("\nlog line")));
|
|
ASSERT_TRUE(content.contains(QByteArrayLiteral("\"filename\":\"user-note.txt\"")));
|
|
ASSERT_TRUE(content.endsWith(QByteArrayLiteral("\nmy note")));
|
|
}
|
|
|
|
TEST(CrashReporterTest, AttachSkipsNoteWhenEmptyAndFindsRunDirEnvelopes)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
const QString dbPath = dir.path();
|
|
ASSERT_TRUE(QDir().mkpath(dbPath + QStringLiteral("/bbbb.run")));
|
|
const QString envelopePath = dbPath + QStringLiteral("/bbbb.run/") + uuidOf('e') + QStringLiteral(".envelope");
|
|
ASSERT_TRUE(writeFileBytes(envelopePath, QByteArrayLiteral("{}")));
|
|
|
|
// Static overload (the pre-init AlwaysSend path) with an empty note.
|
|
ASSERT_EQ(CrashReporter::attachToPendingCrashEnvelopes(dbPath, QStringLiteral("log only"), QString()), 1);
|
|
const QByteArray content = readFileBytes(envelopePath);
|
|
ASSERT_TRUE(content.contains(QByteArrayLiteral("\"filename\":\"crash-log.txt\"")));
|
|
ASSERT_FALSE(content.contains(QByteArrayLiteral("user-note.txt")));
|
|
}
|
|
|
|
TEST(CrashReporterTest, BeginSessionOnEmptyDirReportsClean)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
CrashReporter reporter;
|
|
ASSERT_FALSE(reporter.beginSession(dir.path()));
|
|
reporter.endSession();
|
|
}
|
|
|
|
TEST(CrashReporterTest, BeginSessionIgnoresLiveConcurrentInstance)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
// A second QLockFile held in-process stands in for a live concurrent instance: same
|
|
// pid, alive, so the scan must leave it alone and report a clean start.
|
|
QLockFile liveInstance(dir.filePath(QStringLiteral("session-424242")));
|
|
ASSERT_TRUE(liveInstance.tryLock(0));
|
|
CrashReporter reporter;
|
|
ASSERT_FALSE(reporter.beginSession(dir.path()));
|
|
ASSERT_TRUE(QFile::exists(liveInstance.fileName()));
|
|
reporter.endSession();
|
|
liveInstance.unlock();
|
|
}
|
|
|
|
TEST(CrashReporterTest, BeginSessionDetectsStaleDeadOwnerLock)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
// A well-formed lock whose owner is dead: our own pid (so the host matches) but a
|
|
// process name that cannot match, which is how QLockFile spots pid reuse. Must be
|
|
// reported unclean and cleaned up, even though the file is brand new (this is why
|
|
// the scan probes with staleLockTime 0 rather than the 30 s default).
|
|
const QString stalePath = dir.filePath(QStringLiteral("session-stale-owner"));
|
|
QFile stale(stalePath);
|
|
ASSERT_TRUE(stale.open(QIODevice::WriteOnly | QIODevice::Text));
|
|
QTextStream out(&stale);
|
|
out << QCoreApplication::applicationPid() << '\n'
|
|
<< QStringLiteral("definitely-not-the-test-binary") << '\n'
|
|
<< QSysInfo::machineHostName() << '\n';
|
|
stale.close();
|
|
|
|
CrashReporter reporter;
|
|
ASSERT_TRUE(reporter.beginSession(dir.path()));
|
|
ASSERT_FALSE(QFile::exists(stalePath));
|
|
reporter.endSession();
|
|
}
|
|
|
|
TEST(CrashReporterTest, BeginSessionCleansGarbageFileAndReportsUnclean)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
// An unreadable lock file has no live owner by definition: report unclean and remove it.
|
|
const QString garbagePath = dir.filePath(QStringLiteral("session-garbage"));
|
|
QFile garbage(garbagePath);
|
|
ASSERT_TRUE(garbage.open(QIODevice::WriteOnly));
|
|
garbage.write("not-a-lock-file\x00\xff", 17);
|
|
garbage.close();
|
|
|
|
CrashReporter reporter;
|
|
ASSERT_TRUE(reporter.beginSession(dir.path()));
|
|
ASSERT_FALSE(QFile::exists(garbagePath));
|
|
reporter.endSession();
|
|
}
|
|
|
|
TEST(CrashReporterTest, BeginSessionEndSessionRoundTripReportsClean)
|
|
{
|
|
QTemporaryDir dir;
|
|
ASSERT_TRUE(dir.isValid());
|
|
CrashReporter reporter;
|
|
ASSERT_FALSE(reporter.beginSession(dir.path()));
|
|
reporter.endSession();
|
|
ASSERT_FALSE(reporter.beginSession(dir.path()));
|
|
reporter.endSession();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|