202 lines
9.3 KiB
C++
202 lines
9.3 KiB
C++
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include "common/Module.h"
|
|
#include "production/Production.h"
|
|
|
|
#include <simplesquirrel/simplesquirrel.hpp>
|
|
|
|
using namespace eve::production;
|
|
|
|
TEST_CASE("production.enqueue.stableIdsAndCanonicalContext") {
|
|
WorkQueue queue;
|
|
eve::Value::Object context;
|
|
context.emplace("z", eve::Value(2));
|
|
context.emplace("a", eve::Value(1));
|
|
auto firstResult = queue.enqueue("factory:1", "vehicle", "tank.medium", eve::Value(std::move(context)), 10.0, 2);
|
|
REQUIRE(firstResult.ok());
|
|
const auto first = std::move(firstResult).takeValue();
|
|
auto secondResult = queue.enqueue("factory:1", "vehicle", "truck", eve::Value(eve::Value::Object{}), 5.0, 1);
|
|
REQUIRE(secondResult.ok());
|
|
const auto second = std::move(secondResult).takeValue();
|
|
CHECK_EQ(first, std::string("task-0000000000000001"));
|
|
CHECK_EQ(second, std::string("task-0000000000000002"));
|
|
auto firstTask = queue.find(first);
|
|
REQUIRE(firstTask);
|
|
const auto* firstContext = firstTask->get().context.getIf<eve::Value::Object>();
|
|
REQUIRE(firstContext != nullptr);
|
|
CHECK_EQ(firstContext->at("a").asInt(), 1);
|
|
CHECK_EQ(firstContext->at("z").asInt(), 2);
|
|
CHECK_EQ(firstTask->get().state == TaskState::Running, true);
|
|
auto secondTask = queue.find(second);
|
|
REQUIRE(secondTask);
|
|
CHECK_EQ(secondTask->get().state == TaskState::Queued, true);
|
|
auto emptyOwner = queue.enqueue("", "x", "y", eve::Value(eve::Value::Object{}), 1.0);
|
|
CHECK(!emptyOwner.ok());
|
|
auto nonObjectContext = queue.enqueue("owner", "x", "y", eve::Value("bad"), 1.0);
|
|
CHECK(!nonObjectContext.ok());
|
|
}
|
|
|
|
TEST_CASE("production.scheduling.prioritySlotsAndOwnersAreDeterministic") {
|
|
WorkQueue queue;
|
|
auto slotsDisabled = queue.setSlotCount("base:a", 0);
|
|
REQUIRE(slotsDisabled.ok());
|
|
auto lowResult = queue.enqueue("base:a", "build", "low", eve::Value(eve::Value::Object{}), 2.0, 1);
|
|
auto high1Result = queue.enqueue("base:a", "build", "high1", eve::Value(eve::Value::Object{}), 2.0, 9);
|
|
auto high2Result = queue.enqueue("base:a", "build", "high2", eve::Value(eve::Value::Object{}), 2.0, 9);
|
|
auto otherResult = queue.enqueue("base:b", "train", "unit", eve::Value(eve::Value::Object{}), 2.0, 0);
|
|
REQUIRE(lowResult.ok());
|
|
REQUIRE(high1Result.ok());
|
|
REQUIRE(high2Result.ok());
|
|
REQUIRE(otherResult.ok());
|
|
const auto low = std::move(lowResult).takeValue();
|
|
const auto high1 = std::move(high1Result).takeValue();
|
|
const auto high2 = std::move(high2Result).takeValue();
|
|
const auto other = std::move(otherResult).takeValue();
|
|
auto slotsEnabled = queue.setSlotCount("base:a", 2);
|
|
REQUIRE(slotsEnabled.ok());
|
|
auto high1Task = queue.find(high1);
|
|
auto high2Task = queue.find(high2);
|
|
auto lowTask = queue.find(low);
|
|
auto otherTask = queue.find(other);
|
|
REQUIRE(high1Task);
|
|
REQUIRE(high2Task);
|
|
REQUIRE(lowTask);
|
|
REQUIRE(otherTask);
|
|
CHECK_EQ(high1Task->get().state == TaskState::Running, true);
|
|
CHECK_EQ(high2Task->get().state == TaskState::Running, true);
|
|
CHECK_EQ(lowTask->get().state == TaskState::Queued, true);
|
|
CHECK_EQ(otherTask->get().state == TaskState::Running, true);
|
|
}
|
|
|
|
TEST_CASE("production.advance.fixedDeltaCompletion") {
|
|
WorkQueue queue;
|
|
auto firstResult = queue.enqueue("yard", "assemble", "a", eve::Value(eve::Value::Object{}), 4.0);
|
|
auto secondResult = queue.enqueue("yard", "assemble", "b", eve::Value(eve::Value::Object{}), 1.0);
|
|
REQUIRE(firstResult.ok());
|
|
REQUIRE(secondResult.ok());
|
|
const auto first = std::move(firstResult).takeValue();
|
|
const auto second = std::move(secondResult).takeValue();
|
|
auto firstStep = queue.advance({eve::SimulationTick(1), eve::Duration::fromNanoseconds(1000000000)});
|
|
REQUIRE(firstStep.ok());
|
|
auto firstTask = queue.find(first);
|
|
REQUIRE(firstTask);
|
|
CHECK_EQ(firstTask->get().progress.seconds(), 1.0);
|
|
auto secondStep = queue.advance({eve::SimulationTick(2), eve::Duration::fromNanoseconds(1000000000)});
|
|
REQUIRE(secondStep.ok());
|
|
firstTask = queue.find(first);
|
|
auto secondTask = queue.find(second);
|
|
REQUIRE(firstTask);
|
|
REQUIRE(secondTask);
|
|
CHECK_EQ(firstTask->get().progress.seconds(), 2.0);
|
|
CHECK_EQ(firstTask->get().state == TaskState::Running, true);
|
|
CHECK_EQ(secondTask->get().state == TaskState::Queued, true);
|
|
auto thirdStep = queue.advance({eve::SimulationTick(3), eve::Duration::fromNanoseconds(2000000000)});
|
|
REQUIRE(thirdStep.ok());
|
|
firstTask = queue.find(first);
|
|
secondTask = queue.find(second);
|
|
REQUIRE(firstTask);
|
|
REQUIRE(secondTask);
|
|
CHECK_EQ(firstTask->get().state == TaskState::Completed, true);
|
|
CHECK_EQ(secondTask->get().state == TaskState::Running, true);
|
|
auto fourthStep = queue.advance({eve::SimulationTick(4), eve::Duration::fromNanoseconds(1000000000)});
|
|
REQUIRE(fourthStep.ok());
|
|
secondTask = queue.find(second);
|
|
REQUIRE(secondTask);
|
|
CHECK_EQ(secondTask->get().state == TaskState::Completed, true);
|
|
auto lastEvent = queue.eventAt(queue.eventCount() - 1);
|
|
REQUIRE(lastEvent);
|
|
CHECK_EQ(lastEvent->get().kind == ProductionEventKind::Completed, true);
|
|
}
|
|
|
|
TEST_CASE("production.lifecycle.pauseResumeCancelFail") {
|
|
WorkQueue queue;
|
|
auto aResult = queue.enqueue("owner", "kind", "a", eve::Value(eve::Value::Object{}), 2.0);
|
|
auto bResult = queue.enqueue("owner", "kind", "b", eve::Value(eve::Value::Object{}), 2.0);
|
|
REQUIRE(aResult.ok());
|
|
REQUIRE(bResult.ok());
|
|
const auto a = std::move(aResult).takeValue();
|
|
const auto b = std::move(bResult).takeValue();
|
|
auto paused = queue.pause(a);
|
|
REQUIRE(paused.ok());
|
|
auto aTask = queue.find(a);
|
|
auto bTask = queue.find(b);
|
|
REQUIRE(aTask);
|
|
REQUIRE(bTask);
|
|
CHECK_EQ(aTask->get().state == TaskState::Paused, true);
|
|
CHECK_EQ(bTask->get().state == TaskState::Running, true);
|
|
auto resumed = queue.resume(a);
|
|
REQUIRE(resumed.ok());
|
|
auto cancelled = queue.cancel(b, "changed_plan");
|
|
REQUIRE(cancelled.ok());
|
|
aTask = queue.find(a);
|
|
bTask = queue.find(b);
|
|
REQUIRE(aTask);
|
|
REQUIRE(bTask);
|
|
CHECK_EQ(aTask->get().state == TaskState::Running, true);
|
|
CHECK_EQ(bTask->get().reason, std::string("changed_plan"));
|
|
auto failed = queue.fail(a, "power_loss");
|
|
REQUIRE(failed.ok());
|
|
auto resumeFailed = queue.resume(a);
|
|
CHECK(!resumeFailed.ok());
|
|
aTask = queue.find(a);
|
|
REQUIRE(aTask);
|
|
CHECK_EQ(aTask->get().state == TaskState::Failed, true);
|
|
}
|
|
|
|
TEST_CASE("production.snapshot.restoreIsDeterministicAndTransactional") {
|
|
WorkQueue source;
|
|
auto slots = source.setSlotCount("yard", 2);
|
|
REQUIRE(slots.ok());
|
|
auto idResult =
|
|
source.enqueue("yard", "vehicle", "tank", eve::Value(eve::Value::Object{{"variant", eve::Value("a")}}), 5.0, 7);
|
|
REQUIRE(idResult.ok());
|
|
const auto id = std::move(idResult).takeValue();
|
|
auto sourceStep = source.advance({eve::SimulationTick(1), eve::Duration::fromNanoseconds(1250000000)});
|
|
REQUIRE(sourceStep.ok());
|
|
auto snapshotResult = source.snapshot();
|
|
REQUIRE(snapshotResult.ok());
|
|
const auto snapshot = std::move(snapshotResult).takeValue();
|
|
WorkQueue restored;
|
|
auto restoredResult = restored.restore(snapshot);
|
|
REQUIRE(restoredResult.ok());
|
|
auto restoredSnapshot = restored.snapshot();
|
|
REQUIRE(restoredSnapshot.ok());
|
|
CHECK_EQ(std::move(restoredSnapshot).takeValue(), snapshot);
|
|
auto restoredTask = restored.find(id);
|
|
REQUIRE(restoredTask);
|
|
CHECK_EQ(restoredTask->get().progress.seconds(), 1.25);
|
|
auto beforeResult = restored.snapshot();
|
|
REQUIRE(beforeResult.ok());
|
|
const auto before = std::move(beforeResult).takeValue();
|
|
auto malformed = restored.restore("{\"broken\":true}");
|
|
CHECK(!malformed.ok());
|
|
auto afterMalformed = restored.snapshot();
|
|
REQUIRE(afterMalformed.ok());
|
|
CHECK_EQ(std::move(afterMalformed).takeValue(), before);
|
|
}
|
|
|
|
TEST_CASE("production.script.queueLifecycle") {
|
|
ssq::VM vm(1024, ssq::Libs::ALL);
|
|
eve::ModuleManager::expose(vm);
|
|
vm.run(vm.compileSource(R"(
|
|
result <- "fail";
|
|
local module = eve.Production();
|
|
local queueResult = module.newWorkQueue();
|
|
local queue = queueResult.ok ? queueResult.value : null;
|
|
local slots = queue == null ? { ok=false } : queue.setSlotCount("factory", 2);
|
|
local idResult = queue == null ? { ok=false } : queue.enqueue(
|
|
"factory", "vehicle", "tank", "{\"armor\":100}", 2.0, 5);
|
|
local id = idResult.ok ? idResult.value : "";
|
|
local advanced = queue == null ? { ok=false } : queue.advance(1, 2.0);
|
|
local task = queue == null ? null : queue.find(id);
|
|
local savedResult = queue == null ? { ok=false } : queue.snapshot();
|
|
local saved = savedResult.ok ? savedResult.value : "";
|
|
local context = task == null ? {} : task.getContext();
|
|
if (queueResult.ok && slots.ok && idResult.ok && advanced.ok && savedResult.ok && task != null &&
|
|
task.getState() == "completed" && context.armor == 100 &&
|
|
queue.eventAt(queue.eventCount() - 1).getKind() == "completed" && queue.restore(saved).ok) result = "ok";
|
|
)"));
|
|
CHECK_EQ(vm.find("result").toString(), std::string("ok"));
|
|
}
|