busted 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +3 -1
- data/README.md +67 -14
- data/Rakefile +1 -1
- data/busted.gemspec +26 -7
- data/dtrace/probes/busted/method-cache-clear.d +14 -0
- data/dtrace/probes/examples/method-cache-clear.d +24 -0
- data/lib/busted.rb +19 -25
- data/lib/busted/countable.rb +19 -0
- data/lib/busted/counter.rb +30 -0
- data/lib/busted/current_process.rb +23 -0
- data/lib/busted/profiler.rb +23 -0
- data/lib/busted/profiler/default.rb +31 -0
- data/lib/busted/traceable.rb +37 -0
- data/lib/busted/tracer.rb +88 -0
- data/lib/busted/version.rb +1 -1
- data/test/busted/current_process_test.rb +30 -0
- data/test/busted_test.rb +173 -38
- metadata +17 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca3f17fb6e19fab0e2e5604ff88570507f8c1224
|
4
|
+
data.tar.gz: 15884ecda376d2f29feabd868d7d41de6bc25024
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d76855a302daf921cc84681c5a006872616671aa0ca0eb6e4490ef967c738112191f25a1efcbea25496e5c6d2e4e545ff769bffe85561be84a9c1d1fc33a0625
|
7
|
+
data.tar.gz: 95305e22d2ad4168d506ca83d501e7cb55a0ab90337c0dbd8a2e1843f8661c1f8adccc83f28570f1f9e07138d4577de05614d440a702f5fab5496def07ae1c38
|
data/CONTRIBUTING.md
CHANGED
@@ -8,6 +8,8 @@ welcome.
|
|
8
8
|
|
9
9
|
## Running the Tests
|
10
10
|
|
11
|
+
**Process must have root privileges, and [`dtrace`](http://en.wikipedia.org/wiki/DTrace) must be installed.**
|
12
|
+
|
11
13
|
To run the full suite:
|
12
14
|
|
13
15
|
`$ bundle exec rake`
|
@@ -18,7 +20,7 @@ To run a specific test file:
|
|
18
20
|
|
19
21
|
To run a specific test:
|
20
22
|
|
21
|
-
`$ bundle exec ruby -Itest test/busted_test.rb -n
|
23
|
+
`$ bundle exec ruby -Itest test/busted_test.rb -n test_cache_invalidations_with_new_constant`
|
22
24
|
|
23
25
|
[issues]: https://github.com/simeonwillbanks/busted/issues
|
24
26
|
[pr]: https://help.github.com/articles/using-pull-requests
|
data/README.md
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
-
# Busted
|
1
|
+
# Busted [![Build Status](https://travis-ci.org/simeonwillbanks/busted.png?branch=master)](https://travis-ci.org/simeonwillbanks/busted) [![Code Climate](https://codeclimate.com/github/simeonwillbanks/busted.png)](https://codeclimate.com/github/simeonwillbanks/busted)
|
2
2
|
|
3
|
-
|
3
|
+
#### Disclaimers
|
4
|
+
- Requires MRI Ruby 2.1.0dev at [trunk](https://github.com/ruby/ruby/tree/trunk)
|
5
|
+
|
6
|
+
--
|
4
7
|
|
5
|
-
Find code that busts the Ruby cache.
|
8
|
+
Find code that busts the Ruby 2.1+ cache.
|
6
9
|
|
7
10
|
- Report when code invalidates Ruby's internal cache
|
8
11
|
- Uses [RubyVM.stat](https://github.com/ruby/ruby/commit/cc1063092b366a0a8449528ab6bf67a72f5ce027)
|
@@ -13,39 +16,46 @@ Find code that busts the Ruby cache.
|
|
13
16
|
|
14
17
|
```ruby
|
15
18
|
Busted.cache? do
|
16
|
-
class
|
19
|
+
class Beer
|
17
20
|
end
|
18
21
|
end
|
19
22
|
#=> true
|
23
|
+
|
24
|
+
Busted.run do
|
25
|
+
class Pizza
|
26
|
+
end
|
27
|
+
end
|
28
|
+
#=> {:invalidations=>{:method=>0, :constant=>1}}
|
20
29
|
```
|
21
30
|
|
22
31
|
*Method Cache*
|
23
32
|
|
24
33
|
```ruby
|
25
34
|
Busted.method_cache? do
|
26
|
-
def
|
35
|
+
def beer
|
27
36
|
end
|
28
37
|
end
|
29
38
|
#=> true
|
39
|
+
|
40
|
+
Busted.method_cache_invalidations do
|
41
|
+
def pizza
|
42
|
+
end
|
43
|
+
end
|
44
|
+
#=> 3
|
30
45
|
```
|
31
46
|
|
32
47
|
*Constant Cache*
|
33
48
|
|
34
49
|
```ruby
|
35
50
|
Busted.constant_cache? do
|
36
|
-
|
51
|
+
STOUT = "stout"
|
37
52
|
end
|
38
53
|
#=> true
|
39
|
-
```
|
40
|
-
|
41
|
-
*Class Cache*
|
42
54
|
|
43
|
-
|
44
|
-
|
45
|
-
class Beer
|
46
|
-
end
|
55
|
+
Busted.constant_cache_invalidations do
|
56
|
+
CHEESE = "cheese"
|
47
57
|
end
|
48
|
-
#=>
|
58
|
+
#=> 1
|
49
59
|
```
|
50
60
|
|
51
61
|
*No Cache Busted*
|
@@ -55,8 +65,51 @@ Busted.cache? do
|
|
55
65
|
beer = "beer"
|
56
66
|
end
|
57
67
|
#=> false
|
68
|
+
|
69
|
+
Busted.run do
|
70
|
+
pizza = "pizza"
|
71
|
+
end
|
72
|
+
#=> {:invalidations=>{:method=>0, :constant=>0}}
|
58
73
|
```
|
59
74
|
|
75
|
+
## Advanced Usage
|
76
|
+
Busted can report method cache invalidation locations via [`dtrace`](http://en.wikipedia.org/wiki/DTrace). The running process must have root privileges, and `dtrace` must be installed.
|
77
|
+
|
78
|
+
*trace.rb*
|
79
|
+
```ruby
|
80
|
+
require "busted"
|
81
|
+
require "pp"
|
82
|
+
|
83
|
+
report = Busted.run(trace: true) do
|
84
|
+
def cookie; end
|
85
|
+
end
|
86
|
+
pp report
|
87
|
+
```
|
88
|
+
|
89
|
+
```bash
|
90
|
+
$ whoami
|
91
|
+
simeon
|
92
|
+
|
93
|
+
$ id simeon
|
94
|
+
uid=501(simeon) gid=20(staff) groups=20(staff),80(admin)
|
95
|
+
|
96
|
+
# simeon is an admin; sudo does not require a password
|
97
|
+
$ sudo grep admin /etc/sudoers
|
98
|
+
%admin ALL=(ALL) NOPASSWD: ALL
|
99
|
+
|
100
|
+
$ sudo dtrace -V
|
101
|
+
dtrace: Sun D 1.6.2
|
102
|
+
|
103
|
+
# No need to sudo
|
104
|
+
# Busted runs dtrace in a child process with root privileges
|
105
|
+
$ ruby trace.rb
|
106
|
+
{:invalidations=>{:method=>1, :constant=>0},
|
107
|
+
:traces=>
|
108
|
+
{:method=>[{:class=>"global", :sourcefile=>"trace.rb", :lineno=>"5"}]}}
|
109
|
+
```
|
110
|
+
|
111
|
+
Busted includes an [example `dtrace` probe](/dtrace/probes/examples/method-cache-clear.d) for use on the command line or an application. See the [probe](/dtrace/probes/examples/method-cache-clear.d) for usage.
|
112
|
+
|
60
113
|
## Installation
|
61
114
|
|
62
115
|
***Requires MRI Ruby 2.1.0dev***
|
data/Rakefile
CHANGED
data/busted.gemspec
CHANGED
@@ -8,19 +8,38 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Busted::VERSION
|
9
9
|
spec.authors = ["Simeon F. Willbanks"]
|
10
10
|
spec.email = ["sfw@simeonfosterwillbanks.com"]
|
11
|
-
spec.description =
|
11
|
+
spec.description = "Find code that busts the Ruby 2.1+ cache."
|
12
12
|
spec.summary = <<-DESC
|
13
13
|
MRI Ruby defines RubyVM.stat which accesses internal cache counters.
|
14
|
-
|
14
|
+
When the cache counters are incremented, the internal cache is invalidated.
|
15
|
+
Busted reports when code increments these counters.
|
15
16
|
DESC
|
16
17
|
spec.homepage = "https://github.com/simeonwillbanks/busted"
|
17
18
|
spec.license = "MIT"
|
18
19
|
|
19
|
-
spec.files =
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
spec.files = %w(
|
21
|
+
.ruby-version
|
22
|
+
CONTRIBUTING.md
|
23
|
+
Gemfile
|
24
|
+
LICENSE.txt
|
25
|
+
README.md
|
26
|
+
Rakefile
|
27
|
+
busted.gemspec
|
28
|
+
dtrace/probes/busted/method-cache-clear.d
|
29
|
+
dtrace/probes/examples/method-cache-clear.d
|
30
|
+
lib/busted.rb
|
31
|
+
lib/busted/countable.rb
|
32
|
+
lib/busted/counter.rb
|
33
|
+
lib/busted/current_process.rb
|
34
|
+
lib/busted/profiler.rb
|
35
|
+
lib/busted/profiler/default.rb
|
36
|
+
lib/busted/traceable.rb
|
37
|
+
lib/busted/tracer.rb
|
38
|
+
lib/busted/version.rb
|
39
|
+
test/busted/current_process_test.rb
|
40
|
+
test/busted_test.rb
|
41
|
+
test/test_helper.rb
|
42
|
+
)
|
24
43
|
spec.test_files = spec.files.grep(%r{^test/})
|
25
44
|
spec.require_paths = ["lib"]
|
26
45
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
dtrace:::BEGIN
|
2
|
+
{
|
3
|
+
printf("Busted Trace Started.\n");
|
4
|
+
}
|
5
|
+
ruby$target:::method-cache-clear
|
6
|
+
/arg1/
|
7
|
+
{
|
8
|
+
printf("%s %s %d\n", copyinstr(arg0), copyinstr(arg1), arg2);
|
9
|
+
}
|
10
|
+
ruby$target:::raise
|
11
|
+
/arg1/
|
12
|
+
{
|
13
|
+
printf("%s %s %d\n", copyinstr(arg0), copyinstr(arg1), arg2);
|
14
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
/*
|
2
|
+
* method-cache-clear.d dtrace probe
|
3
|
+
*
|
4
|
+
* Prints invalidation Class, Source File, and Line Number to STDOUT.
|
5
|
+
*
|
6
|
+
* Example Usage:
|
7
|
+
*
|
8
|
+
* $ sudo dtrace -q -s ./method-cache-clear.d -p <pid of running ruby process>
|
9
|
+
* global test/busted_test.rb 78
|
10
|
+
* Hello test/busted_test.rb 66
|
11
|
+
*
|
12
|
+
* More DTrace/Ruby Examples:
|
13
|
+
*
|
14
|
+
* http://magazine.rubyist.net/?Ruby200SpecialEn-dtrace
|
15
|
+
*
|
16
|
+
* Documentation:
|
17
|
+
*
|
18
|
+
* https://github.com/ruby/ruby/blob/trunk/doc/dtrace_probes.rdoc
|
19
|
+
*/
|
20
|
+
ruby$target:::method-cache-clear
|
21
|
+
/arg1/
|
22
|
+
{
|
23
|
+
printf("%s %s %d\n", copyinstr(arg0), copyinstr(arg1), arg2);
|
24
|
+
}
|
data/lib/busted.rb
CHANGED
@@ -1,40 +1,34 @@
|
|
1
|
-
require "busted/
|
1
|
+
require "busted/profiler"
|
2
2
|
|
3
3
|
module Busted
|
4
4
|
extend self
|
5
5
|
|
6
|
-
def
|
7
|
-
|
8
|
-
yield
|
9
|
-
ending = count serial
|
10
|
-
ending > starting
|
6
|
+
def run(options = {}, &block)
|
7
|
+
Profiler.run options, &block
|
11
8
|
end
|
12
9
|
|
13
|
-
def
|
14
|
-
|
10
|
+
def method_cache_invalidations(&block)
|
11
|
+
run(&block)[:invalidations][:method]
|
15
12
|
end
|
16
13
|
|
17
|
-
def
|
18
|
-
|
14
|
+
def constant_cache_invalidations(&block)
|
15
|
+
run(&block)[:invalidations][:constant]
|
19
16
|
end
|
20
17
|
|
21
|
-
def
|
22
|
-
|
18
|
+
def cache?(counter = nil, &block)
|
19
|
+
total = if counter
|
20
|
+
send :"#{counter}_cache_invalidations", &block
|
21
|
+
else
|
22
|
+
run(&block)[:invalidations].values.inject :+
|
23
|
+
end
|
24
|
+
total > 0
|
23
25
|
end
|
24
26
|
|
25
|
-
|
27
|
+
def method_cache?(&block)
|
28
|
+
cache? :method, &block
|
29
|
+
end
|
26
30
|
|
27
|
-
def
|
28
|
-
|
29
|
-
case serial
|
30
|
-
when :method
|
31
|
-
stat[:method_serial]
|
32
|
-
when :constant
|
33
|
-
stat[:constant_serial]
|
34
|
-
when :class
|
35
|
-
stat[:class_serial]
|
36
|
-
else
|
37
|
-
stat[:method_serial] + stat[:constant_serial] + stat[:class_serial]
|
38
|
-
end
|
31
|
+
def constant_cache?(&block)
|
32
|
+
cache? :constant, &block
|
39
33
|
end
|
40
34
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Busted
|
2
|
+
module Countable
|
3
|
+
|
4
|
+
attr_reader :counter
|
5
|
+
attr_writer :report
|
6
|
+
|
7
|
+
def start_counter
|
8
|
+
@counter = Counter.new
|
9
|
+
|
10
|
+
counter.start
|
11
|
+
end
|
12
|
+
|
13
|
+
def finish_counter
|
14
|
+
counter.finish
|
15
|
+
|
16
|
+
report[:invalidations] = counter.report
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Busted
|
2
|
+
class Counter
|
3
|
+
|
4
|
+
def start
|
5
|
+
@started = counts
|
6
|
+
end
|
7
|
+
|
8
|
+
def finish
|
9
|
+
@finished = counts
|
10
|
+
end
|
11
|
+
|
12
|
+
def report
|
13
|
+
[:method, :constant].each_with_object({}) do |counter, result|
|
14
|
+
result[counter] = finished[counter] - started[counter]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :started, :finished
|
21
|
+
|
22
|
+
def counts
|
23
|
+
stat = RubyVM.stat
|
24
|
+
{
|
25
|
+
method: stat[:global_method_state],
|
26
|
+
constant: stat[:global_constant_state]
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Busted
|
2
|
+
module CurrentProcess
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def privileged?
|
6
|
+
if root? || sudoer?
|
7
|
+
true
|
8
|
+
else
|
9
|
+
false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def root?
|
14
|
+
Process.euid == 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def sudoer?
|
18
|
+
system "sudo echo ok > /dev/null 2>&1"
|
19
|
+
rescue Errno::EPERM
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "busted/counter"
|
2
|
+
require "busted/countable"
|
3
|
+
require "busted/tracer"
|
4
|
+
require "busted/traceable"
|
5
|
+
require "busted/profiler/default"
|
6
|
+
|
7
|
+
module Busted
|
8
|
+
module Profiler
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def run(options, &block)
|
12
|
+
klass(options.fetch :profiler, :default).new(options, &block).run
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def klass(profiler)
|
18
|
+
Profiler.const_get profiler.capitalize
|
19
|
+
rescue NameError
|
20
|
+
fail ArgumentError, "profiler `#{profiler}' does not exist"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Busted
|
2
|
+
module Profiler
|
3
|
+
class Default
|
4
|
+
|
5
|
+
include Busted::Traceable
|
6
|
+
include Busted::Countable
|
7
|
+
|
8
|
+
attr_reader :trace, :block, :report
|
9
|
+
|
10
|
+
def initialize(options = {}, &block)
|
11
|
+
fail LocalJumpError, "no block given" unless block
|
12
|
+
|
13
|
+
@trace = options.fetch :trace, false
|
14
|
+
@block = block
|
15
|
+
@report = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
start_tracer
|
20
|
+
start_counter
|
21
|
+
|
22
|
+
block.call
|
23
|
+
|
24
|
+
finish_counter
|
25
|
+
finish_tracer
|
26
|
+
|
27
|
+
report
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "busted/current_process"
|
2
|
+
|
3
|
+
module Busted
|
4
|
+
module Traceable
|
5
|
+
|
6
|
+
attr_reader :trace, :tracer
|
7
|
+
attr_writer :report
|
8
|
+
|
9
|
+
def trace?
|
10
|
+
trace
|
11
|
+
end
|
12
|
+
|
13
|
+
def start_tracer
|
14
|
+
return unless trace?
|
15
|
+
|
16
|
+
unless Tracer.exists?
|
17
|
+
fail Tracer::MissingCommandError, "tracer requires dtrace"
|
18
|
+
end
|
19
|
+
|
20
|
+
unless CurrentProcess.privileged?
|
21
|
+
fail Errno::EPERM, "dtrace requires root privileges"
|
22
|
+
end
|
23
|
+
|
24
|
+
@tracer = Tracer.new
|
25
|
+
|
26
|
+
tracer.start
|
27
|
+
end
|
28
|
+
|
29
|
+
def finish_tracer
|
30
|
+
return unless trace?
|
31
|
+
|
32
|
+
tracer.finish
|
33
|
+
|
34
|
+
report[:traces] = tracer.report
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
|
3
|
+
module Busted
|
4
|
+
class Tracer
|
5
|
+
|
6
|
+
class FinishedException < Exception; end
|
7
|
+
class MissingCommandError < StandardError; end
|
8
|
+
|
9
|
+
def self.exists?
|
10
|
+
system "hash dtrace 2>/dev/null"
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@lines = ""
|
15
|
+
end
|
16
|
+
|
17
|
+
def start
|
18
|
+
spawn
|
19
|
+
wait until started?
|
20
|
+
end
|
21
|
+
|
22
|
+
def finish
|
23
|
+
final_probe
|
24
|
+
wait until finished?
|
25
|
+
kill
|
26
|
+
ensure
|
27
|
+
clean_up
|
28
|
+
end
|
29
|
+
|
30
|
+
def report
|
31
|
+
lines.split("\n").each_with_object({method: []}) do |line, result|
|
32
|
+
next if line =~ /\ABusted/
|
33
|
+
trace = line.split
|
34
|
+
result[:method] << { class: trace[0], sourcefile: trace[1], lineno: trace[2] }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_accessor :lines
|
41
|
+
attr_reader :child_pid
|
42
|
+
|
43
|
+
def wait
|
44
|
+
sleep 0.1
|
45
|
+
end
|
46
|
+
|
47
|
+
def started?
|
48
|
+
!(lines << log.read).empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
def finished?
|
52
|
+
(lines << log.read) =~ /Busted::Tracer::FinishedException/
|
53
|
+
end
|
54
|
+
|
55
|
+
def final_probe
|
56
|
+
raise FinishedException
|
57
|
+
rescue FinishedException
|
58
|
+
end
|
59
|
+
|
60
|
+
def spawn
|
61
|
+
@child_pid = Process.spawn command, STDERR => STDOUT
|
62
|
+
end
|
63
|
+
|
64
|
+
def kill
|
65
|
+
`sudo kill -TERM #{child_pid}`
|
66
|
+
end
|
67
|
+
|
68
|
+
def parent_pid
|
69
|
+
Process.pid
|
70
|
+
end
|
71
|
+
|
72
|
+
def probe
|
73
|
+
File.expand_path "../../../dtrace/probes/busted/method-cache-clear.d", __FILE__
|
74
|
+
end
|
75
|
+
|
76
|
+
def log
|
77
|
+
@log ||= Tempfile.new "busted-dtrace.log"
|
78
|
+
end
|
79
|
+
|
80
|
+
def clean_up
|
81
|
+
log.close!
|
82
|
+
end
|
83
|
+
|
84
|
+
def command
|
85
|
+
"sudo dtrace -q -o #{log.path} -s #{probe} -p #{parent_pid}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/busted/version.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class Busted::CurrentProcessTest < MiniTest::Unit::TestCase
|
4
|
+
|
5
|
+
def test_privileged_because_root
|
6
|
+
Process.stub :euid, 0 do
|
7
|
+
assert Busted::CurrentProcess.privileged?
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_privileged_because_sudoer
|
12
|
+
Process.stub :euid, 1980 do
|
13
|
+
# TODO
|
14
|
+
# Reliably stub Object#system
|
15
|
+
Busted::CurrentProcess.stub :sudoer?, true do
|
16
|
+
assert Busted::CurrentProcess.privileged?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_not_privileged
|
22
|
+
Process.stub :euid, 1980 do
|
23
|
+
# TODO
|
24
|
+
# Reliably stub Object#system
|
25
|
+
Busted::CurrentProcess.stub :sudoer?, false do
|
26
|
+
refute Busted::CurrentProcess.privileged?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/test/busted_test.rb
CHANGED
@@ -1,91 +1,226 @@
|
|
1
1
|
require "test_helper"
|
2
2
|
|
3
3
|
class BustedTest < MiniTest::Unit::TestCase
|
4
|
-
def
|
5
|
-
|
4
|
+
def test_invalid_profiler_exception
|
5
|
+
error = assert_raises ArgumentError do
|
6
|
+
Busted.run profiler: :pizza
|
7
|
+
end
|
8
|
+
assert_equal "profiler `pizza' does not exist", error.message
|
6
9
|
end
|
7
10
|
|
8
|
-
def
|
9
|
-
|
11
|
+
def test_cache_invalidations_requires_block
|
12
|
+
assert_raises LocalJumpError do
|
13
|
+
Busted.run
|
14
|
+
end
|
10
15
|
end
|
11
16
|
|
12
|
-
def
|
13
|
-
|
17
|
+
def test_method_cache_invalidations_requires_block
|
18
|
+
assert_raises LocalJumpError do
|
19
|
+
Busted.method_cache_invalidations
|
20
|
+
end
|
14
21
|
end
|
15
22
|
|
16
|
-
def
|
23
|
+
def test_constant_cache_invalidations_requires_block
|
24
|
+
assert_raises LocalJumpError do
|
25
|
+
Busted.constant_cache_invalidations
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_cache_invalidations_with_empty_block
|
30
|
+
report = Busted.run { }
|
31
|
+
assert_equal 0, report[:invalidations][:method]
|
32
|
+
assert_equal 0, report[:invalidations][:constant]
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_method_cache_invalidations_with_empty_block
|
36
|
+
assert_equal 0, Busted.method_cache_invalidations { }
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_constant_cache_invalidations_with_empty_block
|
40
|
+
assert_equal 0, Busted.constant_cache_invalidations { }
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_cache_invalidations_with_addition
|
44
|
+
report = Busted.run { 1 + 1 }
|
45
|
+
assert_equal 0, report[:invalidations][:method]
|
46
|
+
assert_equal 0, report[:invalidations][:constant]
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_method_cache_invalidations_with_addition
|
50
|
+
assert_equal 0, Busted.method_cache_invalidations { 1 + 1 }
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_constant_cache_invalidations_with_addition
|
54
|
+
assert_equal 0, Busted.constant_cache_invalidations { 1 + 1 }
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_cache_invalidations_with_new_constant
|
58
|
+
report = Busted.run { self.class.const_set :"CHEESE", "cheese" }
|
59
|
+
assert_equal 0, report[:invalidations][:method]
|
60
|
+
assert_equal 1, report[:invalidations][:constant]
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_method_cache_invalidations_with_new_constant
|
64
|
+
invalidations = Busted.method_cache_invalidations do
|
65
|
+
self.class.const_set :"HAWAIIAN", "hawaiian"
|
66
|
+
end
|
67
|
+
assert_equal 0, invalidations
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_constant_cache_invalidations_with_new_constant
|
71
|
+
invalidations = Busted.constant_cache_invalidations do
|
72
|
+
self.class.const_set :"VEGETABLE", "vegetable"
|
73
|
+
end
|
74
|
+
assert_equal 1, invalidations
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_cache_invalidations_with_new_method
|
78
|
+
report = Busted.run { Object.class_exec { def cheese; end } }
|
79
|
+
assert_equal 1, report[:invalidations][:method]
|
80
|
+
assert_equal 0, report[:invalidations][:constant]
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_method_cache_invalidations_with_new_method
|
84
|
+
invalidations = Busted.method_cache_invalidations do
|
85
|
+
Object.class_exec { def hawaiian; end }
|
86
|
+
end
|
87
|
+
assert_equal 1, invalidations
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_constant_cache_invalidations_with_new_method
|
91
|
+
invalidations = Busted.constant_cache_invalidations do
|
92
|
+
Object.class_exec { def vegetable; end }
|
93
|
+
end
|
94
|
+
assert_equal 0, invalidations
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_cache_invalidations_with_new_class
|
98
|
+
report = Busted.run { Object.class_eval "class ThreeCheese; end" }
|
99
|
+
assert_equal 0, report[:invalidations][:method]
|
100
|
+
assert_equal 1, report[:invalidations][:constant]
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_method_cache_invalidations_with_new_class
|
104
|
+
invalidations = Busted.method_cache_invalidations do
|
105
|
+
Object.class_eval "class SweetHawaiian; end"
|
106
|
+
end
|
107
|
+
assert_equal 0, invalidations
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_constant_cache_invalidations_with_new_class
|
111
|
+
invalidations = Busted.constant_cache_invalidations do
|
112
|
+
Object.class_eval "class Veggie; end"
|
113
|
+
end
|
114
|
+
assert_equal 1, invalidations
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_cache_predicate_requires_block
|
17
118
|
assert_raises LocalJumpError do
|
18
119
|
Busted.cache?
|
19
120
|
end
|
20
121
|
end
|
21
122
|
|
22
|
-
def
|
123
|
+
def test_method_cache_predicate_requires_block
|
124
|
+
assert_raises LocalJumpError do
|
125
|
+
Busted.method_cache?
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_constant_cache_predicate_requires_block
|
130
|
+
assert_raises LocalJumpError do
|
131
|
+
Busted.constant_cache?
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_cache_predicate_with_empty_block
|
23
136
|
refute Busted.cache? { }
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_method_cache_predicate_with_empty_block
|
24
140
|
refute Busted.method_cache? { }
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_constant_cache_predicate_with_empty_block
|
25
144
|
refute Busted.constant_cache? { }
|
26
145
|
end
|
27
146
|
|
28
|
-
def
|
147
|
+
def test_cache_predicate_with_addition
|
29
148
|
refute Busted.cache? { 1 + 1 }
|
30
149
|
end
|
31
150
|
|
32
|
-
def
|
151
|
+
def test_method_cache_predicate_with_addition
|
33
152
|
refute Busted.method_cache? { 1 + 1 }
|
34
153
|
end
|
35
154
|
|
36
|
-
def
|
155
|
+
def test_constant_cache_predicate_with_addition
|
37
156
|
refute Busted.constant_cache? { 1 + 1 }
|
38
157
|
end
|
39
158
|
|
40
|
-
def
|
41
|
-
|
159
|
+
def test_cache_predicate_with_new_constant
|
160
|
+
assert Busted.cache? { self.class.const_set :"PORTER", "porter" }
|
42
161
|
end
|
43
162
|
|
44
|
-
def
|
45
|
-
|
163
|
+
def test_method_cache_predicate_with_new_constant
|
164
|
+
refute Busted.method_cache? { self.class.const_set :"SCHWARZBIER", "schwarzbier" }
|
46
165
|
end
|
47
166
|
|
48
|
-
def
|
49
|
-
|
167
|
+
def test_constant_cache_predicate_with_new_constant
|
168
|
+
assert Busted.constant_cache? { self.class.const_set :"STOUT", "stout" }
|
50
169
|
end
|
51
170
|
|
52
|
-
def
|
53
|
-
assert Busted.
|
171
|
+
def test_cache_predicate_with_new_method
|
172
|
+
assert Busted.cache? { Object.class_exec { def porter; end } }
|
54
173
|
end
|
55
174
|
|
56
|
-
def
|
57
|
-
|
175
|
+
def test_method_cache_predicate_with_new_method
|
176
|
+
assert Busted.method_cache? { Object.class_exec { def schwarzbier; end } }
|
58
177
|
end
|
59
178
|
|
60
|
-
def
|
61
|
-
|
179
|
+
def test_constant_cache_predicate_with_new_method
|
180
|
+
refute Busted.constant_cache? { Object.class_exec { def stout; end } }
|
62
181
|
end
|
63
182
|
|
64
|
-
def
|
65
|
-
assert Busted.
|
183
|
+
def test_cache_predicate_with_new_class
|
184
|
+
assert Busted.cache? { Object.class_eval "class PierRatPorter; end" }
|
66
185
|
end
|
67
186
|
|
68
|
-
def
|
69
|
-
refute Busted.
|
187
|
+
def test_method_cache_predicate_with_new_class
|
188
|
+
refute Busted.method_cache? { Object.class_eval "class MidnightExpression; end" }
|
70
189
|
end
|
71
190
|
|
72
|
-
def
|
73
|
-
|
191
|
+
def test_constant_cache_predicate_with_new_class
|
192
|
+
assert Busted.constant_cache? { Object.class_eval "class SantasLittleHelper; end" }
|
74
193
|
end
|
75
194
|
|
76
|
-
|
77
|
-
assert Busted.cache? { Object.class_eval %q{class PierRatPorter; end} }
|
78
|
-
end
|
195
|
+
if Busted::Tracer.exists? && Busted::CurrentProcess.privileged?
|
79
196
|
|
80
|
-
|
81
|
-
|
197
|
+
def test_cache_invalidations_and_traces_with_new_method
|
198
|
+
report = Busted.run(trace: true) { Object.class_exec { def cookie; end } }
|
199
|
+
assert_equal 1, report[:invalidations][:method]
|
200
|
+
assert_equal 0, report[:invalidations][:constant]
|
201
|
+
assert_equal "global", report[:traces][:method][0][:class]
|
202
|
+
assert_match /test\/busted_test.rb\z/, report[:traces][:method][0][:sourcefile]
|
203
|
+
assert_equal "198", report[:traces][:method][0][:lineno]
|
204
|
+
end
|
82
205
|
end
|
83
206
|
|
84
|
-
def
|
85
|
-
|
207
|
+
def test_trace_without_root_privileges
|
208
|
+
Busted::Tracer.stub :exists?, true do
|
209
|
+
Busted::CurrentProcess.stub :privileged?, false do
|
210
|
+
error = assert_raises Errno::EPERM do
|
211
|
+
Busted.run(trace: true) { Object.class_exec { def ice_cream; end } }
|
212
|
+
end
|
213
|
+
assert_equal "Operation not permitted - dtrace requires root privileges", error.message
|
214
|
+
end
|
215
|
+
end
|
86
216
|
end
|
87
217
|
|
88
|
-
def
|
89
|
-
|
218
|
+
def test_trace_without_dtrace_installed
|
219
|
+
Busted::Tracer.stub :exists?, false do
|
220
|
+
error = assert_raises Busted::Tracer::MissingCommandError do
|
221
|
+
Busted.run(trace: true) { Object.class_exec { def pie; end } }
|
222
|
+
end
|
223
|
+
assert_equal "tracer requires dtrace", error.message
|
224
|
+
end
|
90
225
|
end
|
91
226
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: busted
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simeon F. Willbanks
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-12-
|
11
|
+
date: 2013-12-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,7 +38,7 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
-
description: Find code that busts the Ruby cache.
|
41
|
+
description: Find code that busts the Ruby 2.1+ cache.
|
42
42
|
email:
|
43
43
|
- sfw@simeonfosterwillbanks.com
|
44
44
|
executables: []
|
@@ -52,8 +52,18 @@ files:
|
|
52
52
|
- README.md
|
53
53
|
- Rakefile
|
54
54
|
- busted.gemspec
|
55
|
+
- dtrace/probes/busted/method-cache-clear.d
|
56
|
+
- dtrace/probes/examples/method-cache-clear.d
|
55
57
|
- lib/busted.rb
|
58
|
+
- lib/busted/countable.rb
|
59
|
+
- lib/busted/counter.rb
|
60
|
+
- lib/busted/current_process.rb
|
61
|
+
- lib/busted/profiler.rb
|
62
|
+
- lib/busted/profiler/default.rb
|
63
|
+
- lib/busted/traceable.rb
|
64
|
+
- lib/busted/tracer.rb
|
56
65
|
- lib/busted/version.rb
|
66
|
+
- test/busted/current_process_test.rb
|
57
67
|
- test/busted_test.rb
|
58
68
|
- test/test_helper.rb
|
59
69
|
homepage: https://github.com/simeonwillbanks/busted
|
@@ -79,8 +89,10 @@ rubyforge_project:
|
|
79
89
|
rubygems_version: 2.1.11
|
80
90
|
signing_key:
|
81
91
|
specification_version: 4
|
82
|
-
summary: MRI Ruby defines RubyVM.stat which accesses internal cache counters.
|
83
|
-
|
92
|
+
summary: MRI Ruby defines RubyVM.stat which accesses internal cache counters. When
|
93
|
+
the cache counters are incremented, the internal cache is invalidated. Busted reports
|
94
|
+
when code increments these counters.
|
84
95
|
test_files:
|
96
|
+
- test/busted/current_process_test.rb
|
85
97
|
- test/busted_test.rb
|
86
98
|
- test/test_helper.rb
|