Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

## [Unreleased]

### Fixed

- **A GCV tested by an `<if-global-variable>` condition but defined nowhere was a `gcv-undefined` error.** The
engine reads an undefined GCV there as having no value (`not-available` is true, `equal` is false) and
nothing fails; packages test GCVs another package may define that way. An `available` / `not-available`
test is no longer reported, and any other op is the new info `gcv-undefined-condition`. A GCV that is
also substituted (`token-global-variable`, `~name~`) is still `gcv-undefined`.

## [0.64.0] - 2026-11-02

### Added
Expand Down
1 change: 1 addition & 0 deletions docs/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ run time, so an undefined one fails the rule on its first event.
| `script-required-attribute` | W | a DirXML-Script element lacks an attribute its grammar requires (Designer's DTD 4.7.5: `do-send-email-from-template` without `template-dn`, `token-map` without `src`/`dest`, `do-status` without `level`, …). The engine may still load the policy; Designer's importer drops it. Named by rule description |
| `gcv-undefined` | E | a driver policy reads a GCV nothing in the driver's scope defines (names built from a local variable, `drv.x.$name$`, are skipped) |
| `gcv-undefined-library` | W | a Library policy reads a GCV no driver in the set defines (it may be linked only by drivers that do) |
| `gcv-undefined-condition` | I | an `<if-global-variable>` condition compares a GCV nothing defines; the engine reads it as having no value, so the test always comes out the same (`available` / `not-available` tests are never reported) |

### `mapping-tables` — MappingTableCheck

Expand Down
48 changes: 41 additions & 7 deletions src/main/java/com/pointblue/dirxml/dev/validate/GcvCheck.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@
* ({@code gcv-undefined-library}), since the policy may be linked only by drivers
* that do define it.
*
* <p>Codes: {@code gcv-undefined} (E), {@code gcv-undefined-library} (W).
* <p>An {@code <if-global-variable>} condition is different: the engine reads an
* undefined GCV there as having no value ({@code not-available} is true, {@code equal}
* is false) and nothing fails. Packages test GCVs another package may define that way.
* A condition with {@code op="available"} or {@code "not-available"} asks exactly that
* and is never reported; any other op on an undefined GCV is an {@code INFO}
* ({@code gcv-undefined-condition}), since its outcome is then fixed.
*
* <p>Codes: {@code gcv-undefined} (E), {@code gcv-undefined-library} (W),
* {@code gcv-undefined-condition} (I).
*/
public final class GcvCheck implements Check {

Expand All @@ -47,6 +55,7 @@ public void run(DriverSet ds, Report r) {
r.add(Finding.error("gcv-undefined", p.path(),
"references undefined GCV(s): " + String.join(", ", missing)));
}
conditionFinding(r, p, defined);
}
}
for (Policy p : ds.library.policies) {
Expand All @@ -55,13 +64,36 @@ public void run(DriverSet ds, Report r) {
r.add(Finding.warning("gcv-undefined-library", p.path(),
"references GCV(s) no driver in this set defines: " + String.join(", ", missing)));
}
conditionFinding(r, p, union);
}
}

private static void conditionFinding(Report r, Policy p, Set<String> defined) {
if (p.content == null) {
return;
}
Set<String> names = new LinkedHashSet<>();
conditions(p.content, names);
names.removeAll(GcvReferences.referenced(p.content));
List<String> missing = undefined(names, defined);
if (!missing.isEmpty()) {
r.add(Finding.info("gcv-undefined-condition", p.path(),
"tests GCV(s) nothing defines: " + String.join(", ", missing)
+ " — the engine reads them as having no value, so those tests always come out the same"));
}
}

/** {@code <if-global-variable name=…>} conditions read a GCV too; they have no token form, so the reference scan misses them. */
/**
* {@code <if-global-variable name=…>} conditions that compare a GCV's value; they have
* no token form, so the reference scan misses them. {@code available} and
* {@code not-available} only ask whether it is defined, so they're left out.
*/
private static void conditions(org.w3c.dom.Element e, Set<String> names) {
if ("if-global-variable".equals(e.getNodeName()) && !e.getAttribute("name").isEmpty()) {
names.add(e.getAttribute("name"));
String op = e.getAttribute("op");
if (!"available".equals(op) && !"not-available".equals(op)) {
names.add(e.getAttribute("name"));
}
}
for (org.w3c.dom.Node c = e.getFirstChild(); c != null; c = c.getNextSibling()) {
if (c instanceof org.w3c.dom.Element) {
Expand All @@ -71,12 +103,14 @@ private static void conditions(org.w3c.dom.Element e, Set<String> names) {
}

private static List<String> missing(Policy p, Set<String> defined) {
List<String> out = new ArrayList<>();
if (p.content == null) {
return out;
return new ArrayList<>();
}
Set<String> names = new java.util.LinkedHashSet<>(GcvReferences.referenced(p.content));
conditions(p.content, names);
return undefined(GcvReferences.referenced(p.content), defined);
}

private static List<String> undefined(java.util.Collection<String> names, Set<String> defined) {
List<String> out = new ArrayList<>();
for (String name : names) {
// A name built from a local variable (drv.x.$entName$) is resolved at run
// time; nothing static to check.
Expand Down
30 changes: 30 additions & 0 deletions src/test/java/com/pointblue/dirxml/dev/validate/ValidatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,36 @@ public void gcvUndefinedToken() {
assertTrue(f.get(0).message, f.get(0).message.endsWith("drv.missing"));
}

/** The engine reads an undefined GCV in a condition as having no value; nothing fails. */
@Test
public void gcvUndefinedInAConditionIsInfo() {
DriverSet ds = clean();
addSub(ds, "gcv", "<policy><rule><description>r</description><conditions>"
+ "<or><if-global-variable name=\"drv.optional\" op=\"not-available\"/>"
+ "<if-global-variable name=\"drv.optional\" op=\"equal\">false</if-global-variable></or>"
+ "<or><if-global-variable name=\"drv.only.asked\" op=\"available\"/>"
+ "<if-global-variable name=\"drv.users\" op=\"equal\">true</if-global-variable></or>"
+ "</conditions><actions/></rule></policy>");
Report r = owned().validate(ds);
assertTrue(r.text(), r.ok());
List<Finding> f = r.withCode("gcv-undefined-condition");
assertEquals(r.text(), 1, f.size());
assertTrue(f.get(0).message, f.get(0).message.startsWith("tests GCV(s) nothing defines: drv.optional "));
}

/** A GCV both tested and substituted is still an error for the substitution. */
@Test
public void gcvUndefinedTokenAndConditionIsOneError() {
DriverSet ds = clean();
addSub(ds, "gcv", "<policy><rule><description>r</description><conditions>"
+ "<if-global-variable name=\"drv.missing\" op=\"equal\">true</if-global-variable></conditions><actions>"
+ "<do-set-local-variable name=\"x\"><arg-string><token-global-variable name=\"drv.missing\"/>"
+ "</arg-string></do-set-local-variable></actions></rule></policy>");
Report r = owned().validate(ds);
assertEquals(r.text(), 1, r.withCode("gcv-undefined").size());
assertTrue(r.text(), r.withCode("gcv-undefined-condition").isEmpty());
}

/** #134: a driver that runs on another server of the set has its GCVs in that server's never-sync copy. */
@Test
public void gcvDefinedOnlyOnAnotherServerIsDefined() {
Expand Down
Loading