@@ -802,7 +802,84 @@ function _is_subproject(dir, current_projectfile)
802802 return true
803803end
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`.
808885function 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