Skip to content

ts: close remaining Codecov gaps in tcl/, fix 3 dead branches - #672

Merged
xdelaruelle merged 13 commits into
envmodules:mainfrom
xdelaruelle:improve-tcl-coverage
Aug 14, 2026
Merged

ts: close remaining Codecov gaps in tcl/, fix 3 dead branches#672
xdelaruelle merged 13 commits into
envmodules:mainfrom
xdelaruelle:improve-tcl-coverage

Conversation

@xdelaruelle

Copy link
Copy Markdown
Collaborator

Codecov (https://app.codecov.io/gh/envmodules/modules/tree/main/tcl) flagged 26 uncovered lines across tcl/. Working through them:

  • 10 lines got new tests exercising genuine gaps (some via siteconfig.tcl-1 global-state injection, matching the pattern the suite already uses for config-option-shape and on-the-fly-procedure-redefinition coverage, for branches no real caller can currently reach).
  • 3 lines turned out to be genuine dead code, not test gaps — fixed:
    • modfind.tcl.in: an elseif in getModules' version-symbol collection that became unreachable the moment it was added in 2020 (a sibling commit on the same day had already made the if above it absorb the case unconditionally).
    • report.tcl.in: an existence guard in the module-listing proc that's been dead since 2019 (the variable it checks is unconditionally initialized earlier in the same proc).
    • modfind.tcl.in: an existence guard in unsetModuleDependency's unmet-dependency cleanup — the two structures it checks are always written and cleared together, so the guard can never be false.
  • 12 lines were flagged as bare closing braces of else-less if blocks. Those don't mean "block never entered" — Nagelfar/Codecov flag them when the condition has never evaluated false in any run. That reading changed the fix for several of them (forcing a skip path, not an entry path); details are in the individual commit messages.
  • 1 line remains open: modfind.tcl.in's dependency-cycle refresh (setModuleDependency) has a narrow theoretical path via require_via modulepath-swapping combined with a cycle that wasn't reachable through any load/unload/prereq sequence tried, and wasn't proven dead either. Left as-is.

No behavior changes except the 3 dead-code removals, each verified to be a no-op.

Two branches of tcl/init.tcl.in had no test reaching them: the
env-var-override path of the color config init proc (a plain
'module' invocation never overrides MODULES_COLOR, only the default
and command-line paths for that option were exercised) and the
id-output-groups-not-last-field path of __initStateUsergroups (the
fake 'id' used across the whole suite always prints 'groups=' as its
last field, so the branch handling a trailing field placed after it
by some systems, e.g. a security context, was never taken).

Add an unconditional MODULES_COLOR override case to 140-color.exp
(placed before its skip_if_quick_mode so it always runs), and append
a trailing field to testsuite/id fake output -- verified this does
not change the parsed group list, since the parser stops at the last
space before the next '=' either way.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
The startup logic in subcmd.tcl.in that reads the site-wide
.modulespath file resolves each colon-separated path entry as a glob
pattern, keeping the entry as a literal path when the glob matches
nothing. The install example .modulespath.in fixture only ever listed
entries that do resolve, so that branch, and the module use call
appending the collected paths, were never exercised.

Add a third, non-existent path entry to the fixture, and extend
120-autoinit.exp expectations for every scenario that sources it, so
the resulting MODULEPATH and virtual init collection now include it
unresolved.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
The module-warn modulefile command (mfcmd.tcl) raises "Missing value
for '--message' option" when --message is the last token in its
argument list, with nothing left to consume as the value. The
existing warn/.modulerc and warn/1 fixtures covered every other
module-warn error case (no message, no module, unknown option) but
not this one, since none of their scenarios ended the argument list
right after a bare --message.

Add a new scenario to both fixtures and a matching case to
700-module-warn.exp for each of the rc and modulefile call sites.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
The array-key lookup helpers in modspec.tcl that back extended_default
resolution (one variant used when implicit_default is on, one when it
is off) resolve a partial version query matching several available
versions by first checking whether the directory has an explicit
default version and, only if none of the matches is that explicit
default, falling back to the highest matching version. No existing
fixture had a module name with two sibling versions, one of them set
as default through a .version file, queried through a partial spec
that matches both, so the explicit-default branch of either variant
was never taken.

Add a new fixture module with versions 1.0.1 and 1.0.2 where 1.0.1 is
the explicit default, and a test loading it through the partial spec
1.0 with extended_default enabled, checked with implicit_default both
on and off since that selects which of the two lookup variants runs.
Update 140-hide-full-path.exp expected listing for the new fixture,
which that test enumerates as part of the whole modulepath.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
The module-spec argument parser in modspec.tcl, when a sub-command
allows a spec with no module name, flushes a variant-only element
still pending when a following element starts a name of its own by
substituting a wildcard name for it. No existing avail test passed a
variant-only argument ahead of a named one in the same command line,
so that flush branch was never taken.

Add such a case to 270-adv_version_spec.exp: a variant assignment
argument followed by a separate named argument, checked against the
plain named lookup so the wildcard element is confirmed to have no
effect on the result.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
extensions-sc, the scan-time stand-in for the extensions modulefile
command in modscan.tcl, records a provided-alias element alongside
provide only when info_extension is off, the default for that option.
The only existing test exercising a scanned extensions command first
turned info_extension on, so the default-off case was never scanned.

Add a case using the existing provide fixture ahead of that override,
checking both the provide and provided-alias search results at the
default setting.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
The version-handling loop in getModules (tcl/modfind.tcl.in) has
carried a dead branch since it was introduced. Two commits landed
back to back on 2020-10-25 while fixing an issue about global/user rc
definitions not showing up in results: the first made the loop add
every such rc-defined version to the found list and the module symbol
list unconditionally, dropping the previous gate on whether rc
definitions were asked to be included; the second, right after it,
added a further else-if branch meant to still record an rc definition
in the module symbol list when that inclusion flag is false,
apparently written without noticing the branch above it now already
fires whenever a version has an rc definition at all, flag or not.
Since the main condition already covers every case the else-if
checks, the else-if can never run, and it never has, from the day it
was added -- confirmed by checking out the state right before that
second commit in git history.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
The module-listing proc in tcl/report.tcl.in builds its JSON-mode
display list by aliasing display_list to clean_list via 'upvar 0',
then guarding with 'if {![info exists display_list]} { set
display_list {} }' in case clean_list was never populated. clean_list,
though, is unconditionally set to an empty list right at the top of
the same proc, before the per-module loop that may or may not append
to it -- so by the time the alias is created, the variable it points
to already exists, and the guard can never fire.

Checked out the state of modulecmd.tcl.in right before the commit
that introduced this upvar/guard pair (originally for a different,
now-refactored JSON-support commit) to confirm the same unconditional
pre-loop initialization was already present at that point too: the
guard has been dead from the day it was added, not from a later
refactor.

Drop the guard, keeping only the alias. This changes nothing at
runtime since the branch was unreachable, so no new test is needed.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
The array-key lookup helpers in modspec.tcl are backed by two
alternative proc bodies, swapped in under a single common name by a
definer proc depending on whether implicit_default is on. Its
rename-the-previous-variant-back branch only runs on a second call
with a different value than the first, and nothing in the normal
command dispatch path ever calls it more than once per process -- its
only caller runs exactly once per top-level command. A site
configuration script, though, runs in the same interpreter with full
access to every internal proc, and the suite already uses that access
to cover the identical rename-back branch of three sibling helpers
through the existing on-the-fly-procedure-redefinition case in
siteconfig.tcl-1 -- the array-key lookup definer was simply missing
from that coverage.

Add a matching case: call the definer with implicit_default 0, then
1, then 0 again, reporting a lookup through the active variant after
each call to confirm it still resolves correctly once swapped both
ways. Verified against a probe copy of the built interpreter that the
added middle and third calls do reach the rename-back branch
(confirmed by a temporary print statement there, since a local
coverage-instrumented run was out of scope here), and with
'TESTSUITE_ENABLE_SITECONFIG=1 script/mt 00/120' per the existing
convention for exercising this siteconfig fixture locally (a real
install is otherwise required, since the primary siteconfig option
has no environment-variable override).

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
The generic env-var/command-line override handling inside getConf
(tcl/init.tcl.in) has two branches no current config option is
shaped to reach: the single-element validvallist case of its
validation switch, meant for a Tcl 'string is <class>' check rather
than a fixed value list, since every option using an unkeyed
validvallist today either has zero elements or two or more; and the
generic intvallist conversion at the end of that same block, since
the only option that sets intvallist, color, also sets an initproc
that supersedes this whole path before reaching it.

Both branches are reachable by giving an option that different shape
at runtime, and the suite already does exactly that for other options
through g_config_defs overrides in siteconfig.tcl-1 (see the existing
bad-default-value cases for tag_abbrev and variant_shortcut, and the
init_envvars case right below the new ones). Add two more: superseding
run_quarantine with a single-element 'integer' validvallist and an
overridden environment variable to exercise the string-is class check,
and superseding csh_limit (which has no environment variable of its
own to fake) with a two-element validvallist and intvallist to
exercise the generic conversion on its plain default value.

Verified with 'TESTSUITE_ENABLE_SITECONFIG=1 script/mt 00/120' per the
existing convention for exercising this siteconfig fixture locally (a
real install is otherwise required, since the primary siteconfig
option has no environment-variable override), plus the same file
without that variable set and the Tcl lint check, all matching
pre-change results other than the two new passing cases.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
Corrected understanding of what Nagelfar/Codecov actually flag on a
bare closing brace of an else-less if: it means the condition never
evaluated false in any run, not that the block was never entered.
Two consequences:

The array-key lookup helper collecting matching tags checks "$tag ni
$tag_list" to skip a tag root already recorded earlier in the same
call, but that check can never observe a duplicate, since the proc
that populates the underlying tag structure always deduplicates by
tag name before appending -- so this skip branch is unreachable
through module-tag/module-hide/module-forbid. Force the shape
directly through the global tag structures from siteconfig.tcl-1
instead, the same way the suite already does for config option
shapes: two identical tag entries at different indices, so the second
lookup observes the tag already recorded by the first.

The array-key definer redefinition case added earlier only ever
called it with a value different from the one before, so it never
exercised the no-op branch where the currently active variant already
matches what is being asked for -- add a same-value call at the
start, matching the shape the existing on-the-fly-procedure-
redefinition case already uses for its own sibling helpers (same
value twice, then different twice).

Also add a case for the available-version-list lookup helper's
unregistered-argument path: its only real caller always passes an
argument already registered through the module-spec parser, so call
it directly with one that was never registered.

Verified with 'TESTSUITE_ENABLE_SITECONFIG=1 script/mt 00/120' (65
passes) plus the same file without that variable set and the Tcl lint
check, not the full suite.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
The unmet-dependency cleanup loop in unsetModuleDependency checked
"[info exists ::g_unmetDepHash($ummod)]" before touching that entry,
but the two structures it reads from, g_moduleUnmetDep(mod) and
g_unmetDepHash(ummod), are only ever written together, in the same
statement pair in setModuleDependency (one lappend per unmet
occurrence to each, always paired), and only ever cleared together, in
this exact loop. Nothing else in the file touches either structure, so
every module referenced in g_moduleUnmetDep(mod) is guaranteed to
still have a corresponding g_unmetDepHash entry when this loop runs --
the guard can never evaluate false.

Drop the guard, keeping its body unconditional. This changes nothing
at runtime since the branch was unreachable, so no new test is needed;
verified with the dependency-related test files (377-cyclic, 712/713-
require_via, 121-prereq-module, 515-depends-on, 516-prereq-all, 517-
always-load) plus the Tcl lint check, not the full suite.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
@xdelaruelle
xdelaruelle force-pushed the improve-tcl-coverage branch from fc83cdf to 3192c02 Compare August 14, 2026 16:11
The autosymbol block in getModules only defines an auto "latest"
symbol for a directory element when no entry already exists for that
name, or an existing one is in error -- in which case it still leaves
the existing entry alone and only attempts resolution again. No
existing fixture had a real "latest" symbolic version that was itself
also forbidden, so the branch handling an existing (error) entry, as
opposed to the plain not-found case, was never taken.

Add autosym10: a module whose real "2" version is also named "latest"
through an explicit module-version, with that exact symbolic name
targeted by both module-forbid and a hard module-hide. Querying or
loading it by the real name still resolves normally, but the "latest"
name itself stays access-denied rather than falling back to an
autosymbol pointing at the same module, since found_list already
carries an (error) entry for it by the time the autosymbol phase runs.

Verified with 'script/mt 70/274' (229 passes), plus the tests that
enumerate this fixture's modulepath wholesale (avail/spider long
listings, extra-spec/json/output tests) to confirm the new fixture
does not change their expected output, not the full suite.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
@xdelaruelle
xdelaruelle force-pushed the improve-tcl-coverage branch from 3192c02 to 9683519 Compare August 14, 2026 18:13
@xdelaruelle
xdelaruelle merged commit b920ea0 into envmodules:main Aug 14, 2026
20 checks passed
@xdelaruelle
xdelaruelle deleted the improve-tcl-coverage branch August 14, 2026 19:27
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