dbexpect 0.11.0

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.
Files changed (46) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +41 -0
  4. data/COPYING +674 -0
  5. data/Gemfile +20 -0
  6. data/Gemfile.lock +45 -0
  7. data/LICENSE.txt +2 -0
  8. data/README.md +106 -0
  9. data/Rakefile +65 -0
  10. data/VERSION +1 -0
  11. data/bin/dbexpect +33 -0
  12. data/bin/great-expectations +28 -0
  13. data/bin/ready-rig +27 -0
  14. data/database.yml +9 -0
  15. data/lib/dbexpect.rb +19 -0
  16. data/lib/dbexpect/command_runner.rb +21 -0
  17. data/lib/dbexpect/console_formatter.rb +40 -0
  18. data/lib/dbexpect/d_s_l_parser.rb +124 -0
  19. data/lib/dbexpect/database.rb +59 -0
  20. data/lib/dbexpect/db_null.rb +25 -0
  21. data/lib/dbexpect/db_sequence.rb +26 -0
  22. data/lib/dbexpect/db_string.rb +29 -0
  23. data/lib/dbexpect/dbexpect.rb +87 -0
  24. data/lib/dbexpect/defaulting_row_set.rb +68 -0
  25. data/lib/dbexpect/expectation_checker.rb +32 -0
  26. data/lib/dbexpect/expectation_tree_node.rb +67 -0
  27. data/lib/dbexpect/expectations/expectation.rb +45 -0
  28. data/lib/dbexpect/expectations/row_count_expectation.rb +32 -0
  29. data/lib/dbexpect/expectations/row_expectation.rb +32 -0
  30. data/lib/dbexpect/odbc_connection.rb +47 -0
  31. data/lib/dbexpect/row.rb +43 -0
  32. data/lib/dbexpect/table.rb +71 -0
  33. data/spec/dbexpect_integration_spec.rb +131 -0
  34. data/spec/defaulting_row_set_spec.rb +37 -0
  35. data/spec/expectation_checker_spec.rb +47 -0
  36. data/spec/expectations/row_expectation_spec.rb +51 -0
  37. data/spec/fixtures/basic_test_expected_inserts.sql +6 -0
  38. data/spec/fixtures/cleanup_db.sql +2 -0
  39. data/spec/fixtures/expected_output.txt +11 -0
  40. data/spec/fixtures/sample_db.sql +12 -0
  41. data/spec/fixtures/sample_project/database.yml +9 -0
  42. data/spec/fixtures/sample_project/defaults/defaults.rb +5 -0
  43. data/spec/fixtures/sample_project/tests/basic_test.rb +41 -0
  44. data/spec/fixtures/sample_project/tests/test2.rb +3 -0
  45. data/spec/spec_helper.rb +28 -0
  46. metadata +186 -0
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+
9
+
10
+ group :development do
11
+ gem 'ruby-odbc'
12
+ gem "rdoc", "~> 3.12"
13
+ gem "bundler"
14
+ gem "jeweler", "~> 1.8.3"
15
+ gem "rspec", "~> 2.8.0"
16
+ gem 'pg'
17
+ gem "pry"
18
+ gem "ZenTest"
19
+ end
20
+
data/Gemfile.lock ADDED
@@ -0,0 +1,45 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ ZenTest (4.8.2)
5
+ coderay (1.0.7)
6
+ diff-lcs (1.1.3)
7
+ git (1.2.5)
8
+ jeweler (1.8.4)
9
+ bundler (~> 1.0)
10
+ git (>= 1.2.5)
11
+ rake
12
+ rdoc
13
+ json (1.7.5)
14
+ method_source (0.8)
15
+ pg (0.14.0)
16
+ pry (0.9.10)
17
+ coderay (~> 1.0.5)
18
+ method_source (~> 0.8)
19
+ slop (~> 3.3.1)
20
+ rake (0.9.2.2)
21
+ rdoc (3.12)
22
+ json (~> 1.4)
23
+ rspec (2.8.0)
24
+ rspec-core (~> 2.8.0)
25
+ rspec-expectations (~> 2.8.0)
26
+ rspec-mocks (~> 2.8.0)
27
+ rspec-core (2.8.0)
28
+ rspec-expectations (2.8.0)
29
+ diff-lcs (~> 1.1.2)
30
+ rspec-mocks (2.8.0)
31
+ ruby-odbc (0.99994)
32
+ slop (3.3.3)
33
+
34
+ PLATFORMS
35
+ ruby
36
+
37
+ DEPENDENCIES
38
+ ZenTest
39
+ bundler
40
+ jeweler (~> 1.8.3)
41
+ pg
42
+ pry
43
+ rdoc (~> 3.12)
44
+ rspec (~> 2.8.0)
45
+ ruby-odbc
data/LICENSE.txt ADDED
@@ -0,0 +1,2 @@
1
+ Copyright (c) 2012 Beau Fabry
2
+
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ dbexpect
2
+ =======
3
+
4
+ dbexpect is a domain specific language written in ruby for testing ETL solutions.
5
+
6
+ Taking cues from Rspec in structure and usage, the point is to enable
7
+ the specification of unit test data, job running, and expected outcomes
8
+ for an entity/test cases in a ruby file using an internal DSL that is
9
+ targeted at this kind of testing.
10
+
11
+ dbexpect is ideally suited to creating automated unit tests for
12
+ individual ETL jobs in a data warehousing or data migration project.
13
+ Helping to ensure correctness of the job initially developed, and
14
+ catching problems later on when someone makes a change that could affect
15
+ existing functionality.
16
+
17
+ Sample test
18
+ ---------
19
+
20
+ describe "Moving customers from source to target" do
21
+ @src = table(:dbexpect_src,:dbexpect_src,:customers_src)
22
+ @tgt = table(:dbexpect_tgt,:dbexpect_tgt,:customers_tgt)
23
+
24
+ etl_run_command "ruby etl2.rb"
25
+
26
+ expect_total_rows @tgt, 1
27
+
28
+ describe "it should upcase customer names" do
29
+ insert_into @src,
30
+ [:id,:name],
31
+ [[1,"Fred"]]
32
+
33
+ expect_rows @tgt,
34
+ [:id,:name],
35
+ [[1,"FRED"]]
36
+ end
37
+
38
+ describe "it should not migrate smith (because screw that guy)" do
39
+ insert_into @src,
40
+ [:id,:name],
41
+ [[1,"Smith"]]
42
+
43
+ # expect no rows
44
+ end
45
+ end
46
+
47
+ Installation
48
+ ------------
49
+ gem install dbexpect
50
+
51
+ Create a database.yml file in a folder where you want to store your
52
+ tests, and set up connections for each of the databases you want dbexpect
53
+ to talk to. Each of the connections will need to have an ODBC connection
54
+ defined as well.
55
+
56
+ database.yml:
57
+
58
+ database1:
59
+ database: odbc_dsn
60
+ username: barry
61
+ password: secret
62
+
63
+ database2:
64
+ database: odbc_dsn2
65
+ username: shaz
66
+ password: secret
67
+
68
+ Usage
69
+ -----
70
+ Assuming a folder structure for your tests that looks like this:
71
+
72
+ /
73
+ |-database.yml
74
+ |
75
+ |- defaults/
76
+ | |- defaults_for_tablex.rb
77
+ |
78
+ |- tests/
79
+ |- test1.rb
80
+ |- test2.rb
81
+
82
+ To run the tests in test1.rb:
83
+
84
+ prompt:/$ dbexpect tests/test1.rb
85
+
86
+ There is a sample dbexpect project at
87
+ http://github.com/C3/dbexpect_example for more information.
88
+
89
+
90
+ License
91
+ -------
92
+
93
+ Copyright 2012 C3 Business Solutions. See COPYING for further details.
94
+
95
+ dbexpect is free software: you can redistribute it and/or modify
96
+ it under the terms of the GNU General Public License as published by
97
+ the Free Software Foundation, either version 3 of the License, or
98
+ (at your option) any later version.
99
+
100
+ dbexpect is distributed in the hope that it will be useful,
101
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
102
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
103
+ GNU General Public License for more details.
104
+
105
+ You should have received a copy of the GNU General Public License
106
+ along with dbexpect. If not, see <http://www.gnu.org/licenses/>.
data/Rakefile ADDED
@@ -0,0 +1,65 @@
1
+ # Copyright 2012 C3 Business Solutions
2
+ #
3
+ # This file is part of dbexpect.
4
+ #
5
+ # dbexpect is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # dbexpect is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with dbexpect. If not, see <http://www.gnu.org/licenses/>.
17
+ # encoding: utf-8
18
+
19
+ require 'rubygems'
20
+ require 'bundler'
21
+ begin
22
+ Bundler.setup(:default, :development)
23
+ rescue Bundler::BundlerError => e
24
+ $stderr.puts e.message
25
+ $stderr.puts "Run `bundle install` to install missing gems"
26
+ exit e.status_code
27
+ end
28
+ require 'rake'
29
+
30
+ require 'jeweler'
31
+ Jeweler::Tasks.new do |gem|
32
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
33
+ gem.name = "dbexpect"
34
+ gem.homepage = "http://github.com/bfabry/dbexpect"
35
+ gem.license = "PRIVATE"
36
+ gem.summary = %Q{Generates inserts for test data from DSL}
37
+ gem.description = %Q{As above}
38
+ gem.email = "beau.fabry@c3businesssolutions.com"
39
+ gem.authors = ["Beau Fabry"]
40
+ # dependencies defined in Gemfile
41
+ end
42
+ Jeweler::RubygemsDotOrgTasks.new
43
+
44
+ require 'rspec/core'
45
+ require 'rspec/core/rake_task'
46
+ RSpec::Core::RakeTask.new(:spec) do |spec|
47
+ spec.pattern = FileList['spec/**/*_spec.rb']
48
+ end
49
+
50
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
51
+ spec.pattern = 'spec/**/*_spec.rb'
52
+ spec.rcov = true
53
+ end
54
+
55
+ task :default => :spec
56
+
57
+ require 'rdoc/task'
58
+ Rake::RDocTask.new do |rdoc|
59
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
60
+
61
+ rdoc.rdoc_dir = 'rdoc'
62
+ rdoc.title = "dbexpect #{version}"
63
+ rdoc.rdoc_files.include('README*')
64
+ rdoc.rdoc_files.include('lib/**/*.rb')
65
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.11.0
data/bin/dbexpect ADDED
@@ -0,0 +1,33 @@
1
+ # Copyright 2012 C3 Business Solutions
2
+ #
3
+ # This file is part of dbexpect.
4
+ #
5
+ # dbexpect is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # dbexpect is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with dbexpect. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ #!/usr/bin/env ruby
19
+
20
+ puts "dbexpect Copyright 2012 C3 Business Solutions"
21
+ puts "This program comes with ABSOLUTELY NO WARRANTY"
22
+
23
+ begin
24
+ require 'dbexpect'
25
+ rescue LoadError
26
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
27
+ require 'dbexpect'
28
+ end
29
+
30
+ db = Database.hash_from_config
31
+ ret = Dbexpect.new().run_test(ARGV.shift,db,CommandRunner.new)
32
+
33
+ exit(ret)
@@ -0,0 +1,28 @@
1
+ # Copyright 2012 C3 Business Solutions
2
+ #
3
+ # This file is part of dbexpect.
4
+ #
5
+ # dbexpect is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # dbexpect is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with dbexpect. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ #!/usr/bin/env ruby
19
+
20
+ begin
21
+ require 'dbexpect'
22
+ rescue LoadError
23
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
24
+ require 'dbexpect'
25
+ end
26
+
27
+ target_db = Database.from_dsn(ARGV.shift)
28
+ exit(Dbexpect.new().great_expectations(ARGV.shift,target_db))
data/bin/ready-rig ADDED
@@ -0,0 +1,27 @@
1
+ # Copyright 2012 C3 Business Solutions
2
+ #
3
+ # This file is part of dbexpect.
4
+ #
5
+ # dbexpect is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # dbexpect is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with dbexpect. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ #!/usr/bin/env ruby
19
+
20
+ begin
21
+ require 'dbexpect'
22
+ rescue LoadError
23
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
24
+ require 'dbexpect'
25
+ end
26
+ db = Database.from_dsn(ARGV.shift)
27
+ exit(Dbexpect.new().setup_test(ARGV.shift,db))
data/database.yml ADDED
@@ -0,0 +1,9 @@
1
+ dbexpect_src:
2
+ database: testgen_src
3
+ username: testgen
4
+ password: testgen
5
+
6
+ dbexpect_tgt:
7
+ database: testgen_tgt
8
+ username: testgen
9
+ password: testgen
data/lib/dbexpect.rb ADDED
@@ -0,0 +1,19 @@
1
+ # Copyright 2012 C3 Business Solutions
2
+ #
3
+ # This file is part of dbexpect.
4
+ #
5
+ # dbexpect is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # dbexpect is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with dbexpect. If not, see <http://www.gnu.org/licenses/>.
17
+ require_relative 'dbexpect/dbexpect'
18
+ require_relative 'dbexpect/database'
19
+ require_relative 'dbexpect/command_runner'
@@ -0,0 +1,21 @@
1
+ # Copyright 2012 C3 Business Solutions
2
+ #
3
+ # This file is part of dbexpect.
4
+ #
5
+ # dbexpect is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # dbexpect is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with dbexpect. If not, see <http://www.gnu.org/licenses/>.
17
+ class CommandRunner
18
+ def run(command)
19
+ system(command)
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ # Copyright 2012 C3 Business Solutions
2
+ #
3
+ # This file is part of dbexpect.
4
+ #
5
+ # dbexpect is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # dbexpect is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with dbexpect. If not, see <http://www.gnu.org/licenses/>.
17
+ class ConsoleFormatter
18
+ def initialize(output)
19
+ @output = output
20
+ end
21
+
22
+ def notify_passed
23
+ @output.puts "Passed all expectations\n"
24
+ end
25
+
26
+ def notify_failed(failed_expectations)
27
+ failed_expectations.traverse do |depth, description, expectations|
28
+ @output.puts((' ' * depth) + description + ":\n")
29
+
30
+ expectations.collect(&:failure_message).each do |msg|
31
+ @output.puts((' ' * depth) + ' ' + msg + "\n")
32
+ end
33
+ end
34
+ @output.puts "Failed to meet expectations\n"
35
+ end
36
+
37
+ def format_sql(sql)
38
+ @output.puts sql
39
+ end
40
+ end
@@ -0,0 +1,124 @@
1
+ # Copyright 2012 C3 Business Solutions
2
+ #
3
+ # This file is part of dbexpect.
4
+ #
5
+ # dbexpect is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # dbexpect is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with dbexpect. If not, see <http://www.gnu.org/licenses/>.
17
+ require_relative 'expectation_tree_node'
18
+ require 'set'
19
+
20
+ class DSLParser
21
+
22
+ attr_accessor :commands
23
+
24
+ def initialize
25
+ @tables = {}
26
+ @tree_nodes = [ExpectationTreeNode.new('->')]
27
+ @files_loaded = Set.new
28
+ @commands = []
29
+ end
30
+
31
+ def expectation_tree
32
+ @tree_nodes.first
33
+ end
34
+
35
+ def parse(script)
36
+ instance_eval(script)
37
+ end
38
+
39
+ def tables
40
+ @tables.values
41
+ end
42
+
43
+ protected
44
+ def requires(file)
45
+ unless @files_loaded.include?(file)
46
+ instance_eval(File.read(file))
47
+ end
48
+ @files_loaded << file
49
+ end
50
+
51
+ def etl_run_command(command)
52
+ @commands << command
53
+ end
54
+
55
+ def dirty(table)
56
+ table.dirty = true
57
+ end
58
+
59
+ def expect_total_rows(table, count)
60
+ @tree_nodes.last.add([table.row_count_check(count)])
61
+ end
62
+
63
+ def describe(description,&block)
64
+ new_node = @tree_nodes.last.create_child(description)
65
+ @tree_nodes << new_node
66
+ instance_eval(&block)
67
+ @tree_nodes.pop
68
+ end
69
+
70
+ def defaults_for(table, columns)
71
+ __set_defaults(table,:set_default,columns)
72
+ end
73
+
74
+ def expected_defaults(table, columns)
75
+ __set_defaults(table,:set_expected_default,columns)
76
+ end
77
+
78
+ def __set_defaults(table, method, columns)
79
+ columns.each do |col, value|
80
+ table.send(method, col, wrap(value))
81
+ end
82
+ end
83
+
84
+ def insert_into(table,row_columns,rows)
85
+ __add_rows(table, :add_fixture_row, row_columns, rows)
86
+ end
87
+
88
+ def expect_rows(table, row_columns, rows)
89
+ @tree_nodes.last.add __add_rows(table, :add_expected_row, row_columns, rows)
90
+ end
91
+
92
+ def __add_rows(table, row_method, row_columns, rows)
93
+ rows.collect do |row_values|
94
+ wrapped = row_values.map {|v| wrap(v) }
95
+ table.send(row_method,Hash[ row_columns.zip(wrapped)])
96
+ end
97
+ end
98
+
99
+ def all_tables(col_values)
100
+ col_values.each do |col,value|
101
+ @tables.each {|x,t| t.set_default(col, wrap(value)) }
102
+ end
103
+ end
104
+
105
+ def table(db_name,schema,tablename)
106
+ @tables[db_name.to_s + schema.to_s + tablename.to_s] ||= Table.new(db_name,schema,tablename)
107
+ end
108
+
109
+ NULL = DbNull.new
110
+ def null; DbNull.new; end
111
+
112
+ def wrap(val)
113
+ case val
114
+ when DbSequence
115
+ val
116
+ when DbNull
117
+ val
118
+ when nil
119
+ NULL
120
+ else
121
+ DbString.new(val)
122
+ end
123
+ end
124
+ end