given_core 3.0.0.beta.1

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,56 @@
1
+
2
+ module Given
3
+
4
+ # Failure objects will raise the given exception whenever you try
5
+ # to send it *any* message.
6
+ class Failure < BasicObject
7
+ undef_method :==, :!=, :!
8
+
9
+ def initialize(exception)
10
+ @exception = exception
11
+ end
12
+
13
+ def is_a?(klass)
14
+ klass == Failure
15
+ end
16
+
17
+ def ==(other)
18
+ if failure_matcher?(other)
19
+ other.matches?(self)
20
+ else
21
+ die
22
+ end
23
+ end
24
+
25
+ def !=(other)
26
+ if failure_matcher?(other)
27
+ other.does_not_match?(self)
28
+ else
29
+ die
30
+ end
31
+ end
32
+
33
+ def method_missing(sym, *args, &block)
34
+ die
35
+ end
36
+
37
+ def respond_to?(method_symbol)
38
+ method_symbol == :call ||
39
+ method_symbol == :== ||
40
+ method_symbol == :!= ||
41
+ method_symbol == :is_a?
42
+ end
43
+
44
+ private
45
+
46
+ def die
47
+ ::Kernel.raise @exception
48
+ end
49
+
50
+ def failure_matcher?(other)
51
+ other.is_a?(::Given::FailureMatcher) ||
52
+ other.is_a?(::RSpec::Given::HaveFailed::HaveFailedMatcher)
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,63 @@
1
+ module Given
2
+
3
+ class FailureMatcher
4
+ def initialize(exception_class, message_pattern)
5
+ @no_pattern = false
6
+ @expected_exception_class = exception_class
7
+ @expected_message_pattern = message_pattern
8
+ if @expected_message_pattern.nil?
9
+ @expected_message_pattern = //
10
+ @no_pattern = true
11
+ elsif @expected_message_pattern.is_a?(String)
12
+ @expected_message_pattern =
13
+ Regexp.new("\\A" + Regexp.quote(@expected_message_pattern) + "\\Z")
14
+ end
15
+ end
16
+
17
+ def matches?(possible_failure)
18
+ if possible_failure.respond_to?(:call)
19
+ make_sure_it_throws_an_exception(possible_failure)
20
+ else
21
+ Given.fail_with("#{description}, but nothing failed")
22
+ end
23
+ end
24
+
25
+ def does_not_match?(possible_failure)
26
+ if possible_failure.respond_to?(:call)
27
+ false
28
+ else
29
+ true
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def make_sure_it_throws_an_exception(possible_failure)
36
+ possible_failure.call
37
+ Given.fail_with("Expected an exception")
38
+ rescue Exception => ex
39
+ if ! ex.is_a?(@expected_exception_class)
40
+ Given.fail_with("#{description}, but got #{ex.inspect}")
41
+ elsif @expected_message_pattern !~ ex.message
42
+ Given.fail_with("#{description}, but got #{ex.inspect}")
43
+ else
44
+ true
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def description
51
+ result = "Expected failure with #{@expected_exception_class}"
52
+ result << " matching #{@expected_message_pattern.inspect}" unless @no_pattern
53
+ result
54
+ end
55
+ end
56
+
57
+ module FailureMethod
58
+ def Failure(exception_class=Exception, message_pattern=nil)
59
+ FailureMatcher.new(exception_class, message_pattern)
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,18 @@
1
+
2
+ module Given
3
+ class FileCache
4
+ def initialize
5
+ @lines = {}
6
+ end
7
+
8
+ def get(file_name)
9
+ @lines[file_name] ||= read_lines(file_name)
10
+ end
11
+
12
+ private
13
+
14
+ def read_lines(file_name)
15
+ open(file_name) { |f| f.readlines }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,68 @@
1
+
2
+ module Given
3
+ module Fuzzy
4
+ class FuzzyNumber
5
+
6
+ DEFAULT_EPSILON = 10 * Float::EPSILON
7
+
8
+ attr_reader :exact_value, :delta_amount
9
+
10
+ def initialize(exact_value)
11
+ @exact_value = exact_value
12
+ @delta_amount = exact_value * DEFAULT_EPSILON
13
+ end
14
+
15
+ def exactly_equals?(other)
16
+ other.is_a?(self.class) &&
17
+ exact_value == other.exact_value &&
18
+ delta_amount == other.delta_amount
19
+ end
20
+
21
+ # Low limit of the fuzzy number.
22
+ def low_limit
23
+ exact_value - delta_amount
24
+ end
25
+
26
+ # High limit of the fuzzy number.
27
+ def high_limit
28
+ exact_value + delta_amount
29
+ end
30
+
31
+ # True if the other number is in range of the fuzzy number.
32
+ def ==(other)
33
+ (other - exact_value).abs <= delta_amount
34
+ end
35
+
36
+ def to_s
37
+ "<Approximately #{exact_value} +/- #{delta_amount}>"
38
+ end
39
+
40
+ # Set the delta for a fuzzy number.
41
+ def delta(delta)
42
+ @delta_amount = delta.abs
43
+ self
44
+ end
45
+
46
+ # Specifying a percentage of the exact number to be used in
47
+ # setting the delta.
48
+ def percent(percentage)
49
+ delta(exact_value * (percentage / 100.0))
50
+ end
51
+
52
+ # Specifying the number of epsilons to be used in setting the
53
+ # delta.
54
+ def epsilon(neps)
55
+ delta(exact_value * (neps * Float::EPSILON))
56
+ end
57
+ end
58
+
59
+ # Create an approximate number that is approximately equal to
60
+ # the given number, plus or minus the delta value. If no
61
+ # explicit delta is given, then the default delta that is about
62
+ # 10X the size of the smallest possible change in the given
63
+ # number will be used.
64
+ def about(*args)
65
+ FuzzyNumber.new(*args)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1 @@
1
+ require 'given/ext/numeric'
@@ -0,0 +1,41 @@
1
+ require 'given/file_cache'
2
+
3
+ module Given
4
+ class LineExtractor
5
+ def initialize(file_cache=nil)
6
+ @files = file_cache || FileCache.new
7
+ end
8
+
9
+ def line(file_name, line)
10
+ lines = @files.get(file_name)
11
+ extract_lines_from(lines, line-1)
12
+ end
13
+
14
+ def to_s
15
+ "<LineExtractor>"
16
+ end
17
+
18
+ private
19
+
20
+ def extract_lines_from(lines, line_index)
21
+ result = lines[line_index]
22
+ if continued?(result)
23
+ level = indentation_level(result)
24
+ begin
25
+ line_index += 1
26
+ result << lines[line_index]
27
+ end while indentation_level(lines[line_index]) > level
28
+ end
29
+ result
30
+ end
31
+
32
+ def continued?(string)
33
+ string =~ /(\{|do) *$/
34
+ end
35
+
36
+ def indentation_level(string)
37
+ string =~ /^(\s*)\S/
38
+ $1.nil? ? 1000000 : $1.size
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,69 @@
1
+
2
+ module Given
3
+ # Does this platform support natural assertions?
4
+ RBX_IN_USE = (defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx')
5
+ JRUBY_IN_USE = defined?(JRUBY_VERSION)
6
+
7
+ NATURAL_ASSERTIONS_SUPPORTED = ! (JRUBY_IN_USE || RBX_IN_USE)
8
+
9
+ def self.framework
10
+ @_gvn_framework
11
+ end
12
+
13
+ def self.framework=(framework)
14
+ @_gvn_framework = framework
15
+ end
16
+
17
+ def self.source_caching_disabled
18
+ @_gvn_source_caching_disabled
19
+ end
20
+
21
+ def self.source_caching_disabled=(value)
22
+ @_gvn_source_caching_disabled = value
23
+ end
24
+
25
+ # Detect the formatting requested in the given configuration object.
26
+ #
27
+ # If the format requires it, source caching will be enabled.
28
+ def self.detect_formatters(c)
29
+ format_active = c.formatters.any? { |f| f.class.name !~ /ProgressFormatter/ }
30
+ Given.source_caching_disabled = ! format_active
31
+ end
32
+
33
+ # Globally enable/disable natural assertions.
34
+ #
35
+ # There is a similar function in Extensions that works at a
36
+ # describe or context scope.
37
+ def self.use_natural_assertions(enabled=true)
38
+ ok_to_use_natural_assertions(enabled)
39
+ @natural_assertions_enabled = enabled
40
+ end
41
+
42
+ # TRUE if natural assertions are globally enabled?
43
+ def self.natural_assertions_enabled?
44
+ @natural_assertions_enabled
45
+ end
46
+
47
+ # Is is OK to use natural assertions on this platform.
48
+ #
49
+ # An error is raised if the the platform does not support natural
50
+ # assertions and the flag is attempting to enable them.
51
+ def self.ok_to_use_natural_assertions(enabled)
52
+ if enabled && ! NATURAL_ASSERTIONS_SUPPORTED
53
+ fail ArgumentError, "Natural Assertions are disabled for JRuby"
54
+ end
55
+ end
56
+
57
+ # Fail an example with the given messages.
58
+ #
59
+ # This should be the only place we reference the RSpec function.
60
+ # Everywhere else in rspec-given should be calling this function.
61
+ def self.fail_with(*args)
62
+ Given.framework.fail_with(*args)
63
+ end
64
+
65
+ # Error object used by RSpec to indicate a pending example.
66
+ def self.pending_error
67
+ Given.framework.pending_error
68
+ end
69
+ end
@@ -0,0 +1,177 @@
1
+ require 'given/module_methods'
2
+ require 'given/evaluator'
3
+
4
+ if Given::NATURAL_ASSERTIONS_SUPPORTED
5
+ require 'ripper'
6
+ require 'sorcerer'
7
+ end
8
+
9
+ module Given
10
+
11
+ InvalidThenError = Class.new(StandardError)
12
+
13
+ class NaturalAssertion
14
+
15
+ def initialize(clause_type, block, example, line_extractor)
16
+ @clause_type = clause_type
17
+ @evaluator = Evaluator.new(example, block)
18
+ @line_extractor = line_extractor
19
+ set_file_and_line(block)
20
+ end
21
+
22
+ VOID_SEXP = [:void_stmt]
23
+
24
+ def has_content?
25
+ assertion_sexp != VOID_SEXP
26
+ end
27
+
28
+ def message
29
+ @output = "#{@clause_type} expression failed at #{source_line}\n"
30
+ @output << "Failing expression: #{source.strip}\n" if @clause_type != "Then"
31
+ explain_failure
32
+ display_pairs(expression_value_pairs)
33
+ @output << "\n"
34
+ @output
35
+ end
36
+
37
+ private
38
+
39
+ BINARY_EXPLAINATIONS = {
40
+ :== => "to equal",
41
+ :!= => "to not equal",
42
+ :< => "to be less than",
43
+ :<= => "to be less or equal to",
44
+ :> => "to be greater than",
45
+ :>= => "to be greater or equal to",
46
+ :=~ => "to match",
47
+ :!~ => "to not match",
48
+ }
49
+
50
+ def explain_failure
51
+ if assertion_sexp.first == :binary && msg = BINARY_EXPLAINATIONS[assertion_sexp[2]]
52
+ @output << explain_expected("expected", assertion_sexp[1], msg, assertion_sexp[3])
53
+ end
54
+ end
55
+
56
+ def explain_expected(expect_msg, expect_sexp, got_msg, got_sexp)
57
+ width = [expect_msg.size, got_msg.size].max
58
+ sprintf("%#{width}s: %s\n%#{width}s: %s\n",
59
+ expect_msg, eval_sexp(expect_sexp),
60
+ got_msg, eval_sexp(got_sexp))
61
+ end
62
+
63
+ def expression_value_pairs
64
+ assertion_subexpressions.map { |exp|
65
+ [exp, @evaluator.eval_string(exp)]
66
+ }
67
+ end
68
+
69
+ def assertion_subexpressions
70
+ Sorcerer.subexpressions(assertion_sexp).reverse.uniq.reverse
71
+ end
72
+
73
+ def assertion_sexp
74
+ @assertion_sexp ||= extract_test_expression(Ripper::SexpBuilder.new(source).parse)
75
+ end
76
+
77
+ def source
78
+ @source ||= @line_extractor.line(@code_file, @code_line)
79
+ end
80
+
81
+ def set_file_and_line(block)
82
+ @code_file, @code_line = eval "[__FILE__, __LINE__]", block.binding
83
+ @code_line = @code_line.to_i
84
+ end
85
+
86
+ def extract_test_expression(sexp)
87
+ brace_block = extract_brace_block(sexp)
88
+ extract_first_statement(brace_block)
89
+ end
90
+
91
+ def extract_brace_block(sexp)
92
+ unless then_block?(sexp)
93
+ source = Sorcerer.source(sexp)
94
+ fail InvalidThenError, "Unexpected code at #{source_line}\n#{source}"
95
+ end
96
+ sexp[1][2][2]
97
+ end
98
+
99
+ def then_block?(sexp)
100
+ delve(sexp,0) == :program &&
101
+ delve(sexp,1,0) == :stmts_add &&
102
+ delve(sexp,1,2,0) == :method_add_block &&
103
+ (delve(sexp,1,2,2,0) == :brace_block || delve(sexp,1,2,2,0) == :do_block)
104
+ end
105
+
106
+ def extract_first_statement(block_sexp)
107
+ if contains_multiple_statements?(block_sexp)
108
+ source = Sorcerer.source(block_sexp)
109
+ fail InvalidThenError, "Multiple statements in Then block at #{source_line}\n#{source}"
110
+ end
111
+ extract_statement_from_block(block_sexp)
112
+ end
113
+
114
+ def contains_multiple_statements?(block_sexp)
115
+ !(delve(block_sexp,2,0) == :stmts_add &&
116
+ delve(block_sexp,2,1,0) == :stmts_new)
117
+ end
118
+
119
+ def extract_statement_from_block(block_sexp)
120
+ delve(block_sexp,2,2)
121
+ end
122
+
123
+ # Safely dive into an array with a list of indicies. Return nil
124
+ # if the element doesn't exist, or if the intermediate result is
125
+ # not indexable.
126
+ def delve(ary, *indicies)
127
+ result = ary
128
+ while !indicies.empty? && result
129
+ return nil unless result.respond_to?(:[])
130
+ i = indicies.shift
131
+ result = result[i]
132
+ end
133
+ result
134
+ end
135
+
136
+ def eval_sexp(sexp)
137
+ expr_string = Sorcerer.source(sexp)
138
+ @evaluator.eval_string(expr_string)
139
+ end
140
+
141
+ WRAP_WIDTH = 20
142
+
143
+ def display_pairs(pairs)
144
+ width = suggest_width(pairs) + 4
145
+ pairs.each do |x, v|
146
+ v = adjust_indentation(v)
147
+ fmt = multi_line?(v) ?
148
+ "%-#{width}s\n#{' '*width} <- %s\n" :
149
+ "%-#{width}s <- %s\n"
150
+ @output << sprintf(fmt, v, x)
151
+ end
152
+ end
153
+
154
+ def adjust_indentation(string)
155
+ string.to_s.gsub(/^/, ' ')
156
+ end
157
+
158
+ def multi_line?(string)
159
+ (string.size > WRAP_WIDTH) || (string =~ /\n/)
160
+ end
161
+
162
+ def suggest_width(pairs)
163
+ pairs.map { |x,v|
164
+ max_line_length(v)
165
+ }.select { |n| n < WRAP_WIDTH }.max || 10
166
+ end
167
+
168
+ def max_line_length(string)
169
+ string.to_s.split(/\n/).map { |s| s.size }.max
170
+ end
171
+
172
+ def source_line
173
+ "#{@code_file}:#{@code_line}"
174
+ end
175
+ end
176
+
177
+ end