diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index a283a2f3dbd3ef..956ea5f325daa0 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -298,7 +298,7 @@ The compiled Ruby will now automatically crash with a report and a backtrace if ASAN detects a memory safety issue. To run Ruby's test suite under ASAN, issue the following command. Note that this will take quite a long time (over two hours on my laptop); the `RUBY_TEST_TIMEOUT_SCALE` and -`SYNTAX_SUGEST_TIMEOUT` variables are required to make sure tests don't +`SYNTAX_SUGGEST_TIMEOUT` variables are required to make sure tests don't spuriously fail with timeouts when in fact they're just slow. ```sh diff --git a/doc/contributing/documentation_guide.md b/doc/contributing/documentation_guide.md index d0c29e2e76d98e..7a28acdf4dc297 100644 --- a/doc/contributing/documentation_guide.md +++ b/doc/contributing/documentation_guide.md @@ -347,7 +347,7 @@ Alternatives: - Example {source}[https://github.com/ruby/ruby/blob/34d802f32f00df1ac0220b62f72605827c16bad8/file.c#L6570-L6596]. - Corresponding {output}[rdoc-ref:File@ReadWrite+Mode]. -- (Markdown format only): A {Github Flavored Markdown (GFM) table}[https://github.github.com/gfm/#tables-extension-], +- (Markdown format only): A {GitHub Flavored Markdown (GFM) table}[https://github.github.com/gfm/#tables-extension-], using special formatting for the text: - Example {source}[https://github.com/ruby/ruby/blob/34d802f32f00df1ac0220b62f72605827c16bad8/doc/contributing/glossary.md?plain=1]. diff --git a/doc/contributing/making_changes_to_ruby.md b/doc/contributing/making_changes_to_ruby.md index 260fadb7e34b96..4a7aecea0337ab 100644 --- a/doc/contributing/making_changes_to_ruby.md +++ b/doc/contributing/making_changes_to_ruby.md @@ -23,6 +23,6 @@ Use the following style for commit messages: ## CI -GitHub actions will run on each pull request. +GitHub Actions will run on each pull request. There is [a CI that runs on master](https://rubyci.org/). It has broad coverage of different systems and architectures, such as Solaris SPARC and macOS. diff --git a/spec/bundled_gems_spec.rb b/spec/bundled_gems_spec.rb index dc0e7dde6c7bfc..d56b0d2011d9f7 100644 --- a/spec/bundled_gems_spec.rb +++ b/spec/bundled_gems_spec.rb @@ -203,7 +203,11 @@ def script(code, options = {}) it "Show warning when bundle exec with -r option" do create_file("stub.rb", stub_code) create_file("Gemfile", "source 'https://rubygems.org'") - bundle "exec ruby -r./stub -ropenssl -e ''" + # Command-line -r features are required before RUBYOPT's -rbundler/setup, + # and gem_prelude no longer consumes BUNDLER_SETUP in the main box, so + # bundler/setup must be requested explicitly ahead of the bundled gem to + # exercise the warning for requires without a Ruby caller frame. + bundle "exec ruby -rbundler/setup -r./stub -ropenssl -e ''" expect(err).to include(/openssl used to be loaded from (.*) since Ruby #{RUBY_VERSION}/) end diff --git a/test/ruby/test_argf.rb b/test/ruby/test_argf.rb index 55a06296aa7275..3c8959acb92446 100644 --- a/test/ruby/test_argf.rb +++ b/test/ruby/test_argf.rb @@ -383,7 +383,7 @@ def test_inplace_suffix_encoding end def test_inplace_bug_17117 - assert_in_out_err(["-", @t1.path], "#{<<~"{#"}#{<<~'};'}") + assert_in_out_err(["-", @t1.path], "#{<<~"{#"}#{<<~'};'}", timeout: 60) {# #!/usr/bin/ruby -pi.bak BEGIN { diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index 5ca318a5989753..cd6cd1d6a6d31e 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -18,11 +18,11 @@ def filter bt unless $DEBUG then bt.each do |line| - break if pattern.match?(line) + break if pattern =~ line new_bt << line end - new_bt = bt.reject { |line| pattern.match?(line) } if new_bt.empty? + new_bt = bt.reject { |line| pattern =~ line } if new_bt.empty? new_bt = bt.dup if new_bt.empty? else new_bt = bt.dup @@ -327,8 +327,8 @@ def separated_runner(token, out = nil) at_exit { assertions = assertions_ivar_get.call(:@_assertions) out_write.call <<~OUT - - #{array_pack.bind_call([marshal_dump.call($!)], 'm0')} + + #{array_pack.bind([marshal_dump.call($!)]).call('m0')} OUT } @@ -365,7 +365,7 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o args.insert((Hash === args.first ? 1 : 0), "-w", "--disable=gems", *$:.map {|l| "-I#{l}"}) args << "--debug" if RUBY_ENGINE == 'jruby' # warning: tracing (e.g. set_trace_func) will not capture all events without --debug flag # power_assert 3 requires ruby 3.1 or later - args << "-W:no-experimental" if RUBY_VERSION < "3.1." + args << "-W:no-experimental" if ("2.7."..."3.1.").cover?(RUBY_VERSION) stdout, stderr, status = EnvUtil.invoke_ruby(args, src, capture_stdout, true, **opt) if sanitizers&.lsan_enabled? @@ -388,21 +388,23 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o assert(!abort, FailDesc[status, nil, stderr]) res.scan(/^\n(.*?)\n(?=<\/error id="#{token_re}">$)/m) do self._assertions += $1.to_i - res = Marshal.load($2.unpack1("m")) or next - rescue => marshal_error - ignore_stderr = nil - res = nil - else - next if SystemExit === res - if bt = res.backtrace - bt.each do |l| - l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} - end - bt.concat(caller) + begin + res = Marshal.load($2.unpack1("m")) or next + rescue => marshal_error + ignore_stderr = nil + res = nil else - res.set_backtrace(caller) + next if 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 - raise res end # really did it succeed? diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index f07d1b3840e5da..4e87e12bc8b19d 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -305,6 +305,10 @@ def lib((upstream, branch), gemspec_in_subdir: false) ["test/zlib", "test/zlib"], ["zlib.gemspec", "ext/zlib/zlib.gemspec"], ]), + "test-unit-ruby-core":repo("ruby/test-unit-ruby-core", [ + ["lib", "tool/lib"], + ["test", "tool/test"], + ]), }.transform_keys(&:to_s) def REPOSITORIES.[](gem) diff --git a/tool/test/test_core_assertions.rb b/tool/test/test_core_assertions.rb new file mode 100644 index 00000000000000..8293c8168142dd --- /dev/null +++ b/tool/test/test_core_assertions.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "test/unit" +require "core_assertions" + +class TestCoreAssertions < Test::Unit::TestCase + include Test::Unit::CoreAssertions + + def test_backtrace_filter_handles_missing_backtrace + assert_equal(["No backtrace"], Test.filter_backtrace(nil)) + end + + def test_backtrace_filter_removes_internal_entries + backtrace = [ + "/tmp/example.rb:1:in `run'", + "/tmp/lib/test/unit.rb:2:in `assert'", + ] + + assert_equal([backtrace.first], Test.filter_backtrace(backtrace)) + end + + def test_message_adds_sentence_endings + object = Object.new + object.extend(Test::Unit::Assertions) + + message = object.message("details") { "default" } + + assert_equal("details.\ndefault.", message.call) + end + + def test_assert_separately_runs_assertions_in_child_ruby + assert_separately([], <<~RUBY) + assert_equal(4, 2 + 2) + RUBY + end + + def test_assert_separately_propagates_child_failure + error = assert_raise(Test::Unit::AssertionFailedError) do + assert_separately([], <<~RUBY) + assert_equal(:expected, :actual) + RUBY + end + + assert_match(/expected/, error.message) + end +end diff --git a/tool/test/test_envutil.rb b/tool/test/test_envutil.rb new file mode 100644 index 00000000000000..806df6ab059e67 --- /dev/null +++ b/tool/test/test_envutil.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "test/unit" +require "envutil" + +class TestEnvUtil < Test::Unit::TestCase + def test_rubybin_points_to_a_ruby_executable + assert(File.executable?(EnvUtil.rubybin)) + end + + def test_apply_timeout_scale + original_scale = EnvUtil.timeout_scale + EnvUtil.timeout_scale = 2.5 + + assert_equal(5.0, EnvUtil.apply_timeout_scale(2)) + ensure + EnvUtil.timeout_scale = original_scale + end + + def test_invoke_ruby_captures_output_and_status + stdout, stderr, status = EnvUtil.invoke_ruby( + ["-e", "STDOUT.print('out'); STDERR.print('err')"], + "", true, true + ) + + assert_equal("out", stdout) + assert_equal("err", stderr) + assert_predicate(status, :success?) + end +end diff --git a/tool/test/test_find_executable.rb b/tool/test/test_find_executable.rb new file mode 100644 index 00000000000000..8ebaf85db24910 --- /dev/null +++ b/tool/test/test_find_executable.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "test/unit" +require "rbconfig" +require "find_executable" + +class TestFindExecutable < Test::Unit::TestCase + def test_find_executable_returns_command_and_arguments + ruby = RbConfig.ruby + command = File.basename(ruby, RbConfig::CONFIG["EXEEXT"]) + original_path = ENV["PATH"] + ENV["PATH"] = File.dirname(ruby) + File::PATH_SEPARATOR + original_path + + found = EnvUtil.find_executable(command, "--version") do |output| + output.start_with?("ruby ") + end + + assert_equal([ruby, "--version"], found) + ensure + ENV["PATH"] = original_path + end + + def test_find_executable_returns_nil_for_unknown_command + assert_nil(EnvUtil.find_executable("test-unit-ruby-core-missing") { true }) + end +end diff --git a/tool/test/test_memory_status.rb b/tool/test/test_memory_status.rb new file mode 100644 index 00000000000000..53830e9a238f35 --- /dev/null +++ b/tool/test/test_memory_status.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "test/unit" +require "memory_status" + +class TestMemoryStatus < Test::Unit::TestCase + def setup + omit("memory status is unsupported") unless defined?(Memory::Status) + end + + def test_status_reports_numeric_values + status = Memory::Status.new + + assert(status.members.any? { |member| status[member].to_i > 0 }) + assert_match(/\A\{[^}]+:\d+(?:,[^}]+:\d+)*\}\z/, status.to_s) + end + + def test_parse_round_trips_status + status = Memory::Status.new + + assert_equal(status, Memory::Status.parse(status.to_s)) + end +end