slacker 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/Gemfile.lock +26 -0
- data/LICENSE +3 -0
- data/README.markdown +16 -0
- data/Rakefile +11 -0
- data/bin/slacker +28 -0
- data/lib/slacker.rb +138 -0
- data/lib/slacker/application.rb +201 -0
- data/lib/slacker/command_line_formatter.rb +59 -0
- data/lib/slacker/configuration.rb +59 -0
- data/lib/slacker/formatter.rb +19 -0
- data/lib/slacker/query_result_matcher.rb +169 -0
- data/lib/slacker/rspec_ext.rb +69 -0
- data/lib/slacker/rspec_monkey.rb +7 -0
- data/lib/slacker/sql.rb +39 -0
- data/lib/slacker/string_helper.rb +17 -0
- data/lib/slacker/version.rb +3 -0
- data/slacker.gemspec +23 -0
- data/spec/application_spec.rb +14 -0
- data/spec/query_result_matcher_spec.rb +267 -0
- data/spec/rspec_ext_spec.rb +127 -0
- data/spec/slacker_spec.rb +60 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/test_files/matcher/completely_blank.csv +0 -0
- data/spec/test_files/matcher/no_rows.csv +1 -0
- data/spec/test_files/matcher/test_1.csv +3 -0
- data/spec/test_files/test_slacker_project/data/test_1.csv +3 -0
- data/spec/test_files/test_slacker_project/sql/example_1/helper_1.sql +1 -0
- data/spec/test_files/test_slacker_project/sql/example_1/helper_2.sql +1 -0
- data/spec/test_files/test_slacker_project/sql/example_1/helper_2.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/helpers/HELPER_4.SQL +1 -0
- data/spec/test_files/test_slacker_project/sql/helpers/helper_1.sql +1 -0
- data/spec/test_files/test_slacker_project/sql/helpers/helper_2.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/helpers/helper_3.sql +1 -0
- data/spec/test_files/test_slacker_project/sql/helpers/helper_3.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/helpers/text_file_1.txt +1 -0
- data/spec/test_files/test_slacker_project/sql/multi_nested.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/nest/example_1/helper_1.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/nest/nested_1.sql.erb +2 -0
- data/spec/test_files/test_slacker_project/sql/nest/nested_2.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/nested.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/nested_with_params.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/no_params.sql.erb +3 -0
- data/spec/test_files/test_slacker_project/sql/params.sql.erb +2 -0
- data/spec/test_files/test_slacker_project/sql/test_1.sql +1 -0
- metadata +114 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
module Slacker
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :base_dir, :error_stream, :output_stream, :formatter, :db_server, :db_name, :db_user, :db_password, :console_enabled
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@console_enabled = true
|
7
|
+
@base_dir = Dir.pwd
|
8
|
+
@error_stream = nil
|
9
|
+
@output_stream = nil
|
10
|
+
@rspec_args = nil
|
11
|
+
@formatter = nil
|
12
|
+
@db_server = nil
|
13
|
+
@db_name = nil
|
14
|
+
@db_user = nil
|
15
|
+
@db_password = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def expand_path(path)
|
19
|
+
File.expand_path("#{@base_dir}/#{path}")
|
20
|
+
end
|
21
|
+
|
22
|
+
def console_enabled
|
23
|
+
@console_enabled
|
24
|
+
end
|
25
|
+
|
26
|
+
def console_enabled=(value)
|
27
|
+
@console_enabled = value
|
28
|
+
if @console_enabled
|
29
|
+
@error_stream = $stderr
|
30
|
+
@output_stream = $stdout
|
31
|
+
@rspec_args = ARGV
|
32
|
+
else
|
33
|
+
@error_stream = nil
|
34
|
+
@output_stream = nil
|
35
|
+
@rspec_args = []
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def rspec_args
|
40
|
+
if @rspec_args.nil? || @rspec_args.empty?
|
41
|
+
Dir.glob(expand_path("spec/**/*.rb"))
|
42
|
+
else
|
43
|
+
@rspec_args
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def rspec_args=(value)
|
48
|
+
@rspec_args = value
|
49
|
+
end
|
50
|
+
|
51
|
+
def dsn_string
|
52
|
+
"Driver={SQL Server};Server=#{@db_server};Database=#{@db_name};Uid=#{@db_user};Pwd=#{@db_password}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def db_config
|
56
|
+
{:adapter => 'sqlserver', :mode => 'odbc', :dsn => dsn_string}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'slacker'
|
2
|
+
|
3
|
+
module Slacker
|
4
|
+
module Formatter
|
5
|
+
def example_failure_text(example)
|
6
|
+
text = ''
|
7
|
+
exception = example.execution_result[:exception]
|
8
|
+
text << "Failure/Error: #{read_failed_line(exception, example).strip}\n"
|
9
|
+
text << "#{long_padding}#{exception.class.name << ":"}\n" unless exception.class.name =~ /RSpec/
|
10
|
+
exception.message.split("\n").each { |line| text << "#{line}\n" }
|
11
|
+
|
12
|
+
format_backtrace(example.execution_result[:exception].backtrace, example).each do |backtrace_info|
|
13
|
+
text << "# #{backtrace_info}\n"
|
14
|
+
end
|
15
|
+
|
16
|
+
text
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Slacker
|
4
|
+
class QueryResultMatcher
|
5
|
+
def initialize(golden_master)
|
6
|
+
@golden_master = golden_master
|
7
|
+
@failure_message = ''
|
8
|
+
end
|
9
|
+
|
10
|
+
def matches?(subject)
|
11
|
+
does_match = false
|
12
|
+
|
13
|
+
subject = normalize_query_result_subject(subject)
|
14
|
+
|
15
|
+
catch :no_match do
|
16
|
+
test_type_match(subject)
|
17
|
+
test_value_match(subject)
|
18
|
+
does_match = true
|
19
|
+
end
|
20
|
+
|
21
|
+
does_match
|
22
|
+
end
|
23
|
+
|
24
|
+
def failure_message_for_should
|
25
|
+
@failure_message
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
#test if the subject type is consistent with the golden master
|
31
|
+
def test_type_match(subject)
|
32
|
+
if !is_well_formed_query_result?(subject)
|
33
|
+
throw_no_match "Can perform query matches only against a well formed query result subject"
|
34
|
+
end
|
35
|
+
|
36
|
+
if (@golden_master.kind_of? Array) && !is_well_formed_query_result?(@golden_master)
|
37
|
+
throw_no_match "Cannot match against a non-well formed golden master array"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_value_match(subject)
|
42
|
+
case @golden_master
|
43
|
+
when CSV::Table
|
44
|
+
test_csv_table_match(subject)
|
45
|
+
when Array
|
46
|
+
test_array_match(subject)
|
47
|
+
else
|
48
|
+
test_single_value_match(subject)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Compare the golden master CSV table with the subject query result
|
53
|
+
def test_csv_table_match(subject)
|
54
|
+
# Compare the fields
|
55
|
+
if !subject.empty?
|
56
|
+
subject_fields = subject[0].keys
|
57
|
+
master_fields = @golden_master.headers
|
58
|
+
|
59
|
+
if subject_fields.count != master_fields.count
|
60
|
+
throw_no_match "Expected #{master_fields.count} field(s), got #{subject_fields.count}"
|
61
|
+
end
|
62
|
+
|
63
|
+
master_fields.each_with_index do |column, index|
|
64
|
+
if column != subject_fields[index]
|
65
|
+
throw_no_match "Expected field \"#{column}\", got field \"#{subject_fields[index]}\""
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Compare the number of records
|
71
|
+
subject_record_count = subject.count
|
72
|
+
master_record_count = @golden_master.inject(0){|count| count += 1}
|
73
|
+
if subject_record_count != master_record_count
|
74
|
+
throw_no_match "Expected #{master_record_count} record(s), got #{subject_record_count}"
|
75
|
+
end
|
76
|
+
|
77
|
+
# Compare the values of the golden master with the subject
|
78
|
+
current_row = 0
|
79
|
+
@golden_master.each do |row|
|
80
|
+
row.each do |field, master_string|
|
81
|
+
subject_value = subject[current_row][field]
|
82
|
+
if !match_values?(master_string, subject_value)
|
83
|
+
throw_no_match "Field \"#{field}\", Record #{current_row + 1}: Expected value #{master_string.nil? ? '<NULL>' : "\"#{master_string}\""}, got #{subject_value.nil? ? '<NULL>' : "\"#{subject_value}\""}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
current_row += 1
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def match_types?(master_val, subject_val)
|
91
|
+
subject_val.kind_of? master_val.class
|
92
|
+
end
|
93
|
+
|
94
|
+
def match_values?(master_val, subject_val)
|
95
|
+
if master_val.nil?
|
96
|
+
master_val == subject_val
|
97
|
+
elsif subject_val.kind_of?(Time) && master_val.kind_of?(String)
|
98
|
+
Time.parse(master_val) == subject_val
|
99
|
+
else
|
100
|
+
subject_val.to_s == master_val.to_s
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_array_match(subject)
|
105
|
+
# Compare the fields
|
106
|
+
if !(subject.empty? || @golden_master.empty?)
|
107
|
+
subject_fields = subject[0].keys
|
108
|
+
master_fields = @golden_master[0].keys
|
109
|
+
|
110
|
+
if subject_fields.count != master_fields.count
|
111
|
+
throw_no_match "Expected #{master_fields.count} field(s), got #{subject_fields.count}"
|
112
|
+
end
|
113
|
+
|
114
|
+
master_fields.each_with_index do |column, index|
|
115
|
+
if column != subject_fields[index]
|
116
|
+
throw_no_match "Expected field \"#{column}\", got field \"#{subject_fields[index]}\""
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Compare the number of records
|
122
|
+
subject_record_count = subject.count
|
123
|
+
master_record_count = @golden_master.count
|
124
|
+
if subject_record_count != master_record_count
|
125
|
+
throw_no_match "Expected #{master_record_count} record(s), got #{subject_record_count}"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Compare the values of the golden master with the subject
|
129
|
+
current_row = 0
|
130
|
+
@golden_master.each do |row|
|
131
|
+
row.each do |field, master_value|
|
132
|
+
subject_value = subject[current_row][field]
|
133
|
+
if !match_values?(master_value, subject_value)
|
134
|
+
throw_no_match "Field \"#{field}\", Record #{current_row + 1}: Expected value #{master_value.nil? ? '<NULL>' : "\"#{master_value}\""}, got #{subject_value.nil? ? '<NULL>' : "\"#{subject_value}\""}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
current_row += 1
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def is_well_formed_query_result?(arr)
|
142
|
+
return false unless arr.kind_of? Array
|
143
|
+
header =[]
|
144
|
+
arr.find{|row| !row.kind_of?(Hash) || (header = header.empty? ? row.keys : header) != row.keys || row.keys.empty?}.nil?
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_single_value_match(subject)
|
148
|
+
subject_value = subject[0].values[0]
|
149
|
+
subject_field = subject[0].keys[0]
|
150
|
+
if !match_types?(@golden_master, subject_value)
|
151
|
+
throw_no_match "Field \"#{subject_field}\", Record 1: Expected type \"#{@golden_master.class}\", got \"#{subject_value.class}\""
|
152
|
+
end
|
153
|
+
|
154
|
+
if !match_values?(@golden_master, subject_value)
|
155
|
+
throw_no_match "Field \"#{subject_field}\", Record 1: Expected value #{@golden_master.nil? ? '<NULL>' : "\"#{@golden_master}\""}, got #{subject_value.nil? ? '<NULL>' : "\"#{subject_value}\""}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# In case of a multi-resultset subject, extract the first result set
|
160
|
+
def normalize_query_result_subject(subject)
|
161
|
+
subject.kind_of?(Array) && !subject.empty? && is_well_formed_query_result?(subject[0]) ? subject[0] : subject
|
162
|
+
end
|
163
|
+
|
164
|
+
def throw_no_match(message)
|
165
|
+
@failure_message = message
|
166
|
+
throw :no_match
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'slacker'
|
2
|
+
require 'slacker/query_result_matcher'
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
module Slacker
|
6
|
+
module RSpecExt
|
7
|
+
def query(query_string, options = {}, log_name = nil)
|
8
|
+
log_name ||= Slacker.construct_log_name('query', query_string, options)
|
9
|
+
sql = Slacker.sql_from_query_string(query_string, options)
|
10
|
+
@results = Slacker.query_script(example, sql, log_name)
|
11
|
+
if block_given?
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
@results
|
15
|
+
end
|
16
|
+
|
17
|
+
def result(index = 1, options = {})
|
18
|
+
# Flatten the result in case we're getting a multi-result-set response
|
19
|
+
res = case !@results.empty? && @results[0].kind_of?(Array)
|
20
|
+
when true
|
21
|
+
raise "The query result contains only #{@results.count} result set(s)" unless @results.count >= index
|
22
|
+
@results[index - 1]
|
23
|
+
else
|
24
|
+
raise "The query result contains a single result set" unless index == 1
|
25
|
+
@results
|
26
|
+
end
|
27
|
+
|
28
|
+
#Optionally extract the record and or the field
|
29
|
+
if options[:record] != nil
|
30
|
+
raise "The result set contains only #{res.count} record(s)" unless res.count >= options[:record]
|
31
|
+
res = res[options[:record] - 1]
|
32
|
+
if options[:field] != nil
|
33
|
+
raise "The result set does not contain field \"#{options[:field]}\"" unless res[options[:field]] != nil
|
34
|
+
res = res[options[:field]]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
res
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get a matcher which will compare the query results to a golden master
|
42
|
+
def match(golden_master)
|
43
|
+
QueryResultMatcher.new(Slacker.filter_golden_master(golden_master))
|
44
|
+
end
|
45
|
+
|
46
|
+
def load_csv(csv_file_or_object, table_name, log_name = nil)
|
47
|
+
log_name ||= "load_csv '#{csv_file_or_object.kind_of?(CSV::Table) ? 'CSV Object' : csv_file_or_object }', 'table_name'"
|
48
|
+
csv_object = case csv_file_or_object
|
49
|
+
when String then Slacker.get_csv(csv_file_or_object)
|
50
|
+
when CSV::Table then csv_file_or_object
|
51
|
+
when Array then Slacker.hash_array_to_csv(csv_file_or_object)
|
52
|
+
end
|
53
|
+
|
54
|
+
Slacker.load_csv(example, csv_object, table_name, log_name)
|
55
|
+
end
|
56
|
+
|
57
|
+
def csv(csv_file)
|
58
|
+
Slacker.get_csv(csv_file)
|
59
|
+
end
|
60
|
+
|
61
|
+
def sql
|
62
|
+
Slacker.sql(self)
|
63
|
+
end
|
64
|
+
|
65
|
+
def yes?(val)
|
66
|
+
val != nil && val.downcase == 'yes'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/slacker/sql.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'slacker'
|
2
|
+
require 'slacker/rspec_ext'
|
3
|
+
|
4
|
+
module Slacker
|
5
|
+
class Sql < BasicObject
|
6
|
+
attr_accessor :base_folder, :rspec_ext
|
7
|
+
|
8
|
+
def initialize(base_folder, rspec_ext)
|
9
|
+
@base_folder = base_folder
|
10
|
+
@rspec_ext = rspec_ext
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(method_name, *params, &block)
|
14
|
+
::Kernel.raise "Slacker::Sql.rspec_ext not initialized" if rspec_ext.nil?
|
15
|
+
::Kernel.raise "Missing folder #{base_folder}" if !::File.directory?(base_folder)
|
16
|
+
|
17
|
+
method_name = method_name.to_s
|
18
|
+
|
19
|
+
if ::File.directory?(::File.join(base_folder, method_name))
|
20
|
+
::Slacker::Sql.new(::File.join(base_folder, method_name), rspec_ext)
|
21
|
+
else
|
22
|
+
sql_file = ::Slacker.sql_file_from_method_name(base_folder, method_name)
|
23
|
+
case sql_file
|
24
|
+
when nil
|
25
|
+
::Kernel.raise "No SQL file found corresponding to method '#{method_name}' in folder #{base_folder}"
|
26
|
+
else
|
27
|
+
rspec_ext.query sql_file, *params, &block
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def respond_to?(method_name)
|
33
|
+
method_name = method_name.to_s
|
34
|
+
::Kernel.raise "Slacker::Sql.rspec_ext not initialized" if rspec_ext.nil?
|
35
|
+
::File.directory?(::File.join(base_folder, method_name)) ||
|
36
|
+
!(::Slacker.sql_file_from_method_name(base_folder, method_name).nil?)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Slacker
|
2
|
+
module StringHelper
|
3
|
+
class << self
|
4
|
+
def camelize(lower_case_and_underscored_word)
|
5
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
6
|
+
end
|
7
|
+
|
8
|
+
def constantize(camel_cased_word)
|
9
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
|
10
|
+
raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
|
11
|
+
end
|
12
|
+
|
13
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/slacker.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "slacker/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "slacker"
|
7
|
+
s.version = Slacker::VERSION
|
8
|
+
s.authors = ["Vassil Kovatchev"]
|
9
|
+
s.email = ["vassil.kovatchev@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/vassilvk/slacker"
|
11
|
+
s.summary = %q{Behavior Driven Development for SQL Server}
|
12
|
+
s.description = %q{RSpec-based framework for developing automated tests for SQL Server programmable objects such as stored procedures and scalar/table functions}
|
13
|
+
|
14
|
+
s.rubyforge_project = "slacker"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency 'ruby-odbc', '=0.99994'
|
22
|
+
s.add_dependency 'rspec', '=2.5.0'
|
23
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'slacker'
|
2
|
+
|
3
|
+
describe Slacker::Application do
|
4
|
+
it 'responds to run' do
|
5
|
+
Slacker.application.should respond_to(:run)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'responds to target_folder_structure with the correct list of folders' do
|
9
|
+
app = Slacker.application
|
10
|
+
app.should respond_to(:target_folder_structure)
|
11
|
+
folder_struct = app.target_folder_structure
|
12
|
+
folder_struct.should == ['data', 'debug', 'debug/passed_examples', 'debug/failed_examples', 'sql', 'spec', 'lib', 'lib/helpers']
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,267 @@
|
|
1
|
+
require 'slacker'
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
describe Slacker::QueryResultMatcher do
|
6
|
+
def deep_copy(obj)
|
7
|
+
Marshal.load(Marshal.dump(obj))
|
8
|
+
end
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
@subject = [{'Field 1' => 12, 'Field_2' => nil, 'b' => ''},
|
12
|
+
{'Field 1' => 'test string', 'Field_2' => Time.parse('1/1/2011'), 'b' => 8.9}]
|
13
|
+
end
|
14
|
+
|
15
|
+
shared_examples_for 'table-based matcher' do
|
16
|
+
it 'correctly rejects a non-matching query result based on wrong columns' do
|
17
|
+
one_column_too_few = deep_copy(@subject).each{|row| row.delete('b')}
|
18
|
+
one_column_too_many = deep_copy(@subject).each{|row| row['new column'] = 'val 1'}
|
19
|
+
wrong_column_name = deep_copy(@subject).each{|row| row.delete('Field_2'); row['Field_x'] = 'val x'}
|
20
|
+
|
21
|
+
@matcher.matches?(one_column_too_few).should be_false
|
22
|
+
@matcher.failure_message_for_should.should == 'Expected 3 field(s), got 2'
|
23
|
+
|
24
|
+
@matcher.matches?(one_column_too_many).should be_false
|
25
|
+
@matcher.failure_message_for_should.should == 'Expected 3 field(s), got 4'
|
26
|
+
|
27
|
+
@matcher.matches?(wrong_column_name).should be_false
|
28
|
+
@matcher.failure_message_for_should.should == 'Expected field "Field_2", got field "b"'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'correctly rejects a non-matching query result based on number of records' do
|
32
|
+
one_row_too_few = deep_copy(@subject)
|
33
|
+
one_row_too_few.shift
|
34
|
+
one_row_too_many = deep_copy(@subject) << @subject[0]
|
35
|
+
empty_query_result = []
|
36
|
+
|
37
|
+
@matcher.matches?(one_row_too_few).should be_false
|
38
|
+
@matcher.failure_message_for_should.should == 'Expected 2 record(s), got 1'
|
39
|
+
|
40
|
+
@matcher.matches?(one_row_too_many).should be_false
|
41
|
+
@matcher.failure_message_for_should.should == 'Expected 2 record(s), got 3'
|
42
|
+
|
43
|
+
@matcher.matches?(empty_query_result).should be_false
|
44
|
+
@matcher.failure_message_for_should.should == 'Expected 2 record(s), got 0'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'correctly rejects a non-matching query result based on a value' do
|
48
|
+
wrong_int = deep_copy(@subject)
|
49
|
+
wrong_int[0]['Field 1'] = 14
|
50
|
+
|
51
|
+
wrong_int_type = deep_copy(@subject)
|
52
|
+
wrong_int_type[1]['Field 1'] = 14
|
53
|
+
|
54
|
+
wrong_nil = deep_copy(@subject)
|
55
|
+
wrong_nil[0]['Field 1'] = nil
|
56
|
+
|
57
|
+
wrong_non_nil = deep_copy(@subject)
|
58
|
+
wrong_non_nil[0]['Field_2'] = 'whatever'
|
59
|
+
|
60
|
+
misplaced_fields = deep_copy(@subject)
|
61
|
+
misplaced_fields[0].delete('Field 1')
|
62
|
+
misplaced_fields[1].delete('Field 1')
|
63
|
+
misplaced_fields[0]['Field 1'] = 12
|
64
|
+
misplaced_fields[1]['Field 1'] = 'test string'
|
65
|
+
|
66
|
+
@matcher.matches?(wrong_int).should be_false
|
67
|
+
@matcher.failure_message_for_should.should == 'Field "Field 1", Record 1: Expected value "12", got "14"'
|
68
|
+
|
69
|
+
|
70
|
+
@matcher.matches?(wrong_int_type).should be_false
|
71
|
+
@matcher.failure_message_for_should.should == 'Field "Field 1", Record 2: Expected value "test string", got "14"'
|
72
|
+
|
73
|
+
@matcher.matches?(wrong_nil).should be_false
|
74
|
+
@matcher.failure_message_for_should.should == 'Field "Field 1", Record 1: Expected value "12", got <NULL>'
|
75
|
+
|
76
|
+
@matcher.matches?(wrong_non_nil).should be_false
|
77
|
+
@matcher.failure_message_for_should.should == 'Field "Field_2", Record 1: Expected value <NULL>, got "whatever"'
|
78
|
+
|
79
|
+
@matcher.matches?(misplaced_fields).should be_false
|
80
|
+
@matcher.failure_message_for_should.should == 'Expected field "Field 1", got field "Field_2"'
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'correctly compares with a query result' do
|
84
|
+
@matcher.matches?(@subject).should be_true(@matcher.failure_message_for_should)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'only accepts well formed query result subjects' do
|
88
|
+
expected_message = /Can perform query matches only against a well formed query result subject/
|
89
|
+
@matcher.matches?(1).should be_false
|
90
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
91
|
+
|
92
|
+
@matcher.matches?('test string').should be_false
|
93
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
94
|
+
|
95
|
+
@matcher.matches?(1.1).should be_false
|
96
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
97
|
+
|
98
|
+
@matcher.matches?(Time.now).should be_false
|
99
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
100
|
+
|
101
|
+
@matcher.matches?(nil).should be_false
|
102
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
103
|
+
|
104
|
+
@matcher.matches?([{:f1 => 'x', :f2 => 'y'}, {:f1 => 'x'}]).should be_false
|
105
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
106
|
+
|
107
|
+
@matcher.matches?([{:f1 => 'x', :f2 => 'y'}, {:f1 => 'x', :f3 => 'y'}]).should be_false
|
108
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
109
|
+
|
110
|
+
@matcher.matches?([{:f1 => 'x', :f2 => 'y'}, {:f1 => 'x', :f2 => 'y', :f3 => 'z'}]).should be_false
|
111
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
112
|
+
|
113
|
+
@matcher.matches?([{:f1 => 'x', :f2 => 'y'}, 12]).should be_false
|
114
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
115
|
+
|
116
|
+
@matcher.matches?([12, {:f1 => 'x', :f2 => 'y'}]).should be_false
|
117
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
118
|
+
|
119
|
+
# An array with a single empty row is not a well-formed query result
|
120
|
+
@matcher.matches?([{}]).should be_false
|
121
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'CSV-based golden master' do
|
127
|
+
before(:each) do
|
128
|
+
@matcher = Slacker::QueryResultMatcher.new(SpecHelper.load_csv('matcher/test_1.csv'))
|
129
|
+
end
|
130
|
+
|
131
|
+
it_behaves_like 'table-based matcher'
|
132
|
+
|
133
|
+
it 'correctly compares an empty subject with an empty CSV file' do
|
134
|
+
matcher = Slacker::QueryResultMatcher.new(SpecHelper.load_csv('matcher/no_rows.csv'))
|
135
|
+
matcher.matches?([]).should be_true(matcher.failure_message_for_should)
|
136
|
+
|
137
|
+
matcher = Slacker::QueryResultMatcher.new(SpecHelper.load_csv('matcher/completely_blank.csv'))
|
138
|
+
matcher.matches?([]).should be_true(matcher.failure_message_for_should)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe 'Array-based golden master' do
|
143
|
+
before(:each) do
|
144
|
+
@matcher = Slacker::QueryResultMatcher.new(deep_copy(@subject))
|
145
|
+
end
|
146
|
+
|
147
|
+
it_behaves_like 'table-based matcher'
|
148
|
+
|
149
|
+
it 'only accepts well formed query result golden master' do
|
150
|
+
expected_message = /Cannot match against a non-well formed golden master array/
|
151
|
+
|
152
|
+
matcher = Slacker::QueryResultMatcher.new([{:f1 => 'x', :f2 => 'y'}, {:f1 => 'x'}])
|
153
|
+
matcher.matches?([]).should be_false
|
154
|
+
matcher.failure_message_for_should.should =~ expected_message
|
155
|
+
|
156
|
+
matcher = Slacker::QueryResultMatcher.new([{:f1 => 'x', :f2 => 'y'}, {:f1 => 'x', :f3 => 'y'}])
|
157
|
+
matcher.matches?([]).should be_false
|
158
|
+
matcher.failure_message_for_should.should =~ expected_message
|
159
|
+
|
160
|
+
matcher = Slacker::QueryResultMatcher.new([{:f1 => 'x', :f2 => 'y'}, {:f1 => 'x', :f2 => 'y', :f3 => 'z'}])
|
161
|
+
matcher.matches?([]).should be_false
|
162
|
+
matcher.failure_message_for_should.should =~ expected_message
|
163
|
+
|
164
|
+
matcher = Slacker::QueryResultMatcher.new([{:f1 => 'x', :f2 => 'y'}, 12])
|
165
|
+
matcher.matches?([]).should be_false
|
166
|
+
matcher.failure_message_for_should.should =~ expected_message
|
167
|
+
|
168
|
+
matcher = Slacker::QueryResultMatcher.new([12, {:f1 => 'x', :f2 => 'y'}])
|
169
|
+
matcher.matches?([]).should be_false
|
170
|
+
matcher.failure_message_for_should.should =~ expected_message
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'correctly compares an empty subject with an empty golden master' do
|
174
|
+
matcher = Slacker::QueryResultMatcher.new([])
|
175
|
+
matcher.matches?([]).should be_true(matcher.failure_message_for_should)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
shared_examples_for 'single-value-based matcher' do
|
180
|
+
it 'should correctly match the first value in the result set' do
|
181
|
+
matcher = Slacker::QueryResultMatcher.new(@correct_golden_master)
|
182
|
+
matcher.matches?(@subject).should be_true(matcher.failure_message_for_should)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should correctly reject a non-matching first item by value' do
|
186
|
+
if !@wrong_value_golden_master.nil? #Skip nil from value check when the master is nil
|
187
|
+
matcher = Slacker::QueryResultMatcher.new(@wrong_value_golden_master)
|
188
|
+
matcher.matches?(@subject).should be_false
|
189
|
+
matcher.failure_message_for_should.should =~ /Expected value "#{@wrong_value_golden_master}", got "#{@subject[0].values[0]}"/
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'should correctly reject a non-matching first item by type' do
|
194
|
+
matcher = Slacker::QueryResultMatcher.new(@wrong_type_golden_master)
|
195
|
+
matcher.matches?(@subject).should be_false
|
196
|
+
matcher.failure_message_for_should.should =~ /Expected type "#{@wrong_type_golden_master.class}", got \"#{@subject[0].values[0].class}\"/
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe 'Integer-based golden master' do
|
201
|
+
before(:each) do
|
202
|
+
@subject = [{'Field 1' => 14, 'Field_2' => 'whatever'}]
|
203
|
+
@correct_golden_master = 14
|
204
|
+
@wrong_value_golden_master = 15
|
205
|
+
@wrong_type_golden_master = 'whatever'
|
206
|
+
end
|
207
|
+
|
208
|
+
it_behaves_like 'single-value-based matcher'
|
209
|
+
end
|
210
|
+
|
211
|
+
describe 'String-based golden master' do
|
212
|
+
before(:each) do
|
213
|
+
@subject = [{'Field 1' => 'test string', 'Field_2' => 12}]
|
214
|
+
@correct_golden_master = 'test string'
|
215
|
+
@wrong_value_golden_master = 'whatever'
|
216
|
+
@wrong_type_golden_master = 15
|
217
|
+
end
|
218
|
+
|
219
|
+
it_behaves_like 'single-value-based matcher'
|
220
|
+
end
|
221
|
+
|
222
|
+
describe 'Floating point-based golden master' do
|
223
|
+
before(:each) do
|
224
|
+
@subject = [{'Field 1' => 18.2, 'Field_2' => 12}]
|
225
|
+
@correct_golden_master = 18.2
|
226
|
+
@wrong_value_golden_master = 18.7
|
227
|
+
@wrong_type_golden_master = 15
|
228
|
+
end
|
229
|
+
|
230
|
+
it_behaves_like 'single-value-based matcher'
|
231
|
+
end
|
232
|
+
|
233
|
+
describe 'Date-based golden master' do
|
234
|
+
before(:each) do
|
235
|
+
@subject = [{'Field 1' => Time.parse('1/1/2011'), 'Field_2' => 12}]
|
236
|
+
@correct_golden_master = Time.parse('1/1/2011')
|
237
|
+
@wrong_value_golden_master = Time.parse('2/1/2011')
|
238
|
+
@wrong_type_golden_master = 'whatever'
|
239
|
+
end
|
240
|
+
|
241
|
+
it_behaves_like 'single-value-based matcher'
|
242
|
+
end
|
243
|
+
|
244
|
+
describe 'Nil-based golden master' do
|
245
|
+
before(:each) do
|
246
|
+
@subject = [{'Field 1' => nil, 'Field_2' => 12}]
|
247
|
+
@correct_golden_master = nil
|
248
|
+
@wrong_value_golden_master = nil # There is no wrong value for nil-based master
|
249
|
+
@wrong_type_golden_master = 'whatever'
|
250
|
+
end
|
251
|
+
|
252
|
+
it_behaves_like 'single-value-based matcher'
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'correctly matches a multi-result subject' do
|
256
|
+
subject = [@subject, [{:Field_x => 12}]]
|
257
|
+
matcher = Slacker::QueryResultMatcher.new(deep_copy(@subject))
|
258
|
+
matcher.matches?(subject).should be_true(matcher.failure_message_for_should)
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'correctly rejects a wrong multi-result subject' do
|
262
|
+
subject = [[{:Field_x => 12}], @subject]
|
263
|
+
matcher = Slacker::QueryResultMatcher.new(deep_copy(@subject))
|
264
|
+
matcher.matches?(subject).should be_false
|
265
|
+
matcher.failure_message_for_should.should == 'Expected 3 field(s), got 1'
|
266
|
+
end
|
267
|
+
end
|