rotoscope 0.2.2 → 0.3.0.pre.1

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.
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)