Skip to content

Commit 636bc19

Browse files
committed
Don't spawn a task per file to include, use a pool of tasks instead
1 parent c449b77 commit 636bc19

File tree

1 file changed

+88
-44
lines changed

1 file changed

+88
-44
lines changed

src/ReTestItems.jl

Lines changed: 88 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,84 @@ function _is_subproject(dir, current_projectfile)
802802
return true
803803
end
804804

805-
# for each directory, kick off a recursive test-finding task
805+
const _hidden_re = r"\.\w"
806+
807+
# Traverses the directory tree starting at `project_root` and populates `dir_nodes` with
808+
# `DirNode`s and `FileNode`s for each directory and test file found. Filters out non-eligible
809+
# paths.
810+
function walkdir_task(walkdir_channel::Channel{Tuple{String,FileNode}}, project_root::String, root_node, dir_nodes, subproject_root::Union{String,Nothing}, ti_filter, paths, projectfile, report, verbose_results)
811+
try
812+
# Since test items don't store paths to their test setups, we need to traverse the
813+
# whole project, not just the requested paths.
814+
stack = [project_root]
815+
while !isempty(stack)
816+
root = pop!(stack)
817+
rel_root = nestedrelpath(root, project_root)
818+
dir_node = DirNode(rel_root; report, verbose=verbose_results)
819+
dir_nodes[rel_root] = dir_node
820+
push!(get(dir_nodes, dirname(rel_root), root_node), dir_node)
821+
for file in readdir(root)
822+
startswith(file, _hidden_re) && continue # skip hidden files/directories
823+
full_path = joinpath(root, file)
824+
if isdir(full_path)
825+
if subproject_root !== nothing && startswith(full_path, subproject_root)
826+
@debugv 1 "Skipping files in `$root` in subproject `$subproject_root`"
827+
continue
828+
elseif _is_subproject(root, projectfile)
829+
subproject_root = root
830+
continue
831+
end
832+
push!(stack, full_path)
833+
else
834+
# We filter here, rather than the testitem level, to make sure we don't
835+
# `include` a file that isn't supposed to be a test-file at all, e.g. its
836+
# not on a path the user requested but it happens to have a test-file suffix.
837+
# We always include testsetup-files so users don't need to request them,
838+
# even if they're not in a requested path, e.g. they are a level up in the
839+
# directory tree. The testsetup-file suffix is hopefully specific enough
840+
# to ReTestItems that this doesn't lead to `include`ing unexpected files.
841+
if !(is_testsetup_file(full_path) || (is_test_file(full_path) && is_requested(full_path, paths)))
842+
continue
843+
end
844+
rel_full_path = nestedrelpath(full_path, project_root)
845+
file_node = FileNode(rel_full_path, ti_filter; report, verbose=verbose_results)
846+
push!(dir_node, file_node)
847+
put!(walkdir_channel, (full_path, file_node))
848+
end
849+
end
850+
end
851+
close(walkdir_channel)
852+
catch err
853+
close(walkdir_channel, err)
854+
rethrow(err)
855+
end
856+
return nothing
857+
end
858+
859+
# Parses and evals files found by the `walkdir_task`. During macro expansion of `@testitem`
860+
# test items are push!d onto the FileNode stored in task local storage as `:__RE_TEST_ITEMS__`.
861+
function include_task(walkdir_channel, setup_channel, project_root, ti_filter)
862+
try
863+
testitem_names = Set{String}() # to enforce that names in the same file are unique
864+
task_local_storage(:__RE_TEST_RUNNING__, true) do
865+
task_local_storage(:__RE_TEST_PROJECT__, project_root) do
866+
task_local_storage(:__RE_TEST_SETUPS__, setup_channel) do
867+
for (file_path, file_node) in walkdir_channel
868+
@debugv 1 "Including test items from file `$(file_path)`"
869+
task_local_storage(:__RE_TEST_ITEMS__, (file_node, empty!(testitem_names))) do
870+
Base.include(ti_filter, Main, file_path)
871+
end
872+
end
873+
end
874+
end
875+
end
876+
catch err
877+
close(walkdir_channel, err)
878+
rethrow(err)
879+
end
880+
end
881+
882+
# Find test items using a pool of tasks to include files in parallel.
806883
# Returns (testitems::TestItems, setups::Dict{Symbol,TestSetup})
807884
# Assumes `isdir(project_root)`, which is guaranteed by `_runtests`.
808885
function include_testfiles!(project_name, projectfile, paths, ti_filter::TestItemFilter, verbose_results::Bool, report::Bool)
@@ -823,51 +900,18 @@ function include_testfiles!(project_name, projectfile, paths, ti_filter::TestIte
823900
end
824901
return setups
825902
end
826-
hidden_re = r"\.\w"
827-
@sync for (root, d, files) in Base.walkdir(project_root)
828-
if subproject_root !== nothing && startswith(root, subproject_root)
829-
@debugv 1 "Skipping files in `$root` in subproject `$subproject_root`"
830-
continue
831-
elseif _is_subproject(root, projectfile)
832-
subproject_root = root
833-
continue
834-
end
835-
rpath = nestedrelpath(root, project_root)
836-
startswith(rpath, hidden_re) && continue # skip hidden directories
837-
dir_node = DirNode(rpath; report, verbose=verbose_results)
838-
dir_nodes[rpath] = dir_node
839-
push!(get(dir_nodes, dirname(rpath), root_node), dir_node)
840-
for file in files
841-
startswith(file, hidden_re) && continue # skip hidden files
842-
filepath = joinpath(root, file)
843-
# We filter here, rather than the testitem level, to make sure we don't
844-
# `include` a file that isn't supposed to be a test-file at all, e.g. its
845-
# not on a path the user requested but it happens to have a test-file suffix.
846-
# We always include testsetup-files so users don't need to request them,
847-
# even if they're not in a requested path, e.g. they are a level up in the
848-
# directory tree. The testsetup-file suffix is hopefully specific enough
849-
# to ReTestItems that this doesn't lead to `include`ing unexpected files.
850-
if !(is_testsetup_file(filepath) || (is_test_file(filepath) && is_requested(filepath, paths)))
851-
continue
852-
end
853-
fpath = nestedrelpath(filepath, project_root)
854-
file_node = FileNode(fpath, ti_filter; report, verbose=verbose_results)
855-
testitem_names = Set{String}() # to enforce that names in the same file are unique
856-
push!(dir_node, file_node)
857-
@debugv 1 "Including test items from file `$filepath`"
858-
@spawn begin
859-
task_local_storage(:__RE_TEST_RUNNING__, true) do
860-
task_local_storage(:__RE_TEST_ITEMS__, ($file_node, $testitem_names)) do
861-
task_local_storage(:__RE_TEST_PROJECT__, $(project_root)) do
862-
task_local_storage(:__RE_TEST_SETUPS__, $setup_channel) do
863-
Base.include($ti_filter, Main, $filepath)
864-
end
865-
end
866-
end
867-
end
868-
end
903+
904+
walkdir_channel = Channel{Tuple{String, FileNode}}(1024)
905+
@sync begin
906+
@spawn walkdir_task(
907+
$walkdir_channel, $project_root, $root_node, $dir_nodes, $subproject_root,
908+
$ti_filter, $paths, $projectfile, $report, $verbose_results
909+
)
910+
for _ in 1:clamp(2*nthreads(), 1, 16)
911+
@spawn include_task($walkdir_channel, $setup_channel, $project_root, $ti_filter)
869912
end
870913
end
914+
871915
@debugv 2 "Finished including files"
872916
# finished including all test files, so finalize our graph
873917
# prune empty directories/files

0 commit comments

Comments
 (0)