busted 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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 [](https://travis-ci.org/simeonwillbanks/busted) [](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
|