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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2d498f85fa66fcf216da0200686a591694f7378a
4
- data.tar.gz: a486884726f18969c55dfe0f0961e9b07a6dd79e
3
+ metadata.gz: ca3f17fb6e19fab0e2e5604ff88570507f8c1224
4
+ data.tar.gz: 15884ecda376d2f29feabd868d7d41de6bc25024
5
5
  SHA512:
6
- metadata.gz: 412b16c7e7729d9b41aed435d9fd072ed0ce4723fcf202288d2ddc391eebb7f1fe6a08b0f33f8825c83b0d7230b7463d8c102351076902ae106bd10b2a110135
7
- data.tar.gz: 7bd33efe42c6717c8395cb5be99242d4eeaafeab2cc8ee4e4f83a376c5ddb189b25d543e3c253328c173dbe96f865f52ae35c38699249271083b7f332544dde1
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 test_cache_with_new_constant`
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
- ***Requires MRI Ruby 2.1.0dev***
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 Pizza
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 pizza
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
- PIZZA = "pizza"
51
+ STOUT = "stout"
37
52
  end
38
53
  #=> true
39
- ```
40
-
41
- *Class Cache*
42
54
 
43
- ```ruby
44
- Busted.class_cache? do
45
- class Beer
46
- end
55
+ Busted.constant_cache_invalidations do
56
+ CHEESE = "cheese"
47
57
  end
48
- #=> true
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
@@ -3,7 +3,7 @@ require "rake/testtask"
3
3
 
4
4
  Rake::TestTask.new do |t|
5
5
  t.libs << "test"
6
- t.pattern = "test/*_test.rb"
6
+ t.pattern = "test/**/*_test.rb"
7
7
  end
8
8
 
9
9
  task default: :test
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 = %q{Find code that busts the Ruby cache.}
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
- Busted reports when code increments these counters thereby busting the cache.
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 = [".ruby-version", "CONTRIBUTING.md", "Gemfile",
20
- "LICENSE.txt", "README.md", "Rakefile",
21
- "busted.gemspec", "lib/busted.rb",
22
- "lib/busted/version.rb", "test/busted_test.rb",
23
- "test/test_helper.rb"]
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/version"
1
+ require "busted/profiler"
2
2
 
3
3
  module Busted
4
4
  extend self
5
5
 
6
- def cache?(serial = nil, &blk)
7
- starting = count serial
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 method_cache?(&blk)
14
- cache? :method, &blk
10
+ def method_cache_invalidations(&block)
11
+ run(&block)[:invalidations][:method]
15
12
  end
16
13
 
17
- def constant_cache?(&blk)
18
- cache? :constant, &blk
14
+ def constant_cache_invalidations(&block)
15
+ run(&block)[:invalidations][:constant]
19
16
  end
20
17
 
21
- def class_cache?(&blk)
22
- cache? :class, &blk
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
- private
27
+ def method_cache?(&block)
28
+ cache? :method, &block
29
+ end
26
30
 
27
- def count(serial)
28
- stat = RubyVM.stat
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
@@ -1,3 +1,3 @@
1
1
  module Busted
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -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 test_responds_to_cache?
5
- assert Busted.respond_to? :cache?
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 test_responds_to_method_cache?
9
- assert Busted.respond_to? :method_cache?
11
+ def test_cache_invalidations_requires_block
12
+ assert_raises LocalJumpError do
13
+ Busted.run
14
+ end
10
15
  end
11
16
 
12
- def test_responds_to_constant_cache?
13
- assert Busted.respond_to? :constant_cache?
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 test_block_required
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 test_empty_block
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 test_cache_with_addition
147
+ def test_cache_predicate_with_addition
29
148
  refute Busted.cache? { 1 + 1 }
30
149
  end
31
150
 
32
- def test_method_cache_with_addition
151
+ def test_method_cache_predicate_with_addition
33
152
  refute Busted.method_cache? { 1 + 1 }
34
153
  end
35
154
 
36
- def test_constant_cache_with_addition
155
+ def test_constant_cache_predicate_with_addition
37
156
  refute Busted.constant_cache? { 1 + 1 }
38
157
  end
39
158
 
40
- def test_class_cache_with_addition
41
- refute Busted.class_cache? { 1 + 1 }
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 test_cache_with_new_constant
45
- assert Busted.cache? { self.class.const_set :"FOO", "foo" }
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 test_method_cache_with_new_constant
49
- refute Busted.method_cache? { self.class.const_set :"BAR", "bar" }
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 test_constant_cache_with_new_constant
53
- assert Busted.constant_cache? { self.class.const_set :"BAZ", "baz" }
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 test_class_cache_with_new_constant
57
- refute Busted.class_cache? { self.class.const_set :"BEER", "beer" }
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 test_cache_with_new_method
61
- assert Busted.cache? { Object.class_exec { def foo; end } }
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 test_method_cache_with_new_method
65
- assert Busted.method_cache? { Object.class_exec { def bar; end } }
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 test_constant_cache_with_new_method
69
- refute Busted.constant_cache? { Object.class_exec { def baz; end } }
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 test_class_cache_with_new_method
73
- refute Busted.class_cache? { Object.class_exec { def beer; end } }
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
- def test_cache_with_new_class
77
- assert Busted.cache? { Object.class_eval %q{class PierRatPorter; end} }
78
- end
195
+ if Busted::Tracer.exists? && Busted::CurrentProcess.privileged?
79
196
 
80
- def test_method_cache_with_new_class
81
- refute Busted.method_cache? { Object.class_eval %q{class MidnightExpression; end} }
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 test_constant_cache_with_new_class
85
- assert Busted.constant_cache? { Object.class_eval %q{class SantasLittleHelper; end} }
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 test_class_cache_with_new_class
89
- assert Busted.class_cache? { Object.class_eval %q{class TStreetWheat; end} }
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.2
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-08 00:00:00.000000000 Z
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. Busted
83
- reports when code increments these counters thereby busting the cache.
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