rotoscope 0.2.2 → 0.3.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4d1b991a9d63ea46762db53c24178d30ec63e361
4
- data.tar.gz: c3e44a17741d2b5e5bb90a490ac2c98d6dd87889
3
+ metadata.gz: 8a94009a6d6ee2a34d124e8b922feb3ff3eda7f2
4
+ data.tar.gz: eab31fe889c50ea034197aa1a435ccb22c832630
5
5
  SHA512:
6
- metadata.gz: be48429248b53ca6a9a9a1cf5cce8b130cb799419e557fe2da757b828a08ccfda64ecbc920109c40d301304b0d24a2811fd742f4f635160bd96b9c4b01b31121
7
- data.tar.gz: aee5f638d65fc3d167f5f5f6d2a5097866fffc11612f312809bebc3ad484928b36f135793c41e4948daae0b2f4b5208104edb25dcba0224affa2bc8758ec91ad
6
+ metadata.gz: 8ab381c724461c0b5dbab3a8dbbbf3e451c5aae060b8aecbaa9f28abc4d7aa636ecf7cd451b68537e104e13fc53c45243c47e4b44b48875f58bf1e32902a3f58
7
+ data.tar.gz: 5a598811ac79f0524d0e2d93d28a727a51e296cb575ed1de9b98e0f1b3921927b8557873e0bdcc682b8f7d09136f6d347c5d6eb2f8a7192bf39355a01e26f228
@@ -14,9 +14,7 @@
14
14
  #include "tracepoint.h"
15
15
 
16
16
  VALUE cRotoscope, cTracePoint;
17
-
18
- // recursive with singleton2str
19
- static rs_class_desc_t class2str(VALUE klass);
17
+ ID id_initialize;
20
18
 
21
19
  static unsigned long gettid() {
22
20
  return NUM2ULONG(rb_obj_id(rb_thread_current()));
@@ -47,46 +45,27 @@ static bool rejected_path(VALUE path, Rotoscope *config) {
47
45
  return false;
48
46
  }
49
47
 
50
- static VALUE class_of_singleton(VALUE klass) {
51
- return rb_iv_get(klass, "__attached__");
52
- }
53
-
54
- static bool is_class_singleton(VALUE klass) {
55
- VALUE obj = class_of_singleton(klass);
56
- return (RB_TYPE_P(obj, T_MODULE) || RB_TYPE_P(obj, T_CLASS));
57
- }
58
-
59
- static VALUE singleton2str(VALUE klass) {
60
- if (is_class_singleton(klass)) {
61
- VALUE obj = class_of_singleton(klass);
62
- VALUE cached_lookup = rb_class_path_cached(obj);
63
- VALUE name = (NIL_P(cached_lookup)) ? rb_class_name(obj) : cached_lookup;
64
- return name;
65
- } else // singleton of an instance
66
- {
67
- return singleton2str(CLASS_OF(klass));
48
+ static VALUE class_path(VALUE klass) {
49
+ VALUE cached_path = rb_class_path_cached(klass);
50
+ if (!NIL_P(cached_path)) {
51
+ return cached_path;
68
52
  }
53
+ return rb_class_path(klass);
69
54
  }
70
55
 
71
- static rs_class_desc_t class2str(VALUE klass) {
72
- rs_class_desc_t real_class;
73
- real_class.method_level = INSTANCE_METHOD;
56
+ static VALUE singleton_object(VALUE singleton_class) {
57
+ return rb_iv_get(singleton_class, "__attached__");
58
+ }
74
59
 
75
- VALUE cached_lookup = rb_class_path_cached(klass);
76
- if (RTEST(cached_lookup)) {
77
- real_class.name = cached_lookup;
78
- } else {
79
- if (FL_TEST(klass, FL_SINGLETON)) {
80
- real_class.name = singleton2str(klass);
81
- if (is_class_singleton(klass)) {
82
- real_class.method_level = CLASS_METHOD;
83
- }
84
- } else {
85
- real_class.name = rb_class_path(klass);
60
+ static VALUE class2str(VALUE klass) {
61
+ while (FL_TEST(klass, FL_SINGLETON)) {
62
+ klass = singleton_object(klass);
63
+ if (!RB_TYPE_P(klass, T_MODULE) && !RB_TYPE_P(klass, T_CLASS)) {
64
+ // singleton of an instance
65
+ klass = rb_obj_class(klass);
86
66
  }
87
67
  }
88
-
89
- return real_class;
68
+ return class_path(klass);
90
69
  }
91
70
 
92
71
  static rs_callsite_t tracearg_path(rb_trace_arg_t *trace_arg) {
@@ -101,19 +80,21 @@ static rs_callsite_t tracearg_path(rb_trace_arg_t *trace_arg) {
101
80
 
102
81
  static rs_class_desc_t tracearg_class(rb_trace_arg_t *trace_arg) {
103
82
  VALUE klass;
83
+ const char *method_level;
104
84
  VALUE self = rb_tracearg_self(trace_arg);
105
85
 
106
- if (RB_TYPE_P(self, T_MODULE) || RB_TYPE_P(self, T_OBJECT)) {
107
- klass = CLASS_OF(self);
108
- } else if (RB_TYPE_P(self, T_CLASS)) {
109
- // Does the object have an attached singleton?
110
- // If not, name based on self instead of its singleton
111
- klass = (FL_TEST(CLASS_OF(self), FL_SINGLETON)) ? CLASS_OF(self) : self;
86
+ if ((RB_TYPE_P(self, T_CLASS) || RB_TYPE_P(self, T_MODULE)) &&
87
+ SYM2ID(rb_tracearg_method_id(trace_arg)) != id_initialize) {
88
+ method_level = CLASS_METHOD;
89
+ klass = self;
112
90
  } else {
113
- klass = rb_tracearg_defined_class(trace_arg);
91
+ method_level = INSTANCE_METHOD;
92
+ klass = rb_obj_class(self);
114
93
  }
115
94
 
116
- return class2str(klass);
95
+ return (rs_class_desc_t){
96
+ .name = class2str(klass), .method_level = method_level,
97
+ };
117
98
  }
118
99
 
119
100
  static VALUE tracearg_method_name(rb_trace_arg_t *trace_arg) {
@@ -138,30 +119,24 @@ static rs_tracepoint_t extract_full_tracevals(rb_trace_arg_t *trace_arg,
138
119
 
139
120
  static bool in_fork(Rotoscope *config) { return config->pid != getpid(); }
140
121
 
141
- static bool tracecmp(rs_tracepoint_t *a, rs_tracepoint_t *b) {
142
- return (!rb_str_cmp(a->method_name, b->method_name) &&
143
- !rb_str_cmp(a->entity, b->entity) &&
144
- a->method_level == b->method_level);
145
- }
146
-
147
- static void log_raw_trace(FILE *stream, rs_tracepoint_t trace) {
122
+ static void log_trace_event(FILE *stream, rs_tracepoint_t *trace) {
148
123
  fprintf(stream, RS_CSV_FORMAT "\n", RS_CSV_VALUES(trace));
149
124
  }
150
125
 
151
- unsigned char output_buffer[500];
152
- static void log_stack_frame(FILE *stream, rs_stack_t *stack,
153
- rs_strmemo_t **call_memo, rs_tracepoint_t trace,
154
- rb_event_flag_t event) {
155
- if (event & EVENT_CALL) {
156
- rs_stack_frame_t frame = rs_stack_push(stack, trace);
157
- sprintf((char *)output_buffer, RS_FLATTENED_CSV_FORMAT "\n",
158
- RS_FLATTENED_CSV_VALUES(frame));
159
-
160
- if (rs_strmemo_uniq(call_memo, output_buffer)) {
161
- fputs((char *)output_buffer, stream);
162
- }
163
- } else if (event & EVENT_RETURN) {
164
- if (tracecmp(&trace, &rs_stack_peek(stack)->tp)) rs_stack_pop(stack);
126
+ unsigned char output_buffer[LOG_BUFFER_SIZE];
127
+ static void log_trace_event_with_caller(FILE *stream,
128
+ rs_stack_frame_t *stack_frame,
129
+ rs_strmemo_t **call_memo) {
130
+ rs_stack_frame_t *caller = stack_frame->caller;
131
+ while (caller->blacklisted) {
132
+ caller = caller->caller;
133
+ }
134
+
135
+ snprintf((char *)output_buffer, LOG_BUFFER_SIZE, RS_FLATTENED_CSV_FORMAT "\n",
136
+ RS_FLATTENED_CSV_VALUES(&stack_frame->tp, &caller->tp));
137
+
138
+ if (rs_strmemo_uniq(call_memo, output_buffer)) {
139
+ fputs((char *)output_buffer, stream);
165
140
  }
166
141
  }
167
142
 
@@ -176,19 +151,40 @@ static void event_hook(VALUE tpval, void *data) {
176
151
  }
177
152
 
178
153
  rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tpval);
179
- rs_callsite_t trace_path = tracearg_path(trace_arg);
154
+ rb_event_flag_t event_flag = rb_tracearg_event_flag(trace_arg);
180
155
 
181
- if (rejected_path(trace_path.filepath, config)) return;
156
+ rs_callsite_t trace_path;
157
+ bool blacklisted;
158
+ if (event_flag & EVENT_CALL) {
159
+ trace_path = tracearg_path(trace_arg);
160
+ blacklisted = rejected_path(trace_path.filepath, config);
161
+ } else {
162
+ if (rs_stack_empty(&config->stack)) return;
163
+ rs_stack_frame_t *call_frame = rs_stack_peek(&config->stack);
164
+ trace_path = (rs_callsite_t){
165
+ .filepath = call_frame->tp.filepath, .lineno = call_frame->tp.lineno,
166
+ };
167
+ blacklisted = call_frame->blacklisted;
168
+ }
182
169
 
183
170
  rs_tracepoint_t trace = extract_full_tracevals(trace_arg, &trace_path);
184
171
  if (!strcmp("Rotoscope", StringValueCStr(trace.entity))) return;
185
172
 
173
+ if (event_flag & EVENT_CALL) {
174
+ rs_stack_push(&config->stack, trace, blacklisted);
175
+ } else {
176
+ rs_stack_pop(&config->stack);
177
+ }
178
+
179
+ if (blacklisted) return;
180
+
186
181
  if (config->flatten_output) {
187
- rb_event_flag_t event_flag = rb_tracearg_event_flag(trace_arg);
188
- log_stack_frame(config->log, &config->stack, &config->call_memo, trace,
189
- event_flag);
182
+ if (event_flag & EVENT_CALL) {
183
+ log_trace_event_with_caller(config->log, rs_stack_peek(&config->stack),
184
+ &config->call_memo);
185
+ }
190
186
  } else {
191
- log_raw_trace(config->log, trace);
187
+ log_trace_event(config->log, &trace);
192
188
  }
193
189
  }
194
190
 
@@ -317,6 +313,7 @@ VALUE rotoscope_stop_trace(VALUE self) {
317
313
  if (rb_tracepoint_enabled_p(config->tracepoint)) {
318
314
  rb_tracepoint_disable(config->tracepoint);
319
315
  config->state = RS_OPEN;
316
+ rs_stack_reset(&config->stack, STACK_CAPACITY);
320
317
  }
321
318
 
322
319
  return Qnil;
@@ -373,6 +370,8 @@ VALUE rotoscope_state(VALUE self) {
373
370
  void Init_rotoscope(void) {
374
371
  cTracePoint = rb_const_get(rb_cObject, rb_intern("TracePoint"));
375
372
 
373
+ id_initialize = rb_intern("initialize");
374
+
376
375
  cRotoscope = rb_define_class("Rotoscope", rb_cObject);
377
376
  rb_define_alloc_func(cRotoscope, rs_alloc);
378
377
  rb_define_method(cRotoscope, "initialize", initialize, -1);
@@ -12,29 +12,30 @@
12
12
  #define INSTANCE_METHOD "instance"
13
13
 
14
14
  #define STACK_CAPACITY 500
15
+ #define LOG_BUFFER_SIZE 1000
15
16
 
16
17
  // clang-format off
17
18
  #define _RS_COMMON_CSV_HEADER "entity,method_name,method_level,filepath,lineno"
18
19
  #define _RS_COMMON_CSV_FORMAT "\"%s\",\"%s\",%s,\"%s\",%d"
19
20
  #define _RS_COMMON_CSV_VALUES(trace) \
20
- StringValueCStr(trace.entity), \
21
- StringValueCStr(trace.method_name), \
22
- trace.method_level, \
23
- StringValueCStr(trace.filepath), \
24
- trace.lineno
21
+ StringValueCStr((trace)->entity), \
22
+ StringValueCStr((trace)->method_name), \
23
+ (trace)->method_level, \
24
+ StringValueCStr((trace)->filepath), \
25
+ (trace)->lineno
25
26
 
26
27
  #define RS_CSV_HEADER "event," _RS_COMMON_CSV_HEADER
27
28
  #define RS_CSV_FORMAT "%s," _RS_COMMON_CSV_FORMAT
28
- #define RS_CSV_VALUES(trace) trace.event, _RS_COMMON_CSV_VALUES(trace)
29
+ #define RS_CSV_VALUES(trace) trace->event, _RS_COMMON_CSV_VALUES(trace)
29
30
 
30
31
  #define RS_FLATTENED_CSV_HEADER \
31
32
  _RS_COMMON_CSV_HEADER ",caller_entity,caller_method_name,caller_method_level"
32
33
  #define RS_FLATTENED_CSV_FORMAT _RS_COMMON_CSV_FORMAT ",\"%s\",\"%s\",%s"
33
- #define RS_FLATTENED_CSV_VALUES(frame) \
34
- _RS_COMMON_CSV_VALUES(frame.tp), \
35
- StringValueCStr(frame.caller->tp.entity), \
36
- StringValueCStr(frame.caller->tp.method_name), \
37
- frame.caller->tp.method_level
34
+ #define RS_FLATTENED_CSV_VALUES(trace, caller_trace) \
35
+ _RS_COMMON_CSV_VALUES(trace), \
36
+ StringValueCStr((caller_trace)->entity), \
37
+ StringValueCStr((caller_trace)->method_name), \
38
+ (caller_trace)->method_level
38
39
  // clang-format on
39
40
 
40
41
  typedef enum {
@@ -14,7 +14,7 @@ static void insert_root_node(rs_stack_t *stack) {
14
14
  .method_name = rb_unknown_str,
15
15
  .method_level = UNKNOWN_STR,
16
16
  .lineno = 0};
17
- rs_stack_push(stack, root_trace);
17
+ rs_stack_push(stack, root_trace, false);
18
18
  }
19
19
 
20
20
  static void resize_buffer(rs_stack_t *stack) {
@@ -31,15 +31,16 @@ bool rs_stack_full(rs_stack_t *stack) {
31
31
 
32
32
  bool rs_stack_empty(rs_stack_t *stack) { return stack->top < 0; }
33
33
 
34
- rs_stack_frame_t rs_stack_push(rs_stack_t *stack, rs_tracepoint_t trace) {
34
+ rs_stack_frame_t rs_stack_push(rs_stack_t *stack, rs_tracepoint_t trace,
35
+ bool blacklisted) {
35
36
  if (rs_stack_full(stack)) {
36
37
  resize_buffer(stack);
37
38
  }
38
39
 
39
40
  rs_stack_frame_t *caller =
40
41
  rs_stack_empty(stack) ? NULL : rs_stack_peek(stack);
41
- rs_stack_frame_t new_frame =
42
- (rs_stack_frame_t){.tp = trace, .caller = caller};
42
+ rs_stack_frame_t new_frame = (rs_stack_frame_t){
43
+ .tp = trace, .caller = caller, .blacklisted = blacklisted};
43
44
 
44
45
  stack->contents[++stack->top] = new_frame;
45
46
  return new_frame;
@@ -7,6 +7,7 @@
7
7
 
8
8
  typedef struct rs_stack_frame_t {
9
9
  struct rs_tracepoint_t tp;
10
+ bool blacklisted;
10
11
  struct rs_stack_frame_t *caller;
11
12
  } rs_stack_frame_t;
12
13
 
@@ -19,7 +20,7 @@ typedef struct {
19
20
  void rs_stack_init(rs_stack_t *stack, unsigned int capacity);
20
21
  void rs_stack_reset(rs_stack_t *stack, unsigned int capacity);
21
22
  void rs_stack_free(rs_stack_t *stack);
22
- rs_stack_frame_t rs_stack_push(rs_stack_t *stack, rs_tracepoint_t trace);
23
+ rs_stack_frame_t rs_stack_push(rs_stack_t *stack, rs_tracepoint_t trace, bool backlisted);
23
24
  bool rs_stack_empty(rs_stack_t *stack);
24
25
  bool rs_stack_full(rs_stack_t *stack);
25
26
  rs_stack_frame_t rs_stack_pop(rs_stack_t *stack);
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ class Rotoscope
3
+ VERSION = "0.3.0.pre.1"
4
+ end
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "rotoscope/version"
5
+
2
6
  Gem::Specification.new do |s|
3
7
  s.name = 'rotoscope'
4
- s.version = '0.2.2'
5
- s.date = '2017-09-16'
8
+ s.version = Rotoscope::VERSION
9
+ s.date = '2017-09-20'
6
10
 
7
- s.authors = ["Jahfer Husain"]
11
+ s.authors = ["Jahfer Husain", "Dylan Thacker-Smith"]
8
12
  s.email = 'jahfer.husain@shopify.com'
9
13
  s.homepage = 'https://github.com/shopify/rotoscope'
10
14
  s.license = 'MIT'
@@ -12,7 +16,9 @@ Gem::Specification.new do |s|
12
16
  s.summary = "Tracing Ruby"
13
17
  s.description = "High-performance logger of Ruby method invocations"
14
18
 
15
- s.files = `git ls-files`.split("\n")
19
+ s.files = `git ls-files -z`.split("\x0").reject do |f|
20
+ f.match(%r{^(test)/})
21
+ end
16
22
  s.required_ruby_version = ">= 2.2.0"
17
23
  s.extensions = %w(ext/rotoscope/extconf.rb)
18
24
 
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rotoscope
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0.pre.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jahfer Husain
8
+ - Dylan Thacker-Smith
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2017-09-16 00:00:00.000000000 Z
12
+ date: 2017-09-20 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: rake-compiler
@@ -96,12 +97,9 @@ files:
96
97
  - ext/rotoscope/tracepoint.c
97
98
  - ext/rotoscope/tracepoint.h
98
99
  - lib/rotoscope.rb
100
+ - lib/rotoscope/version.rb
99
101
  - lib/uthash/uthash.h
100
102
  - rotoscope.gemspec
101
- - test/fixture_inner.rb
102
- - test/fixture_outer.rb
103
- - test/monadify.rb
104
- - test/rotoscope_test.rb
105
103
  homepage: https://github.com/shopify/rotoscope
106
104
  licenses:
107
105
  - MIT
@@ -117,12 +115,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
117
115
  version: 2.2.0
118
116
  required_rubygems_version: !ruby/object:Gem::Requirement
119
117
  requirements:
120
- - - ">="
118
+ - - ">"
121
119
  - !ruby/object:Gem::Version
122
- version: '0'
120
+ version: 1.3.1
123
121
  requirements: []
124
122
  rubyforge_project:
125
- rubygems_version: 2.6.10
123
+ rubygems_version: 2.6.13
126
124
  signing_key:
127
125
  specification_version: 4
128
126
  summary: Tracing Ruby
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
- class FixtureInner
3
- def do_work
4
- raise unless sum == 2
5
- end
6
-
7
- def sum
8
- 1 + 1
9
- end
10
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
- class FixtureOuter
3
- def initialize
4
- @inner = FixtureInner.new
5
- end
6
-
7
- def do_work
8
- @inner.do_work
9
- end
10
- end
@@ -1,16 +0,0 @@
1
- module Monadify
2
- define_method("contents") do
3
- 42
4
- end
5
-
6
- def foo
7
- false
8
- end
9
-
10
- def monad(value)
11
- foo
12
- contents
13
- define_singleton_method("contents=") { |val| val }
14
- self.contents = value
15
- end
16
- end
@@ -1,482 +0,0 @@
1
- # frozen_string_literal: true
2
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
3
- $LOAD_PATH.unshift File.expand_path('../', __FILE__)
4
- require 'rotoscope'
5
- require 'minitest'
6
- require 'zlib'
7
- require 'fileutils'
8
- require 'csv'
9
-
10
- require 'fixture_inner'
11
- require 'fixture_outer'
12
- require 'monadify'
13
-
14
- module MyModule
15
- def module_method; end
16
- end
17
-
18
- module PrependedModule
19
- def prepended_method; end
20
- end
21
-
22
- class Example
23
- prepend PrependedModule
24
- include MyModule
25
- extend MyModule
26
- extend Monadify
27
-
28
- class << self
29
- def singleton_method
30
- true
31
- end
32
-
33
- def apply(val)
34
- monad val
35
- end
36
- end
37
-
38
- def normal_method
39
- true
40
- end
41
-
42
- def exception_method
43
- oops
44
- rescue
45
- nil
46
- end
47
-
48
- def yielding_method
49
- yield
50
- end
51
-
52
- private
53
-
54
- def oops
55
- raise "I've made a terrible mistake"
56
- end
57
- end
58
-
59
- ROOT_FIXTURE_PATH = File.expand_path('../', __FILE__)
60
- INNER_FIXTURE_PATH = File.expand_path('../fixture_inner.rb', __FILE__)
61
- OUTER_FIXTURE_PATH = File.expand_path('../fixture_outer.rb', __FILE__)
62
- MONADIFY_PATH = File.expand_path('monadify.rb', ROOT_FIXTURE_PATH)
63
-
64
- class RotoscopeTest < MiniTest::Test
65
- def setup
66
- @logfile = File.expand_path('tmp/test.csv')
67
- end
68
-
69
- def teardown
70
- FileUtils.remove_file(@logfile) if File.file?(@logfile)
71
- end
72
-
73
- def test_new
74
- rs = Rotoscope.new(@logfile, blacklist: ['tmp'], flatten: true)
75
- assert rs.is_a?(Rotoscope)
76
- end
77
-
78
- def test_close
79
- rs = Rotoscope.new(@logfile)
80
- assert rs.close
81
- end
82
-
83
- def test_closed?
84
- rs = Rotoscope.new(@logfile)
85
- refute_predicate rs, :closed?
86
- rs.close
87
- assert_predicate rs, :closed?
88
- end
89
-
90
- def test_state
91
- rs = Rotoscope.new(@logfile)
92
- assert_equal :open, rs.state
93
- rs.trace do
94
- assert_equal :tracing, rs.state
95
- end
96
- assert_equal :open, rs.state
97
- rs.close
98
- assert_equal :closed, rs.state
99
- end
100
-
101
- def test_mark
102
- contents = rotoscope_trace do |rs|
103
- Example.new.normal_method
104
- rs.mark
105
- end
106
-
107
- assert_includes contents.split("\n"), '--- '
108
- end
109
-
110
- def test_mark_with_custom_strings
111
- mark_strings = ["Hello", "ÅÉÎØÜ åéîøü"]
112
- contents = rotoscope_trace do |rs|
113
- e = Example.new
114
- e.normal_method
115
- mark_strings.each { |str| rs.mark(str) }
116
- end
117
-
118
- content_lines = contents.split("\n")
119
- mark_strings.each do |str|
120
- assert_includes content_lines, "--- #{str}"
121
- end
122
- end
123
-
124
- def test_flatten
125
- contents = rotoscope_trace(flatten: true) do
126
- Example.new.normal_method
127
- end
128
-
129
- assert_equal [
130
- { entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "<ROOT>", caller_method_name: "<UNKNOWN>", caller_method_level: "<UNKNOWN>" },
131
- { entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "Example", caller_method_name: "new", caller_method_level: "class" },
132
- { entity: "Example", method_name: "normal_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "<ROOT>", caller_method_name: "<UNKNOWN>", caller_method_level: "<UNKNOWN>" },
133
- ], parse_and_normalize(contents)
134
- end
135
-
136
- def test_flatten_removes_duplicates
137
- contents = rotoscope_trace(flatten: true) do
138
- e = Example.new
139
- 10.times { e.normal_method }
140
- end
141
-
142
- assert_equal [
143
- { entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "<ROOT>", caller_method_name: "<UNKNOWN>", caller_method_level: "<UNKNOWN>" },
144
- { entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "Example", caller_method_name: "new", caller_method_level: "class" },
145
- { entity: "Integer", method_name: "times", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "<ROOT>", caller_method_name: "<UNKNOWN>", caller_method_level: "<UNKNOWN>" },
146
- { entity: "Example", method_name: "normal_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "Integer", caller_method_name: "times", caller_method_level: "instance" },
147
- ], parse_and_normalize(contents)
148
- end
149
-
150
- def test_start_trace_and_stop_trace
151
- rs = Rotoscope.new(@logfile)
152
- rs.start_trace
153
- Example.new.normal_method
154
- rs.stop_trace
155
- rs.close
156
- contents = File.read(@logfile)
157
-
158
- assert_equal [
159
- { event: "call", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
160
- { event: "call", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
161
- { event: "return", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
162
- { event: "return", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
163
- { event: "call", entity: "Example", method_name: "normal_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
164
- { event: "return", entity: "Example", method_name: "normal_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
165
- ], parse_and_normalize(contents)
166
-
167
- assert_frames_consistent contents
168
- end
169
-
170
- def test_traces_instance_method
171
- contents = rotoscope_trace { Example.new.normal_method }
172
- assert_equal [
173
- { event: "call", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
174
- { event: "call", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
175
- { event: "return", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
176
- { event: "return", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
177
- { event: "call", entity: "Example", method_name: "normal_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
178
- { event: "return", entity: "Example", method_name: "normal_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 }
179
- ], parse_and_normalize(contents)
180
-
181
- assert_frames_consistent contents
182
- end
183
-
184
- def test_traces_yielding_method
185
- contents = rotoscope_trace do
186
- e = Example.new
187
- e.yielding_method { e.normal_method }
188
- end
189
-
190
- assert_equal [
191
- { event: "call", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
192
- { event: "call", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
193
- { event: "return", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
194
- { event: "return", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
195
- { event: "call", entity: "Example", method_name: "yielding_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
196
- { event: "call", entity: "Example", method_name: "normal_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
197
- { event: "return", entity: "Example", method_name: "normal_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
198
- { event: "return", entity: "Example", method_name: "yielding_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 }
199
- ], parse_and_normalize(contents)
200
-
201
- assert_frames_consistent contents
202
- end
203
-
204
- def test_calls_are_consistent_after_exception
205
- contents = rotoscope_trace { Example.new.exception_method }
206
- assert_frames_consistent contents
207
- end
208
-
209
- def test_traces_and_formats_singletons_of_a_class
210
- contents = rotoscope_trace { Example.singleton_method }
211
- assert_equal [
212
- { event: "call", entity: "Example", method_name: "singleton_method", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
213
- { event: "return", entity: "Example", method_name: "singleton_method", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 }
214
- ], parse_and_normalize(contents)
215
-
216
- assert_frames_consistent contents
217
- end
218
-
219
- def test_traces_and_formats_singletons_of_an_instance
220
- contents = rotoscope_trace { Example.new.singleton_class.singleton_method }
221
- assert_equal [
222
- { event: "call", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
223
- { event: "call", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
224
- { event: "return", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
225
- { event: "return", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
226
- { event: "call", entity: "Example", method_name: "singleton_class", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
227
- { event: "return", entity: "Example", method_name: "singleton_class", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
228
- { event: "call", entity: "Example", method_name: "singleton_method", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
229
- { event: "return", entity: "Example", method_name: "singleton_method", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
230
- ], parse_and_normalize(contents)
231
-
232
- assert_frames_consistent contents
233
- end
234
-
235
- def test_traces_included_module_method
236
- contents = rotoscope_trace { Example.new.module_method }
237
- assert_equal [
238
- { event: "call", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
239
- { event: "call", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
240
- { event: "return", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
241
- { event: "return", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
242
- { event: "call", entity: "Example", method_name: "module_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
243
- { event: "return", entity: "Example", method_name: "module_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 }
244
- ], parse_and_normalize(contents)
245
-
246
- assert_frames_consistent contents
247
- end
248
-
249
- def test_traces_extended_module_method
250
- contents = rotoscope_trace { Example.module_method }
251
- assert_equal [
252
- { event: "call", entity: "Example", method_name: "module_method", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
253
- { event: "return", entity: "Example", method_name: "module_method", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 }
254
- ], parse_and_normalize(contents)
255
-
256
- assert_frames_consistent contents
257
- end
258
-
259
- def test_traces_prepended_module_method
260
- contents = rotoscope_trace { Example.new.prepended_method }
261
- assert_equal [
262
- { event: "call", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
263
- { event: "call", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
264
- { event: "return", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
265
- { event: "return", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
266
- { event: "call", entity: "Example", method_name: "prepended_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
267
- { event: "return", entity: "Example", method_name: "prepended_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 }
268
- ], parse_and_normalize(contents)
269
-
270
- assert_frames_consistent contents
271
- end
272
-
273
- def test_trace_ignores_calls_if_blacklisted
274
- contents = rotoscope_trace(blacklist: [INNER_FIXTURE_PATH, OUTER_FIXTURE_PATH]) do
275
- foo = FixtureOuter.new
276
- foo.do_work
277
- end
278
-
279
- assert_equal [
280
- { event: "call", entity: "FixtureOuter", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
281
- { event: "call", entity: "FixtureOuter", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
282
- { event: "return", entity: "FixtureOuter", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
283
- { event: "return", entity: "FixtureOuter", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
284
- { event: "call", entity: "FixtureOuter", method_name: "do_work", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
285
- { event: "return", entity: "FixtureOuter", method_name: "do_work", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
286
- ], parse_and_normalize(contents)
287
-
288
- assert_frames_consistent contents
289
- end
290
-
291
- def test_trace_ignores_writes_in_fork
292
- contents = rotoscope_trace do |rotoscope|
293
- fork do
294
- Example.singleton_method
295
- rotoscope.mark
296
- rotoscope.close
297
- end
298
- Example.singleton_method
299
- Process.wait
300
- end
301
- assert_equal [
302
- { event: "call", entity: "RotoscopeTest", method_name: "fork", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
303
- { event: "return", entity: "RotoscopeTest", method_name: "fork", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
304
- { event: "call", entity: "Example", method_name: "singleton_method", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
305
- { event: "return", entity: "Example", method_name: "singleton_method", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
306
- { event: "call", entity: "Process", method_name: "wait", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
307
- { event: "return", entity: "Process", method_name: "wait", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
308
- ], parse_and_normalize(contents)
309
- end
310
-
311
- def test_trace_disabled_on_close
312
- contents = rotoscope_trace do |rotoscope|
313
- Example.singleton_method
314
- rotoscope.close
315
- rotoscope.mark
316
- Example.singleton_method
317
- end
318
- assert_equal [
319
- { event: "call", entity: "Example", method_name: "singleton_method", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
320
- { event: "return", entity: "Example", method_name: "singleton_method", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
321
- ], parse_and_normalize(contents)
322
- end
323
-
324
- def test_trace_flatten
325
- contents = rotoscope_trace(flatten: true) { Example.new.normal_method }
326
- assert_equal [
327
- { entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "<ROOT>", caller_method_name: "<UNKNOWN>", caller_method_level: "<UNKNOWN>" },
328
- { entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "Example", caller_method_name: "new", caller_method_level: "class" },
329
- { entity: "Example", method_name: "normal_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "<ROOT>", caller_method_name: "<UNKNOWN>", caller_method_level: "<UNKNOWN>" },
330
- ], parse_and_normalize(contents)
331
- end
332
-
333
- def test_trace_flatten_across_files
334
- contents = rotoscope_trace(flatten: true) do
335
- foo = FixtureOuter.new
336
- foo.do_work
337
- end
338
-
339
- assert_equal [
340
- { entity: "FixtureOuter", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "<ROOT>", caller_method_name: "<UNKNOWN>", caller_method_level: "<UNKNOWN>" },
341
- { entity: "FixtureOuter", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "FixtureOuter", caller_method_name: "new", caller_method_level: "class" },
342
- { entity: "FixtureInner", method_name: "new", method_level: "class", filepath: "/fixture_outer.rb", lineno: -1, caller_entity: "FixtureOuter", caller_method_name: "initialize", caller_method_level: "instance" },
343
- { entity: "FixtureInner", method_name: "initialize", method_level: "instance", filepath: "/fixture_outer.rb", lineno: -1, caller_entity: "FixtureInner", caller_method_name: "new", caller_method_level: "class" },
344
- { entity: "FixtureOuter", method_name: "do_work", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "<ROOT>", caller_method_name: "<UNKNOWN>", caller_method_level: "<UNKNOWN>" },
345
- { entity: "FixtureInner", method_name: "do_work", method_level: "instance", filepath: "/fixture_outer.rb", lineno: -1, caller_entity: "FixtureOuter", caller_method_name: "do_work", caller_method_level: "instance" },
346
- { entity: "FixtureInner", method_name: "sum", method_level: "instance", filepath: "/fixture_inner.rb", lineno: -1, caller_entity: "FixtureInner", caller_method_name: "do_work", caller_method_level: "instance" }
347
- ], parse_and_normalize(contents)
348
- end
349
-
350
- def test_trace_uses_io_objects
351
- string_io = StringIO.new
352
- Rotoscope.trace(string_io) do
353
- Example.new.normal_method
354
- end
355
- refute_predicate string_io, :closed?
356
- assert_predicate string_io, :eof?
357
- contents = string_io.string
358
-
359
- assert_equal [
360
- { event: "call", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
361
- { event: "call", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
362
- { event: "return", entity: "Example", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
363
- { event: "return", entity: "Example", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
364
- { event: "call", entity: "Example", method_name: "normal_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
365
- { event: "return", entity: "Example", method_name: "normal_method", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
366
- ], parse_and_normalize(contents)
367
-
368
- assert_frames_consistent contents
369
- end
370
-
371
- def test_stop_trace_before_start_does_not_raise
372
- rs = Rotoscope.new(@logfile)
373
- rs.stop_trace
374
- end
375
-
376
- def test_gc_rotoscope_without_stop_trace_does_not_crash
377
- proc {
378
- rs = Rotoscope.new(@logfile)
379
- rs.start_trace
380
- }.call
381
- GC.start
382
- end
383
-
384
- def test_gc_rotoscope_without_stop_trace_does_not_break_process_cleanup
385
- child_pid = fork do
386
- rs = Rotoscope.new(@logfile)
387
- rs.start_trace
388
- end
389
- Process.waitpid(child_pid)
390
- assert_equal true, $CHILD_STATUS.success?
391
- end
392
-
393
- def test_log_path
394
- rs = Rotoscope.new(File.expand_path('tmp/test.csv.gz'))
395
- GC.start
396
- assert_equal File.expand_path('tmp/test.csv.gz'), rs.log_path
397
- end
398
-
399
- def test_ignores_calls_inside_of_threads
400
- thread = nil
401
- contents = rotoscope_trace do
402
- thread = Thread.new { Example.new }
403
- end
404
- thread.join
405
-
406
- assert_equal [
407
- { event: "call", entity: "Thread", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
408
- { event: "call", entity: "Thread", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
409
- { event: "return", entity: "Thread", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
410
- { event: "return", entity: "Thread", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
411
- ], parse_and_normalize(contents)
412
- end
413
-
414
- def test_dynamic_class_creation
415
- contents = rotoscope_trace { Class.new }
416
-
417
- assert_equal [
418
- { event: "call", entity: "Class", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
419
- { event: "call", entity: "#<Class:0xXXXXXX>", method_name: "initialize", method_level: "instance", filepath: "/rotoscope_test.rb", lineno: -1 },
420
- { event: "call", entity: "Object", method_name: "inherited", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
421
- { event: "return", entity: "Object", method_name: "inherited", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
422
- { event: "return", entity: "#<Class:0xXXXXXX>", method_name: "initialize", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
423
- { event: "return", entity: "Class", method_name: "new", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 }
424
- ], parse_and_normalize(contents)
425
- end
426
-
427
- def test_dynamic_methods_in_blacklist
428
- skip <<-FAILING_TEST_CASE
429
- Return events for dynamically created methods (define_method, define_singleton_method)
430
- do not have the correct stack frame information (the call of a dynamically defined method
431
- is correctly treated as a Ruby :call, but its return must be treated as a :c_return)
432
- FAILING_TEST_CASE
433
-
434
- contents = rotoscope_trace(blacklist: [MONADIFY_PATH]) { Example.apply("my value!") }
435
-
436
- assert_equal [
437
- { event: "call", entity: "Example", method_name: "apply", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
438
- { event: "call", entity: "Example", method_name: "monad", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
439
- { event: "return", entity: "Example", method_name: "monad", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
440
- { event: "return", entity: "Example", method_name: "apply", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1 },
441
- ], parse_and_normalize(contents)
442
- end
443
-
444
- def test_flatten_with_dynamic_methods_in_blacklist
445
- # the failing test above passes when using `flatten: true` since unmatched stack returns are ignored
446
- contents = rotoscope_trace(blacklist: [MONADIFY_PATH], flatten: true) { Example.apply("my value!") }
447
-
448
- assert_equal [
449
- { entity: "Example", method_name: "apply", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "<ROOT>", caller_method_name: "<UNKNOWN>", caller_method_level: "<UNKNOWN>" },
450
- { entity: "Example", method_name: "monad", method_level: "class", filepath: "/rotoscope_test.rb", lineno: -1, caller_entity: "Example", caller_method_name: "apply", caller_method_level: "class" },
451
- ], parse_and_normalize(contents)
452
- end
453
-
454
- private
455
-
456
- def parse_and_normalize(csv_string)
457
- CSV.parse(csv_string, headers: true, header_converters: :symbol).map do |row|
458
- row = row.to_h
459
- row[:lineno] = -1
460
- row[:filepath] = row[:filepath].gsub(ROOT_FIXTURE_PATH, '')
461
- row[:entity] = row[:entity].gsub(/:0x[a-fA-F0-9]{4,}/m, ":0xXXXXXX")
462
- row
463
- end
464
- end
465
-
466
- def assert_frames_consistent(csv_string)
467
- assert_equal csv_string.scan(/\Acall/).size, csv_string.scan(/\Areturn/).size
468
- end
469
-
470
- def rotoscope_trace(blacklist: [], flatten: false)
471
- Rotoscope.trace(@logfile, blacklist: blacklist, flatten: flatten) { |rotoscope| yield rotoscope }
472
- File.read(@logfile)
473
- end
474
-
475
- def unzip(path)
476
- File.open(path) { |f| Zlib::GzipReader.new(f).read }
477
- end
478
- end
479
-
480
- # https://github.com/seattlerb/minitest/pull/683 needed to use
481
- # autorun without affecting the exit status of forked processes
482
- Minitest.run(ARGV)