C0 code coverage information

Generated on Sat Jul 21 11:12:32 -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 379 214
100.0% 
100.0% 
  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 RuleLoadingError, "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         str = IO.read(fileName)
 65         load_xml_rules_as_string(str)
 66       rescue Exception => e
 67         raise RuleLoadingError, "loading xml file"
 68       end
 69     end
 70     
 71     # load xml rules as a string
 72     def load_xml_rules_as_string( str )
 73       begin
 74         doc = REXML::Document.new str
 75         doc.elements.each( "rule-set") { |rs| 
 76           facts = rs.elements.each( "facts") { |f| 
 77             facts( f.attributes["name"] ) do f.text.strip end
 78           }
 79           
 80           rules = rs.elements.each( "rule") { |rule_node|
 81              rule_name  = rule_node.attributes["name"]
 82              priority   = rule_node.attributes["priority"]
 83              
 84              rule       = Rule.new(self, rule_name, priority, nil)
 85              
 86              parameters = rule_node.elements.each("parameter") { |param|
 87                 #logger.debug "xml parameter: #{param.text.strip}"
 88                 rule.parameters(eval(param.text.strip))
 89              } 
 90              
 91              conditions = rule_node.elements.each("condition") { |cond|
 92                 #logger.debug "xml condition #{cond}"
 93                 rule.condition do eval(cond.text.strip) end
 94              } 
 95    
 96              consequences = rule_node.elements.each("consequence") { |cons|
 97                #logger.debug "xml consequence #{cons}"
 98                rule.consequence do eval(cons.text.strip) end
 99              } 
100              
101              @rules[rule_name] = rule
102           }
103           logger.debug( "loaded #{rules.size} rules") if logger
104         }
105       rescue Exception => e
106         raise RuleLoadingError, "loading xml file"
107       end
108       
109     end
110     
111     #
112     # Ruby File format loading
113     #
114     def load_rb( file )
115       begin
116         str = IO.read(file)
117         load_rb_rules_as_string(str)
118       rescue Exception => e
119         raise RuleLoadingError, "loading ruby file"
120       end
121     end
122     
123     # load ruby rules as a string
124     def load_rb_rules_as_string( str )
125       instance_eval(str)    
126     end
127     
128     #
129     # returns an array of facts
130     # 
131     def get_facts
132       @facts
133     end
134     
135     #
136     # returns all the rules defined for that set
137     # 
138     def get_rules
139       @rules
140     end
141     
142     # rule creates a Rools::Rule. Make sure to use a descriptive name or symbol.
143     # For the purposes of extending Rules, all names are converted to
144     # strings and downcased.
145     # ==Example
146     #   rule 'ruby is the best' do
147     #     condition { language.name.downcase == 'ruby' }
148     #     consequence { "#{language.name} is the best!" }
149     #   end
150     def rule(name, priority=0, &b)
151       name.to_s.downcase!
152       @rules[name] = Rule.new(self, name, priority, b)
153     end
154     
155     # facts can be created in a similar manner to rules
156     # all names are converted to strings and downcased.
157     # Facts name is equivalent to a Class Name
158     #	
159     # ==Example
160     #	
161     #     require 'rools'
162     #
163     #     rules = Rools::RuleSet.new do
164     #       
165     #       facts 'Countries' do
166     #       	["China", "USSR", "France", "Great Britain", "USA"]
167     #       end
168     #       
169     #       rule 'Is it on Security Council?' do
170     #         parameter String
171     #       	condition { countries.include?(string) }
172     #       	consequence { puts "Yes, #{string} is in the country list"}
173     #       end
174     #     end
175     #
176     #     rules.assert 'France'
177     #
178     def facts(name, &b)
179       name.gsub!(/:/, '_')
180       name.to_s.downcase!
181       @facts[name] = Facts.new(self, name, b)
182      logger.debug( "created facts: #{name}") if logger
183     end
184     
185     # A single fact can be an single object of a particular class type
186     # or a collection of objects of a particular type
187     def fact( obj )
188       #begin
189         # check if facts already exist for that class
190         # if so, we need to add it to the existing list
191         cls = obj.class.to_s.downcase
192         cls.gsub!(/:/, '_')
193         if @facts.key? cls
194           logger.debug( "adding to facts: #{cls}") if logger
195           @facts[cls].fact_value << obj
196         else
197           logger.debug( "creating facts: #{cls}") if logger
198           arr = Array.new
199           arr << obj
200           proc = Proc.new { arr }
201           @facts[cls] = Facts.new(self, cls, proc ) 
202         end
203       #rescue Exception=> e
204       #  logger.error e if logger
205       #end
206     end
207     
208     # Delete all existing facts
209     def delete_facts
210         @facts = {}
211     end
212     
213     # Use in conjunction with Rools::RuleSet#with to create a Rools::Rule dependent on
214     # another. Dependencies are created through names (converted to
215     # strings and downcased), so lax naming can get you into trouble with
216     # creating dependencies or overwriting rules you didn't mean to.
217     def extend(name, &b)
218       name.to_s.downcase!
219       @extend_rule_name = name
220       instance_eval(&b) if block_given?
221       return self
222     end
223     
224     # Used in conjunction with Rools::RuleSet#extend to create a dependent Rools::Rule
225     # ==Example
226     #   extend('ruby is the best').with('ruby rules the world') do
227     #     condition { language.age > 15 }
228     #     consequence { "In the year 2008 Ruby conquered the known universe" }
229     #   end
230     def with(name, prio=0, &b)
231       name.to_s.downcase!
232        (@dependencies[@extend_rule_name] ||= []) << Rule.new(self, name, prio, b)
233        #@rules[name] = Rule.new(self, name, prio, b)
234     end
235     
236     # Stops the current assertion. Does not indicate failure.
237     def stop(message = nil)
238       @assert = false
239     end
240     
241     # Stops the current assertion and change status to :fail
242     def fail(message = nil)
243       @status = FAIL
244       @assert = false
245     end
246     
247     #
248     # an assert has been made within a rule
249     # 
250     def rule_assert( obj )
251       # add object as a new fact
252       f = fact(obj)
253       # get_relevant_rules
254       logger.debug( "Check if we need to add more rules") if logger
255       add_relevant_rules_for_fact(f)
256       sort_relevant_rules
257     end
258     
259     # Turn passed object into facts and evaluate all relevant rules
260     # Previous facts of same type are removed
261     def assert( *objs )
262       objs.each { |obj| 
263         fact(obj)
264       }
265       return evaluate()
266     end
267     
268     #
269     # for a particular fact, we need to retrieve the relevant rules
270     # and add them to the relevant list
271     # 
272     def add_relevant_rules_for_fact fact
273       @rules.values.select { |rule| 
274         if !@relevant_rules.include?( rule)
275             if rule.parameters_match?(fact.value) 
276               @relevant_rules << rule 
277               logger.debug "#{rule} is relevant" if logger
278             else
279               logger.debug "#{rule} is not relevant" if logger          
280             end 
281         end
282       } 
283     end
284     
285     #
286     # relevant rules need to be sorted in priority order
287     # 
288     def sort_relevant_rules
289       # sort array in rule priority order
290       @relevant_rules = @relevant_rules.sort do  |r1, r2| 
291         r2.priority <=> r1.priority 
292       end
293     end
294     
295     # get all relevant rules for all specified facts
296     def get_relevant_rules
297       @relevant_rules = Array.new
298       @facts.each { |k,f| 
299         add_relevant_rules_for_fact f
300       }
301       sort_relevant_rules
302     end
303     
304     # evaluate all relevant rules for specified facts
305     def evaluate
306       @status = PASS
307       @assert = true
308       @num_executed = 0;
309       @num_evaluated = 0;
310       
311       get_relevant_rules()
312       logger.debug("no relevant rules") if logger && @relevant_rules.size==0
313       
314       #begin #rescue
315         
316         # loop through the available_rules, evaluating each one,
317         # until there are no more matching rules available
318         begin # loop
319           
320           # the loop condition is reset to break by default after every iteration
321           matches = false
322           obj     = nil #deprecated
323  
324           #logger.debug("available rules: #{available_rules.size.to_s}") if logger
325           @relevant_rules.each do |rule|
326             # RuleCheckErrors are caught and swallowed and the rule that
327             # raised the error is removed from the working-set.
328             logger.debug("evaluating: #{rule}") if logger
329             begin
330               @num_evaluated += 1
331               if rule.conditions_match?(obj)
332                 logger.debug("rule #{rule} matched") if logger
333                 matches = true
334                 
335                 # remove the rule from the working-set so it's not re-evaluated
336                 @relevant_rules.delete(rule)
337                 
338                 # find all parameter-matching dependencies of this rule and
339                 # add them to the working-set.
340                 if @dependencies.has_key?(rule.name)
341                   logger.debug( "found dependant rules to #{rule}") if logger
342                   @relevant_rules += @dependencies[rule.name].select do |dependency|
343                     dependency.parameters_match?(obj)
344                   end
345                 end
346                 
347                 # execute this rule
348                 logger.debug("executing rule #{rule}") if logger
349                 rule.call(obj)
350                 @num_executed += 1
351                 
352                 # break the current iteration and start back from the first rule defined.
353                 break
354               end # if rule.conditions_match?(obj)
355               
356             rescue RuleConsequenceError
357               fail
358             rescue RuleCheckError => e
359               fail
360             end # begin/rescue
361             
362           end # available_rules.each
363           
364         end while(matches && @assert)
365         
366       #rescue RuleConsequenceError => rce
367         # RuleConsequenceErrors are allowed to break out of the current assertion,
368         # then the inner error is bubbled-up to the asserting code.
369       #  @status = FAIL
370       #  raise rce.inner_error
371       #end
372       
373       @assert = false
374       
375       return @status
376     end
377     
378   end # class RuleSet
379 end # module Rools

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

Valid XHTML 1.0! Valid CSS!