stackprof 0.2.10 → 0.2.25

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,31 +9,31 @@ class StackProf::MiddlewareTest < MiniTest::Test
9
9
  def test_path_default
10
10
  StackProf::Middleware.new(Object.new)
11
11
 
12
- assert_equal 'tmp', StackProf::Middleware.path
12
+ assert_equal 'tmp/', StackProf::Middleware.path
13
13
  end
14
14
 
15
15
  def test_path_custom
16
- StackProf::Middleware.new(Object.new, { path: '/foo' })
16
+ StackProf::Middleware.new(Object.new, { path: 'foo/' })
17
17
 
18
- assert_equal '/foo', StackProf::Middleware.path
18
+ assert_equal 'foo/', StackProf::Middleware.path
19
19
  end
20
20
 
21
21
  def test_save_default
22
22
  StackProf::Middleware.new(Object.new)
23
23
 
24
24
  StackProf.stubs(:results).returns({ mode: 'foo' })
25
- FileUtils.expects(:mkdir_p).with('tmp')
25
+ FileUtils.expects(:mkdir_p).with('tmp/')
26
26
  File.expects(:open).with(regexp_matches(/^tmp\/stackprof-foo/), 'wb')
27
27
 
28
28
  StackProf::Middleware.save
29
29
  end
30
30
 
31
31
  def test_save_custom
32
- StackProf::Middleware.new(Object.new, { path: '/foo' })
32
+ StackProf::Middleware.new(Object.new, { path: 'foo/' })
33
33
 
34
34
  StackProf.stubs(:results).returns({ mode: 'foo' })
35
- FileUtils.expects(:mkdir_p).with('/foo')
36
- File.expects(:open).with(regexp_matches(/^\/foo\/stackprof-foo/), 'wb')
35
+ FileUtils.expects(:mkdir_p).with('foo/')
36
+ File.expects(:open).with(regexp_matches(/^foo\/stackprof-foo/), 'wb')
37
37
 
38
38
  StackProf::Middleware.save
39
39
  end
@@ -64,4 +64,10 @@ class StackProf::MiddlewareTest < MiniTest::Test
64
64
  StackProf::Middleware.new(Object.new, raw: true)
65
65
  assert StackProf::Middleware.raw
66
66
  end
67
+
68
+ def test_metadata
69
+ metadata = { key: 'value' }
70
+ StackProf::Middleware.new(Object.new, metadata: metadata)
71
+ assert_equal metadata, StackProf::Middleware.metadata
72
+ end
67
73
  end
data/test/test_report.rb CHANGED
@@ -32,3 +32,27 @@ class ReportDumpTest < MiniTest::Test
32
32
  assert_equal expected, Marshal.load(marshal_data)
33
33
  end
34
34
  end
35
+
36
+ class ReportReadTest < MiniTest::Test
37
+ require 'pathname'
38
+
39
+ def test_from_file_read_json
40
+ file = fixture("profile.json")
41
+ report = StackProf::Report.from_file(file)
42
+
43
+ assert_equal({ mode: "cpu" }, report.data)
44
+ end
45
+
46
+ def test_from_file_read_marshal
47
+ file = fixture("profile.dump")
48
+ report = StackProf::Report.from_file(file)
49
+
50
+ assert_equal({ mode: "cpu" }, report.data)
51
+ end
52
+
53
+ private
54
+
55
+ def fixture(name)
56
+ Pathname.new(__dir__).join("fixtures", name)
57
+ end
58
+ end
@@ -2,11 +2,16 @@ $:.unshift File.expand_path('../../lib', __FILE__)
2
2
  require 'stackprof'
3
3
  require 'minitest/autorun'
4
4
  require 'tempfile'
5
+ require 'pathname'
5
6
 
6
7
  class StackProfTest < MiniTest::Test
8
+ def setup
9
+ Object.new # warm some caches to avoid flakiness
10
+ end
11
+
7
12
  def test_info
8
13
  profile = StackProf.run{}
9
- assert_equal 1.1, profile[:version]
14
+ assert_equal 1.2, profile[:version]
10
15
  assert_equal :wall, profile[:mode]
11
16
  assert_equal 1000, profile[:interval]
12
17
  assert_equal 0, profile[:samples]
@@ -18,16 +23,16 @@ class StackProfTest < MiniTest::Test
18
23
  end
19
24
 
20
25
  def test_start_stop_results
21
- assert_equal nil, StackProf.results
26
+ assert_nil StackProf.results
22
27
  assert_equal true, StackProf.start
23
28
  assert_equal false, StackProf.start
24
29
  assert_equal true, StackProf.running?
25
- assert_equal nil, StackProf.results
30
+ assert_nil StackProf.results
26
31
  assert_equal true, StackProf.stop
27
32
  assert_equal false, StackProf.stop
28
33
  assert_equal false, StackProf.running?
29
34
  assert_kind_of Hash, StackProf.results
30
- assert_equal nil, StackProf.results
35
+ assert_nil StackProf.results
31
36
  end
32
37
 
33
38
  def test_object_allocation
@@ -38,17 +43,30 @@ class StackProfTest < MiniTest::Test
38
43
  end
39
44
  assert_equal :object, profile[:mode]
40
45
  assert_equal 1, profile[:interval]
41
- assert_equal 2, profile[:samples]
46
+ if RUBY_VERSION >= '3'
47
+ assert_equal 4, profile[:samples]
48
+ else
49
+ assert_equal 2, profile[:samples]
50
+ end
42
51
 
43
52
  frame = profile[:frames].values.first
44
- assert_equal "block in StackProfTest#test_object_allocation", frame[:name]
53
+ assert_includes frame[:name], "StackProfTest#test_object_allocation"
45
54
  assert_equal 2, frame[:samples]
46
- assert_equal profile_base_line, frame[:line]
47
- assert_equal [1, 1], frame[:lines][profile_base_line+1]
48
- assert_equal [1, 1], frame[:lines][profile_base_line+2]
55
+ assert_includes [profile_base_line - 2, profile_base_line], frame[:line]
56
+ if RUBY_VERSION >= '3'
57
+ assert_equal [2, 1], frame[:lines][profile_base_line+1]
58
+ assert_equal [2, 1], frame[:lines][profile_base_line+2]
59
+ else
60
+ assert_equal [1, 1], frame[:lines][profile_base_line+1]
61
+ assert_equal [1, 1], frame[:lines][profile_base_line+2]
62
+ end
63
+ frame = profile[:frames].values[1] if RUBY_VERSION < '2.3'
49
64
 
50
- frame = profile[:frames].values[1]
51
- assert_equal [2, 0], frame[:lines][profile_base_line]
65
+ if RUBY_VERSION >= '3'
66
+ assert_equal [4, 0], frame[:lines][profile_base_line]
67
+ else
68
+ assert_equal [2, 0], frame[:lines][profile_base_line]
69
+ end
52
70
  end
53
71
 
54
72
  def test_object_allocation_interval
@@ -63,9 +81,15 @@ class StackProfTest < MiniTest::Test
63
81
  math
64
82
  end
65
83
 
66
- assert_operator profile[:samples], :>, 1
67
- frame = profile[:frames].values.first
68
- assert_equal "block in StackProfTest#math", frame[:name]
84
+ assert_operator profile[:samples], :>=, 1
85
+ if RUBY_VERSION >= '3'
86
+ assert profile[:frames].values.take(2).map { |f|
87
+ f[:name].include? "StackProfTest#math"
88
+ }.any?
89
+ else
90
+ frame = profile[:frames].values.first
91
+ assert_includes frame[:name], "StackProfTest#math"
92
+ end
69
93
  end
70
94
 
71
95
  def test_walltime
@@ -74,8 +98,12 @@ class StackProfTest < MiniTest::Test
74
98
  end
75
99
 
76
100
  frame = profile[:frames].values.first
77
- assert_equal "StackProfTest#idle", frame[:name]
78
- assert_in_delta 200, frame[:samples], 5
101
+ if RUBY_VERSION >= '3'
102
+ assert_equal "IO.select", frame[:name]
103
+ else
104
+ assert_equal "StackProfTest#idle", frame[:name]
105
+ end
106
+ assert_in_delta 200, frame[:samples], 25
79
107
  end
80
108
 
81
109
  def test_custom
@@ -89,23 +117,81 @@ class StackProfTest < MiniTest::Test
89
117
  assert_equal :custom, profile[:mode]
90
118
  assert_equal 10, profile[:samples]
91
119
 
92
- frame = profile[:frames].values.first
93
- assert_equal "block (2 levels) in StackProfTest#test_custom", frame[:name]
94
- assert_equal profile_base_line+1, frame[:line]
95
- assert_equal [10, 10], frame[:lines][profile_base_line+2]
120
+ offset = RUBY_VERSION >= '3' ? 1 : 0
121
+ frame = profile[:frames].values[offset]
122
+ assert_includes frame[:name], "StackProfTest#test_custom"
123
+ assert_includes [profile_base_line-2, profile_base_line+1], frame[:line]
124
+
125
+ if RUBY_VERSION >= '3'
126
+ assert_equal [10, 0], frame[:lines][profile_base_line+2]
127
+ else
128
+ assert_equal [10, 10], frame[:lines][profile_base_line+2]
129
+ end
96
130
  end
97
131
 
98
132
  def test_raw
133
+ before_monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
134
+
99
135
  profile = StackProf.run(mode: :custom, raw: true) do
100
136
  10.times do
101
137
  StackProf.sample
138
+ sleep 0.0001
102
139
  end
103
140
  end
104
141
 
142
+ after_monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
143
+
105
144
  raw = profile[:raw]
106
145
  assert_equal 10, raw[-1]
107
146
  assert_equal raw[0] + 2, raw.size
108
- assert_equal 'block (2 levels) in StackProfTest#test_raw', profile[:frames][raw[-2]][:name]
147
+
148
+ offset = RUBY_VERSION >= '3' ? -3 : -2
149
+ assert_includes profile[:frames][raw[offset]][:name], 'StackProfTest#test_raw'
150
+
151
+ assert_equal 10, profile[:raw_sample_timestamps].size
152
+ profile[:raw_sample_timestamps].each_cons(2) do |t1, t2|
153
+ assert_operator t1, :>, before_monotonic
154
+ assert_operator t2, :>=, t1
155
+ assert_operator t2, :<, after_monotonic
156
+ end
157
+
158
+ assert_equal 10, profile[:raw_timestamp_deltas].size
159
+ total_duration = after_monotonic - before_monotonic
160
+ assert_operator profile[:raw_timestamp_deltas].inject(&:+), :<, total_duration
161
+
162
+ profile[:raw_timestamp_deltas].each do |delta|
163
+ assert_operator delta, :>, 0
164
+ end
165
+ end
166
+
167
+ def test_metadata
168
+ metadata = {
169
+ path: '/foo/bar',
170
+ revision: '5c0b01f1522ae8c194510977ae29377296dd236b',
171
+ }
172
+ profile = StackProf.run(mode: :cpu, metadata: metadata) do
173
+ math
174
+ end
175
+
176
+ assert_equal metadata, profile[:metadata]
177
+ end
178
+
179
+ def test_empty_metadata
180
+ profile = StackProf.run(mode: :cpu) do
181
+ math
182
+ end
183
+
184
+ assert_equal({}, profile[:metadata])
185
+ end
186
+
187
+ def test_raises_if_metadata_is_not_a_hash
188
+ exception = assert_raises ArgumentError do
189
+ StackProf.run(mode: :cpu, metadata: 'foobar') do
190
+ math
191
+ end
192
+ end
193
+
194
+ assert_equal 'metadata should be a hash', exception.message
109
195
  end
110
196
 
111
197
  def test_fork
@@ -119,16 +205,47 @@ class StackProfTest < MiniTest::Test
119
205
  end
120
206
  end
121
207
 
208
+ def foo(n = 10)
209
+ if n == 0
210
+ StackProf.sample
211
+ return
212
+ end
213
+ foo(n - 1)
214
+ end
215
+
216
+ def test_recursive_total_samples
217
+ profile = StackProf.run(mode: :cpu, raw: true) do
218
+ 10.times do
219
+ foo
220
+ end
221
+ end
222
+
223
+ frame = profile[:frames].values.find do |frame|
224
+ frame[:name] == "StackProfTest#foo"
225
+ end
226
+ assert_equal 10, frame[:total_samples]
227
+ end
228
+
122
229
  def test_gc
123
- profile = StackProf.run(interval: 100) do
230
+ profile = StackProf.run(interval: 100, raw: true) do
124
231
  5.times do
125
232
  GC.start
126
233
  end
127
234
  end
128
235
 
129
- assert_empty profile[:frames]
236
+ gc_frame = profile[:frames].values.find{ |f| f[:name] == "(garbage collection)" }
237
+ marking_frame = profile[:frames].values.find{ |f| f[:name] == "(marking)" }
238
+ sweeping_frame = profile[:frames].values.find{ |f| f[:name] == "(sweeping)" }
239
+
240
+ assert gc_frame
241
+ assert marking_frame
242
+ assert sweeping_frame
243
+
244
+ assert_equal gc_frame[:total_samples], profile[:gc_samples]
245
+ assert_equal profile[:gc_samples], [gc_frame, marking_frame, sweeping_frame].map{|x| x[:samples] }.inject(:+)
246
+
130
247
  assert_operator profile[:gc_samples], :>, 0
131
- assert_equal 0, profile[:missed_samples]
248
+ assert_operator profile[:missed_samples], :<=, 25
132
249
  end
133
250
 
134
251
  def test_out
@@ -143,6 +260,41 @@ class StackProfTest < MiniTest::Test
143
260
  refute_empty profile[:frames]
144
261
  end
145
262
 
263
+ def test_out_to_path_string
264
+ tmpfile = Tempfile.new('stackprof-out')
265
+ ret = StackProf.run(mode: :custom, out: tmpfile.path) do
266
+ StackProf.sample
267
+ end
268
+
269
+ refute_equal tmpfile, ret
270
+ assert_equal tmpfile.path, ret.path
271
+ tmpfile.rewind
272
+ profile = Marshal.load(tmpfile.read)
273
+ refute_empty profile[:frames]
274
+ end
275
+
276
+ def test_pathname_out
277
+ tmpfile = Tempfile.new('stackprof-out')
278
+ pathname = Pathname.new(tmpfile.path)
279
+ ret = StackProf.run(mode: :custom, out: pathname) do
280
+ StackProf.sample
281
+ end
282
+
283
+ assert_equal tmpfile.path, ret.path
284
+ tmpfile.rewind
285
+ profile = Marshal.load(tmpfile.read)
286
+ refute_empty profile[:frames]
287
+ end
288
+
289
+ def test_min_max_interval
290
+ [-1, 0, 1_000_000, 1_000_001].each do |invalid_interval|
291
+ err = assert_raises(ArgumentError, "invalid interval #{invalid_interval}") do
292
+ StackProf.run(interval: invalid_interval, debug: true) {}
293
+ end
294
+ assert_match(/microseconds/, err.message)
295
+ end
296
+ end
297
+
146
298
  def math
147
299
  250_000.times do
148
300
  2 ** 10
@@ -156,4 +308,4 @@ class StackProfTest < MiniTest::Test
156
308
  r.close
157
309
  w.close
158
310
  end
159
- end
311
+ end unless RUBY_ENGINE == 'truffleruby'
@@ -0,0 +1,18 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'stackprof'
3
+ require 'minitest/autorun'
4
+
5
+ if RUBY_ENGINE == 'truffleruby'
6
+ class StackProfTruffleRubyTest < MiniTest::Test
7
+ def test_error
8
+ error = assert_raises RuntimeError do
9
+ StackProf.run(mode: :cpu) do
10
+ unreacheable
11
+ end
12
+ end
13
+
14
+ assert_match(/TruffleRuby/, error.message)
15
+ assert_match(/--cpusampler/, error.message)
16
+ end
17
+ end
18
+ end