Skip to content

test(globals): cover parse_args, the thread/RAM scheduler, and the print_* formatters (#182) - #202

Merged
TimD1 merged 1 commit into
devfrom
182_td_parse-args-tests
Aug 5, 2026
Merged

test(globals): cover parse_args, the thread/RAM scheduler, and the print_* formatters (#182)#202
TimD1 merged 1 commit into
devfrom
182_td_parse-args-tests

Conversation

@TimD1-bot

Copy link
Copy Markdown
Collaborator

Note

Authorship: the content below was drafted by Claude Opus 5 (an AI coding agent) and
filed via gh under @TimD1-bot, a bot account operated by @TimD1. It reflects the
agent's analysis, not a statement authored by @TimD1.

Adds 96 cases to tests/unit/src/test_globals.cpp, taking Globals::parse_args from zero unit
coverage to the whole CLI contract. No source file is modified. The suite goes from 561 to
657 passing cases.

What's covered

parse_args — 83 cases

Group Cases
argc < 4 short-circuit no arguments, -h, --help, -v, --version, -ci, and an unrecognized argument — all exit(0)
Mandatory positionals all three accepted; each of the three open failures; optional-before-mandatory
Verbosity pre-pass each valid level, non-numeric, and both out-of-range ends
Per flag -b, -p, -f, -l, -sv, -q, -mq, -n, -x, -o, -e, -i, -s, -t, -ct, -r — a happy case each, plus missing-value, bad-numeric, and bound-violation wherever the parser enforces one
Main loop, non-exiting -h, --version, -ci — none of these exit, unlike their argc < 4 counterparts
Unrecognized / consumed --bogus errors; -v <n> is consumed without effect and the flag after it still parses
Cross-field, post-loop --max-qual below --min-qual; --largest-variant below --sv-threshold; --max-supercluster-size below --largest-variant + 2; --largest-variant 1

set_thread_ram_steps — 7 cases, driven directly rather than through an argv array, since
both the constructor and the end of parse_args call it: the 64-thread ladder, a single thread,
integer halving on a non-power-of-two (10 → {10, 5, 2, 1}), the RAM-per-thread division, that
repeated calls replace rather than append, thread_nsteps agreeing with both vector sizes, and
that an out-of-range thread count yields no steps rather than dividing by zero.

print_version, print_usage, print_citation — 6 cases on captured stdout, including
that the usage text interpolates the live settings rather than compiled-in literals, and that it
omits the four flags whose usage lines are commented out at src/globals.cpp:492-499.

Harness notes

Three pieces, all local to the test file:

  • ArgsFixture writes an openable query and truth VCF into a TempDir and points the
    reference at the committed tiny.fasta, so each case states only the flags it varies.
    parse_args opens argv[1] and argv[2] with bcf_open and argv[3] with fopen, so every case
    reaching the optional-argument loop needs three files that exist.
  • parse() copies each argument into its own mutable buffer, since parse_args takes
    char ** rather than const char **. It also closes the reference FASTA, which parse_args
    opens and never closes — 96 cases would otherwise leak one descriptor each.
  • parse_showing_stdout() dup2s stdout onto stderr inside the death-test child. A
    death-test matcher only sees the child's stderr, but print_usage, print_version and
    print_citation write to stdout; without this the argc < 4 group could assert only an exit
    code, which makes -v, -h and -ci indistinguishable. I confirmed the matchers actually
    bite by temporarily patching one to a sentinel string and watching the case fail.

Defects found — pinned, not fixed

Three cases document current behavior rather than enforcing it. Each is marked as such in-file
and names the issue that will change it, so the amendment is a grep away:

  • parse_args: a trailing comma in --filter adds an empty filter name #199 — a trailing comma in --filter appends an empty filter name, which then warns once
    per callset from src/variant.cpp:790-793. Pinned by ParseArgs.FilterTrailingComma.
  • parse_args: -b's catch block is unreachable for an unopenable BED and mislabels a malformed one #200-b's catch block is unreachable for an unopenable BED (the bedData
    constructor exits first), and for its one reachable trigger it reports an invalid filename
    when the contents were invalid. Both routes are pinned, by ParseArgs.BedBadFileErrors and
    ParseArgs.BedMalformedLineErrors.
  • Unfiled — the missing-value error for --verbosity at src/globals.cpp:92 is
    unreachable: the pre-pass loop is for (int i = 0; i+1 < argc; i++), so i never exceeds
    argc-2 and i == argc cannot hold. A trailing -v is never examined, the main loop then
    advances past the end, and the parse succeeds with verbosity unchanged instead of reporting the
    missing value. Pinned by ParseArgs.VerbosityMissingNotReached.

-i/--max-iterations and -s/--max-supercluster-size carry in-file comments naming #46 and
#47, since those issues remove and rename them respectively.

Verification

  • cd tests/unit/build && make — clean; the only warning is the pre-existing unused-parameter
    one at src/dist.cpp:1063.
  • ./test_vcfdist — 657 passed, 74 suites.
  • cd tests && pytest — 65 passed.

Closes #182

…int_* formatters (#182)

Adds 96 cases to tests/unit/src/test_globals.cpp, the last major gap in the unit
suite. No source file is modified.

parse_args (83 cases): the argc < 4 short-circuit group, distinguished by output
rather than exit code alone; the three mandatory positionals; the verbosity
pre-pass; every optional flag's happy, missing-value, bad-numeric, and
bound-violation cases; the main-loop -h/--version/-ci paths that do not exit;
unrecognized options; and the four post-loop cross-field checks.

set_thread_ram_steps (7 cases) is driven directly rather than through an argv
array, since the constructor and parse_args both call it.

print_version, print_usage and print_citation (6 cases) assert on captured
stdout, including that the usage text interpolates live settings and omits the
four flags whose usage lines are commented out.

Three harness pieces, all local to the test file:

- ArgsFixture writes an openable query and truth VCF and points the reference at
  the committed tiny.fasta, so a case states only the flags it varies.
- parse() copies the arguments into mutable buffers, since parse_args takes
  char**, and closes the reference FASTA that parse_args opens and never closes.
- parse_showing_stdout() redirects stdout onto stderr inside the death-test
  child, because a death-test matcher only sees stderr while print_usage,
  print_version and print_citation write to stdout. Without it the argc < 4 group
  could assert only an exit code, making -v, -h and -ci indistinguishable.

Two cases document current behavior rather than enforcing it, each marked in-file
with the issue that will change it: a trailing comma in --filter appends an empty
filter name (#199), and -b's catch block reports an invalid filename for what is
actually invalid file contents (#200). A third pins that the missing-value error
for --verbosity is unreachable, since the pre-pass loop stops at argc-2.

Closes #182
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.

2 participants