rubyless 0.5.0 → 0.6.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.
data/History.txt CHANGED
@@ -1,3 +1,14 @@
1
+ == 0.6.0 2010-07-22
2
+
3
+ * Major enhancements
4
+ * Added 'safe_eval' method.
5
+ * Added 'safe_eval_string' method.
6
+ * Better handling of syntax errors.
7
+ * Support for constants.
8
+
9
+ * Minor enhancements
10
+ * Added safe methods on Time (compare, add/subtract number).
11
+
1
12
  == 0.5.0 2010-05-27
2
13
 
3
14
  * Major enhancements
@@ -22,6 +22,17 @@ RubyLess::SafeClass.safe_method_for( Number,
22
22
  [:% , Number] => Number, [:"-@"] => Number
23
23
  )
24
24
 
25
+ RubyLess::SafeClass.safe_method_for( Time,
26
+ [:==, Time] => Boolean, [:< , Time] => Boolean, [:> , Time] => Boolean,
27
+ [:<=, Time] => Boolean, [:>=, Time] => Boolean,
28
+ [:- , Number] => Time, [:+ , Number] => Time
29
+ )
30
+
25
31
  RubyLess::SafeClass.safe_method_for( String,
26
32
  [:==, String] => Boolean
27
33
  )
34
+
35
+ RubyLess::SafeClass.safe_method_for( NilClass,
36
+ [:==, String] => Boolean,
37
+ [:==, Number] => Boolean
38
+ )
@@ -1,3 +1,3 @@
1
1
  module RubyLess
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
@@ -10,8 +10,13 @@ module RubyLess
10
10
  PREFIX_OPERATOR = ['-@']
11
11
 
12
12
  def self.translate(string, helper)
13
- sexp = RubyParser.new.parse(string)
14
- self.new(helper).process(sexp)
13
+ if sexp = RubyParser.new.parse(string)
14
+ self.new(helper).process(sexp)
15
+ elsif string.size == 0
16
+ ''
17
+ else
18
+ raise RubyLess::SyntaxError.new("Syntax error")
19
+ end
15
20
  rescue Racc::ParseError => err
16
21
  raise RubyLess::SyntaxError.new(err.message)
17
22
  end
@@ -38,12 +43,26 @@ module RubyLess
38
43
  # send("process_#{method}", exp)
39
44
  end
40
45
 
46
+ def process_const(exp)
47
+ const_name = exp.pop.to_s
48
+ if opts = @helper.respond_to?(:safe_const_type) ? @helper.safe_const_type(const_name) : nil
49
+ t opts[:method], opts
50
+ else
51
+ raise RubyLess::Error.new("Unknown constant '#{const_name}'.")
52
+ end
53
+ end
54
+
41
55
  def process_and(exp)
42
56
  t "(#{process(exp.shift)} and #{process(exp.shift)})", Boolean
43
57
  end
44
58
 
45
59
  def process_or(exp)
46
- t "(#{process(exp.shift)} or #{process(exp.shift)})", Boolean
60
+ left, right = process(exp.shift), process(exp.shift)
61
+ if left.klass == right.klass
62
+ t "(#{left} or #{right})", :class => right.klass, :nil => right.could_be_nil?
63
+ else
64
+ t "(#{left} or #{right})", Boolean
65
+ end
47
66
  end
48
67
 
49
68
  def process_not(exp)
@@ -109,7 +128,7 @@ module RubyLess
109
128
  def process_vcall(exp)
110
129
  var_name = exp.shift
111
130
  unless opts = get_method([var_name], @helper, false)
112
- raise RubyLess::NoMethodError.new("Unknown variable or method '#{var_name}'.")
131
+ raise RubyLess::Error.new("Unknown variable or method '#{var_name}'.")
113
132
  end
114
133
  method = opts[:method]
115
134
  if args = opts[:prepend_args]
@@ -258,7 +277,8 @@ module RubyLess
258
277
  method = opts[:method]
259
278
  arg_list = args ? args.list : []
260
279
 
261
- if receiver.could_be_nil? && opts != SafeClass.safe_method_type_for(NilClass, signature)
280
+ if receiver.could_be_nil? &&
281
+ !(opts == SafeClass.safe_method_type_for(NilClass, signature) && receiver.cond == [receiver])
262
282
  # Do not add a condition if the method applies on nil
263
283
  cond += receiver.cond
264
284
  elsif receiver.literal && (proc = opts[:pre_processor]) && !arg_list.detect {|a| !a.literal}
@@ -73,125 +73,127 @@ module RubyLess
73
73
  end
74
74
  end
75
75
 
76
- def self.included(base)
77
- base.class_eval do
78
-
79
- # Declare safe methods. By providing
80
- #
81
- # The methods hash has the following format:
82
- # signature => return type
83
- # or
84
- # signature => options
85
- # or
86
- # signature => lambda {|h| ... }
87
- #
88
- # The lambda expression will be called with @helper as argument during compilation.
89
- #
90
- # The signature can be either a single symbol or an array containing the method name and type arguments like:
91
- # [:strftime, Time, String]
92
- #
93
- # If your method accepts variable arguments through a Hash, you should declare it with:
94
- # [:img, String, {:mode => String, :max_size => Number}]
95
- #
96
- # Make sure your literal values are of the right type: +:mode+ and +'mode'+ are not the same here.
97
- #
98
- # If the signature is :defaults, the options defined are used as defaults for the other elements defined in the
99
- # same call.
100
- #
101
- # The return type can be a string with the class name or a class.
102
- #
103
- # Options are:
104
- # :class the return type (class name)
105
- # :nil set this to true if the method could return nil
106
- def self.safe_method(methods_hash)
107
- RubyLess::SafeClass.safe_method_for(self, methods_hash)
108
- end
76
+ # Return a safe type from a column
77
+ def self.safe_method_type_for_column(col, is_property = false)
78
+ opts = {}
79
+ opts[:nil] = col.default.nil?
80
+ if col.number?
81
+ opts[:class] = Number
82
+ elsif col.text?
83
+ opts[:class] = String
84
+ else
85
+ opts[:class] = col.klass
86
+ end
87
+ if is_property
88
+ opts[:method] = "prop['#{col.name.gsub("'",'')}']"
89
+ else
90
+ opts[:method] = col.name
91
+ end
109
92
 
110
- # A safe context is simply a safe method that can return nil in some situations. The rest of the
111
- # syntax is the same as #safe_method. We call it a safe context because it enables syntaxes such
112
- # as: if var = my_context(...) ---> enter context.
113
- def self.safe_context(methods_hash)
114
- methods_hash[:defaults] ||= {}
115
- methods_hash[:defaults][:nil] = true
116
- safe_method(methods_hash)
117
- end
93
+ opts
94
+ end
118
95
 
119
- def self.safe_literal_class(hash)
120
- RubyLess::SafeClass.safe_literal_class(hash)
121
- end
96
+ module ClassMethods
122
97
 
123
- # Declare a safe method to access a list of attributes.
124
- # This method should only be used when the class is linked with a database table and provides
125
- # proper introspection to detect types and the possibility of NULL values.
126
- def self.safe_attribute(*attributes)
127
- attributes.each do |att|
128
- if col = columns_hash[att.to_s]
129
- opts = {}
130
- opts[:nil] = col.default.nil?
131
- if col.number?
132
- opts[:class] = Number
133
- elsif col.text?
134
- opts[:class] = String
135
- else
136
- opts[:class] = col.klass
137
- end
138
- safe_method att.to_sym => opts
139
- else
140
- puts "Warning: could not declare safe_attribute '#{att}' (No column with this name found in class #{self})"
141
- end
98
+ # Declare safe methods. By providing
99
+ #
100
+ # The methods hash has the following format:
101
+ # signature => return type
102
+ # or
103
+ # signature => options
104
+ # or
105
+ # signature => lambda {|h| ... }
106
+ #
107
+ # The lambda expression will be called with @helper as argument during compilation.
108
+ #
109
+ # The signature can be either a single symbol or an array containing the method name and type arguments like:
110
+ # [:strftime, Time, String]
111
+ #
112
+ # If your method accepts variable arguments through a Hash, you should declare it with:
113
+ # [:img, String, {:mode => String, :max_size => Number}]
114
+ #
115
+ # Make sure your literal values are of the right type: +:mode+ and +'mode'+ are not the same here.
116
+ #
117
+ # If the signature is :defaults, the options defined are used as defaults for the other elements defined in the
118
+ # same call.
119
+ #
120
+ # The return type can be a string with the class name or a class.
121
+ #
122
+ # Options are:
123
+ # :class the return type (class name)
124
+ # :nil set this to true if the method could return nil
125
+ def safe_method(methods_hash)
126
+ RubyLess::SafeClass.safe_method_for(self, methods_hash)
127
+ end
128
+
129
+ # A safe context is simply a safe method that can return nil in some situations. The rest of the
130
+ # syntax is the same as #safe_method. We call it a safe context because it enables syntaxes such
131
+ # as: if var = my_context(...) ---> enter context.
132
+ def safe_context(methods_hash)
133
+ methods_hash[:defaults] ||= {}
134
+ methods_hash[:defaults][:nil] = true
135
+ safe_method(methods_hash)
136
+ end
137
+
138
+ def safe_literal_class(hash)
139
+ RubyLess::SafeClass.safe_literal_class(hash)
140
+ end
141
+
142
+ # Declare a safe method to access a list of attributes.
143
+ # This method should only be used when the class is linked with a database table and provides
144
+ # proper introspection to detect types and the possibility of NULL values.
145
+ def safe_attribute(*attributes)
146
+ attributes.each do |att|
147
+ if col = columns_hash[att.to_s]
148
+ safe_method att.to_sym => SafeClass.safe_method_type_for_column(col)
149
+ else
150
+ puts "Warning: could not declare safe_attribute '#{att}' (No column with this name found in class #{self})"
142
151
  end
143
152
  end
153
+ end
144
154
 
145
- # Declare a safe method to access a list of properties.
146
- # This method should only be used in conjunction with the Property gem.
147
- def self.safe_property(*properties)
148
- columns = schema.columns
149
- properties.each do |att|
150
- if col = columns[att.to_s]
151
- opts = {}
152
- opts[:nil] = col.default.nil?
153
- if col.number?
154
- opts[:class] = Number
155
- elsif col.text?
156
- opts[:class] = String
157
- else
158
- opts[:class] = col.klass
159
- end
160
- opts[:method] = "prop['#{att.to_s.gsub("'",'')}']"
161
- safe_method att.to_sym => opts
162
- else
163
- puts "Warning: could not declare safe_property '#{att}' (No property column with this name found in class #{self})"
164
- end
155
+ # Declare a safe method to access a list of properties.
156
+ # This method should only be used in conjunction with the Property gem.
157
+ def safe_property(*properties)
158
+ columns = schema.columns
159
+ properties.each do |att|
160
+ if col = columns[att.to_s]
161
+ safe_method att.to_sym => SafeClass.safe_method_type_for_column(col, true)
162
+ else
163
+ puts "Warning: could not declare safe_property '#{att}' (No property column with this name found in class #{self})"
165
164
  end
166
165
  end
166
+ end
167
167
 
168
+ # Declare a safe method for a given class
169
+ def safe_method_for(klass, signature)
170
+ SafeClass.safe_method_for(klass, signature)
171
+ end
168
172
 
169
- # Declare a safe method for a given class
170
- def self.safe_method_for(klass, signature)
171
- SafeClass.safe_method_for(klass, signature)
172
- end
173
+ # Hash of all safe methods defined for the class.
174
+ def safe_methods
175
+ SafeClass.safe_methods_for(self)
176
+ end
173
177
 
174
- # Hash of all safe methods defined for the class.
175
- def self.safe_methods
176
- SafeClass.safe_methods_for(self)
177
- end
178
+ # Return the type if the given signature corresponds to a safe method for the class.
179
+ def safe_method_type(signature)
180
+ SafeClass.safe_method_type_for(self, signature)
181
+ end
178
182
 
179
- # Return the type if the given signature corresponds to a safe method for the class.
180
- def self.safe_method_type(signature)
181
- SafeClass.safe_method_type_for(self, signature)
182
- end
183
+ # Return true if the class is safe (we can call safe_read on its instances)
184
+ def safe_class?
185
+ true
186
+ end
183
187
 
184
- # Return true if the class is safe (we can call safe_read on its instances)
185
- def self.safe_class?
186
- true
187
- end
188
+ # Use this if you want to disable 'safe_read'. This is useful if you provided
189
+ # a mock as return type signature.
190
+ def disable_safe_read
191
+ undef_method(:safe_read)
192
+ end
193
+ end # ClassMethods
188
194
 
189
- # Use this if you want to disable 'safe_read'. This is useful if you provided
190
- # a mock as return type signature.
191
- def self.disable_safe_read
192
- undef_method(:safe_read)
193
- end
194
- end # base.class_eval
195
+ def self.included(base)
196
+ base.extend ClassMethods
195
197
  end # included
196
198
 
197
199
 
@@ -203,12 +205,25 @@ module RubyLess
203
205
  end
204
206
 
205
207
  # Safe attribute reader used when 'safe_readable?' could not be called because the class
206
- # is not known during compile time. FIXME: Is this used anymore ?
208
+ # is not known during compile time.
209
+ # FIXME: Is this used anymore ?
207
210
  def safe_read(key)
208
211
  return "'#{key}' not readable" unless type = self.class.safe_method_type([key])
209
212
  self.send(type[:method])
210
213
  end
211
214
 
215
+ # Evaluate a RubyLess expression. This is just like 'eval' but with safe method checking and typing.
216
+ def safe_eval(code)
217
+ ruby = RubyLessProcessor.translate(code, self)
218
+ eval(ruby)
219
+ end
220
+
221
+ # Evaluate a RubyLess expression. This is just like 'eval' but with safe method checking and typing.
222
+ def safe_eval_string(code)
223
+ ruby = RubyLess.translate_string(code, self)
224
+ eval(ruby)
225
+ end
226
+
212
227
  private
213
228
  def self.build_signature(key)
214
229
  keys = key.kind_of?(Array) ? key : [key]
data/lib/ruby_less.rb CHANGED
@@ -24,6 +24,12 @@ module RubyLess
24
24
 
25
25
  def self.translate(string, helper)
26
26
  RubyLessProcessor.translate(string, helper)
27
+ rescue Exception => err
28
+ if err.kind_of?(RubyLess::Error)
29
+ raise err
30
+ else
31
+ raise RubyLess::Error.new("Error parsing \"#{string}\": #{err.message.strip}")
32
+ end
27
33
  end
28
34
 
29
35
  def self.translate_string(string, helper)
@@ -32,8 +38,12 @@ module RubyLess
32
38
  else
33
39
  TypedString.new(string.inspect, :class => String, :literal => string)
34
40
  end
35
- rescue => err
36
- raise RubyLess::Error.new("Error parsing string \"#{string}\": #{err.message.strip}")
41
+ rescue Exception => err
42
+ if err.kind_of?(RubyLess::Error)
43
+ raise err
44
+ else
45
+ raise RubyLess::Error.new("Error parsing string \"#{string}\": #{err.message.strip}")
46
+ end
37
47
  end
38
48
  end
39
49
 
data/rubyless.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{rubyless}
8
- s.version = "0.5.0"
8
+ s.version = "0.6.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Gaspard Bucher"]
12
- s.date = %q{2010-05-27}
12
+ s.date = %q{2010-07-22}
13
13
  s.description = %q{RubyLess is an interpreter for "safe ruby". The idea is to transform some "unsafe" ruby code into safe, type checked ruby, eventually rewriting some variables or methods.}
14
14
  s.email = %q{gaspard@teti.ch}
15
15
  s.extra_rdoc_files = [
@@ -41,6 +41,7 @@ Gem::Specification.new do |s|
41
41
  "test/RubyLess/errors.yml",
42
42
  "test/RubyLess/hash.yml",
43
43
  "test/RubyLess/string.yml",
44
+ "test/RubyLess/time.yml",
44
45
  "test/RubyLess_test.rb",
45
46
  "test/mock/active_record_mock.rb",
46
47
  "test/mock/dummy_class.rb",
@@ -1,6 +1,6 @@
1
1
  empty:
2
2
  src: ""
3
- tem: null
3
+ tem: ""
4
4
 
5
5
  numbers:
6
6
  src: "id > 45 and (3 > -id or 3+3)"
@@ -184,4 +184,21 @@ build_finder:
184
184
 
185
185
  methods_on_nil:
186
186
  src: 'dictionary[:foo].blank?'
187
- tem: 'get_dict[:foo].blank?'
187
+ tem: 'get_dict[:foo].blank?'
188
+
189
+ equality_on_nil:
190
+ src: 'dictionary[:foo] == "yo"'
191
+ tem: '(get_dict[:foo]=="yo")'
192
+
193
+ or_same_class:
194
+ src: "vowel_count(dictionary[:bar] || 'aeiou')"
195
+ tem: 'vowel_count((get_dict[:bar] or "aeiou"))'
196
+ res: '5'
197
+
198
+ class:
199
+ src: "@foo.kind_of?(Page)"
200
+ tem: "node.kind_of?(Page)"
201
+
202
+ class_map_as_string:
203
+ src: "@foo.kind_of?(Document)"
204
+ tem: "node.is_like?(\"DOC\")"
@@ -55,3 +55,23 @@ hash_arguments_wrong_type:
55
55
  mixed_array:
56
56
  src: "[3, '4']"
57
57
  tem: 'Mixed Array not supported ([Number,String]).'
58
+
59
+ hash_odd_keys_syntax_error:
60
+ src: '{text}'
61
+ tem: 'Syntax error'
62
+
63
+ syntax_error:
64
+ src: "3 * / * 5"
65
+ tem: "/nterminated string meets end of file/"
66
+
67
+ no_lasagna_in_rubyless:
68
+ src: "foo = system"
69
+ tem: "'lasgn' not available in RubyLess."
70
+
71
+ call_on_class:
72
+ src: "Page.name"
73
+ tem: "unknown method 'name()' for 'Page' of type Class."
74
+
75
+ unknown_constant:
76
+ src: "RubyLess"
77
+ tem: "Unknown constant 'RubyLess'."
@@ -0,0 +1,15 @@
1
+ minus:
2
+ src: "now - 4"
3
+ tem: "(Time.now-4)"
4
+
5
+ plus:
6
+ src: "now + 2* 3600"
7
+ tem: "(Time.now+(2*3600))"
8
+
9
+ compare:
10
+ src: "now == now"
11
+ tem: "(Time.now==Time.now)"
12
+
13
+ compare:
14
+ src: "now > now"
15
+ tem: "(Time.now>Time.now)"
@@ -26,6 +26,8 @@ class RubyLessTest < Test::Unit::TestCase
26
26
 
27
27
  safe_method_for Time, [:strftime, String] => String
28
28
 
29
+ safe_method :now => {:method => 'Time.now', :class => Time}
30
+
29
31
  safe_method :@foo => {:class => Dummy, :method => "node"}
30
32
  safe_method :sub => SubDummy
31
33
  safe_method :str => SubString
@@ -46,12 +48,22 @@ class RubyLessTest < Test::Unit::TestCase
46
48
  # methods on nil
47
49
  safe_method_for Object, :blank? => Boolean
48
50
 
51
+ def safe_const_type(constant)
52
+ if constant == 'Page'
53
+ {:method => 'Page', :class => Class}
54
+ elsif constant =~ /^D/
55
+ {:method => constant[0..2].upcase.inspect, :class => String, :literal => constant}
56
+ else
57
+ nil
58
+ end
59
+ end
60
+
49
61
  # Example to dynamically rewrite method calls during compilation
50
62
  def safe_method_type(signature)
51
63
  unless res = super
52
64
  if signature == ['prepend_test', Number]
53
65
  res ={:class => Number, :prepend_args => RubyLess::TypedString.new('10', :class => Number), :method => 'add'}
54
- elsif res = context[:node_class].safe_method_type(signature)
66
+ elsif context && res = context[:node_class].safe_method_type(signature)
55
67
  # try to execute method in the current var "var.method"
56
68
  res = res.call(self, signature) if res.kind_of?(Proc)
57
69
  res = res.merge(:method => "#{context[:node]}.#{res[:method] || signature[0]}")
@@ -4,6 +4,10 @@ module RubyLess
4
4
  @opts = opts
5
5
  end
6
6
 
7
+ def name
8
+ @opts[:name]
9
+ end
10
+
7
11
  def default
8
12
  @opts[:default]
9
13
  end
@@ -42,10 +46,10 @@ module RubyLess
42
46
 
43
47
  class ActiveRecordMock
44
48
  COLUMNS = {
45
- 'format' => ColumnMock.new(:default => '%d.%m.%Y', :type => :text),
46
- 'age' => ColumnMock.new(:default => 5, :type => :float),
47
- 'friend_id' => ColumnMock.new(:type => :integer),
48
- 'log_at' => ColumnMock.new(:type => :datetime),
49
+ 'format' => ColumnMock.new(:default => '%d.%m.%Y', :type => :text, :name => 'format'),
50
+ 'age' => ColumnMock.new(:default => 5, :type => :float, :name => 'age'),
51
+ 'friend_id' => ColumnMock.new(:type => :integer, :name => 'friend_id'),
52
+ 'log_at' => ColumnMock.new(:type => :datetime, :name => 'log_at'),
49
53
  }
50
54
  def self.columns_hash
51
55
  COLUMNS
@@ -16,7 +16,10 @@ class Dummy < RubyLess::ActiveRecordMock
16
16
  :id => {:class => Number, :method => :zip},
17
17
  :name => String,
18
18
  :foo => :bar,
19
- [:width, {:mode => String, :type => String, 'nice' => Boolean}] => String
19
+ [:width, {:mode => String, :type => String, 'nice' => Boolean}] => String,
20
+ [:kind_of?, Class] => Boolean,
21
+ [:kind_of?, String] => {:method => 'is_like?', :class => Boolean}
22
+
20
23
  safe_context :spouse => 'Dummy',
21
24
  :husband => {:class => 'Dummy', :context => {:clever => 'no'}}
22
25
 
@@ -12,5 +12,32 @@ class SafeClassTest < Test::Unit::TestCase
12
12
  should 'have an associated SignatureHash for safe methods' do
13
13
  assert_kind_of RubyLess::SignatureHash, Dummy.safe_methods
14
14
  end
15
- end
15
+ end # A safe model
16
+
17
+ context 'An instance of a safe model' do
18
+ subject do
19
+ Dummy.new
20
+ end
21
+
22
+ context 'on safe_eval' do
23
+ should 'evaluate RubyLess' do
24
+ assert_equal 'Biscotte', subject.safe_eval("dog_name")
25
+ end
26
+
27
+ should 'raise NoMethodError on missing method' do
28
+ assert_raise(RubyLess::NoMethodError) { subject.safe_eval("bad_method('Bp Oil Spill')") }
29
+ end
30
+ end # on safe_eval
31
+
32
+ context 'on safe_eval_string' do
33
+ should 'evaluate RubyLess as dstring' do
34
+ assert_equal 'my Biscotte', subject.safe_eval_string('my #{dog_name}')
35
+ end
36
+
37
+ should 'raise NoMethodError on missing method' do
38
+ assert_raise(RubyLess::NoMethodError) { subject.safe_eval_string("their \#{bad_method('Bp Oil Spill')}") }
39
+ end
40
+ end # on safe_eval
41
+
42
+ end # An instance of a safe model
16
43
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 5
7
+ - 6
8
8
  - 0
9
- version: 0.5.0
9
+ version: 0.6.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Gaspard Bucher
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-05-27 00:00:00 +02:00
17
+ date: 2010-07-22 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -105,6 +105,7 @@ files:
105
105
  - test/RubyLess/errors.yml
106
106
  - test/RubyLess/hash.yml
107
107
  - test/RubyLess/string.yml
108
+ - test/RubyLess/time.yml
108
109
  - test/RubyLess_test.rb
109
110
  - test/mock/active_record_mock.rb
110
111
  - test/mock/dummy_class.rb