From 4e12c045d1d5f61b98685b6cc083c31c3473a41f Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 3 Jan 2022 21:44:50 +0900 Subject: [PATCH 01/12] Fix typo in CHANGELOG `DidYouMean::SPELL_CHECKERS` (#169) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e248ef..d6b6307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ _released at 2020-12-22 13:22:35 UTC_ #### Deprecations - Deprecate custom formatters to reduce complexity for Ractor support. -- Deprecate access to the `DidYouMean::SPELL_CHECEKRS` constant for Ractor support. +- Deprecate access to the `DidYouMean::SPELL_CHECKERS` constant for Ractor support. #### Features From 531760f323df8d43a7017af5a3052f20e8a03fda Mon Sep 17 00:00:00 2001 From: Jean byroot Boussier Date: Fri, 21 Jan 2022 11:04:56 +0100 Subject: [PATCH 02/12] Fix `frozen_string_literal is ignored after any tokens` warning. (#172) ``` did_you_mean/formatters/verbose_formatter.rb:5: warning: `frozen_string_literal' is ignored after any tokens ``` --- lib/did_you_mean/formatters/verbose_formatter.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/did_you_mean/formatters/verbose_formatter.rb b/lib/did_you_mean/formatters/verbose_formatter.rb index 8ee98fa..f662368 100644 --- a/lib/did_you_mean/formatters/verbose_formatter.rb +++ b/lib/did_you_mean/formatters/verbose_formatter.rb @@ -1,8 +1,9 @@ +# frozen-string-literal: true + warn "`require 'did_you_mean/formatters/verbose_formatter'` is deprecated and falls back to the default formatter. " require_relative '../formatter' -# frozen-string-literal: true module DidYouMean # For compatibility: VerboseFormatter = Formatter From 20f26629f4af0fc5513635465a4f5947f18d9292 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 25 Mar 2022 16:19:10 +0900 Subject: [PATCH 03/12] Added dependabot --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b18fd29 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' From 4a8de913c2a6cd6c9bed7fa12872ce7fb3997ad8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Mar 2022 07:41:48 +0000 Subject: [PATCH 04/12] Bump actions/checkout from 1 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 1 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v1...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ruby.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 4b1033b..505263c 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -15,7 +15,7 @@ jobs: matrix: ruby: [ '2.5', '2.6', '2.7', '3.0', 'ruby-head', 'jruby-9.2', 'jruby-head' ] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} @@ -27,7 +27,7 @@ jobs: benchmark: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.7.1 From c3fc412f6ff51780dfece556eb80402943233590 Mon Sep 17 00:00:00 2001 From: Imir Kiyamov Date: Fri, 20 May 2022 16:32:11 +0300 Subject: [PATCH 05/12] Fixed correction duplicates in VariableNameChecker --- .../name_error_checkers/variable_name_checker.rb | 2 +- test/spell_checking/test_variable_name_check.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb b/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb index 36d0034..9a6e04f 100644 --- a/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb +++ b/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb @@ -79,7 +79,7 @@ def initialize(exception) def corrections @corrections ||= SpellChecker .new(dictionary: (RB_RESERVED_WORDS + lvar_names + method_names + ivar_names + cvar_names)) - .correct(name) - NAMES_TO_EXCLUDE[@name] + .correct(name).uniq - NAMES_TO_EXCLUDE[@name] end end end diff --git a/test/spell_checking/test_variable_name_check.rb b/test/spell_checking/test_variable_name_check.rb index 193e2b7..34d0aca 100644 --- a/test/spell_checking/test_variable_name_check.rb +++ b/test/spell_checking/test_variable_name_check.rb @@ -137,4 +137,16 @@ def test_exclude_typical_incorrect_suggestions error = assert_raise(NameError){ foo } assert_empty error.corrections end + + def test_exclude_duplicates_with_same_name + error = assert_raise(NameError) do + eval(<<~RUBY, binding, __FILE__, __LINE__) + bar = 1 + def bar;end + zar + RUBY + end + + assert_correction [:bar], error.corrections + end end From 62799d728c351ff10ecf84f04611d95a1cbf104a Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Mon, 23 May 2022 18:18:25 +0900 Subject: [PATCH 06/12] Define Exception#detailed_message instead of clobbering #message This changeset lets DidYouMean::Correctable to use #detailed_message, which is Ruby 3.2's more dedicated way to show additional error information like did_you_mean and error_highlight. There are some problems with the approach of overriding #message. One is that the approach breaks a test that checks a return value of #message. I actually faced this kind of issues in real-world applications when I was implementing error_highlight with the same approach as did_you_mean. See https://github.com/minitest/minitest/pull/880 for example. For this reason, error_highlight enhances only NameError. The support of other error types such as TypeError and ArgumentError is currently disabled because it breaks more tests: https://github.com/ruby/error_highlight/blob/f88b6fab2ff34559a0f08d019d574dbb52426a20/lib/error_highlight/core_ext.rb#L49-L52 I believe #detailed_message will solve the issue. I want did_you_mean and error_highlight to use the new API instead of clobbering #message method. See https://bugs.ruby-lang.org/issues/18564 for detail. --- lib/did_you_mean/core_ext/name_error.rb | 55 ++++++++++++++----- test/core_ext/test_name_error_extension.rb | 16 ++++-- test/helper.rb | 10 ++++ test/spell_checking/test_key_name_check.rb | 14 ++--- test/spell_checking/test_method_name_check.rb | 20 +++---- .../test_pattern_key_name_check.rb | 2 +- .../spell_checking/test_require_path_check.rb | 6 +- .../test_variable_name_check.rb | 24 ++++---- test/test_ractor_compatibility.rb | 8 +-- 9 files changed, 98 insertions(+), 57 deletions(-) diff --git a/lib/did_you_mean/core_ext/name_error.rb b/lib/did_you_mean/core_ext/name_error.rb index eb3ef11..8c170c4 100644 --- a/lib/did_you_mean/core_ext/name_error.rb +++ b/lib/did_you_mean/core_ext/name_error.rb @@ -1,24 +1,49 @@ module DidYouMean module Correctable - SKIP_TO_S_FOR_SUPER_LOOKUP = true - private_constant :SKIP_TO_S_FOR_SUPER_LOOKUP + if Exception.method_defined?(:detailed_message) + # just for compatibility + def original_message + # we cannot use alias here because + to_s + end + + def detailed_message(highlight: true, did_you_mean: true, **) + msg = super.dup + + return msg unless did_you_mean + + suggestion = DidYouMean.formatter.message_for(corrections) + + if highlight + suggestion = suggestion.gsub(/.+/) { "\e[1m" + $& + "\e[m" } + end - def original_message - meth = method(:to_s) - while meth.owner.const_defined?(:SKIP_TO_S_FOR_SUPER_LOOKUP) - meth = meth.super_method + msg << suggestion + msg + rescue + super + end + else + SKIP_TO_S_FOR_SUPER_LOOKUP = true + private_constant :SKIP_TO_S_FOR_SUPER_LOOKUP + + def original_message + meth = method(:to_s) + while meth.owner.const_defined?(:SKIP_TO_S_FOR_SUPER_LOOKUP) + meth = meth.super_method + end + meth.call end - meth.call - end - def to_s - msg = super.dup - suggestion = DidYouMean.formatter.message_for(corrections) + def to_s + msg = super.dup + suggestion = DidYouMean.formatter.message_for(corrections) - msg << suggestion if !msg.include?(suggestion) - msg - rescue - super + msg << suggestion if !msg.include?(suggestion) + msg + rescue + super + end end def corrections diff --git a/test/core_ext/test_name_error_extension.rb b/test/core_ext/test_name_error_extension.rb index 91871cd..1fdbd45 100644 --- a/test/core_ext/test_name_error_extension.rb +++ b/test/core_ext/test_name_error_extension.rb @@ -1,6 +1,8 @@ require_relative '../helper' class NameErrorExtensionTest < Test::Unit::TestCase + include DidYouMean::TestHelper + SPELL_CHECKERS = DidYouMean.spell_checkers class TestSpellChecker @@ -20,8 +22,12 @@ def teardown end def test_message - assert_match(/Did you mean\? does_exist/, @error.to_s) - assert_match(/Did you mean\? does_exist/, @error.message) + if Exception.method_defined?(:detailed_message) + assert_match(/Did you mean\? does_exist/, @error.detailed_message) + else + assert_match(/Did you mean\? does_exist/, @error.to_s) + assert_match(/Did you mean\? does_exist/, @error.message) + end end def test_to_s_does_not_make_disruptive_changes_to_error_message @@ -29,8 +35,8 @@ def test_to_s_does_not_make_disruptive_changes_to_error_message raise NameError, "uninitialized constant Object" end - error.to_s - assert_equal 1, error.to_s.scan("Did you mean?").count + get_message(error) + assert_equal 1, get_message(error).scan("Did you mean?").count end def test_correctable_error_objects_are_dumpable @@ -41,7 +47,7 @@ def test_correctable_error_objects_are_dumpable e end - error.to_s + get_message(error) assert_equal "undefined method `sizee' for #", Marshal.load(Marshal.dump(error)).original_message diff --git a/test/helper.rb b/test/helper.rb index 7cb7b10..d40d58d 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -29,5 +29,15 @@ def ractor_compatible? def assert_correction(expected, array) assert_equal Array(expected), array, "Expected #{array.inspect} to only include #{expected.inspect}" end + + def get_message(err) + if err.respond_to?(:detailed_message) + err.detailed_message(highlight: false) + else + err.to_s + end + end + + module_function :get_message end end diff --git a/test/spell_checking/test_key_name_check.rb b/test/spell_checking/test_key_name_check.rb index ea05ff6..2f246f0 100644 --- a/test/spell_checking/test_key_name_check.rb +++ b/test/spell_checking/test_key_name_check.rb @@ -8,11 +8,11 @@ def test_corrects_hash_key_name_with_fetch error = assert_raise(KeyError) { hash.fetch(:bax) } assert_correction ":bar", error.corrections - assert_match "Did you mean? :bar", error.to_s + assert_match "Did you mean? :bar", get_message(error) error = assert_raise(KeyError) { hash.fetch("fooo") } assert_correction %("foo"), error.corrections - assert_match %(Did you mean? "foo"), error.to_s + assert_match %(Did you mean? "foo"), get_message(error) end def test_corrects_hash_key_name_with_fetch_values @@ -20,11 +20,11 @@ def test_corrects_hash_key_name_with_fetch_values error = assert_raise(KeyError) { hash.fetch_values("foo", :bar, :bax) } assert_correction ":bar", error.corrections - assert_match "Did you mean? :bar", error.to_s + assert_match "Did you mean? :bar", get_message(error) error = assert_raise(KeyError) { hash.fetch_values("foo", :bar, "fooo") } assert_correction %("foo"), error.corrections - assert_match %(Did you mean? "foo"), error.to_s + assert_match %(Did you mean? "foo"), get_message(error) end def test_correct_symbolized_hash_keys_with_string_value @@ -32,13 +32,13 @@ def test_correct_symbolized_hash_keys_with_string_value error = assert_raise(KeyError) { hash.fetch('foo_1') } assert_correction %(:foo_1), error.corrections - assert_match %(Did you mean? :foo_1), error.to_s + assert_match %(Did you mean? :foo_1), get_message(error) end def test_corrects_sprintf_key_name error = assert_raise(KeyError) { sprintf("%d", {fooo: 1}) } assert_correction ":fooo", error.corrections - assert_match "Did you mean? :fooo", error.to_s + assert_match "Did you mean? :fooo", get_message(error) end def test_corrects_env_key_name @@ -46,7 +46,7 @@ def test_corrects_env_key_name ENV["BAR"] = "2" error = assert_raise(KeyError) { ENV.fetch("BAX") } assert_correction %("BAR"), error.corrections - assert_match %(Did you mean? "BAR"), error.to_s + assert_match %(Did you mean? "BAR"), get_message(error) ensure ENV.delete("FOO") ENV.delete("BAR") diff --git a/test/spell_checking/test_method_name_check.rb b/test/spell_checking/test_method_name_check.rb index 6e14e6a..d2e46d5 100644 --- a/test/spell_checking/test_method_name_check.rb +++ b/test/spell_checking/test_method_name_check.rb @@ -41,28 +41,28 @@ def test_corrections_include_instance_method error = assert_raise(NoMethodError){ @user.flrst_name } assert_correction :first_name, error.corrections - assert_match "Did you mean? first_name", error.to_s + assert_match "Did you mean? first_name", get_message(error) end def test_corrections_include_private_method error = assert_raise(NoMethodError){ @user.friend } assert_correction :friends, error.corrections - assert_match "Did you mean? friends", error.to_s + assert_match "Did you mean? friends", get_message(error) end def test_corrections_include_method_from_module error = assert_raise(NoMethodError){ @user.fr0m_module } assert_correction :from_module, error.corrections - assert_match "Did you mean? from_module", error.to_s + assert_match "Did you mean? from_module", get_message(error) end def test_corrections_include_class_method error = assert_raise(NoMethodError){ User.l0ad } assert_correction :load, error.corrections - assert_match "Did you mean? load", error.to_s + assert_match "Did you mean? load", get_message(error) end def test_private_methods_should_not_be_suggested @@ -77,7 +77,7 @@ def test_corrections_when_private_method_is_called_with_args error = assert_raise(NoMethodError){ @user.call_incorrect_private_method } assert_correction :raise, error.corrections - assert_match "Did you mean? raise", error.to_s + assert_match "Did you mean? raise", get_message(error) end def test_exclude_methods_on_nil @@ -104,7 +104,7 @@ def test_does_not_append_suggestions_twice end end - assert_equal 1, error.to_s.scan(/Did you mean/).count + assert_equal 1, get_message(error).scan(/Did you mean/).count end def test_does_not_append_suggestions_three_times @@ -116,7 +116,7 @@ def test_does_not_append_suggestions_three_times end end - assert_equal 1, error.to_s.scan(/Did you mean/).count + assert_equal 1, get_message(error).scan(/Did you mean/).count end def test_suggests_corrections_on_nested_error @@ -128,20 +128,20 @@ def test_suggests_corrections_on_nested_error end end - assert_equal 1, error.to_s.scan(/Did you mean/).count + assert_equal 1, get_message(error).scan(/Did you mean/).count end def test_suggests_yield error = assert_raise(NoMethodError) { yeild(1) } assert_correction :yield, error.corrections - assert_match "Did you mean? yield", error.to_s + assert_match "Did you mean? yield", get_message(error) end def test_does_not_suggest_yield error = assert_raise(NoMethodError) { 1.yeild } assert_correction [], error.corrections - assert_not_match(/Did you mean\? +yield/, error.to_s) + assert_not_match(/Did you mean\? +yield/, get_message(error)) end if RUBY_ENGINE != "jruby" end diff --git a/test/spell_checking/test_pattern_key_name_check.rb b/test/spell_checking/test_pattern_key_name_check.rb index 2b0752a..10f9738 100644 --- a/test/spell_checking/test_pattern_key_name_check.rb +++ b/test/spell_checking/test_pattern_key_name_check.rb @@ -15,6 +15,6 @@ def test_corrects_hash_key_name_with_single_pattern_match end assert_correction ":foo", error.corrections - assert_match "Did you mean? :foo", error.to_s + assert_match "Did you mean? :foo", get_message(error) end end diff --git a/test/spell_checking/test_require_path_check.rb b/test/spell_checking/test_require_path_check.rb index f67fab0..d6c06e9 100644 --- a/test/spell_checking/test_require_path_check.rb +++ b/test/spell_checking/test_require_path_check.rb @@ -11,7 +11,7 @@ def test_load_error_from_require_has_suggestions end assert_correction 'ostruct', error.corrections - assert_match "Did you mean? ostruct", error.to_s + assert_match "Did you mean? ostruct", get_message(error) end def test_load_error_from_require_for_nested_files_has_suggestions @@ -20,13 +20,13 @@ def test_load_error_from_require_for_nested_files_has_suggestions end assert_correction 'net/http', error.corrections - assert_match "Did you mean? net/http", error.to_s + assert_match "Did you mean? net/http", get_message(error) error = assert_raise LoadError do require 'net-http' end assert_correction ['net/http', 'net/https'], error.corrections - assert_match "Did you mean? net/http", error.to_s + assert_match "Did you mean? net/http", get_message(error) end end diff --git a/test/spell_checking/test_variable_name_check.rb b/test/spell_checking/test_variable_name_check.rb index 193e2b7..9d8b86e 100644 --- a/test/spell_checking/test_variable_name_check.rb +++ b/test/spell_checking/test_variable_name_check.rb @@ -39,7 +39,7 @@ def test_corrections_include_instance_method end assert_correction :first_name, error.corrections - assert_match "Did you mean? first_name", error.to_s + assert_match "Did you mean? first_name", get_message(error) end def test_corrections_include_method_from_module @@ -48,7 +48,7 @@ def test_corrections_include_method_from_module end assert_correction :from_module, error.corrections - assert_match "Did you mean? from_module", error.to_s + assert_match "Did you mean? from_module", get_message(error) end def test_corrections_include_local_variable_name @@ -57,7 +57,7 @@ def test_corrections_include_local_variable_name error = (eprson rescue $!) # Do not use @assert_raise here as it changes a scope. assert_correction :person, error.corrections - assert_match "Did you mean? person", error.to_s + assert_match "Did you mean? person", get_message(error) end end @@ -81,30 +81,30 @@ def test_corrections_include_ruby_predefined_objects end assert_correction :false, false_error.corrections - assert_match "Did you mean? false", false_error.to_s + assert_match "Did you mean? false", get_message(false_error) assert_correction :true, true_error.corrections - assert_match "Did you mean? true", true_error.to_s + assert_match "Did you mean? true", get_message(true_error) assert_correction :nil, nil_error.corrections - assert_match "Did you mean? nil", nil_error.to_s + assert_match "Did you mean? nil", get_message(nil_error) assert_correction :__FILE__, file_error.corrections - assert_match "Did you mean? __FILE__", file_error.to_s + assert_match "Did you mean? __FILE__", get_message(file_error) end def test_suggests_yield error = assert_raise(NameError) { yeild } assert_correction :yield, error.corrections - assert_match "Did you mean? yield", error.to_s + assert_match "Did you mean? yield", get_message(error) end def test_corrections_include_instance_variable_name error = assert_raise(NameError){ @user.to_s } assert_correction :@email_address, error.corrections - assert_match "Did you mean? @email_address", error.to_s + assert_match "Did you mean? @email_address", get_message(error) end def test_corrections_include_private_method @@ -113,7 +113,7 @@ def test_corrections_include_private_method end assert_correction :cia_codename, error.corrections - assert_match "Did you mean? cia_codename", error.to_s + assert_match "Did you mean? cia_codename", get_message(error) end @@does_exist = true @@ -122,7 +122,7 @@ def test_corrections_include_class_variable_name error = assert_raise(NameError){ @@doesnt_exist } assert_correction :@@does_exist, error.corrections - assert_match "Did you mean? @@does_exist", error.to_s + assert_match "Did you mean? @@does_exist", get_message(error) end def test_struct_name_error @@ -130,7 +130,7 @@ def test_struct_name_error error = assert_raise(NameError){ value[:doesnt_exist] } assert_correction [:does_exist, :does_exist=], error.corrections - assert_match "Did you mean? does_exist", error.to_s + assert_match "Did you mean? does_exist", get_message(error) end def test_exclude_typical_incorrect_suggestions diff --git a/test/test_ractor_compatibility.rb b/test/test_ractor_compatibility.rb index 1a9e639..0cf724f 100644 --- a/test/test_ractor_compatibility.rb +++ b/test/test_ractor_compatibility.rb @@ -34,7 +34,7 @@ def test_key_name_suggestion_works_in_ractor }.take assert_correction ":bar", error.corrections - assert_match "Did you mean? :bar", error.to_s + assert_match "Did you mean? :bar", get_message(error) end def test_method_name_suggestion_works_in_ractor @@ -48,7 +48,7 @@ def test_method_name_suggestion_works_in_ractor }.take assert_correction :to_s, error.corrections - assert_match "Did you mean? to_s", error.to_s + assert_match "Did you mean? to_s", get_message(error) end if defined?(::NoMatchingPatternKeyError) @@ -67,7 +67,7 @@ def test_pattern_key_name_suggestion_works_in_ractor }.take assert_correction ":foo", error.corrections - assert_match "Did you mean? :foo", error.to_s + assert_match "Did you mean? :foo", get_message(error) end end @@ -97,6 +97,6 @@ def test_variable_name_suggestion_works_in_ractor }.take assert_correction :in_ractor, error.corrections - assert_match "Did you mean? in_ractor", error.to_s + assert_match "Did you mean? in_ractor", get_message(error) end end From e6ab3307a87642fa075411967f21eee2832f0654 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 7 Jun 2022 16:24:43 +0900 Subject: [PATCH 07/12] Manually merge https://github.com/ruby/ruby/commit/4146fd284b3c3995cf6638b239625c530c6da875 --- test/test_ractor_compatibility.rb | 157 ++++++++++++++++-------------- 1 file changed, 86 insertions(+), 71 deletions(-) diff --git a/test/test_ractor_compatibility.rb b/test/test_ractor_compatibility.rb index 0cf724f..7385f10 100644 --- a/test/test_ractor_compatibility.rb +++ b/test/test_ractor_compatibility.rb @@ -3,100 +3,115 @@ return if not DidYouMean::TestHelper.ractor_compatible? class RactorCompatibilityTest < Test::Unit::TestCase - include DidYouMean::TestHelper - - class ::Book; end - class FirstNameError < NameError; end - def test_class_name_suggestion_works_in_ractor - error = Ractor.new { - begin - Boook - rescue NameError => e - e.corrections # It is important to call the #corrections method within Ractor. - e - end - }.take + assert_ractor(<<~CODE, require_relative: "helper") + class ::Book; end + include DidYouMean::TestHelper + error = Ractor.new { + begin + Boook + rescue NameError => e + e.corrections # It is important to call the #corrections method within Ractor. + e + end + }.take - assert_correction "Book", error.corrections + assert_correction "Book", error.corrections + CODE end def test_key_name_suggestion_works_in_ractor - error = Ractor.new { - begin - hash = { "foo" => 1, bar: 2 } + assert_ractor(<<~CODE, require_relative: "helper") + include DidYouMean::TestHelper + error = Ractor.new { + begin + hash = { "foo" => 1, bar: 2 } - hash.fetch(:bax) - rescue KeyError => e - e.corrections # It is important to call the #corrections method within Ractor. - e - end - }.take + hash.fetch(:bax) + rescue KeyError => e + e.corrections # It is important to call the #corrections method within Ractor. + e + end + }.take - assert_correction ":bar", error.corrections - assert_match "Did you mean? :bar", get_message(error) + assert_correction ":bar", error.corrections + assert_match "Did you mean? :bar", get_message(error) + CODE end def test_method_name_suggestion_works_in_ractor - error = Ractor.new { - begin - self.to__s - rescue NoMethodError => e - e.corrections # It is important to call the #corrections method within Ractor. - e - end - }.take - - assert_correction :to_s, error.corrections - assert_match "Did you mean? to_s", get_message(error) - end - - if defined?(::NoMatchingPatternKeyError) - def test_pattern_key_name_suggestion_works_in_ractor + assert_ractor(<<~CODE, require_relative: "helper") + include DidYouMean::TestHelper error = Ractor.new { begin - eval(<<~RUBY, binding, __FILE__, __LINE__) - hash = {foo: 1, bar: 2, baz: 3} - hash => {fooo:} - fooo = 1 # suppress "unused variable: fooo" warning - RUBY - rescue NoMatchingPatternKeyError => e + self.to__s + rescue NoMethodError => e e.corrections # It is important to call the #corrections method within Ractor. e end }.take - assert_correction ":foo", error.corrections - assert_match "Did you mean? :foo", get_message(error) + assert_correction :to_s, error.corrections + assert_match "Did you mean? to_s", get_message(error) + CODE + end + + if defined?(::NoMatchingPatternKeyError) + def test_pattern_key_name_suggestion_works_in_ractor + assert_ractor(<<~CODE, require_relative: "helper") + include DidYouMean::TestHelper + error = Ractor.new { + begin + eval(<<~RUBY, binding, __FILE__, __LINE__) + hash = {foo: 1, bar: 2, baz: 3} + hash => {fooo:} + fooo = 1 # suppress "unused variable: fooo" warning + RUBY + rescue NoMatchingPatternKeyError => e + e.corrections # It is important to call the #corrections method within Ractor. + e + end + }.take + + assert_correction ":foo", error.corrections + assert_match "Did you mean? :foo", get_message(error) + CODE end end def test_can_raise_other_name_error_in_ractor - error = Ractor.new { - begin - raise FirstNameError, "Other name error" - rescue FirstNameError => e - e.corrections # It is important to call the #corrections method within Ractor. - e - end - }.take - - assert_not_match(/Did you mean\?/, error.message) + assert_ractor(<<~CODE, require_relative: "helper") + class FirstNameError < NameError; end + include DidYouMean::TestHelper + error = Ractor.new { + begin + raise FirstNameError, "Other name error" + rescue FirstNameError => e + e.corrections # It is important to call the #corrections method within Ractor. + e + end + }.take + + assert_not_match(/Did you mean\?/, error.message) + CODE end def test_variable_name_suggestion_works_in_ractor - error = Ractor.new { - in_ractor = in_ractor = 1 - - begin - in_reactor - rescue NameError => e - e.corrections # It is important to call the #corrections method within Ractor. - e - end - }.take - - assert_correction :in_ractor, error.corrections - assert_match "Did you mean? in_ractor", get_message(error) + assert_ractor(<<~CODE, require_relative: "helper") + include DidYouMean::TestHelper + error = Ractor.new { + in_ractor = in_ractor = 1 + + begin + in_reactor + rescue NameError => e + e.corrections # It is important to call the #corrections method within Ractor. + e + end + }.take + + assert_correction :in_ractor, error.corrections + assert_match "Did you mean? in_ractor", get_message(error) + CODE end end From 03dc05154d474091648cdf166e62cdd3826a33e4 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 7 Jun 2022 21:17:13 +0900 Subject: [PATCH 08/12] Import assert_ractor from ruby/ruby --- test/lib/core_assertions.rb | 751 ++++++++++++++++++++++++++++++++++++ test/lib/envutil.rb | 372 ++++++++++++++++++ test/lib/find_executable.rb | 22 ++ 3 files changed, 1145 insertions(+) create mode 100644 test/lib/core_assertions.rb create mode 100644 test/lib/envutil.rb create mode 100644 test/lib/find_executable.rb diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb new file mode 100644 index 0000000..723a174 --- /dev/null +++ b/test/lib/core_assertions.rb @@ -0,0 +1,751 @@ +# frozen_string_literal: true + +module Test + module Unit + module Assertions + def _assertions= n # :nodoc: + @_assertions = n + end + + def _assertions # :nodoc: + @_assertions ||= 0 + end + + ## + # Returns a proc that will output +msg+ along with the default message. + + def message msg = nil, ending = nil, &default + proc { + ending ||= (ending_pattern = /(? 0 and b > 0 + assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"}) + end + rescue LoadError + pend + end + + # :call-seq: + # assert_nothing_raised( *args, &block ) + # + #If any exceptions are given as arguments, the assertion will + #fail if one of those exceptions are raised. Otherwise, the test fails + #if any exceptions are raised. + # + #The final argument may be a failure message. + # + # assert_nothing_raised RuntimeError do + # raise Exception #Assertion passes, Exception is not a RuntimeError + # end + # + # assert_nothing_raised do + # raise Exception #Assertion fails + # end + def assert_nothing_raised(*args) + self._assertions += 1 + if Module === args.last + msg = nil + else + msg = args.pop + end + begin + yield + rescue Test::Unit::PendedError, *(Test::Unit::AssertionFailedError if args.empty?) + raise + rescue *(args.empty? ? Exception : args) => e + msg = message(msg) { + "Exception raised:\n<#{mu_pp(e)}>\n""Backtrace:\n" << + Test.filter_backtrace(e.backtrace).map{|frame| " #{frame}"}.join("\n") + } + raise Test::Unit::AssertionFailedError, msg.call, e.backtrace + end + end + + def prepare_syntax_check(code, fname = nil, mesg = nil, verbose: nil) + fname ||= caller_locations(2, 1)[0] + mesg ||= fname.to_s + verbose, $VERBOSE = $VERBOSE, verbose + case + when Array === fname + fname, line = *fname + when defined?(fname.path) && defined?(fname.lineno) + fname, line = fname.path, fname.lineno + else + line = 1 + end + yield(code, fname, line, message(mesg) { + if code.end_with?("\n") + "```\n#{code}```\n" + else + "```\n#{code}\n```\n""no-newline" + end + }) + ensure + $VERBOSE = verbose + end + + def assert_valid_syntax(code, *args, **opt) + prepare_syntax_check(code, *args, **opt) do |src, fname, line, mesg| + yield if defined?(yield) + assert_nothing_raised(SyntaxError, mesg) do + assert_equal(:ok, syntax_check(src, fname, line), mesg) + end + end + end + + def assert_normal_exit(testsrc, message = '', child_env: nil, **opt) + assert_valid_syntax(testsrc, caller_locations(1, 1)[0]) + if child_env + child_env = [child_env] + else + child_env = [] + end + out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt) + assert !status.signaled?, FailDesc[status, message, out] + end + + def assert_ruby_status(args, test_stdin="", message=nil, **opt) + out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt) + desc = FailDesc[status, message, out] + assert(!status.signaled?, desc) + message ||= "ruby exit status is not success:" + assert(status.success?, desc) + end + + ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") + + def separated_runner(token, out = nil) + include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) }) + out = out ? IO.new(out, 'w') : STDOUT + at_exit { + out.puts "#{token}", [Marshal.dump($!)].pack('m'), "#{token}", "#{token}assertions=#{self._assertions}" + } + Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) if defined?(Test::Unit::Runner) + end + + def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt) + unless file and line + loc, = caller_locations(1,1) + file ||= loc.path + line ||= loc.lineno + end + capture_stdout = true + unless /mswin|mingw/ =~ RbConfig::CONFIG['host_os'] + capture_stdout = false + opt[:out] = Test::Unit::Runner.output if defined?(Test::Unit::Runner) + res_p, res_c = IO.pipe + opt[:ios] = [res_c] + end + token_dump, token_re = new_test_token + src = <\n\K.*\n(?=#{token_re}<\/error>$)/m].unpack1("m")) + rescue => marshal_error + ignore_stderr = nil + res = nil + end + if res and !(SystemExit === res) + if bt = res.backtrace + bt.each do |l| + l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} + end + bt.concat(caller) + else + res.set_backtrace(caller) + end + raise res + end + + # really is it succeed? + unless ignore_stderr + # the body of assert_separately must not output anything to detect error + assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr]) + end + assert(status.success?, FailDesc[status, "assert_separately failed", stderr]) + raise marshal_error if marshal_error + end + + # Run Ractor-related test without influencing the main test suite + def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt) + return unless defined?(Ractor) + + require = "require #{require.inspect}" if require + if require_relative + dir = File.dirname(caller_locations[0,1][0].absolute_path) + full_path = File.expand_path(require_relative, dir) + require = "#{require}; require #{full_path.inspect}" + end + + assert_separately(args, file, line, <<~RUBY, ignore_stderr: ignore_stderr, **opt) + #{require} + previous_verbose = $VERBOSE + $VERBOSE = nil + Ractor.new {} # trigger initial warning + $VERBOSE = previous_verbose + #{src} + RUBY + end + + # :call-seq: + # assert_throw( tag, failure_message = nil, &block ) + # + #Fails unless the given block throws +tag+, returns the caught + #value otherwise. + # + #An optional failure message may be provided as the final argument. + # + # tag = Object.new + # assert_throw(tag, "#{tag} was not thrown!") do + # throw tag + # end + def assert_throw(tag, msg = nil) + ret = catch(tag) do + begin + yield(tag) + rescue UncaughtThrowError => e + thrown = e.tag + end + msg = message(msg) { + "Expected #{mu_pp(tag)} to have been thrown"\ + "#{%Q[, not #{thrown}] if thrown}" + } + assert(false, msg) + end + assert(true) + ret + end + + # :call-seq: + # assert_raise( *args, &block ) + # + #Tests if the given block raises an exception. Acceptable exception + #types may be given as optional arguments. If the last argument is a + #String, it will be used as the error message. + # + # assert_raise do #Fails, no Exceptions are raised + # end + # + # assert_raise NameError do + # puts x #Raises NameError, so assertion succeeds + # end + def assert_raise(*exp, &b) + case exp.last + when String, Proc + msg = exp.pop + end + + begin + yield + rescue Test::Unit::PendedError => e + return e if exp.include? Test::Unit::PendedError + raise e + rescue Exception => e + expected = exp.any? { |ex| + if ex.instance_of? Module then + e.kind_of? ex + else + e.instance_of? ex + end + } + + assert expected, proc { + flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"}) + } + + return e + ensure + unless e + exp = exp.first if exp.size == 1 + + flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"}) + end + end + end + + # :call-seq: + # assert_raise_with_message(exception, expected, msg = nil, &block) + # + #Tests if the given block raises an exception with the expected + #message. + # + # assert_raise_with_message(RuntimeError, "foo") do + # nil #Fails, no Exceptions are raised + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise ArgumentError, "foo" #Fails, different Exception is raised + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise "bar" #Fails, RuntimeError is raised but the message differs + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise "foo" #Raises RuntimeError with the message, so assertion succeeds + # end + def assert_raise_with_message(exception, expected, msg = nil, &block) + case expected + when String + assert = :assert_equal + when Regexp + assert = :assert_match + else + raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}" + end + + ex = m = nil + EnvUtil.with_default_internal(expected.encoding) do + ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do + yield + end + m = ex.message + end + msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} + + if assert == :assert_equal + assert_equal(expected, m, msg) + else + msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" } + assert expected =~ m, msg + block.binding.eval("proc{|_|$~=_}").call($~) + end + ex + end + + TEST_DIR = File.join(__dir__, "test/unit") #:nodoc: + + # :call-seq: + # assert(test, [failure_message]) + # + #Tests if +test+ is true. + # + #+msg+ may be a String or a Proc. If +msg+ is a String, it will be used + #as the failure message. Otherwise, the result of calling +msg+ will be + #used as the message if the assertion fails. + # + #If no +msg+ is given, a default message will be used. + # + # assert(false, "This was expected to be true") + def assert(test, *msgs) + case msg = msgs.first + when String, Proc + when nil + msgs.shift + else + bt = caller.reject { |s| s.start_with?(TEST_DIR) } + raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt + end unless msgs.empty? + super + end + + # :call-seq: + # assert_respond_to( object, method, failure_message = nil ) + # + #Tests if the given Object responds to +method+. + # + #An optional failure message may be provided as the final argument. + # + # assert_respond_to("hello", :reverse) #Succeeds + # assert_respond_to("hello", :does_not_exist) #Fails + def assert_respond_to(obj, (meth, *priv), msg = nil) + unless priv.empty? + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}" + } + return assert obj.respond_to?(meth, *priv), msg + end + #get rid of overcounting + if caller_locations(1, 1)[0].path.start_with?(TEST_DIR) + return if obj.respond_to?(meth) + end + super(obj, meth, msg) + end + + # :call-seq: + # assert_not_respond_to( object, method, failure_message = nil ) + # + #Tests if the given Object does not respond to +method+. + # + #An optional failure message may be provided as the final argument. + # + # assert_not_respond_to("hello", :reverse) #Fails + # assert_not_respond_to("hello", :does_not_exist) #Succeeds + def assert_not_respond_to(obj, (meth, *priv), msg = nil) + unless priv.empty? + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}" + } + return assert !obj.respond_to?(meth, *priv), msg + end + #get rid of overcounting + if caller_locations(1, 1)[0].path.start_with?(TEST_DIR) + return unless obj.respond_to?(meth) + end + refute_respond_to(obj, meth, msg) + end + + # pattern_list is an array which contains regexp and :*. + # :* means any sequence. + # + # pattern_list is anchored. + # Use [:*, regexp, :*] for non-anchored match. + def assert_pattern_list(pattern_list, actual, message=nil) + rest = actual + anchored = true + pattern_list.each_with_index {|pattern, i| + if pattern == :* + anchored = false + else + if anchored + match = /\A#{pattern}/.match(rest) + else + match = pattern.match(rest) + end + unless match + msg = message(msg) { + expect_msg = "Expected #{mu_pp pattern}\n" + if /\n[^\n]/ =~ rest + actual_mesg = +"to match\n" + rest.scan(/.*\n+/) { + actual_mesg << ' ' << $&.inspect << "+\n" + } + actual_mesg.sub!(/\+\n\z/, '') + else + actual_mesg = "to match " + mu_pp(rest) + end + actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters" + expect_msg + actual_mesg + } + assert false, msg + end + rest = match.post_match + anchored = true + end + } + if anchored + assert_equal("", rest) + end + end + + def assert_warning(pat, msg = nil) + result = nil + stderr = EnvUtil.with_default_internal(pat.encoding) { + EnvUtil.verbose_warning { + result = yield + } + } + msg = message(msg) {diff pat, stderr} + assert(pat === stderr, msg) + result + end + + def assert_warn(*args) + assert_warning(*args) {$VERBOSE = false; yield} + end + + def assert_deprecated_warning(mesg = /deprecated/) + assert_warning(mesg) do + Warning[:deprecated] = true if Warning.respond_to?(:[]=) + yield + end + end + + def assert_deprecated_warn(mesg = /deprecated/) + assert_warn(mesg) do + Warning[:deprecated] = true if Warning.respond_to?(:[]=) + yield + end + end + + class << (AssertFile = Struct.new(:failure_message).new) + include Assertions + include CoreAssertions + def assert_file_predicate(predicate, *args) + if /\Anot_/ =~ predicate + predicate = $' + neg = " not" + end + result = File.__send__(predicate, *args) + result = !result if neg + mesg = "Expected file ".dup << args.shift.inspect + mesg << "#{neg} to be #{predicate}" + mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty? + mesg << " #{failure_message}" if failure_message + assert(result, mesg) + end + alias method_missing assert_file_predicate + + def for(message) + clone.tap {|a| a.failure_message = message} + end + end + + class AllFailures + attr_reader :failures + + def initialize + @count = 0 + @failures = {} + end + + def for(key) + @count += 1 + yield key + rescue Exception => e + @failures[key] = [@count, e] + end + + def foreach(*keys) + keys.each do |key| + @count += 1 + begin + yield key + rescue Exception => e + @failures[key] = [@count, e] + end + end + end + + def message + i = 0 + total = @count.to_s + fmt = "%#{total.size}d" + @failures.map {|k, (n, v)| + v = v.message + "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.b.gsub(/^/, ' | ').force_encoding(v.encoding)}" + }.join("\n") + end + + def pass? + @failures.empty? + end + end + + # threads should respond to shift method. + # Array can be used. + def assert_join_threads(threads, message = nil) + errs = [] + values = [] + while th = threads.shift + begin + values << th.value + rescue Exception + errs << [th, $!] + th = nil + end + end + values + ensure + if th&.alive? + th.raise(Timeout::Error.new) + th.join rescue errs << [th, $!] + end + if !errs.empty? + msg = "exceptions on #{errs.length} threads:\n" + + errs.map {|t, err| + "#{t.inspect}:\n" + + err.full_message(highlight: false, order: :top) + }.join("\n---\n") + if message + msg = "#{message}\n#{msg}" + end + raise Test::Unit::AssertionFailedError, msg + end + end + + def assert_all?(obj, m = nil, &blk) + failed = [] + obj.each do |*a, &b| + unless blk.call(*a, &b) + failed << (a.size > 1 ? a : a[0]) + end + end + assert(failed.empty?, message(m) {failed.pretty_inspect}) + end + + def assert_all_assertions(msg = nil) + all = AllFailures.new + yield all + ensure + assert(all.pass?, message(msg) {all.message.chomp(".")}) + end + alias all_assertions assert_all_assertions + + def assert_all_assertions_foreach(msg = nil, *keys, &block) + all = AllFailures.new + all.foreach(*keys, &block) + ensure + assert(all.pass?, message(msg) {all.message.chomp(".")}) + end + alias all_assertions_foreach assert_all_assertions_foreach + + def diff(exp, act) + require 'pp' + q = PP.new(+"") + q.guard_inspect_key do + q.group(2, "expected: ") do + q.pp exp + end + q.text q.newline + q.group(2, "actual: ") do + q.pp act + end + q.flush + end + q.output + end + + def new_test_token + token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m" + return token.dump, Regexp.quote(token) + end + end + end +end diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb new file mode 100644 index 0000000..e21305c --- /dev/null +++ b/test/lib/envutil.rb @@ -0,0 +1,372 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: true +require "open3" +require "timeout" +require_relative "find_executable" +begin + require 'rbconfig' +rescue LoadError +end +begin + require "rbconfig/sizeof" +rescue LoadError +end + +module EnvUtil + def rubybin + if ruby = ENV["RUBY"] + return ruby + end + ruby = "ruby" + exeext = RbConfig::CONFIG["EXEEXT"] + rubyexe = (ruby + exeext if exeext and !exeext.empty?) + 3.times do + if File.exist? ruby and File.executable? ruby and !File.directory? ruby + return File.expand_path(ruby) + end + if rubyexe and File.exist? rubyexe and File.executable? rubyexe + return File.expand_path(rubyexe) + end + ruby = File.join("..", ruby) + end + if defined?(RbConfig.ruby) + RbConfig.ruby + else + "ruby" + end + end + module_function :rubybin + + LANG_ENVS = %w"LANG LC_ALL LC_CTYPE" + + DEFAULT_SIGNALS = Signal.list + DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM + + RUBYLIB = ENV["RUBYLIB"] + + class << self + attr_accessor :timeout_scale + attr_reader :original_internal_encoding, :original_external_encoding, + :original_verbose, :original_warning + + def capture_global_values + @original_internal_encoding = Encoding.default_internal + @original_external_encoding = Encoding.default_external + @original_verbose = $VERBOSE + @original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil + end + end + + def apply_timeout_scale(t) + if scale = EnvUtil.timeout_scale + t * scale + else + t + end + end + module_function :apply_timeout_scale + + def timeout(sec, klass = nil, message = nil, &blk) + return yield(sec) if sec == nil or sec.zero? + sec = apply_timeout_scale(sec) + Timeout.timeout(sec, klass, message, &blk) + end + module_function :timeout + + def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1) + reprieve = apply_timeout_scale(reprieve) if reprieve + + signals = Array(signal).select do |sig| + DEFAULT_SIGNALS[sig.to_s] or + DEFAULT_SIGNALS[Signal.signame(sig)] rescue false + end + signals |= [:ABRT, :KILL] + case pgroup + when 0, true + pgroup = -pid + when nil, false + pgroup = pid + end + + lldb = true if /darwin/ =~ RUBY_PLATFORM + + while signal = signals.shift + + if lldb and [:ABRT, :KILL].include?(signal) + lldb = false + # sudo -n: --non-interactive + # lldb -p: attach + # -o: run command + system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit]) + true + end + + begin + Process.kill signal, pgroup + rescue Errno::EINVAL + next + rescue Errno::ESRCH + break + end + if signals.empty? or !reprieve + Process.wait(pid) + else + begin + Timeout.timeout(reprieve) {Process.wait(pid)} + rescue Timeout::Error + else + break + end + end + end + $? + end + module_function :terminate + + def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, + encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error, + stdout_filter: nil, stderr_filter: nil, ios: nil, + signal: :TERM, + rubybin: EnvUtil.rubybin, precommand: nil, + **opt) + timeout = apply_timeout_scale(timeout) + + in_c, in_p = IO.pipe + out_p, out_c = IO.pipe if capture_stdout + err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout + opt[:in] = in_c + opt[:out] = out_c if capture_stdout + opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr + if encoding + out_p.set_encoding(encoding) if out_p + err_p.set_encoding(encoding) if err_p + end + ios.each {|i, o = i|opt[i] = o} if ios + + c = "C" + child_env = {} + LANG_ENVS.each {|lc| child_env[lc] = c} + if Array === args and Hash === args.first + child_env.update(args.shift) + end + if RUBYLIB and lib = child_env["RUBYLIB"] + child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR) + end + + # remain env + %w(ASAN_OPTIONS RUBY_ON_BUG).each{|name| + child_env[name] = ENV[name] if ENV[name] + } + + args = [args] if args.kind_of?(String) + pid = spawn(child_env, *precommand, rubybin, *args, opt) + in_c.close + out_c&.close + out_c = nil + err_c&.close + err_c = nil + if block_given? + return yield in_p, out_p, err_p, pid + else + th_stdout = Thread.new { out_p.read } if capture_stdout + th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout + in_p.write stdin_data.to_str unless stdin_data.empty? + in_p.close + if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) + timeout_error = nil + else + status = terminate(pid, signal, opt[:pgroup], reprieve) + terminated = Time.now + end + stdout = th_stdout.value if capture_stdout + stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout + out_p.close if capture_stdout + err_p.close if capture_stderr && capture_stderr != :merge_to_stdout + status ||= Process.wait2(pid)[1] + stdout = stdout_filter.call(stdout) if stdout_filter + stderr = stderr_filter.call(stderr) if stderr_filter + if timeout_error + bt = caller_locations + msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)" + msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n")) + raise timeout_error, msg, bt.map(&:to_s) + end + return stdout, stderr, status + end + ensure + [th_stdout, th_stderr].each do |th| + th.kill if th + end + [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| + io&.close + end + [th_stdout, th_stderr].each do |th| + th.join if th + end + end + module_function :invoke_ruby + + def verbose_warning + class << (stderr = "".dup) + alias write concat + def flush; end + end + stderr, $stderr = $stderr, stderr + $VERBOSE = true + yield stderr + return $stderr + ensure + stderr, $stderr = $stderr, stderr + $VERBOSE = EnvUtil.original_verbose + EnvUtil.original_warning&.each {|i, v| Warning[i] = v} + end + module_function :verbose_warning + + def default_warning + $VERBOSE = false + yield + ensure + $VERBOSE = EnvUtil.original_verbose + end + module_function :default_warning + + def suppress_warning + $VERBOSE = nil + yield + ensure + $VERBOSE = EnvUtil.original_verbose + end + module_function :suppress_warning + + def under_gc_stress(stress = true) + stress, GC.stress = GC.stress, stress + yield + ensure + GC.stress = stress + end + module_function :under_gc_stress + + def with_default_external(enc) + suppress_warning { Encoding.default_external = enc } + yield + ensure + suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding } + end + module_function :with_default_external + + def with_default_internal(enc) + suppress_warning { Encoding.default_internal = enc } + yield + ensure + suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding } + end + module_function :with_default_internal + + def labeled_module(name, &block) + Module.new do + singleton_class.class_eval { + define_method(:to_s) {name} + alias inspect to_s + alias name to_s + } + class_eval(&block) if block + end + end + module_function :labeled_module + + def labeled_class(name, superclass = Object, &block) + Class.new(superclass) do + singleton_class.class_eval { + define_method(:to_s) {name} + alias inspect to_s + alias name to_s + } + class_eval(&block) if block + end + end + module_function :labeled_class + + if /darwin/ =~ RUBY_PLATFORM + DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports") + DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S' + @ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME'] + + def self.diagnostic_reports(signame, pid, now) + return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame) + cmd = File.basename(rubybin) + cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd + path = DIAGNOSTIC_REPORTS_PATH + timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT + pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash" + first = true + 30.times do + first ? (first = false) : sleep(0.1) + Dir.glob(pat) do |name| + log = File.read(name) rescue next + if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log + File.unlink(name) + File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil + return log + end + end + end + nil + end + else + def self.diagnostic_reports(signame, pid, now) + end + end + + def self.failure_description(status, now, message = "", out = "") + pid = status.pid + if signo = status.termsig + signame = Signal.signame(signo) + sigdesc = "signal #{signo}" + end + log = diagnostic_reports(signame, pid, now) + if signame + sigdesc = "SIG#{signame} (#{sigdesc})" + end + if status.coredump? + sigdesc = "#{sigdesc} (core dumped)" + end + full_message = ''.dup + message = message.call if Proc === message + if message and !message.empty? + full_message << message << "\n" + end + full_message << "pid #{pid}" + full_message << " exit #{status.exitstatus}" if status.exited? + full_message << " killed by #{sigdesc}" if sigdesc + if out and !out.empty? + full_message << "\n" << out.b.gsub(/^/, '| ') + full_message.sub!(/(? Date: Thu, 1 Sep 2022 14:04:38 +0900 Subject: [PATCH 09/12] Added sync task and sync them --- Rakefile | 7 +++++++ test/lib/core_assertions.rb | 16 +++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Rakefile b/Rakefile index a16b816..670da5a 100644 --- a/Rakefile +++ b/Rakefile @@ -21,6 +21,13 @@ end task default: %i(test) +task :sync_tool do + require 'fileutils' + FileUtils.cp "../ruby/tool/lib/core_assertions.rb", "./test/lib" + FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib" + FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib" +end + namespace :test do namespace :accuracy do desc "Download Wiktionary's Simple English data and save it as a dictionary" diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb index 723a174..7cd598b 100644 --- a/test/lib/core_assertions.rb +++ b/test/lib/core_assertions.rb @@ -3,6 +3,10 @@ module Test module Unit module Assertions + def assert_raises(*exp, &b) + raise NoMethodError, "use assert_raise", caller + end + def _assertions= n # :nodoc: @_assertions = n end @@ -264,7 +268,7 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o src = < Date: Thu, 1 Sep 2022 16:13:17 +0900 Subject: [PATCH 10/12] require CoreAssertion for assert_ractor --- test/test_ractor_compatibility.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_ractor_compatibility.rb b/test/test_ractor_compatibility.rb index 7385f10..b3ae0fb 100644 --- a/test/test_ractor_compatibility.rb +++ b/test/test_ractor_compatibility.rb @@ -2,6 +2,9 @@ return if not DidYouMean::TestHelper.ractor_compatible? +require_relative "lib/core_assertions" +Test::Unit::TestCase.include Test::Unit::CoreAssertions + class RactorCompatibilityTest < Test::Unit::TestCase def test_class_name_suggestion_works_in_ractor assert_ractor(<<~CODE, require_relative: "helper") From 2f954f42ef115109cfb0c8e8f7f3c53d9bac1e62 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 1 Sep 2022 16:51:54 +0900 Subject: [PATCH 11/12] require CoreAssertion in helper file --- Rakefile | 4 ++-- test/lib/helper.rb | 3 +++ test/test_ractor_compatibility.rb | 3 --- 3 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 test/lib/helper.rb diff --git a/Rakefile b/Rakefile index 670da5a..aca1a73 100644 --- a/Rakefile +++ b/Rakefile @@ -2,12 +2,12 @@ require 'bundler/gem_tasks' require 'rake/testtask' Rake::TestTask.new do |task| - task.libs << "test" + task.libs << "test/lib" << "test" task.test_files = Dir['test/**/test_*.rb'].reject {|path| path.end_with?("test_explore.rb") } task.verbose = true task.warning = true - task.ruby_opts = %w[ --disable-did_you_mean ] + task.ruby_opts = %w[ --disable-did_you_mean -rhelper ] end Rake::TestTask.new("test:explore") do |task| diff --git a/test/lib/helper.rb b/test/lib/helper.rb new file mode 100644 index 0000000..c7c98e8 --- /dev/null +++ b/test/lib/helper.rb @@ -0,0 +1,3 @@ +require "test/unit" +require_relative "core_assertions" +Test::Unit::TestCase.include Test::Unit::CoreAssertions diff --git a/test/test_ractor_compatibility.rb b/test/test_ractor_compatibility.rb index b3ae0fb..7385f10 100644 --- a/test/test_ractor_compatibility.rb +++ b/test/test_ractor_compatibility.rb @@ -2,9 +2,6 @@ return if not DidYouMean::TestHelper.ractor_compatible? -require_relative "lib/core_assertions" -Test::Unit::TestCase.include Test::Unit::CoreAssertions - class RactorCompatibilityTest < Test::Unit::TestCase def test_class_name_suggestion_works_in_ractor assert_ractor(<<~CODE, require_relative: "helper") From f5b15128e254bacc74c795e520acc98af35343bb Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 5 Dec 2022 19:29:20 +0900 Subject: [PATCH 12/12] Bump version to 1.6.2 --- lib/did_you_mean/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/did_you_mean/version.rb b/lib/did_you_mean/version.rb index b5fe50b..9fd6f17 100644 --- a/lib/did_you_mean/version.rb +++ b/lib/did_you_mean/version.rb @@ -1,3 +1,3 @@ module DidYouMean - VERSION = "1.6.1".freeze + VERSION = "1.6.2".freeze end