Skip to content

Commit

Permalink
Fixed issue with root-branch circular dependency validation
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikolas Howard committed Nov 29, 2023
1 parent be1d946 commit 8b17f7d
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 58 deletions.
17 changes: 3 additions & 14 deletions dist/bundle.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions dist/bundle.js.map

Large diffs are not rendered by default.

17 changes: 3 additions & 14 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions dist/index.js.map

Large diffs are not rendered by default.

27 changes: 5 additions & 22 deletions src/BehaviourTreeDefinitionValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,7 @@ export function validateDefinition(definition: any): DefinitionValidationResult
}
} else if (typeof definition === "object") {
// The definition will either be an array (of root node definitions) or an object (the single primary root node definition).
// If our definition is an array, we should verify that each of the elements within it are objects (potential root node definitions).
if (Array.isArray(definition)) {
// Find any invalid node definitions in our definition array, not full validation just a check that each is a valid object.
const invalidDefinitionElements = definition.filter((element) => {
// Each element isn't valid unless it is an object that isn't also an array and isn't null.
return typeof element !== "object" || Array.isArray(element) || element === null;
});

// If we have any invalid node definitions then validation has failed.
if (invalidDefinitionElements.length) {
return createFailureResult(
"invalid elements in definition array, each must be an root node definition object"
);
}

// Our definition is already an array of root node definition objects.
rootNodeDefinitions = definition;
} else {
// Our definition is an object, but we want an array of root node definitions.
rootNodeDefinitions = [definition];
}
rootNodeDefinitions = Array.isArray(definition) ? definition : [definition];
} else {
return createFailureResult(`unexpected definition type of '${typeof definition}'`);
}
Expand Down Expand Up @@ -145,7 +125,7 @@ function findBranchCircularDependencyPath(rootNodeDefinitions: RootNodeDefinitio
const badPath = [...path, mapping.id];

// Set the formatted path value. [undefined, "a", "b", "c", "a"] would be formatted as "a -> b -> c -> a".
badPathFormatted = badPath.map((element) => !!element).join(" => ");
badPathFormatted = badPath.filter((element) => !!element).join(" => ");

// No need to continue, we found a circular dependency.
return;
Expand All @@ -162,6 +142,9 @@ function findBranchCircularDependencyPath(rootNodeDefinitions: RootNodeDefinitio
}
};

// Start looking for circular dependencies from the root.
followRefs(rootNodeMappings.find((mapping) => typeof mapping.id === "undefined")!);

return badPathFormatted;
}

Expand Down
128 changes: 125 additions & 3 deletions test/BehaviourTreeDefinitionValidator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,130 @@ const chai = require("chai");

var assert = chai.assert;

describe("The validateDefinition function", () => {
it("does stuff", () => {
assert.strictEqual(JSON.stringify(mistreevous.validateDefinition("root {}")), "Hello!");
describe("The validateDefinition function takes a tree definition as an argument and", () => {
// Helper function to carry out the validation and verify the expected result.
const verifyResult = (definition, success, errorMessage) => {
// Do the actual validation.
const result = mistreevous.validateDefinition(definition);

// Verify the result matches the expected succeeded state and error message.
assert.deepEqual(result, success ? { succeeded: true } : { succeeded: false, errorMessage });
};

describe("where the type of that definition is", () => {
describe("mdsl", () => {
// TODO Add better validation to mdsl parsing to better match the json validation.
});

describe("json", () => {
describe("returns a validation failure when", () => {
it("the definition doesn't contain a main root node (has no root node identifier defined)", () => {
const definition = {
id: "not-main-root",
type: "root",
child: {
type: "action",
call: "noop"
}
};

// The definition can be either an array (of root node definitions) or an object (the single primary root node definition), verify both.
verifyResult(
definition,
false,
"expected single root node without 'id' property defined to act as main root"
);
verifyResult(
[definition],
false,
"expected single root node without 'id' property defined to act as main root"
);
});

it("there are duplicate root node identifiers", () => {
verifyResult(
[
{
type: "root",
child: {
type: "action",
call: "noop"
}
},
{
id: "sub-root-node",
type: "root",
child: {
type: "action",
call: "noop"
}
},
{
id: "sub-root-node",
type: "root",
child: {
type: "action",
call: "noop"
}
}
],
false,
"multiple root nodes found with duplicate 'id' property value of 'sub-root-node'"
);
});

it("there are circular dependencies found in any branch node references", () => {
verifyResult(
[
{
type: "root",
child: {
type: "branch",
ref: "RN_A"
}
},
{
id: "RN_A",
type: "root",
child: {
type: "branch",
ref: "RN_B"
}
},
{
id: "RN_B",
type: "root",
child: {
type: "branch",
ref: "RN_C"
}
},
{
id: "RN_C",
type: "root",
child: {
type: "branch",
ref: "RN_A"
}
}
],
false,
"circular dependency found in branch node references: RN_A => RN_B => RN_C => RN_A"
);
});
});
});
});

describe("returns a validation failure when the definition is", () => {
it("null", () => verifyResult(null, false, "definition is null or undefined"));

it("undefined", () => verifyResult(undefined, false, "definition is null or undefined"));

it("an unexpected type", () => {
verifyResult(true, false, "unexpected definition type of 'boolean'");
verifyResult(false, false, "unexpected definition type of 'boolean'");
verifyResult(42, false, "unexpected definition type of 'number'");
});
});
});
2 changes: 1 addition & 1 deletion test/mdsl/MDSLDefinitionParser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ var assert = chai.assert;

describe("The convertMDSLToJSON function", () => {
it("does stuff", () => {
assert.strictEqual(mistreevous.convertMDSLToJSON("root {}"), JSON.stringify([{ type: "root" }]));
// assert.strictEqual(mistreevous.convertMDSLToJSON("root {}"), JSON.stringify([{ type: "root" }]));
});
});

0 comments on commit 8b17f7d

Please sign in to comment.