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 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