cordon 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +13 -0
- data/README.markdown +72 -0
- data/Rakefile +92 -0
- data/VERSION +1 -0
- data/cordon.gemspec +76 -0
- data/lib/cordon.rb +52 -0
- data/lib/cordon/blacklist.rb +30 -0
- data/lib/cordon/sanitaire.rb +21 -0
- data/lib/cordon/violation.rb +9 -0
- data/lib/cordon/whitelist.rb +16 -0
- data/test/framework_integration/minitest_spec_spec.rb +19 -0
- data/test/framework_integration/rspec_spec.rb +19 -0
- data/test/helper.rb +14 -0
- data/test/integration/backtrace_cleaning_test.rb +20 -0
- data/test/integration/cordon_basic_examples_test.rb +39 -0
- data/test/integration/cordon_customization_test.rb +29 -0
- data/test/integration/cordon_default_behavior_test.rb +14 -0
- data/test/integration/cordon_edge_cases_test.rb +82 -0
- data/test/integration/integration_test_helper.rb +16 -0
- metadata +146 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
2
|
+
Version 2, December 2004
|
3
|
+
|
4
|
+
License copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
5
|
+
|
6
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
7
|
+
copies of this license document, and changing it is allowed as long
|
8
|
+
as the name is changed.
|
9
|
+
|
10
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
11
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
12
|
+
|
13
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
data/README.markdown
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Cordon Sanitaire
|
2
|
+
|
3
|
+
From Wikipedia:
|
4
|
+
|
5
|
+
> Cordon sanitaire -- or quarantine line -- is a French phrase that, literally
|
6
|
+
> translated, means "sanitary cordon". Though in French it originally denoted a barrier
|
7
|
+
> implemented to stop the spread of disease, it has often been used in English in a
|
8
|
+
> metaphorical sense to refer to attempts to prevent the spread of an ideology deemed
|
9
|
+
> unwanted or dangerous, such as the containment policy adopted by George F. Kennan
|
10
|
+
> against the Soviet Union.
|
11
|
+
|
12
|
+
I've never been a big fan of the way RSpec adds <code>#should</code> and <code>#should_not</code> to Kernel, but until recently I'd never been able to articulate why. Then I worked on a project that became <a href="https://github.com/geeksam/kookaburra/">Kookaburra</a>, and I found a specific reason to be annoyed.
|
13
|
+
|
14
|
+
Basically, putting <code>#should</code> on all objects gives you the freedom to <s>shoot yourself in the foot</s> put RSpec expectations *anywhere*, not just inside an <code>#it</code> block. So, I went looking for a way to make <code>#should</code> explode if it was called outside a specific context.
|
15
|
+
|
16
|
+
After several false starts and horrible ideas, I've got something that actually isn't too bad.
|
17
|
+
|
18
|
+
Cordon makes specs look like this:
|
19
|
+
|
20
|
+
<pre><code lang="ruby">describe "a toggle switch" do
|
21
|
+
it "should be on when flipped up" do
|
22
|
+
toggle = ToggleSwitch.new
|
23
|
+
toggle.flip_up
|
24
|
+
<em><strong>assert_that</strong></em>(toggle).should be_on
|
25
|
+
end
|
26
|
+
end</code></pre>
|
27
|
+
|
28
|
+
Cordon lets you declare certain methods as "off-limits" to casual code. Any calls to blacklisted methods will raise a Cordon::Violation exception -- <em>unless</em> they're called on the object thats passed as the single argument to <code>Object#assert\_that</code>, as shown above. What <code>#assert\_that</code> does is temporarily add its argument to a whitelist. This effectively gives that object permission to call any blacklisted method once (and only once!).
|
29
|
+
|
30
|
+
## Quick setup for popular testing frameworks
|
31
|
+
|
32
|
+
Currently, Cordon ships with shorthand configuration items for RSpec and MiniTest::Spec. You can set these up like so:
|
33
|
+
|
34
|
+
### RSpec
|
35
|
+
<pre><code lang="ruby">require 'rspec'
|
36
|
+
Cordon.embargo :rspec</code></pre>
|
37
|
+
|
38
|
+
### MiniTest::Spec
|
39
|
+
<pre><code lang="ruby">require 'minitest/autorun'
|
40
|
+
Cordon.embargo :minitest_spec</code></pre>
|
41
|
+
|
42
|
+
In both examples, note that Cordon's <code>.embargo</code> method must be called *after* the test framework has been loaded.
|
43
|
+
|
44
|
+
|
45
|
+
## TODO
|
46
|
+
|
47
|
+
- Write integration macros (and tests, obviously) for various spec frameworks:
|
48
|
+
- <s>RSpec</s>
|
49
|
+
- <s>MiniTest::Spec</s>
|
50
|
+
- <s>Yoda</s> <em>(Actually, Yoda appears to abuse Ruby syntax badly enough that it may not work with Cordon.)</em>
|
51
|
+
- ?
|
52
|
+
- <s>Add a declarative API to customize the name of the function that wraps assertions</s>
|
53
|
+
- probably some other stuff I can't think of at the moment
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
## Contributing to Cordon
|
58
|
+
|
59
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
60
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
61
|
+
* Fork the project.
|
62
|
+
* Start a feature/bugfix branch.
|
63
|
+
* Commit and push until you are happy with your contribution.
|
64
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
65
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
## Copyright
|
70
|
+
|
71
|
+
Copyright (c) 2012 Sam Livingston-Gray. See LICENSE.txt for
|
72
|
+
further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
require 'jeweler'
|
17
|
+
Jeweler::Tasks.new do |gem|
|
18
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
19
|
+
gem.name = "cordon"
|
20
|
+
gem.homepage = "http://github.com/geeksam/cordon"
|
21
|
+
gem.license = "WTFPL"
|
22
|
+
gem.summary = %Q{Cordon Sanitaire: hygienic handling of infectious methods}
|
23
|
+
gem.description = %Q{A bit of an experiment, really}
|
24
|
+
gem.email = "geeksam@gmail.com"
|
25
|
+
gem.authors = ["Sam Livingston-Gray"]
|
26
|
+
# dependencies defined in Gemfile
|
27
|
+
|
28
|
+
gem.files.exclude *%w[ .document .rvmrc Gemfile Gemfile.lock]
|
29
|
+
end
|
30
|
+
Jeweler::RubygemsDotOrgTasks.new
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
require 'rake/testtask'
|
35
|
+
require 'rspec/core/rake_task'
|
36
|
+
namespace :test do
|
37
|
+
desc 'Run all tests'
|
38
|
+
task :all => ['test:integration', 'test:framework_integration']
|
39
|
+
|
40
|
+
desc 'Run integration tests'
|
41
|
+
Rake::TestTask.new(:integration) do |t|
|
42
|
+
t.pattern = 'test/integration/*_test.rb'
|
43
|
+
t.verbose = true
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'Run all framework integration tests'
|
47
|
+
task :framework_integration => ['test:framework:rspec', 'test:framework:minitest_spec']
|
48
|
+
|
49
|
+
namespace :framework do
|
50
|
+
RSpec::Core::RakeTask.new(:rspec) do |t|
|
51
|
+
t.pattern = 'test/framework_integration/rspec_spec.rb'
|
52
|
+
t.verbose = true
|
53
|
+
end
|
54
|
+
|
55
|
+
Rake::TestTask.new(:minitest_spec) do |t|
|
56
|
+
t.pattern = 'test/framework_integration/minitest_spec_spec.rb'
|
57
|
+
t.verbose = true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
task :default => ['test:all']
|
63
|
+
|
64
|
+
|
65
|
+
require 'rdoc/task'
|
66
|
+
Rake::RDocTask.new do |rdoc|
|
67
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
68
|
+
|
69
|
+
rdoc.rdoc_dir = 'rdoc'
|
70
|
+
rdoc.title = "cordon #{version}"
|
71
|
+
rdoc.rdoc_files.include('README*')
|
72
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
desc 'Quick and dirty SLOC counts'
|
77
|
+
task :stats do
|
78
|
+
sloc_count = lambda do |glob_expression|
|
79
|
+
glob_expression = File.join(File.dirname(__FILE__), glob_expression)
|
80
|
+
lines_in_file = lambda { |filename| File.open(filename, 'r').readlines.reject { |line| line =~ /^\s*(#|$)/ }.length }
|
81
|
+
n = Dir.glob(glob_expression).inject(0) { |n, f| n + lines_in_file[f] }
|
82
|
+
n.to_f
|
83
|
+
end
|
84
|
+
lib = sloc_count['lib/**/*.rb']
|
85
|
+
test = sloc_count['test/**/*_{test,spec}.rb']
|
86
|
+
puts <<-EOF
|
87
|
+
Cordon code stats
|
88
|
+
Lib LOC: #{ '%3d' % lib }
|
89
|
+
Test LOC: #{ '%3d' % test }
|
90
|
+
Lib/test: #{ '%.1f' % (lib/test) }
|
91
|
+
EOF
|
92
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.2
|
data/cordon.gemspec
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{cordon}
|
8
|
+
s.version = "0.2.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = [%q{Sam Livingston-Gray}]
|
12
|
+
s.date = %q{2012-03-09}
|
13
|
+
s.description = %q{A bit of an experiment, really}
|
14
|
+
s.email = %q{geeksam@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"LICENSE.txt",
|
21
|
+
"README.markdown",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"cordon.gemspec",
|
25
|
+
"lib/cordon.rb",
|
26
|
+
"lib/cordon/blacklist.rb",
|
27
|
+
"lib/cordon/sanitaire.rb",
|
28
|
+
"lib/cordon/violation.rb",
|
29
|
+
"lib/cordon/whitelist.rb",
|
30
|
+
"test/framework_integration/minitest_spec_spec.rb",
|
31
|
+
"test/framework_integration/rspec_spec.rb",
|
32
|
+
"test/helper.rb",
|
33
|
+
"test/integration/backtrace_cleaning_test.rb",
|
34
|
+
"test/integration/cordon_basic_examples_test.rb",
|
35
|
+
"test/integration/cordon_customization_test.rb",
|
36
|
+
"test/integration/cordon_default_behavior_test.rb",
|
37
|
+
"test/integration/cordon_edge_cases_test.rb",
|
38
|
+
"test/integration/integration_test_helper.rb"
|
39
|
+
]
|
40
|
+
s.homepage = %q{http://github.com/geeksam/cordon}
|
41
|
+
s.licenses = [%q{WTFPL}]
|
42
|
+
s.require_paths = [%q{lib}]
|
43
|
+
s.rubygems_version = %q{1.8.6}
|
44
|
+
s.summary = %q{Cordon Sanitaire: hygienic handling of infectious methods}
|
45
|
+
|
46
|
+
if s.respond_to? :specification_version then
|
47
|
+
s.specification_version = 3
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
50
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
51
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
52
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
53
|
+
s.add_development_dependency(%q<mocha>, ["~> 0.10.3"])
|
54
|
+
s.add_development_dependency(%q<ruby-debug19>, [">= 0"])
|
55
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
56
|
+
s.add_development_dependency(%q<minitest>, [">= 0"])
|
57
|
+
else
|
58
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
59
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
60
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
61
|
+
s.add_dependency(%q<mocha>, ["~> 0.10.3"])
|
62
|
+
s.add_dependency(%q<ruby-debug19>, [">= 0"])
|
63
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
64
|
+
s.add_dependency(%q<minitest>, [">= 0"])
|
65
|
+
end
|
66
|
+
else
|
67
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
68
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
69
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
70
|
+
s.add_dependency(%q<mocha>, ["~> 0.10.3"])
|
71
|
+
s.add_dependency(%q<ruby-debug19>, [">= 0"])
|
72
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
73
|
+
s.add_dependency(%q<minitest>, [">= 0"])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
data/lib/cordon.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Require ALL the sources!
|
2
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'cordon/*.rb')).each { |f| require f }
|
3
|
+
|
4
|
+
|
5
|
+
# Protect ALL the objects!
|
6
|
+
class Object
|
7
|
+
include Cordon::Sanitaire
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
# Patch ALL the monkeys!
|
12
|
+
module Kernel
|
13
|
+
# This bit of metaprogramming replaces a blacklisted method with a hook that calls back
|
14
|
+
# into the #__cordon__call_method__ defined in Cordon::Sanitaire (and, therefore, mixed in to Object)
|
15
|
+
def __cordon__wrap_method__(method)
|
16
|
+
# Take advantage of the fact that the block passed to define_method is a closure,
|
17
|
+
# so we can find the blacklisted method quickly.
|
18
|
+
# Use a name unlikely to collide in the object's own binding.
|
19
|
+
__cordon__receiver__ = self
|
20
|
+
define_method(method) do |*args, &b|
|
21
|
+
subject = __cordon__receiver__
|
22
|
+
__cordon__call_method__(subject, method, *args, &b)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
module Cordon
|
29
|
+
# Declare specific methods as off-limits
|
30
|
+
def self.blacklist(subject, methods)
|
31
|
+
methods.each do |method|
|
32
|
+
Blacklist.blacklist_method(subject, method.to_sym)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Shorthand for blacklisting the undesirable methods in specific frameworks
|
37
|
+
def self.embargo(framework)
|
38
|
+
case framework
|
39
|
+
when :rspec
|
40
|
+
blacklist Kernel, [:should, :should_not]
|
41
|
+
when :minitest_spec
|
42
|
+
must_and_wont = MiniTest::Expectations.instance_methods.map(&:to_s).select {|e| e =~ /^(must|wont)_/}
|
43
|
+
blacklist MiniTest::Expectations, must_and_wont
|
44
|
+
else
|
45
|
+
raise "I don't know how to embargo #{framework}!"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.wrap_assertions_with(custom_method_name)
|
50
|
+
Sanitaire.wrap_assertions_with(custom_method_name)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Cordon
|
2
|
+
module Blacklist
|
3
|
+
TheList = {}
|
4
|
+
|
5
|
+
def self.blacklist_method(subject, method)
|
6
|
+
return if unbound_method(subject, method) # be idempotent
|
7
|
+
|
8
|
+
# Unbind the original method, and replace it with a wrapper that
|
9
|
+
# checks for permission before binding and calling the original
|
10
|
+
Blacklist.unbind_method(subject, method)
|
11
|
+
subject.__cordon__wrap_method__(method)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.invoke_method(instance, subject, method, *args, &b)
|
15
|
+
um = unbound_method(subject, method)
|
16
|
+
um.bind(instance).call(*args, &b)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def self.unbind_method(subject, method)
|
22
|
+
um = subject.instance_method(method) # will raise NameError if the method doesn't exist
|
23
|
+
TheList[[subject, method]] = um
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.unbound_method(subject, method)
|
27
|
+
TheList[[subject, method]]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Cordon
|
2
|
+
module Sanitaire
|
3
|
+
|
4
|
+
def __cordon__assertion_wrapper__(predicate)
|
5
|
+
::Cordon::Whitelist.admit_one(predicate)
|
6
|
+
return predicate
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.wrap_assertions_with(custom_method_name)
|
10
|
+
alias_method custom_method_name, :__cordon__assertion_wrapper__
|
11
|
+
end
|
12
|
+
wrap_assertions_with :assert_that
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def __cordon__call_method__(subject, method, *args, &b)
|
17
|
+
::Cordon::Whitelist.check_permissions(self, subject, method, *args)
|
18
|
+
::Cordon::Blacklist.invoke_method(self, subject, method, *args, &b)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Cordon
|
2
|
+
module Whitelist
|
3
|
+
TheList = []
|
4
|
+
|
5
|
+
def self.admit_one(object)
|
6
|
+
TheList << object
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.check_permissions(instance, subject, method, *args)
|
10
|
+
allowed = !! TheList.delete(instance)
|
11
|
+
return if allowed
|
12
|
+
message = '%s#%s(%s)' % [subject, method, args.map(&:inspect).join(', ')]
|
13
|
+
raise ::Cordon::Violation, message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), *%w[.. helper]))
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
Cordon.embargo :minitest_spec
|
5
|
+
|
6
|
+
describe "MiniTest::Spec integration" do
|
7
|
+
it 'works just fine if you adhere to the rules' do
|
8
|
+
assert_that(42).must_equal 42
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'explodes if you use standard^H^H evil MiniTest::Spec idiom' do
|
12
|
+
begin
|
13
|
+
42.must_equal 6*9
|
14
|
+
flunk "This line should not be executed"
|
15
|
+
rescue Cordon::Violation
|
16
|
+
# all is well; carry on
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), *%w[.. helper]))
|
2
|
+
require 'rspec'
|
3
|
+
|
4
|
+
Cordon.embargo :rspec
|
5
|
+
|
6
|
+
describe "RSpec integration" do
|
7
|
+
it 'works just fine if you adhere to the rules' do
|
8
|
+
assert_that(42).should == 42
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'explodes if you use standard^H^H evil RSpec idiom' do
|
12
|
+
begin
|
13
|
+
42.should == 6*9
|
14
|
+
raise "This line should not be executed"
|
15
|
+
rescue Cordon::Violation
|
16
|
+
# all is well; carry on
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'ruby-debug'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
require 'cordon'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'integration_test_helper')
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
def verboten_method_for_backtrace
|
5
|
+
raise VerbotenMethodCallReachedKernel
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
Cordon.blacklist Kernel, [:verboten_method_for_backtrace]
|
10
|
+
|
11
|
+
# Cordon will remove its own method calls from the backtrace; hopefully this will reduce confusion somewhat
|
12
|
+
class BacktraceCleaningUnitTest < CordonUnitTest
|
13
|
+
def test_cordon_lines_are_removed_from_violation_backtrace
|
14
|
+
foo.verboten_method_for_backtrace
|
15
|
+
rescue Cordon::Violation => e
|
16
|
+
failure_message = "cordon.rb appears in first line of backtrace:\n"
|
17
|
+
failure_message << (e.backtrace[0..3] + ['(rest of backtrace omitted)']).map { |e| ' ' + e }.join("\n")
|
18
|
+
refute e.backtrace.first.include?('cordon.rb:'), failure_message
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'integration_test_helper')
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
def verboten_method
|
5
|
+
raise VerbotenMethodCallReachedKernel
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# You can tell Cordon to blacklist certain methods on all objects.
|
10
|
+
Cordon.blacklist Kernel, [:verboten_method]
|
11
|
+
|
12
|
+
class CordonBasicExamples < CordonUnitTest
|
13
|
+
# Calling #verboten_method on an object will raise an exception...
|
14
|
+
def test_raises_exception_when_calling__verboten_method
|
15
|
+
assert_raise(Cordon::Violation) do
|
16
|
+
foo.verboten_method
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# ...unless you explicitly wrap that object in an #assert_that call
|
21
|
+
def test_allows_explicit_calls_to__verboten_method
|
22
|
+
assert_raises(VerbotenMethodCallReachedKernel) do
|
23
|
+
assert_that(foo).verboten_method
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# ...every time!
|
28
|
+
def test_permission_provided_by_assert_that_only_works_once
|
29
|
+
assert_raise(Cordon::Violation) do
|
30
|
+
begin
|
31
|
+
assert_that(foo).verboten_method
|
32
|
+
rescue VerbotenMethodCallReachedKernel
|
33
|
+
# carry on
|
34
|
+
end
|
35
|
+
foo.verboten_method
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'integration_test_helper')
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
def should_probably(*_)
|
5
|
+
raise VerbotenMethodCallReachedKernel
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
Cordon.blacklist Kernel, [:should_probably]
|
10
|
+
|
11
|
+
|
12
|
+
# You can specify a different method than #assert_that as your custom assertion wrapper
|
13
|
+
Cordon.wrap_assertions_with :if_you_would_be_so_kind
|
14
|
+
|
15
|
+
class CordonCustomizationExamples < CordonUnitTest
|
16
|
+
def test_raises_exception_when_calling__verboten_method
|
17
|
+
assert_raise(Cordon::Violation) do
|
18
|
+
foo.should_probably :explode
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# ...unless you explicitly wrap that object in an #assert_that call
|
23
|
+
def test_allows_explicit_calls_to__verboten_method
|
24
|
+
assert_raises(VerbotenMethodCallReachedKernel) do
|
25
|
+
if_you_would_be_so_kind(foo).should_probably :work # but only if you feel like it
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'integration_test_helper')
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
def verboten_method_not_on_cordon_watch_list
|
5
|
+
raise VerbotenMethodCallReachedKernel
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# By default, Cordon does nothing!
|
10
|
+
class CordonDefaultBehaviorUnitTest < CordonUnitTest
|
11
|
+
def test_allows_calls_to__verboten_method_not_on_cordon_watch_list
|
12
|
+
assert_raises(VerbotenMethodCallReachedKernel) { foo.verboten_method_not_on_cordon_watch_list }
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'integration_test_helper')
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
def verboten_method_with_args(*args)
|
5
|
+
raise "No args given!" if args.empty?
|
6
|
+
raise VerbotenMethodCallReachedKernel, args.inspect
|
7
|
+
end
|
8
|
+
|
9
|
+
def verboten_method_with_block(*_)
|
10
|
+
raise "Block expected!" unless block_given?
|
11
|
+
yield
|
12
|
+
raise VerbotenMethodCallReachedKernel
|
13
|
+
end
|
14
|
+
|
15
|
+
def verboten_method_with_args_and_block_with_args(options = {})
|
16
|
+
raise "No args given!" if options.empty?
|
17
|
+
raise "Block expected!" unless block_given?
|
18
|
+
arg_for_block = options.fetch(:arg_for_block)
|
19
|
+
yield arg_for_block
|
20
|
+
raise VerbotenMethodCallReachedKernel, options.inspect
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Cordon.blacklist Kernel, [:verboten_method, :verboten_method_with_args, :verboten_method_with_block]
|
25
|
+
|
26
|
+
class CordonEdgeCaseTests < CordonUnitTest
|
27
|
+
# Arguments to verboten methods are passed along as one might expect
|
28
|
+
def test_raises_exception_when_calling__verboten_method_with_args
|
29
|
+
args = [:foo, :bar, :baz]
|
30
|
+
begin
|
31
|
+
line = __LINE__; foo.verboten_method_with_args(*args)
|
32
|
+
rescue Cordon::Violation => e
|
33
|
+
expected = "Kernel#verboten_method_with_args(#{args.map(&:inspect).join(', ')})"
|
34
|
+
assert_equal expected, e.message
|
35
|
+
rescue VerbotenMethodCallReachedKernel
|
36
|
+
flunk "foo.verboten_method_with_args(#{args.map(&:inspect).join(', ')}) should not be allowed! (See line #{line})"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_allows_explicit_calls_to__verboten_method_with_args
|
41
|
+
assert_raises(VerbotenMethodCallReachedKernel) do
|
42
|
+
assert_that(foo).verboten_method_with_args(:foo, :bar, :baz)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# And blocks given to verboten methods are yielded to as one might expect
|
47
|
+
# Calling #verboten_method_with_block should raise an exception
|
48
|
+
def test_raises_exception_when_calling__verboten_method_with_block
|
49
|
+
block_was_called = false
|
50
|
+
begin
|
51
|
+
line = __LINE__; foo.verboten_method_with_block() { block_was_called = true }
|
52
|
+
rescue Cordon::Violation => e
|
53
|
+
expected = "Kernel#verboten_method_with_block()"
|
54
|
+
assert_equal expected, e.message
|
55
|
+
assert_equal false, block_was_called, "Block was called, but it shouldn't have been!"
|
56
|
+
rescue VerbotenMethodCallReachedKernel
|
57
|
+
flunk "foo.verboten_method_with_block(&b) should not be allowed! (See line #{line})"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_allows_explicit_calls_to__verboten_method_with_block
|
62
|
+
block_was_called = false
|
63
|
+
assert_raises(VerbotenMethodCallReachedKernel) do
|
64
|
+
assert_that(foo).verboten_method_with_block() { block_was_called = true }
|
65
|
+
end
|
66
|
+
assert block_was_called, "Block wasn't called when it should have been!"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Blocks with args get passed the args they expect.
|
70
|
+
def test_allows_explicit_calls_to__verboten_method_with_args_and_block_with_args
|
71
|
+
block_was_called = false
|
72
|
+
arg_passed_to_block = nil
|
73
|
+
assert_raises(VerbotenMethodCallReachedKernel) do
|
74
|
+
assert_that(foo).verboten_method_with_args_and_block_with_args(:arg_for_block => 42) do |x|
|
75
|
+
block_was_called = true
|
76
|
+
arg_passed_to_block = x
|
77
|
+
end
|
78
|
+
end
|
79
|
+
assert block_was_called, "Block wasn't called when it should have been!"
|
80
|
+
assert_equal 42, arg_passed_to_block
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. helper])
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
module Kernel
|
5
|
+
VerbotenMethodCallReachedKernel = Class.new(Exception)
|
6
|
+
end
|
7
|
+
|
8
|
+
class CordonUnitTest < Test::Unit::TestCase
|
9
|
+
def foo
|
10
|
+
@foo ||= Object.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def refute(predicate, *other_args)
|
14
|
+
assert !predicate, *other_args
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cordon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sam Livingston-Gray
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-09 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rdoc
|
16
|
+
requirement: &2156968780 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.12'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2156968780
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: bundler
|
27
|
+
requirement: &2156967060 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2156967060
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: jeweler
|
38
|
+
requirement: &2156964940 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.8.3
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2156964940
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: mocha
|
49
|
+
requirement: &2156962400 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.10.3
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2156962400
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: ruby-debug19
|
60
|
+
requirement: &2156960720 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *2156960720
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: &2156958500 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *2156958500
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: minitest
|
82
|
+
requirement: &2156956460 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *2156956460
|
91
|
+
description: A bit of an experiment, really
|
92
|
+
email: geeksam@gmail.com
|
93
|
+
executables: []
|
94
|
+
extensions: []
|
95
|
+
extra_rdoc_files:
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.markdown
|
98
|
+
files:
|
99
|
+
- LICENSE.txt
|
100
|
+
- README.markdown
|
101
|
+
- Rakefile
|
102
|
+
- VERSION
|
103
|
+
- cordon.gemspec
|
104
|
+
- lib/cordon.rb
|
105
|
+
- lib/cordon/blacklist.rb
|
106
|
+
- lib/cordon/sanitaire.rb
|
107
|
+
- lib/cordon/violation.rb
|
108
|
+
- lib/cordon/whitelist.rb
|
109
|
+
- test/framework_integration/minitest_spec_spec.rb
|
110
|
+
- test/framework_integration/rspec_spec.rb
|
111
|
+
- test/helper.rb
|
112
|
+
- test/integration/backtrace_cleaning_test.rb
|
113
|
+
- test/integration/cordon_basic_examples_test.rb
|
114
|
+
- test/integration/cordon_customization_test.rb
|
115
|
+
- test/integration/cordon_default_behavior_test.rb
|
116
|
+
- test/integration/cordon_edge_cases_test.rb
|
117
|
+
- test/integration/integration_test_helper.rb
|
118
|
+
homepage: http://github.com/geeksam/cordon
|
119
|
+
licenses:
|
120
|
+
- WTFPL
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ! '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
segments:
|
132
|
+
- 0
|
133
|
+
hash: 2252535489042103113
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
none: false
|
136
|
+
requirements:
|
137
|
+
- - ! '>='
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 1.8.6
|
143
|
+
signing_key:
|
144
|
+
specification_version: 3
|
145
|
+
summary: ! 'Cordon Sanitaire: hygienic handling of infectious methods'
|
146
|
+
test_files: []
|