157 lines
10 KiB
C++
157 lines
10 KiB
C++
#include <mbgl/particle/field/field_profile_validator.hpp>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace mbgl;
|
|
|
|
namespace {
|
|
|
|
particle::DecodedFieldPlane plane(uint16_t semantic, particle::FieldValueClass valueClass, uint16_t unit,
|
|
uint16_t group, particle::FieldComponent component, particle::FieldDType dtype,
|
|
bool required, std::shared_ptr<const std::vector<uint8_t>> storage,
|
|
std::string identity = "plane") {
|
|
return {.key = {semantic, group, component}, .dtype = dtype, .valueClass = valueClass, .unitID = unit,
|
|
.requiredFlag = required, .count = 4, .scale = 0.1f, .offset = 0.0f,
|
|
.contentIdentity = std::move(identity), .storage = std::move(storage)};
|
|
}
|
|
|
|
particle::DecodedFieldFrame frame() {
|
|
const auto numbers = std::make_shared<const std::vector<uint8_t>>(std::vector<uint8_t>{1, 2, 3, 4});
|
|
const auto bits = std::make_shared<const std::vector<uint8_t>>(std::vector<uint8_t>{0x0F});
|
|
particle::DecodedFieldFrame value;
|
|
value.frameContentIdentity = {"future-product", 1, 1, 1'000};
|
|
value.referenceTimeMs = 1'000;
|
|
value.grid = {.layout = 1, .projection = 1, .sampleOrder = 1, .gridRegistration = 1, .width = 2, .height = 2,
|
|
.west = 130.0, .south = 30.0, .east = 131.0, .north = 31.0};
|
|
value.validityContentIdentity = "validity";
|
|
value.validityBits = bits;
|
|
value.validityPlane = plane(900, particle::FieldValueClass::Validity, particle::FieldUnitNone, 0,
|
|
particle::FieldComponent::None, particle::FieldDType::Bitset, true, bits, "validity");
|
|
value.planes.push_back(plane(777, particle::FieldValueClass::Scalar, 65432, 0, particle::FieldComponent::None,
|
|
particle::FieldDType::UInt8, true, numbers, "scalar"));
|
|
return value;
|
|
}
|
|
|
|
particle::ResolvedFieldProfileContract contract() {
|
|
particle::ResolvedFieldProfileContract value;
|
|
value.profileID = "future.product-42";
|
|
value.profileVersion = 1;
|
|
value.allowAdditionalPlanes = true;
|
|
value.requiredPlanes = {{777, particle::FieldValueClass::Scalar, 65432, 0, particle::FieldComponent::None, true},
|
|
{900, particle::FieldValueClass::Validity, particle::FieldUnitNone, 0,
|
|
particle::FieldComponent::None, true}};
|
|
return value;
|
|
}
|
|
|
|
particle::FieldProfileValidationCode validate(const particle::ResolvedFieldProfileContract& c,
|
|
const particle::DecodedFieldFrame& f) {
|
|
return particle::FieldProfileValidator::validate(c, f).code;
|
|
}
|
|
|
|
std::shared_ptr<const particle::DecodedFieldFrame> runtimeFrame(uint64_t time, uint64_t generation,
|
|
uint64_t version, std::string content,
|
|
std::string id = "field") {
|
|
auto value = frame();
|
|
value.frameContentIdentity = {std::move(id), generation, version, time};
|
|
value.planes.front().contentIdentity = std::move(content);
|
|
return std::make_shared<const particle::DecodedFieldFrame>(std::move(value));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(ParticleFieldProfileValidator, AcceptsGenericProfileAndOpaqueScalarUnit) {
|
|
EXPECT_EQ(validate(contract(), frame()), particle::FieldProfileValidationCode::Accepted);
|
|
}
|
|
|
|
TEST(ParticleFieldProfileValidator, EnforcesRequiredPlanesAndAdditionalPlanePolicy) {
|
|
auto c = contract(); auto f = frame();
|
|
f.planes.clear();
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::MissingRequiredPlane);
|
|
f = frame(); f.planes.front().unitID = 99;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::PlaneDescriptorMismatch);
|
|
f = frame(); f.planes.front().requiredFlag = false;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::PlaneDescriptorMismatch);
|
|
f = frame(); f.planes.push_back(plane(778, particle::FieldValueClass::Scalar, 123, 0, particle::FieldComponent::None,
|
|
particle::FieldDType::UInt8, true, f.planes.front().storage, "required-extra"));
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::UndeclaredRequiredPlane);
|
|
f.planes.back().requiredFlag = false; c.allowAdditionalPlanes = false;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::AdditionalPlaneNotAllowed);
|
|
c.allowAdditionalPlanes = true;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted);
|
|
}
|
|
|
|
TEST(ParticleFieldProfileValidator, EnforcesVelocityDirectionAndValidityCoreRules) {
|
|
auto c = contract(); auto f = frame(); const auto bytes = f.planes.front().storage;
|
|
f.planes.push_back(plane(10, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3,
|
|
particle::FieldComponent::UEast, particle::FieldDType::Int16, false, bytes));
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidVelocityGroup);
|
|
f.planes.push_back(plane(11, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3,
|
|
particle::FieldComponent::VNorth, particle::FieldDType::Int16, false, bytes));
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted);
|
|
f.planes.back().unitID = 99;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
|
f = frame();
|
|
f.planes.push_back(plane(10, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 0,
|
|
particle::FieldComponent::UEast, particle::FieldDType::Int16, false, bytes));
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidVelocityGroup);
|
|
f = frame();
|
|
f.planes.push_back(plane(10, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3,
|
|
particle::FieldComponent::UEast, particle::FieldDType::Int16, false, bytes));
|
|
f.planes.push_back(plane(11, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3,
|
|
particle::FieldComponent::UEast, particle::FieldDType::Int16, false, bytes));
|
|
f.planes.push_back(plane(12, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3,
|
|
particle::FieldComponent::VNorth, particle::FieldDType::Int16, false, bytes));
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidVelocityGroup);
|
|
f = frame(); f.planes.front().valueClass = particle::FieldValueClass::DirectionAngle; f.planes.front().unitID = 99;
|
|
c.requiredPlanes.front().valueClass = particle::FieldValueClass::DirectionAngle; c.requiredPlanes.front().unitID = 99;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
|
c = contract(); f = frame(); f.validityPlane.dtype = particle::FieldDType::UInt8;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
|
f = frame(); f.validityPlane.unitID = 1;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
|
f = frame(); f.validityPlane.valueClass = particle::FieldValueClass::Scalar;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
|
}
|
|
|
|
TEST(ParticleFieldProfileValidator, AppliesSchemaSharedValidityAndTimeRules) {
|
|
auto c = contract(); auto f = frame(); c.contractSchemaVersion = 2;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::UnsupportedContractSchema);
|
|
c = contract(); c.sharedValidityRequired = false;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::UnsupportedSharedValidityMode);
|
|
c = contract(); f.frameContentIdentity.validTimeMs = 999;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted);
|
|
c.timeRelation = particle::FieldTimeRelation::ValidGteReference;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::TimeRelationMismatch);
|
|
f.frameContentIdentity.validTimeMs = 1'000;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted);
|
|
c.timeRelation = particle::FieldTimeRelation::ValidEqReference; f.frameContentIdentity.validTimeMs = 1'001;
|
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::TimeRelationMismatch);
|
|
}
|
|
|
|
TEST(ParticleFieldRuntime, RevisionIdentityAndIndependentCadence) {
|
|
particle::FieldSeries series; const uint64_t base = 1'700'000'000'000ULL, hour = 3'600'000ULL;
|
|
EXPECT_EQ(series.submit(runtimeFrame(base, 1, 1, "a")), particle::FieldSeriesSubmitResult::Inserted);
|
|
EXPECT_EQ(series.submit(runtimeFrame(base, 1, 1, "a")), particle::FieldSeriesSubmitResult::Duplicate);
|
|
EXPECT_EQ(series.submit(runtimeFrame(base, 1, 1, "changed")), particle::FieldSeriesSubmitResult::RejectedIdentityConflict);
|
|
EXPECT_EQ(series.submit(runtimeFrame(base, 2, 2, "b")), particle::FieldSeriesSubmitResult::ReplacedRevision);
|
|
particle::EncodedFieldStore store;
|
|
EXPECT_EQ(store.submit("wind-key", runtimeFrame(base, 1, 1, "w0", "wind")), particle::FieldSeriesSubmitResult::Inserted);
|
|
EXPECT_EQ(store.submit("wind-key", runtimeFrame(base + hour, 2, 1, "w1", "wind")), particle::FieldSeriesSubmitResult::Inserted);
|
|
EXPECT_EQ(store.submit("wave-key", runtimeFrame(base, 1, 1, "a0", "wave")), particle::FieldSeriesSubmitResult::Inserted);
|
|
EXPECT_EQ(store.submit("wave-key", runtimeFrame(base + 3 * hour, 2, 1, "a3", "wave")), particle::FieldSeriesSubmitResult::Inserted);
|
|
const auto wind = store.resolve("wind-key", base + hour / 2), wave = store.resolve("wave-key", base + hour / 2);
|
|
ASSERT_TRUE(wind); ASSERT_TRUE(wave); EXPECT_FLOAT_EQ(wind->mix, 0.5f); EXPECT_FLOAT_EQ(wave->mix, 1.0f / 6.0f);
|
|
}
|
|
|
|
TEST(ParticleFieldRuntime, ForecastClockOwnsRequestedTimeAndPlayback) {
|
|
particle::ForecastClock clock;
|
|
EXPECT_TRUE(clock.setRequestedForecastTime(1'700'000'000'000ULL)); EXPECT_FALSE(clock.advance(1'000));
|
|
EXPECT_TRUE(clock.setPlaybackSpeed(2.0f)); clock.play(); EXPECT_TRUE(clock.advance(500));
|
|
EXPECT_EQ(clock.requestedForecastTime(), 1'700'000'001'000ULL); clock.pause();
|
|
EXPECT_FALSE(clock.advance(500)); EXPECT_FALSE(clock.setPlaybackSpeed(3.0f));
|
|
}
|