# frozen_string_literal: truerequire"shellwords"require"rake/file_list"require"active_support"require"active_support/core_ext/module/attribute_accessors"require"active_support/core_ext/range"require"rails/test_unit/test_parser"moduleRailsmoduleTestUnitclassInvalidTestError<StandardErrordefinitialize(path,suggestion)super(<<~MESSAGE.squish)
Could not load test file: #{path}.
#{suggestion} MESSAGEendendclassRunnerTEST_FOLDERS=[:models,:helpers,:channels,:controllers,:mailers,:integration,:jobs,:mailboxes]PATH_ARGUMENT_PATTERN=%r"^(?!/.+/$)[.\w]*[/\\]"mattr_reader:filters,default: []class<<selfdefattach_before_load_options(opts)opts.on("--warnings","-w","Run with Ruby warnings enabled"){}opts.on("-e","--environment ENV","Run tests in the ENV environment"){}enddefparse_options(argv)# Perform manual parsing and cleanup since option parser raises on unknown options.env_index=argv.index("--environment")||argv.index("-e")ifenv_indexargv.delete_at(env_index)environment=argv.delete_at(env_index).stripendENV["RAILS_ENV"]=environment||"test"w_index=argv.index("--warnings")||argv.index("-w")$VERBOSE=argv.delete_at(w_index)ifw_indexenddefrun_from_rake(test_command,argv=[])# Ensure the tests run during the Rake Task action, not when the process exitssuccess=system("rails",test_command,*argv,*Shellwords.split(ENV["TESTOPTS"]||""))success||exit(false)enddefrun(argv=[])load_tests(argv)require"active_support/testing/autorun"enddefload_tests(argv)patterns=extract_filters(argv)tests=list_tests(patterns)tests.to_a.eachdo|path|abs_path=File.expand_path(path)requireabs_pathrescueLoadError=>exceptionifexception.path==abs_pathall_tests=list_tests([default_test_glob])corrections=DidYouMean::SpellChecker.new(dictionary: all_tests).correct(path)ifcorrections.empty?raiseexceptionendraiseInvalidTestError.new(path,DidYouMean::Formatter.message_for(corrections))elseraiseendendenddefcompose_filter(runnable,filter)filter=normalize_declarative_test_filter(filter)iffilters.any?{|_,lines|lines.any?}CompositeFilter.new(runnable,filter,filters)elsefilterendendprivatedefextract_filters(argv)# Extract absolute and relative paths but skip -n /.*/ regexp filters.argv.filter_mapdo|path|nextunlesspath_argument?(path)path=path.tr("\\","/")casewhen/(:\d+(-\d+)?)+$/.match?(path)file,*lines=path.split(":")filters<<[file,lines]filewhenDir.exist?(path)"#{path}/**/*_test.rb"elsefilters<<[path,[]]pathendendenddefdefault_test_globENV["DEFAULT_TEST"]||"test/**/*_test.rb"enddefdefault_test_exclude_globENV["DEFAULT_TEST_EXCLUDE"]||"test/{system,dummy,fixtures}/**/*_test.rb"enddefregexp_filter?(arg)arg.start_with?("/")&&arg.end_with?("/")enddefpath_argument?(arg)PATH_ARGUMENT_PATTERN.match?(arg)enddeflist_tests(patterns)tests=Rake::FileList[patterns.any??patterns:default_test_glob]tests.exclude(default_test_exclude_glob)ifpatterns.empty?testsenddefnormalize_declarative_test_filter(filter)iffilter.is_a?(String)ifregexp_filter?(filter)# Minitest::Spec::DSL#it does not replace whitespace in method# names, so match unmodified method names as well.filter=filter.gsub(/\s+/,"_").delete_suffix("/")+"|"+filter.delete_prefix("/")elsif!filter.start_with?("test_")filter="test_#{filter.gsub(/\s+/,"_")}"endendfilterendendendclassCompositeFilter# :nodoc:attr_reader:named_filterdefinitialize(runnable,filter,patterns)@runnable=runnable@named_filter=derive_named_filter(filter)@filters=[@named_filter,*derive_line_filters(patterns)].compactend# minitest uses === to find matching filters.def===(method)@filters.any?{|filter|filter===method}endprivatedefderive_named_filter(filter)iffilter.respond_to?(:named_filter)filter.named_filterelsiffilter=~%r%/(.*)/%# Regexp filtering copied from minitest.Regexp.new$1elsiffilter.is_a?(String)filterendenddefderive_line_filters(patterns)patterns.flat_mapdo|file,lines|iflines.empty?Filter.new(@runnable,file,nil)iffileelselines.map{|line|Filter.new(@runnable,file,line)}endendendendclassFilter# :nodoc:definitialize(runnable,file,line_or_range)@runnable,@file=runnable,File.expand_path(file)ifline_or_rangefirst,last=line_or_range.split("-").map(&:to_i)last||=first@line_range=Range.new(first,last)endenddef===(method)returnunless@runnable.method_defined?(method)if@line_rangetest_file,test_range=definition_for(@runnable.instance_method(method))test_file==@file&&@line_range.overlaps?(test_range)else@runnable.instance_method(method).source_location.first==@fileendendprivatedefdefinition_for(method)TestParser.definition_for(method)endendendend