summaryrefslogtreecommitdiff
path: root/test/dtrace
diff options
context:
space:
mode:
Diffstat (limited to 'test/dtrace')
-rw-r--r--test/dtrace/dummy.rb1
-rw-r--r--test/dtrace/helper.rb144
-rw-r--r--test/dtrace/test_array_create.rb9
-rw-r--r--test/dtrace/test_cmethod.rb1
-rw-r--r--test/dtrace/test_function_entry.rb10
-rw-r--r--test/dtrace/test_gc.rb1
-rw-r--r--test/dtrace/test_hash_create.rb7
-rw-r--r--test/dtrace/test_load.rb1
-rw-r--r--test/dtrace/test_method_cache.rb29
-rw-r--r--test/dtrace/test_object_create_start.rb1
-rw-r--r--test/dtrace/test_raise.rb1
-rw-r--r--test/dtrace/test_require.rb7
-rw-r--r--test/dtrace/test_singleton_function.rb10
-rw-r--r--test/dtrace/test_string.rb7
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] })
}