damphyr/rutema

View on GitHub
lib/rutema/core/objectmodel.rb

Summary

Maintainability
A
1 hr
Test Coverage
#  Copyright (c) 2007-2021 Vassilis Rizopoulos. All rights reserved.

require 'patir/command'

module Rutema
  ##
  # Mix-in module allowing to add attributes to a class on the go
  #
  # This allows to add attributes to classes on the go and makes the accessor
  # methods appear automatically too.
  #
  # It will add a #has_attribute? method too to query if an attribute is part of
  # the object or not.
  module SpecificationElement
    ##
    # Add an attribute with a given value to the class instance
    #
    # * +symbol+ - a symbol or a string (which will be converted to a symbol
    #   internally) which shall be the name of the new attribute
    # * +value+ - the initial value of the attribute
    #
    # If +symbol+ is neither a string nor a symbol this method will be a no-op.
    def attribute symbol,value
      @attributes||=Hash.new
      case symbol
        when String then @attributes[:"#{symbol}"]=value
        when Symbol then @attributes[symbol]=value
      end
    end

    ##
    # Method enabling to call object.attribute, object.attribute=, object.attribute? and object.has_attribute?
    #
    # object.attribute and object.attribute? will throw NoMethodError if the
    # attribute is not set on the object instance.
    #
    # object.attribute= will set the attribute to the right operand and
    # object.has_attribute? will return +true+ or +false+ according to the
    # existence of the attribute.
    def method_missing symbol,*args
      @attributes||=Hash.new
      key=symbol.id2name.chomp('?').chomp('=').sub(/^has_/,"")
      @attributes[:"#{key}"]=args[0] if key+"="==symbol.id2name
      if @attributes.has_key?(:"#{key}")
          return true if "has_"+key+"?"==symbol.id2name
          return @attributes[:"#{key}"]
      else
        return false if "has_"+key+"?"==symbol.id2name
        super(symbol,*args)
      end
    end

    ##
    # Return +true+ if the object responds to the given method or else +false+
    def respond_to? symbol,include_all
      @attributes||=Hash.new
      key=symbol.id2name.chomp('?').chomp('=').sub(/^has_/,"")
      if @attributes.has_key?(:"#{key}")
          return true
      else
        super(symbol,include_all)
      end
    end
  end

  ##
  # A Rutema::Specification encompasses all elements required to run a test, the builds used, the scenario to run,
  # together with a textual description and information that aids in tracing the test back to the requirements.
  class Specification
    include SpecificationElement

    attr_accessor :scenario
    ##
    # Expects a Hash of parameters
    #
    # Following keys have meaning in initialization:
    #
    # :name - the name of the testcase. Should uniquely identify the testcase
    #
    # :title - a one liner describing what the testcase does
    #
    # :filename - the filename describing the testcase
    #
    # :description - a full textual description for the testcase. To be used in reports and documents
    #
    # :scenario - An instance of Rutema::Scenario
    #
    # :version - The version of this specification
    #
    # Default values are empty strings and arrays. (scenario is nil)
    def initialize params
      begin
        @attributes=params
      end if params
      @attributes||=Hash.new
      @attributes[:name]||=""
      @attributes[:title]||=""
      @attributes[:filename]||=""
      @attributes[:description]||=""
      @scenario=@attributes[:scenario]
    end

    ##
    # Create a concise string representation consisting of +name+ and +title+
    def to_s
      return "#{@attributes[:name]} - #{@attributes[:title]}"
    end
  end

  #A Rutema::Scenario is a sequence of Rutema::Step instances.
  #
  #Rutema::Step instances are run in the definition sequence and the scenario
  #is succesfull when all steps are succesfull. 
  #
  #From the execution point of view each step is either succesfull or failed and it depends on 
  #the exit code of the step's command. 
  #
  #Failure in a step results in the interruption of execution and the report of the errors.
  class Scenario
    include SpecificationElement

    attr_reader :steps
    
    def initialize steps
      @attributes=Hash.new
      @steps=steps
      @steps||=Array.new
    end

    #Adds a step at the end of the step sequence
    def add_step step
      @steps<<step
    end

    #Overwrites the step sequence
    def steps= array_of_steps
      @steps=array_of_steps
    end
  end

  #Represents a step in a Scenario.
  #
  #Each Rutema::Step can have text and a command associated with it. 
  #
  #Step standard attributes are.
  #
  #attended - the step can only run in attended mode, it requires user input.
  #
  #step_type - a string identifying the type of the step. It is "step" by default.
  #
  #ignore - set to true if the step's success or failure is to be ignored. It essentially means that the step is always considered succesfull
  #
  #continue - set to true if the step's success or failure is to be recognized, but following steps in the same scenario are to be carried out regardless
  #  of if indicates test failure, but ensures any further state is carried out as needed
  #
  # skip_on_error - if the test case has been marked a failure, skip steps marked with this attribute
  #
  #number - this is set when the step is assigned to a Scenario and is the sequence number
  #
  #cmd - the command associated with this step. This should quack like Patir::Command.
  #
  #status - one of :not_executed, :success, :warning, :error. Encapsulates the underlying command's status
  #
  #==Dynamic behaviour
  #
  #A Rutema::Step can be queried dynamicaly about the attributes it posesses:
  # step.has_script? - will return true if script is step's attribute.
  #Attribute's are mostly assigned by the parser, i.e. the Rutema::BaseXMLParser from the XML element
  # <test script="some_script"/>
  #will create a Step instance with step_type=="test" and script="some_script". In this case
  #
  # step.has_script? returns true
  # step.script returns "some_script"
  #
  #Just like an OpenStruct, Step attributes will be created by direct assignment:
  # step.script="some_script" creates the script attribute if it does not exist.
  #
  #See Rutema::SpecificationElement for the implementation details. 
  class Step
    include SpecificationElement
    include Patir::Command
    
    #_txt_ describes the step, _cmd_ is the command to run
    def initialize txt="",cmd=nil
      @attributes=Hash.new
      #ignore is off by default
      @attributes[:ignore]=false
      # continue is off by default
      @attributes[:continue] = false
      # skip_on_error is off by default
      @attributes[:skip_on_error]=false
      #assign
      @attributes[:cmd]=cmd if cmd
      @attributes[:text]=txt
      @number=0
      @attributes[:step_type]="step"
    end

    def name
      return name_with_parameters
    end

    def output
      return "" unless @attributes[:cmd]
      return @attributes[:cmd].output
    end

    def error
      return "no command associated" unless @attributes[:cmd]
      return @attributes[:cmd].error
    end

    def backtrace
      return "no backtrace associated" unless @attributes[:cmd]
      return @attributes[:cmd].backtrace if @attributes[:cmd].respond_to?(:backtrace)
      return ""
    end

    def skip_on_error?
      return false unless @attributes[:skip_on_error]
      return @attributes[:skip_on_error]
    end

    def ignore?
      return false unless @attributes[:ignore]
      return @attributes[:ignore]
    end

    def continue?
      return false unless @attributes[:continue]
      return @attributes[:continue]
    end

    def exec_time
      return 0 unless @attributes[:cmd]
      return @attributes[:cmd].exec_time
    end

    def status
      return :warning unless @attributes[:cmd]
      return @attributes[:cmd].status
    end

    def status= st
      @attributes[:cmd].status=st if @attributes[:cmd]
    end

    def run context=nil
      return not_executed unless @attributes[:cmd]
      return @attributes[:cmd].run(context)
    end

    def reset
      @attributes[:cmd].reset if @attributes[:cmd]
    end

    def name_with_parameters
      param=" - #{self.cmd.to_s}" if self.has_cmd?
      return "#{@attributes[:step_type]}#{param}"
    end

    def to_s#:nodoc:
      if self.has_cmd?
        msg="#{self.number} - #{self.cmd.to_s}"
      else
        msg="#{self.number} - #{self.name}"
      end
        msg<<" in #{self.included_in}" if self.has_included_in?
      return msg
    end
  end
end

class Patir::ShellCommand
  def to_s#:nodoc:
    return @command
  end
end

class Patir::RubyCommand
  def to_s#:nodoc:
    return @name
  end
end