mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
The three cases that force a write failure - CardDatabaseTest.Xml4ShortWriteKeepsInstalledFile, OracleDatabaseWriter.ShortWriteKeepsPreviouslyInstalledDatabase and SpoilerFileTest.ShortWriteLeavesExistingFileIntact - all forked a child, lowered RLIMIT_FSIZE and ignored SIGXFSZ. That child is killed by SIGSEGV on the macOS runners, so all three have been red on every macOS leg of master since they landed, while the same cases pass on Linux. Cap the soft limit in the test process instead: ctest already runs one case per process, and the guard restores the limit and the SIGXFSZ handler, so a whole binary keeps working. Same real EFBIG failure, no fork, and macOS keeps its coverage of the writers.
135 lines
4.7 KiB
C++
135 lines
4.7 KiB
C++
#include "client/network/update/card_spoiler/spoiler_file.h"
|
|
#include "support/scoped_file_size_limit.h"
|
|
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QTemporaryDir>
|
|
#include <gtest/gtest.h>
|
|
|
|
namespace
|
|
{
|
|
class SpoilerFileTest : public ::testing::Test
|
|
{
|
|
protected:
|
|
QTemporaryDir directory;
|
|
QString path;
|
|
QString error;
|
|
const QByteArray original = "<cockatrice_carddatabase version=\"4\"><cards/></cockatrice_carddatabase>";
|
|
|
|
void SetUp() override
|
|
{
|
|
ASSERT_TRUE(directory.isValid());
|
|
path = directory.filePath("spoiler.xml");
|
|
QFile file(path);
|
|
ASSERT_TRUE(file.open(QIODevice::WriteOnly));
|
|
ASSERT_EQ(file.write(original), original.size());
|
|
}
|
|
|
|
QByteArray read()
|
|
{
|
|
QFile file(path);
|
|
EXPECT_TRUE(file.open(QIODevice::ReadOnly));
|
|
return file.readAll();
|
|
}
|
|
|
|
SpoilerFile::SaveResult save(const QByteArray &data)
|
|
{
|
|
return SpoilerFile::save(path, data, [] { return true; }, error);
|
|
}
|
|
};
|
|
|
|
TEST_F(SpoilerFileTest, InvalidDocumentsPreserveExistingFile)
|
|
{
|
|
for (const QByteArray &invalid :
|
|
{QByteArray(), QByteArray("<html><cards/></html>"),
|
|
QByteArray("<cockatrice_carddatabase version=\"2\"><cards/></cockatrice_carddatabase>"),
|
|
QByteArray("<cockatrice_carddatabase><cards/></cockatrice_carddatabase>"),
|
|
QByteArray("<cockatrice_carddatabase version=\"4\"><sets/></cockatrice_carddatabase>"),
|
|
QByteArray("<cockatrice_carddatabase version=\"4\"><cards>"),
|
|
QByteArray("<cockatrice_carddatabase version=\"4\"><cards/></cockatrice_carddatabase><extra/>")}) {
|
|
EXPECT_EQ(save(invalid), SpoilerFile::SaveResult::Failed);
|
|
EXPECT_FALSE(error.isEmpty());
|
|
EXPECT_EQ(read(), original);
|
|
}
|
|
}
|
|
|
|
TEST_F(SpoilerFileTest, SupportedVersionsAreAccepted)
|
|
{
|
|
EXPECT_TRUE(SpoilerFile::validate(original).isEmpty());
|
|
EXPECT_TRUE(
|
|
SpoilerFile::validate("<cockatrice_carddatabase version=\"3\"><cards/></cockatrice_carddatabase>").isEmpty());
|
|
}
|
|
|
|
TEST_F(SpoilerFileTest, IdenticalFileIsUnchanged)
|
|
{
|
|
EXPECT_EQ(save(original), SpoilerFile::SaveResult::Unchanged);
|
|
EXPECT_TRUE(error.isEmpty());
|
|
EXPECT_EQ(read(), original);
|
|
}
|
|
|
|
TEST_F(SpoilerFileTest, CompleteFileHashDetectsChangesAfterLargeCommonPrefix)
|
|
{
|
|
QByteArray changed = "<cockatrice_carddatabase version=\"4\"><!--";
|
|
changed += QByteArray(100000, 'x');
|
|
changed += "--><cards/></cockatrice_carddatabase>";
|
|
ASSERT_EQ(save(changed), SpoilerFile::SaveResult::Written);
|
|
changed.replace("<cards/>", "<cards></cards>");
|
|
EXPECT_EQ(save(changed), SpoilerFile::SaveResult::Written);
|
|
EXPECT_EQ(read(), changed);
|
|
EXPECT_TRUE(error.isEmpty());
|
|
}
|
|
|
|
TEST_F(SpoilerFileTest, CancellationBeforeWritePreservesExistingFile)
|
|
{
|
|
EXPECT_EQ(SpoilerFile::save(path, original, [] { return false; }, error), SpoilerFile::SaveResult::Failed);
|
|
EXPECT_FALSE(error.isEmpty());
|
|
EXPECT_EQ(read(), original);
|
|
}
|
|
|
|
TEST_F(SpoilerFileTest, CancellationBeforeCommitPreservesExistingFile)
|
|
{
|
|
int checks = 0;
|
|
EXPECT_EQ(SpoilerFile::save(
|
|
path, original + "\n", [&checks] { return ++checks == 1; }, error),
|
|
SpoilerFile::SaveResult::Failed);
|
|
EXPECT_EQ(checks, 2);
|
|
EXPECT_FALSE(error.isEmpty());
|
|
EXPECT_EQ(read(), original);
|
|
EXPECT_EQ(QDir(directory.path()).entryList(QDir::Files), QStringList{"spoiler.xml"});
|
|
}
|
|
|
|
TEST_F(SpoilerFileTest, MissingParentIsCreated)
|
|
{
|
|
path = directory.filePath("nested/spoiler.xml");
|
|
EXPECT_EQ(save(original), SpoilerFile::SaveResult::Written);
|
|
EXPECT_EQ(read(), original);
|
|
}
|
|
|
|
TEST_F(SpoilerFileTest, FileInParentPathReportsFailureWithoutModifyingIt)
|
|
{
|
|
EXPECT_EQ(SpoilerFile::save(
|
|
QDir(path).filePath("spoiler.xml"), original, [] { return true; }, error),
|
|
SpoilerFile::SaveResult::Failed);
|
|
EXPECT_FALSE(error.isEmpty());
|
|
EXPECT_EQ(read(), original);
|
|
}
|
|
|
|
#ifdef Q_OS_UNIX
|
|
TEST_F(SpoilerFileTest, ShortWriteLeavesExistingFileIntact)
|
|
{
|
|
const QByteArray changed = "<cockatrice_carddatabase version=\"4\"><!--" + QByteArray(100000, 'x') +
|
|
"--><cards/></cockatrice_carddatabase>";
|
|
// A real short write exercises QSaveFile's buffered flush failure without depending
|
|
// on disk capacity. The cap is scoped rather than forked, because a death-test child
|
|
// under RLIMIT_FSIZE is killed by SIGSEGV on macOS.
|
|
test_support::ScopedFileSizeLimit fileSizeCap(128);
|
|
ASSERT_TRUE(fileSizeCap.isValid());
|
|
|
|
EXPECT_EQ(save(changed), SpoilerFile::SaveResult::Failed);
|
|
EXPECT_FALSE(error.isEmpty());
|
|
EXPECT_EQ(read(), original);
|
|
EXPECT_EQ(QDir(directory.path()).entryList(QDir::Files), QStringList{"spoiler.xml"});
|
|
}
|
|
#endif
|
|
} // namespace
|