lhm-shopify 3.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +34 -0
  3. data/.gitignore +17 -0
  4. data/.rubocop.yml +183 -0
  5. data/.travis.yml +21 -0
  6. data/CHANGELOG.md +216 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE +27 -0
  9. data/README.md +284 -0
  10. data/Rakefile +22 -0
  11. data/bin/.gitkeep +0 -0
  12. data/dbdeployer/config.json +32 -0
  13. data/dbdeployer/install.sh +64 -0
  14. data/dev.yml +20 -0
  15. data/gemfiles/ar-2.3_mysql.gemfile +6 -0
  16. data/gemfiles/ar-3.2_mysql.gemfile +5 -0
  17. data/gemfiles/ar-3.2_mysql2.gemfile +5 -0
  18. data/gemfiles/ar-4.0_mysql2.gemfile +5 -0
  19. data/gemfiles/ar-4.1_mysql2.gemfile +5 -0
  20. data/gemfiles/ar-4.2_mysql2.gemfile +5 -0
  21. data/gemfiles/ar-5.0_mysql2.gemfile +5 -0
  22. data/lhm.gemspec +34 -0
  23. data/lib/lhm.rb +131 -0
  24. data/lib/lhm/atomic_switcher.rb +52 -0
  25. data/lib/lhm/chunk_finder.rb +32 -0
  26. data/lib/lhm/chunk_insert.rb +51 -0
  27. data/lib/lhm/chunker.rb +87 -0
  28. data/lib/lhm/cleanup/current.rb +74 -0
  29. data/lib/lhm/command.rb +48 -0
  30. data/lib/lhm/entangler.rb +117 -0
  31. data/lib/lhm/intersection.rb +51 -0
  32. data/lib/lhm/invoker.rb +98 -0
  33. data/lib/lhm/locked_switcher.rb +74 -0
  34. data/lib/lhm/migration.rb +43 -0
  35. data/lib/lhm/migrator.rb +237 -0
  36. data/lib/lhm/printer.rb +59 -0
  37. data/lib/lhm/railtie.rb +9 -0
  38. data/lib/lhm/sql_helper.rb +77 -0
  39. data/lib/lhm/sql_retry.rb +61 -0
  40. data/lib/lhm/table.rb +121 -0
  41. data/lib/lhm/table_name.rb +23 -0
  42. data/lib/lhm/test_support.rb +35 -0
  43. data/lib/lhm/throttler.rb +36 -0
  44. data/lib/lhm/throttler/slave_lag.rb +145 -0
  45. data/lib/lhm/throttler/threads_running.rb +53 -0
  46. data/lib/lhm/throttler/time.rb +29 -0
  47. data/lib/lhm/timestamp.rb +11 -0
  48. data/lib/lhm/version.rb +6 -0
  49. data/shipit.rubygems.yml +0 -0
  50. data/spec/.lhm.example +4 -0
  51. data/spec/README.md +58 -0
  52. data/spec/fixtures/bigint_table.ddl +4 -0
  53. data/spec/fixtures/composite_primary_key.ddl +7 -0
  54. data/spec/fixtures/custom_primary_key.ddl +6 -0
  55. data/spec/fixtures/destination.ddl +6 -0
  56. data/spec/fixtures/lines.ddl +7 -0
  57. data/spec/fixtures/origin.ddl +6 -0
  58. data/spec/fixtures/permissions.ddl +5 -0
  59. data/spec/fixtures/small_table.ddl +4 -0
  60. data/spec/fixtures/tracks.ddl +5 -0
  61. data/spec/fixtures/users.ddl +14 -0
  62. data/spec/fixtures/wo_id_int_column.ddl +6 -0
  63. data/spec/integration/atomic_switcher_spec.rb +93 -0
  64. data/spec/integration/chunk_insert_spec.rb +29 -0
  65. data/spec/integration/chunker_spec.rb +185 -0
  66. data/spec/integration/cleanup_spec.rb +136 -0
  67. data/spec/integration/entangler_spec.rb +66 -0
  68. data/spec/integration/integration_helper.rb +237 -0
  69. data/spec/integration/invoker_spec.rb +33 -0
  70. data/spec/integration/lhm_spec.rb +585 -0
  71. data/spec/integration/lock_wait_timeout_spec.rb +30 -0
  72. data/spec/integration/locked_switcher_spec.rb +50 -0
  73. data/spec/integration/sql_retry/lock_wait_spec.rb +125 -0
  74. data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +101 -0
  75. data/spec/integration/table_spec.rb +91 -0
  76. data/spec/test_helper.rb +32 -0
  77. data/spec/unit/atomic_switcher_spec.rb +31 -0
  78. data/spec/unit/chunk_finder_spec.rb +73 -0
  79. data/spec/unit/chunk_insert_spec.rb +44 -0
  80. data/spec/unit/chunker_spec.rb +166 -0
  81. data/spec/unit/entangler_spec.rb +124 -0
  82. data/spec/unit/intersection_spec.rb +51 -0
  83. data/spec/unit/lhm_spec.rb +29 -0
  84. data/spec/unit/locked_switcher_spec.rb +51 -0
  85. data/spec/unit/migrator_spec.rb +146 -0
  86. data/spec/unit/printer_spec.rb +97 -0
  87. data/spec/unit/sql_helper_spec.rb +32 -0
  88. data/spec/unit/table_name_spec.rb +39 -0
  89. data/spec/unit/table_spec.rb +47 -0
  90. data/spec/unit/throttler/slave_lag_spec.rb +317 -0
  91. data/spec/unit/throttler/threads_running_spec.rb +64 -0
  92. data/spec/unit/throttler_spec.rb +124 -0
  93. data/spec/unit/unit_helper.rb +13 -0
  94. metadata +239 -0
@@ -0,0 +1,11 @@
1
+ module Lhm
2
+ class Timestamp
3
+ def initialize(time)
4
+ @time = time
5
+ end
6
+
7
+ def to_s
8
+ @time.strftime "%Y_%m_%d_%H_%M_%S_#{ '%03d' % (@time.usec / 1000) }"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ # Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
2
+ # Schmidt
3
+
4
+ module Lhm
5
+ VERSION = '3.3.5'
6
+ end
File without changes
data/spec/.lhm.example ADDED
@@ -0,0 +1,4 @@
1
+ mysqldir=/usr/local/mysql
2
+ basedir=~/lhm-cluster
3
+ master_port=3306
4
+ slave_port=3307
data/spec/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # Preparing for master slave integration tests
2
+
3
+ ## Configuration
4
+
5
+ create ~/.lhm:
6
+
7
+ mysqldir=/usr/local/mysql
8
+ basedir=~/lhm-cluster
9
+ master_port=3306
10
+ slave_port=3307
11
+
12
+ mysqldir specifies the location of your mysql install. basedir is the
13
+ directory master and slave databases will get installed into.
14
+
15
+ ## Automatic setup
16
+
17
+ ### Run
18
+
19
+ bin/lhm-spec-clobber.sh
20
+
21
+ You can set the integration specs up to run against a master slave setup by
22
+ running the included that. This deletes the configured lhm master slave setup and reinstalls and configures a master slave setup.
23
+
24
+ Follow the manual instructions if you want more control over this process.
25
+
26
+ ## Manual setup
27
+
28
+ ### set up instances
29
+
30
+ bin/lhm-spec-setup-cluster.sh
31
+
32
+ ### start instances
33
+
34
+ basedir=/opt/lhm-luster
35
+ mysqld --defaults-file="$basedir/master/my.cnf"
36
+ mysqld --defaults-file="$basedir/slave/my.cnf"
37
+
38
+ ### run the grants
39
+
40
+ bin/lhm-spec-grants.sh
41
+
42
+ ## run specs
43
+
44
+ Setup the dependency gems
45
+
46
+ export BUNDLE_GEMFILE=gemfiles/ar-4.2_mysql2.gemfile
47
+ bundle install
48
+
49
+ To run specs in slave mode, set the MASTER_SLAVE=1 when running tests:
50
+
51
+ MASTER_SLAVE=1 bundle exec rake specs
52
+
53
+ # connecting
54
+
55
+ you can connect by running (with the respective ports):
56
+
57
+ mysql --protocol=TCP -p3307
58
+
@@ -0,0 +1,4 @@
1
+ CREATE TABLE `bigint_table` (
2
+ `id` BIGINT(20) auto_increment,
3
+ PRIMARY KEY (`id`)
4
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -0,0 +1,7 @@
1
+
2
+ CREATE TABLE `composite_primary_key` (
3
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
4
+ `shop_id` bigint(20) NOT NULL,
5
+ PRIMARY KEY (`shop_id`,`id`),
6
+ INDEX `index_key_id` (`id`)
7
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -0,0 +1,6 @@
1
+ CREATE TABLE `custom_primary_key` (
2
+ `id` int(11) NOT NULL AUTO_INCREMENT,
3
+ `pk` varchar(255),
4
+ PRIMARY KEY (`pk`),
5
+ UNIQUE KEY `index_custom_primary_key_on_id` (`id`)
6
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -0,0 +1,6 @@
1
+ CREATE TABLE `destination` (
2
+ `id` int(11) NOT NULL AUTO_INCREMENT,
3
+ `destination` int(11) DEFAULT NULL,
4
+ `common` varchar(255) DEFAULT NULL,
5
+ PRIMARY KEY (`id`)
6
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -0,0 +1,7 @@
1
+ CREATE TABLE `lines` (
2
+ `id` int(11) NOT NULL AUTO_INCREMENT,
3
+ `between` varchar(10),
4
+ `lines` int(11),
5
+ `key` varchar(10),
6
+ PRIMARY KEY (`id`)
7
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -0,0 +1,6 @@
1
+ CREATE TABLE `origin` (
2
+ `id` int(11) NOT NULL AUTO_INCREMENT,
3
+ `origin` int(11) DEFAULT NULL,
4
+ `common` varchar(255) DEFAULT NULL,
5
+ PRIMARY KEY (`id`)
6
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -0,0 +1,5 @@
1
+ CREATE TABLE `permissions` (
2
+ `id` int(11) NOT NULL AUTO_INCREMENT,
3
+ `track_id` int(11) DEFAULT NULL,
4
+ PRIMARY KEY (`id`)
5
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -0,0 +1,4 @@
1
+ CREATE TABLE `small_table` (
2
+ `id` INT(11) auto_increment,
3
+ PRIMARY KEY (`id`)
4
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -0,0 +1,5 @@
1
+ CREATE TABLE `tracks` (
2
+ `id` int(11) NOT NULL AUTO_INCREMENT,
3
+ `public` int(4) DEFAULT 0,
4
+ PRIMARY KEY (`id`)
5
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -0,0 +1,14 @@
1
+ CREATE TABLE `users` (
2
+ `id` int(11) NOT NULL AUTO_INCREMENT,
3
+ `reference` int(11) DEFAULT NULL,
4
+ `username` varchar(255) DEFAULT NULL,
5
+ `group` varchar(255) DEFAULT 'Superfriends',
6
+ `created_at` datetime DEFAULT NULL,
7
+ `comment` varchar(20) DEFAULT NULL,
8
+ `description` text,
9
+ PRIMARY KEY (`id`),
10
+ UNIQUE KEY `index_users_on_reference` (`reference`),
11
+ KEY `index_users_on_username_and_created_at` (`username`,`created_at`),
12
+ KEY `index_with_a_custom_name` (`username`,`group`)
13
+
14
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -0,0 +1,6 @@
1
+ -- Without id int column
2
+ CREATE TABLE `wo_id_int_column` (
3
+ `id` varchar(15) NOT NULL,
4
+ PRIMARY KEY (`id`)
5
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
6
+
@@ -0,0 +1,93 @@
1
+ # Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
2
+ # Schmidt
3
+
4
+ require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
5
+
6
+ require 'lhm/table'
7
+ require 'lhm/migration'
8
+ require 'lhm/atomic_switcher'
9
+
10
+ describe Lhm::AtomicSwitcher do
11
+ include IntegrationHelper
12
+
13
+ before(:each) { connect_master! }
14
+
15
+ describe 'switching' do
16
+ before(:each) do
17
+ Thread.abort_on_exception = true
18
+ @origin = table_create('origin')
19
+ @destination = table_create('destination')
20
+ @migration = Lhm::Migration.new(@origin, @destination)
21
+ @logs = StringIO.new
22
+ Lhm.logger = Logger.new(@logs)
23
+ @connection.execute('SET GLOBAL innodb_lock_wait_timeout=3')
24
+ @connection.execute('SET GLOBAL lock_wait_timeout=3')
25
+ end
26
+
27
+ after(:each) do
28
+ Thread.abort_on_exception = false
29
+ end
30
+
31
+ it 'should retry and log on lock wait timeouts' do
32
+ connection = mock()
33
+ connection.stubs(:data_source_exists?).returns(true)
34
+ connection.stubs(:execute).raises(ActiveRecord::StatementInvalid, 'Lock wait timeout exceeded; try restarting transaction.').then.returns(true)
35
+
36
+ switcher = Lhm::AtomicSwitcher.new(@migration, connection, retriable: {base_interval: 0})
37
+
38
+ assert switcher.run
39
+
40
+ log_messages = @logs.string.split("\n")
41
+ assert_equal(2, log_messages.length)
42
+ assert log_messages[0].include? "Starting run of class=Lhm::AtomicSwitcher"
43
+ assert log_messages[1].include? "[AtomicSwitcher] ActiveRecord::StatementInvalid: 'Lock wait timeout exceeded; try restarting transaction.' - 1 tries"
44
+ end
45
+
46
+ it 'should give up on lock wait timeouts after a configured number of tries' do
47
+ connection = mock()
48
+ connection.stubs(:data_source_exists?).returns(true)
49
+ connection.stubs(:execute).twice.raises(ActiveRecord::StatementInvalid, 'Lock wait timeout exceeded; try restarting transaction.')
50
+
51
+ switcher = Lhm::AtomicSwitcher.new(@migration, connection, retriable: {tries: 2, base_interval: 0})
52
+
53
+ assert_raises(ActiveRecord::StatementInvalid) { switcher.run }
54
+ end
55
+
56
+ it 'should raise on non lock wait timeout exceptions' do
57
+ switcher = Lhm::AtomicSwitcher.new(@migration, connection)
58
+ switcher.send :define_singleton_method, :atomic_switch do
59
+ 'SELECT * FROM nonexistent'
60
+ end
61
+ -> { switcher.run }.must_raise(ActiveRecord::StatementInvalid)
62
+ end
63
+
64
+ it "should raise when destination doesn't exist" do
65
+ connection = mock()
66
+ connection.stubs(:data_source_exists?).returns(false)
67
+
68
+ switcher = Lhm::AtomicSwitcher.new(@migration, connection)
69
+
70
+ assert_raises(Lhm::Error) { switcher.run }
71
+ end
72
+
73
+ it 'rename origin to archive' do
74
+ switcher = Lhm::AtomicSwitcher.new(@migration, connection)
75
+ switcher.run
76
+
77
+ slave do
78
+ data_source_exists?(@origin).must_equal true
79
+ table_read(@migration.archive_name).columns.keys.must_include 'origin'
80
+ end
81
+ end
82
+
83
+ it 'rename destination to origin' do
84
+ switcher = Lhm::AtomicSwitcher.new(@migration, connection)
85
+ switcher.run
86
+
87
+ slave do
88
+ data_source_exists?(@destination).must_equal false
89
+ table_read(@origin.name).columns.keys.must_include 'destination'
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,29 @@
1
+ require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
2
+ require 'lhm/migration'
3
+
4
+ describe Lhm::ChunkInsert do
5
+ include IntegrationHelper
6
+
7
+ describe 'insert_and_return_count_of_rows_created' do
8
+ before(:each) do
9
+ connect_master!
10
+ @origin = table_create(:origin)
11
+ @destination = table_create(:destination)
12
+ @migration = Lhm::Migration.new(@origin, @destination)
13
+ execute("insert into origin set id = 1001")
14
+ @instance = Lhm::ChunkInsert.new(@migration, connection, 1001, 1001)
15
+ end
16
+
17
+ it "returns the count" do
18
+ assert_equal 1, @instance.insert_and_return_count_of_rows_created
19
+ end
20
+
21
+ it "inserts the record into the slave" do
22
+ @instance.insert_and_return_count_of_rows_created
23
+
24
+ slave do
25
+ count_all(@destination.name).must_equal(1)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,185 @@
1
+ # Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
2
+ # Schmidt
3
+
4
+ require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
5
+ require 'lhm/table'
6
+ require 'lhm/migration'
7
+
8
+ describe Lhm::Chunker do
9
+ include IntegrationHelper
10
+
11
+ before(:each) { connect_master! }
12
+
13
+ describe 'copying' do
14
+ before(:each) do
15
+ @origin = table_create(:origin)
16
+ @destination = table_create(:destination)
17
+ @migration = Lhm::Migration.new(@origin, @destination)
18
+ end
19
+
20
+ it 'should copy 1 row from origin to destination even if the id of the single row does not start at 1' do
21
+ execute("insert into origin set id = 1001 ")
22
+ printer = Lhm::Printer::Base.new
23
+
24
+ def printer.notify(*) ;end
25
+ def printer.end(*) [] ;end
26
+
27
+ Lhm::Chunker.new(@migration, connection, {:throttler => Lhm::Throttler::Time.new(:stride => 100), :printer => printer} ).run
28
+
29
+ slave do
30
+ count_all(@destination.name).must_equal(1)
31
+ end
32
+
33
+ end
34
+
35
+ it 'should create the modified destination, even if the source is empty' do
36
+ execute("truncate origin ")
37
+ printer = Lhm::Printer::Base.new
38
+
39
+ def printer.notify(*) ;end
40
+ def printer.end(*) [] ;end
41
+
42
+ Lhm::Chunker.new(@migration, connection, {:throttler => Lhm::Throttler::Time.new(:stride => 100), :printer => printer} ).run
43
+
44
+ slave do
45
+ count_all(@destination.name).must_equal(0)
46
+ end
47
+
48
+ end
49
+
50
+ it 'should copy 23 rows from origin to destination in one shot, regardless of the value of the id' do
51
+ 23.times { |n| execute("insert into origin set id = '#{ n * n + 23 }'") }
52
+
53
+ printer = MiniTest::Mock.new
54
+ printer.expect(:notify, :return_value, [Integer, Integer])
55
+ printer.expect(:end, :return_value, [])
56
+
57
+ Lhm::Chunker.new(
58
+ @migration, connection, { :throttler => Lhm::Throttler::Time.new(:stride => 100), :printer => printer }
59
+ ).run
60
+
61
+ slave do
62
+ count_all(@destination.name).must_equal(23)
63
+ end
64
+
65
+ printer.verify
66
+
67
+ end
68
+
69
+ it 'should copy all the records of a table, even if the last chunk starts with the last record of it.' do
70
+ 11.times { |n| execute("insert into origin set id = '#{ n + 1 }'") }
71
+
72
+ printer = Lhm::Printer::Base.new
73
+
74
+ def printer.notify(*) ;end
75
+ def printer.end(*) [] ;end
76
+
77
+ Lhm::Chunker.new(
78
+ @migration, connection, { :throttler => Lhm::Throttler::Time.new(:stride => 10), :printer => printer }
79
+ ).run
80
+
81
+ slave do
82
+ count_all(@destination.name).must_equal(11)
83
+ end
84
+
85
+ end
86
+
87
+ it 'should copy 23 rows from origin to destination in one shot with slave lag based throttler, regardless of the value of the id' do
88
+ 23.times { |n| execute("insert into origin set id = '#{ 100000 + n * n + 23 }'") }
89
+
90
+ printer = MiniTest::Mock.new
91
+ printer.expect(:notify, :return_value, [Integer, Integer])
92
+ printer.expect(:end, :return_value, [])
93
+
94
+ Lhm::Chunker.new(
95
+ @migration, connection, { :throttler => Lhm::Throttler::SlaveLag.new(:stride => 100), :printer => printer }
96
+ ).run
97
+
98
+ slave do
99
+ count_all(@destination.name).must_equal(23)
100
+ end
101
+
102
+ printer.verify
103
+ end
104
+
105
+ it 'should throttle work stride based on slave lag' do
106
+ 15.times { |n| execute("insert into origin set id = '#{ (n * n) + 1 }'") }
107
+
108
+ printer = mock()
109
+ printer.expects(:notify).with(instance_of(Integer), instance_of(Integer)).twice
110
+ printer.expects(:end)
111
+
112
+ throttler = Lhm::Throttler::SlaveLag.new(:stride => 10, :allowed_lag => 0)
113
+ def throttler.max_current_slave_lag
114
+ 1
115
+ end
116
+
117
+ Lhm::Chunker.new(
118
+ @migration, connection, { :throttler => throttler, :printer => printer }
119
+ ).run
120
+
121
+ assert_equal(Lhm::Throttler::SlaveLag::INITIAL_TIMEOUT * 2 * 2, throttler.timeout_seconds)
122
+
123
+ slave do
124
+ count_all(@destination.name).must_equal(15)
125
+ end
126
+ end
127
+
128
+ it 'should detect a single slave with no lag in the default configuration' do
129
+ 15.times { |n| execute("insert into origin set id = '#{ (n * n) + 1 }'") }
130
+
131
+ printer = mock()
132
+ printer.expects(:notify).with(instance_of(Integer), instance_of(Integer)).twice
133
+ printer.expects(:verify)
134
+ printer.expects(:end)
135
+
136
+ throttler = Lhm::Throttler::SlaveLag.new(:stride => 10, :allowed_lag => 0)
137
+
138
+ def throttler.slave_hosts
139
+ ['127.0.0.1']
140
+ end
141
+
142
+ if master_slave_mode?
143
+ def throttler.slave_connection(slave)
144
+ config = ActiveRecord::Base.connection_pool.spec.config.dup
145
+ config[:host] = slave
146
+ config[:port] = 3307
147
+ ActiveRecord::Base.send('mysql2_connection', config)
148
+ end
149
+ end
150
+
151
+ Lhm::Chunker.new(
152
+ @migration, connection, { :throttler => throttler, :printer => printer }
153
+ ).run
154
+
155
+ assert_equal(Lhm::Throttler::SlaveLag::INITIAL_TIMEOUT, throttler.timeout_seconds)
156
+ assert_equal(0, throttler.send(:max_current_slave_lag))
157
+
158
+ slave do
159
+ count_all(@destination.name).must_equal(15)
160
+ end
161
+
162
+ printer.verify
163
+ end
164
+
165
+ it 'should abort early if the triggers are removed' do
166
+ 15.times { |n| execute("insert into origin set id = '#{ (n * n) + 1 }'") }
167
+
168
+ printer = mock()
169
+
170
+ failer = Proc.new { false }
171
+
172
+ exception = assert_raises do
173
+ Lhm::Chunker.new(
174
+ @migration, connection, { :verifier => failer, :printer => printer, :throttler => Lhm::Throttler::Time.new(:stride => 100) }
175
+ ).run
176
+ end
177
+
178
+ assert_match "Verification failed, aborting early", exception.message
179
+
180
+ slave do
181
+ count_all(@destination.name).must_equal(0)
182
+ end
183
+ end
184
+ end
185
+ end