katsuya-rcov 0.9.7.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.
- data/BLURB +111 -0
- data/LICENSE +53 -0
- data/Rakefile +103 -0
- data/THANKS +110 -0
- data/bin/rcov +514 -0
- data/doc/readme_for_api.markdown +22 -0
- data/doc/readme_for_emacs.markdown +52 -0
- data/doc/readme_for_rake.markdown +51 -0
- data/doc/readme_for_vim.markdown +34 -0
- data/editor-extensions/rcov.el +131 -0
- data/editor-extensions/rcov.vim +38 -0
- data/ext/rcovrt/1.8/callsite.c +216 -0
- data/ext/rcovrt/1.8/rcovrt.c +294 -0
- data/ext/rcovrt/1.9/callsite.c +234 -0
- data/ext/rcovrt/1.9/rcovrt.c +264 -0
- data/ext/rcovrt/extconf.rb +21 -0
- data/lib/rcov.rb +33 -0
- data/lib/rcov/call_site_analyzer.rb +225 -0
- data/lib/rcov/code_coverage_analyzer.rb +271 -0
- data/lib/rcov/coverage_info.rb +36 -0
- data/lib/rcov/differential_analyzer.rb +116 -0
- data/lib/rcov/file_statistics.rb +355 -0
- data/lib/rcov/formatters.rb +13 -0
- data/lib/rcov/formatters/base_formatter.rb +174 -0
- data/lib/rcov/formatters/failure_report.rb +15 -0
- data/lib/rcov/formatters/full_text_report.rb +48 -0
- data/lib/rcov/formatters/html_coverage.rb +274 -0
- data/lib/rcov/formatters/html_erb_template.rb +62 -0
- data/lib/rcov/formatters/text_coverage_diff.rb +193 -0
- data/lib/rcov/formatters/text_report.rb +32 -0
- data/lib/rcov/formatters/text_summary.rb +11 -0
- data/lib/rcov/lowlevel.rb +146 -0
- data/lib/rcov/rcovtask.rb +155 -0
- data/lib/rcov/templates/detail.html.erb +64 -0
- data/lib/rcov/templates/index.html.erb +93 -0
- data/lib/rcov/templates/jquery-1.3.2.min.js +19 -0
- data/lib/rcov/templates/jquery.tablesorter.min.js +15 -0
- data/lib/rcov/templates/print.css +12 -0
- data/lib/rcov/templates/rcov.js +42 -0
- data/lib/rcov/templates/screen.css +270 -0
- data/lib/rcov/version.rb +10 -0
- data/setup.rb +1588 -0
- data/test/assets/sample_01.rb +7 -0
- data/test/assets/sample_02.rb +5 -0
- data/test/assets/sample_03.rb +20 -0
- data/test/assets/sample_04.rb +10 -0
- data/test/assets/sample_05-new.rb +17 -0
- data/test/assets/sample_05-old.rb +13 -0
- data/test/assets/sample_05.rb +17 -0
- data/test/assets/sample_06.rb +8 -0
- data/test/call_site_analyzer_test.rb +171 -0
- data/test/code_coverage_analyzer_test.rb +219 -0
- data/test/expected_coverage/diff-gcc-all.out +7 -0
- data/test/expected_coverage/diff-gcc-diff.out +11 -0
- data/test/expected_coverage/diff-gcc-original.out +5 -0
- data/test/expected_coverage/diff-no-color.out +12 -0
- data/test/expected_coverage/diff.out +12 -0
- data/test/expected_coverage/gcc-text.out +10 -0
- data/test/expected_coverage/sample_03_rb.html +651 -0
- data/test/expected_coverage/sample_03_rb.rb +28 -0
- data/test/expected_coverage/sample_04_rb.html +641 -0
- data/test/file_statistics_test.rb +471 -0
- data/test/functional_test.rb +91 -0
- data/test/test_helper.rb +4 -0
- data/test/turn_off_rcovrt.rb +4 -0
- metadata +126 -0
| @@ -0,0 +1,294 @@ | |
| 1 | 
            +
            #include <ruby.h>
         | 
| 2 | 
            +
            #include <env.h>
         | 
| 3 | 
            +
            #include <node.h>
         | 
| 4 | 
            +
            #include <st.h>
         | 
| 5 | 
            +
            #include <stdlib.h>
         | 
| 6 | 
            +
            #include <assert.h>
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            #define COVERAGE_DEBUG_EVENTS 0
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            #define RCOVRT_VERSION_MAJOR 2
         | 
| 11 | 
            +
            #define RCOVRT_VERSION_MINOR 0
         | 
| 12 | 
            +
            #define RCOVRT_VERSION_REV   0
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            static VALUE mRcov;
         | 
| 15 | 
            +
            static VALUE mRCOV__;
         | 
| 16 | 
            +
            static VALUE oSCRIPT_LINES__;
         | 
| 17 | 
            +
            static ID id_cover;
         | 
| 18 | 
            +
            static st_table* coverinfo = 0;
         | 
| 19 | 
            +
            static char coverage_hook_set_p;
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            struct cov_array {
         | 
| 22 | 
            +
              unsigned int len;
         | 
| 23 | 
            +
              unsigned int *ptr;
         | 
| 24 | 
            +
            };
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            static struct cov_array *cached_array = 0;
         | 
| 27 | 
            +
            static char *cached_file = 0; 
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            static struct cov_array * coverage_increase_counter_uncached(char *sourcefile, unsigned int sourceline, char mark_only) {
         | 
| 30 | 
            +
              struct cov_array *carray = NULL;
         | 
| 31 | 
            +
             
         | 
| 32 | 
            +
              if(sourcefile == NULL) {
         | 
| 33 | 
            +
                /* "can't happen", just ignore and avoid segfault */
         | 
| 34 | 
            +
                return NULL;
         | 
| 35 | 
            +
              } 
         | 
| 36 | 
            +
              else if(!st_lookup(coverinfo, (st_data_t)sourcefile, (st_data_t*)&carray)) {
         | 
| 37 | 
            +
                VALUE arr;
         | 
| 38 | 
            +
                
         | 
| 39 | 
            +
                arr = rb_hash_aref(oSCRIPT_LINES__, rb_str_new2(sourcefile));
         | 
| 40 | 
            +
                if(NIL_P(arr)) 
         | 
| 41 | 
            +
                  return 0;
         | 
| 42 | 
            +
                rb_check_type(arr, T_ARRAY);
         | 
| 43 | 
            +
                carray = calloc(1, sizeof(struct cov_array));
         | 
| 44 | 
            +
                carray->ptr = calloc(RARRAY(arr)->len, sizeof(unsigned int));
         | 
| 45 | 
            +
                carray->len = RARRAY(arr)->len;
         | 
| 46 | 
            +
                st_insert(coverinfo, (st_data_t)strdup(sourcefile), (st_data_t) carray);
         | 
| 47 | 
            +
              } 
         | 
| 48 | 
            +
              else {
         | 
| 49 | 
            +
                /* recovered carray, sanity check */
         | 
| 50 | 
            +
                assert(carray && "failed to create valid carray");
         | 
| 51 | 
            +
              }
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              if(mark_only) {
         | 
| 54 | 
            +
                if (carray && carray->len > sourceline) {
         | 
| 55 | 
            +
                  if(!carray->ptr[sourceline])
         | 
| 56 | 
            +
                    carray->ptr[sourceline] = 1;
         | 
| 57 | 
            +
                } else {
         | 
| 58 | 
            +
                  #if COVERAGE_DEBUG_EVENTS
         | 
| 59 | 
            +
                    printf("DEBUG: %s carray->len:%d sourceline:%d\n",
         | 
| 60 | 
            +
                           sourcefile, carray->len, sourceline);
         | 
| 61 | 
            +
                  #endif
         | 
| 62 | 
            +
                }
         | 
| 63 | 
            +
              } else {
         | 
| 64 | 
            +
                if (carray && carray->len > sourceline) {
         | 
| 65 | 
            +
                  carray->ptr[sourceline]++;
         | 
| 66 | 
            +
                }
         | 
| 67 | 
            +
              }
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              return carray;
         | 
| 70 | 
            +
            }
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            static void coverage_mark_caller() {
         | 
| 73 | 
            +
              struct FRAME *frame = ruby_frame;
         | 
| 74 | 
            +
              NODE *n;
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              if (frame->last_func == ID_ALLOCATOR) {
         | 
| 77 | 
            +
                frame = frame->prev;
         | 
| 78 | 
            +
              }
         | 
| 79 | 
            +
              for (; frame && (n = frame->node); frame = frame->prev) {
         | 
| 80 | 
            +
                if (frame->prev && frame->prev->last_func) {
         | 
| 81 | 
            +
                  if (frame->prev->node == n) {
         | 
| 82 | 
            +
                    if (frame->prev->last_func == frame->last_func) continue;
         | 
| 83 | 
            +
                  }
         | 
| 84 | 
            +
                  coverage_increase_counter_uncached(n->nd_file, nd_line(n) - 1, 1);
         | 
| 85 | 
            +
                }
         | 
| 86 | 
            +
                else {
         | 
| 87 | 
            +
                  coverage_increase_counter_uncached(n->nd_file, nd_line(n) - 1, 1);
         | 
| 88 | 
            +
                }
         | 
| 89 | 
            +
                break;
         | 
| 90 | 
            +
              }
         | 
| 91 | 
            +
            }
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            static void coverage_increase_counter_cached(char *sourcefile, int sourceline) {
         | 
| 94 | 
            +
              if(cached_file == sourcefile && cached_array && cached_array->len > sourceline) {
         | 
| 95 | 
            +
                cached_array->ptr[sourceline]++;
         | 
| 96 | 
            +
                return;
         | 
| 97 | 
            +
              }
         | 
| 98 | 
            +
              cached_file = sourcefile;
         | 
| 99 | 
            +
              cached_array = coverage_increase_counter_uncached(sourcefile, sourceline, 0);
         | 
| 100 | 
            +
            }
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            static void coverage_event_coverage_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass) {
         | 
| 103 | 
            +
              char *sourcefile;
         | 
| 104 | 
            +
              unsigned int sourceline;
         | 
| 105 | 
            +
              static unsigned int in_hook = 0;
         | 
| 106 | 
            +
             
         | 
| 107 | 
            +
              if(in_hook) {
         | 
| 108 | 
            +
                return;
         | 
| 109 | 
            +
              }
         | 
| 110 | 
            +
             | 
| 111 | 
            +
              in_hook++;
         | 
| 112 | 
            +
             | 
| 113 | 
            +
              #if COVERAGE_DEBUG_EVENTS
         | 
| 114 | 
            +
                do {
         | 
| 115 | 
            +
                  int status;
         | 
| 116 | 
            +
                  VALUE old_exception;
         | 
| 117 | 
            +
                  old_exception = rb_gv_get("$!");
         | 
| 118 | 
            +
                  rb_protect(rb_inspect, klass, &status);
         | 
| 119 | 
            +
                  if(!status) {
         | 
| 120 | 
            +
                    printf("EVENT: %d %s %s %s %d\n", event,
         | 
| 121 | 
            +
                           klass ? RSTRING(rb_inspect(klass))->ptr : "", 
         | 
| 122 | 
            +
                           mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
         | 
| 123 | 
            +
                           : "unknown",
         | 
| 124 | 
            +
                           node ? node->nd_file : "", node ? nd_line(node) : 0);
         | 
| 125 | 
            +
                  } 
         | 
| 126 | 
            +
                  else {
         | 
| 127 | 
            +
                    printf("EVENT: %d %s %s %d\n", event,
         | 
| 128 | 
            +
                           mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid)) 
         | 
| 129 | 
            +
                           : "unknown",
         | 
| 130 | 
            +
                           node ? node->nd_file : "", node ? nd_line(node) : 0);
         | 
| 131 | 
            +
                  }
         | 
| 132 | 
            +
                  
         | 
| 133 | 
            +
                  rb_gv_set("$!", old_exception);
         | 
| 134 | 
            +
                } while (0); 
         | 
| 135 | 
            +
              #endif
         | 
| 136 | 
            +
             | 
| 137 | 
            +
              if(event & RUBY_EVENT_C_CALL) {
         | 
| 138 | 
            +
                coverage_mark_caller();
         | 
| 139 | 
            +
              }
         | 
| 140 | 
            +
              
         | 
| 141 | 
            +
              if(event & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN | RUBY_EVENT_CLASS)) {
         | 
| 142 | 
            +
                in_hook--;
         | 
| 143 | 
            +
                return;
         | 
| 144 | 
            +
              }
         | 
| 145 | 
            +
              
         | 
| 146 | 
            +
              if(node == NULL) {
         | 
| 147 | 
            +
                in_hook--;
         | 
| 148 | 
            +
                  return;
         | 
| 149 | 
            +
              }
         | 
| 150 | 
            +
              
         | 
| 151 | 
            +
              sourcefile = node->nd_file;
         | 
| 152 | 
            +
              sourceline = nd_line(node) - 1;
         | 
| 153 | 
            +
              
         | 
| 154 | 
            +
              coverage_increase_counter_cached(sourcefile, sourceline);
         | 
| 155 | 
            +
              
         | 
| 156 | 
            +
              if(event & RUBY_EVENT_CALL)
         | 
| 157 | 
            +
                coverage_mark_caller();
         | 
| 158 | 
            +
              in_hook--;
         | 
| 159 | 
            +
            }
         | 
| 160 | 
            +
             | 
| 161 | 
            +
            static VALUE cov_install_coverage_hook(VALUE self) {
         | 
| 162 | 
            +
              if(!coverage_hook_set_p) {
         | 
| 163 | 
            +
                if(!coverinfo)
         | 
| 164 | 
            +
                  coverinfo = st_init_strtable();
         | 
| 165 | 
            +
                coverage_hook_set_p = 1;
         | 
| 166 | 
            +
                /* TODO: allow C_CALL too, since it's supported already
         | 
| 167 | 
            +
                 * the overhead is around ~30%, tested on typo */
         | 
| 168 | 
            +
                rb_add_event_hook(coverage_event_coverage_hook, RUBY_EVENT_ALL & ~RUBY_EVENT_C_CALL & ~RUBY_EVENT_C_RETURN & ~RUBY_EVENT_CLASS);
         | 
| 169 | 
            +
                
         | 
| 170 | 
            +
                return Qtrue;
         | 
| 171 | 
            +
              }
         | 
| 172 | 
            +
              else
         | 
| 173 | 
            +
                return Qfalse;
         | 
| 174 | 
            +
            }
         | 
| 175 | 
            +
             | 
| 176 | 
            +
            static int populate_cover(st_data_t key, st_data_t value, st_data_t cover) {
         | 
| 177 | 
            +
              VALUE rcover;
         | 
| 178 | 
            +
              VALUE rkey;
         | 
| 179 | 
            +
              VALUE rval;
         | 
| 180 | 
            +
              struct cov_array *carray;
         | 
| 181 | 
            +
              unsigned int i;
         | 
| 182 | 
            +
             
         | 
| 183 | 
            +
              rcover = (VALUE)cover;
         | 
| 184 | 
            +
              carray = (struct cov_array *) value;
         | 
| 185 | 
            +
              rkey = rb_str_new2((char*) key);
         | 
| 186 | 
            +
              rval = rb_ary_new2(carray->len);
         | 
| 187 | 
            +
             | 
| 188 | 
            +
              for(i = 0; i < carray->len; i++)
         | 
| 189 | 
            +
                RARRAY(rval)->ptr[i] = UINT2NUM(carray->ptr[i]);
         | 
| 190 | 
            +
             | 
| 191 | 
            +
              RARRAY(rval)->len = carray->len;
         | 
| 192 | 
            +
              rb_hash_aset(rcover, rkey, rval);
         | 
| 193 | 
            +
              
         | 
| 194 | 
            +
              return ST_CONTINUE;
         | 
| 195 | 
            +
            }
         | 
| 196 | 
            +
             | 
| 197 | 
            +
            static int free_table(st_data_t key, st_data_t value, st_data_t ignored) {
         | 
| 198 | 
            +
              struct cov_array *carray;
         | 
| 199 | 
            +
             
         | 
| 200 | 
            +
              carray = (struct cov_array *) value;
         | 
| 201 | 
            +
              free((char *)key);
         | 
| 202 | 
            +
              free(carray->ptr);
         | 
| 203 | 
            +
              free(carray);
         | 
| 204 | 
            +
             | 
| 205 | 
            +
              return ST_CONTINUE;
         | 
| 206 | 
            +
            }
         | 
| 207 | 
            +
             | 
| 208 | 
            +
            static VALUE cov_remove_coverage_hook(VALUE self) {
         | 
| 209 | 
            +
              if(!coverage_hook_set_p) 
         | 
| 210 | 
            +
                return Qfalse;
         | 
| 211 | 
            +
              else {
         | 
| 212 | 
            +
                rb_remove_event_hook(coverage_event_coverage_hook);
         | 
| 213 | 
            +
                coverage_hook_set_p = 0;
         | 
| 214 | 
            +
                return Qtrue;
         | 
| 215 | 
            +
              }
         | 
| 216 | 
            +
            }
         | 
| 217 | 
            +
             | 
| 218 | 
            +
            static VALUE cov_generate_coverage_info(VALUE self) {
         | 
| 219 | 
            +
              VALUE cover;
         | 
| 220 | 
            +
             | 
| 221 | 
            +
              if(rb_const_defined_at(mRCOV__, id_cover)) {
         | 
| 222 | 
            +
                rb_mod_remove_const(mRCOV__, ID2SYM(id_cover));
         | 
| 223 | 
            +
              }
         | 
| 224 | 
            +
             | 
| 225 | 
            +
              cover = rb_hash_new();
         | 
| 226 | 
            +
             | 
| 227 | 
            +
              if(coverinfo)
         | 
| 228 | 
            +
                st_foreach(coverinfo, populate_cover, cover);
         | 
| 229 | 
            +
             | 
| 230 | 
            +
              rb_define_const(mRCOV__, "COVER", cover);
         | 
| 231 | 
            +
             | 
| 232 | 
            +
              return cover;
         | 
| 233 | 
            +
            }
         | 
| 234 | 
            +
             | 
| 235 | 
            +
            static VALUE cov_reset_coverage(VALUE self) {
         | 
| 236 | 
            +
              if(coverage_hook_set_p) {
         | 
| 237 | 
            +
                rb_raise(rb_eRuntimeError, "Cannot reset the coverage info in the middle of a traced run.");
         | 
| 238 | 
            +
                return Qnil;
         | 
| 239 | 
            +
              }
         | 
| 240 | 
            +
             | 
| 241 | 
            +
              cached_array = 0;
         | 
| 242 | 
            +
              cached_file = 0;
         | 
| 243 | 
            +
              st_foreach(coverinfo, free_table, Qnil); 
         | 
| 244 | 
            +
              st_free_table(coverinfo);
         | 
| 245 | 
            +
              coverinfo = 0;
         | 
| 246 | 
            +
             | 
| 247 | 
            +
              return Qnil;
         | 
| 248 | 
            +
            }
         | 
| 249 | 
            +
             | 
| 250 | 
            +
            static VALUE cov_ABI(VALUE self) {
         | 
| 251 | 
            +
              VALUE ret;
         | 
| 252 | 
            +
              
         | 
| 253 | 
            +
              ret = rb_ary_new();
         | 
| 254 | 
            +
              rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_MAJOR));
         | 
| 255 | 
            +
              rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_MINOR));
         | 
| 256 | 
            +
              rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_REV));
         | 
| 257 | 
            +
              
         | 
| 258 | 
            +
              return ret;
         | 
| 259 | 
            +
            }
         | 
| 260 | 
            +
             | 
| 261 | 
            +
            void Init_rcovrt() {
         | 
| 262 | 
            +
              ID id_rcov = rb_intern("Rcov");
         | 
| 263 | 
            +
              ID id_coverage__ = rb_intern("RCOV__");
         | 
| 264 | 
            +
              ID id_script_lines__ = rb_intern("SCRIPT_LINES__");
         | 
| 265 | 
            +
             
         | 
| 266 | 
            +
              id_cover = rb_intern("COVER");
         | 
| 267 | 
            +
              
         | 
| 268 | 
            +
              if(rb_const_defined(rb_cObject, id_rcov)) 
         | 
| 269 | 
            +
                mRcov = rb_const_get(rb_cObject, id_rcov);
         | 
| 270 | 
            +
              else
         | 
| 271 | 
            +
                mRcov = rb_define_module("Rcov");
         | 
| 272 | 
            +
              
         | 
| 273 | 
            +
              if(rb_const_defined(mRcov, id_coverage__))
         | 
| 274 | 
            +
                mRCOV__ = rb_const_get_at(mRcov, id_coverage__);
         | 
| 275 | 
            +
              else
         | 
| 276 | 
            +
                mRCOV__ = rb_define_module_under(mRcov, "RCOV__");
         | 
| 277 | 
            +
              
         | 
| 278 | 
            +
              if(rb_const_defined(rb_cObject, id_script_lines__))
         | 
| 279 | 
            +
                oSCRIPT_LINES__ = rb_const_get(rb_cObject, rb_intern("SCRIPT_LINES__"));
         | 
| 280 | 
            +
              else {
         | 
| 281 | 
            +
                oSCRIPT_LINES__ = rb_hash_new();
         | 
| 282 | 
            +
                rb_const_set(rb_cObject, id_script_lines__, oSCRIPT_LINES__);
         | 
| 283 | 
            +
              }
         | 
| 284 | 
            +
              
         | 
| 285 | 
            +
              coverage_hook_set_p = 0;
         | 
| 286 | 
            +
              
         | 
| 287 | 
            +
              rb_define_singleton_method(mRCOV__, "install_coverage_hook", cov_install_coverage_hook, 0);
         | 
| 288 | 
            +
              rb_define_singleton_method(mRCOV__, "remove_coverage_hook",  cov_remove_coverage_hook, 0);
         | 
| 289 | 
            +
              rb_define_singleton_method(mRCOV__, "generate_coverage_info", cov_generate_coverage_info, 0);
         | 
| 290 | 
            +
              rb_define_singleton_method(mRCOV__, "reset_coverage", cov_reset_coverage, 0);
         | 
| 291 | 
            +
              rb_define_singleton_method(mRCOV__, "ABI", cov_ABI, 0);
         | 
| 292 | 
            +
              
         | 
| 293 | 
            +
              Init_rcov_callsite();
         | 
| 294 | 
            +
            }
         | 
| @@ -0,0 +1,234 @@ | |
| 1 | 
            +
            #include <ruby.h>
         | 
| 2 | 
            +
            #include <ruby/st.h>
         | 
| 3 | 
            +
            #include <stdlib.h>
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            #define DEBUG 0
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            static char callsite_hook_set_p;
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            typedef struct {
         | 
| 10 | 
            +
              const char *sourcefile;
         | 
| 11 | 
            +
              unsigned int sourceline;
         | 
| 12 | 
            +
              VALUE curr_meth;
         | 
| 13 | 
            +
            } type_def_site;       
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            static VALUE caller_info = 0;
         | 
| 16 | 
            +
            static VALUE method_def_site_info = 0;
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            static int caller_stack_len = 1;
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            static VALUE record_callsite_info(VALUE args) {
         | 
| 21 | 
            +
              VALUE caller_ary;
         | 
| 22 | 
            +
              VALUE curr_meth;
         | 
| 23 | 
            +
              VALUE count_hash;
         | 
| 24 | 
            +
              VALUE count;
         | 
| 25 | 
            +
              VALUE *pargs = (VALUE *)args;
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              caller_ary = pargs[0];
         | 
| 28 | 
            +
              curr_meth = pargs[1];
         | 
| 29 | 
            +
              count_hash = rb_hash_aref(caller_info, curr_meth);
         | 
| 30 | 
            +
              
         | 
| 31 | 
            +
              if(TYPE(count_hash) != T_HASH) { 
         | 
| 32 | 
            +
                /* Qnil, anything else should be impossible unless somebody's been
         | 
| 33 | 
            +
                 * messing with ObjectSpace */
         | 
| 34 | 
            +
                count_hash = rb_hash_new();
         | 
| 35 | 
            +
                rb_hash_aset(caller_info, curr_meth, count_hash);
         | 
| 36 | 
            +
              }
         | 
| 37 | 
            +
              
         | 
| 38 | 
            +
              count = rb_hash_aref(count_hash, caller_ary);
         | 
| 39 | 
            +
              
         | 
| 40 | 
            +
              if(count == Qnil) 
         | 
| 41 | 
            +
                count = INT2FIX(0);
         | 
| 42 | 
            +
              
         | 
| 43 | 
            +
              count = INT2FIX(FIX2UINT(count) + 1);
         | 
| 44 | 
            +
              rb_hash_aset(count_hash, caller_ary, count);
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              if(DEBUG == 1)
         | 
| 47 | 
            +
                printf("CALLSITE: %s -> %s   %d\n", RSTRING_PTR(rb_inspect(curr_meth)), RSTRING_PTR(rb_inspect(caller_ary)), FIX2INT(count));
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              return Qnil;
         | 
| 50 | 
            +
            }
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            static VALUE record_method_def_site(VALUE args) {
         | 
| 53 | 
            +
              type_def_site *pargs = (type_def_site *)args;
         | 
| 54 | 
            +
              VALUE def_site_info;
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              if( RTEST(rb_hash_aref(method_def_site_info, pargs->curr_meth)) )
         | 
| 57 | 
            +
                return Qnil;
         | 
| 58 | 
            +
              
         | 
| 59 | 
            +
              def_site_info = rb_ary_new();
         | 
| 60 | 
            +
              rb_ary_push(def_site_info, rb_str_new2(pargs->sourcefile));
         | 
| 61 | 
            +
              rb_ary_push(def_site_info, INT2NUM(pargs->sourceline+1));
         | 
| 62 | 
            +
              rb_hash_aset(method_def_site_info, pargs->curr_meth, def_site_info);
         | 
| 63 | 
            +
             | 
| 64 | 
            +
              if(DEBUG == 1)
         | 
| 65 | 
            +
                printf("DEFSITE: %s:%d  for %s\n", pargs->sourcefile, pargs->sourceline+1, RSTRING_PTR(rb_inspect(pargs->curr_meth)));
         | 
| 66 | 
            +
              
         | 
| 67 | 
            +
              return Qnil;
         | 
| 68 | 
            +
            }
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            static VALUE callsite_custom_backtrace(int lev) {
         | 
| 71 | 
            +
              ID id;
         | 
| 72 | 
            +
              VALUE klass;
         | 
| 73 | 
            +
              VALUE klass_path;
         | 
| 74 | 
            +
              VALUE eval_string;
         | 
| 75 | 
            +
              
         | 
| 76 | 
            +
              rb_frame_method_id_and_class(&id, &klass);
         | 
| 77 | 
            +
              
         | 
| 78 | 
            +
              if (id == ID_ALLOCATOR)
         | 
| 79 | 
            +
                return Qnil;
         | 
| 80 | 
            +
              
         | 
| 81 | 
            +
              if (klass) {
         | 
| 82 | 
            +
                if (TYPE(klass) == T_ICLASS) {
         | 
| 83 | 
            +
                  klass = RBASIC(klass)->klass;
         | 
| 84 | 
            +
                }
         | 
| 85 | 
            +
                else if (FL_TEST(klass, FL_SINGLETON)) {
         | 
| 86 | 
            +
                  klass = rb_iv_get(klass, "__attached__");
         | 
| 87 | 
            +
                }
         | 
| 88 | 
            +
              }
         | 
| 89 | 
            +
              // rb_sprintf("\"#<Class:%s>\"", RSTRING_PTR(klass_path))
         | 
| 90 | 
            +
              
         | 
| 91 | 
            +
              /*
         | 
| 92 | 
            +
              klass = class << klass; self end unless klass === eval("self", binding)
         | 
| 93 | 
            +
              */
         | 
| 94 | 
            +
              
         | 
| 95 | 
            +
              klass_path = rb_class_path(klass);
         | 
| 96 | 
            +
              VALUE reciever = rb_funcall(rb_binding_new(), rb_intern("eval"), 1, rb_str_new2("self"));
         | 
| 97 | 
            +
              
         | 
| 98 | 
            +
              if (rb_funcall(klass, rb_intern("=="), 1, reciever) == Qtrue) {
         | 
| 99 | 
            +
                klass_path = rb_sprintf("\"#<Class:%s>\"", RSTRING_PTR(klass_path));
         | 
| 100 | 
            +
                OBJ_FREEZE(klass_path);
         | 
| 101 | 
            +
              }
         | 
| 102 | 
            +
              
         | 
| 103 | 
            +
              eval_string = rb_sprintf("caller[%d, 1].map do |line|\nmd = /^([^:]*)(?::(\\d+)(?::in `(?:block in )?(.*)'))?/.match(line)\nraise \"Bad backtrace format\" unless md\n[%s, md[3] ? md[3].to_sym : nil, md[1], (md[2] || '').to_i]\nend", lev, RSTRING_PTR(klass_path));
         | 
| 104 | 
            +
              return rb_eval_string(RSTRING_PTR(eval_string));
         | 
| 105 | 
            +
            }
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            static void coverage_event_callsite_hook(rb_event_flag_t event, VALUE node,  VALUE self, ID mid, VALUE klass) {
         | 
| 108 | 
            +
              VALUE caller_ary;
         | 
| 109 | 
            +
              VALUE curr_meth;
         | 
| 110 | 
            +
              VALUE args[2];
         | 
| 111 | 
            +
              int status;
         | 
| 112 | 
            +
             | 
| 113 | 
            +
              caller_ary = callsite_custom_backtrace(caller_stack_len);
         | 
| 114 | 
            +
             | 
| 115 | 
            +
              VALUE klass_path;
         | 
| 116 | 
            +
              curr_meth = rb_ary_new();
         | 
| 117 | 
            +
               
         | 
| 118 | 
            +
              rb_frame_method_id_and_class(&mid, &klass);
         | 
| 119 | 
            +
              
         | 
| 120 | 
            +
              if (mid == ID_ALLOCATOR)
         | 
| 121 | 
            +
                return; //Qnil;
         | 
| 122 | 
            +
              if (klass) {
         | 
| 123 | 
            +
                if (TYPE(klass) == T_ICLASS) {
         | 
| 124 | 
            +
                  klass = RBASIC(klass)->klass;
         | 
| 125 | 
            +
                }
         | 
| 126 | 
            +
                else if (FL_TEST(klass, FL_SINGLETON)) {
         | 
| 127 | 
            +
                  klass = rb_iv_get(klass, "__attached__");
         | 
| 128 | 
            +
                }
         | 
| 129 | 
            +
              }
         | 
| 130 | 
            +
              
         | 
| 131 | 
            +
              /*
         | 
| 132 | 
            +
              klass = class << klass; self end unless klass === eval("self", binding)
         | 
| 133 | 
            +
              */
         | 
| 134 | 
            +
              
         | 
| 135 | 
            +
              klass_path = rb_class_path(klass);
         | 
| 136 | 
            +
              VALUE reciever = rb_funcall(rb_binding_new(), rb_intern("eval"), 1, rb_str_new2("self"));
         | 
| 137 | 
            +
             | 
| 138 | 
            +
              if (rb_funcall(klass, rb_intern("=="), 1, reciever) == Qtrue) {
         | 
| 139 | 
            +
                klass_path = rb_sprintf("#<Class:%s>", RSTRING_PTR(klass_path));
         | 
| 140 | 
            +
                OBJ_FREEZE(klass_path);
         | 
| 141 | 
            +
              }
         | 
| 142 | 
            +
                
         | 
| 143 | 
            +
              rb_ary_push(curr_meth, klass_path);
         | 
| 144 | 
            +
              rb_ary_push(curr_meth, ID2SYM(mid));
         | 
| 145 | 
            +
             | 
| 146 | 
            +
              args[0] = caller_ary;
         | 
| 147 | 
            +
              args[1] = curr_meth;
         | 
| 148 | 
            +
              rb_protect(record_callsite_info, (VALUE)args, &status);
         | 
| 149 | 
            +
             | 
| 150 | 
            +
              if(!status) {
         | 
| 151 | 
            +
                type_def_site args;
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                args.sourcefile = rb_sourcefile();
         | 
| 154 | 
            +
                args.sourceline = rb_sourceline();
         | 
| 155 | 
            +
                args.curr_meth = curr_meth;
         | 
| 156 | 
            +
                rb_protect(record_method_def_site, (VALUE)&args, NULL);
         | 
| 157 | 
            +
              }
         | 
| 158 | 
            +
             | 
| 159 | 
            +
              if(status)
         | 
| 160 | 
            +
                rb_gv_set("$!", Qnil);
         | 
| 161 | 
            +
            }
         | 
| 162 | 
            +
             | 
| 163 | 
            +
            static VALUE cov_install_callsite_hook(VALUE self) {
         | 
| 164 | 
            +
              if(!callsite_hook_set_p) {
         | 
| 165 | 
            +
                if(TYPE(caller_info) != T_HASH)
         | 
| 166 | 
            +
                  caller_info = rb_hash_new();
         | 
| 167 | 
            +
                callsite_hook_set_p = 1;
         | 
| 168 | 
            +
                VALUE something = 0;
         | 
| 169 | 
            +
                rb_add_event_hook(coverage_event_callsite_hook, 
         | 
| 170 | 
            +
                                  RUBY_EVENT_CALL, something);
         | 
| 171 | 
            +
                return Qtrue;
         | 
| 172 | 
            +
              } 
         | 
| 173 | 
            +
              else
         | 
| 174 | 
            +
                return Qfalse;
         | 
| 175 | 
            +
            }
         | 
| 176 | 
            +
             | 
| 177 | 
            +
            static VALUE cov_remove_callsite_hook(VALUE self) {
         | 
| 178 | 
            +
              if(!callsite_hook_set_p) 
         | 
| 179 | 
            +
                return Qfalse;
         | 
| 180 | 
            +
              else {
         | 
| 181 | 
            +
                rb_remove_event_hook(coverage_event_callsite_hook);
         | 
| 182 | 
            +
                callsite_hook_set_p = 0;
         | 
| 183 | 
            +
                return Qtrue;
         | 
| 184 | 
            +
              }
         | 
| 185 | 
            +
            }
         | 
| 186 | 
            +
             | 
| 187 | 
            +
            static VALUE cov_generate_callsite_info(VALUE self) {
         | 
| 188 | 
            +
              VALUE ret;
         | 
| 189 | 
            +
             | 
| 190 | 
            +
              ret = rb_ary_new();
         | 
| 191 | 
            +
              rb_ary_push(ret, caller_info);
         | 
| 192 | 
            +
              rb_ary_push(ret, method_def_site_info);
         | 
| 193 | 
            +
              return ret;
         | 
| 194 | 
            +
            }
         | 
| 195 | 
            +
             | 
| 196 | 
            +
            static VALUE cov_reset_callsite(VALUE self) {
         | 
| 197 | 
            +
              if(callsite_hook_set_p) {
         | 
| 198 | 
            +
                rb_raise(rb_eRuntimeError, "Cannot reset the callsite info in the middle of a traced run.");
         | 
| 199 | 
            +
                return Qnil;
         | 
| 200 | 
            +
              }
         | 
| 201 | 
            +
              
         | 
| 202 | 
            +
              caller_info = rb_hash_new();
         | 
| 203 | 
            +
              method_def_site_info = rb_hash_new();
         | 
| 204 | 
            +
              return Qnil;
         | 
| 205 | 
            +
            }
         | 
| 206 | 
            +
             | 
| 207 | 
            +
            void Init_rcov_callsite() {
         | 
| 208 | 
            +
              VALUE mRcov;
         | 
| 209 | 
            +
              VALUE mRCOV__;
         | 
| 210 | 
            +
              ID id_rcov = rb_intern("Rcov");
         | 
| 211 | 
            +
              ID id_coverage__ = rb_intern("RCOV__");
         | 
| 212 | 
            +
              // ID id_script_lines__ = rb_intern("SCRIPT_LINES__");
         | 
| 213 | 
            +
              
         | 
| 214 | 
            +
              if(rb_const_defined(rb_cObject, id_rcov)) 
         | 
| 215 | 
            +
                mRcov = rb_const_get(rb_cObject, id_rcov);
         | 
| 216 | 
            +
              else
         | 
| 217 | 
            +
                mRcov = rb_define_module("Rcov");
         | 
| 218 | 
            +
              
         | 
| 219 | 
            +
              if(rb_const_defined(mRcov, id_coverage__))
         | 
| 220 | 
            +
                mRCOV__ = rb_const_get_at(mRcov, id_coverage__);
         | 
| 221 | 
            +
              else
         | 
| 222 | 
            +
                mRCOV__ = rb_define_module_under(mRcov, "RCOV__");
         | 
| 223 | 
            +
             | 
| 224 | 
            +
              callsite_hook_set_p = 0;
         | 
| 225 | 
            +
              caller_info = rb_hash_new();
         | 
| 226 | 
            +
              method_def_site_info = rb_hash_new();
         | 
| 227 | 
            +
              rb_gc_register_address(&caller_info);
         | 
| 228 | 
            +
              rb_gc_register_address(&method_def_site_info);
         | 
| 229 | 
            +
              
         | 
| 230 | 
            +
              rb_define_singleton_method(mRCOV__, "install_callsite_hook", cov_install_callsite_hook, 0);
         | 
| 231 | 
            +
              rb_define_singleton_method(mRCOV__, "remove_callsite_hook", cov_remove_callsite_hook, 0);
         | 
| 232 | 
            +
              rb_define_singleton_method(mRCOV__, "generate_callsite_info", cov_generate_callsite_info, 0);
         | 
| 233 | 
            +
              rb_define_singleton_method(mRCOV__, "reset_callsite", cov_reset_callsite, 0);
         | 
| 234 | 
            +
            }
         |