module XSpec

Hello and welcome to XSpec!

XSpec is an rspec-inspired testing library that is designed to be highly modular and easy to extend. Let's dive right in.

XSpec data structures are very dumb. They:

Common DSL functions are provided as a module so that they can be used in both top-level and nested contexts. The method names are modeled after rspec, and should behave roughly the same.

They delegate to method in the [current context](xspec.html#section-5) named in a way that more accurately represents XSpec implementation details.

Evaluators are usually composed together into a stack. The final stack has a single API method `call`, which is sent the unit of work to be executed and must return an array of `Failure` objects. It should not allow code-level exceptions to be raised, though should not block system exceptions (`SignalException`, `NoMemoryError`, etc).

Without a notifier, there is no way for XSpec to interact with the outside world. A notifier handles progress updates as tests are executed, and summarizing the run when it finished.

Schedulers are responsible for collecting all units of work to be run and scheduling them.

Constants

CodeException

An exception is mostly handled the same way as a failure.

ExecutedUnitOfWork

The result of executing a unit of work, including timing information. This is passed to notifiers for display or other processing.

Failure

A test failure will be reported as a `Failure`, which includes contextual information about the failure useful for reporting to the user.

NestedUnitOfWork

Units of work can be nested inside contexts. This is the main object that other components of the system work with.

UnitOfWork

A unit of work, usually created by the `it` DSL method, is a labeled, indivisible code block that expresses an assertion about a property of the system under test. They are run by a scheduler.

Public Class Methods

add_defaults(options = {}) click to toggle source
# File lib/xspec/defaults.rb, line 25
def add_defaults(options = {})
  # A notifier makes it possible to observe the state of the system, be that
  # progress or details of failing tests.
  options[:notifier] ||= XSpec::Notifier::DEFAULT

  # A unit of work will run as an instance method on the context it is
  # defined in, but in addition an assertion context will be added as well.
  # This is a module that is included as the final step in constructing a
  # context. Allows for different matchers and expectation frameworks to be
  # used.
  options[:evaluator] ||= Evaluator::DEFAULT

  options[:short_id] ||= XSpec.method(:default_short_id)


  # An scheduler is responsible for scheduling units of work and handing them
  # off to the assertion context. Any logic regarding threads, remote
  # execution or the like belongs in a scheduler.
  options[:scheduler]         ||= Scheduler::DEFAULT
  options
end
default_short_id(uow) click to toggle source
# File lib/xspec/defaults.rb, line 13
def default_short_id(uow)
  length  = 3
  base    = 32
  digest  = Digest::SHA1.hexdigest(uow.full_name).hex
  bottom  = base ** (length-1)
  top     = base ** length
  shifted = digest % (top - bottom) + bottom

  shifted.to_s(base)
end
dsl(config = {}) click to toggle source

The DSL is the core of XSpec. It dynamically generates a module that can be mixed into which ever context you choose (using `extend`), be that the top-level namespace or a specific class.

This enables different options to be specified per DSL, which is at the heart of XSpec's modularity. It is easy to change every component to your liking.

# File lib/xspec.rb, line 15
def dsl(config = {})
  config = XSpec.add_defaults(config)

  Module.new do
    # Each DSL provides a standard set of methods provided by the [DSL
    # module](dsl.html).
    include DSL

    # In addition, each DSL has its own independent context, which is
    # described in detail in the
    # [`data_structures.rb`](data_structures.html).
    def __xspec_context
      evaluator = __xspec_config.fetch(:evaluator)
      @__xspec_context ||= XSpec::Context.root(evaluator)
    end

    # Some meta-magic is needed to enable the config from local scope above
    # to be available inside the module.
    define_method(:__xspec_config) { config }

    # `run!` is where the magic happens. Typically called at the end of a
    # file (or by `autorun!`), this method takes all the data that was
    # accumulated by the DSL methods above and runs it through the scheduler.
    #
    # It takes an optional block that can be used to override any options
    # set in the initial `XSpec.dsl` call.
    def run!(&overrides)
      overrides ||= -> x { x }
      config = overrides.(__xspec_config)
      scheduler = config.fetch(:scheduler)

      scheduler.run(__xspec_context, config)
    end

    # It is often convenient to trigger a run after all files have been
    # processed, which is what `autorun!` sets up for you. Requiring
    # `xspec/autorun` does this automatically for you.
    def autorun!
      at_exit do
        exit 1 unless run!
      end
    end
  end
end

Public Instance Methods

__xspec_context() click to toggle source

In addition, each DSL has its own independent context, which is described in detail in the [`data_structures.rb`](data_structures.html).

# File lib/xspec.rb, line 26
def __xspec_context
  evaluator = __xspec_config.fetch(:evaluator)
  @__xspec_context ||= XSpec::Context.root(evaluator)
end
autorun!() click to toggle source

It is often convenient to trigger a run after all files have been processed, which is what `autorun!` sets up for you. Requiring `xspec/autorun` does this automatically for you.

# File lib/xspec.rb, line 52
def autorun!
  at_exit do
    exit 1 unless run!
  end
end
run!(&overrides) click to toggle source

`run!` is where the magic happens. Typically called at the end of a file (or by `autorun!`), this method takes all the data that was accumulated by the DSL methods above and runs it through the scheduler.

It takes an optional block that can be used to override any options set in the initial `XSpec.dsl` call.

# File lib/xspec.rb, line 41
def run!(&overrides)
  overrides ||= -> x { x }
  config = overrides.(__xspec_config)
  scheduler = config.fetch(:scheduler)

  scheduler.run(__xspec_context, config)
end

Private Instance Methods

add_defaults(options = {}) click to toggle source
# File lib/xspec/defaults.rb, line 25
def add_defaults(options = {})
  # A notifier makes it possible to observe the state of the system, be that
  # progress or details of failing tests.
  options[:notifier] ||= XSpec::Notifier::DEFAULT

  # A unit of work will run as an instance method on the context it is
  # defined in, but in addition an assertion context will be added as well.
  # This is a module that is included as the final step in constructing a
  # context. Allows for different matchers and expectation frameworks to be
  # used.
  options[:evaluator] ||= Evaluator::DEFAULT

  options[:short_id] ||= XSpec.method(:default_short_id)


  # An scheduler is responsible for scheduling units of work and handing them
  # off to the assertion context. Any logic regarding threads, remote
  # execution or the like belongs in a scheduler.
  options[:scheduler]         ||= Scheduler::DEFAULT
  options
end
default_short_id(uow) click to toggle source
# File lib/xspec/defaults.rb, line 13
def default_short_id(uow)
  length  = 3
  base    = 32
  digest  = Digest::SHA1.hexdigest(uow.full_name).hex
  bottom  = base ** (length-1)
  top     = base ** length
  shifted = digest % (top - bottom) + bottom

  shifted.to_s(base)
end
dsl(config = {}) click to toggle source

The DSL is the core of XSpec. It dynamically generates a module that can be mixed into which ever context you choose (using `extend`), be that the top-level namespace or a specific class.

This enables different options to be specified per DSL, which is at the heart of XSpec's modularity. It is easy to change every component to your liking.

# File lib/xspec.rb, line 15
def dsl(config = {})
  config = XSpec.add_defaults(config)

  Module.new do
    # Each DSL provides a standard set of methods provided by the [DSL
    # module](dsl.html).
    include DSL

    # In addition, each DSL has its own independent context, which is
    # described in detail in the
    # [`data_structures.rb`](data_structures.html).
    def __xspec_context
      evaluator = __xspec_config.fetch(:evaluator)
      @__xspec_context ||= XSpec::Context.root(evaluator)
    end

    # Some meta-magic is needed to enable the config from local scope above
    # to be available inside the module.
    define_method(:__xspec_config) { config }

    # `run!` is where the magic happens. Typically called at the end of a
    # file (or by `autorun!`), this method takes all the data that was
    # accumulated by the DSL methods above and runs it through the scheduler.
    #
    # It takes an optional block that can be used to override any options
    # set in the initial `XSpec.dsl` call.
    def run!(&overrides)
      overrides ||= -> x { x }
      config = overrides.(__xspec_config)
      scheduler = config.fetch(:scheduler)

      scheduler.run(__xspec_context, config)
    end

    # It is often convenient to trigger a run after all files have been
    # processed, which is what `autorun!` sets up for you. Requiring
    # `xspec/autorun` does this automatically for you.
    def autorun!
      at_exit do
        exit 1 unless run!
      end
    end
  end
end