fixpoints 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: accfd17194594b7b1f83144d68a15930991a6aa0369fe695bd4ac769d247abbe
4
+ data.tar.gz: 831f6c9c694d96173175b7b1082600947575f54864161fac52cf5dabdd039e7c
5
+ SHA512:
6
+ metadata.gz: 5684b274d7118189c284c483816e6f18059529f08ce92585fd8c94dec3360106a9a77238cc6348d9b25f8daa4f4d9f2de227ef0daffe8fd511b7eb0ceddee05a
7
+ data.tar.gz: 626a9395a2472992c02c18c0e6bf51a5502b29d460023034ef13501221b0312b5e35fd493b7d9661a0f8ddccee13c1755345e5ca4e0521c14b37bb9f3ba1f828
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fixpoints.gemspec
4
+ gemspec
5
+
6
+ gem "rspec"
7
+ gem "activerecord", ">=5.0.0"
8
+ gem "sqlite3"
9
+
10
+ gem "pry"
@@ -0,0 +1,61 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fixpoints (0.1.0)
5
+ activerecord (>= 5.0.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (5.2.4.4)
11
+ activesupport (= 5.2.4.4)
12
+ activerecord (5.2.4.4)
13
+ activemodel (= 5.2.4.4)
14
+ activesupport (= 5.2.4.4)
15
+ arel (>= 9.0)
16
+ activesupport (5.2.4.4)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 0.7, < 2)
19
+ minitest (~> 5.1)
20
+ tzinfo (~> 1.1)
21
+ arel (9.0.0)
22
+ coderay (1.1.3)
23
+ concurrent-ruby (1.1.7)
24
+ diff-lcs (1.4.4)
25
+ i18n (1.8.5)
26
+ concurrent-ruby (~> 1.0)
27
+ method_source (1.0.0)
28
+ minitest (5.14.2)
29
+ pry (0.13.1)
30
+ coderay (~> 1.1)
31
+ method_source (~> 1.0)
32
+ rspec (3.9.0)
33
+ rspec-core (~> 3.9.0)
34
+ rspec-expectations (~> 3.9.0)
35
+ rspec-mocks (~> 3.9.0)
36
+ rspec-core (3.9.2)
37
+ rspec-support (~> 3.9.3)
38
+ rspec-expectations (3.9.2)
39
+ diff-lcs (>= 1.2.0, < 2.0)
40
+ rspec-support (~> 3.9.0)
41
+ rspec-mocks (3.9.1)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.9.0)
44
+ rspec-support (3.9.3)
45
+ sqlite3 (1.4.2)
46
+ thread_safe (0.3.6)
47
+ tzinfo (1.2.7)
48
+ thread_safe (~> 0.1)
49
+
50
+ PLATFORMS
51
+ ruby
52
+
53
+ DEPENDENCIES
54
+ activerecord (>= 5.0.0)
55
+ fixpoints!
56
+ pry
57
+ rspec
58
+ sqlite3
59
+
60
+ BUNDLED WITH
61
+ 2.1.4
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Tom Rothe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,67 @@
1
+ # Fixpoints
2
+
3
+ Fixpoints enables saving, restoring and comparing the database state before & after tests.
4
+
5
+ ## Motivation
6
+
7
+ TODO
8
+
9
+ Link to `https://tomrothe.de/posts/behaviour-driven-test-data.html`
10
+
11
+ ## Usage
12
+
13
+ Add this line to your application's Gemfile: `gem 'fixpoints'`
14
+
15
+ TODO: Write usage instructions here
16
+
17
+
18
+ ```ruby
19
+ # TODO: update this code
20
+ it 'registers a user' do
21
+ visit new_user_path
22
+ fill_in 'Name', with: 'Hans'
23
+ click_on 'Save'
24
+
25
+ store_fixpoint :registred_user
26
+ # creates YAML files containing all records (/spec/fixpoints/[table_name].yml)
27
+ end
28
+
29
+ it 'posts an item' do
30
+ restore_fixpoint :registered_user
31
+
32
+ user = User.find_by(name: 'Hans')
33
+ visit new_item_path(user)
34
+ fill_in 'Item', with: '...'
35
+ click_on 'Post'
36
+
37
+ compare_fixpoint(:posted_item, ignore_columns: [:release_date], store_fixpoint_and_fail: true)
38
+ # compares the database state with the previously saved fixpoint and
39
+ # raises if there is a difference. when there is no previous fixpoint,
40
+ # it writes it and fails the test (so it can be re-run)
41
+ end
42
+ ```
43
+
44
+ ## Development
45
+
46
+ ```bash
47
+ docker run --rm -ti -v (pwd):/app -w /app ruby:2.7 bash
48
+ bundle install
49
+ rspec
50
+ pry # require_relative 'lib/fixpoints.rb'
51
+
52
+ gem build
53
+ gem install fixpoints-0.1.0.gem
54
+ pry -r fixpoints
55
+ gem uninstall fixpoints
56
+ ```
57
+
58
+
59
+ ## Development
60
+
61
+ ## Contributing
62
+
63
+ Bug reports and pull requests are welcome on GitHub at https://github.com/motine/fixpoints.
64
+
65
+ ## License
66
+
67
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,27 @@
1
+ require_relative 'lib/fixpoints/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "fixpoints"
5
+ spec.version = Fixpoints::VERSION
6
+ spec.authors = ["Tom Rothe"]
7
+ spec.email = ["info@tomrothe.de"]
8
+
9
+ spec.summary = "Don't discard the database state at the end of your test. Use it!"
10
+ spec.description = 'Fixpoints enables saving, restoring and comparing the database state before & after tests'
11
+ spec.homepage = "https://github.com/motine/fixpoints"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = spec.homepage
17
+
18
+ spec.add_runtime_dependency "activerecord", ">=5.0.0"
19
+ spec.add_runtime_dependency "rspec"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.require_paths = ["lib"]
27
+ end
@@ -0,0 +1,171 @@
1
+ # A fixpoint is a snapshot of the database contents.
2
+ # It is saved to the +spec/fixpoints+ folder.
3
+ # A fixpoint (file) contains a mapping of table names to a list if their records.
4
+ #
5
+ # Empty tables are stripped from files.
6
+ #
7
+ # Make sure to run the tests in the right order: In a single RSpec file, you can use the order in which the tests are defined (`RSpec.describe 'MyFeature', order: :defined do`).
8
+ # However, tests in groups might follow a slightly different order (see https://relishapp.com/rspec/rspec-core/docs/configuration/overriding-global-ordering)
9
+ #
10
+ # If you did a lot of changes to a test, you can remove a fixpoint file from its directory.
11
+ # It will be recreated when the test producing it runs again.
12
+ # Don't forget re-running the tests _based on_ it because their fixpoints might have to change too.
13
+ # Example: You need to add something to the database's seeds.rb. All subsequent fixpoints are missing the required entry.
14
+ # To update all fixpoints, just remove the whole `spec/fixpoints` folder and re-run all tests. Now all fixpoints should be updated.
15
+ # Be careful though, don't just remove the fixpoints if you are not sure what is going on.
16
+ # A change in a fixpoint might point to an unintended change in code.
17
+ #
18
+ # We need to be be careful to use +let+ and +let!+ with factories.
19
+ # Records might be created twice when using create in there (once by the fixpoint and once by the factory).
20
+ #
21
+ # KNOWN ISSUES
22
+ # Under certain conditions you may get `duplicate key value violates unique constraint` because the primary key sequences are not updated correctly.
23
+ # If this happens, just add a Fixpoint.reset_pk_sequences! at the beginning of your test. We need to dig a little deeper here at some point...
24
+ #
25
+ # LIMITATIONS
26
+ # The records in tables are ordered by their id.
27
+ # If there is no id for a table, we use database's order (what the SELECT query returns).
28
+ # This order may be instable.
29
+ class Fixpoint
30
+ class Error < StandardError; end
31
+
32
+ FIXPOINT_FOLDER = 'fixpoints'
33
+ TABLES_TO_SKIP = %w[ar_internal_metadata delayed_jobs schema_info schema_migrations].freeze
34
+
35
+ class << self
36
+ def exists?(fixname)
37
+ File.exist?(fixpoint_path(fixname))
38
+ end
39
+
40
+ def from_file(fixname)
41
+ raise Fixpoint::Error, "The requested fixpoint (\"#{fixname}\") could not be found. Re-run the test which stores the fixpoint." unless exists?(fixname)
42
+
43
+ file_path = fixpoint_path(fixname)
44
+ new(YAML.load_file(file_path))
45
+ end
46
+
47
+ # Creates a Fixpoint from the database contents. Empty tables are skipped.
48
+ def from_database
49
+ new(read_database_records)
50
+ end
51
+
52
+ def remove(fixname)
53
+ FileUtils.rm_f(fixpoint_path(fixname))
54
+ end
55
+
56
+ # reset primary key sequences for all tables
57
+ # useful when tests sometimes run before the storing the first fixpoint.
58
+ # these test might have incremented the id sequence already, so the ids in the fixpoints chance (which leads to differences).
59
+ def reset_pk_sequences!
60
+ return unless conn.respond_to?(:reset_pk_sequence!)
61
+ conn.tables.each { |table_name| conn.reset_pk_sequence!(table_name) }
62
+ end
63
+
64
+ def fixpoint_path(fixname)
65
+ fspath = self.fixpoints_path
66
+ raise Fixpoint::Error, 'Can not automatically infer the base path for the specs, please set `rspec_config.fixpoints_path` explicitly' if fspath.nil?
67
+ raise Fixpoint::Error, "Please create the fixpoints folder (and maybe create a .gitkeep): #{fspath}" if !File.exist?(fspath)
68
+
69
+ File.join(fspath, "#{fixname}.yml")
70
+ end
71
+
72
+ def conn
73
+ ActiveRecord::Base.connection
74
+ end
75
+
76
+ protected
77
+
78
+ def fixpoints_path
79
+ return RSpec.configuration.fixpoints_path unless RSpec.configuration.fixpoints_path.nil?
80
+ return Rails.root.join(RSpec.configuration.default_path, FIXPOINT_FOLDER) if defined?(Rails)
81
+ # now this is ugly, but necessary. we go up from the current example's path until we find the spec folder...
82
+ return nil if RSpec.current_example.nil?
83
+ spec_path = Pathname.new(RSpec.current_example.file_path).ascend.find { |pn| pn.basename.to_s == RSpec.configuration.default_path }.expand_path
84
+
85
+ File.join(spec_path, FIXPOINT_FOLDER)
86
+ end
87
+
88
+ def read_database_records
89
+ # adapted from: https://yizeng.me/2017/07/16/generate-rails-test-fixtures-yaml-from-database-dump/
90
+ tables = conn.tables
91
+ tables.reject! { |table_name| TABLES_TO_SKIP.include?(table_name) }
92
+
93
+ tables.each_with_object({}) do |table_name, acc|
94
+ result = conn.select_all("SELECT * FROM #{table_name}")
95
+ next if result.count.zero?
96
+
97
+ rows = result.to_a
98
+ rows.sort_by! { |row| row['id'] } if result.columns.include?('id') # let's make the order of items stable
99
+ acc[table_name] = rows
100
+ end
101
+ end
102
+ end
103
+
104
+ attr_reader :records_in_tables # the complete records in the tables
105
+
106
+ def initialize(records_in_tables)
107
+ @records_in_tables = records_in_tables
108
+ end
109
+
110
+ def load_into_database
111
+ # Here some more pointers on implementation details of fixtures:
112
+ # - https://github.com/rails/rails/blob/2998672fc22f0d5e1a79a29ccb60d0d0e627a430/activerecord/lib/active_record/fixtures.rb#L612
113
+ # - http://api.rubyonrails.org/v5.2.4/classes/ActiveRecord/FixtureSet.html#method-c-create_fixtures
114
+ # - https://github.com/rails/rails/blob/67feba0c822d64741d574dfea808c1a2feedbcfc/activerecord/test/cases/fixtures_test.rb
115
+ #
116
+ # Note from the past (useful if we want to get back to using Rails' +create_fixtures+ method)
117
+ # we used to do: ActiveRecord::FixtureSet.create_fixtures(folder_path, filename_without_extension) # this will also clear the table
118
+ # but we abandoned this approach because we want to one file per fixpoint (not one file per table)
119
+ # ActiveRecord::FixtureSet.reset_cache # create_fixtures does use only the table name as cache key. we always invalidate the cache because we may want to read different fixpoints but with the same table names
120
+
121
+ # let's remove all data
122
+ conn.tables.each { |table| conn.select_all("DELETE FROM #{conn.quote_table_name(table)}") }
123
+
124
+ # actually insert
125
+ conn.insert_fixtures_set(@records_in_tables)
126
+ self.class.reset_pk_sequences!
127
+ end
128
+
129
+ def save_to_file(fixname)
130
+ file_path = self.class.fixpoint_path(fixname)
131
+ FileUtils.mkdir_p(File.dirname(file_path))
132
+ File.write(file_path, contents_for_file)
133
+ end
134
+
135
+ def table_names
136
+ @records_in_tables.keys
137
+ end
138
+
139
+ # Returns the records for the given +table_name+ as a list of Hashes.
140
+ # +ignore_columns+ array of columns to remove from each record Hash.
141
+ # Aside from having the form <tt>[:created_at, :updated_at]</tt>,
142
+ # it can contain attributes scoped by a table name <tt>[:created_at, :updated_at, users: [:password_hash]]</tt>
143
+ def records_for_table(table_name, ignore_columns = [])
144
+ strip_columns_from_records(@records_in_tables[table_name], table_name, ignore_columns)
145
+ end
146
+
147
+ protected
148
+
149
+ delegate :conn, to: :class
150
+
151
+ def contents_for_file
152
+ YAML.dump(@records_in_tables)
153
+ end
154
+
155
+ # see #records_for_table
156
+ def strip_columns_from_records(records, table_name, columns)
157
+ return nil if records.nil?
158
+
159
+ if columns.last.is_a?(Hash) # columns has the a table names at the end (e.g. [:created_at, :updated_at, users: [:password_hash]])
160
+ columns = columns.dup
161
+ all_table_scoped = columns.pop.stringify_keys
162
+ table_scoped = all_table_scoped[table_name]
163
+ columns += table_scoped if table_scoped
164
+ end
165
+ columns = columns.collect(&:to_s)
166
+
167
+ records.collect do |attributes|
168
+ attributes.reject { |col, _value| columns.include?(col) }
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,58 @@
1
+ # Helper module which implements diff-ing for fixpoints
2
+ module FixpointDiff
3
+ DELETED_KEY = '++DELETED++'
4
+ IGNORE_ATTRIBUTES = ['updated_at']
5
+
6
+
7
+ def apply_changes(parent_records_in_tables, changes_in_tables)
8
+ tables = (parent_records_in_tables.keys + changes_in_tables.keys).uniq
9
+
10
+ tables.each_with_object({}) do |table, records|
11
+ records[table] = apply_records_changes(parent_records_in_tables[table], changes_in_tables[table])
12
+ end
13
+ end
14
+
15
+ def extract_changes(parent_records_in_tables, records_in_tables)
16
+ tables = (parent_records_in_tables.keys + records_in_tables.keys).uniq
17
+
18
+ tables.each_with_object({}) do |table, changes_in_tables|
19
+ changes_in_tables[table] = extract_records_changes(parent_records_in_tables[table], records_in_tables[table])
20
+ end
21
+ end
22
+
23
+ module_function :apply_changes, :extract_changes
24
+
25
+ protected
26
+
27
+ def apply_records_changes(parent_records, changes)
28
+ return parent_records if changes.blank?
29
+
30
+ parent_records ||= [] # the table was not part of an earlier fixpoint
31
+ changes.zip(parent_records).collect do |change, parent_record| # we can rely on the fact that changes has always more entries than parent_records
32
+ next change if parent_record.nil? # we do have a new record
33
+ next nil if change[DELETED_KEY]
34
+
35
+ parent_record.merge(change)
36
+ end.compact
37
+ end
38
+
39
+ def extract_records_changes(parent_records, records)
40
+ return records if parent_records.blank?
41
+ records ||= []
42
+
43
+ # we pad parent_records with nil values so we can zip them together
44
+ parent_records = parent_records + [nil] * (records.count - parent_records.count) if parent_records.count < records.count
45
+
46
+ parent_records.zip(records).collect do |parent, record|
47
+ next { DELETED_KEY => true } if record.nil?
48
+ next record if parent.nil? # newly added record
49
+
50
+ parent.each_with_object({}) do |(parent_key, parent_value), changes| # we can rely on the fact that both hashes have the same attributes
51
+ record_value = record[parent_key]
52
+ changes[parent_key] = record_value unless record_value == parent_value || IGNORE_ATTRIBUTES.include?(parent_key)
53
+ end
54
+ end
55
+ end
56
+
57
+ module_function :apply_records_changes, :extract_records_changes
58
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "fixpoints/version"
2
+ require_relative "fixpoint_diff"
3
+
4
+ require_relative "fixpoint"
5
+ require_relative "incremental_fixpoint"
6
+
7
+ if defined?(RSpec)
8
+ RSpec.configure { |c| c.add_setting :fixpoints_path }
9
+ end
@@ -0,0 +1,3 @@
1
+ module Fixpoints
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,109 @@
1
+ # Enhances Fixpoint to only save incremental changes.
2
+ #
3
+ # A fixpoint can be saved fully, where all records are saved to the file or one can give a parent fixpoint.
4
+ # When doing so, only the difference (aka. changes) to the parent is saved in the file.
5
+ # Yet, if a record does not change parent an empty hash is saved. This is done, so removals from the database can be tracked.
6
+ #
7
+ # LIMITATIONS
8
+ # Assume you remove a record at the end of a table and then add another one.
9
+ # Then the fixpoint diff will complain that an entry has changed instead of noticing the addition/removal.
10
+ class IncrementalFixpoint < Fixpoint
11
+ PARENT_YAML_KEY = '++parent_fixpoint++'
12
+
13
+ attr_reader :changes_in_tables # only the difference to the parent (in tables)
14
+
15
+ def initialize(changes_in_tables, parent_fixname=nil)
16
+ @parent_fixname = parent_fixname
17
+ @changes_in_tables = changes_in_tables
18
+ if parent_fixname.nil?
19
+ super(changes_in_tables)
20
+ else
21
+ parent = self.class.from_file(parent_fixname)
22
+ super(FixpointDiff.apply_changes(parent.records_in_tables, @changes_in_tables))
23
+ end
24
+ end
25
+
26
+ def self.from_file(fixname)
27
+ raise Fixpoint::Error, "The requested fixpoint (\"#{fixname}\") could not be found. Re-run the test which stores the fixpoint." unless exists?(fixname)
28
+
29
+ file_path = fixpoint_path(fixname)
30
+ changes_in_tables = YAML.load_file(file_path)
31
+ parent_fixname = changes_in_tables.delete(PARENT_YAML_KEY)
32
+ new(changes_in_tables, parent_fixname)
33
+ end
34
+
35
+ # Creates a Fixpoint from the database contents. Empty tables are skipped.
36
+ def self.from_database(parent_fixname=nil)
37
+ return super() if parent_fixname.nil?
38
+
39
+ parent = from_file(parent_fixname)
40
+ changes_in_tables = FixpointDiff.extract_changes(parent.records_in_tables, read_database_records)
41
+ new(changes_in_tables, parent_fixname)
42
+ end
43
+
44
+ protected
45
+
46
+ def contents_for_file
47
+ file_contents = @changes_in_tables.dup
48
+ file_contents[PARENT_YAML_KEY] = @parent_fixname unless @parent_fixname.nil?
49
+ return YAML.dump(file_contents)
50
+ end
51
+ end
52
+
53
+ # Helper methods to be included into RSpec
54
+ module FixpointTestHelpers
55
+ def restore_fixpoint(fixname)
56
+ IncrementalFixpoint.from_file(fixname).load_into_database
57
+ end
58
+
59
+ # Compares the fixpoint with the records in the database.
60
+ # If there is no such fixpoint yet, it will write a new one to the file system.
61
+ # The latter is useful if the fixpoint was deleted to accommodate changes to it (see example in class description).
62
+ #
63
+ # +tables_to_compare+ can either be +:all+ or a list of table names (e.g. ['users', 'posts'])
64
+ # +ignored_columns+ see Fixnum#records_for_table
65
+ # +not_exists_handler+ when given and the fixpoint does not exists, it will be called with the fixname as argument
66
+ #
67
+ # ---
68
+ # If we refactor this to a gem, we should rely on rspec (e.g. use minitest or move comparison logic to Fixpoint class).
69
+ # Anyhow, we keep it like this for now, because the expectations give much nicer output than the minitest assertions.
70
+ def compare_fixpoint(fixname, ignored_columns=[:updated_at, :created_at], tables_to_compare=:all, &not_exists_handler)
71
+ if !IncrementalFixpoint.exists?(fixname)
72
+ not_exists_handler.call(fixname) if not_exists_handler
73
+ return
74
+ end
75
+
76
+ database_fp = IncrementalFixpoint.from_database
77
+ fixpoint_fp = IncrementalFixpoint.from_file(fixname)
78
+
79
+ tables_to_compare = (database_fp.table_names + fixpoint_fp.table_names).uniq if tables_to_compare == :all
80
+ tables_to_compare.each do |table_name|
81
+ db_records = database_fp.records_for_table(table_name, ignored_columns)
82
+ fp_records = fixpoint_fp.records_for_table(table_name, ignored_columns)
83
+
84
+ # if a table is present in a fixpoint, there must be records in it because empty tables are stripped from fixpoints
85
+ expect(db_records).not_to be_empty, "#{table_name} not in database, but in fixpoint"
86
+ expect(fp_records).not_to be_empty, "#{table_name} not in fixpoint, but in database"
87
+ # we assume that the order of records returned by SELECT is stable (so we do not do any sorting)
88
+ expect(db_records).to eq(fp_records), "Database records for table \"#{table_name}\" did not match fixpoint \"#{fixname}\". Consider removing the fixpoint and re-running the test if the change is intended."
89
+ end
90
+ end
91
+
92
+ def store_fixpoint_and_fail(fixname, parent_fixname = nil)
93
+ store_fixpoint(fixname, parent_fixname)
94
+ pending("Fixpoint \"#{fixname}\" did not exist yet. Skipping comparison, but created fixpoint from database")
95
+ fail
96
+ end
97
+
98
+ # it is not a good idea to overwrite the fixpoint each time because timestamps may change (which then shows up in version control).
99
+ # Hence we only provide a method to write to it if it does not exist.
100
+ def store_fixpoint_unless_present(fixname, parent_fixname = nil)
101
+ store_fixpoint(fixname, parent_fixname) unless IncrementalFixpoint.exists?(fixname)
102
+ end
103
+
104
+ # +parent_fixname+ when given, only the (incremental) changes to the parent are saved
105
+ # please see store_fixpoint_unless_present for note on why not to use this method
106
+ def store_fixpoint(fixname, parent_fixname = nil)
107
+ IncrementalFixpoint.from_database(parent_fixname).save_to_file(fixname)
108
+ end
109
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fixpoints
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Rothe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-09-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 5.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 5.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Fixpoints enables saving, restoring and comparing the database state
42
+ before & after tests
43
+ email:
44
+ - info@tomrothe.de
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - ".rspec"
51
+ - Gemfile
52
+ - Gemfile.lock
53
+ - LICENSE.txt
54
+ - README.md
55
+ - fixpoints.gemspec
56
+ - lib/fixpoint.rb
57
+ - lib/fixpoint_diff.rb
58
+ - lib/fixpoints.rb
59
+ - lib/fixpoints/version.rb
60
+ - lib/incremental_fixpoint.rb
61
+ homepage: https://github.com/motine/fixpoints
62
+ licenses:
63
+ - MIT
64
+ metadata:
65
+ homepage_uri: https://github.com/motine/fixpoints
66
+ source_code_uri: https://github.com/motine/fixpoints
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 2.3.0
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubygems_version: 3.1.2
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: Don't discard the database state at the end of your test. Use it!
86
+ test_files: []