cleanroom 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ require 'rspec'
2
+ require 'cleanroom'
3
+
4
+ RSpec.configure do |config|
5
+ config.filter_run(focus: true)
6
+ config.run_all_when_everything_filtered = true
7
+
8
+ # Force the expect syntax
9
+ config.expect_with :rspec do |c|
10
+ c.syntax = :expect
11
+ end
12
+
13
+ # Create and clear tmp_path on each run
14
+ config.before(:each) do
15
+ FileUtils.rm_rf(tmp_path)
16
+ FileUtils.mkdir_p(tmp_path)
17
+ end
18
+
19
+ # Run specs in a random order
20
+ config.order = 'random'
21
+ end
22
+
23
+ #
24
+ # The path on disk to the temporary directory.
25
+ #
26
+ # @param [String, Array<String>] paths
27
+ # the extra path parameters to join
28
+ #
29
+ # @return [String]
30
+ #
31
+ def tmp_path(*paths)
32
+ root = File.expand_path('../..', __FILE__)
33
+ File.join('tmp', *paths)
34
+ end
@@ -0,0 +1,265 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cleanroom do
4
+ let(:klass) do
5
+ Class.new do
6
+ include Cleanroom
7
+
8
+ def exposed_method
9
+ @called = true
10
+ end
11
+ expose :exposed_method
12
+
13
+ def unexposed_method; end
14
+ end
15
+ end
16
+
17
+ let(:instance) { klass.new }
18
+
19
+ describe '.included' do
20
+ let(:klass) { Class.new { include Cleanroom } }
21
+
22
+ it 'extends the ClassMethods' do
23
+ expect(klass).to be_a(Cleanroom::ClassMethods)
24
+ end
25
+
26
+ it 'includes the InstanceMethods' do
27
+ expect(instance).to be_a(Cleanroom::InstanceMethods)
28
+ end
29
+ end
30
+
31
+ describe '.extended' do
32
+ let(:klass) { Class.new { extend Cleanroom } }
33
+
34
+ it 'extends the ClassMethods' do
35
+ expect(klass).to be_a(Cleanroom::ClassMethods)
36
+ end
37
+
38
+ it 'includes the InstanceMethods' do
39
+ expect(instance).to be_a(Cleanroom::InstanceMethods)
40
+ end
41
+ end
42
+
43
+ describe '.evaluate_file' do
44
+ let(:path) { '/path/to/file' }
45
+ let(:contents) { 'contents' }
46
+
47
+ before do
48
+ allow(File).to receive(:expand_path)
49
+ .with(path)
50
+ .and_return(path)
51
+
52
+ allow(IO).to receive(:read)
53
+ .with(path)
54
+ .and_return(contents)
55
+
56
+ allow(klass).to receive(:evaluate)
57
+ end
58
+
59
+ it 'gets the absolute path to the file' do
60
+ expect(File).to receive(:expand_path).with(path).once
61
+ klass.evaluate_file(instance, path)
62
+ end
63
+
64
+ it 'reads the contents to a string' do
65
+ expect(IO).to receive(:read).with(path).once
66
+ klass.evaluate_file(instance, path)
67
+ end
68
+
69
+ it 'evaluates the contents' do
70
+ expect(klass).to receive(:evaluate).with(instance, contents, path, 1).once
71
+ klass.evaluate_file(instance, path)
72
+ end
73
+ end
74
+
75
+ describe '.evaluate' do
76
+ let(:cleanroom) { double('Cleanroom.cleanroom') }
77
+ let(:cleanroom_instance) { double('Cleanroom.cleanroom_instance') }
78
+
79
+ let(:string) { '"hello"' }
80
+
81
+ before do
82
+ allow(cleanroom).to receive(:new)
83
+ .with(instance)
84
+ .and_return(cleanroom_instance)
85
+
86
+ allow(cleanroom_instance).to receive(:instance_eval)
87
+
88
+ allow(klass).to receive(:cleanroom)
89
+ .and_return(cleanroom)
90
+ end
91
+
92
+ it 'creates a new cleanroom object' do
93
+ expect(cleanroom).to receive(:new).with(instance).once
94
+ klass.evaluate(instance, string)
95
+ end
96
+
97
+ it 'evaluates against the new cleanroom object' do
98
+ expect(cleanroom_instance).to receive(:instance_eval).with(string).once
99
+ klass.evaluate(instance, string)
100
+ end
101
+ end
102
+
103
+ describe '.expose' do
104
+ let(:klass) do
105
+ Class.new do
106
+ include Cleanroom
107
+
108
+ def public_method; end
109
+
110
+ protected
111
+ def protected_method; end
112
+
113
+ private
114
+ def private_method; end
115
+ end
116
+ end
117
+
118
+ it 'exposes the method when it is public' do
119
+ expect { klass.expose(:public_method) }.to_not raise_error
120
+ expect(klass.exposed_methods).to include(:public_method)
121
+ end
122
+
123
+ it 'raises an exception if the method is not defined' do
124
+ expect { klass.expose(:no_method) }.to raise_error(NameError)
125
+ end
126
+
127
+ it 'raises an exception if the method is protected' do
128
+ expect { klass.expose(:protected_method) }.to raise_error(NameError)
129
+ end
130
+
131
+ it 'raises an exception if the method is private' do
132
+ expect { klass.expose(:private_method) }.to raise_error(NameError)
133
+ end
134
+ end
135
+
136
+ describe '.exposed_methods' do
137
+ it 'returns a hash' do
138
+ expect(klass.exposed_methods).to be_a(Hash)
139
+ end
140
+ end
141
+
142
+ describe '.cleanroom' do
143
+ let(:klass) do
144
+ Class.new do
145
+ include Cleanroom
146
+
147
+ def method_1
148
+ @method_1 = true
149
+ end
150
+ expose :method_1
151
+
152
+ def method_2
153
+ @method_2 = true
154
+ end
155
+ expose :method_2
156
+ end
157
+ end
158
+
159
+ it 'creates a new anonymous class each time' do
160
+ a, b = klass.send(:cleanroom), klass.send(:cleanroom)
161
+ expect(a).to_not be(b)
162
+ end
163
+
164
+ it 'creates a method for each exposed one on the proxy object' do
165
+ cleanroom = klass.send(:cleanroom)
166
+
167
+ expect(cleanroom).to be_public_method_defined(:method_1)
168
+ expect(cleanroom).to be_public_method_defined(:method_2)
169
+ end
170
+
171
+ it 'calls the proxied method' do
172
+ cleanroom = klass.send(:cleanroom).new(instance)
173
+ cleanroom.method_1
174
+ cleanroom.method_2
175
+
176
+ expect(instance.instance_variable_get(:@method_1)).to be(true)
177
+ expect(instance.instance_variable_get(:@method_2)).to be(true)
178
+ end
179
+
180
+ it 'prevents calls to the instance directly' do
181
+ cleanroom = klass.send(:cleanroom).new(instance)
182
+ expect {
183
+ cleanroom.__instance__
184
+ }.to raise_error(Cleanroom::InaccessibleError)
185
+
186
+ expect {
187
+ cleanroom.send(:__instance__)
188
+ }.to raise_error(Cleanroom::InaccessibleError)
189
+ end
190
+ end
191
+
192
+ describe '#evaluate_file' do
193
+ let(:path) { '/path/to/file' }
194
+
195
+ before do
196
+ allow(klass).to receive(:evaluate_file)
197
+ .with(instance, path)
198
+ end
199
+
200
+ it 'delegates to the class method' do
201
+ expect(klass).to receive(:evaluate_file).with(instance, path)
202
+ instance.evaluate_file(path)
203
+ end
204
+
205
+ it 'returns self' do
206
+ expect(instance.evaluate_file(path)).to be(instance)
207
+ end
208
+ end
209
+
210
+ describe '#evaluate' do
211
+ let(:string) { '"hello"' }
212
+
213
+ before do
214
+ allow(klass).to receive(:evaluate)
215
+ .with(instance, string)
216
+ end
217
+
218
+ it 'delegates to the class method' do
219
+ expect(klass).to receive(:evaluate).with(instance, string)
220
+ instance.evaluate(string)
221
+ end
222
+
223
+ it 'returns self' do
224
+ expect(instance.evaluate(string)).to be(instance)
225
+ end
226
+ end
227
+
228
+ context 'when evaluating a DSL subclass' do
229
+ let(:parent) do
230
+ Class.new do
231
+ include Cleanroom
232
+
233
+ def parent_method; end
234
+ expose :parent_method
235
+ end
236
+ end
237
+
238
+ let(:child) do
239
+ Class.new(parent) do
240
+ def child_method; end
241
+ expose :child_method
242
+ end
243
+ end
244
+
245
+ let(:instance) { child.new }
246
+
247
+ it 'inherits the parent DSL methods' do
248
+ expect {
249
+ instance.evaluate("parent_method")
250
+ }.to_not raise_error
251
+ end
252
+
253
+ it 'allows for custom DSL methods' do
254
+ expect {
255
+ instance.evaluate("child_method")
256
+ }.to_not raise_error
257
+ end
258
+
259
+ it 'does not change the parent DSL' do
260
+ expect {
261
+ parent.new.evaluate("child_method")
262
+ }.to raise_error(NameError)
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+ require 'cleanroom/rspec'
3
+
4
+ describe 'RSpec matchers' do
5
+ let(:klass) do
6
+ Class.new do
7
+ include Cleanroom
8
+
9
+ def method_1; end
10
+ expose :method_1
11
+
12
+ def method_2; end
13
+ end
14
+ end
15
+
16
+ let(:instance) { klass.new }
17
+
18
+ describe '#be_an_exposed_method_on' do
19
+ context 'when given a class' do
20
+ it 'is true when the method is exposed' do
21
+ expect(:method_1).to be_an_exposed_method_on(klass)
22
+ end
23
+
24
+ it 'is false when the method exists, but is not exposed' do
25
+ expect(:method_2).to_not be_an_exposed_method_on(klass)
26
+ end
27
+
28
+ it 'is false when the method is not exposed' do
29
+ expect(:method_3).to_not be_an_exposed_method_on(klass)
30
+ end
31
+ end
32
+
33
+ context 'when given an instance' do
34
+ it 'is true when the method is exposed' do
35
+ expect(:method_1).to be_an_exposed_method_on(instance)
36
+ end
37
+
38
+ it 'is false when the method exists, but is not exposed' do
39
+ expect(:method_2).to_not be_an_exposed_method_on(instance)
40
+ end
41
+
42
+ it 'is false when the method is not exposed' do
43
+ expect(:method_3).to_not be_an_exposed_method_on(instance)
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '#have_exposed_method' do
49
+ context 'when given a class' do
50
+ it 'is true when the method is exposed' do
51
+ expect(klass).to have_exposed_method(:method_1)
52
+ end
53
+
54
+ it 'is false when the method exists, but is not exposed' do
55
+ expect(klass).to_not have_exposed_method(:method_2)
56
+ end
57
+
58
+ it 'is false when the method is not exposed' do
59
+ expect(klass).to_not have_exposed_method(:method_3)
60
+ end
61
+ end
62
+
63
+ context 'when given an instance' do
64
+ it 'is true when the method is exposed' do
65
+ expect(instance).to have_exposed_method(:method_1)
66
+ end
67
+
68
+ it 'is false when the method exists, but is not exposed' do
69
+ expect(instance).to_not have_exposed_method(:method_2)
70
+ end
71
+
72
+ it 'is false when the method is not exposed' do
73
+ expect(instance).to_not have_exposed_method(:method_3)
74
+ end
75
+ end
76
+ end
77
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cleanroom
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Seth Vargo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Ruby is an excellent programming language for creating and managing custom
56
+ DSLs, but how can you securely evaluate a DSL while explicitly controlling the methods
57
+ exposed to the user? Our good friends instance_eval and instance_exec are great,
58
+ but they expose all methods - public, protected, and private - to the user. Even
59
+ worse, they expose the ability to accidentally or intentionally alter the behavior
60
+ of the system! The cleanroom pattern is a safer, more convenient, Ruby-like approach
61
+ for limiting the information exposed by a DSL while giving users the ability to
62
+ write awesome code!
63
+ email: sethvargo@gmail.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - ".gitignore"
69
+ - ".travis.yml"
70
+ - CHANGELOG.md
71
+ - Gemfile
72
+ - LICENSE
73
+ - README.md
74
+ - Rakefile
75
+ - cleanroom.gemspec
76
+ - lib/cleanroom.rb
77
+ - lib/cleanroom/errors.rb
78
+ - lib/cleanroom/rspec.rb
79
+ - lib/cleanroom/version.rb
80
+ - spec/functional/cleanroom_spec.rb
81
+ - spec/spec_helper.rb
82
+ - spec/unit/cleanroom_spec.rb
83
+ - spec/unit/rspec_spec.rb
84
+ homepage: https://github.com/sethvargo/cleanroom
85
+ licenses:
86
+ - Apache 2.0
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 1.9.3
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 2.3.0
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: "(More) safely evaluate Ruby DSLs with cleanroom"
108
+ test_files:
109
+ - spec/functional/cleanroom_spec.rb
110
+ - spec/spec_helper.rb
111
+ - spec/unit/cleanroom_spec.rb
112
+ - spec/unit/rspec_spec.rb
113
+ has_rdoc: