cleanroom 1.0.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.
@@ -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: