stackprof 0.2.10 → 0.2.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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