thoreau 0.2.1 → 0.2.2
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.
- checksums.yaml +4 -4
- data/lib/thoreau/auto_run.rb +5 -0
- data/lib/thoreau/case/case_builder.rb +114 -0
- data/lib/thoreau/case/context_builder.rb +3 -4
- data/lib/thoreau/case/multi_clan_case_builder.rb +27 -0
- data/lib/thoreau/case/suite_runner.rb +61 -0
- data/lib/thoreau/configuration.rb +9 -0
- data/lib/thoreau/dsl/appendix.rb +5 -5
- data/lib/thoreau/dsl/clan.rb +94 -0
- data/lib/thoreau/dsl/suite_context.rb +46 -0
- data/lib/thoreau/dsl/test_cases.rb +20 -0
- data/lib/thoreau/dsl.rb +39 -26
- data/lib/thoreau/errors.rb +15 -0
- data/lib/thoreau/legacy_expected_outcomes.rb +53 -0
- data/lib/thoreau/logging.rb +36 -0
- data/lib/thoreau/models/appendix.rb +17 -0
- data/lib/thoreau/models/outcome.rb +33 -0
- data/lib/thoreau/models/setup.rb +17 -0
- data/lib/thoreau/models/test_case.rb +110 -0
- data/lib/thoreau/models/test_clan.rb +37 -0
- data/lib/thoreau/models/test_family.rb +53 -0
- data/lib/thoreau/test_suite.rb +17 -11
- data/lib/thoreau/test_suite_data.rb +27 -0
- data/lib/thoreau/util.rb +29 -0
- data/lib/thoreau/version.rb +1 -1
- data/lib/thoreau.rb +8 -2
- metadata +34 -11
- data/lib/thoreau/case/builder.rb +0 -110
- data/lib/thoreau/case/runner.rb +0 -42
- data/lib/thoreau/case.rb +0 -97
- data/lib/thoreau/dsl/context.rb +0 -55
- data/lib/thoreau/dsl/groups.rb +0 -20
- data/lib/thoreau/dsl/groups_support.rb +0 -58
- data/lib/thoreau/legacy_results.rb +0 -8
- data/lib/thoreau/setup.rb +0 -15
- data/lib/thoreau/spec_group.rb +0 -45
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 732fd064266fbd5f93dab80bee925ea5a61031de4cfdc82a9015ce5ac0e16c0d
         | 
| 4 | 
            +
              data.tar.gz: 82a1e6dabb420fce9d2260c07a7913ecc996838b3f4e0d98f2f5a8938cafcdaa
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ac549caf454efbc7d6e5806176897dd10a93177c34bea768f27030a49ed1c2da3bf8ad63ee86deb45ee4f5148a48f136f04ad24cacc63b921c03cdcd0912be37
         | 
| 7 | 
            +
              data.tar.gz: 1acaf812da2937c14ae7b509fcdd96bf0c3211b4eeaa06eabe97ce032c1d50401915e316bfa3a39558e78895646fb11186c18fa0c730e45dd9f5a3c04f7a7577
         | 
| @@ -0,0 +1,114 @@ | |
| 1 | 
            +
            module Thoreau
         | 
| 2 | 
            +
              module Case
         | 
| 3 | 
            +
                # Build test cases.
         | 
| 4 | 
            +
                #
         | 
| 5 | 
            +
                # It is responsible for:
         | 
| 6 | 
            +
                # - building an list of Test::Case objects based
         | 
| 7 | 
            +
                #   on the groups provided.
         | 
| 8 | 
            +
                # - expanding input specs in the groups into multiple cases
         | 
| 9 | 
            +
                # - skipping unfocused tests, if any are focused
         | 
| 10 | 
            +
                # - returning a count of those skipped
         | 
| 11 | 
            +
                class CaseBuilder
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  include Logging
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def initialize(test_clan:)
         | 
| 16 | 
            +
                    logger.debug("CaseBuilder.new #{test_clan.name} #{test_clan.test_families.size} families")
         | 
| 17 | 
            +
                    @test_families = test_clan.test_families
         | 
| 18 | 
            +
                    @action_block  = test_clan.action_block
         | 
| 19 | 
            +
                    @appendix      = test_clan.appendix
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def any_focused?
         | 
| 23 | 
            +
                    @test_families.count(&:focused?) > 0
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def skipped_count
         | 
| 27 | 
            +
                    return 0 unless any_focused?
         | 
| 28 | 
            +
                    @test_families.count - @test_families.count(&:focused?)
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def build_test_cases!
         | 
| 32 | 
            +
                    logger.debug "   build_test_cases! (#{@test_families.size} families)"
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    @test_families
         | 
| 35 | 
            +
                      .select { |fam| any_focused? && fam.focused? || !any_focused? }
         | 
| 36 | 
            +
                      .flat_map { |fam| build_family_cases fam }
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  private
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def setup_key_to_inputs key
         | 
| 42 | 
            +
                    setup = @appendix.setups[key.to_s]
         | 
| 43 | 
            +
                    raise "Unrecognized setup context '#{key}'. Available: #{@appendix.setups.keys.to_sentence}" if setup.nil?
         | 
| 44 | 
            +
                    logger.debug("   setup_key_to_inputs `#{key}`: #{setup}")
         | 
| 45 | 
            +
                    return setup.values if setup.block.nil?
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    result = Class.new.new.instance_eval(&setup.block)
         | 
| 48 | 
            +
                    logger.error "Setup #{key} did not return a hash object" unless result.is_a?(Hash)
         | 
| 49 | 
            +
                    result
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def build_family_cases fam
         | 
| 53 | 
            +
                    # We have "specs" for the inputs. These may be actual
         | 
| 54 | 
            +
                    # values, or they may be enumerables that need to execute.
         | 
| 55 | 
            +
                    # So we need to "explode" (or enumerate) the values,
         | 
| 56 | 
            +
                    # generating a single test for each combination.
         | 
| 57 | 
            +
                    #
         | 
| 58 | 
            +
                    setup_values = fam.setups
         | 
| 59 | 
            +
                                      .map { |key| setup_key_to_inputs key }
         | 
| 60 | 
            +
                                      .reduce(Hash.new) { |m, h| m.merge(h) }
         | 
| 61 | 
            +
                    logger.debug("   -> setup_values = #{setup_values}")
         | 
| 62 | 
            +
                    logger.debug("   -> fam.input_specs = #{fam.input_specs}")
         | 
| 63 | 
            +
                    input_sets = fam.input_specs
         | 
| 64 | 
            +
                                    .map { |is| setup_values.merge(is) }
         | 
| 65 | 
            +
                                    .flat_map do |input_spec|
         | 
| 66 | 
            +
                      explode_input_specs(input_spec.keys, input_spec)
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
                    input_sets = [{}] if input_sets.size == 0
         | 
| 69 | 
            +
                    logger.debug("   -> input_sets: #{input_sets}")
         | 
| 70 | 
            +
                    logger.debug("   build cases for '#{fam.desc}', #{setup_values.size} setups, #{input_sets.size} input sets, build_family_cases")
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                    input_sets.map do |input_set|
         | 
| 73 | 
            +
                      expectation = fam.use_legacy_snapshot ?
         | 
| 74 | 
            +
                                      :use_legacy_snapshot :
         | 
| 75 | 
            +
                                      Models::Outcome.new(output:    fam.expected_output,
         | 
| 76 | 
            +
                                                          exception: fam.expected_exception)
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                      Thoreau::Models::TestCase.new family_desc:  "#{fam.kind.to_s.ljust(10).capitalize} #{fam.desc}",
         | 
| 79 | 
            +
                                                    input:        input_set,
         | 
| 80 | 
            +
                                                    action_block: @action_block,
         | 
| 81 | 
            +
                                                    expectation:  expectation,
         | 
| 82 | 
            +
                                                    asserts:      fam.asserts,
         | 
| 83 | 
            +
                                                    negativo:     fam.failure_expected?
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  # Expand any values that are enumerators (Thoreau::DSL::Expanded),
         | 
| 89 | 
            +
                  # creating a list of objects, where all the combinations
         | 
| 90 | 
            +
                  # of enumerated values are present.
         | 
| 91 | 
            +
                  def explode_input_specs(keys, input_spec)
         | 
| 92 | 
            +
                    k = keys.pop
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                    value_spec = input_spec[k]
         | 
| 95 | 
            +
                    specs      = if value_spec.is_a?(Thoreau::DSL::Expanded)
         | 
| 96 | 
            +
                                   value_spec.map do |v|
         | 
| 97 | 
            +
                                     input_spec.merge(k => v)
         | 
| 98 | 
            +
                                   end
         | 
| 99 | 
            +
                                 else
         | 
| 100 | 
            +
                                   [input_spec]
         | 
| 101 | 
            +
                                 end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                    # Are we done?
         | 
| 104 | 
            +
                    return specs if keys.empty?
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    specs.flat_map do |spec|
         | 
| 107 | 
            +
                      explode_input_specs(keys, spec) # recurse!
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
              end
         | 
| 114 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            module Thoreau
         | 
| 2 | 
            +
              module Case
         | 
| 3 | 
            +
                class MultiClanCaseBuilder
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  include Logging
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def initialize(test_clans:)
         | 
| 8 | 
            +
                    @case_builders = test_clans.map do |test_clan|
         | 
| 9 | 
            +
                      CaseBuilder.new test_clan: test_clan
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def any_focused?
         | 
| 14 | 
            +
                    @case_builders.any? &:any_focused?
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def skipped_count
         | 
| 18 | 
            +
                    return 0 unless any_focused?
         | 
| 19 | 
            +
                    @case_builders.map(&:skipped_count).reduce(&:+)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def build_test_cases!
         | 
| 23 | 
            +
                    @case_builders.flat_map(&:build_test_cases!)
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| @@ -0,0 +1,61 @@ | |
| 1 | 
            +
            require 'rainbow/refinement'
         | 
| 2 | 
            +
            using Rainbow
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Thoreau
         | 
| 5 | 
            +
              module Case
         | 
| 6 | 
            +
                class SuiteRunner
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  include Logging
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize(name)
         | 
| 11 | 
            +
                    @suite_name = name
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def run_test_cases! cases, skipped
         | 
| 15 | 
            +
                    legacy_data = LegacyExpectedOutcomes.new(@suite_name)
         | 
| 16 | 
            +
                    logger.info "     #{@suite_name.underline.bright}"
         | 
| 17 | 
            +
                    cases.each do |c|
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                      legacy = c.expectation == :use_legacy_snapshot
         | 
| 20 | 
            +
                      if legacy
         | 
| 21 | 
            +
                        if !legacy_data.has_saved_for?(c) ||
         | 
| 22 | 
            +
                          ENV['RESET_SNAPSHOTS']
         | 
| 23 | 
            +
                          logger.info "     [#{ENV['RESET_SNAPSHOTS'] ? 'resetting' : 'saving'} legacy data]"
         | 
| 24 | 
            +
                          c.run
         | 
| 25 | 
            +
                          c.expectation = c.actual # by definition
         | 
| 26 | 
            +
                          legacy_data.save!(c)
         | 
| 27 | 
            +
                        else
         | 
| 28 | 
            +
                          c.expectation = legacy_data.load_for c
         | 
| 29 | 
            +
                        end
         | 
| 30 | 
            +
                      end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                      c.run
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      if c.ok?
         | 
| 35 | 
            +
                        logger.info "  #{legacy ? '▶️ ' : '✓ ' } #{c.desc}"
         | 
| 36 | 
            +
                      else
         | 
| 37 | 
            +
                        logger.error "❓  #{c.desc.bright}"
         | 
| 38 | 
            +
                        logger.error "    #{c.problem.red.bright}"
         | 
| 39 | 
            +
                      end
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                    logger.info (summary cases, skipped)
         | 
| 42 | 
            +
                    logger.info ""
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  def summary cases, skipped
         | 
| 47 | 
            +
                    ok     = cases.count(&:ok?)
         | 
| 48 | 
            +
                    total  = cases.count
         | 
| 49 | 
            +
                    failed = cases.count(&:failed?)
         | 
| 50 | 
            +
                    if failed == 0
         | 
| 51 | 
            +
                      "  ∴  No problems detected 👌🏾 #{skipped > 0 ? "#{skipped} skipped." : ""}"
         | 
| 52 | 
            +
                    else
         | 
| 53 | 
            +
                      " 🛑  #{failed} problem(s) detected.  [#{ok} of #{total} OK#{skipped > 0 ? ", #{skipped} skipped" : ""}.]".bright
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
    
        data/lib/thoreau/dsl/appendix.rb
    CHANGED
    
    | @@ -1,15 +1,15 @@ | |
| 1 | 
            -
            require_relative '../setup'
         | 
| 1 | 
            +
            require_relative '../models/setup'
         | 
| 2 2 |  | 
| 3 3 | 
             
            module Thoreau
         | 
| 4 4 | 
             
              module DSL
         | 
| 5 5 | 
             
                class Appendix
         | 
| 6 | 
            -
                  def initialize( | 
| 7 | 
            -
                    @ | 
| 6 | 
            +
                  def initialize(appendix_model, &appendix_block)
         | 
| 7 | 
            +
                    @model = appendix_model
         | 
| 8 | 
            +
                    self.instance_eval(&appendix_block)
         | 
| 8 9 | 
             
                  end
         | 
| 9 10 |  | 
| 10 11 | 
             
                  def setup name, values = {}, &block
         | 
| 11 | 
            -
                     | 
| 12 | 
            -
                    @context.setups[name.to_s] = Thoreau::Setup.new(name, values, block)
         | 
| 12 | 
            +
                    @model.add_setup(name, values, block)
         | 
| 13 13 | 
             
                  end
         | 
| 14 14 |  | 
| 15 15 | 
             
                end
         | 
| @@ -0,0 +1,94 @@ | |
| 1 | 
            +
            require_relative '../models/test_family'
         | 
| 2 | 
            +
            require_relative './expanded'
         | 
| 3 | 
            +
            require_relative '../util'
         | 
| 4 | 
            +
            require 'active_support/core_ext/array/conversions'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Thoreau
         | 
| 7 | 
            +
              module DSL
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                SPEC_FAMILY_NAMES = %i[happy sad spec test edge edges boundary corner gigo]
         | 
| 10 | 
            +
                # gigo = garbage in / garbage out
         | 
| 11 | 
            +
                #
         | 
| 12 | 
            +
                PROPS               = {
         | 
| 13 | 
            +
                  asserts:            %i[assert asserts post post_condition],
         | 
| 14 | 
            +
                  expected_exception: %i[raises],
         | 
| 15 | 
            +
                  expected_output:    %i[equals equal expected expect expects output],
         | 
| 16 | 
            +
                  failure_expected:   %i[fails pending],
         | 
| 17 | 
            +
                  input_specs:        %i[input inputs],
         | 
| 18 | 
            +
                  setups:             %i[setup setups]
         | 
| 19 | 
            +
                }
         | 
| 20 | 
            +
                ALL_PROPS           = PROPS.values.flatten.map(&:to_s)
         | 
| 21 | 
            +
                PROPS_SPELL_CHECKER = DidYouMean::SpellChecker.new(dictionary: ALL_PROPS)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                module Clan
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  # Note: requires `@test_clan_model`.
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def action(&block)
         | 
| 28 | 
            +
                    logger.debug "   + Adding action block"
         | 
| 29 | 
            +
                    @test_clan_model.action_block = block
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                  alias testing action
         | 
| 32 | 
            +
                  alias subject action
         | 
| 33 | 
            +
             | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def self.def_family_methods_for(sym)
         | 
| 36 | 
            +
                    define_method sym do |*args|
         | 
| 37 | 
            +
                      desc = args.shift if args.size > 1 && args.first.is_a?(String)
         | 
| 38 | 
            +
                      raise "Too many arguments to #{sym}!" if args.size > 1
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                      spec = args.first&.stringify_keys || {}
         | 
| 41 | 
            +
                      spec.keys
         | 
| 42 | 
            +
                          .reject { |k| ALL_PROPS.include? k }
         | 
| 43 | 
            +
                          .each do |k|
         | 
| 44 | 
            +
                        suggestions = PROPS_SPELL_CHECKER.correct(k)
         | 
| 45 | 
            +
                        logger.error "Ignoring unrecognized property '#{k}'."
         | 
| 46 | 
            +
                        logger.info "    Did you mean #{suggestions.to_sentence}?" if suggestions.size > 0
         | 
| 47 | 
            +
                        logger.info "    Available properties: #{ALL_PROPS.to_sentence}"
         | 
| 48 | 
            +
                      end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                      params = HashUtil.normalize_props(spec.symbolize_keys, PROPS).tap { |props|
         | 
| 51 | 
            +
                        # These two props are easier to deal with downstream as empty arrays
         | 
| 52 | 
            +
                        props[:input_specs] = [props[:input_specs]].flatten.compact
         | 
| 53 | 
            +
                        props[:setups]      = [props[:setups]].flatten.compact
         | 
| 54 | 
            +
                      }.merge kind: sym,
         | 
| 55 | 
            +
                              desc: desc
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      family = Models::TestFamily.new **params
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                      yield family if block_given?
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                      logger.debug "   * Created new family #{params.inspect}"
         | 
| 62 | 
            +
                      @test_clan_model.add_test_family family
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    define_method "#{sym}!" do |*args|
         | 
| 66 | 
            +
                      family       = self.send(sym, *args)
         | 
| 67 | 
            +
                      family.focus = true
         | 
| 68 | 
            +
                      family
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  SPEC_FAMILY_NAMES.each do |sym|
         | 
| 73 | 
            +
                    def_family_methods_for sym
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  def_family_methods_for :legacy do |r|
         | 
| 77 | 
            +
                    r.use_legacy_snapshot = true
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  alias legacy_spec legacy
         | 
| 81 | 
            +
                  alias legacy_test legacy
         | 
| 82 | 
            +
                  alias legacy_code   legacy
         | 
| 83 | 
            +
                  alias legacy_spec! legacy!
         | 
| 84 | 
            +
                  alias legacy_test! legacy!
         | 
| 85 | 
            +
                  alias legacy_code! legacy!
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  def expanded(a)
         | 
| 88 | 
            +
                    Thoreau::DSL::Expanded.new(a)
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
            end
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            require_relative '../test_suite_data'
         | 
| 2 | 
            +
            require 'active_support/core_ext/module/delegation'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Thoreau
         | 
| 5 | 
            +
              module DSL
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                class SuiteContext
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  include Thoreau::Logging
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  attr_reader :suite_data
         | 
| 12 | 
            +
                  attr_reader :test_clan_model
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def initialize suite_data:, test_clan_model:
         | 
| 15 | 
            +
                    raise "Suites must have (unique) names." if suite_data.name.blank?
         | 
| 16 | 
            +
                    @suite_data = suite_data
         | 
| 17 | 
            +
                    @test_clan_model  = test_clan_model
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  delegate :name, to: :suite_data
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def cases(name = nil, &block)
         | 
| 23 | 
            +
                    name = self.suite_data.name if name.nil?
         | 
| 24 | 
            +
                    logger.debug "   + adding cases named `#{name}`"
         | 
| 25 | 
            +
                    @suite_data.test_cases_blocks << [name, block]
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  alias test_cases cases
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def appendix(&block)
         | 
| 31 | 
            +
                    logger.debug "   adding appendix block"
         | 
| 32 | 
            +
                    @suite_data.appendix_block = block
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def context
         | 
| 36 | 
            +
                    self
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def add_test_family family
         | 
| 40 | 
            +
                    suite_data.add_test_family family
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            module Thoreau
         | 
| 2 | 
            +
              module DSL
         | 
| 3 | 
            +
                class TestCases
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  include Thoreau::Logging
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  attr_reader :test_clan_model
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def initialize(clan_model, &context)
         | 
| 10 | 
            +
                    @test_clan_model = clan_model
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    self.instance_eval(&context)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  include Thoreau::DSL::Clan
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
    
        data/lib/thoreau/dsl.rb
    CHANGED
    
    | @@ -1,40 +1,53 @@ | |
| 1 | 
            -
            require ' | 
| 2 | 
            -
             | 
| 1 | 
            +
            require 'thoreau/logging'
         | 
| 3 2 | 
             
            require 'thoreau/test_suite'
         | 
| 4 | 
            -
            require 'thoreau/ | 
| 5 | 
            -
            require 'thoreau/ | 
| 6 | 
            -
            require 'thoreau/case/ | 
| 7 | 
            -
            require 'thoreau/ | 
| 8 | 
            -
            require 'thoreau/dsl/ | 
| 9 | 
            -
            require 'thoreau/dsl/ | 
| 3 | 
            +
            require 'thoreau/models/test_case'
         | 
| 4 | 
            +
            require 'thoreau/models/test_clan'
         | 
| 5 | 
            +
            require 'thoreau/case/case_builder'
         | 
| 6 | 
            +
            require 'thoreau/case/suite_runner'
         | 
| 7 | 
            +
            require 'thoreau/dsl/clan'
         | 
| 8 | 
            +
            require 'thoreau/dsl/suite_context'
         | 
| 9 | 
            +
            require 'thoreau/dsl/test_cases'
         | 
| 10 10 | 
             
            require 'thoreau/dsl/appendix'
         | 
| 11 | 
            -
             | 
| 12 | 
            -
            at_exit do
         | 
| 13 | 
            -
              Thoreau::TestSuite.run_all!
         | 
| 14 | 
            -
            end
         | 
| 11 | 
            +
            require_relative './errors'
         | 
| 15 12 |  | 
| 16 13 | 
             
            module Thoreau
         | 
| 14 | 
            +
             | 
| 17 15 | 
             
              module DSL
         | 
| 18 16 |  | 
| 19 | 
            -
                 | 
| 17 | 
            +
                include Thoreau::Logging
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                attr_reader :suite_data
         | 
| 20 20 |  | 
| 21 21 | 
             
                def test_suite name = nil, focus: false, &block
         | 
| 22 | 
            -
                   | 
| 23 | 
            -
             | 
| 24 | 
            -
                   | 
| 25 | 
            -
                   | 
| 26 | 
            -
                   | 
| 22 | 
            +
                  logger.debug("# Processing keyword `test_suite`")
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  appendix        = Models::Appendix.new
         | 
| 25 | 
            +
                  top_level_clan_model = Thoreau::Models::TestClan.new name, appendix: appendix
         | 
| 26 | 
            +
                  @suite_data     = TestSuiteData.new name, test_clan: top_level_clan_model, appendix: appendix
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # Evaluate all the top-level keywords: test_cases, appendix
         | 
| 29 | 
            +
                  @suite_context = Thoreau::DSL::SuiteContext.new suite_data:      @suite_data,
         | 
| 30 | 
            +
                                                                  test_clan_model: top_level_clan_model
         | 
| 31 | 
            +
                  logger.debug("## Evaluating suite")
         | 
| 32 | 
            +
                  @suite_context.instance_eval(&block)
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  logger.debug("## Evaluating appendix block")
         | 
| 35 | 
            +
                  appendix_block = @suite_data.appendix_block
         | 
| 36 | 
            +
                  Thoreau::DSL::Appendix.new(@suite_data, &appendix_block) unless appendix_block.nil?
         | 
| 27 37 |  | 
| 28 | 
            -
                   | 
| 38 | 
            +
                  logger.debug("## Evaluating test_cases blocks")
         | 
| 39 | 
            +
                  @suite_data.test_cases_blocks.each do |name, cases_block|
         | 
| 29 40 |  | 
| 30 | 
            -
             | 
| 31 | 
            -
                  groups_context   = Thoreau::DSL::Groups.new(@context)
         | 
| 41 | 
            +
                    raise TestCasesAtMultipleLevelsError unless @suite_data.test_clans.first.empty?
         | 
| 32 42 |  | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 43 | 
            +
                    test_clan_model = Thoreau::Models::TestClan.new name,
         | 
| 44 | 
            +
                                                                    appendix: appendix,
         | 
| 45 | 
            +
                                                                    action_block: top_level_clan_model.action_block
         | 
| 46 | 
            +
                    Thoreau::DSL::TestCases.new(test_clan_model, &cases_block)
         | 
| 47 | 
            +
                    @suite_data.test_clans << test_clan_model
         | 
| 48 | 
            +
                  end
         | 
| 36 49 |  | 
| 37 | 
            -
                  TestSuite.new( | 
| 50 | 
            +
                  TestSuite.new(data: @suite_data, focus: focus)
         | 
| 38 51 | 
             
                end
         | 
| 39 52 |  | 
| 40 53 | 
             
                def xtest_suite name = nil, &block
         | 
| @@ -49,7 +62,7 @@ module Thoreau | |
| 49 62 |  | 
| 50 63 | 
             
                alias suite! test_suite!
         | 
| 51 64 |  | 
| 52 | 
            -
                include Thoreau::DSL:: | 
| 65 | 
            +
                include Thoreau::DSL::Clan
         | 
| 53 66 |  | 
| 54 67 | 
             
              end
         | 
| 55 68 | 
             
            end
         |