Skip to content

Add the update operation and five shared cases for IdfObject.update - #43

Merged
samuelduchesne merged 2 commits into
mainfrom
015-update-operation
Oct 21, 2026
Merged

samuelduchesne merged 2 commits into
mainfrom
015-update-operation

Conversation

@samuelduchesne

Copy link
Copy Markdown
Contributor

Summary

Adds the corpus support for the multi-field update shared by both libraries (idfkit/idfkit#235, feature 015): one operation kind, one assertion, and five cases with truth = "convention". The naming-register entries for the concept are already released in governance-2026.30 (#42). This pull request is intended to be released as conformance-2026.17, the level each library raises its pin to when it lands its side.

Changes

  1. Operation kind update. An operation names an object and a group of fields (a list of groups is accepted), declares outcome = "applied" or "refused", and, when refused, declares refused: the group keys expected to be refused. Both runners call the library's update. A refused outcome passes only if the library raises and its message holds a line beginning - <entry>: for each declared key; any other error, or no error, fails the case. A library without update fails rather than refuses.

  2. Assertion post-operation-epjson. Reads the case's input (IDF or epJSON, as input_file declares), applies the operations, and compares the canonical epJSON with expected.after.epJSON, which tools/regenerate.sh produces with ConvertInputFormat from the committed after.idf.

  3. Corpus loading checks. A case declaring post-operation-epjson must have operations and an after.idf. When every operation is a refused update, after.idf must equal the input byte for byte; otherwise it must differ from it.

  4. Five cases, all energyplus_version = "26.1.0" and tagged tier1:

    Case Group Outcome
    update-all-valid four fields of one object applied
    update-refused-unknown-name a valid field, then an unknown name refused (refused = [the unknown name])
    update-refused-name a valid field and name (tag naming) refused
    update-wrapper-key the extensible wrapper key with a three-group list (tag extensible) applied
    update-refused-wrapper-contents a valid field and the wrapper key with a group holding an unknown field (tag extensible) refused
  5. known-divergence.toml. 16 entries for the update cases, all tracked by Add IDFObject.update() for bulk field updates idfkit#235: 10 for idfkit main, which has no IDFObject.update, and 6 for idfkit-js main, whose update writes entries before the refused one. Each library's pull request removes its own. The file now holds 28 entries over 7 issues and 14 cases, on a corpus of 74 cases and 226 assertions.

  6. Runners and tooling.

    • The Python runner's operation model uses typed, deeply frozen values (GroupValue, tuples, a picklable read-only mapping), matching the JavaScript model's deep freeze.
    • Field values are coerced with one schema lookup per operation; a field with no declared type is kept as text.
    • tools/regenerate.sh detects assertions declared in multi-line arrays, and --help prints its whole header.
    • README, run headers, workflow comments and counts are updated.

Results against the libraries

Library Commit Result
idfkit main 66d90b2b Exits 0; 17 failures, all recorded
idfkit-js main b44819b Exits 0; 11 failures, all recorded
idfkit, feature branch c90639fb All 15 update assertions pass
idfkit-js, feature branch (idfkit/idfkit-js#81, stacked on #80) same library source All 15 update assertions pass

Verification

  • python -m pytest tests -q: 322 passed.
  • python -m pytest runners/tests -q: 229 passed.
  • node --test on the four runner test files: 230 passed.
  • tools/validate_governance.py, tools/check_consumers.py and tools.energyplus.check_roster: pass.
  • manifest.json validates against manifest.schema.json, and fails as expected if a refused case's refused list is removed.

Known limitation, not changed here

Both runner models drop writer_options when writing the manifest back out, so a round trip does not reproduce manifest.json exactly. This predates this pull request.

An all-or-nothing multi-field update cannot be expressed as a sequence of
set-field operations, each of which applies on its own, so the corpus
gains a fifth operation kind. An update carries its group in fields,
which may be empty, and an optional outcome, applied (the default) or
refused. A field value may also be a list of groups, each mapping inner
field names to text, for an extensible wrapper key such as vertices.

Each runner maps the operation to obj.update(group) with values coerced
as for add, catches the library's error only when the case declares the
group refused, and reports a mismatch in either direction as a failure
that names the operation. A library with no update method on objects is
a failure too, never a refusal: the method is looked up on the class,
because attribute access on an unknown name can itself raise. Neither
runner reads the return value, which differs between the libraries.

The new assertion compares the canonical epJSON after the operations
with expected.after.epJSON. The epjson assertion compares the document
as read, and preserved-text excludes touched objects, so neither can
show what an operation produced, or that a refused group was partly
written. tools/regenerate.sh produces the expectation with
ConvertInputFormat from a committed after.idf for every case that
declares the assertion, whatever its truth; a convention case still has
no expected.epJSON.

Four convention cases, each generated from the oracle: update-all-valid,
update-refused-unknown-name, update-refused-name and update-wrapper-key.
Both libraries' current update implementations pass all four. idfkit's
main branch has no IDFObject.update yet, so known-divergence.toml lists
its eight failures against idfkit/idfkit#235, to be removed by the pull
request that adds the method.

Also corrects the Python runner's coercion of fields that accept a
sizing keyword, which the library describes as number|string and which
were handed over as text.

idfkit-unification feature 015, FR-015.
The review of the update operation found that a refused case accepted
any exception as the refusal, so a library whose update raised on every
call would pass all of them, and that both libraries applied plain
entries before rejecting the contents of the extensible list.

1. A fifth case, update-refused-wrapper-contents, gives vertices a list
   whose second group carries the unknown member bogus beside a valid
   Surface Type entry. The group is refused and after.idf is the input.
   expected.after.epJSON is generated by ConvertInputFormat 26.1.0.
2. An update declared refused now names, in a required refused list,
   the entries the library must report. Both runners fail the case
   unless the error carries a line "  - <entry>:" for each one. The
   schema, both models and the manifest carry the list, and each
   refused case declares it.
3. known-divergence.toml records the six typescript failures measured
   against idfkit-js b44819b and the two python failures of the new
   case, all against idfkit#235, under a section of their own. The
   header counts are recounted: 28 entries over 7 issues and 14 cases.
4. post-operation-epjson reads an epJSON input as epJSON in both
   runners rather than always as IDF.
5. regenerate.sh detects an assertion declared in a multi-line array,
   and its help prints the whole header rather than a fixed line range.
6. Loading the corpus now ties after.idf to the operations: a case
   declaring post-operation-epjson must declare operations and carry
   after.idf; a case whose operations are all refused updates must have
   an after.idf identical to its input, and any other case one that
   differs; an update without post-operation-epjson is rejected.
7. The Python runner reads a type's description once per operation and
   treats a field described without a type as text instead of failing
   on None.split. The JavaScript runner looks the type up once too.
8. Operation fields are typed and frozen all the way down in Python:
   a group is a GroupValue, a list of groups a tuple, and both mappings
   a picklable read-only FrozenMapping, matching the deep freeze of the
   JavaScript model.
9. Stale prose: the runner's "ninth assertion" comment, the operation
   kind count, the workflow's test count (117 of 230), the README case
   count and layout, regenerate.sh usage, and the case notes that
   implied preserved-text could detect a partly applied group. It
   cannot, since it excludes every touched object; post-operation-epjson
   is the assertion that shows nothing was applied.

Against idfkit 66d90b2b and idfkit-js b44819b both runners exit 0 with
17 and 11 outstanding exceptions. Against the feature heads, idfkit
c90639fb and idfkit-js ebf97c6, all five update cases pass in both
languages, so each library's pull request removes its own entries.

idfkit-unification feature 015, FR-015.
@samuelduchesne
samuelduchesne requested review from a team as code owners October 21, 2026 15:31
@samuelduchesne
samuelduchesne merged commit 2428417 into main Oct 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant