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.
@@ -0,0 +1,15 @@
1
+ module Thoreau
2
+ class TestCasesAtMultipleLevelsError < RuntimeError
3
+ def initialize(msg = nil)
4
+ super "Test cases must be specified either at the top level or inside test_cases blocks, but not both!"
5
+ end
6
+ end
7
+
8
+ class OverriddenActionError < RuntimeError
9
+ def initialize(msg = nil)
10
+ super "Extra action/subject provided for tests. Actions/subjects must be specified EITHER in the `suite` or within `test_cases` (not both)."
11
+ end
12
+ end
13
+
14
+
15
+ end
@@ -0,0 +1,53 @@
1
+ require 'logger'
2
+ require 'json'
3
+ require "pstore"
4
+
5
+ module Thoreau
6
+
7
+ class LegacyExpectedOutcomes
8
+
9
+ # A simple store of expected outcomes of test cases.
10
+ #
11
+ # They are stored in a "pstore" database, with the test descriptor
12
+ # and inputs being the key. If you change the input of the test, it
13
+ # should generate a new saved result.
14
+ #
15
+ # Set ENV['RESET_SNAPSHOTS'] to, well, reset all the snapshots.
16
+
17
+ VERSION = 1
18
+
19
+ def initialize(suite_name)
20
+ @suite_name = suite_name
21
+ end
22
+
23
+ def key_for(test_case)
24
+ o = {
25
+ desc: test_case.family_desc,
26
+ input: test_case.input
27
+ }
28
+ o.to_json
29
+ end
30
+
31
+ def has_saved_for? test_case
32
+ logger.debug("has_saved_for? for #{key_for(test_case)}")
33
+ !!(load_for test_case)
34
+ end
35
+
36
+ def load_for test_case
37
+ logger.debug("load_for for #{key_for(test_case)}")
38
+ wiki = PStore.new(Thoreau.configuration.legacy_outcome_path)
39
+ wiki.transaction do
40
+ wiki[key_for(test_case)]
41
+ end
42
+ end
43
+
44
+ def save! test_case
45
+ logger.debug("save! for #{key_for(test_case)}")
46
+ wiki = PStore.new(Thoreau.configuration.legacy_outcome_path)
47
+ wiki.transaction do
48
+ wiki[key_for(test_case)] = test_case.actual
49
+ end
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,36 @@
1
+ require 'logger'
2
+
3
+ # https://stackoverflow.com/questions/917566/ruby-share-logger-instance-among-module-classes
4
+ # The intended use is via "include":
5
+ module Thoreau
6
+ module Logging
7
+ class << self
8
+ def logger
9
+ if @logger.nil?
10
+ @logger = Logger.new(STDOUT, formatter: proc { |severity, datetime, progname, msg|
11
+ "#{severity}: #{msg}\n"
12
+ })
13
+ @logger.level = Logger::INFO
14
+ @logger.level = Logger::DEBUG if ENV['DEBUG']
15
+ end
16
+ @logger
17
+ end
18
+
19
+ # def logger=(logger)
20
+ # @logger = logger
21
+ # end
22
+ end
23
+
24
+ def self.included(base)
25
+ class << base
26
+ def logger
27
+ Logging.logger
28
+ end
29
+ end
30
+ end
31
+
32
+ def logger
33
+ Logging.logger
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ module Thoreau
2
+ module Models
3
+ class Appendix
4
+
5
+ attr_reader :setups
6
+
7
+ def initialize setups: {}
8
+ @setups = setups
9
+ end
10
+
11
+ def add_setup setup
12
+ raise "Duplicate setup block #{setup.name}" unless setups[setup.name].nil?
13
+ @setups[setup.name] = setup
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module Thoreau
2
+ module Models
3
+ class Outcome
4
+ # Reprents the outcome of a given test case.
5
+ # It can either be successful and have an `output`,
6
+ # or it can raise an exception.
7
+ #
8
+ # This is used both for recording what happens to a test
9
+ # and for representing the expectations for what will happen.
10
+ # It is also used to save as a "snapshot" for legacy tests.
11
+
12
+ include Thoreau::Logging
13
+
14
+ attr_reader :output
15
+ attr_reader :exception
16
+
17
+ def initialize output: nil,
18
+ exception: nil
19
+
20
+ if output.is_a?(Proc)
21
+ @output_proc = output
22
+ else
23
+ @output = output
24
+ end
25
+ @exception = exception
26
+ end
27
+
28
+ def evaluate(result, context)
29
+ @output = context.instance_exec(result, &(@output_proc)) if @output_proc
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ module Thoreau
2
+ module Models
3
+
4
+ class Setup
5
+
6
+ attr_reader :name, :values, :block
7
+
8
+ def initialize name, values, block
9
+ @name = name.to_s
10
+ @values = values
11
+ # @value = [values].flatten unless values.nil?
12
+ @block = block
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,110 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require_relative '../case/context_builder'
3
+ require_relative './outcome'
4
+ require_relative '../legacy_expected_outcomes'
5
+
6
+ module Thoreau
7
+ module Models
8
+ class TestCase
9
+
10
+ include Thoreau::Logging
11
+
12
+ attr_reader :actual, :family_desc, :input
13
+ attr_accessor :expectation
14
+
15
+ def initialize family_desc:,
16
+ input:,
17
+ action_block:,
18
+ expectation:,
19
+ asserts:,
20
+ negativo:
21
+
22
+ @family_desc = family_desc
23
+ @input = input
24
+ @action_block = action_block
25
+ @negativo = negativo
26
+
27
+ @expectation = expectation
28
+
29
+ @assert_proc = asserts
30
+
31
+ @ran = false
32
+ end
33
+
34
+ def failure_expected?
35
+ @negativo
36
+ end
37
+
38
+ def desc
39
+ "#{@family_desc} #{(@input == {} ? nil : @input.sort.to_h) || @expectation.exception || "(no args)"}"
40
+ end
41
+
42
+ def problem
43
+
44
+ run unless @ran
45
+
46
+ if @expectation.exception
47
+
48
+ logger.debug " -> Expected Exception #{@expectation.exception} @actual.exception:#{@actual.exception}"
49
+
50
+ if @actual.exception.to_s == @expectation.exception.to_s
51
+ nil
52
+ elsif @actual.exception.nil?
53
+ "Expected exception, but none raised"
54
+ elsif @actual.exception.is_a?(NameError)
55
+ "Did you forget to define an input? Error: #{@actual.exception}"
56
+ else
57
+ "Expected '#{@expectation.exception}' exception, but raised '#{@actual.exception}' (#{@actual.exception.class.name})"
58
+ end
59
+
60
+ elsif @assert_proc
61
+
62
+ logger.debug " -> Assert Proc result=#{@assert_result}"
63
+
64
+ @assert_result ? nil : "Assertion failed. (got #{@assert_result})"
65
+ else
66
+
67
+ logger.debug " -> Result expected: result=#{@actual.output} expected_output: #{@expectation.output} @actual.exception:#{@actual.exception}"
68
+
69
+ if @actual.exception
70
+ "Expected output, but raised exception '#{@actual.exception}'"
71
+ elsif @expectation.output != @actual.output
72
+ "Expected '#{@expectation.output}', but got '#{@actual.output}'"
73
+ else
74
+ nil
75
+ end
76
+ end
77
+ end
78
+
79
+ def ok?
80
+ failure_expected? ^ !!problem.nil?
81
+ end
82
+
83
+ def failed?
84
+ !ok?
85
+ end
86
+
87
+ def run
88
+ logger.debug "## RUN #{desc}"
89
+ context_builder = Case::ContextBuilder.new(input: @input)
90
+ context = context_builder.create_context
91
+ begin
92
+ # Only capture exceptions around the action itself.
93
+ output = context.instance_exec(&(@action_block))
94
+ @actual = Models::Outcome.new output: output
95
+ rescue Exception => e
96
+ logger.debug("** Exception: #{e.class.name} #{e}")
97
+ logger.debug("Available local variables: #{@input.keys.empty? ? '(none)' : @input.keys.to_sentence}") if e.is_a? NameError
98
+ @actual = Models::Outcome.new exception: e
99
+ return
100
+ ensure
101
+ @ran = true
102
+ end
103
+
104
+ @expectation.evaluate(@actual.output, context) unless @expectation == :use_legacy_snapshot
105
+ @assert_result = context.instance_exec(@actual.output, &(@assert_proc)) if @assert_proc
106
+ end
107
+
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,37 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require_relative '../errors'
3
+
4
+ module Thoreau
5
+
6
+ module Models
7
+ class TestClan # set of TestFamilies
8
+
9
+ include Thoreau::Logging
10
+
11
+ attr_accessor :test_families, :appendix
12
+ attr_reader :name, :action_block
13
+
14
+ delegate :empty?, to: :test_families
15
+
16
+ def initialize(name, appendix:, action_block: nil)
17
+ @name = name
18
+ @test_families = []
19
+ @appendix = appendix
20
+ @action_block = action_block
21
+ end
22
+
23
+ def action_block= block
24
+ raise OverriddenActionError unless @action_block.nil?
25
+ @action_block = block
26
+ end
27
+
28
+ def add_test_family fam
29
+ logger.debug " + Adding test family #{fam}"
30
+ fam.desc = self.name if fam.desc.blank?
31
+ @test_families.push fam
32
+ fam
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,53 @@
1
+ module Thoreau
2
+ module Models
3
+
4
+ class TestFamily
5
+ attr_reader :asserts,
6
+ :expected_exception,
7
+ :expected_output,
8
+ :kind,
9
+ :setups
10
+ attr_writer :focus
11
+ attr_accessor :use_legacy_snapshot
12
+ attr_accessor :desc
13
+
14
+ def initialize(asserts:,
15
+ desc:,
16
+ expected_exception:,
17
+ expected_output:,
18
+ failure_expected:,
19
+ input_specs:,
20
+ kind:,
21
+ setups:
22
+ )
23
+ @asserts = asserts
24
+ @desc = desc
25
+ @expected_exception = expected_exception
26
+ @expected_output = expected_output
27
+ @failure_expected = failure_expected
28
+ @input_specs = input_specs
29
+ @kind = kind
30
+ @setups = setups
31
+ end
32
+
33
+ def input_specs
34
+ @input_specs.size == 0 ?
35
+ [{}] : @input_specs
36
+ end
37
+
38
+ def failure_expected?
39
+ @failure_expected
40
+ end
41
+
42
+ def focused?
43
+ @focus
44
+ end
45
+
46
+ def to_s
47
+ "#{@desc || "#{@kind} #{(@input_specs.map &:to_s).to_sentence } expect #{expected_output}"}"
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+
@@ -1,24 +1,29 @@
1
+ require_relative './models/appendix'
2
+ require_relative './case/multi_clan_case_builder'
3
+ require 'active_support/core_ext/module/delegation'
4
+
1
5
  module Thoreau
2
6
  class TestSuite
3
7
 
4
- attr_reader :name
5
- attr_reader :logger
6
-
7
8
  @@suites = []
8
9
 
9
- def initialize(context:, logger:, name:, focus:)
10
- @context = context
11
- @logger = logger
12
- @name = name
13
- @focus = focus
10
+ def initialize(data:, focus:)
11
+ @data = data
12
+ @focus = focus
14
13
  @@suites << self
15
- @builder = Thoreau::Case::Builder.new @context.data.groups, @context
14
+
15
+ # @builder = Thoreau::Case::CaseBuilder.new test_clan: @data.test_clan
16
+ @builder = Thoreau::Case::MultiClanCaseBuilder.new test_clans: @data.test_clans
16
17
  end
17
18
 
19
+ delegate :name, to: :@data
20
+
18
21
  def build_and_run
22
+ logger.debug("## build_and_run")
19
23
  cases = @builder.build_test_cases!
24
+ logger.debug(" ... built #{cases.size} cases")
20
25
 
21
- runner = Thoreau::Case::Runner.new @context
26
+ runner = Thoreau::Case::SuiteRunner.new @data.name
22
27
  runner.run_test_cases! cases,
23
28
  @builder.skipped_count # for reporting
24
29
  end
@@ -28,12 +33,13 @@ module Thoreau
28
33
  end
29
34
 
30
35
  def self.run_all!
36
+ logger.debug("# run_all! ############")
31
37
  run_all = !@@suites.any?(&:focused?)
32
38
  @@suites.each do |suite|
33
39
  if suite.focused? || run_all
34
40
  suite.build_and_run
35
41
  else
36
- suite.logger.info("Suite '#{suite.name}' skipped (unfocused)")
42
+ logger.info(" Suite '#{suite.name}' skipped (unfocused)")
37
43
  end
38
44
  end
39
45
  end
@@ -0,0 +1,27 @@
1
+ require_relative './models/test_clan'
2
+
3
+ module Thoreau
4
+ class TestSuiteData
5
+
6
+ include Thoreau::Logging
7
+
8
+ attr_accessor :appendix_block
9
+ attr_reader :test_cases_blocks
10
+
11
+ attr_reader :name
12
+ attr_reader :test_clans
13
+
14
+ def initialize name, appendix:, test_clan:
15
+ @name = name
16
+ @appendix = appendix
17
+ @test_clans = [test_clan]
18
+ @test_cases_blocks = []
19
+ end
20
+
21
+ def add_setup(name, values, block)
22
+ logger.debug " Adding setup block #{name}"
23
+ @appendix.add_setup Thoreau::Models::Setup.new(name, values, block)
24
+ end
25
+
26
+ end
27
+ end