Skip to content

Add khepri predicate conformance tests for rules-engine-internal#3574

Draft
ajpallares wants to merge 43 commits into
mainfrom
pallares/android-khepri-predicate-conformance
Draft

Add khepri predicate conformance tests for rules-engine-internal#3574
ajpallares wants to merge 43 commits into
mainfrom
pallares/android-khepri-predicate-conformance

Conversation

@ajpallares

@ajpallares ajpallares commented Jun 9, 2026

Copy link
Copy Markdown
Member

Resolves SDK-4334

Motivation

Run khepri's predicate_conformance_v1.json against rules-engine-internal through the existing fixture runner, so the in-repo and khepri-downloaded fixtures share one harness. Android port of RevenueCat/purchases-ios#6822.

Description

An on-demand run-rules-engine-conformance-tests CI job downloads the fixtures (gitignored) and runs only the conformance suite; it is excluded from the default test task unless -PrunRulesEngineConformanceTests is set.

ajpallares and others added 30 commits June 5, 2026 14:11
…e fixtures

Adds per-operator JSON predicate fixtures (exact copies of the iOS #6885
corpus) under src/test/resources/predicate-fixtures, run by a shared
parameterized runner. Deletes the migrated arithmetic/comparison/equality/
logic suites and trims the accessor/evaluator/string-array suites to the
cases not expressible as predicate->boolean. Test-only change.

Co-authored-by: Cursor <cursoragent@cursor.com>
Expresses the "empty-string key resolves to the whole scope and is not
missing" case as a `{"!!":{"missing":[""]}}` -> false fixture (the string
coercion the other missing fixtures use can't distinguish [] from [""]).
Removes the now-covered Kotlin test and bumps the fixture count to 237.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds fixtures for the empty-segment dot-path splits, var default-vs-null-leaf
behavior, and the non-numeric missing_some threshold (7 new cases), and drops
the now-redundant Kotlin tests. AccessorOperatorsTest keeps only the cases
that cannot be a predicate->boolean fixture (top-level array scope or a
whole-object return). Fixture count is now 244.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Mirror the iOS rules-engine implementation: Kotlin some/all operators
matching json-logic-js semantics, shared JSON predicate fixtures, and
pinned fixture count bumped to 271.

Co-authored-by: Cursor <cursoragent@cursor.com>
place operator docs on each op instead of duplicating at the type level; drop "not vacuous truth" wording

Co-authored-by: Cursor <cursoragent@cursor.com>
Implement variadic min/max with JS Math.min/Math.max semantics: operands
coerce via Number() (toNumberOrNull), empty input yields ±∞, and NaN
operands poison the result. Wire into the operator dispatcher and add
byte-identical min.json/max.json fixtures from purchases-ios, bumping
the pinned fixture count to 305.

Co-authored-by: Cursor <cursoragent@cursor.com>
Completes the json-logic-js iteration family on top of some/all.
none/map/filter rebind vars to the current item; reduce rebinds to
{current, accumulator}. Non-array sources: none -> true, map/filter
-> [], reduce -> seed unchanged. Coverage is the shared declarative
JSON fixtures (none/map/filter/reduce.json, byte-identical to iOS),
bumping the pinned fixture count to 345. Android counterpart of
RevenueCat/purchases-ios#6834.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Label each operator grouping in Operators.dispatch per PR review feedback.

Co-authored-by: Cursor <cursoragent@cursor.com>
Move the string/array group after comparison so the Android dispatcher
mirrors the iOS Operators.dispatch grouping order.

Co-authored-by: Cursor <cursoragent@cursor.com>
Rename the all-match fixture and add cases where only the first or only
the middle item matches, addressing PR review feedback that the existing
some coverage was order dependent. Fixtures kept byte-identical with iOS.

Co-authored-by: Cursor <cursoragent@cursor.com>
khepri guards all with notEmpty, so it returns false for an empty array
like json-logic-js; drop the inaccurate claim that khepri returns true.
Fixture kept byte-identical with iOS.

Co-authored-by: Cursor <cursoragent@cursor.com>
…teration-operators

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	rules-engine-internal/src/test/kotlin/com/revenuecat/purchases/rules/PredicateFixtureLoaderTest.kt
…erators' into pallares/json-logic-min-max-operators

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	rules-engine-internal/src/main/kotlin/com/revenuecat/purchases/rules/operators/Operators.kt
#	rules-engine-internal/src/test/kotlin/com/revenuecat/purchases/rules/PredicateFixtureLoaderTest.kt
…in-max-operators

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	rules-engine-internal/src/main/kotlin/com/revenuecat/purchases/rules/operators/Operators.kt
#	rules-engine-internal/src/test/kotlin/com/revenuecat/purchases/rules/PredicateFixtureLoaderTest.kt
Add a dedicated "Min and max" grouping comment so the min/max cases are not
lumped under arithmetic, matching the iOS dispatcher.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ators' into pallares/json-logic-iteration-mapping-operators

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	rules-engine-internal/src/test/kotlin/com/revenuecat/purchases/rules/PredicateFixtureLoaderTest.kt
Self-documenting replacement for the {"/": [-10,0]} workaround; constants
seeded only by the conformance harness (reservedConstants), engine untouched;
NaN still uses x != x; fixtures kept byte-identical with iOS.

Co-authored-by: Cursor <cursoragent@cursor.com>
substr.json and missing_some.json now use the seeded "+Infinity" and
"-Infinity" variables instead of division-by-zero operands. NaN cases
and evaluator.json are unchanged. Fixtures are byte-identical to iOS.

Co-authored-by: Cursor <cursoragent@cursor.com>
Mirror iOS predicate fixture updates: remove the redundant
"comes from the test-only" clause from 7 fixture descriptions
across min, max, substr, and missing_some. Fixtures kept
byte-identical with iOS.

Co-authored-by: Cursor <cursoragent@cursor.com>
…nfinity-fixture-vars

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	rules-engine-internal/src/test/resources/predicate-fixtures/max.json
#	rules-engine-internal/src/test/resources/predicate-fixtures/min.json
…ture-vars' into pallares/json-logic-iteration-mapping-operators
Co-authored-by: Cursor <cursoragent@cursor.com>
ajpallares and others added 9 commits June 8, 2026 18:22
…ument)

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Cover logging ±Infinity, NaN, empty object, and empty array. Empty
array and empty args were indistinguishable under substring matching
(they log "" and "null"), so `expectedLogs` now asserts the exact,
ordered list of emitted messages. `expectedWarnings` is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
The tag was always the single `[RulesEngine]` constant and never varied
at any call site, so it added interface surface without flexibility. For
the `log` channel a prefix also defeats the verbatim passthrough. Hosts
that bridge the logger prepend their own identifier when they need one.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Wrap NaN- and infinity-producing subexpressions in `log` and assert the
exact emitted string via `expectedLogs`, so these fixtures verify the
intermediate value, not just the final truthiness. Covers arithmetic,
min, max, reduce, missing_some, and evaluator edge cases.

Co-authored-by: Cursor <cursoragent@cursor.com>
Place a `log` in a branch that should be skipped and assert it never
fires (`expectedLogs: []`), with positive controls that log when the
branch is evaluated — verifying and/or/if laziness.

Co-authored-by: Cursor <cursoragent@cursor.com>
Downloads khepri's predicate_conformance_v1.json and evaluates each fixture
through the shared PredicateConformanceRunner, reusing the same in-repo fixture
model and parameterized JUnit harness as the predicate fixtures. Adds the
on-demand run-rules-engine-conformance-tests CircleCI job (triggered via the
run_rules_engine_conformance_tests pipeline action) and a download script that
fetches the fixtures from the private RevenueCat/khepri repo.

Mirrors iOS PR #6822 (RevenueCat/purchases-ios). The conformance suite is kept
out of the standard unit-test run because the fixture file is downloaded on
demand and git-ignored (absent by default): the suite skips via a JUnit
assumption when absent and is excluded from the default test task unless
-PrunRulesEngineConformanceTests is set. No production changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.30%. Comparing base (a291940) to head (7cb2d2b).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3574   +/-   ##
=======================================
  Coverage   80.30%   80.30%           
=======================================
  Files         377      377           
  Lines       15387    15387           
  Branches     2135     2135           
=======================================
  Hits        12356    12356           
  Misses       2174     2174           
  Partials      857      857           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Base automatically changed from pallares/android-rules-fixture-log-assertions to main June 9, 2026 15:35
ajpallares and others added 4 commits June 9, 2026 17:38
…ri-predicate-conformance

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	rules-engine-internal/src/test/resources/predicate-fixtures/log.json
Mirror iOS' Xcode scheme pre-action: when `-PrunRulesEngineConformanceTests`
is set, a Gradle task runs the download script before the suite and points the
test JVM at the fetched file, so the conformance tests can be run locally with a
single command. Default unit-test runs are unchanged (suite still excluded).

Co-authored-by: Cursor <cursoragent@cursor.com>
Read and decode the downloaded conformance envelope once via a lazy, shared
by the existence check and both accessors, mirroring the in-repo `allCases`.
Avoids the few redundant reads/decodes per run. Test-only change.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant