diff --git a/README.md b/README.md index 7628dc5..7304220 100644 --- a/README.md +++ b/README.md @@ -545,6 +545,11 @@ metrics.frozenDocumentReads(); metrics.nodeMaterializations(); ``` +The deterministic gas rules are specified in [docs/GAS.md](docs/GAS.md). The +portable fixture format is documented in [docs/FIXTURES.md](docs/FIXTURES.md). +The rich fixture suite includes exact gas conformance fixtures under +`src/test/resources/rich-fixtures/gas/`. + ## Tests ```bash diff --git a/docs/FIXTURES.md b/docs/FIXTURES.md new file mode 100644 index 0000000..b6f1c03 --- /dev/null +++ b/docs/FIXTURES.md @@ -0,0 +1,187 @@ +# BEX Rich Fixture Format + +Rich fixtures are portable YAML test cases for BEX implementations. They live +under: + +```text +src/test/resources/rich-fixtures/ +``` + +The Java runner rejects unknown fixture fields so typos do not silently weaken a +conformance test. + +## Root Fields + +Allowed root fields: + +| Field | Required | Meaning | +| --- | --- | --- | +| `fixtureId` | yes | Stable fixture identifier. | +| `title` | yes | Human-readable fixture title. | +| `targetStatus` | no | Informational status used while migrating fixtures. | +| `tags` | no | List of grouping tags. | +| `context` | no | Execution context data. | +| `blueDefinitions` | no | Fixture-local BlueId provider definitions. | +| `gasSchedule` | no | Per-fixture gas schedule overrides. | +| `programSource` | yes | Blue YAML source for the BEX program. | +| `expectation` | yes | Expected outcome and assertions. | + +Example: + +```yaml +fixtureId: BEX-EXAMPLE-001 +title: Const returns declared value +tags: + - constants +programSource: | + type: Blue/BEX Program + constants: + amount: 400 + expr: + $const: amount +expectation: + outcome: success + resultSimple: 400 +``` + +`tags` must be a list of non-empty text values. `targetStatus`, when present, +must be one of: + +```text +current-pass +current-compile-error +current-runtime-error +current-output-conversion-error +current-parse-error +current-parse-error-or-output-conversion-error +current-gas-property +``` + +## Context + +Allowed `context` fields: + +| Field | Meaning | +| --- | --- | +| `documentScope` | Current document scope path. Defaults to `/`. | +| `rootDocumentSource` | Blue YAML for the canonical/resolved root document. Defaults to `{}`. | +| `eventSource` | Blue YAML for the event binding. Defaults to `{}`. | +| `currentContractSource` | Blue YAML for the current contract binding. Defaults to `{}`. | +| `stepsBinding` | Map of step names to simple step-result values. | +| `gasLimit` | Execution gas limit. Defaults to `1000000`. | +| `bindings` | Additional host bindings as simple YAML values. | + +Document pointers are resolved using `documentScope`. Value-local pointers such +as `$event`, `$currentContract`, `$steps`, `$binding`, `$pointerGet`, and +`$pointerSet` are resolved inside the selected value. + +## Blue Definitions + +Use `blueDefinitions` when a fixture references custom BlueIds: + +```yaml +blueDefinitions: + HotelOrderType: | + status: + type: Text +``` + +The fixture runner exposes each key as a Blue provider entry. This keeps +fixtures portable and avoids Java-only hardcoded provider behavior. + +## Gas Schedule + +Allowed `gasSchedule` override fields: + +```text +expressionBase +statementBase +documentRead +eventRead +stepsRead +currentContractRead +varRead +resultValueRead +pointerGetBase +pointerSetBase +objectSetBase +appendChangeBase +appendEventBase +forEachItem +functionCall +``` + +Any omitted field uses the default schedule documented in +[GAS.md](GAS.md). + +## Outcomes + +Allowed `expectation.outcome` values: + +| Outcome | Meaning | +| --- | --- | +| `success` | Program compiles and executes successfully. | +| `compile-error` | Program parses but BEX compilation fails. | +| `runtime-error` | Program compiles but execution fails. | +| `parse-error` | Blue YAML parsing fails. | +| `output-conversion-error` | Execution succeeds, but converting the output to a Blue node/frozen node fails. | +| `parse-error-or-output-conversion-error` | Either parse or output conversion failure is acceptable for strict Blue authoring edge cases. | +| `gas-property` | Fixture asserts a named gas property rather than exact output. | + +For `success`, allowed expectation fields are: + +```text +outcome +resultSimple +changeset +events +gasUsed +``` + +For `compile-error`, `runtime-error`, `parse-error`, +`output-conversion-error`, and `parse-error-or-output-conversion-error`, allowed +fields are: + +```text +outcome +errorContains +``` + +For `gas-property`, allowed fields are: + +```text +outcome +property +``` + +## Exact Gas + +Success fixtures may assert exact gas: + +```yaml +expectation: + outcome: success + gasUsed: 10 +``` + +Runtime gas exhaustion fixtures usually set a low context limit: + +```yaml +context: + gasLimit: 2 +expectation: + outcome: runtime-error + errorContains: BEX gas exhausted +``` + +## Manifest + +The fixture suite has a machine-readable manifest at: + +```text +src/test/resources/rich-fixtures/manifest.yaml +``` + +The manifest records the suite name, version, fixture root, required +directories, gas-model document, fixture-format document, and fixture counts. +It is not itself executed as a fixture. diff --git a/docs/GAS.md b/docs/GAS.md new file mode 100644 index 0000000..bcb9a71 --- /dev/null +++ b/docs/GAS.md @@ -0,0 +1,219 @@ +# BEX Gas Model + +BEX gas is deterministic execution accounting. It is intended to make one +implementation's runtime behavior portable enough for conformance tests, not to +model every CPU or memory cost. + +Compilation does not consume gas. Every execution starts at `0`. Each runtime +charge adds to the total. If `gasLimit >= 0` and the total becomes greater than +the limit, execution throws: + +```text +BEX gas exhausted at gas units +``` + +## Default Schedule + +| Field | Default | +| --- | ---: | +| `expressionBase` | 1 | +| `statementBase` | 1 | +| `documentRead` | 2 | +| `eventRead` | 1 | +| `stepsRead` | 1 | +| `currentContractRead` | 1 | +| `varRead` | 1 | +| `resultValueRead` | 2 | +| `pointerGetBase` | 1 | +| `pointerSetBase` | 3 | +| `objectSetBase` | 2 | +| `appendChangeBase` | 5 | +| `appendEventBase` | 5 | +| `forEachItem` | 1 | +| `functionCall` | 2 | + +Every function invocation charges `functionCall`. This includes the root program +function, so a trivial root expression costs at least `2 + expressionBase`. + +Every evaluated expression charges `expressionBase`. Every executed statement +charges `statementBase`. Source-path wrappers and other diagnostics wrappers do +not charge gas. + +## Reads + +Read operators charge their read cost in addition to `expressionBase`: + +| Operator | Cost | +| --- | --- | +| `$document` | `expressionBase + documentRead` | +| `$event` | `expressionBase + eventRead` | +| `$currentContract` | `expressionBase + currentContractRead` | +| `$steps` | `expressionBase + stepsRead` | +| `$binding` | `expressionBase + varRead` | +| `$var` | `expressionBase + varRead` | +| `$resultValue` | `expressionBase + resultValueRead` | + +Canonical and resolved document reads currently cost the same. + +## Pointer And Object Updates + +`$pointerGet` charges: + +```text +expressionBase ++ gas for object expression ++ pointerGetBase ++ numberOfPathSegments ++ gas for default expression only if default is used +``` + +`$pointerSet` charges: + +```text +expressionBase ++ gas for val expression, unless op is remove ++ gas for object expression ++ pointerSetBase ++ numberOfPathSegments ++ estimatedSize(val) +``` + +For `remove`, `val` is not evaluated and `estimatedSize(undefined) = 0`. + +`$objectSet` charges: + +```text +expressionBase ++ gas for val expression ++ objectSetBase ++ estimatedSize(val) ++ gas for object expression +``` + +Static keys and paths do not consume expression gas. Dynamic keys and paths do, +because their expressions are evaluated. + +## Append And Output + +`$appendChange` charges: + +```text +statementBase ++ gas for val expression if op is add or replace ++ appendChangeBase ++ estimatedSize(val) +``` + +For `remove`, `val` is not evaluated and size is `0`. + +`$appendChanges` charges: + +```text +statementBase ++ gas for list expression ++ for each patch: appendChangeBase + estimatedSize(val) +``` + +`$appendEvent` charges: + +```text +statementBase ++ gas for event expression ++ appendEventBase ++ estimatedSize(event) +``` + +`$appendEvents` charges: + +```text +statementBase ++ gas for list expression ++ for each event: appendEventBase + estimatedSize(event) +``` + +## Control Flow + +`$if` charges `statementBase`, then the condition expression, then only the +selected branch. + +`$forEach` charges `statementBase`, the input expression, `forEachItem` for each +iterated item, then the body statements for each iteration. + +`$and`, `$or`, and `$coalesce` short-circuit. Unevaluated operands consume no +gas. + +Function calls charge the caller expression or statement normally, then the +called function invocation charges `functionCall`. Argument expressions are +charged before entering the callee. + +## Size Estimator + +`estimatedSize(value)` is: + +| Value | Size | +| --- | ---: | +| `undefined` | 0 | +| `null` | 0 | +| scalar | `max(1, length(value.asText()))` | +| list | `list.size + sum(estimatedSize(item))` | +| object | `numberOfKeys + sum(length(key)) + sum(estimatedSize(valueForKey))` | + +Examples: + +```text +estimatedSize("x") = 1 +estimatedSize("") = 1 +estimatedSize(null) = 0 +estimatedSize("hello") = 5 +estimatedSize(12345) = 5 +estimatedSize(true) = 4 +estimatedSize(["a", "bb"]) = 2 + 1 + 2 = 5 +estimatedSize({ a: "x", bb: "yy" }) = 2 + 1 + 1 + 2 + 2 = 8 +``` + +Frozen values may be cached by BlueId and runtime values may be cached by +identity, but caching does not change gas used. It only affects metrics and +performance. + +## Known Limit + +Large values returned by pure expressions are not directly size-charged unless +they are later appended or inserted through charged output/update operators. +For example, `$concat`, `$join`, `$split`, `$keys`, `$entries`, `$merge`, +`$listConcat`, object literals, and list literals pay expression and operand gas +but not `estimatedSize(result)`. + +This is the current specified model: simple execution and output accounting. + +## Conformance Fixtures + +Exact gas conformance fixtures live under: + +```text +src/test/resources/rich-fixtures/gas/ +``` + +Success fixtures may assert: + +```yaml +expectation: + outcome: success + gasUsed: 10 +``` + +Fixtures may set execution limits: + +```yaml +context: + gasLimit: 2 +expectation: + outcome: runtime-error + errorContains: BEX gas exhausted +``` + +Fixtures may override individual schedule fields: + +```yaml +gasSchedule: + expressionBase: 10 +``` diff --git a/src/main/java/blue/bex/compile/BexContainsCache.java b/src/main/java/blue/bex/compile/BexContainsCache.java index 289edf0..510f06a 100644 --- a/src/main/java/blue/bex/compile/BexContainsCache.java +++ b/src/main/java/blue/bex/compile/BexContainsCache.java @@ -3,6 +3,7 @@ import blue.bex.result.BexMetrics; import blue.language.snapshot.FrozenNode; +import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -10,14 +11,15 @@ * Shared cache for detecting whether a frozen subtree contains any BEX operator. */ public final class BexContainsCache { - private final Map cache; + private final Map blueIdCache; + private final IdentityHashMap identityCache = new IdentityHashMap<>(); public BexContainsCache() { this(8192); } public BexContainsCache(final int capacity) { - this.cache = new LinkedHashMap(capacity, 0.75f, true) { + this.blueIdCache = new LinkedHashMap(capacity, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > capacity; @@ -29,8 +31,8 @@ public synchronized boolean containsBex(FrozenNode node, BexMetrics metrics) { if (node == null) { return false; } - String key = node.blueId() != null ? "blueId:" + node.blueId() : "identity:" + System.identityHashCode(node); - Boolean cached = cache.get(key); + String blueId = node.blueId(); + Boolean cached = blueId != null ? blueIdCache.get(blueId) : identityCache.get(node); if (cached != null) { if (metrics != null) { metrics.incrementContainsBexCacheHits(); @@ -42,7 +44,11 @@ public synchronized boolean containsBex(FrozenNode node, BexMetrics metrics) { metrics.incrementContainsBexScans(); } boolean result = scan(node); - cache.put(key, result); + if (blueId != null) { + blueIdCache.put(blueId, result); + } else { + identityCache.put(node, result); + } return result; } diff --git a/src/main/java/blue/bex/compile/BexNodeFingerprint.java b/src/main/java/blue/bex/compile/BexNodeFingerprint.java index 6dd8ac8..0b9d97d 100644 --- a/src/main/java/blue/bex/compile/BexNodeFingerprint.java +++ b/src/main/java/blue/bex/compile/BexNodeFingerprint.java @@ -1,6 +1,8 @@ package blue.bex.compile; import blue.language.snapshot.FrozenNode; +import blue.language.model.Node; +import blue.language.model.Schema; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -36,6 +38,12 @@ private static void updateNode(MessageDigest digest, FrozenNode node) { updateField(digest, "description", node.getDescription()); updateField(digest, "referenceBlueId", node.getReferenceBlueId()); updateField(digest, "blueId", node.blueId()); + if (node.isInlineValue()) { + updateField(digest, "inlineValue", "true"); + } + updateField(digest, "mergePolicy", node.getMergePolicy()); + updateField(digest, "previousBlueId", node.getPreviousBlueId()); + updateField(digest, "position", node.getPosition()); if (node.getValue() != null) { update(digest, "value:"); update(digest, node.getValue().getClass().getName()); @@ -44,9 +52,22 @@ private static void updateNode(MessageDigest digest, FrozenNode node) { update(digest, ";"); } if (node.getType() != null) { - update(digest, "type{"); - updateNode(digest, node.getType()); - update(digest, "}"); + updateNodeField(digest, "type", node.getType()); + } + if (node.getItemType() != null) { + updateNodeField(digest, "itemType", node.getItemType()); + } + if (node.getKeyType() != null) { + updateNodeField(digest, "keyType", node.getKeyType()); + } + if (node.getValueType() != null) { + updateNodeField(digest, "valueType", node.getValueType()); + } + if (node.getBlue() != null) { + updateNodeField(digest, "blue", node.getBlue()); + } + if (node.getSchema() != null) { + updateSchema(digest, node.getSchema()); } if (node.getItems() != null) { update(digest, "items["); @@ -68,6 +89,57 @@ private static void updateNode(MessageDigest digest, FrozenNode node) { } } + private static void updateNodeField(MessageDigest digest, String name, FrozenNode value) { + update(digest, name); + update(digest, "{"); + updateNode(digest, value); + update(digest, "}"); + } + + private static void updateSchema(MessageDigest digest, Schema schema) { + update(digest, "schema{"); + updateSchemaNodeField(digest, "required", schema.getRequired()); + updateSchemaNodeField(digest, "allowMultiple", schema.getAllowMultiple()); + updateSchemaNodeField(digest, "minLength", schema.getMinLength()); + updateSchemaNodeField(digest, "maxLength", schema.getMaxLength()); + updateSchemaNodeField(digest, "minimum", schema.getMinimum()); + updateSchemaNodeField(digest, "maximum", schema.getMaximum()); + updateSchemaNodeField(digest, "exclusiveMinimum", schema.getExclusiveMinimum()); + updateSchemaNodeField(digest, "exclusiveMaximum", schema.getExclusiveMaximum()); + updateSchemaNodeField(digest, "multipleOf", schema.getMultipleOf()); + updateSchemaNodeField(digest, "minItems", schema.getMinItems()); + updateSchemaNodeField(digest, "maxItems", schema.getMaxItems()); + updateSchemaNodeField(digest, "uniqueItems", schema.getUniqueItems()); + updateSchemaNodeField(digest, "minFields", schema.getMinFields()); + updateSchemaNodeField(digest, "maxFields", schema.getMaxFields()); + updateSchemaNodeListField(digest, "enum", schema.getEnum()); + updateSchemaNodeListField(digest, "options", schema.getOptions()); + update(digest, "}"); + } + + private static void updateSchemaNodeField(MessageDigest digest, String name, Node value) { + if (value == null) { + return; + } + update(digest, name); + update(digest, "{"); + updateNode(digest, FrozenNode.fromResolvedNode(value)); + update(digest, "}"); + } + + private static void updateSchemaNodeListField(MessageDigest digest, String name, List values) { + if (values == null) { + return; + } + update(digest, name); + update(digest, "["); + for (Node value : values) { + updateNode(digest, FrozenNode.fromResolvedNode(value)); + update(digest, ","); + } + update(digest, "]"); + } + private static void updateField(MessageDigest digest, String name, String value) { if (value != null) { update(digest, name); @@ -77,6 +149,12 @@ private static void updateField(MessageDigest digest, String name, String value) } } + private static void updateField(MessageDigest digest, String name, Object value) { + if (value != null) { + updateField(digest, name, String.valueOf(value)); + } + } + private static void update(MessageDigest digest, String value) { digest.update(value.getBytes(StandardCharsets.UTF_8)); } diff --git a/src/main/java/blue/bex/value/BexBlueNodeWriter.java b/src/main/java/blue/bex/value/BexBlueNodeWriter.java index c9d8538..76737ef 100644 --- a/src/main/java/blue/bex/value/BexBlueNodeWriter.java +++ b/src/main/java/blue/bex/value/BexBlueNodeWriter.java @@ -5,13 +5,18 @@ import blue.language.model.Schema; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; /** * Boundary writer from BEX values to Blue nodes using Blue language keys. */ public final class BexBlueNodeWriter { + private static final Set SCHEMA_KEYS = schemaKeys(); + private BexBlueNodeWriter() { } @@ -182,6 +187,7 @@ private static Schema toSchema(BexValue value) { if (!value.isObject()) { throw new BexException("Blue schema field must be an object"); } + validateSchemaKeys(value); Schema schema = new Schema(); setSchemaNode(schema, value, "required"); setSchemaNode(schema, value, "allowMultiple"); @@ -254,4 +260,32 @@ private static void setSchemaList(Schema schema, BexValue source, String key) { schema.options(nodes); } } + + private static void validateSchemaKeys(BexValue value) { + for (String key : value.keys()) { + if (!SCHEMA_KEYS.contains(key)) { + throw new BexException("Unsupported Blue schema field: " + key); + } + } + } + + private static Set schemaKeys() { + return new LinkedHashSet<>(Arrays.asList( + "required", + "allowMultiple", + "minLength", + "maxLength", + "minimum", + "maximum", + "exclusiveMinimum", + "exclusiveMaximum", + "multipleOf", + "minItems", + "maxItems", + "uniqueItems", + "minFields", + "maxFields", + "enum", + "options")); + } } diff --git a/src/test/java/blue/bex/BexBlueTypeSupportTest.java b/src/test/java/blue/bex/BexBlueTypeSupportTest.java index b80de92..4c31956 100644 --- a/src/test/java/blue/bex/BexBlueTypeSupportTest.java +++ b/src/test/java/blue/bex/BexBlueTypeSupportTest.java @@ -799,6 +799,16 @@ void nodeWriterRejectsSchemaAndConstraintsTogether() { assertThrows(BexException.class, () -> BexNodeWriter.toNode(value)); } + @Test + void nodeWriterRejectsUnknownSchemaFields() { + BexValue value = BexValues.fromSimple(m( + "schema", m("minLenght", 3))); + + BexException ex = assertThrows(BexException.class, () -> BexNodeWriter.toNode(value)); + assertTrue(ex.getMessage().contains("Unsupported Blue schema field: minLenght")); + assertThrows(BexException.class, () -> BexFrozenWriter.toFrozen(value)); + } + @Test void nodeWriterRejectsListControlFields() { assertThrows(BexException.class, () -> BexNodeWriter.toNode(BexValues.fromSimple(m( diff --git a/src/test/java/blue/bex/BexCompiledProgramCacheTest.java b/src/test/java/blue/bex/BexCompiledProgramCacheTest.java index f49b5f3..91af71e 100644 --- a/src/test/java/blue/bex/BexCompiledProgramCacheTest.java +++ b/src/test/java/blue/bex/BexCompiledProgramCacheTest.java @@ -6,6 +6,7 @@ import blue.bex.compile.BexCompiledProgramKey; import blue.bex.compile.BexNodeIdentity; import blue.bex.compile.LruBexCompiledProgramCache; +import blue.language.Blue; import blue.language.snapshot.FrozenNode; import org.junit.jupiter.api.Test; @@ -13,6 +14,8 @@ import static org.junit.jupiter.api.Assertions.*; class BexCompiledProgramCacheTest { + private final Blue blue = new Blue(); + @Test void differentNodesWithoutBlueIdDoNotCollide() { FrozenNode first = frozen(stepExpr(op("$document", "/status"))); @@ -47,4 +50,148 @@ void entryNameParticipatesInCacheKey() { assertNotEquals(BexCompiledProgramKey.from(BexProgramSource.withDefinition(step, definition, "a")), BexCompiledProgramKey.from(BexProgramSource.withDefinition(step, definition, "b"))); } + + @Test + void schemaDifferencesParticipateInCacheKeyAndCompiledBehavior() { + BexProgramSource minLengthOne = source(String.join("\n", + "type: Blue/BEX Program", + "functions:", + " f:", + " args:", + " input:", + " type: Text", + " schema:", + " minLength: 1", + " expr:", + " $var: input", + "expr:", + " $call:", + " function: f", + " args:", + " input: abc")); + BexProgramSource minLengthFive = source(String.join("\n", + "type: Blue/BEX Program", + "functions:", + " f:", + " args:", + " input:", + " type: Text", + " schema:", + " minLength: 5", + " expr:", + " $var: input", + "expr:", + " $call:", + " function: f", + " args:", + " input: abc")); + + assertNotEquals(BexNodeIdentity.stable(minLengthOne.programNode()), + BexNodeIdentity.stable(minLengthFive.programNode())); + assertNotEquals(BexCompiledProgramKey.from(minLengthOne), BexCompiledProgramKey.from(minLengthFive)); + + BexEngine engine = BexEngine.builder() + .blue(blue) + .cache(new LruBexCompiledProgramCache()) + .build(); + + assertEquals("abc", engine.compileAndExecute(minLengthOne, defaultContext()).value().toSimple()); + assertThrows(BexException.class, () -> engine.compileAndExecute(minLengthFive, defaultContext())); + } + + @Test + void valueTypeDifferencesParticipateInCacheKey() { + BexProgramSource integerValueType = source(String.join("\n", + "type: Blue/BEX Program", + "expr:", + " valueType:", + " type: Integer")); + BexProgramSource textValueType = source(String.join("\n", + "type: Blue/BEX Program", + "expr:", + " valueType:", + " type: Text")); + + assertNotEquals(BexNodeIdentity.stable(integerValueType.programNode()), + BexNodeIdentity.stable(textValueType.programNode())); + assertNotEquals(BexCompiledProgramKey.from(integerValueType), BexCompiledProgramKey.from(textValueType)); + } + + @Test + void itemTypeDifferencesParticipateInCacheKey() { + assertDifferentProgramIdentities( + String.join("\n", + "type: Blue/BEX Program", + "expr:", + " itemType:", + " type: Integer", + " items:", + " - 1"), + String.join("\n", + "type: Blue/BEX Program", + "expr:", + " itemType:", + " type: Text", + " items:", + " - 1")); + } + + @Test + void keyTypeDifferencesParticipateInCacheKey() { + assertDifferentProgramIdentities( + String.join("\n", + "type: Blue/BEX Program", + "expr:", + " keyType:", + " type: Text", + " a: 1"), + String.join("\n", + "type: Blue/BEX Program", + "expr:", + " keyType:", + " type: Integer", + " a: 1")); + } + + @Test + void blueDifferencesParticipateInCacheKey() { + assertDifferentProgramIdentities( + String.join("\n", + "type: Blue/BEX Program", + "expr:", + " blue:", + " source: A", + " value: payload"), + String.join("\n", + "type: Blue/BEX Program", + "expr:", + " blue:", + " source: B", + " value: payload")); + } + + @Test + void mergePolicyDifferencesParticipateInCacheKey() { + BexProgramSource first = BexProgramSource.inline(frozen(stepExpr(new blue.language.model.Node() + .mergePolicy("replace") + .properties(props("a", v(1)))))); + BexProgramSource second = BexProgramSource.inline(frozen(stepExpr(new blue.language.model.Node() + .mergePolicy("append") + .properties(props("a", v(1)))))); + + assertNotEquals(BexNodeIdentity.stable(first.programNode()), BexNodeIdentity.stable(second.programNode())); + assertNotEquals(BexCompiledProgramKey.from(first), BexCompiledProgramKey.from(second)); + } + + private BexProgramSource source(String yaml) { + return BexProgramSource.inline(FrozenNode.fromResolvedNode(blue.yamlToNode(yaml))); + } + + private void assertDifferentProgramIdentities(String firstYaml, String secondYaml) { + BexProgramSource first = source(firstYaml); + BexProgramSource second = source(secondYaml); + + assertNotEquals(BexNodeIdentity.stable(first.programNode()), BexNodeIdentity.stable(second.programNode())); + assertNotEquals(BexCompiledProgramKey.from(first), BexCompiledProgramKey.from(second)); + } } diff --git a/src/test/java/blue/bex/BexRichFixtureTest.java b/src/test/java/blue/bex/BexRichFixtureTest.java index 0b612e1..76c8fec 100644 --- a/src/test/java/blue/bex/BexRichFixtureTest.java +++ b/src/test/java/blue/bex/BexRichFixtureTest.java @@ -6,6 +6,7 @@ import blue.bex.api.BexStepResults; import blue.bex.api.FrozenBexDocumentView; import blue.bex.compile.BexCompiledProgram; +import blue.bex.gas.BexGasSchedule; import blue.bex.result.BexExecutionResult; import blue.bex.value.BexFrozenWriter; import blue.bex.value.BexNodeWriter; @@ -28,11 +29,14 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -51,20 +55,6 @@ class BexRichFixtureTest { " eventKind: Tiny", " payload: x"); - private final Blue blue = new Blue(blueId -> { - if ("HotelOrderType".equals(blueId)) { - return Collections.singletonList(YAML_BLUE.yamlToNode(String.join("\n", - "status:", - " type: Text"))); - } - if ("RestaurantOrderType".equals(blueId)) { - return Collections.singletonList(YAML_BLUE.yamlToNode(String.join("\n", - "restaurantStatus:", - " type: Text"))); - } - return Collections.emptyList(); - }); - private final BexEngine engine = BexEngine.builder().blue(blue).build(); private final Yaml yaml = new Yaml(); @TestFactory @@ -79,23 +69,26 @@ Collection richFixtures() throws Exception { private void runFixture(Path path) throws Exception { Map fixture = readFixture(path); + validateFixtureShape(fixture, path); + Blue blue = blueForFixture(fixture); Map expectation = map(fixture.get("expectation")); String outcome = string(expectation.get("outcome")); assertNotNull(outcome, "Fixture outcome is required: " + path); if ("parse-error".equals(outcome)) { - RuntimeException ex = assertThrows(RuntimeException.class, () -> parseProgram(fixture)); + RuntimeException ex = assertThrows(RuntimeException.class, () -> parseProgram(fixture, blue)); assertErrorContains(ex, expectation); return; } if ("parse-error-or-output-conversion-error".equals(outcome)) { - assertParseOrOutputConversionError(fixture, expectation); + assertParseOrOutputConversionError(fixture, expectation, blue); return; } - Node program = parseProgram(fixture); + Node program = parseProgram(fixture, blue); BexProgramSource source = BexProgramSource.inline(FrozenNode.fromResolvedNode(program)); - BexExecutionContext context = context(fixture); + BexExecutionContext context = context(fixture, blue); + BexEngine engine = engineForFixture(fixture, blue); if ("compile-error".equals(outcome)) { BexException ex = assertThrows(BexException.class, () -> engine.compile(source)); @@ -115,7 +108,7 @@ private void runFixture(Path path) throws Exception { return; } if ("gas-property".equals(outcome)) { - assertGasProperty(compiled, context, expectation); + assertGasProperty(engine, compiled, context, expectation, blue); return; } if (!"success".equals(outcome)) { @@ -126,17 +119,17 @@ private void runFixture(Path path) throws Exception { assertSuccessExpectations(result, expectation); } - private void assertParseOrOutputConversionError(Map fixture, Map expectation) { + private void assertParseOrOutputConversionError(Map fixture, Map expectation, Blue blue) { Node program; try { - program = parseProgram(fixture); + program = parseProgram(fixture, blue); } catch (RuntimeException ex) { assertErrorContains(ex, expectation); return; } BexProgramSource source = BexProgramSource.inline(FrozenNode.fromResolvedNode(program)); try { - BexExecutionResult result = engine.compileAndExecute(source, context(fixture)); + BexExecutionResult result = engineForFixture(fixture, blue).compileAndExecute(source, context(fixture, blue)); assertOutputConversionError(result.value(), expectation); } catch (BexException ex) { assertErrorContains(ex, expectation); @@ -156,13 +149,13 @@ private void assertOutputConversionError(BexValue value, Map exp assertErrorContains(thrown, expectation); } - private void assertGasProperty(BexCompiledProgram compiled, BexExecutionContext context, Map expectation) { + private void assertGasProperty(BexEngine engine, BexCompiledProgram compiled, BexExecutionContext context, Map expectation, Blue blue) { String property = string(expectation.get("property")); if (!"gasUsedGreaterThanEquivalentTinyEvent".equals(property)) { fail("Unsupported gas property: " + property); } long large = engine.execute(compiled, context).gasUsed(); - long tiny = engine.compileAndExecute(source(TINY_EVENT_PROGRAM), context).gasUsed(); + long tiny = engine.compileAndExecute(source(TINY_EVENT_PROGRAM, blue), context).gasUsed(); assertTrue(large > tiny, "Expected large output gas " + large + " to be greater than tiny output gas " + tiny); } @@ -176,27 +169,102 @@ private void assertSuccessExpectations(BexExecutionResult result, Map fixture) { + private BexExecutionContext context(Map fixture, Blue blue) { Map context = map(fixture.get("context")); String scope = string(context.get("documentScope")); if (scope == null) { scope = "/"; } - Node root = parseNodeSource(string(context.get("rootDocumentSource"))); - Node event = parseNodeSource(string(context.get("eventSource"))); - Node currentContract = parseNodeSource(string(context.get("currentContractSource"))); + Node root = parseNodeSource(string(context.get("rootDocumentSource")), blue); + Node event = parseNodeSource(string(context.get("eventSource")), blue); + Node currentContract = parseNodeSource(string(context.get("currentContractSource")), blue); + long gasLimit = context.containsKey("gasLimit") ? longValue(context.get("gasLimit")) : 1_000_000L; - return BexExecutionContext.builder() + BexExecutionContext.Builder builder = BexExecutionContext.builder() .document(new FrozenBexDocumentView(FrozenNode.fromResolvedNode(root), FrozenNode.fromResolvedNode(root), scope)) .event(BexValues.nodeSnapshot(event)) .currentContract(BexValues.nodeSnapshot(currentContract)) .steps(steps(context.get("stepsBinding"))) - .gasLimit(1_000_000) + .gasLimit(gasLimit); + for (Map.Entry entry : map(context.get("bindings")).entrySet()) { + builder.binding(entry.getKey(), BexValues.fromSimple(normalize(entry.getValue()))); + } + return builder.build(); + } + + private BexEngine engineForFixture(Map fixture, Blue blue) { + return BexEngine.builder() + .blue(blue) + .gasSchedule(gasSchedule(fixture)) .build(); } + private BexGasSchedule gasSchedule(Map fixture) { + Map overrides = map(fixture.get("gasSchedule")); + if (overrides.isEmpty()) { + return BexGasSchedule.defaults(); + } + BexGasSchedule.Builder builder = BexGasSchedule.builder(); + for (Map.Entry entry : overrides.entrySet()) { + long value = longValue(entry.getValue()); + switch (entry.getKey()) { + case "expressionBase": + builder.expressionBase(value); + break; + case "statementBase": + builder.statementBase(value); + break; + case "documentRead": + builder.documentRead(value); + break; + case "eventRead": + builder.eventRead(value); + break; + case "stepsRead": + builder.stepsRead(value); + break; + case "currentContractRead": + builder.currentContractRead(value); + break; + case "varRead": + builder.varRead(value); + break; + case "resultValueRead": + builder.resultValueRead(value); + break; + case "pointerGetBase": + builder.pointerGetBase(value); + break; + case "pointerSetBase": + builder.pointerSetBase(value); + break; + case "objectSetBase": + builder.objectSetBase(value); + break; + case "appendChangeBase": + builder.appendChangeBase(value); + break; + case "appendEventBase": + builder.appendEventBase(value); + break; + case "forEachItem": + builder.forEachItem(value); + break; + case "functionCall": + builder.functionCall(value); + break; + default: + throw new IllegalArgumentException("Unsupported gasSchedule field: " + entry.getKey()); + } + } + return builder.build(); + } + private BexStepResults steps(Object stepsObject) { Map stepsMap = map(stepsObject); BexStepResults.Builder builder = BexStepResults.builder(); @@ -206,18 +274,34 @@ private BexStepResults steps(Object stepsObject) { return builder.build(); } - private Node parseProgram(Map fixture) { + private Blue blueForFixture(Map fixture) { + Map definitions = map(fixture.get("blueDefinitions")); + if (definitions.isEmpty()) { + return new Blue(); + } + Map> parsed = new LinkedHashMap<>(); + for (Map.Entry entry : definitions.entrySet()) { + parsed.put(entry.getKey(), Collections.singletonList(YAML_BLUE.yamlToNode(requiredString(entry.getValue(), + "blueDefinitions." + entry.getKey())))); + } + return new Blue(blueId -> { + List nodes = parsed.get(blueId); + return nodes != null ? nodes : Collections.emptyList(); + }); + } + + private Node parseProgram(Map fixture, Blue blue) { return blue.yamlToNode(requiredString(fixture.get("programSource"), "programSource")); } - private Node parseNodeSource(String source) { + private Node parseNodeSource(String source, Blue blue) { if (source == null || source.trim().isEmpty()) { return blue.yamlToNode("{}"); } return blue.yamlToNode(source); } - private BexProgramSource source(String source) { + private BexProgramSource source(String source, Blue blue) { return BexProgramSource.inline(FrozenNode.fromResolvedNode(blue.yamlToNode(source))); } @@ -238,7 +322,9 @@ private List fixturePaths() throws URISyntaxException, IOException { final Path root = Paths.get(url.toURI()); List paths = new ArrayList<>(); try (Stream stream = Files.walk(root)) { - stream.filter(path -> Files.isRegularFile(path) && path.getFileName().toString().endsWith(".yaml")) + stream.filter(path -> Files.isRegularFile(path) + && path.getFileName().toString().endsWith(".yaml") + && !"manifest.yaml".equals(path.getFileName().toString())) .forEach(paths::add); } Collections.sort(paths); @@ -250,6 +336,106 @@ private String displayName(Path path) { return fileName != null ? fileName.toString() : path.toString(); } + private void validateFixtureShape(Map fixture, Path path) { + validateAllowedKeys(fixture, set("fixtureId", "title", "targetStatus", "tags", "context", + "blueDefinitions", "gasSchedule", "programSource", "expectation"), "fixture " + path); + requiredString(fixture.get("fixtureId"), "fixtureId"); + requiredString(fixture.get("title"), "title"); + validateTags(fixture.get("tags"), path); + validateTargetStatus(fixture.get("targetStatus"), path); + if (!fixture.containsKey("programSource")) { + throw new IllegalArgumentException("Fixture missing programSource: " + path); + } + if (!fixture.containsKey("expectation")) { + throw new IllegalArgumentException("Fixture missing expectation: " + path); + } + + Map context = map(fixture.get("context")); + validateAllowedKeys(context, set("documentScope", "rootDocumentSource", "eventSource", + "currentContractSource", "stepsBinding", "gasLimit", "bindings"), "context in " + path); + if (context.containsKey("bindings")) { + map(context.get("bindings")); + } + + Map definitions = map(fixture.get("blueDefinitions")); + for (Map.Entry entry : definitions.entrySet()) { + requiredString(entry.getValue(), "blueDefinitions." + entry.getKey()); + } + + validateAllowedKeys(map(fixture.get("gasSchedule")), gasScheduleFields(), "gasSchedule in " + path); + + Map expectation = map(fixture.get("expectation")); + String outcome = string(expectation.get("outcome")); + if (outcome == null) { + throw new IllegalArgumentException("Fixture expectation missing outcome: " + path); + } + Set allowedExpectation; + if ("success".equals(outcome)) { + allowedExpectation = set("outcome", "resultSimple", "changeset", "events", "gasUsed"); + } else if ("gas-property".equals(outcome)) { + allowedExpectation = set("outcome", "property"); + } else if ("parse-error".equals(outcome) + || "parse-error-or-output-conversion-error".equals(outcome) + || "compile-error".equals(outcome) + || "runtime-error".equals(outcome) + || "output-conversion-error".equals(outcome)) { + allowedExpectation = set("outcome", "errorContains"); + } else { + throw new IllegalArgumentException("Unsupported fixture outcome: " + outcome + " in " + path); + } + validateAllowedKeys(expectation, allowedExpectation, "expectation in " + path); + } + + private void validateTags(Object tags, Path path) { + if (tags == null) { + return; + } + if (!(tags instanceof List)) { + throw new IllegalArgumentException("Fixture tags must be a list: " + path); + } + for (Object tag : (List) tags) { + String text = string(tag); + if (text == null || text.trim().isEmpty()) { + throw new IllegalArgumentException("Fixture tags must contain only non-empty text values: " + path); + } + } + } + + private void validateTargetStatus(Object targetStatus, Path path) { + if (targetStatus == null) { + return; + } + String status = requiredString(targetStatus, "targetStatus"); + if (!targetStatuses().contains(status)) { + throw new IllegalArgumentException("Unsupported targetStatus " + status + " in " + path); + } + } + + private void validateAllowedKeys(Map values, Set allowed, String label) { + for (String key : values.keySet()) { + if (!allowed.contains(key)) { + throw new IllegalArgumentException(label + " contains unsupported field: " + key); + } + } + } + + private Set gasScheduleFields() { + return set("expressionBase", "statementBase", "documentRead", "eventRead", "stepsRead", + "currentContractRead", "varRead", "resultValueRead", "pointerGetBase", + "pointerSetBase", "objectSetBase", "appendChangeBase", "appendEventBase", + "forEachItem", "functionCall"); + } + + private Set targetStatuses() { + return set("current-pass", "current-compile-error", "current-runtime-error", + "current-output-conversion-error", "current-parse-error", + "current-parse-error-or-output-conversion-error", "current-gas-property"); + } + + private Set set(String... values) { + return new LinkedHashSet<>(Arrays.asList(values)); + } + private void assertErrorContains(Throwable ex, Map expectation) { String expected = string(expectation.get("errorContains")); if (expected == null || expected.isEmpty()) { @@ -287,6 +473,16 @@ private String string(Object value) { return value != null ? String.valueOf(value) : null; } + private long longValue(Object value) { + if (value instanceof Number) { + return ((Number) value).longValue(); + } + if (value instanceof String) { + return Long.parseLong((String) value); + } + throw new IllegalArgumentException("Expected integer value but found: " + value); + } + @SuppressWarnings("unchecked") private Object normalize(Object value) { if (value instanceof Map) { diff --git a/src/test/resources/rich-fixtures/current/14-is-blueid-typed-node-true.yaml b/src/test/resources/rich-fixtures/current/14-is-blueid-typed-node-true.yaml index c078348..89b3e26 100644 --- a/src/test/resources/rich-fixtures/current/14-is-blueid-typed-node-true.yaml +++ b/src/test/resources/rich-fixtures/current/14-is-blueid-typed-node-true.yaml @@ -5,6 +5,10 @@ tags: - types - $is - blueId +blueDefinitions: + HotelOrderType: | + status: + type: Text context: documentScope: / rootDocumentSource: | diff --git a/src/test/resources/rich-fixtures/current/15-is-blueid-wrong-type-false.yaml b/src/test/resources/rich-fixtures/current/15-is-blueid-wrong-type-false.yaml index 7408ad0..513a26c 100644 --- a/src/test/resources/rich-fixtures/current/15-is-blueid-wrong-type-false.yaml +++ b/src/test/resources/rich-fixtures/current/15-is-blueid-wrong-type-false.yaml @@ -5,6 +5,13 @@ tags: - types - $is - blueId +blueDefinitions: + HotelOrderType: | + status: + type: Text + RestaurantOrderType: | + restaurantStatus: + type: Text context: documentScope: / rootDocumentSource: | diff --git a/src/test/resources/rich-fixtures/gas/gas-001-root-expression-literal.yaml b/src/test/resources/rich-fixtures/gas/gas-001-root-expression-literal.yaml new file mode 100644 index 0000000..2223bb1 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-001-root-expression-literal.yaml @@ -0,0 +1,10 @@ +fixtureId: GAS-001 +title: Root expression literal charges root call and expression base +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: 1 +expectation: + outcome: success + resultSimple: 1 + gasUsed: 3 diff --git a/src/test/resources/rich-fixtures/gas/gas-002-empty-do-body.yaml b/src/test/resources/rich-fixtures/gas/gas-002-empty-do-body.yaml new file mode 100644 index 0000000..752b060 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-002-empty-do-body.yaml @@ -0,0 +1,9 @@ +fixtureId: GAS-002 +title: Empty do body charges only root function call +tags: [gas] +programSource: | + type: Blue/BEX Program + do: [] +expectation: + outcome: success + gasUsed: 2 diff --git a/src/test/resources/rich-fixtures/gas/gas-003-return-literal-statement.yaml b/src/test/resources/rich-fixtures/gas/gas-003-return-literal-statement.yaml new file mode 100644 index 0000000..6f4a782 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-003-return-literal-statement.yaml @@ -0,0 +1,11 @@ +fixtureId: GAS-003 +title: Return literal statement charges statement and literal expression +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $return: 1 +expectation: + outcome: success + resultSimple: 1 + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-004-custom-expression-base.yaml b/src/test/resources/rich-fixtures/gas/gas-004-custom-expression-base.yaml new file mode 100644 index 0000000..2a2f969 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-004-custom-expression-base.yaml @@ -0,0 +1,12 @@ +fixtureId: GAS-004 +title: Custom expressionBase schedule applies +tags: [gas] +gasSchedule: + expressionBase: 10 +programSource: | + type: Blue/BEX Program + expr: 1 +expectation: + outcome: success + resultSimple: 1 + gasUsed: 12 diff --git a/src/test/resources/rich-fixtures/gas/gas-010-document-read.yaml b/src/test/resources/rich-fixtures/gas/gas-010-document-read.yaml new file mode 100644 index 0000000..257e5a4 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-010-document-read.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-010 +title: Document read charges documentRead +tags: [gas] +context: + rootDocumentSource: | + status: pending +programSource: | + type: Blue/BEX Program + expr: + $document: /status +expectation: + outcome: success + resultSimple: pending + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-011-resolved-document-read.yaml b/src/test/resources/rich-fixtures/gas/gas-011-resolved-document-read.yaml new file mode 100644 index 0000000..12e4a4e --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-011-resolved-document-read.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-011 +title: Resolved document read costs the same as canonical document read +tags: [gas] +context: + rootDocumentSource: | + status: pending +programSource: | + type: Blue/BEX Program + expr: + $document: + path: /status + view: resolved +expectation: + outcome: success + resultSimple: pending + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-012-event-read.yaml b/src/test/resources/rich-fixtures/gas/gas-012-event-read.yaml new file mode 100644 index 0000000..84b52c2 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-012-event-read.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-012 +title: Event read charges eventRead +tags: [gas] +context: + eventSource: | + message: + request: + amount: 7 +programSource: | + type: Blue/BEX Program + expr: + $event: message/request/amount +expectation: + outcome: success + resultSimple: 7 + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-013-current-contract-read.yaml b/src/test/resources/rich-fixtures/gas/gas-013-current-contract-read.yaml new file mode 100644 index 0000000..d143471 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-013-current-contract-read.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-013 +title: Current contract read charges currentContractRead +tags: [gas] +context: + currentContractSource: | + channel: + name: hotel +programSource: | + type: Blue/BEX Program + expr: + $currentContract: channel/name +expectation: + outcome: success + resultSimple: hotel + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-014-steps-read.yaml b/src/test/resources/rich-fixtures/gas/gas-014-steps-read.yaml new file mode 100644 index 0000000..6a9a9a3 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-014-steps-read.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-014 +title: Steps read charges stepsRead +tags: [gas] +context: + stepsBinding: + Build: + value: 7 +programSource: | + type: Blue/BEX Program + expr: + $steps: Build.value +expectation: + outcome: success + resultSimple: 7 + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-015-binding-read.yaml b/src/test/resources/rich-fixtures/gas/gas-015-binding-read.yaml new file mode 100644 index 0000000..d43cbb2 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-015-binding-read.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-015 +title: Binding read charges varRead +tags: [gas] +context: + eventSource: | + message: + request: + amount: 7 +programSource: | + type: Blue/BEX Program + expr: + $binding: event/message/request/amount +expectation: + outcome: success + resultSimple: 7 + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-016-var-read.yaml b/src/test/resources/rich-fixtures/gas/gas-016-var-read.yaml new file mode 100644 index 0000000..1189ec9 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-016-var-read.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-016 +title: Variable read charges varRead +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $let: + name: x + expr: 1 + - $return: + $var: x +expectation: + outcome: success + resultSimple: 1 + gasUsed: 7 diff --git a/src/test/resources/rich-fixtures/gas/gas-017-result-value-read.yaml b/src/test/resources/rich-fixtures/gas/gas-017-result-value-read.yaml new file mode 100644 index 0000000..8850c3c --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-017-result-value-read.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-017 +title: Result value read charges resultValueRead +tags: [gas] +context: + rootDocumentSource: | + status: pending +programSource: | + type: Blue/BEX Program + expr: + $resultValue: /status +expectation: + outcome: success + resultSimple: pending + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-020-unary-integer.yaml b/src/test/resources/rich-fixtures/gas/gas-020-unary-integer.yaml new file mode 100644 index 0000000..61b3cfc --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-020-unary-integer.yaml @@ -0,0 +1,11 @@ +fixtureId: GAS-020 +title: Unary integer conversion charges operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $integer: "10" +expectation: + outcome: success + resultSimple: 10 + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-021-is-static-pattern.yaml b/src/test/resources/rich-fixtures/gas/gas-021-is-static-pattern.yaml new file mode 100644 index 0000000..35528e3 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-021-is-static-pattern.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-021 +title: Is predicate charges node expression but not static pattern +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $is: + node: 400 + pattern: + type: Integer +expectation: + outcome: success + resultSimple: true + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-022-compare-eq.yaml b/src/test/resources/rich-fixtures/gas/gas-022-compare-eq.yaml new file mode 100644 index 0000000..5b2869e --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-022-compare-eq.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-022 +title: Equality comparison charges both operands +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $eq: + - 1 + - 1 +expectation: + outcome: success + resultSimple: true + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-023-numeric-three-operands.yaml b/src/test/resources/rich-fixtures/gas/gas-023-numeric-three-operands.yaml new file mode 100644 index 0000000..0e77499 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-023-numeric-three-operands.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-023 +title: Numeric expression charges each operand +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $add: + - 1 + - 2 + - 3 +expectation: + outcome: success + resultSimple: 6 + gasUsed: 6 diff --git a/src/test/resources/rich-fixtures/gas/gas-024-concat-three-operands.yaml b/src/test/resources/rich-fixtures/gas/gas-024-concat-three-operands.yaml new file mode 100644 index 0000000..a7dc01b --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-024-concat-three-operands.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-024 +title: Concat charges each operand +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $concat: + - a + - b + - c +expectation: + outcome: success + resultSimple: abc + gasUsed: 6 diff --git a/src/test/resources/rich-fixtures/gas/gas-025-join.yaml b/src/test/resources/rich-fixtures/gas/gas-025-join.yaml new file mode 100644 index 0000000..f39e9ce --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-025-join.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-025 +title: Join charges list and separator expressions +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $join: + list: + - a + - b + separator: ":" +expectation: + outcome: success + resultSimple: "a:b" + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-026-split-no-limit.yaml b/src/test/resources/rich-fixtures/gas/gas-026-split-no-limit.yaml new file mode 100644 index 0000000..2d7ee5b --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-026-split-no-limit.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-026 +title: Split without limit charges text and separator expressions +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $split: + text: "a:b" + separator: ":" +expectation: + outcome: success + resultSimple: + - a + - b + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-027-split-with-limit.yaml b/src/test/resources/rich-fixtures/gas/gas-027-split-with-limit.yaml new file mode 100644 index 0000000..2e826fe --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-027-split-with-limit.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-027 +title: Split with limit charges the limit expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $split: + text: "a:b:c" + separator: ":" + limit: 2 +expectation: + outcome: success + resultSimple: + - a + - "b:c" + gasUsed: 6 diff --git a/src/test/resources/rich-fixtures/gas/gas-028-pointer-join-three-segments.yaml b/src/test/resources/rich-fixtures/gas/gas-028-pointer-join-three-segments.yaml new file mode 100644 index 0000000..05dda29 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-028-pointer-join-three-segments.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-028 +title: Pointer join charges each segment expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $pointerJoin: + - orders + - id + - status +expectation: + outcome: success + resultSimple: /orders/id/status + gasUsed: 6 diff --git a/src/test/resources/rich-fixtures/gas/gas-029-keys.yaml b/src/test/resources/rich-fixtures/gas/gas-029-keys.yaml new file mode 100644 index 0000000..2f3f0c3 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-029-keys.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-029 +title: Keys charges object operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $keys: + a: 1 + b: 2 +expectation: + outcome: success + resultSimple: + - a + - b + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-030-entries.yaml b/src/test/resources/rich-fixtures/gas/gas-030-entries.yaml new file mode 100644 index 0000000..8344b4c --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-030-entries.yaml @@ -0,0 +1,17 @@ +fixtureId: GAS-030 +title: Entries charges object operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $entries: + a: 1 + b: 2 +expectation: + outcome: success + resultSimple: + - key: a + val: 1 + - key: b + val: 2 + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-031-size.yaml b/src/test/resources/rich-fixtures/gas/gas-031-size.yaml new file mode 100644 index 0000000..7505543 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-031-size.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-031 +title: Size charges operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $size: + - a + - b +expectation: + outcome: success + resultSimple: 2 + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-032-list-concat-two-lists.yaml b/src/test/resources/rich-fixtures/gas/gas-032-list-concat-two-lists.yaml new file mode 100644 index 0000000..52ef2a1 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-032-list-concat-two-lists.yaml @@ -0,0 +1,17 @@ +fixtureId: GAS-032 +title: List concat charges each list operand +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $listConcat: + - + - a + - + - b +expectation: + outcome: success + resultSimple: + - a + - b + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-033-merge-two-objects.yaml b/src/test/resources/rich-fixtures/gas/gas-033-merge-two-objects.yaml new file mode 100644 index 0000000..ea41385 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-033-merge-two-objects.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-033 +title: Merge charges each object operand +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $merge: + - a: 1 + - b: 2 +expectation: + outcome: success + resultSimple: + a: 1 + b: 2 + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-034-static-object-literal.yaml b/src/test/resources/rich-fixtures/gas/gas-034-static-object-literal.yaml new file mode 100644 index 0000000..10def07 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-034-static-object-literal.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-034 +title: Static object literal charges as one expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + a: 1 + b: 2 +expectation: + outcome: success + resultSimple: + a: 1 + b: 2 + gasUsed: 3 diff --git a/src/test/resources/rich-fixtures/gas/gas-035-dynamic-object-literal.yaml b/src/test/resources/rich-fixtures/gas/gas-035-dynamic-object-literal.yaml new file mode 100644 index 0000000..c00eab4 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-035-dynamic-object-literal.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-035 +title: Dynamic object literal charges object expression and dynamic child +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + answer: + $add: + - 1 + - 2 + - 3 +expectation: + outcome: success + resultSimple: + answer: 6 + gasUsed: 7 diff --git a/src/test/resources/rich-fixtures/gas/gas-036-static-list-literal.yaml b/src/test/resources/rich-fixtures/gas/gas-036-static-list-literal.yaml new file mode 100644 index 0000000..425bc6e --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-036-static-list-literal.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-036 +title: Static list literal charges as one expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + - a + - b +expectation: + outcome: success + resultSimple: + - a + - b + gasUsed: 3 diff --git a/src/test/resources/rich-fixtures/gas/gas-037-dynamic-list-literal.yaml b/src/test/resources/rich-fixtures/gas/gas-037-dynamic-list-literal.yaml new file mode 100644 index 0000000..8b9e8f5 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-037-dynamic-list-literal.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-037 +title: Dynamic list literal charges list expression and dynamic child +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + - $add: + - 1 + - 2 + - 3 +expectation: + outcome: success + resultSimple: + - 6 + gasUsed: 7 diff --git a/src/test/resources/rich-fixtures/gas/gas-038-get-static-key.yaml b/src/test/resources/rich-fixtures/gas/gas-038-get-static-key.yaml new file mode 100644 index 0000000..8db50c1 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-038-get-static-key.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-038 +title: Get with static key charges object expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $get: + object: + a: 1 + key: a +expectation: + outcome: success + resultSimple: 1 + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-039-get-dynamic-key.yaml b/src/test/resources/rich-fixtures/gas/gas-039-get-dynamic-key.yaml new file mode 100644 index 0000000..687b02e --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-039-get-dynamic-key.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-039 +title: Get with dynamic key charges key expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $get: + object: + a: 1 + key: + $literal: a +expectation: + outcome: success + resultSimple: 1 + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-040-list-get-hit.yaml b/src/test/resources/rich-fixtures/gas/gas-040-list-get-hit.yaml new file mode 100644 index 0000000..235b2ee --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-040-list-get-hit.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-040 +title: List get hit charges list and index expressions +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $listGet: + list: + - a + - b + index: 1 +expectation: + outcome: success + resultSimple: b + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-041-list-get-default.yaml b/src/test/resources/rich-fixtures/gas/gas-041-list-get-default.yaml new file mode 100644 index 0000000..6a3b997 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-041-list-get-default.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-041 +title: List get default charges default expression only when used +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $listGet: + list: + - a + index: 3 + default: fallback +expectation: + outcome: success + resultSimple: fallback + gasUsed: 6 diff --git a/src/test/resources/rich-fixtures/gas/gas-042-object-set-scalar.yaml b/src/test/resources/rich-fixtures/gas/gas-042-object-set-scalar.yaml new file mode 100644 index 0000000..bc2d185 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-042-object-set-scalar.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-042 +title: Object set charges value size and object expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $objectSet: + object: {} + key: a + val: x +expectation: + outcome: success + resultSimple: + a: x + gasUsed: 8 diff --git a/src/test/resources/rich-fixtures/gas/gas-043-pointer-get-hit-two-segments.yaml b/src/test/resources/rich-fixtures/gas/gas-043-pointer-get-hit-two-segments.yaml new file mode 100644 index 0000000..4a44e43 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-043-pointer-get-hit-two-segments.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-043 +title: Pointer get charges base and path segment count +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $pointerGet: + object: + a: + b: 1 + path: a/b +expectation: + outcome: success + resultSimple: 1 + gasUsed: 7 diff --git a/src/test/resources/rich-fixtures/gas/gas-044-pointer-get-default.yaml b/src/test/resources/rich-fixtures/gas/gas-044-pointer-get-default.yaml new file mode 100644 index 0000000..f40bd32 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-044-pointer-get-default.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-044 +title: Pointer get default charges default expression only when used +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $pointerGet: + object: + a: 1 + path: missing/path + default: fallback +expectation: + outcome: success + resultSimple: fallback + gasUsed: 8 diff --git a/src/test/resources/rich-fixtures/gas/gas-045-pointer-set-value.yaml b/src/test/resources/rich-fixtures/gas/gas-045-pointer-set-value.yaml new file mode 100644 index 0000000..7bedcdd --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-045-pointer-set-value.yaml @@ -0,0 +1,17 @@ +fixtureId: GAS-045 +title: Pointer set charges path segments and value size +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $pointerSet: + object: + a: {} + path: a/b + val: x +expectation: + outcome: success + resultSimple: + a: + b: x + gasUsed: 11 diff --git a/src/test/resources/rich-fixtures/gas/gas-046-pointer-set-remove.yaml b/src/test/resources/rich-fixtures/gas/gas-046-pointer-set-remove.yaml new file mode 100644 index 0000000..9f25e8a --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-046-pointer-set-remove.yaml @@ -0,0 +1,19 @@ +fixtureId: GAS-046 +title: Pointer set remove does not evaluate val and charges undefined size +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $pointerSet: + object: + a: + b: x + op: remove + path: a/b + val: + $document: /expensive +expectation: + outcome: success + resultSimple: + a: {} + gasUsed: 9 diff --git a/src/test/resources/rich-fixtures/gas/gas-047-changeset-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-047-changeset-expression.yaml new file mode 100644 index 0000000..d2bc416 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-047-changeset-expression.yaml @@ -0,0 +1,11 @@ +fixtureId: GAS-047 +title: Changeset expression charges only expression base +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $changeset: true +expectation: + outcome: success + resultSimple: [] + gasUsed: 3 diff --git a/src/test/resources/rich-fixtures/gas/gas-048-events-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-048-events-expression.yaml new file mode 100644 index 0000000..968b00c --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-048-events-expression.yaml @@ -0,0 +1,11 @@ +fixtureId: GAS-048 +title: Events expression charges only expression base +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $events: true +expectation: + outcome: success + resultSimple: [] + gasUsed: 3 diff --git a/src/test/resources/rich-fixtures/gas/gas-050-and-short-circuit.yaml b/src/test/resources/rich-fixtures/gas/gas-050-and-short-circuit.yaml new file mode 100644 index 0000000..b1c1b95 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-050-and-short-circuit.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-050 +title: And short-circuits without charging skipped operands +tags: [gas] +context: + rootDocumentSource: | + status: confirmed +programSource: | + type: Blue/BEX Program + expr: + $and: + - false + - $document: /status +expectation: + outcome: success + resultSimple: false + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-051-and-full-evaluation.yaml b/src/test/resources/rich-fixtures/gas/gas-051-and-full-evaluation.yaml new file mode 100644 index 0000000..3a527c9 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-051-and-full-evaluation.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-051 +title: And charges later operand when earlier operand is truthy +tags: [gas] +context: + rootDocumentSource: | + status: confirmed +programSource: | + type: Blue/BEX Program + expr: + $and: + - true + - $document: /status +expectation: + outcome: success + resultSimple: true + gasUsed: 7 diff --git a/src/test/resources/rich-fixtures/gas/gas-052-or-short-circuit.yaml b/src/test/resources/rich-fixtures/gas/gas-052-or-short-circuit.yaml new file mode 100644 index 0000000..d2ec486 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-052-or-short-circuit.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-052 +title: Or short-circuits without charging skipped operands +tags: [gas] +context: + rootDocumentSource: | + status: confirmed +programSource: | + type: Blue/BEX Program + expr: + $or: + - true + - $document: /status +expectation: + outcome: success + resultSimple: true + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-053-or-full-evaluation.yaml b/src/test/resources/rich-fixtures/gas/gas-053-or-full-evaluation.yaml new file mode 100644 index 0000000..3733980 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-053-or-full-evaluation.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-053 +title: Or charges later operand when earlier operand is falsy +tags: [gas] +context: + rootDocumentSource: | + status: confirmed +programSource: | + type: Blue/BEX Program + expr: + $or: + - false + - $document: /status +expectation: + outcome: success + resultSimple: true + gasUsed: 7 diff --git a/src/test/resources/rich-fixtures/gas/gas-054-coalesce-short-circuit.yaml b/src/test/resources/rich-fixtures/gas/gas-054-coalesce-short-circuit.yaml new file mode 100644 index 0000000..126cbbf --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-054-coalesce-short-circuit.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-054 +title: Coalesce short-circuits on first non-empty value +tags: [gas] +context: + rootDocumentSource: | + status: confirmed +programSource: | + type: Blue/BEX Program + expr: + $coalesce: + - a + - $document: /status +expectation: + outcome: success + resultSimple: a + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-055-coalesce-full-evaluation.yaml b/src/test/resources/rich-fixtures/gas/gas-055-coalesce-full-evaluation.yaml new file mode 100644 index 0000000..48a60b4 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-055-coalesce-full-evaluation.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-055 +title: Coalesce charges later operand when earlier value is empty +tags: [gas] +context: + rootDocumentSource: | + status: confirmed +programSource: | + type: Blue/BEX Program + expr: + $coalesce: + - "" + - $document: /status +expectation: + outcome: success + resultSimple: confirmed + gasUsed: 7 diff --git a/src/test/resources/rich-fixtures/gas/gas-056-if-selected-branch-only.yaml b/src/test/resources/rich-fixtures/gas/gas-056-if-selected-branch-only.yaml new file mode 100644 index 0000000..5002929 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-056-if-selected-branch-only.yaml @@ -0,0 +1,18 @@ +fixtureId: GAS-056 +title: If charges only the selected branch +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $if: + cond: true + then: + - $appendEvent: x + else: + - $appendEvent: + $document: /expensive +expectation: + outcome: success + events: + - x + gasUsed: 12 diff --git a/src/test/resources/rich-fixtures/gas/gas-057-foreach-list-empty-body.yaml b/src/test/resources/rich-fixtures/gas/gas-057-foreach-list-empty-body.yaml new file mode 100644 index 0000000..390045d --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-057-foreach-list-empty-body.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-057 +title: ForEach over list charges one item cost per element +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $forEach: + in: + - a + - b + - c + item: item + do: [] +expectation: + outcome: success + gasUsed: 7 diff --git a/src/test/resources/rich-fixtures/gas/gas-058-foreach-list-body-append.yaml b/src/test/resources/rich-fixtures/gas/gas-058-foreach-list-body-append.yaml new file mode 100644 index 0000000..16f8a7e --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-058-foreach-list-body-append.yaml @@ -0,0 +1,19 @@ +fixtureId: GAS-058 +title: ForEach body charges statements on each iteration +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $forEach: + in: + - a + - b + item: item + do: + - $appendEvent: x +expectation: + outcome: success + events: + - x + - x + gasUsed: 22 diff --git a/src/test/resources/rich-fixtures/gas/gas-059-function-call-no-args.yaml b/src/test/resources/rich-fixtures/gas/gas-059-function-call-no-args.yaml new file mode 100644 index 0000000..f45e011 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-059-function-call-no-args.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-059 +title: Function call charges caller expression and callee function call +tags: [gas] +programSource: | + type: Blue/BEX Program + functions: + f: + expr: 1 + expr: + $call: + function: f + args: {} +expectation: + outcome: success + resultSimple: 1 + gasUsed: 6 diff --git a/src/test/resources/rich-fixtures/gas/gas-060-function-call-typed-arg.yaml b/src/test/resources/rich-fixtures/gas/gas-060-function-call-typed-arg.yaml new file mode 100644 index 0000000..ade0021 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-060-function-call-typed-arg.yaml @@ -0,0 +1,21 @@ +fixtureId: GAS-060 +title: Typed function call charges argument expression and callee var read +tags: [gas] +programSource: | + type: Blue/BEX Program + functions: + f: + args: + amount: + type: Integer + expr: + $var: amount + expr: + $call: + function: f + args: + amount: 1 +expectation: + outcome: success + resultSimple: 1 + gasUsed: 8 diff --git a/src/test/resources/rich-fixtures/gas/gas-061-call-statement.yaml b/src/test/resources/rich-fixtures/gas/gas-061-call-statement.yaml new file mode 100644 index 0000000..02fdcc0 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-061-call-statement.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-061 +title: Call statement charges statement and call expression +tags: [gas] +programSource: | + type: Blue/BEX Program + functions: + f: + expr: 1 + do: + - $call: + function: f + args: {} +expectation: + outcome: success + gasUsed: 7 diff --git a/src/test/resources/rich-fixtures/gas/gas-070-append-event-null.yaml b/src/test/resources/rich-fixtures/gas/gas-070-append-event-null.yaml new file mode 100644 index 0000000..9a10afb --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-070-append-event-null.yaml @@ -0,0 +1,12 @@ +fixtureId: GAS-070 +title: Append null event charges zero estimated size +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $appendEvent: null +expectation: + outcome: success + events: + - null + gasUsed: 9 diff --git a/src/test/resources/rich-fixtures/gas/gas-071-append-event-string.yaml b/src/test/resources/rich-fixtures/gas/gas-071-append-event-string.yaml new file mode 100644 index 0000000..ba9b785 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-071-append-event-string.yaml @@ -0,0 +1,12 @@ +fixtureId: GAS-071 +title: Append scalar event charges appendEventBase plus scalar size +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $appendEvent: x +expectation: + outcome: success + events: + - x + gasUsed: 10 diff --git a/src/test/resources/rich-fixtures/gas/gas-072-append-event-boolean.yaml b/src/test/resources/rich-fixtures/gas/gas-072-append-event-boolean.yaml new file mode 100644 index 0000000..94ff053 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-072-append-event-boolean.yaml @@ -0,0 +1,12 @@ +fixtureId: GAS-072 +title: Boolean event size is text length of true +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $appendEvent: true +expectation: + outcome: success + events: + - true + gasUsed: 13 diff --git a/src/test/resources/rich-fixtures/gas/gas-073-append-event-integer.yaml b/src/test/resources/rich-fixtures/gas/gas-073-append-event-integer.yaml new file mode 100644 index 0000000..0e28edf --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-073-append-event-integer.yaml @@ -0,0 +1,12 @@ +fixtureId: GAS-073 +title: Integer event size is decimal text length +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $appendEvent: 12345 +expectation: + outcome: success + events: + - 12345 + gasUsed: 14 diff --git a/src/test/resources/rich-fixtures/gas/gas-074-append-event-list.yaml b/src/test/resources/rich-fixtures/gas/gas-074-append-event-list.yaml new file mode 100644 index 0000000..ec72e96 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-074-append-event-list.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-074 +title: List event size includes item count and item sizes +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $appendEvent: + - a + - bb +expectation: + outcome: success + events: + - + - a + - bb + gasUsed: 14 diff --git a/src/test/resources/rich-fixtures/gas/gas-075-append-event-object.yaml b/src/test/resources/rich-fixtures/gas/gas-075-append-event-object.yaml new file mode 100644 index 0000000..7a9f6c5 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-075-append-event-object.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-075 +title: Object event size includes key count, key lengths, and value sizes +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $appendEvent: + a: x + bb: yy +expectation: + outcome: success + events: + - a: x + bb: yy + gasUsed: 17 diff --git a/src/test/resources/rich-fixtures/gas/gas-076-append-events-two-scalars.yaml b/src/test/resources/rich-fixtures/gas/gas-076-append-events-two-scalars.yaml new file mode 100644 index 0000000..1876e7e --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-076-append-events-two-scalars.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-076 +title: AppendEvents charges append event cost per item +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $appendEvents: + - x + - yy +expectation: + outcome: success + events: + - x + - yy + gasUsed: 17 diff --git a/src/test/resources/rich-fixtures/gas/gas-077-append-change-replace-scalar.yaml b/src/test/resources/rich-fixtures/gas/gas-077-append-change-replace-scalar.yaml new file mode 100644 index 0000000..c396343 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-077-append-change-replace-scalar.yaml @@ -0,0 +1,17 @@ +fixtureId: GAS-077 +title: AppendChange replace charges value expression and value size +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $appendChange: + op: replace + path: /status + val: x +expectation: + outcome: success + changeset: + - op: replace + path: /status + val: x + gasUsed: 10 diff --git a/src/test/resources/rich-fixtures/gas/gas-078-append-change-remove-lazy-val.yaml b/src/test/resources/rich-fixtures/gas/gas-078-append-change-remove-lazy-val.yaml new file mode 100644 index 0000000..47b91e2 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-078-append-change-remove-lazy-val.yaml @@ -0,0 +1,17 @@ +fixtureId: GAS-078 +title: AppendChange remove does not evaluate val and charges undefined size +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $appendChange: + op: remove + path: /status + val: + $document: /expensive +expectation: + outcome: success + changeset: + - op: remove + path: /status + gasUsed: 8 diff --git a/src/test/resources/rich-fixtures/gas/gas-079-append-changes-two-entries.yaml b/src/test/resources/rich-fixtures/gas/gas-079-append-changes-two-entries.yaml new file mode 100644 index 0000000..f1205b1 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-079-append-changes-two-entries.yaml @@ -0,0 +1,23 @@ +fixtureId: GAS-079 +title: AppendChanges charges append change cost per entry +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $appendChanges: + - op: replace + path: /a + val: x + - op: replace + path: /b + val: yy +expectation: + outcome: success + changeset: + - op: replace + path: /a + val: x + - op: replace + path: /b + val: yy + gasUsed: 17 diff --git a/src/test/resources/rich-fixtures/gas/gas-090-expression-gas-exhaustion.yaml b/src/test/resources/rich-fixtures/gas/gas-090-expression-gas-exhaustion.yaml new file mode 100644 index 0000000..5a468b8 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-090-expression-gas-exhaustion.yaml @@ -0,0 +1,11 @@ +fixtureId: GAS-090 +title: Expression gas exhaustion fails when total exceeds limit +tags: [gas] +context: + gasLimit: 2 +programSource: | + type: Blue/BEX Program + expr: 1 +expectation: + outcome: runtime-error + errorContains: BEX gas exhausted diff --git a/src/test/resources/rich-fixtures/gas/gas-091-output-size-gas-exhaustion.yaml b/src/test/resources/rich-fixtures/gas/gas-091-output-size-gas-exhaustion.yaml new file mode 100644 index 0000000..5180214 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-091-output-size-gas-exhaustion.yaml @@ -0,0 +1,12 @@ +fixtureId: GAS-091 +title: Output size gas exhaustion fails during append output charge +tags: [gas] +context: + gasLimit: 12 +programSource: | + type: Blue/BEX Program + do: + - $appendEvent: abcdefghij +expectation: + outcome: runtime-error + errorContains: BEX gas exhausted diff --git a/src/test/resources/rich-fixtures/gas/gas-092-literal-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-092-literal-expression.yaml new file mode 100644 index 0000000..ee32979 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-092-literal-expression.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-092 +title: Literal expression does not compile nested operator-like payload +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $literal: + $document: /status +expectation: + outcome: success + resultSimple: + $document: /status + gasUsed: 3 diff --git a/src/test/resources/rich-fixtures/gas/gas-093-const-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-093-const-expression.yaml new file mode 100644 index 0000000..83e0e73 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-093-const-expression.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-093 +title: Const expression charges expression base only +tags: [gas] +programSource: | + type: Blue/BEX Program + constants: + amount: 400 + expr: + $const: amount +expectation: + outcome: success + resultSimple: 400 + gasUsed: 3 diff --git a/src/test/resources/rich-fixtures/gas/gas-094-unwrap-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-094-unwrap-expression.yaml new file mode 100644 index 0000000..5e1bf01 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-094-unwrap-expression.yaml @@ -0,0 +1,12 @@ +fixtureId: GAS-094 +title: Unwrap charges wrapped value expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $unwrap: + value: x +expectation: + outcome: success + resultSimple: x + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-095-text-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-095-text-expression.yaml new file mode 100644 index 0000000..9376d25 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-095-text-expression.yaml @@ -0,0 +1,11 @@ +fixtureId: GAS-095 +title: Text conversion charges operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $text: 10 +expectation: + outcome: success + resultSimple: "10" + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-096-number-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-096-number-expression.yaml new file mode 100644 index 0000000..27572f7 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-096-number-expression.yaml @@ -0,0 +1,10 @@ +fixtureId: GAS-096 +title: Number conversion charges operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $number: "10" +expectation: + outcome: success + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-097-boolean-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-097-boolean-expression.yaml new file mode 100644 index 0000000..2a3a0ee --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-097-boolean-expression.yaml @@ -0,0 +1,11 @@ +fixtureId: GAS-097 +title: Boolean conversion charges operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $boolean: true +expectation: + outcome: success + resultSimple: true + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-098-object-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-098-object-expression.yaml new file mode 100644 index 0000000..bd7797f --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-098-object-expression.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-098 +title: Object conversion charges operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $object: + a: 1 +expectation: + outcome: success + resultSimple: + a: 1 + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-099-list-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-099-list-expression.yaml new file mode 100644 index 0000000..3581b50 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-099-list-expression.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-099 +title: List conversion charges operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $list: + - a +expectation: + outcome: success + resultSimple: + - a + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-100-truthy-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-100-truthy-expression.yaml new file mode 100644 index 0000000..468ef4a --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-100-truthy-expression.yaml @@ -0,0 +1,11 @@ +fixtureId: GAS-100 +title: Truthy conversion charges operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $truthy: x +expectation: + outcome: success + resultSimple: true + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-101-empty-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-101-empty-expression.yaml new file mode 100644 index 0000000..2ef2959 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-101-empty-expression.yaml @@ -0,0 +1,11 @@ +fixtureId: GAS-101 +title: Empty conversion charges operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $empty: "" +expectation: + outcome: success + resultSimple: true + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-102-exists-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-102-exists-expression.yaml new file mode 100644 index 0000000..cc618c2 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-102-exists-expression.yaml @@ -0,0 +1,12 @@ +fixtureId: GAS-102 +title: Exists charges evaluated operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $exists: + $document: /missing +expectation: + outcome: success + resultSimple: false + gasUsed: 6 diff --git a/src/test/resources/rich-fixtures/gas/gas-103-not-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-103-not-expression.yaml new file mode 100644 index 0000000..307e96b --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-103-not-expression.yaml @@ -0,0 +1,11 @@ +fixtureId: GAS-103 +title: Not charges operand expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $not: true +expectation: + outcome: success + resultSimple: false + gasUsed: 4 diff --git a/src/test/resources/rich-fixtures/gas/gas-104-default-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-104-default-expression.yaml new file mode 100644 index 0000000..27f2ecd --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-104-default-expression.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-104 +title: Default is coalesce and charges until first non-empty value +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $default: + - "" + - fallback +expectation: + outcome: success + resultSimple: fallback + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-105-starts-with-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-105-starts-with-expression.yaml new file mode 100644 index 0000000..2e94df2 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-105-starts-with-expression.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-105 +title: StartsWith charges two operands +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $startsWith: + - abc + - a +expectation: + outcome: success + resultSimple: true + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-106-slice-after-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-106-slice-after-expression.yaml new file mode 100644 index 0000000..4121851 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-106-slice-after-expression.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-106 +title: SliceAfter charges two operands +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $sliceAfter: + - abc + - a +expectation: + outcome: success + resultSimple: bc + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-107-ne-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-107-ne-expression.yaml new file mode 100644 index 0000000..63c97ec --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-107-ne-expression.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-107 +title: Not-equal comparison charges both operands +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $ne: + - 1 + - 2 +expectation: + outcome: success + resultSimple: true + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-108-gt-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-108-gt-expression.yaml new file mode 100644 index 0000000..f34b5b6 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-108-gt-expression.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-108 +title: Greater-than comparison charges both operands +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $gt: + - 2 + - 1 +expectation: + outcome: success + resultSimple: true + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-109-gte-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-109-gte-expression.yaml new file mode 100644 index 0000000..ce159a8 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-109-gte-expression.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-109 +title: Greater-or-equal comparison charges both operands +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $gte: + - 1 + - 1 +expectation: + outcome: success + resultSimple: true + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-110-lt-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-110-lt-expression.yaml new file mode 100644 index 0000000..5a84d29 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-110-lt-expression.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-110 +title: Less-than comparison charges both operands +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $lt: + - 1 + - 2 +expectation: + outcome: success + resultSimple: true + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-111-lte-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-111-lte-expression.yaml new file mode 100644 index 0000000..0dd34d5 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-111-lte-expression.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-111 +title: Less-or-equal comparison charges both operands +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $lte: + - 1 + - 1 +expectation: + outcome: success + resultSimple: true + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-112-subtract-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-112-subtract-expression.yaml new file mode 100644 index 0000000..219b05c --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-112-subtract-expression.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-112 +title: Subtract charges each operand +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $subtract: + - 10 + - 2 + - 3 +expectation: + outcome: success + resultSimple: 5 + gasUsed: 6 diff --git a/src/test/resources/rich-fixtures/gas/gas-113-multiply-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-113-multiply-expression.yaml new file mode 100644 index 0000000..bf21b03 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-113-multiply-expression.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-113 +title: Multiply charges each operand +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $multiply: + - 2 + - 3 + - 4 +expectation: + outcome: success + resultSimple: 24 + gasUsed: 6 diff --git a/src/test/resources/rich-fixtures/gas/gas-114-divide-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-114-divide-expression.yaml new file mode 100644 index 0000000..b049223 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-114-divide-expression.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-114 +title: Divide charges each operand +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $divide: + - 8 + - 2 + - 2 +expectation: + outcome: success + resultSimple: 2 + gasUsed: 6 diff --git a/src/test/resources/rich-fixtures/gas/gas-115-choose-expression.yaml b/src/test/resources/rich-fixtures/gas/gas-115-choose-expression.yaml new file mode 100644 index 0000000..f529113 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-115-choose-expression.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-115 +title: Choose charges only condition and selected branch +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $choose: + cond: true + then: selected + else: + $document: /expensive +expectation: + outcome: success + resultSimple: selected + gasUsed: 5 diff --git a/src/test/resources/rich-fixtures/gas/gas-116-set-statement.yaml b/src/test/resources/rich-fixtures/gas/gas-116-set-statement.yaml new file mode 100644 index 0000000..f775ce6 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-116-set-statement.yaml @@ -0,0 +1,18 @@ +fixtureId: GAS-116 +title: Set statement charges statement and assigned expression +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $let: + name: x + expr: 1 + - $set: + name: x + expr: 2 + - $return: + $var: x +expectation: + outcome: success + resultSimple: 2 + gasUsed: 9 diff --git a/src/test/resources/rich-fixtures/gas/gas-117-append-change-add-scalar.yaml b/src/test/resources/rich-fixtures/gas/gas-117-append-change-add-scalar.yaml new file mode 100644 index 0000000..c1393a7 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-117-append-change-add-scalar.yaml @@ -0,0 +1,17 @@ +fixtureId: GAS-117 +title: AppendChange add charges like replace for scalar val +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $appendChange: + op: add + path: /status + val: x +expectation: + outcome: success + changeset: + - op: add + path: /status + val: x + gasUsed: 10 diff --git a/src/test/resources/rich-fixtures/gas/gas-118-append-changes-remove-entry.yaml b/src/test/resources/rich-fixtures/gas/gas-118-append-changes-remove-entry.yaml new file mode 100644 index 0000000..b929e2e --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-118-append-changes-remove-entry.yaml @@ -0,0 +1,15 @@ +fixtureId: GAS-118 +title: AppendChanges remove entry charges batch list and remove size zero +tags: [gas] +programSource: | + type: Blue/BEX Program + do: + - $appendChanges: + - op: remove + path: /status +expectation: + outcome: success + changeset: + - op: remove + path: /status + gasUsed: 9 diff --git a/src/test/resources/rich-fixtures/gas/gas-119-dynamic-pointer-expression-cost.yaml b/src/test/resources/rich-fixtures/gas/gas-119-dynamic-pointer-expression-cost.yaml new file mode 100644 index 0000000..8f4ecb0 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-119-dynamic-pointer-expression-cost.yaml @@ -0,0 +1,18 @@ +fixtureId: GAS-119 +title: Dynamic pointer operands charge their expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $pointerGet: + object: + a: + b: 1 + path: + $concat: + - a + - /b +expectation: + outcome: success + resultSimple: 1 + gasUsed: 10 diff --git a/src/test/resources/rich-fixtures/gas/gas-120-dynamic-text-expression-cost.yaml b/src/test/resources/rich-fixtures/gas/gas-120-dynamic-text-expression-cost.yaml new file mode 100644 index 0000000..b91398f --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-120-dynamic-text-expression-cost.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-120 +title: Dynamic text operands charge their expression +tags: [gas] +programSource: | + type: Blue/BEX Program + expr: + $get: + object: + a: 1 + key: + $concat: + - a +expectation: + outcome: success + resultSimple: 1 + gasUsed: 6 diff --git a/src/test/resources/rich-fixtures/gas/gas-121-custom-function-call.yaml b/src/test/resources/rich-fixtures/gas/gas-121-custom-function-call.yaml new file mode 100644 index 0000000..a99fe79 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-121-custom-function-call.yaml @@ -0,0 +1,12 @@ +fixtureId: GAS-121 +title: Custom functionCall schedule applies +tags: [gas] +gasSchedule: + functionCall: 10 +programSource: | + type: Blue/BEX Program + expr: 1 +expectation: + outcome: success + resultSimple: 1 + gasUsed: 11 diff --git a/src/test/resources/rich-fixtures/gas/gas-122-custom-statement-base.yaml b/src/test/resources/rich-fixtures/gas/gas-122-custom-statement-base.yaml new file mode 100644 index 0000000..7ac667e --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-122-custom-statement-base.yaml @@ -0,0 +1,13 @@ +fixtureId: GAS-122 +title: Custom statementBase schedule applies +tags: [gas] +gasSchedule: + statementBase: 10 +programSource: | + type: Blue/BEX Program + do: + - $return: 1 +expectation: + outcome: success + resultSimple: 1 + gasUsed: 13 diff --git a/src/test/resources/rich-fixtures/gas/gas-123-custom-append-event-base.yaml b/src/test/resources/rich-fixtures/gas/gas-123-custom-append-event-base.yaml new file mode 100644 index 0000000..955ad30 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-123-custom-append-event-base.yaml @@ -0,0 +1,14 @@ +fixtureId: GAS-123 +title: Custom appendEventBase schedule applies +tags: [gas] +gasSchedule: + appendEventBase: 10 +programSource: | + type: Blue/BEX Program + do: + - $appendEvent: x +expectation: + outcome: success + events: + - x + gasUsed: 15 diff --git a/src/test/resources/rich-fixtures/gas/gas-124-custom-append-change-base.yaml b/src/test/resources/rich-fixtures/gas/gas-124-custom-append-change-base.yaml new file mode 100644 index 0000000..b675d9b --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-124-custom-append-change-base.yaml @@ -0,0 +1,19 @@ +fixtureId: GAS-124 +title: Custom appendChangeBase schedule applies +tags: [gas] +gasSchedule: + appendChangeBase: 10 +programSource: | + type: Blue/BEX Program + do: + - $appendChange: + op: replace + path: /status + val: x +expectation: + outcome: success + changeset: + - op: replace + path: /status + val: x + gasUsed: 15 diff --git a/src/test/resources/rich-fixtures/gas/gas-125-custom-document-read.yaml b/src/test/resources/rich-fixtures/gas/gas-125-custom-document-read.yaml new file mode 100644 index 0000000..7e5b627 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-125-custom-document-read.yaml @@ -0,0 +1,16 @@ +fixtureId: GAS-125 +title: Custom documentRead schedule applies +tags: [gas] +gasSchedule: + documentRead: 10 +context: + rootDocumentSource: | + status: pending +programSource: | + type: Blue/BEX Program + expr: + $document: /status +expectation: + outcome: success + resultSimple: pending + gasUsed: 13 diff --git a/src/test/resources/rich-fixtures/gas/gas-126-custom-pointer-set-base.yaml b/src/test/resources/rich-fixtures/gas/gas-126-custom-pointer-set-base.yaml new file mode 100644 index 0000000..3122c51 --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-126-custom-pointer-set-base.yaml @@ -0,0 +1,19 @@ +fixtureId: GAS-126 +title: Custom pointerSetBase schedule applies +tags: [gas] +gasSchedule: + pointerSetBase: 10 +programSource: | + type: Blue/BEX Program + expr: + $pointerSet: + object: + a: {} + path: a/b + val: x +expectation: + outcome: success + resultSimple: + a: + b: x + gasUsed: 18 diff --git a/src/test/resources/rich-fixtures/gas/gas-127-custom-foreach-item.yaml b/src/test/resources/rich-fixtures/gas/gas-127-custom-foreach-item.yaml new file mode 100644 index 0000000..44b764d --- /dev/null +++ b/src/test/resources/rich-fixtures/gas/gas-127-custom-foreach-item.yaml @@ -0,0 +1,18 @@ +fixtureId: GAS-127 +title: Custom forEachItem schedule applies +tags: [gas] +gasSchedule: + forEachItem: 10 +programSource: | + type: Blue/BEX Program + do: + - $forEach: + in: + - a + - b + - c + item: item + do: [] +expectation: + outcome: success + gasUsed: 34 diff --git a/src/test/resources/rich-fixtures/manifest.yaml b/src/test/resources/rich-fixtures/manifest.yaml new file mode 100644 index 0000000..edfde92 --- /dev/null +++ b/src/test/resources/rich-fixtures/manifest.yaml @@ -0,0 +1,15 @@ +suiteName: blue-bex-rich-fixtures +version: 1 +fixtureRoot: src/test/resources/rich-fixtures +fixtureFileExtension: .yaml +directories: + current: required + parse-errors: required + gas: required +gasModel: docs/GAS.md +fixtureFormat: docs/FIXTURES.md +counts: + current: 51 + parseErrors: 3 + gas: 101 + totalFixtures: 155