Files
OmaPhoto/tests/RenderStarvationTests.cpp
T

155 lines
4.7 KiB
C++

#include "Document/BrushStroke.h"
#include "Document/LayerMask.h"
#include "Document/LiveLayerMask.h"
#include "IO/ProjectStore.h"
#include "Rendering/LiveMaskRenderer.h"
#include <QtTest>
#include <cstdlib>
#include <new>
// A render's own containers may fail to allocate; none terminates.
namespace {
// This thread's allocations left before one fails; below zero, none.
thread_local long allowed = -1;
}
void *operator new(std::size_t size)
{
if (allowed == 0)
throw std::bad_alloc();
if (allowed > 0)
--allowed;
if (void *memory = std::malloc(size))
return memory;
throw std::bad_alloc();
}
void *operator new[](std::size_t size)
{
return operator new(size);
}
// New is malloc, so free pairs; GCC 16 cannot tell.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmismatched-new-delete"
void operator delete(void *memory) noexcept
{
std::free(memory);
}
void operator delete(void *memory, std::size_t) noexcept
{
std::free(memory);
}
void operator delete[](void *memory) noexcept
{
std::free(memory);
}
void operator delete[](void *memory, std::size_t) noexcept
{
std::free(memory);
}
#pragma GCC diagnostic pop
namespace {
// Fails each allocation in turn; returns the call's count.
long starved(const std::function<void()> &call)
{
for (long count = 0; count < 100000; ++count) {
allowed = count;
try {
call();
allowed = -1;
return count;
} catch (const std::bad_alloc &) {
allowed = -1;
}
}
throw std::logic_error("the call never finished");
}
// Layers in two folders, every third clipped below.
std::vector<ProjectLayerRecord> layers(int count)
{
std::vector<ProjectLayerRecord> result;
const QUuid folders[2]{QUuid::createUuid(), QUuid::createUuid()};
for (const QUuid &folder : folders)
result.push_back({.id = folder, .name = QStringLiteral("Folder"), .isVisible = true, .transform = {.origin = {0, 0}, .size = {4, 4}},
.imageFile = std::nullopt, .isGroup = true});
for (int index = 0; index < count; ++index) {
ProjectLayerRecord layer{.id = QUuid::createUuid(), .name = QStringLiteral("Layer"), .isVisible = true,
.transform = {.origin = {0, 0}, .size = {4, 4}}, .imageFile = QStringLiteral("L.png"),
.parentID = folders[index % 2]};
if (index % 3 == 2)
layer.maskSourceID = result.back().id;
result.push_back(layer);
}
return result;
}
}
class RenderStarvationTests : public QObject {
Q_OBJECT
private slots:
void validatingTheGraphThrows();
void preparingStacksThrows();
void folderClipsThrow();
};
void RenderStarvationTests::validatingTheGraphThrows()
{
const std::vector<ProjectLayerRecord> records = layers(64);
QVERIFY(starved([&] { LiveMaskGraph::validate(records); }) > 64);
}
void RenderStarvationTests::preparingStacksThrows()
{
const std::vector<ProjectLayerRecord> records = layers(64);
std::vector<QUuid> ids;
for (const ProjectLayerRecord &layer : records)
ids.push_back(layer.id);
const long made = starved([&] {
LiveMaskRenderer live(
[&records](QUuid id) {
for (const ProjectLayerRecord &layer : records)
if (layer.id == id)
return layer.maskSourceID;
return std::optional<QUuid>();
},
[](QUuid, QPainter &, const QImage &) {});
live.prepareStacks(ids, [](QUuid) { return std::optional<QUuid>(); }, [](QUuid) { return LayerBlendMode::multiply; });
});
QVERIFY(made > 64);
}
void RenderStarvationTests::folderClipsThrow()
{
const std::vector<ProjectLayerRecord> records = layers(6);
std::vector<QUuid> ids;
for (const ProjectLayerRecord &layer : records)
ids.push_back(layer.id);
QImage device = BrushRaster::context(4, 4, false);
QPainter painter(&device);
int drawn = 0;
const long made = starved([&] {
drawn = 0;
FolderMaskClip::draw(
ids,
[&records](QUuid id) {
for (const ProjectLayerRecord &layer : records)
if (layer.id == id)
return layer.parentID;
return std::optional<QUuid>();
},
[](QUuid) { return std::optional<FolderMaskClip::Applier>([](const QPainter &, QImage &coverage) { coverage.fill(128); }); },
painter, [&drawn](QUuid, const QImage &) { ++drawn; });
});
QVERIFY(made > 2);
QCOMPARE(drawn, int(ids.size()));
}
QTEST_GUILESS_MAIN(RenderStarvationTests)
#include "RenderStarvationTests.moc"