Skip to content

The "Running N tests" header re-sources every test file and re-runs every data provider (671ms over 240 files) #1347

Description

@Chemaclass

Summary

Printing "Running N tests" sources the whole suite a second time, and runs every data provider a second time, before the run starts.

Measured

The counting pass over this repo's 240 test files costs 671 ms on macOS arm64, bash 3.2.57: 487 ms of nested subshells and 184 ms of re-sourcing. That is before any data provider executes, and 44 provider annotations in the suite do execute inside it.

Current behavior

bashunit::helper::find_total_tests, src/helper/discovery.sh:225-275, called from src/console/header.sh:36-46:

file_count=$( (
  source "$file"
  all_fn_names=$(compgen -A function)
  filtered_functions=$(bashunit::helper::get_functions_to_run "test" "$filter" "$all_fn_names")
  ...
  done <<<"$(bashunit::helper::execute_function_if_exists "$_BASHUNIT_PROVIDER_FN_OUT")"
) )

A double subshell per file, which sources the file and runs its providers. The runner then sources everything again (src/runner/discovery.sh:96, :143, :146, :148) and runs the providers again (src/runner/exec.sh:184-188).

It is skipped only under --parallel --simple (src/console/header.sh:38-40).

Expected behavior

Count provider-free files statically. bashunit::helper::build_provider_map (src/helper/provider.sh:29-100) already walks every line of the file with awk, and is memoized by path (:44-46) and deliberately pre-warmed in the parent so its cache survives into the runner (see the comment at src/console/header.sh:42-44). Extending that same awk pass to also emit top-level test_* names adds no fork. The count is then the names surviving get_functions_to_run (src/helper/discovery.sh:132-159).

Files whose provider map is non-empty keep the sourcing fallback, since a provider's row count is only knowable by running it. In this suite that leaves roughly 85% of files counted without a fork.

The risk is a test function a static scan cannot see, defined through eval, a loop, or a nested source. Then the header would disagree with the run. The gate for this change should be a differential over every file in tests/, plus several --filter and --exclude-filter values, requiring the old and new counts to be identical.

Whatever lands must preserve exactly what the count includes today. It ignores --tag, because tags are applied later in the runner (src/runner/discovery.sh:153-164).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions