Skip to content

ps.testing

ps.testing.assert_issue(subject: Any, *, code: str, address: str | None = None, fix_contains: str | None = None) -> Issue

Assert that a subject’s checks hold an issue of code, and return that issue.

ps.testing.assert_issue(lambda: ps.tracer.Tracer(name="tracer1", phase="water"), code="tracer.name.invalid") passes: the construction is refused with that code.

subject : object Where the rows are read: a ps.Model (its m.check() rows), a ps.ModelError or ps.CompileError (its issues), any object with an issues list, one ps.Issue, a list of issues, or a callable: called with no argument, the rows of the ModelError or CompileError it raises, else the rows of what it returns. Any other exception the callable raises propagates, a field refusal (field.unknown, field.invalid) among them. code : str The issue code to find ("well.control.missing"). address : str, optional The address the issue must name as well ("schedule/2021-01-01/P1/control"). fix_contains : str, optional Text the issue’s fix must hold, matched without regard to case.

Issue The first matching issue.

AssertionError No issue matches; the message lists the code and address of every issue found.

ps.testing.first_divergence(expected: Model, actual: Model) -> str | None

Return where two models first differ, field by field, or None when they are equal.

ps.testing.first_divergence(m, loaded) is None when loaded, the model the deck of m loads back as, equals it field for field. Where a value came from is not compared (a provenance, a table’s source, an array’s name), and a summary compares by the vectors it requests, not how they were grouped. A stated bare EQLDIMS equals no statement where the compile would derive the bare card itself (EQUIL over one equilibration region): elsewhere it counts.

expected : Model The model as stated (the one compiled). actual : Model The model to compare with it (the one its deck loads back as).

str or None "<dotted path>: expected <value>, imported <value>" for the first field that differs, walking the models’ fields in order ("model.rock.porosity: expected 0.22, imported 0.2"); None when none does.

ps.testing.records(path_or_text: str | Path, *, includes: Literal['refuse', 'expand'] = 'refuse') -> tuple[KeywordRecord, ...]

Read a deck file, or deck text, as the keyword occurrences it states, in deck order.

A str holding a line break is deck text; any other str or path names a file. Both are read as the deck importer reads them, and a deck it refuses is refused here with the same DeckImportError.

Every occurrence is kept, duplicates included, so a keyword the deck states twice is two records in the order the deck states them; nothing is keyed by name. Content the deck skips (SKIP/ENDSKIP, after END) is not read; ps.load reads what a deck means.

path_or_text : str or Path The deck’s file, or its text. includes : {“refuse”, “expand”} "refuse" (the default) refuses a deck that includes another file, so what comes back is one self-contained file; "expand" reads the included files in place, each record’s at naming the file it came from. Text has no folder to include from, so an INCLUDE in text is refused either way, and each record’s at names the text <rendered deck>.

tuple of KeywordRecord One per keyword occurrence: its name, its section, its records (the data records in deck order, each a tuple of int, float, str or None: a n*value repeat expanded, a defaulted slot None), at (the file and line) and lines (the body of a keyword kept verbatim, TITLE, UDQ or ACTIONX, which then has no records; empty for every other keyword).

DeckImportError A deck the importer refuses, with its code: deck.include.present for an INCLUDE under includes="refuse" or in text, naming the INCLUDE line. ValueError includes is neither "refuse" nor "expand".

PetroScript is licensed under the Apache License 2.0. © 2026 Blauweiss EDV LLC. PetroScript by Blauweiss AI.For AI agents: llms.txt