C0 code coverage information

Generated on Mon May 21 22:36:02 -0400 2007 with rcov 0.8.0


Code reported as executed by Ruby looks like this...
and this: this line is also marked as covered.
Lines considered as run by rcov, but not reported by Ruby, look like this,
and this: these lines were inferred by rcov (using simple heuristics).
Finally, here's a line marked as not executed.
Name Total lines Lines of code Total coverage Code coverage
lib/rools/rule_set.rb 411 196
85.4% 
75.5% 
  1 require 'rools/errors'
  2 require 'rools/rule'
  3 require 'rools/base'
  4 require 'rools/facts'
  5 require 'rools/csv_table'
  6 
  7 require 'rexml/document'
  8 
  9 module Rools
 10   class RuleSet < Base
 11     attr_reader :num_executed, :num_evaluated, :facts, :status
 12     
 13     PASS = :pass
 14     FAIL = :fail
 15     UNDETERMINED = :undetermined
 16   
 17     # You can pass a set of Rools::Rules with a block parameter,
 18     # or you can pass a file-path to evaluate.
 19     def initialize(file = nil, &b)
 20       
 21       @rules = {}
 22       @facts = {}
 23       @dependencies = {}
 24       
 25       if block_given?
 26         instance_eval(&b)
 27       elsif file
 28         # loading a file, check extension
 29         name,ext = file.split(".")
 30         logger.debug("loading ext: #{name}.#{ext}") if logger
 31         case ext
 32           when 'csv'
 33             load_csv( file )
 34             
 35           when 'xml'
 36             load_xml( file )
 37             
 38           when 'rb'
 39             load_rb( file )
 40               
 41           when 'rules'  # for backwards compatibility
 42             load_rb(file)
 43             
 44           else
 45             raise "invalid file extension: #{ext}"
 46         end
 47        end
 48     end		
 49     
 50     #
 51     # Loads decision table
 52     #
 53     def load_csv( file )
 54       csv = CsvTable.new( file )
 55       logger.debug "csv rules: #{csv.rules}" if logger
 56       instance_eval(csv.rules)
 57     end
 58     
 59     #
 60     # XML File format loading
 61     #
 62     def load_xml( fileName )
 63       begin
 64         file = File.new( fileName )
 65         doc = REXML::Document.new file
 66         doc.elements.each( "rule-set") { |rs| 
 67           facts = rs.elements.each( "facts") { |f| 
 68             facts( f.attributes["name"] ) do f.text.strip end
 69           }
 70           
 71           rules = rs.elements.each( "rule") { |rule_node|
 72              rule_name  = rule_node.attributes["name"]
 73              priority   = rule_node.attributes["priority"]
 74              
 75              rule       = Rule.new(self, rule_name, priority, nil)
 76              
 77              parameters = rule_node.elements.each("parameter") { |param|
 78                 #logger.debug "xml parameter: #{param.text.strip}"
 79                 rule.parameters(eval(param.text.strip))
 80              } 
 81              
 82              conditions = rule_node.elements.each("condition") { |cond|
 83                 #logger.debug "xml condition #{cond}"
 84                 rule.condition do eval(cond.text.strip) end
 85              } 
 86    
 87              consequences = rule_node.elements.each("consequence") { |cons|
 88                #logger.debug "xml consequence #{cons}"
 89                rule.consequence do eval(cons.text.strip) end
 90              } 
 91              
 92              @rules[rule_name] = rule
 93           }
 94           logger.debug( "loaded #{rules.size} rules") if logger
 95         }
 96       rescue Exception => e
 97         puts "Load XML Exception: #{e.to_s}"
 98         puts  e.backtrace.join("\n")
 99       end
100       
101     end
102     
103     #
104     # Ruby File format
105     #
106     def load_rb( file )
107       instance_eval(File::open(file).read)
108     end
109     
110     #
111     # returns an array of facts
112     # 
113     def get_facts
114       @facts
115     end
116     
117     #
118     # returns all the rules defined for that set
119     # 
120     def get_rules
121       @rules
122     end
123     
124     # rule creates a Rools::Rule. Make sure to use a descriptive name or symbol.
125     # For the purposes of extending Rules, all names are converted to
126     # strings and downcased.
127     # ==Example
128     #   rule 'ruby is the best' do
129     #     condition { language.name.downcase == 'ruby' }
130     #     consequence { "#{language.name} is the best!" }
131     #   end
132     def rule(name, priority=0, &b)
133       name.to_s.downcase!
134       @rules[name] = Rule.new(self, name, priority, b)
135     end
136     
137     # facts can be created in a similar manner to rules
138     # all names are converted to strings and downcased.
139     # Facts name is equivalent to a Class Name
140     # ==Example
141     # require 'rools'
142 	#
143 	# rules = Rools::RuleSet.new do
144 	#	
145 	#	facts 'Countries' do
146 	#		["China", "USSR", "France", "Great Britain", "USA"]
147 	#	end
148 	#	
149 	#	rule 'Is it on Security Council?' do
150 	#	  parameter String
151 	#		condition { countries.include?(string) }
152 	#		consequence { puts "Yes, #{string} is in the country list"}
153 	#	end
154 	# end
155     #
156 	# rules.assert 'France'
157     #
158     def facts(name, &b)
159       name.to_s.downcase!
160       @facts[name] = Facts.new(self, name, b)
161      logger.debug( "created facts: #{name}") if logger
162     end
163     
164     # A single fact can be an single object of a particular class type
165     # or a collection of objects of a particular type
166     def fact( obj )
167       begin
168         # check if facts already exist for that class
169         # if so, we need to add it to the existing list
170         cls = obj.class.to_s.downcase
171         
172         if @facts.key? cls
173           logger.debug( "adding to facts: #{cls}") if logger
174           @facts[cls].fact_value << obj
175         else
176           logger.debug( "creating facts: #{cls}") if logger
177           arr = Array.new
178           arr << obj
179           proc = Proc.new { arr }
180           @facts[cls] = Facts.new(self, cls, proc ) 
181         end
182       rescue Exception=> e
183         logger.error e if logger
184       end
185     end
186     
187     # Delete all existing facts
188     def delete_facts
189         @facts = {}
190     end
191     
192     # Use in conjunction with Rools::RuleSet#with to create a Rools::Rule dependent on
193     # another. Dependencies are created through names (converted to
194     # strings and downcased), so lax naming can get you into trouble with
195     # creating dependencies or overwriting rules you didn't mean to.
196     def extend(name, &b)
197       name.to_s.downcase!
198       @extend_rule_name = name
199       instance_eval(&b) if block_given?
200       return self
201     end
202     
203     # Used in conjunction with Rools::RuleSet#extend to create a dependent Rools::Rule
204     # ==Example
205     #   extend('ruby is the best').with('ruby rules the world') do
206     #     condition { language.age > 15 }
207     #     consequence { "In the year 2008 Ruby conquered the known universe" }
208     #   end
209     def with(name, prio=0, &b)
210       name.to_s.downcase!
211        (@dependencies[@extend_rule_name] ||= []) << Rule.new(self, name, prio, b)
212        #@rules[name] = Rule.new(self, name, prio, b)
213     end
214     
215     # Stops the current assertion. Does not indicate failure.
216     def stop(message = nil)
217       @assert = false
218     end
219     
220     # Stops the current assertion and change status to :fail
221     def fail(message = nil)
222       @status = FAIL
223       @assert = false
224     end
225     
226  
227     
228     # Used to create a working-set of rules for an object, and evaluate it
229     # against them. Returns a status, simply PASS or FAIL
230     #def assert_1(obj)
231     #  @status = PASS
232     #  @assert = true
233     #  @num_executed = 0;
234     #  @num_evaluated = 0;
235       
236       # create a working-set of all parameter-matching, non-dependent rules
237     #  available_rules = @rules.values.select { |rule| rule.parameters_match?(obj) }
238       
239     #  available_rules = available_rules.sort do  |r1, r2| 
240     #    r2.priority <=> r1.priority 
241     #  end
242       
243     #  begin
244         
245         # loop through the available_rules, evaluating each one,
246         # until there are no more matching rules available
247     #    begin # loop
248           
249           # the loop condition is reset to break by default after every iteration
250     #      matches = false
251           #logger.debug("available rules: #{available_rules.size.to_s}") if logger
252     #      available_rules.each do |rule|
253             # RuleCheckErrors are caught and swallowed and the rule that
254             # raised the error is removed from the working-set.
255     #        logger.debug("evaluating: #{rule}") if logger
256     #       begin
257     #          @num_evaluated += 1
258     #          if rule.conditions_match?(obj)
259     #            logger.debug("rule #{rule} matched") if logger
260     #            matches = true
261                 
262                 # remove the rule from the working-set so it's not re-evaluated
263     #            available_rules.delete(rule)
264                 
265                 # find all parameter-matching dependencies of this rule and
266                 # add them to the working-set.
267     #            if @dependencies.has_key?(rule.name)
268     #              available_rules += @dependencies[rule.name].select do |dependency|
269     #                dependency.parameters_match?(obj)
270     #              end
271     #            end
272                 
273                 # execute this rule
274     #            logger.debug("executing rule #{rule}") if logger
275     #            rule.call(obj)
276     #            @num_executed += 1
277                 
278                 # break the current iteration and start back from the first rule defined.
279     #            break
280     #          end # if rule.conditions_match?(obj)
281               
282     #        rescue RuleCheckError => e
283               # log da error or sumpin
284     #          available_rules.delete(e.rule)
285     #          @status = fail
286     #        end # begin/rescue
287             
288     #      end # available_rules.each
289           
290     #    end while(matches && @assert)
291         
292     #  rescue RuleConsequenceError => rce
293         # RuleConsequenceErrors are allowed to break out of the current assertion,
294         # then the inner error is bubbled-up to the asserting code.
295     #    @status = fail
296     #    raise rce.inner_error
297     #  end
298       
299     #  @assert = false
300       
301     #  return @status
302     #end # def assert
303     
304     # Turn passed object into facts and evaluate all relevant rules
305     # Previous facts of same type are removed
306     def assert( *objs )
307       objs.each { |obj| 
308         fact(obj)
309       }
310       return evaluate()
311     end
312     
313     # get all relevant rules for all specified facts
314     def get_relevant_rules
315       @relevant_rules = Array.new
316       @facts.each { |k,f| 
317         @rules.values.select { |rule| 
318           if !@relevant_rules.include?( rule)
319             if rule.parameters_match?(f.value) 
320               @relevant_rules << rule 
321               logger.debug "#{rule} is relevant" if logger
322             else
323               logger.debug "#{rule} is not relevant" if logger          
324             end 
325           end
326         } 
327       }
328       
329       # sort array in rule priority order
330       @relevant_rules = @relevant_rules.sort do  |r1, r2| 
331         r2.priority <=> r1.priority 
332       end
333     end
334     
335     # evaluate all relevant rules for specified facts
336     def evaluate
337       @status = PASS
338       @assert = true
339       @num_executed = 0;
340       @num_evaluated = 0;
341       
342       get_relevant_rules()
343       logger.debug("no relevant rules") if logger && @relevant_rules.size==0
344       
345       begin #rescue
346         
347         # loop through the available_rules, evaluating each one,
348         # until there are no more matching rules available
349         begin # loop
350           
351           # the loop condition is reset to break by default after every iteration
352           matches = false
353           obj     = nil #deprecated
354  
355           #logger.debug("available rules: #{available_rules.size.to_s}") if logger
356           @relevant_rules.each do |rule|
357             # RuleCheckErrors are caught and swallowed and the rule that
358             # raised the error is removed from the working-set.
359             logger.debug("evaluating: #{rule}") if logger
360             begin
361               @num_evaluated += 1
362               if rule.conditions_match?(obj)
363                 logger.debug("rule #{rule} matched") if logger
364                 matches = true
365                 
366                 # remove the rule from the working-set so it's not re-evaluated
367                 @relevant_rules.delete(rule)
368                 
369                 # find all parameter-matching dependencies of this rule and
370                 # add them to the working-set.
371                 if @dependencies.has_key?(rule.name)
372                   logger.debug( "found dependant rules to #{rule}") if logger
373                   @relevant_rules += @dependencies[rule.name].select do |dependency|
374                     dependency.parameters_match?(obj)
375                   end
376                 end
377                 
378                 # execute this rule
379                 logger.debug("executing rule #{rule}") if logger
380                 rule.call(obj)
381                 @num_executed += 1
382                 
383                 # break the current iteration and start back from the first rule defined.
384                 break
385               end # if rule.conditions_match?(obj)
386               
387             rescue RuleCheckError => e
388               puts "evaluate RuleCheckError: #{e}"
389               logger.debug( "RuleCheckError") if logger
390               @relevant_rules.delete(e.rule)
391               @status = fail
392             end # begin/rescue
393             
394           end # available_rules.each
395           
396         end while(matches && @assert)
397         
398       rescue RuleConsequenceError => rce
399         # RuleConsequenceErrors are allowed to break out of the current assertion,
400         # then the inner error is bubbled-up to the asserting code.
401         @status = fail
402         raise rce.inner_error
403       end
404       
405       @assert = false
406       
407       return @status
408     end
409     
410   end # class RuleSet
411 end # module Rools

Generated using the rcov code coverage analysis tool for Ruby version 0.8.0.

Valid XHTML 1.0! Valid CSS!