given_core 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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