diff options
Diffstat (limited to 'test/dtrace')
| -rw-r--r-- | test/dtrace/dummy.rb | 1 | ||||
| -rw-r--r-- | test/dtrace/helper.rb | 144 | ||||
| -rw-r--r-- | test/dtrace/test_array_create.rb | 9 | ||||
| -rw-r--r-- | test/dtrace/test_cmethod.rb | 1 | ||||
| -rw-r--r-- | test/dtrace/test_function_entry.rb | 10 | ||||
| -rw-r--r-- | test/dtrace/test_gc.rb | 1 | ||||
| -rw-r--r-- | test/dtrace/test_hash_create.rb | 7 | ||||
| -rw-r--r-- | test/dtrace/test_load.rb | 1 | ||||
| -rw-r--r-- | test/dtrace/test_method_cache.rb | 29 | ||||
| -rw-r--r-- | test/dtrace/test_object_create_start.rb | 1 | ||||
| -rw-r--r-- | test/dtrace/test_raise.rb | 1 | ||||
| -rw-r--r-- | test/dtrace/test_require.rb | 7 | ||||
| -rw-r--r-- | test/dtrace/test_singleton_function.rb | 10 | ||||
| -rw-r--r-- | test/dtrace/test_string.rb | 7 |
14 files changed, 196 insertions, 33 deletions
diff --git a/test/dtrace/dummy.rb b/test/dtrace/dummy.rb index e85614228c..932cb4e625 100644 --- a/test/dtrace/dummy.rb +++ b/test/dtrace/dummy.rb @@ -1 +1,2 @@ +# frozen_string_literal: false # this is a dummy file used by test/dtrace/test_require.rb diff --git a/test/dtrace/helper.rb b/test/dtrace/helper.rb index a462851dbf..9e8c7ecd52 100644 --- a/test/dtrace/helper.rb +++ b/test/dtrace/helper.rb @@ -1,47 +1,161 @@ # -*- coding: us-ascii -*- -require 'minitest/autorun' +# frozen_string_literal: false +require 'test/unit' require 'tempfile' -require_relative '../ruby/envutil' if Process.euid == 0 ok = true -elsif (sudo = ENV["SUDO"]) and (`#{sudo} echo ok` rescue false) +elsif (sudo = ENV["SUDO"]) and !sudo.empty? and (`#{sudo} echo ok` rescue false) ok = true else ok = false end -ok &= (`dtrace -V` rescue false) + +impl = :dtrace + +# GNU/Linux distros with Systemtap support allows unprivileged users +# in the stapusr and statdev groups to work. +if RUBY_PLATFORM =~ /linux/ + impl = :stap + begin + require 'etc' + ok = (%w[stapusr stapdev].map {|g|(Etc.getgrnam(g) || raise(ArgumentError)).gid} & Process.groups).size == 2 + rescue LoadError, ArgumentError + end unless ok +end + +if ok + case RUBY_PLATFORM + when /darwin/i + begin + require 'pty' + rescue LoadError + end + end +end + +# use miniruby to reduce the amount of trace data we don't care about +rubybin = "miniruby#{RbConfig::CONFIG["EXEEXT"]}" +rubybin = File.join(File.dirname(EnvUtil.rubybin), rubybin) +rubybin = EnvUtil.rubybin unless File.executable?(rubybin) + +# make sure ruby was built with --enable-dtrace and we can run +# dtrace(1) or stap(1): +cmd = "#{rubybin} --disable=gems -eexit" +case impl +when :dtrace; cmd = %W(dtrace -l -n ruby$target:::gc-sweep-end -c #{cmd}) +when :stap; cmd = %W(stap -l process.mark("gc__sweep__end") -c #{cmd}) +else + warn "don't know how to check if built with #{impl} support" + cmd = false +end + +NEEDED_ENVS = [RbConfig::CONFIG["LIBPATHENV"], "RUBY", "RUBYOPT"].compact + +if cmd and ok + sudocmd = [] + if sudo + sudocmd << sudo + NEEDED_ENVS.each {|name| val = ENV[name] and sudocmd << "#{name}=#{val}"} + end + ok = system(*sudocmd, *cmd, err: IO::NULL, out: IO::NULL) +end + module DTrace - class TestCase < MiniTest::Unit::TestCase + class TestCase < Test::Unit::TestCase INCLUDE = File.expand_path('..', File.dirname(__FILE__)) + if RUBY_PLATFORM =~ /darwin/i + READ_PROBES = proc do |cmd| + lines = nil + PTY.spawn(*cmd) do |io, _, pid| + lines = io.readlines.each {|line| line.sub!(/\r$/, "")} + Process.wait(pid) + end + lines + end if defined?(PTY) + end + + # only handles simple cases, use a Hash for d_program + # if there are more complex cases + def dtrace2systemtap(d_program) + translate = lambda do |str| + # dtrace starts args with '0', systemtap with '1' and prefixes '$' + str = str.gsub(/\barg(\d+)/) { "$arg#{$1.to_i + 1}" } + # simple function mappings: + str.gsub!(/\bcopyinstr\b/, 'user_string') + str.gsub!(/\bstrstr\b/, 'isinstr') + str + end + out = '' + cond = nil + d_program.split(/^/).each do |l| + case l + when /\bruby\$target:::([a-z-]+)/ + name = $1.gsub(/-/, '__') + out << %Q{probe process.mark("#{name}")\n} + when %r{/(.+)/} + cond = translate.call($1) + when "{\n" + out << l + out << "if (#{cond}) {\n" if cond + when "}\n" + out << "}\n" if cond + out << l + else + out << translate.call(l) + end + end + out + end + + DTRACE_CMD ||= %w[dtrace] + + READ_PROBES ||= proc do |cmd| + IO.popen(cmd, err: [:child, :out], &:readlines) + end + def trap_probe d_program, ruby_program - d = Tempfile.new('probe.d') + if Hash === d_program + d_program = d_program[IMPL] or + omit "#{d_program} not implemented for #{IMPL}" + elsif String === d_program && IMPL == :stap + d_program = dtrace2systemtap(d_program) + end + d = Tempfile.new(%w'probe .d') d.write d_program d.flush - rb = Tempfile.new('probed.rb') + rb = Tempfile.new(%w'probed .rb') rb.write ruby_program rb.flush d_path = d.path rb_path = rb.path - - cmd = ["dtrace", "-q", "-s", d_path, "-c", "#{EnvUtil.rubybin} -I#{INCLUDE} #{rb_path}"] - if sudo = ENV["SUDO"] - [RbConfig::CONFIG["LIBPATHENV"], "RUBY", "RUBYOPT"].each do |name| - if name and val = ENV[name] + cmd = "#{RUBYBIN} --disable=gems -I#{INCLUDE} #{rb_path}" + if IMPL == :stap + cmd = %W(stap #{d_path} -c #{cmd}) + else + cmd = [*DTRACE_CMD, "-q", "-s", d_path, "-c", cmd ] + end + if sudo = @@sudo + NEEDED_ENVS.each do |name| + if val = ENV[name] cmd.unshift("#{name}=#{val}") end end cmd.unshift(sudo) end - probes = IO.popen(cmd) do |io| - io.readlines - end + probes = READ_PROBES.(cmd) d.close(true) rb.close(true) yield(d_path, rb_path, probes) end end end if ok + +if ok + DTrace::TestCase.class_variable_set(:@@sudo, sudo) + DTrace::TestCase.const_set(:IMPL, impl) + DTrace::TestCase.const_set(:RUBYBIN, rubybin) +end diff --git a/test/dtrace/test_array_create.rb b/test/dtrace/test_array_create.rb index d849bcc0c7..1bf20085ba 100644 --- a/test/dtrace/test_array_create.rb +++ b/test/dtrace/test_array_create.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative 'helper' module DTrace @@ -13,11 +14,11 @@ module DTrace end def test_many_lit - trap_probe(probe, '[1,2,3,4]') { |_,rbfile,saw| - saw = saw.map(&:split).find_all { |num, file, line| + trap_probe(probe, '[1,2,3,4]') { |_,rbfile,orig| + saw = orig.map(&:split).find_all { |num, file, line| file == rbfile && num == '4' && line == '1' } - assert_operator saw.length, :>, 0 + assert_operator saw.length, :>, 0, orig } end @@ -25,7 +26,7 @@ module DTrace def probe type = 'array' <<-eoprobe ruby$target:::#{type}-create -/arg1/ +/arg1 && arg2/ { printf("%d %s %d\\n", arg0, copyinstr(arg1), arg2); } diff --git a/test/dtrace/test_cmethod.rb b/test/dtrace/test_cmethod.rb index 0a9107fa38..a0aa1b646b 100644 --- a/test/dtrace/test_cmethod.rb +++ b/test/dtrace/test_cmethod.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative 'helper' module DTrace diff --git a/test/dtrace/test_function_entry.rb b/test/dtrace/test_function_entry.rb index 74aee64b02..e2395ab15a 100644 --- a/test/dtrace/test_function_entry.rb +++ b/test/dtrace/test_function_entry.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative 'helper' module DTrace @@ -16,8 +17,8 @@ ruby$target:::method-entry row.first == 'Foo' && row[1] == 'foo' } - assert_equal 10, foo_calls.length - line = '2' + assert_equal 10, foo_calls.length, probes + line = '3' foo_calls.each { |f| assert_equal line, f[3] } foo_calls.each { |f| assert_equal rb_file, f[2] } } @@ -37,8 +38,8 @@ ruby$target:::method-return row.first == 'Foo' && row[1] == 'foo' } - assert_equal 10, foo_calls.length - line = '2' + assert_equal 10, foo_calls.length, probes.inspect + line = '3' foo_calls.each { |f| assert_equal line, f[3] } foo_calls.each { |f| assert_equal rb_file, f[2] } } @@ -76,6 +77,7 @@ ruby$target:::method-return private def ruby_program <<-eoruby + TracePoint.new{}.__enable(nil, nil, Thread.current) class Foo def foo; end end diff --git a/test/dtrace/test_gc.rb b/test/dtrace/test_gc.rb index 2f58a11096..77de7998dd 100644 --- a/test/dtrace/test_gc.rb +++ b/test/dtrace/test_gc.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative 'helper' module DTrace diff --git a/test/dtrace/test_hash_create.rb b/test/dtrace/test_hash_create.rb index 2cceded38f..603ee21872 100644 --- a/test/dtrace/test_hash_create.rb +++ b/test/dtrace/test_hash_create.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative 'helper' module DTrace @@ -21,11 +22,11 @@ module DTrace end def test_hash_lit_elements - trap_probe(probe, '{ :foo => :bar }') { |_,rbfile,saw| - saw = saw.map(&:split).find_all { |num, file, line| + trap_probe(probe, '{ :foo => :bar }') { |_,rbfile,orig| + saw = orig.map(&:split).find_all { |num, file, line| file == rbfile && num == '2' } - assert_operator saw.length, :>, 0 + assert_operator saw.length, :>, 0, orig } end diff --git a/test/dtrace/test_load.rb b/test/dtrace/test_load.rb index cceb0c2925..1c208bd488 100644 --- a/test/dtrace/test_load.rb +++ b/test/dtrace/test_load.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative 'helper' require 'tempfile' diff --git a/test/dtrace/test_method_cache.rb b/test/dtrace/test_method_cache.rb new file mode 100644 index 0000000000..88b927464a --- /dev/null +++ b/test/dtrace/test_method_cache.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: false +require_relative 'helper' + +module DTrace + class TestMethodCacheClear < TestCase + def test_method_cache_clear + trap_probe(probe, <<-code) do |_,rbfile,lines| + class String; end + class String; def abc() end end + class Object; def abc() end end + code + assert_not_include lines, "String #{rbfile} 1\n" + assert_include lines, "String #{rbfile} 2\n" + assert_include lines, "global #{rbfile} 3\n" + end + end + + private + def probe + <<-eoprobe +ruby$target:::method-cache-clear +/arg1 && arg2/ +{ + printf("%s %s %d\\n", copyinstr(arg0), copyinstr(arg1), arg2); +} + eoprobe + end + end +end if defined?(DTrace::TestCase) diff --git a/test/dtrace/test_object_create_start.rb b/test/dtrace/test_object_create_start.rb index 2be9611613..81f8ed2110 100644 --- a/test/dtrace/test_object_create_start.rb +++ b/test/dtrace/test_object_create_start.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative 'helper' module DTrace diff --git a/test/dtrace/test_raise.rb b/test/dtrace/test_raise.rb index 48fdbf14d1..81e64e8c16 100644 --- a/test/dtrace/test_raise.rb +++ b/test/dtrace/test_raise.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative 'helper' module DTrace diff --git a/test/dtrace/test_require.rb b/test/dtrace/test_require.rb index 46a1d7652a..da5c08f7fc 100644 --- a/test/dtrace/test_require.rb +++ b/test/dtrace/test_require.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative 'helper' module DTrace @@ -24,6 +25,12 @@ ruby$target:::require-return printf("%s\\n", copyinstr(arg0)); } eoprobe + trap_probe(probe, ruby_program) { |d_file, rb_file, saw| + required = saw.map { |s| s.split }.find_all do |(required, _)| + required == 'dtrace/dummy' + end + assert_equal 10, required.length + } end private diff --git a/test/dtrace/test_singleton_function.rb b/test/dtrace/test_singleton_function.rb index 9e118f65b7..bad1fa0692 100644 --- a/test/dtrace/test_singleton_function.rb +++ b/test/dtrace/test_singleton_function.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative 'helper' module DTrace @@ -16,8 +17,8 @@ ruby$target:::method-entry row.first == 'Foo' && row[1] == 'foo' } - assert_equal 10, foo_calls.length - line = '2' + assert_equal 10, foo_calls.length, probes.inspect + line = '3' foo_calls.each { |f| assert_equal line, f[3] } foo_calls.each { |f| assert_equal rb_file, f[2] } } @@ -36,8 +37,8 @@ ruby$target:::method-return row.first == 'Foo' && row[1] == 'foo' } - assert_equal 10, foo_calls.length - line = '2' + assert_equal 10, foo_calls.length, probes.inspect + line = '3' foo_calls.each { |f| assert_equal line, f[3] } foo_calls.each { |f| assert_equal rb_file, f[2] } } @@ -45,6 +46,7 @@ ruby$target:::method-return def ruby_program <<-eoruby + TracePoint.new{}.__enable(nil, nil, Thread.current) class Foo def self.foo; end end diff --git a/test/dtrace/test_string.rb b/test/dtrace/test_string.rb index 873d5ac364..a72f989f63 100644 --- a/test/dtrace/test_string.rb +++ b/test/dtrace/test_string.rb @@ -1,13 +1,14 @@ +# frozen_string_literal: false require_relative 'helper' module DTrace class TestStringProbes < TestCase def test_object_create_start_string_lit - trap_probe(probe, '"omglolwutbbq"') { |_,rbfile,saw| - saw = saw.map(&:split).find_all { |klass, file, line, len| + trap_probe(probe, '"omglolwutbbq"') { |_,rbfile,orig| + saw = orig.map(&:split).find_all { |klass, file, line, len| file == rbfile && len == '12' && line == '1' } - assert_equal(%w{ String }, saw.map(&:first)) + assert_equal(%w{ String }, saw.map(&:first), orig.inspect) assert_equal([rbfile], saw.map { |line| line[1] }) assert_equal(['1'], saw.map { |line| line[2] }) } |
