mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-08-24 22:31:30 -04:00
Add tests for the MetadataProvider constant-time index
- Core/tests/PlatformMetadataIndex.cpp (native/catch): every lookup kind against
DummyPlatform, object-expression fallback to the base ("") type, behavior
actions indexed alongside free/object ones, bad-metadata for unknown types,
the declaring extension is returned, and the index rebuilds after an extension
is removed.
- Expand the jest MetadataProviderIndex test to assert *all* metadata kinds
resolve after an extension is added and *all* become bad after it is removed
(invalidation covers the whole index, not just objects/actions).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2026 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
/**
|
||||
* @file Tests for gd::MetadataProvider lookups, which are backed by
|
||||
* gd::PlatformMetadataIndex (constant-time index of a platform's metadata).
|
||||
*/
|
||||
#include "DummyPlatform.h"
|
||||
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/EffectMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
using namespace gd;
|
||||
|
||||
TEST_CASE("PlatformMetadataIndex (via MetadataProvider)", "[common]") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
|
||||
SECTION("It resolves every kind of metadata declared by an extension") {
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadObjectMetadata(
|
||||
MetadataProvider::GetObjectMetadata(platform, "MyExtension::Sprite")));
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadBehaviorMetadata(
|
||||
MetadataProvider::GetBehaviorMetadata(platform,
|
||||
"MyExtension::MyBehavior")));
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadInstructionMetadata(
|
||||
MetadataProvider::GetActionMetadata(platform, "MyExtension::DoSomething")));
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata(
|
||||
MetadataProvider::GetExpressionMetadata(platform, "MyExtension::GetNumber")));
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata(
|
||||
MetadataProvider::GetStrExpressionMetadata(platform, "MyExtension::ToString")));
|
||||
}
|
||||
|
||||
SECTION("Behavior actions are indexed alongside free/object actions") {
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadInstructionMetadata(
|
||||
MetadataProvider::GetActionMetadata(
|
||||
platform, "MyExtension::BehaviorDoSomething")));
|
||||
}
|
||||
|
||||
SECTION("It resolves object/behavior expressions on their own type") {
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata(
|
||||
MetadataProvider::GetObjectExpressionMetadata(
|
||||
platform, "MyExtension::Sprite", "GetObjectNumber")));
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata(
|
||||
MetadataProvider::GetObjectStrExpressionMetadata(
|
||||
platform, "MyExtension::Sprite", "GetObjectStringWith1Param")));
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata(
|
||||
MetadataProvider::GetBehaviorExpressionMetadata(
|
||||
platform, "MyExtension::MyBehavior", "GetBehaviorNumberWith1Param")));
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata(
|
||||
MetadataProvider::GetBehaviorStrExpressionMetadata(
|
||||
platform, "MyExtension::MyBehavior", "GetBehaviorStringWith1Param")));
|
||||
}
|
||||
|
||||
SECTION("Object expressions fall back to the base object type") {
|
||||
// "GetFromBaseExpression" is declared on the base object (""), not on
|
||||
// Sprite, so it must be resolved through the base-type fallback.
|
||||
const auto& fromBase = MetadataProvider::GetObjectExpressionMetadata(
|
||||
platform, "MyExtension::Sprite", "GetFromBaseExpression");
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata(fromBase));
|
||||
|
||||
// It must also be found for an object type that is not declared by any
|
||||
// extension (still resolved via the base object).
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata(
|
||||
MetadataProvider::GetObjectExpressionMetadata(
|
||||
platform, "UnknownObjectType", "GetFromBaseExpression")));
|
||||
}
|
||||
|
||||
SECTION("Unknown types resolve to the bad metadata") {
|
||||
REQUIRE(MetadataProvider::IsBadObjectMetadata(
|
||||
MetadataProvider::GetObjectMetadata(platform, "MyExtension::DoesNotExist")));
|
||||
REQUIRE(MetadataProvider::IsBadBehaviorMetadata(
|
||||
MetadataProvider::GetBehaviorMetadata(platform, "Does::NotExist")));
|
||||
REQUIRE(MetadataProvider::IsBadInstructionMetadata(
|
||||
MetadataProvider::GetActionMetadata(platform, "Does::NotExist")));
|
||||
REQUIRE(MetadataProvider::IsBadInstructionMetadata(
|
||||
MetadataProvider::GetConditionMetadata(platform, "Does::NotExist")));
|
||||
REQUIRE(MetadataProvider::IsBadExpressionMetadata(
|
||||
MetadataProvider::GetExpressionMetadata(platform, "Does::NotExist")));
|
||||
REQUIRE(MetadataProvider::IsBadExpressionMetadata(
|
||||
MetadataProvider::GetObjectExpressionMetadata(
|
||||
platform, "MyExtension::Sprite", "DoesNotExist")));
|
||||
}
|
||||
|
||||
SECTION("It returns the extension that declares the metadata") {
|
||||
auto extensionAndMetadata =
|
||||
MetadataProvider::GetExtensionAndObjectMetadata(platform,
|
||||
"MyExtension::Sprite");
|
||||
REQUIRE(extensionAndMetadata.GetExtension().GetName() == "MyExtension");
|
||||
|
||||
auto badExtensionAndMetadata =
|
||||
MetadataProvider::GetExtensionAndObjectMetadata(platform,
|
||||
"MyExtension::DoesNotExist");
|
||||
REQUIRE(badExtensionAndMetadata.GetExtension().GetName().empty());
|
||||
}
|
||||
|
||||
SECTION("The index is rebuilt after the platform's extensions change") {
|
||||
REQUIRE_FALSE(MetadataProvider::IsBadObjectMetadata(
|
||||
MetadataProvider::GetObjectMetadata(platform, "MyExtension::Sprite")));
|
||||
|
||||
platform.RemoveExtension("MyExtension");
|
||||
REQUIRE(MetadataProvider::IsBadObjectMetadata(
|
||||
MetadataProvider::GetObjectMetadata(platform, "MyExtension::Sprite")));
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,28 @@ const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.j
|
||||
|
||||
// Checks that gd::MetadataProvider lookups (backed by the platform's
|
||||
// gd::PlatformMetadataIndex) stay correct when extensions are added, replaced
|
||||
// and removed at runtime - i.e. that the index is properly invalidated.
|
||||
describe('MetadataProvider index invalidation', () => {
|
||||
// and removed at runtime - i.e. that the index is properly invalidated - and
|
||||
// that every kind of metadata is indexed and resolved.
|
||||
describe('MetadataProvider index', () => {
|
||||
let gd = null;
|
||||
beforeAll(async () => {
|
||||
gd = await initializeGDevelopJs();
|
||||
});
|
||||
|
||||
const extensionName = 'MetadataIndexTestExtension';
|
||||
const objectType = extensionName + '::IndexTestObject';
|
||||
const actionType = extensionName + '::IndexTestAction';
|
||||
const prefix = extensionName + '::';
|
||||
const objectType = prefix + 'TestObject';
|
||||
const behaviorType = prefix + 'TestBehavior';
|
||||
const actionType = prefix + 'TestAction';
|
||||
const conditionType = prefix + 'TestCondition';
|
||||
const expressionType = prefix + 'TestExpression';
|
||||
const strExpressionType = prefix + 'TestStrExpression';
|
||||
// Object/behavior expression names are not namespaced (they are scoped to the
|
||||
// object/behavior type they belong to).
|
||||
const objectExpressionName = 'TestObjectExpression';
|
||||
const objectStrExpressionName = 'TestObjectStrExpression';
|
||||
const behaviorExpressionName = 'TestBehaviorExpression';
|
||||
const behaviorStrExpressionName = 'TestBehaviorStrExpression';
|
||||
|
||||
const makeExtension = (objectFullName) => {
|
||||
const extension = new gd.PlatformExtension();
|
||||
@@ -22,69 +34,92 @@ describe('MetadataProvider index invalidation', () => {
|
||||
'Author',
|
||||
'MIT'
|
||||
);
|
||||
|
||||
const objectConfiguration = new gd.ObjectJsImplementation();
|
||||
extension.addObject(
|
||||
'IndexTestObject',
|
||||
const objectMetadata = extension.addObject(
|
||||
'TestObject',
|
||||
objectFullName,
|
||||
'A test object',
|
||||
'',
|
||||
objectConfiguration
|
||||
);
|
||||
extension.addAction(
|
||||
'IndexTestAction',
|
||||
'Index test action',
|
||||
'Does something',
|
||||
'Does something',
|
||||
objectMetadata.addExpression(objectExpressionName, 'Obj expr', 'd', 'g', '');
|
||||
objectMetadata.addStrExpression(objectStrExpressionName, 'Obj str', 'd', 'g', '');
|
||||
|
||||
const behaviorInstance = new gd.BehaviorJsImplementation();
|
||||
behaviorInstance.initializeContent = function (behaviorContent) {};
|
||||
const behaviorMetadata = extension.addBehavior(
|
||||
'TestBehavior',
|
||||
'Test behavior',
|
||||
'TestBehavior',
|
||||
'A test behavior',
|
||||
'',
|
||||
'',
|
||||
''
|
||||
'TestBehavior',
|
||||
behaviorInstance,
|
||||
new gd.BehaviorsSharedData()
|
||||
);
|
||||
behaviorMetadata.addExpression(behaviorExpressionName, 'Beh expr', 'd', 'g', '');
|
||||
behaviorMetadata.addStrExpression(behaviorStrExpressionName, 'Beh str', 'd', 'g', '');
|
||||
|
||||
extension.addAction('TestAction', 'Test action', 'Does', 'Does', '', '', '');
|
||||
extension.addCondition('TestCondition', 'Test cond', 'Is', 'Is', '', '', '');
|
||||
extension.addExpression('TestExpression', 'Test expr', 'd', 'g', '');
|
||||
extension.addStrExpression('TestStrExpression', 'Test str', 'd', 'g', '');
|
||||
|
||||
return extension;
|
||||
};
|
||||
|
||||
// Asserts every kind of metadata declared by makeExtension is found (when
|
||||
// expectFound) or resolves to the "bad" metadata (when not).
|
||||
const expectAllResolved = (platform, expectFound) => {
|
||||
const P = gd.MetadataProvider;
|
||||
const cases = [
|
||||
P.isBadObjectMetadata(P.getObjectMetadata(platform, objectType)),
|
||||
P.isBadBehaviorMetadata(P.getBehaviorMetadata(platform, behaviorType)),
|
||||
P.isBadInstructionMetadata(P.getActionMetadata(platform, actionType)),
|
||||
P.isBadInstructionMetadata(P.getConditionMetadata(platform, conditionType)),
|
||||
P.isBadExpressionMetadata(P.getExpressionMetadata(platform, expressionType)),
|
||||
P.isBadExpressionMetadata(P.getStrExpressionMetadata(platform, strExpressionType)),
|
||||
P.isBadExpressionMetadata(
|
||||
P.getObjectExpressionMetadata(platform, objectType, objectExpressionName)
|
||||
),
|
||||
P.isBadExpressionMetadata(
|
||||
P.getObjectStrExpressionMetadata(platform, objectType, objectStrExpressionName)
|
||||
),
|
||||
P.isBadExpressionMetadata(
|
||||
P.getBehaviorExpressionMetadata(platform, behaviorType, behaviorExpressionName)
|
||||
),
|
||||
P.isBadExpressionMetadata(
|
||||
P.getBehaviorStrExpressionMetadata(platform, behaviorType, behaviorStrExpressionName)
|
||||
),
|
||||
];
|
||||
// When found, none should be "bad"; when not, all should be "bad".
|
||||
for (const isBad of cases) expect(isBad).toBe(!expectFound);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
if (gd.JsPlatform.get().isExtensionLoaded(extensionName))
|
||||
gd.JsPlatform.get().removeExtension(extensionName);
|
||||
});
|
||||
|
||||
it('does not find metadata before the extension is added', () => {
|
||||
const platform = gd.JsPlatform.get();
|
||||
expect(
|
||||
gd.MetadataProvider.isBadObjectMetadata(
|
||||
gd.MetadataProvider.getObjectMetadata(platform, objectType)
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
gd.MetadataProvider.isBadInstructionMetadata(
|
||||
gd.MetadataProvider.getActionMetadata(platform, actionType)
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('finds metadata right after the extension is added (index rebuilt)', () => {
|
||||
it('resolves every kind of metadata after the extension is added, and none before/after removal', () => {
|
||||
const platform = gd.JsPlatform.get();
|
||||
|
||||
// Build the index once so the test proves it is invalidated, not just
|
||||
// lazily built for the first time.
|
||||
// Build the index once first so we test invalidation, not lazy first build.
|
||||
gd.MetadataProvider.getObjectMetadata(platform, 'Sprite');
|
||||
|
||||
expectAllResolved(platform, false);
|
||||
|
||||
const extension = makeExtension('First name');
|
||||
platform.addNewExtension(extension);
|
||||
extension.delete();
|
||||
|
||||
expect(
|
||||
gd.MetadataProvider.isBadObjectMetadata(
|
||||
gd.MetadataProvider.getObjectMetadata(platform, objectType)
|
||||
)
|
||||
).toBe(false);
|
||||
expect(
|
||||
gd.MetadataProvider.getObjectMetadata(platform, objectType).getFullName()
|
||||
).toBe('First name');
|
||||
expect(
|
||||
gd.MetadataProvider.isBadInstructionMetadata(
|
||||
gd.MetadataProvider.getActionMetadata(platform, actionType)
|
||||
)
|
||||
).toBe(false);
|
||||
expectAllResolved(platform, true);
|
||||
|
||||
platform.removeExtension(extensionName);
|
||||
|
||||
expectAllResolved(platform, false);
|
||||
});
|
||||
|
||||
it('returns updated metadata after the extension is replaced (not stale)', () => {
|
||||
@@ -94,13 +129,12 @@ describe('MetadataProvider index invalidation', () => {
|
||||
platform.addNewExtension(firstExtension);
|
||||
firstExtension.delete();
|
||||
|
||||
// Resolve once so the index caches the first version's metadata.
|
||||
expect(
|
||||
gd.MetadataProvider.getObjectMetadata(platform, objectType).getFullName()
|
||||
).toBe('First name');
|
||||
|
||||
// Replacing an extension goes through RemoveExtension + AddExtension, which
|
||||
// must discard the cached (now dangling) metadata.
|
||||
// Replacing goes through RemoveExtension + AddExtension and must discard the
|
||||
// cached (now dangling) metadata.
|
||||
const secondExtension = makeExtension('Second name');
|
||||
platform.addNewExtension(secondExtension);
|
||||
secondExtension.delete();
|
||||
@@ -109,32 +143,4 @@ describe('MetadataProvider index invalidation', () => {
|
||||
gd.MetadataProvider.getObjectMetadata(platform, objectType).getFullName()
|
||||
).toBe('Second name');
|
||||
});
|
||||
|
||||
it('stops finding metadata after the extension is removed', () => {
|
||||
const platform = gd.JsPlatform.get();
|
||||
|
||||
const extension = makeExtension('First name');
|
||||
platform.addNewExtension(extension);
|
||||
extension.delete();
|
||||
|
||||
// Resolve once so the index is populated before removal.
|
||||
expect(
|
||||
gd.MetadataProvider.isBadObjectMetadata(
|
||||
gd.MetadataProvider.getObjectMetadata(platform, objectType)
|
||||
)
|
||||
).toBe(false);
|
||||
|
||||
platform.removeExtension(extensionName);
|
||||
|
||||
expect(
|
||||
gd.MetadataProvider.isBadObjectMetadata(
|
||||
gd.MetadataProvider.getObjectMetadata(platform, objectType)
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
gd.MetadataProvider.isBadInstructionMetadata(
|
||||
gd.MetadataProvider.getActionMetadata(platform, actionType)
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user