diff --git a/.rubocop.yml b/.rubocop.yml index 9b26c88..cf0fa3d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,6 +2,7 @@ inherit_gem: rubocop-shopify: rubocop.yml AllCops: + TargetRubyVersion: 2.6 SuggestExtensions: false Style/GlobalVars: diff --git a/lib/ruby_memcheck/rspec/rake_task.rb b/lib/ruby_memcheck/rspec/rake_task.rb new file mode 100644 index 0000000..5715d5d --- /dev/null +++ b/lib/ruby_memcheck/rspec/rake_task.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "rspec/core/rake_task" + +module RubyMemcheck + module RSpec + class RakeTask < ::RSpec::Core::RakeTask + include TestTaskReporter + + attr_reader :configuration + + def initialize(*args) + @configuration = + if !args.empty? && args[0].is_a?(Configuration) + args.shift + else + RubyMemcheck.default_configuration + end + + super + end + + def run_task(verbose) + error = nil + + begin + # RSpec::Core::RakeTask#run_task calls Kernel.exit on failure + super + rescue SystemExit => e + error = e + end + + report_valgrind_errors + + if error + raise error + end + end + + private + + def spec_command + # First part of command is Ruby + args = super.split(" ")[1..] + + configuration.command(args) + end + end + end +end diff --git a/ruby_memcheck.gemspec b/ruby_memcheck.gemspec index cd93bdf..adf2593 100644 --- a/ruby_memcheck.gemspec +++ b/ruby_memcheck.gemspec @@ -27,8 +27,10 @@ Gem::Specification.new do |spec| spec.add_dependency("nokogiri") spec.add_development_dependency("minitest", "~> 5.0") + spec.add_development_dependency("minitest-parallel_fork", "~> 1.2") spec.add_development_dependency("rake", "~> 13.0") spec.add_development_dependency("rake-compiler", "~> 1.1") + spec.add_development_dependency("rspec-core") spec.add_development_dependency("rubocop", "~> 1.22") spec.add_development_dependency("rubocop-shopify", "~> 2.3") end diff --git a/test/ruby_memcheck/rspec/rake_task_test.rb b/test/ruby_memcheck/rspec/rake_task_test.rb new file mode 100644 index 0000000..5454be2 --- /dev/null +++ b/test/ruby_memcheck/rspec/rake_task_test.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "ruby_memcheck" +require "ruby_memcheck/rspec/rake_task" +require "ruby_memcheck/shared_test_task_reporter_tests" + +module RubyMemcheck + module RSpec + class RakeTaskTest < Minitest::Test + include SharedTestTaskReporterTests + + def setup + @output_io = StringIO.new + build_configuration + end + + private + + def run_with_memcheck(code, raise_on_failure: true, spawn_opts: {}) + ok = true + stdout = nil + + Dir.chdir(Dir.mktmpdir) do |dir| + spec_dir = File.join(dir, "spec") + Dir.mkdir(spec_dir) + + stdout_log = Tempfile.new("", spec_dir) + + script = Tempfile.new(["", "_spec.rb"], spec_dir) + script.write(<<~RUBY) + # Redirect stdout to log file for RSpec output + $stdout.reopen(File.open("#{stdout_log.path}", "w")) + + $LOAD_PATH.unshift("#{File.join(__dir__, "../ext")}") + require "ruby_memcheck_c_test" + + RSpec.describe RubyMemcheck::CTest do + it "test" do + #{code} + end + end + RUBY + script.flush + + begin + @test_task.run_task(false) + rescue SystemExit + # RSpec::Core::RakeTask#run_task calls Kernel.exit on failure + ok = false + end + + # Get the stdout of RSpec + stdout = File.read(stdout_log.path) + + # Check RSpec test passed + unless /^1 example, 0 failures$/.match?(stdout) + ok = false + end + end + + if raise_on_failure && !ok + raise "Command failed. stdout:\n#{stdout}" + end + + ok + end + + def build_test_task + @test_task = RubyMemcheck::RSpec::RakeTask.new(@configuration) + end + end + end +end diff --git a/test/ruby_memcheck/shared_test_task_reporter_tests.rb b/test/ruby_memcheck/shared_test_task_reporter_tests.rb index 8d5022f..13c5808 100644 --- a/test/ruby_memcheck/shared_test_task_reporter_tests.rb +++ b/test/ruby_memcheck/shared_test_task_reporter_tests.rb @@ -5,7 +5,7 @@ module RubyMemcheck module SharedTestTaskReporterTests def test_succeeds_when_there_is_no_memory_leak - ok, _ = run_with_memcheck(<<~RUBY) + ok = run_with_memcheck(<<~RUBY) RubyMemcheck::CTest.new.no_memory_leak RUBY @@ -56,7 +56,7 @@ def test_does_not_report_uninitialized_value end def test_call_into_ruby_mem_leak_does_not_report - ok, _ = run_with_memcheck(<<~RUBY) + ok = run_with_memcheck(<<~RUBY) RubyMemcheck::CTest.new.call_into_ruby_mem_leak RUBY @@ -80,7 +80,7 @@ def test_call_into_ruby_mem_leak_reports_when_not_skipped def test_suppressions build_configuration(valgrind_suppressions_dir: File.join(__dir__, "suppressions")) - ok, _ = run_with_memcheck(<<~RUBY) + ok = run_with_memcheck(<<~RUBY) RubyMemcheck::CTest.new.memory_leak RUBY @@ -149,7 +149,7 @@ def test_reports_multiple_errors end def test_ruby_failure_without_errors - ok, _ = run_with_memcheck(<<~RUBY, raise_on_failure: false, spawn_opts: { out: "/dev/null", err: "/dev/null" }) + ok = run_with_memcheck(<<~RUBY, raise_on_failure: false, spawn_opts: { out: "/dev/null", err: "/dev/null" }) foobar RUBY @@ -176,6 +176,10 @@ def test_ruby_failure_with_errors private + def run_with_memcheck(code, raise_on_failure: true, spawn_opts: {}) + raise NotImplementedError + end + def build_configuration( binary_name: "ruby_memcheck_c_test", output_io: @output_io, @@ -186,7 +190,11 @@ def build_configuration( output_io: @output_io, **options ) - @test_task = RubyMemcheck::TestTask.new(@configuration) + build_test_task + end + + def build_test_task + raise NotImplementedError end end end diff --git a/test/ruby_memcheck/ruby_memcheck_test.rb b/test/ruby_memcheck/test_task_test.rb similarity index 72% rename from test/ruby_memcheck/ruby_memcheck_test.rb rename to test/ruby_memcheck/test_task_test.rb index 45a7009..11b471e 100644 --- a/test/ruby_memcheck/ruby_memcheck_test.rb +++ b/test/ruby_memcheck/test_task_test.rb @@ -3,7 +3,7 @@ require "ruby_memcheck/shared_test_task_reporter_tests" module RubyMemcheck - class RubyMemcheckTest < Minitest::Test + class TestTaskTest < Minitest::Test include SharedTestTaskReporterTests def setup @@ -17,26 +17,31 @@ def setup def run_with_memcheck(code, raise_on_failure: true, spawn_opts: {}) script = Tempfile.new - script.write("require 'ruby_memcheck_c_test'\n#{code}") + script.write(<<~RUBY) + require "ruby_memcheck_c_test" + #{code} + RUBY script.flush ok = nil - status = nil @test_task.ruby( "-I#{File.join(__dir__, "ext")}", script.path, **spawn_opts - ) do |ok_val, status_val| + ) do |ok_val, status| ok = ok_val - status = status_val if raise_on_failure && !ok raise "Command failed with status (#{status.exitstatus})" end end - [ok, status] + ok + end + + def build_test_task + @test_task = RubyMemcheck::TestTask.new(@configuration) end end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 9d8a8ca..0172c8c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -4,3 +4,9 @@ require "ruby_memcheck" require "minitest/autorun" + +if ENV["CI"] + require "etc" + ENV["NCPU"] ||= Etc.nprocessors.to_s + require "minitest/parallel_fork" +end