Skip to content

test: cover the remaining long-tail branches - #866

Merged
blaipr merged 1 commit into
mainfrom
test/cover-the-remaining-long-tail-branches
Aug 23, 2026
Merged

test: cover the remaining long-tail branches#866
blaipr merged 1 commit into
mainfrom
test/cover-the-remaining-long-tail-branches

Conversation

@blaipr

@blaipr blaipr commented Aug 23, 2026

Copy link
Copy Markdown
Member

Error paths and unused accessors across five classes, none of which had a test for the
branch in question.

DataGridBase: the three independent catch sites for a missing template — header, pager
and row — each reached separately, so a fix that only guarded one would still fail two
of these.

FileHandler: the lock and unlock failure paths, and delete's failed unlink. The lock ones
need a custom stream wrapper: real flock() has no permission-based failure and the code
calls it without LOCK_NB, so a genuine conflict would block rather than return false.
The unlink one swaps a directory in for the file after opening it, which fails EISDIR
regardless of who is running the suite.

Serde: both JSON error paths — NAN defeats json_encode with JSON_THROW_ON_ERROR, and
truncated input defeats json_decode.

JsonMessage and AccountHistoryView: accessors that were never read.

Three branches are deliberately left uncovered, each established by measurement:

  • XmlExport::createDocument() only calls new DOMDocument('1.0', 'UTF-8') and
    createElement('Root') on literals, in a private method called once from the constructor
    of a final class. No input or seam can make it throw.
  • FileHandler's fgetcsv() === false guard: SplFileObject::fgetcsv() returns [null] on
    exhaustion and false only at true EOF, which the loop condition has already excluded.
  • Functions.php's if (!defined('MODULES_PATH')) inside initModule(): the constant is
    defined unconditionally as the first executable line of the same file.

Also found while writing the DataGrid tests, and left for its own change:
DataGridBase::getDataHeaderTemplate() is declared : string while its backing property is
?string and is only assigned inside the try block that the missing-template case skips.
Calling it in that state is a TypeError. Its three siblings are all declared ?string.
The test here asserts the setter's behaviour and does not call that getter, so it does not
depend on which way that is resolved.

Error paths and unused accessors across five classes, none of which had a test for the
branch in question.

DataGridBase: the three independent catch sites for a missing template — header, pager
and row — each reached separately, so a fix that only guarded one would still fail two
of these.

FileHandler: the lock and unlock failure paths, and delete's failed unlink. The lock ones
need a custom stream wrapper: real flock() has no permission-based failure and the code
calls it without LOCK_NB, so a genuine conflict would block rather than return false.
The unlink one swaps a directory in for the file after opening it, which fails EISDIR
regardless of who is running the suite.

Serde: both JSON error paths — NAN defeats json_encode with JSON_THROW_ON_ERROR, and
truncated input defeats json_decode.

JsonMessage and AccountHistoryView: accessors that were never read.

Three branches are deliberately left uncovered, each established by measurement:

- XmlExport::createDocument() only calls `new DOMDocument('1.0', 'UTF-8')` and
  createElement('Root') on literals, in a private method called once from the constructor
  of a final class. No input or seam can make it throw.
- FileHandler's `fgetcsv() === false` guard: SplFileObject::fgetcsv() returns `[null]` on
  exhaustion and false only at true EOF, which the loop condition has already excluded.
- Functions.php's `if (!defined('MODULES_PATH'))` inside initModule(): the constant is
  defined unconditionally as the first executable line of the same file.

Also found while writing the DataGrid tests, and left for its own change:
DataGridBase::getDataHeaderTemplate() is declared `: string` while its backing property is
`?string` and is only assigned inside the try block that the missing-template case skips.
Calling it in that state is a TypeError. Its three siblings are all declared `?string`.
The test here asserts the setter's behaviour and does not call that getter, so it does not
depend on which way that is resolved.
@blaipr
blaipr merged commit e0beecc into main Aug 23, 2026
8 checks passed
@blaipr
blaipr deleted the test/cover-the-remaining-long-tail-branches branch August 23, 2026 23:05
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