relevance-rcov 0.8.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/BLURB +149 -0
- data/CHANGES +177 -0
- data/LEGAL +36 -0
- data/LICENSE +56 -0
- data/Rakefile +93 -0
- data/THANKS +96 -0
- data/bin/rcov +560 -0
- data/doc/readme_for_api +41 -0
- data/doc/readme_for_emacs +64 -0
- data/doc/readme_for_rake +62 -0
- data/doc/readme_for_vim +47 -0
- data/editor-extensions/rcov.el +131 -0
- data/editor-extensions/rcov.vim +38 -0
- data/ext/rcovrt/1.8/callsite.c +242 -0
- data/ext/rcovrt/1.8/rcovrt.c +331 -0
- data/ext/rcovrt/1.9/callsite.c +258 -0
- data/ext/rcovrt/1.9/rcovrt.c +315 -0
- data/ext/rcovrt/extconf.rb +23 -0
- data/lib/rcov.rb +991 -0
- data/lib/rcov/lowlevel.rb +145 -0
- data/lib/rcov/rcovtask.rb +156 -0
- data/lib/rcov/report.rb +71 -0
- data/lib/rcov/rexml_extensions.rb +44 -0
- data/lib/rcov/version.rb +11 -0
- data/lib/rcov/xx.rb +754 -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/call_site_analyzer_test.rb +171 -0
- data/test/code_coverage_analyzer_test.rb +188 -0
- data/test/file_statistics_test.rb +471 -0
- data/test/functional_test.rb +89 -0
- data/test/turn_off_rcovrt.rb +4 -0
- metadata +99 -0
data/doc/readme_for_api
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
= +rcov+
|
3
|
+
|
4
|
+
+rcov+ is a:
|
5
|
+
1. tool for code coverage analysis for Ruby
|
6
|
+
2. library for collecting code coverage and execution count information
|
7
|
+
introspectively
|
8
|
+
|
9
|
+
If you want to use the command line tool, the output from
|
10
|
+
rcov -h
|
11
|
+
is self-explicative.
|
12
|
+
|
13
|
+
If you want to automate the execution of +rcov+ via Rake take a look at
|
14
|
+
readme_for_rake[link:files/README_rake.html].
|
15
|
+
|
16
|
+
If you want to use the associated library, read on.
|
17
|
+
|
18
|
+
== Usage of the +rcov+ runtime/library
|
19
|
+
|
20
|
+
+rcov+ is primarily a tool for code coverage analysis, but since 0.4.0 it
|
21
|
+
exposes some of its code so that you can build on top of its heuristics for
|
22
|
+
code analysis and its capabilities for coverage information and execution
|
23
|
+
count gathering.
|
24
|
+
|
25
|
+
The main classes of interest are Rcov::FileStatistics,
|
26
|
+
Rcov::CodeCoverageAnalyzer and Rcov::CallSiteAnalyzer.
|
27
|
+
|
28
|
+
Rcov::FileStatistics can use some heuristics to determine
|
29
|
+
which parts of the file are executable and which are mere comments.
|
30
|
+
|
31
|
+
Rcov::CodeCoverageAnalyzer is used to gather code coverage and execution
|
32
|
+
count information inside a running Ruby program.
|
33
|
+
|
34
|
+
Rcov::CallSiteAnalyzer is used to obtain information about where methods are
|
35
|
+
defined and who calls them.
|
36
|
+
|
37
|
+
The parts of +rcov+'s runtime meant to be reused (i.e. the external API) are
|
38
|
+
documented with RDoc. Those not meant to be used are clearly marked as so or
|
39
|
+
were deliberately removed from the present documentation.
|
40
|
+
|
41
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
<tt>rcov.el</tt> allows you to use rcov from Emacs conveniently.
|
3
|
+
* Run unit tests and jump to uncovered code by <tt>C-x `</tt>.
|
4
|
+
* Run unit tests and save the current coverage status.
|
5
|
+
* Run unit tests and jump to uncovered code introduced since the last run.
|
6
|
+
* View cross-reference annotated code.
|
7
|
+
|
8
|
+
== Installation
|
9
|
+
|
10
|
+
Copy <tt>rcov.el</tt> to the appropriate directory, which is in load-path.
|
11
|
+
Then require it.
|
12
|
+
(require 'rcov)
|
13
|
+
|
14
|
+
|
15
|
+
== Usage
|
16
|
+
|
17
|
+
There are some commands to run rcov in Emacs.
|
18
|
+
All of them displays +rcov+ window, whose major-mode is compilation-mode.
|
19
|
+
Therefore you can jump to uncovered code by <tt>C-x `</tt>.
|
20
|
+
|
21
|
+
+rcov-command-line+, +rcovsave-command-line+, and +rcovdiff-command-line+ define
|
22
|
+
command line to run rcov.
|
23
|
+
If you do not use +rcov+ from Rake, you must modify them.
|
24
|
+
|
25
|
+
=== Finding uncovered code
|
26
|
+
|
27
|
+
Type the following while editing your program:
|
28
|
+
M-x rcov
|
29
|
+
|
30
|
+
=== Setting the reference point
|
31
|
+
|
32
|
+
+rcov+'s <tt>--text-coverage-diff</tt> mode compares the current coverage status against
|
33
|
+
the saved one. It therefore needs that information to be recorded
|
34
|
+
before you write new code (typically right after you perform a commit) in
|
35
|
+
order to have something to compare against.
|
36
|
+
|
37
|
+
You can save the current status with the <tt>--save</tt> option.
|
38
|
+
|
39
|
+
Type the following to save the current status in Emacs:
|
40
|
+
M-x rcovsave
|
41
|
+
If you do not use +rcov+ from Rake, you must modify +rcovsave-command-line+ variable.
|
42
|
+
|
43
|
+
=== Finding new uncovered code
|
44
|
+
|
45
|
+
Type the following to save the current status in Emacs:
|
46
|
+
M-x rcovdiff
|
47
|
+
|
48
|
+
=== Viewing cross-reference annotated code
|
49
|
+
|
50
|
+
If you read cross-reference annotated code, issue
|
51
|
+
rake rcov RCOVOPTS='-a'
|
52
|
+
at the beginning.
|
53
|
+
This command creates +coverage+ directory and many *.rb files in it.
|
54
|
+
Filenames of these Ruby scripts are converted from original path.
|
55
|
+
You can browse them by normally <tt>C-x C-f</tt>.
|
56
|
+
You can think of <tt>-a</tt> option as <tt>--xrefs</tt> option and output format is Ruby script.
|
57
|
+
|
58
|
+
After find-file-ed annotated script, the major-mode is rcov-xref-mode,
|
59
|
+
which is derived from ruby-mode and specializes navigation.
|
60
|
+
|
61
|
+
<tt>Tab</tt> and <tt>M-Tab</tt> goes forward/backward links.
|
62
|
+
<tt>Ret</tt> follows selected link.
|
63
|
+
|
64
|
+
This feature is useful to read third-party code or to follow control flow.
|
data/doc/readme_for_rake
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
== Code coverage analysis automation with Rake
|
3
|
+
|
4
|
+
Since 0.4.0, <tt>rcov</tt> features a <tt>Rcov::RcovTask</tt> task for rake
|
5
|
+
which can be used to automate test coverage analysis. Basic usage is as
|
6
|
+
follows:
|
7
|
+
|
8
|
+
require 'rcov/rcovtask'
|
9
|
+
Rcov::RcovTask.new do |t|
|
10
|
+
t.test_files = FileList['test/test*.rb']
|
11
|
+
# t.verbose = true # uncomment to see the executed command
|
12
|
+
end
|
13
|
+
|
14
|
+
This will create by default a task named <tt>rcov</tt>, and also a task to
|
15
|
+
remove the output directory where the XHTML report is generated.
|
16
|
+
The latter will be named <tt>clobber_rcob</tt>, and will be added to the main
|
17
|
+
<tt>clobber</tt> target.
|
18
|
+
|
19
|
+
=== Passing command line options to <tt>rcov</tt>
|
20
|
+
|
21
|
+
You can provide a description, change the name of the generated tasks (the
|
22
|
+
one used to generate the report(s) and the clobber_ one) and pass options to
|
23
|
+
<tt>rcov</tt>:
|
24
|
+
|
25
|
+
desc "Analyze code coverage of the unit tests."
|
26
|
+
Rcov::RcovTask.new(:coverage) do |t|
|
27
|
+
t.test_files = FileList['test/test*.rb']
|
28
|
+
t.verbose = true
|
29
|
+
## get a text report on stdout when rake is run:
|
30
|
+
t.rcov_opts << "--text-report"
|
31
|
+
## only report files under 80% coverage
|
32
|
+
t.rcov_opts << "--threshold 80"
|
33
|
+
end
|
34
|
+
|
35
|
+
That will generate a <tt>coverage</tt> task and the associated
|
36
|
+
<tt>clobber_coverage</tt> task to remove the directory the report is dumped
|
37
|
+
to ("<tt>coverage</tt>" by default).
|
38
|
+
|
39
|
+
You can specify a different destination directory, which comes handy if you
|
40
|
+
have several <tt>RcovTask</tt>s; the <tt>clobber_*</tt> will take care of
|
41
|
+
removing that directory:
|
42
|
+
|
43
|
+
desc "Analyze code coverage for the FileStatistics class."
|
44
|
+
Rcov::RcovTask.new(:rcov_sourcefile) do |t|
|
45
|
+
t.test_files = FileList['test/test_FileStatistics.rb']
|
46
|
+
t.verbose = true
|
47
|
+
t.rcov_opts << "--test-unit-only"
|
48
|
+
t.output_dir = "coverage.sourcefile"
|
49
|
+
end
|
50
|
+
|
51
|
+
Rcov::RcovTask.new(:rcov_ccanalyzer) do |t|
|
52
|
+
t.test_files = FileList['test/test_CodeCoverageAnalyzer.rb']
|
53
|
+
t.verbose = true
|
54
|
+
t.rcov_opts << "--test-unit-only"
|
55
|
+
t.output_dir = "coverage.ccanalyzer"
|
56
|
+
end
|
57
|
+
|
58
|
+
=== Options passed through the <tt>rake</tt> command line
|
59
|
+
|
60
|
+
You can override the options defined in the RcovTask by passing the new
|
61
|
+
options at the time you invoke rake.
|
62
|
+
The documentation for the Rcov::RcovTask explains how this can be done.
|
data/doc/readme_for_vim
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
<tt>rcov.vim</tt> allows you to run unit tests from vim and enter quickfix mode in
|
3
|
+
order to jump to uncovered code introduced since the last run.
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
Copy <tt>rcov.vim</tt> to the appropriate "compiler" directory (typically
|
7
|
+
<tt>$HOME/.vim/compiler</tt>).
|
8
|
+
|
9
|
+
== Usage
|
10
|
+
|
11
|
+
=== Setting the reference point
|
12
|
+
|
13
|
+
+rcov+'s <tt>--text-coverage-diff</tt> mode compares the current coverage status against
|
14
|
+
the saved one. It therefore needs that information to be recorded
|
15
|
+
before you write new code (typically right after you perform a commit) in
|
16
|
+
order to have something to compare against.
|
17
|
+
|
18
|
+
You can save the current status with the <tt>--save</tt> option.
|
19
|
+
If you're running +rcov+ from Rake, you can do something like
|
20
|
+
rake rcov_units RCOVOPTS="-T --save --rails"
|
21
|
+
in order to take the current status as the reference point.
|
22
|
+
|
23
|
+
=== Finding new uncovered code
|
24
|
+
|
25
|
+
Type the following in command mode while editing your program:
|
26
|
+
:compiler rcov
|
27
|
+
|
28
|
+
rcov.vim assumes +rcov+ can be invoked with a rake task (see
|
29
|
+
readme_for_rake[link:files/README_rake.html] for
|
30
|
+
information on how to create it).
|
31
|
+
|
32
|
+
You can then execute +rcov+ and enter quickfix mode by typing
|
33
|
+
|
34
|
+
:make <taskname>
|
35
|
+
|
36
|
+
where taskname is the +rcov+ task you want to use; if you didn't override the
|
37
|
+
default name in the Rakefile, just
|
38
|
+
|
39
|
+
:make rcov
|
40
|
+
|
41
|
+
will do.
|
42
|
+
|
43
|
+
vim will then enter quickfix mode, allowing you to jump to the areas that were
|
44
|
+
not covered since the last time you saved the coverage data.
|
45
|
+
|
46
|
+
--------
|
47
|
+
# vim: ft=text :
|
@@ -0,0 +1,131 @@
|
|
1
|
+
;;; rcov.el -- Ruby Coverage Analysis Tool
|
2
|
+
|
3
|
+
;;; Copyright (c) 2006 rubikitch <rubikitch@ruby-lang.org>
|
4
|
+
;;;
|
5
|
+
;;; Use and distribution subject to the terms of the rcov license.
|
6
|
+
|
7
|
+
(defvar rcov-xref-before-visit-source-hook nil
|
8
|
+
"Hook executed before jump.")
|
9
|
+
(defvar rcov-xref-after-visit-source-hook nil
|
10
|
+
"Hook executed after jump.")
|
11
|
+
(defvar rcov-command-line "rake rcov RCOVOPTS='--gcc --no-html'"
|
12
|
+
"Rcov command line to find uncovered code.
|
13
|
+
It is good to use rcov with Rake because it `cd's appropriate directory.
|
14
|
+
`--gcc' option is strongly recommended because `rcov' uses compilation-mode.")
|
15
|
+
(defvar rcovsave-command-line "rake rcov RCOVOPTS='--gcc --no-html --save=coverage.info'"
|
16
|
+
"Rcov command line to save coverage status. See also `rcov-command-line'.")
|
17
|
+
(defvar rcovdiff-command-line "rake rcov RCOVOPTS='-D --gcc --no-html'"
|
18
|
+
"Rcov command line to find new uncovered code. See also `rcov-command-line'.")
|
19
|
+
|
20
|
+
;;;; rcov-xref-mode
|
21
|
+
(define-derived-mode rcov-xref-mode ruby-mode "Rxref"
|
22
|
+
"Major mode for annotated Ruby scripts (coverage/*.rb) by rcov."
|
23
|
+
(setq truncate-lines t)
|
24
|
+
;; ruby-electric-mode / pabbrev-mode hijacks TAB binding.
|
25
|
+
(and ruby-electric-mode (ruby-electric-mode -1))
|
26
|
+
(and (boundp 'pabbrev-mode) pabbrev-mode (pabbrev-mode -1))
|
27
|
+
(suppress-keymap rcov-xref-mode-map)
|
28
|
+
(define-key rcov-xref-mode-map "\C-i" 'rcov-xref-next-tag)
|
29
|
+
(define-key rcov-xref-mode-map "\M-\C-i" 'rcov-xref-previous-tag)
|
30
|
+
(define-key rcov-xref-mode-map "\C-m" 'rcov-xref-visit-source)
|
31
|
+
(set (make-local-variable 'automatic-hscrolling) nil)
|
32
|
+
)
|
33
|
+
|
34
|
+
(defvar rcov-xref-tag-regexp "\\[\\[\\(.*?\\)\\]\\]")
|
35
|
+
|
36
|
+
(defun rcov-xref-next-tag (n)
|
37
|
+
"Go to next LINK."
|
38
|
+
(interactive "p")
|
39
|
+
(when (looking-at rcov-xref-tag-regexp)
|
40
|
+
(goto-char (match-end 0)))
|
41
|
+
(when (re-search-forward rcov-xref-tag-regexp nil t n)
|
42
|
+
(goto-char (match-beginning 0)))
|
43
|
+
(rcov-xref-show-link))
|
44
|
+
|
45
|
+
(defun rcov-xref-previous-tag (n)
|
46
|
+
"Go to previous LINK."
|
47
|
+
(interactive "p")
|
48
|
+
(re-search-backward rcov-xref-tag-regexp nil t n)
|
49
|
+
(rcov-xref-show-link))
|
50
|
+
|
51
|
+
(defvar rcov-xref-link-tempbuffer " *rcov-link*")
|
52
|
+
(defun rcov-xref-show-link ()
|
53
|
+
"Follow current LINK."
|
54
|
+
(let ((link (match-string 1))
|
55
|
+
(eol (point-at-eol)))
|
56
|
+
(save-excursion
|
57
|
+
(when (and link
|
58
|
+
(re-search-backward "# \\(>>\\|<<\\) " (point-at-bol) t))
|
59
|
+
(while (re-search-forward rcov-xref-tag-regexp eol t)
|
60
|
+
(let ((matched (match-string 1)))
|
61
|
+
(when (string= link matched)
|
62
|
+
(add-text-properties 0 (length matched) '(face highlight) matched))
|
63
|
+
(with-current-buffer (get-buffer-create rcov-xref-link-tempbuffer)
|
64
|
+
(insert matched "\n"))))
|
65
|
+
(let (message-log-max) ; inhibit *Messages*
|
66
|
+
(message "%s" (with-current-buffer rcov-xref-link-tempbuffer
|
67
|
+
(substring (buffer-string) 0 -1)))) ; chomp
|
68
|
+
(kill-buffer rcov-xref-link-tempbuffer)))))
|
69
|
+
|
70
|
+
|
71
|
+
;; copied from jw-visit-source
|
72
|
+
(defun rcov-xref-extract-file-lines (line)
|
73
|
+
"Extract a list of file/line pairs from the given line of text."
|
74
|
+
(let*
|
75
|
+
((unix_fn "[^ \t\n\r\"'([<{]+")
|
76
|
+
(dos_fn "[a-zA-Z]:[^ \t\n\r\"'([<{]+")
|
77
|
+
(flre (concat "\\(" unix_fn "\\|" dos_fn "\\):\\([0-9]+\\)"))
|
78
|
+
(start nil)
|
79
|
+
(result nil))
|
80
|
+
(while (string-match flre line start)
|
81
|
+
(setq start (match-end 0))
|
82
|
+
(setq result
|
83
|
+
(cons (list
|
84
|
+
(substring line (match-beginning 1) (match-end 1))
|
85
|
+
(string-to-int (substring line (match-beginning 2) (match-end 2))))
|
86
|
+
result)))
|
87
|
+
result))
|
88
|
+
|
89
|
+
(defun rcov-xref-select-file-line (candidates)
|
90
|
+
"Select a file/line candidate that references an existing file."
|
91
|
+
(cond ((null candidates) nil)
|
92
|
+
((file-readable-p (caar candidates)) (car candidates))
|
93
|
+
(t (rcov-xref-select-file-line (cdr candidates))) ))
|
94
|
+
|
95
|
+
(defun rcov-xref-visit-source ()
|
96
|
+
"If the current line contains text like '../src/program.rb:34', visit
|
97
|
+
that file in the other window and position point on that line."
|
98
|
+
(interactive)
|
99
|
+
(let* ((line (progn (looking-at rcov-xref-tag-regexp) (match-string 1)))
|
100
|
+
(candidates (rcov-xref-extract-file-lines line))
|
101
|
+
(file-line (rcov-xref-select-file-line candidates)))
|
102
|
+
(cond (file-line
|
103
|
+
(run-hooks 'rcov-xref-before-visit-source-hook)
|
104
|
+
(find-file (car file-line))
|
105
|
+
(goto-line (cadr file-line))
|
106
|
+
(run-hooks 'rcov-xref-after-visit-source-hook))
|
107
|
+
(t
|
108
|
+
(error "No source location on line.")) )))
|
109
|
+
|
110
|
+
;;;; Running rcov with various options.
|
111
|
+
(defun rcov-internal (cmdline)
|
112
|
+
"Run rcov with various options."
|
113
|
+
(compile-internal cmdline ""
|
114
|
+
nil nil nil (lambda (x) "*rcov*")))
|
115
|
+
|
116
|
+
(defun rcov ()
|
117
|
+
"Run rcov to find uncovered code."
|
118
|
+
(interactive)
|
119
|
+
(rcov-internal rcov-command-line))
|
120
|
+
|
121
|
+
(defun rcovsave ()
|
122
|
+
"Run rcov to save coverage status."
|
123
|
+
(interactive)
|
124
|
+
(rcov-internal rcovsave-command-line))
|
125
|
+
|
126
|
+
(defun rcovdiff ()
|
127
|
+
"Run rcov to find new uncovered code."
|
128
|
+
(interactive)
|
129
|
+
(rcov-internal rcovdiff-command-line))
|
130
|
+
|
131
|
+
(provide 'rcov)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
" Vim compiler file
|
2
|
+
" Language: Ruby
|
3
|
+
" Function: Code coverage information with rcov
|
4
|
+
" Maintainer: Mauricio Fernandez <mfp at acm dot org>
|
5
|
+
" Info:
|
6
|
+
" URL: http://eigenclass.org/hiki.rb?rcov
|
7
|
+
" ----------------------------------------------------------------------------
|
8
|
+
"
|
9
|
+
" Changelog:
|
10
|
+
" 0.1: initial version, shipped with rcov 0.6.0
|
11
|
+
"
|
12
|
+
" Comments:
|
13
|
+
" Initial attempt.
|
14
|
+
" ----------------------------------------------------------------------------
|
15
|
+
|
16
|
+
if exists("current_compiler")
|
17
|
+
finish
|
18
|
+
endif
|
19
|
+
let current_compiler = "rcov"
|
20
|
+
|
21
|
+
if exists(":CompilerSet") != 2 " older Vim always used :setlocal
|
22
|
+
command -nargs=* CompilerSet setlocal <args>
|
23
|
+
endif
|
24
|
+
|
25
|
+
let s:cpo_save = &cpo
|
26
|
+
set cpo-=C
|
27
|
+
|
28
|
+
CompilerSet makeprg=rake\ $*\ RCOVOPTS=\"-D\ --no-html\ --no-color\"\ $*
|
29
|
+
|
30
|
+
CompilerSet errorformat=
|
31
|
+
\%+W\#\#\#\ %f:%l\,
|
32
|
+
\%-C\ \ \ ,
|
33
|
+
\%-C!!\
|
34
|
+
|
35
|
+
let &cpo = s:cpo_save
|
36
|
+
unlet s:cpo_save
|
37
|
+
|
38
|
+
" vim: nowrap sw=2 sts=2 ts=8 ff=unix :
|
@@ -0,0 +1,242 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <env.h>
|
3
|
+
#include <node.h>
|
4
|
+
#include <st.h>
|
5
|
+
#include <stdlib.h>
|
6
|
+
|
7
|
+
|
8
|
+
static char callsite_hook_set_p;
|
9
|
+
|
10
|
+
typedef struct {
|
11
|
+
char *sourcefile;
|
12
|
+
unsigned int sourceline;
|
13
|
+
VALUE curr_meth;
|
14
|
+
} type_def_site;
|
15
|
+
static VALUE caller_info = 0;
|
16
|
+
static VALUE method_def_site_info = 0;
|
17
|
+
|
18
|
+
static caller_stack_len = 1;
|
19
|
+
|
20
|
+
/*
|
21
|
+
*
|
22
|
+
* callsite hook and associated functions
|
23
|
+
*
|
24
|
+
* */
|
25
|
+
|
26
|
+
static VALUE
|
27
|
+
record_callsite_info(VALUE args)
|
28
|
+
{
|
29
|
+
VALUE caller_ary;
|
30
|
+
VALUE curr_meth;
|
31
|
+
VALUE count_hash;
|
32
|
+
VALUE count;
|
33
|
+
VALUE *pargs = (VALUE *)args;
|
34
|
+
|
35
|
+
caller_ary = pargs[0];
|
36
|
+
curr_meth = pargs[1];
|
37
|
+
count_hash = rb_hash_aref(caller_info, curr_meth);
|
38
|
+
if(TYPE(count_hash) != T_HASH) {
|
39
|
+
/* Qnil, anything else should be impossible unless somebody's been
|
40
|
+
* messing with ObjectSpace */
|
41
|
+
count_hash = rb_hash_new();
|
42
|
+
rb_hash_aset(caller_info, curr_meth, count_hash);
|
43
|
+
}
|
44
|
+
count = rb_hash_aref(count_hash, caller_ary);
|
45
|
+
if(count == Qnil)
|
46
|
+
count = INT2FIX(0);
|
47
|
+
count = INT2FIX(FIX2UINT(count) + 1);
|
48
|
+
rb_hash_aset(count_hash, caller_ary, count);
|
49
|
+
/*
|
50
|
+
printf("CALLSITE: %s -> %s %d\n", RSTRING(rb_inspect(curr_meth))->ptr,
|
51
|
+
RSTRING(rb_inspect(caller_ary))->ptr, FIX2INT(count));
|
52
|
+
*/
|
53
|
+
|
54
|
+
return Qnil;
|
55
|
+
}
|
56
|
+
|
57
|
+
|
58
|
+
static VALUE
|
59
|
+
record_method_def_site(VALUE args)
|
60
|
+
{
|
61
|
+
type_def_site *pargs = (type_def_site *)args;
|
62
|
+
VALUE def_site_info;
|
63
|
+
VALUE hash;
|
64
|
+
|
65
|
+
if( RTEST(rb_hash_aref(method_def_site_info, pargs->curr_meth)) )
|
66
|
+
return Qnil;
|
67
|
+
def_site_info = rb_ary_new();
|
68
|
+
rb_ary_push(def_site_info, rb_str_new2(pargs->sourcefile));
|
69
|
+
rb_ary_push(def_site_info, INT2NUM(pargs->sourceline+1));
|
70
|
+
rb_hash_aset(method_def_site_info, pargs->curr_meth, def_site_info);
|
71
|
+
/*
|
72
|
+
printf("DEFSITE: %s:%d for %s\n", pargs->sourcefile, pargs->sourceline+1,
|
73
|
+
RSTRING(rb_inspect(pargs->curr_meth))->ptr);
|
74
|
+
*/
|
75
|
+
|
76
|
+
return Qnil;
|
77
|
+
}
|
78
|
+
|
79
|
+
static VALUE
|
80
|
+
callsite_custom_backtrace(int lev)
|
81
|
+
{
|
82
|
+
struct FRAME *frame = ruby_frame;
|
83
|
+
VALUE ary;
|
84
|
+
NODE *n;
|
85
|
+
VALUE level;
|
86
|
+
VALUE klass;
|
87
|
+
|
88
|
+
ary = rb_ary_new();
|
89
|
+
if (frame->last_func == ID_ALLOCATOR) {
|
90
|
+
frame = frame->prev;
|
91
|
+
}
|
92
|
+
for (; frame && (n = frame->node); frame = frame->prev) {
|
93
|
+
if (frame->prev && frame->prev->last_func) {
|
94
|
+
if (frame->prev->node == n) continue;
|
95
|
+
level = rb_ary_new();
|
96
|
+
klass = frame->prev->last_class ? frame->prev->last_class : Qnil;
|
97
|
+
if(TYPE(klass) == T_ICLASS) {
|
98
|
+
klass = CLASS_OF(klass);
|
99
|
+
}
|
100
|
+
rb_ary_push(level, klass);
|
101
|
+
rb_ary_push(level, ID2SYM(frame->prev->last_func));
|
102
|
+
rb_ary_push(level, rb_str_new2(n->nd_file));
|
103
|
+
rb_ary_push(level, INT2NUM(nd_line(n)));
|
104
|
+
}
|
105
|
+
else {
|
106
|
+
level = rb_ary_new();
|
107
|
+
rb_ary_push(level, Qnil);
|
108
|
+
rb_ary_push(level, Qnil);
|
109
|
+
rb_ary_push(level, rb_str_new2(n->nd_file));
|
110
|
+
rb_ary_push(level, INT2NUM(nd_line(n)));
|
111
|
+
}
|
112
|
+
rb_ary_push(ary, level);
|
113
|
+
if(--lev == 0)
|
114
|
+
break;
|
115
|
+
}
|
116
|
+
|
117
|
+
return ary;
|
118
|
+
}
|
119
|
+
|
120
|
+
static void
|
121
|
+
coverage_event_callsite_hook(rb_event_t event, NODE *node, VALUE self,
|
122
|
+
ID mid, VALUE klass)
|
123
|
+
{
|
124
|
+
VALUE caller_ary;
|
125
|
+
VALUE curr_meth;
|
126
|
+
VALUE args[2];
|
127
|
+
int status;
|
128
|
+
|
129
|
+
caller_ary = callsite_custom_backtrace(caller_stack_len);
|
130
|
+
|
131
|
+
if(TYPE(klass) == T_ICLASS) {
|
132
|
+
klass = CLASS_OF(klass);
|
133
|
+
}
|
134
|
+
curr_meth = rb_ary_new();
|
135
|
+
rb_ary_push(curr_meth, klass);
|
136
|
+
rb_ary_push(curr_meth, ID2SYM(mid));
|
137
|
+
|
138
|
+
args[0] = caller_ary;
|
139
|
+
args[1] = curr_meth;
|
140
|
+
rb_protect(record_callsite_info, (VALUE)args, &status);
|
141
|
+
if(!status && node) {
|
142
|
+
type_def_site args;
|
143
|
+
|
144
|
+
args.sourcefile = node->nd_file;
|
145
|
+
args.sourceline = nd_line(node) - 1;
|
146
|
+
args.curr_meth = curr_meth;
|
147
|
+
rb_protect(record_method_def_site, (VALUE)&args, NULL);
|
148
|
+
}
|
149
|
+
if(status)
|
150
|
+
rb_gv_set("$!", Qnil);
|
151
|
+
}
|
152
|
+
|
153
|
+
|
154
|
+
static VALUE
|
155
|
+
cov_install_callsite_hook(VALUE self)
|
156
|
+
{
|
157
|
+
if(!callsite_hook_set_p) {
|
158
|
+
if(TYPE(caller_info) != T_HASH)
|
159
|
+
caller_info = rb_hash_new();
|
160
|
+
callsite_hook_set_p = 1;
|
161
|
+
rb_add_event_hook(coverage_event_callsite_hook,
|
162
|
+
RUBY_EVENT_CALL);
|
163
|
+
|
164
|
+
return Qtrue;
|
165
|
+
} else
|
166
|
+
return Qfalse;
|
167
|
+
}
|
168
|
+
|
169
|
+
|
170
|
+
static VALUE
|
171
|
+
cov_remove_callsite_hook(VALUE self)
|
172
|
+
{
|
173
|
+
if(!callsite_hook_set_p)
|
174
|
+
return Qfalse;
|
175
|
+
else {
|
176
|
+
rb_remove_event_hook(coverage_event_callsite_hook);
|
177
|
+
callsite_hook_set_p = 0;
|
178
|
+
return Qtrue;
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
|
183
|
+
static VALUE
|
184
|
+
cov_generate_callsite_info(VALUE self)
|
185
|
+
{
|
186
|
+
VALUE ret;
|
187
|
+
|
188
|
+
ret = rb_ary_new();
|
189
|
+
rb_ary_push(ret, caller_info);
|
190
|
+
rb_ary_push(ret, method_def_site_info);
|
191
|
+
return ret;
|
192
|
+
}
|
193
|
+
|
194
|
+
|
195
|
+
static VALUE
|
196
|
+
cov_reset_callsite(VALUE self)
|
197
|
+
{
|
198
|
+
if(callsite_hook_set_p) {
|
199
|
+
rb_raise(rb_eRuntimeError,
|
200
|
+
"Cannot reset the callsite info in the middle of a traced run.");
|
201
|
+
return Qnil;
|
202
|
+
}
|
203
|
+
|
204
|
+
caller_info = rb_hash_new();
|
205
|
+
method_def_site_info = rb_hash_new();
|
206
|
+
return Qnil;
|
207
|
+
}
|
208
|
+
|
209
|
+
void
|
210
|
+
Init_rcov_callsite()
|
211
|
+
{
|
212
|
+
VALUE mRcov;
|
213
|
+
VALUE mRCOV__;
|
214
|
+
ID id_rcov = rb_intern("Rcov");
|
215
|
+
ID id_coverage__ = rb_intern("RCOV__");
|
216
|
+
ID id_script_lines__ = rb_intern("SCRIPT_LINES__");
|
217
|
+
|
218
|
+
if(rb_const_defined(rb_cObject, id_rcov))
|
219
|
+
mRcov = rb_const_get(rb_cObject, id_rcov);
|
220
|
+
else
|
221
|
+
mRcov = rb_define_module("Rcov");
|
222
|
+
|
223
|
+
if(rb_const_defined(mRcov, id_coverage__))
|
224
|
+
mRCOV__ = rb_const_get_at(mRcov, id_coverage__);
|
225
|
+
else
|
226
|
+
mRCOV__ = rb_define_module_under(mRcov, "RCOV__");
|
227
|
+
|
228
|
+
callsite_hook_set_p = 0;
|
229
|
+
caller_info = rb_hash_new();
|
230
|
+
method_def_site_info = rb_hash_new();
|
231
|
+
rb_gc_register_address(&caller_info);
|
232
|
+
rb_gc_register_address(&method_def_site_info);
|
233
|
+
|
234
|
+
rb_define_singleton_method(mRCOV__, "install_callsite_hook",
|
235
|
+
cov_install_callsite_hook, 0);
|
236
|
+
rb_define_singleton_method(mRCOV__, "remove_callsite_hook",
|
237
|
+
cov_remove_callsite_hook, 0);
|
238
|
+
rb_define_singleton_method(mRCOV__, "generate_callsite_info",
|
239
|
+
cov_generate_callsite_info, 0);
|
240
|
+
rb_define_singleton_method(mRCOV__, "reset_callsite", cov_reset_callsite, 0);
|
241
|
+
}
|
242
|
+
/* vim: set sw=8 expandtab: */
|