lhm-shopify 3.4.0 → 3.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +24 -15
  3. data/.gitignore +1 -6
  4. data/Appraisals +24 -0
  5. data/CHANGELOG.md +30 -0
  6. data/Gemfile.lock +66 -0
  7. data/README.md +55 -4
  8. data/Rakefile +11 -0
  9. data/dev.yml +31 -6
  10. data/docker-compose.yml +58 -0
  11. data/gemfiles/activerecord_5.2.gemfile +9 -0
  12. data/gemfiles/activerecord_5.2.gemfile.lock +65 -0
  13. data/gemfiles/activerecord_6.0.gemfile +7 -0
  14. data/gemfiles/activerecord_6.0.gemfile.lock +67 -0
  15. data/gemfiles/activerecord_6.1.gemfile +7 -0
  16. data/gemfiles/activerecord_6.1.gemfile.lock +66 -0
  17. data/gemfiles/activerecord_7.0.0.alpha2.gemfile +7 -0
  18. data/gemfiles/activerecord_7.0.0.alpha2.gemfile.lock +64 -0
  19. data/lhm.gemspec +7 -3
  20. data/lib/lhm/atomic_switcher.rb +5 -11
  21. data/lib/lhm/chunk_insert.rb +7 -10
  22. data/lib/lhm/chunker.rb +21 -10
  23. data/lib/lhm/cleanup/current.rb +9 -12
  24. data/lib/lhm/connection.rb +108 -0
  25. data/lib/lhm/entangler.rb +8 -13
  26. data/lib/lhm/invoker.rb +6 -4
  27. data/lib/lhm/locked_switcher.rb +2 -0
  28. data/lib/lhm/migrator.rb +2 -0
  29. data/lib/lhm/printer.rb +10 -6
  30. data/lib/lhm/proxysql_helper.rb +10 -0
  31. data/lib/lhm/sql_retry.rb +129 -10
  32. data/lib/lhm/throttler/slave_lag.rb +19 -2
  33. data/lib/lhm/version.rb +1 -1
  34. data/lib/lhm.rb +41 -16
  35. data/scripts/helpers/wait-for-dbs.sh +21 -0
  36. data/scripts/mysql/reader/create_replication.sql +10 -0
  37. data/scripts/mysql/writer/create_test_db.sql +1 -0
  38. data/scripts/mysql/writer/create_users.sql +6 -0
  39. data/scripts/proxysql/proxysql.cnf +117 -0
  40. data/spec/integration/atomic_switcher_spec.rb +53 -17
  41. data/spec/integration/chunk_insert_spec.rb +3 -2
  42. data/spec/integration/chunker_spec.rb +18 -16
  43. data/spec/integration/cleanup_spec.rb +49 -38
  44. data/spec/integration/database.yml +25 -0
  45. data/spec/integration/entangler_spec.rb +7 -5
  46. data/spec/integration/integration_helper.rb +25 -10
  47. data/spec/integration/lhm_spec.rb +114 -40
  48. data/spec/integration/lock_wait_timeout_spec.rb +2 -2
  49. data/spec/integration/locked_switcher_spec.rb +4 -4
  50. data/spec/integration/proxysql_spec.rb +34 -0
  51. data/spec/integration/sql_retry/db_connection_helper.rb +52 -0
  52. data/spec/integration/sql_retry/lock_wait_spec.rb +8 -6
  53. data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +17 -4
  54. data/spec/integration/sql_retry/proxysql_helper.rb +22 -0
  55. data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +109 -0
  56. data/spec/integration/table_spec.rb +11 -19
  57. data/spec/integration/toxiproxy_helper.rb +40 -0
  58. data/spec/test_helper.rb +24 -0
  59. data/spec/unit/atomic_switcher_spec.rb +4 -6
  60. data/spec/unit/chunk_insert_spec.rb +7 -2
  61. data/spec/unit/chunker_spec.rb +47 -42
  62. data/spec/unit/connection_spec.rb +111 -0
  63. data/spec/unit/entangler_spec.rb +85 -22
  64. data/spec/unit/intersection_spec.rb +4 -4
  65. data/spec/unit/lhm_spec.rb +23 -6
  66. data/spec/unit/locked_switcher_spec.rb +13 -18
  67. data/spec/unit/migrator_spec.rb +17 -19
  68. data/spec/unit/printer_spec.rb +14 -26
  69. data/spec/unit/sql_helper_spec.rb +8 -12
  70. data/spec/unit/table_spec.rb +5 -5
  71. data/spec/unit/throttler/slave_lag_spec.rb +14 -9
  72. data/spec/unit/throttler_spec.rb +12 -12
  73. data/spec/unit/unit_helper.rb +13 -0
  74. metadata +85 -14
  75. data/bin/.gitkeep +0 -0
  76. data/dbdeployer/config.json +0 -32
  77. data/dbdeployer/install.sh +0 -64
  78. data/gemfiles/ar-2.3_mysql.gemfile +0 -6
  79. data/gemfiles/ar-3.2_mysql.gemfile +0 -5
  80. data/gemfiles/ar-3.2_mysql2.gemfile +0 -5
  81. data/gemfiles/ar-4.0_mysql2.gemfile +0 -5
  82. data/gemfiles/ar-4.1_mysql2.gemfile +0 -5
  83. data/gemfiles/ar-4.2_mysql2.gemfile +0 -5
  84. data/gemfiles/ar-5.0_mysql2.gemfile +0 -5
@@ -6,6 +6,7 @@ require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
6
6
  require 'lhm/table'
7
7
  require 'lhm/migration'
8
8
  require 'lhm/entangler'
9
+ require 'lhm/connection'
9
10
 
10
11
  describe Lhm::Entangler do
11
12
  include UnitHelper
@@ -34,7 +35,7 @@ describe Lhm::Entangler do
34
35
  values (`NEW`.`info`, `NEW`.`tags`)
35
36
  }
36
37
 
37
- @entangler.entangle.must_include strip(ddl)
38
+ value(@entangler.entangle).must_include strip(ddl)
38
39
  end
39
40
 
40
41
  it 'should create an update trigger to the destination table' do
@@ -45,7 +46,7 @@ describe Lhm::Entangler do
45
46
  values (`NEW`.`info`, `NEW`.`tags`)
46
47
  }
47
48
 
48
- @entangler.entangle.must_include strip(ddl)
49
+ value(@entangler.entangle).must_include strip(ddl)
49
50
  end
50
51
 
51
52
  it 'should create a delete trigger to the destination table' do
@@ -56,38 +57,100 @@ describe Lhm::Entangler do
56
57
  where `destination`.`id` = OLD.`id`
57
58
  }
58
59
 
59
- @entangler.entangle.must_include strip(ddl)
60
+ value(@entangler.entangle).must_include strip(ddl)
60
61
  end
61
62
 
62
63
  it 'should retry trigger creation when it hits a lock wait timeout' do
63
- connection = mock()
64
64
  tries = 1
65
- @entangler = Lhm::Entangler.new(@migration, connection, retriable: {base_interval: 0, tries: tries})
66
- connection.expects(:execute).times(tries).raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
65
+ ar_connection = mock()
66
+ ar_connection.stubs(:execute)
67
+ .returns([["dummy"]], [["dummy"]], [["dummy"]])
68
+ .then
69
+ .raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
70
+ ar_connection.stubs(:active?).returns(true)
71
+
72
+ connection = Lhm::Connection.new(connection: ar_connection, options: {
73
+ reconnect_with_consistent_host: true,
74
+ retriable: {
75
+ base_interval: 0,
76
+ tries: tries
77
+ }
78
+ })
79
+
80
+ @entangler = Lhm::Entangler.new(@migration, connection)
67
81
 
68
82
  assert_raises(Mysql2::Error) { @entangler.before }
69
83
  end
70
84
 
71
85
  it 'should not retry trigger creation with other mysql errors' do
72
- connection = mock()
73
- connection.expects(:execute).once.raises(Mysql2::Error, 'The MySQL server is running with the --read-only option so it cannot execute this statement.')
74
-
75
- @entangler = Lhm::Entangler.new(@migration, connection, retriable: {base_interval: 0})
86
+ ar_connection = mock()
87
+ ar_connection.stubs(:execute)
88
+ .returns([["dummy"]], [["dummy"]], [["dummy"]])
89
+ .then
90
+ .raises(Mysql2::Error, 'The MySQL server is running with the --read-only option so it cannot execute this statement.')
91
+ ar_connection.stubs(:active?).returns(true)
92
+ connection = Lhm::Connection.new(connection: ar_connection, options: {
93
+ reconnect_with_consistent_host: true,
94
+ retriable: {
95
+ base_interval: 0
96
+ },
97
+ })
98
+
99
+ @entangler = Lhm::Entangler.new(@migration, connection)
76
100
  assert_raises(Mysql2::Error) { @entangler.before }
77
101
  end
78
102
 
79
103
  it 'should succesfully finish after retrying' do
80
- connection = mock()
81
- connection.stubs(:execute).raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction').then.returns(true)
82
- @entangler = Lhm::Entangler.new(@migration, connection, retriable: {base_interval: 0})
104
+ ar_connection = mock()
105
+ ar_connection.stubs(:execute)
106
+ .returns([["dummy"]], [["dummy"]], [["dummy"]])
107
+ .then
108
+ .raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
109
+ .then
110
+ .returns([["dummy"]])
111
+ ar_connection.stubs(:active?).returns(true)
112
+
113
+ connection = Lhm::Connection.new(connection: ar_connection, options: {
114
+ reconnect_with_consistent_host: true,
115
+ retriable: {
116
+ base_interval: 0
117
+ },
118
+ })
119
+
120
+ @entangler = Lhm::Entangler.new(@migration, connection)
83
121
 
84
122
  assert @entangler.before
85
123
  end
86
124
 
87
125
  it 'should retry as many times as specified by configuration' do
88
- connection = mock()
89
- connection.expects(:execute).times(5).raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
90
- @entangler = Lhm::Entangler.new(@migration, connection, retriable: {tries: 5, base_interval: 0})
126
+ ar_connection = mock()
127
+ ar_connection.stubs(:execute)
128
+ .returns([["dummy"]], [["dummy"]], [["dummy"]]) # initial
129
+ .then
130
+ .raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
131
+ .then
132
+ .returns([["dummy"]]) # reconnect 1
133
+ .then
134
+ .raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
135
+ .then
136
+ .returns([["dummy"]]) # reconnect 2
137
+ .then
138
+ .raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
139
+ .then
140
+ .returns([["dummy"]]) # reconnect 3
141
+ .then
142
+ .raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction') # final error
143
+ ar_connection.stubs(:active?).returns(true)
144
+
145
+ connection = Lhm::Connection.new(connection: ar_connection, options: {
146
+ reconnect_with_consistent_host: true,
147
+ retriable: {
148
+ tries: 5,
149
+ base_interval: 0
150
+ },
151
+ })
152
+
153
+ @entangler = Lhm::Entangler.new(@migration, connection)
91
154
 
92
155
  assert_raises(Mysql2::Error) { @entangler.before }
93
156
  end
@@ -101,24 +164,24 @@ describe Lhm::Entangler do
101
164
  end
102
165
 
103
166
  it 'should use truncated names' do
104
- @entangler.trigger(:ins).length.must_be :<=, 64
105
- @entangler.trigger(:upd).length.must_be :<=, 64
106
- @entangler.trigger(:del).length.must_be :<=, 64
167
+ value(@entangler.trigger(:ins).length).must_be :<=, 64
168
+ value(@entangler.trigger(:upd).length).must_be :<=, 64
169
+ value(@entangler.trigger(:del).length).must_be :<=, 64
107
170
  end
108
171
  end
109
172
  end
110
173
 
111
174
  describe 'removal' do
112
175
  it 'should remove insert trigger' do
113
- @entangler.untangle.must_include('drop trigger if exists `lhmt_ins_origin`')
176
+ value(@entangler.untangle).must_include('drop trigger if exists `lhmt_ins_origin`')
114
177
  end
115
178
 
116
179
  it 'should remove update trigger' do
117
- @entangler.untangle.must_include('drop trigger if exists `lhmt_upd_origin`')
180
+ value(@entangler.untangle).must_include('drop trigger if exists `lhmt_upd_origin`')
118
181
  end
119
182
 
120
183
  it 'should remove delete trigger' do
121
- @entangler.untangle.must_include('drop trigger if exists `lhmt_del_origin`')
184
+ value(@entangler.untangle).must_include('drop trigger if exists `lhmt_del_origin`')
122
185
  end
123
186
  end
124
187
  end
@@ -18,7 +18,7 @@ describe Lhm::Intersection do
18
18
  destination.columns['retained'] = varchar
19
19
 
20
20
  intersection = Lhm::Intersection.new(origin, destination)
21
- intersection.destination.include?('dropped').must_equal(false)
21
+ value(intersection.destination.include?('dropped')).must_equal(false)
22
22
  end
23
23
 
24
24
  it 'should have unchanged columns' do
@@ -30,7 +30,7 @@ describe Lhm::Intersection do
30
30
  destination.columns['retained'] = varchar
31
31
 
32
32
  intersection = Lhm::Intersection.new(origin, destination)
33
- intersection.destination.must_equal(['retained'])
33
+ value(intersection.destination).must_equal(['retained'])
34
34
  end
35
35
 
36
36
  it 'should have renamed columns' do
@@ -41,8 +41,8 @@ describe Lhm::Intersection do
41
41
  destination.columns['new_name'] = varchar
42
42
 
43
43
  intersection = Lhm::Intersection.new(origin, destination, { 'old_name' => 'new_name' })
44
- intersection.origin.must_equal(['old_name'])
45
- intersection.destination.must_equal(['new_name'])
44
+ value(intersection.origin).must_equal(['old_name'])
45
+ value(intersection.destination).must_equal(['new_name'])
46
46
  end
47
47
 
48
48
  def varchar
@@ -11,9 +11,9 @@ describe Lhm do
11
11
  describe 'logger' do
12
12
 
13
13
  it 'should use the default parameters if no logger explicitly set' do
14
- Lhm.logger.must_be_kind_of Logger
15
- Lhm.logger.level.must_equal Logger::INFO
16
- Lhm.logger.instance_eval { @logdev }.dev.must_equal STDOUT
14
+ value(Lhm.logger).must_be_kind_of Logger
15
+ value(Lhm.logger.level).must_equal Logger::INFO
16
+ value(Lhm.logger.instance_eval { @logdev }.dev).must_equal STDOUT
17
17
  end
18
18
 
19
19
  it 'should use s new logger if set' do
@@ -21,9 +21,26 @@ describe Lhm do
21
21
  l.level = Logger::ERROR
22
22
  Lhm.logger = l
23
23
 
24
- Lhm.logger.level.must_equal Logger::ERROR
25
- Lhm.logger.instance_eval { @logdev }.dev.must_be_kind_of File
26
- Lhm.logger.instance_eval { @logdev }.dev.path.must_equal 'omg.ponies'
24
+ value(Lhm.logger.level).must_equal Logger::ERROR
25
+ value(Lhm.logger.instance_eval { @logdev }.dev).must_be_kind_of File
26
+ value(Lhm.logger.instance_eval { @logdev }.dev.path).must_equal 'omg.ponies'
27
+ end
28
+ end
29
+
30
+ describe 'api' do
31
+
32
+ before(:each) do
33
+ @connection = mock()
34
+ end
35
+
36
+ it 'should create a new connection when calling setup' do
37
+ Lhm.setup(@connection)
38
+ value(Lhm.connection).must_be_kind_of(Lhm::Connection)
39
+ end
40
+
41
+ it 'should create a new connection when none is created' do
42
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
43
+ value(Lhm.connection).must_be_kind_of(Lhm::Connection)
27
44
  end
28
45
  end
29
46
  end
@@ -20,32 +20,27 @@ describe Lhm::LockedSwitcher do
20
20
 
21
21
  describe 'uncommitted' do
22
22
  it 'should disable autocommit first' do
23
- @switcher.
24
- statements[0..1].
25
- must_equal([
26
- 'set @lhm_auto_commit = @@session.autocommit',
27
- 'set session autocommit = 0'
28
- ])
23
+ value(@switcher.statements[0..1]).must_equal([
24
+ 'set @lhm_auto_commit = @@session.autocommit',
25
+ 'set session autocommit = 0'
26
+ ])
29
27
  end
30
28
 
31
29
  it 'should reapply original autocommit settings at the end' do
32
- @switcher.
33
- statements[-1].
34
- must_equal('set session autocommit = @lhm_auto_commit')
30
+ value(@switcher.statements[-1])
31
+ .must_equal('set session autocommit = @lhm_auto_commit')
35
32
  end
36
33
  end
37
34
 
38
35
  describe 'switch' do
39
36
  it 'should lock origin and destination table, switch, commit and unlock' do
40
- @switcher.
41
- switch.
42
- must_equal([
43
- 'lock table `origin` write, `destination` write',
44
- "alter table `origin` rename `#{ @migration.archive_name }`",
45
- 'alter table `destination` rename `origin`',
46
- 'commit',
47
- 'unlock tables'
48
- ])
37
+ value(@switcher.switch).must_equal([
38
+ 'lock table `origin` write, `destination` write',
39
+ "alter table `origin` rename `#{ @migration.archive_name }`",
40
+ 'alter table `destination` rename `origin`',
41
+ 'commit',
42
+ 'unlock tables'
43
+ ])
49
44
  end
50
45
  end
51
46
  end
@@ -18,7 +18,7 @@ describe Lhm::Migrator do
18
18
  it 'should add an index' do
19
19
  @creator.add_index(:a)
20
20
 
21
- @creator.statements.must_equal([
21
+ value(@creator.statements).must_equal([
22
22
  'create index `index_alt_on_a` on `lhmn_alt` (`a`)'
23
23
  ])
24
24
  end
@@ -26,7 +26,7 @@ describe Lhm::Migrator do
26
26
  it 'should add a composite index' do
27
27
  @creator.add_index([:a, :b])
28
28
 
29
- @creator.statements.must_equal([
29
+ value(@creator.statements).must_equal([
30
30
  'create index `index_alt_on_a_and_b` on `lhmn_alt` (`a`, `b`)'
31
31
  ])
32
32
  end
@@ -34,7 +34,7 @@ describe Lhm::Migrator do
34
34
  it 'should add an index with prefix length' do
35
35
  @creator.add_index(['a(10)', 'b'])
36
36
 
37
- @creator.statements.must_equal([
37
+ value(@creator.statements).must_equal([
38
38
  'create index `index_alt_on_a_and_b` on `lhmn_alt` (`a`(10), `b`)'
39
39
  ])
40
40
  end
@@ -42,7 +42,7 @@ describe Lhm::Migrator do
42
42
  it 'should add an index with a custom name' do
43
43
  @creator.add_index([:a, :b], :custom_index_name)
44
44
 
45
- @creator.statements.must_equal([
45
+ value(@creator.statements).must_equal([
46
46
  'create index `custom_index_name` on `lhmn_alt` (`a`, `b`)'
47
47
  ])
48
48
  end
@@ -56,7 +56,7 @@ describe Lhm::Migrator do
56
56
  it 'should add a unique index' do
57
57
  @creator.add_unique_index(['a(5)', :b])
58
58
 
59
- @creator.statements.must_equal([
59
+ value(@creator.statements).must_equal([
60
60
  'create unique index `index_alt_on_a_and_b` on `lhmn_alt` (`a`(5), `b`)'
61
61
  ])
62
62
  end
@@ -64,7 +64,7 @@ describe Lhm::Migrator do
64
64
  it 'should add a unique index with a custom name' do
65
65
  @creator.add_unique_index([:a, :b], :custom_index_name)
66
66
 
67
- @creator.statements.must_equal([
67
+ value(@creator.statements).must_equal([
68
68
  'create unique index `custom_index_name` on `lhmn_alt` (`a`, `b`)'
69
69
  ])
70
70
  end
@@ -78,7 +78,7 @@ describe Lhm::Migrator do
78
78
  it 'should remove an index' do
79
79
  @creator.remove_index(['b', 'a'])
80
80
 
81
- @creator.statements.must_equal([
81
+ value(@creator.statements).must_equal([
82
82
  'drop index `index_alt_on_b_and_a` on `lhmn_alt`'
83
83
  ])
84
84
  end
@@ -86,7 +86,7 @@ describe Lhm::Migrator do
86
86
  it 'should remove an index with a custom name' do
87
87
  @creator.remove_index([:a, :b], :custom_index_name)
88
88
 
89
- @creator.statements.must_equal([
89
+ value(@creator.statements).must_equal([
90
90
  'drop index `custom_index_name` on `lhmn_alt`'
91
91
  ])
92
92
  end
@@ -96,7 +96,7 @@ describe Lhm::Migrator do
96
96
  it 'should add a column' do
97
97
  @creator.add_column('logins', 'INT(12)')
98
98
 
99
- @creator.statements.must_equal([
99
+ value(@creator.statements).must_equal([
100
100
  'alter table `lhmn_alt` add column `logins` INT(12)'
101
101
  ])
102
102
  end
@@ -104,7 +104,7 @@ describe Lhm::Migrator do
104
104
  it 'should remove a column' do
105
105
  @creator.remove_column('logins')
106
106
 
107
- @creator.statements.must_equal([
107
+ value(@creator.statements).must_equal([
108
108
  'alter table `lhmn_alt` drop `logins`'
109
109
  ])
110
110
  end
@@ -112,7 +112,7 @@ describe Lhm::Migrator do
112
112
  it 'should change a column' do
113
113
  @creator.change_column('logins', 'INT(11)')
114
114
 
115
- @creator.statements.must_equal([
115
+ value(@creator.statements).must_equal([
116
116
  'alter table `lhmn_alt` modify column `logins` INT(11)'
117
117
  ])
118
118
  end
@@ -122,7 +122,7 @@ describe Lhm::Migrator do
122
122
  it 'should accept a ddl statement' do
123
123
  @creator.ddl('alter table `%s` add column `f` tinyint(1)' % @creator.name)
124
124
 
125
- @creator.statements.must_equal([
125
+ value(@creator.statements).must_equal([
126
126
  'alter table `lhmn_alt` add column `f` tinyint(1)'
127
127
  ])
128
128
  end
@@ -132,15 +132,13 @@ describe Lhm::Migrator do
132
132
  it 'should add two columns' do
133
133
  @creator.add_column('first', 'VARCHAR(64)')
134
134
  @creator.add_column('last', 'VARCHAR(64)')
135
- @creator.statements.length.must_equal(2)
135
+ value(@creator.statements.length).must_equal(2)
136
136
 
137
- @creator.
138
- statements[0].
139
- must_equal('alter table `lhmn_alt` add column `first` VARCHAR(64)')
137
+ value(@creator.statements[0])
138
+ .must_equal('alter table `lhmn_alt` add column `first` VARCHAR(64)')
140
139
 
141
- @creator.
142
- statements[1].
143
- must_equal('alter table `lhmn_alt` add column `last` VARCHAR(64)')
140
+ value(@creator.statements[1])
141
+ .must_equal('alter table `lhmn_alt` add column `last` VARCHAR(64)')
144
142
  end
145
143
  end
146
144
  end
@@ -1,6 +1,9 @@
1
1
  require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
2
2
 
3
3
  require 'lhm/printer'
4
+ require 'logger'
5
+
6
+
4
7
 
5
8
  describe Lhm::Printer do
6
9
  include UnitHelper
@@ -12,24 +15,17 @@ describe Lhm::Printer do
12
15
  end
13
16
 
14
17
  it 'prints the percentage' do
15
- mock = MiniTest::Mock.new
18
+ r, w = IO.pipe
19
+ Lhm.logger = Logger.new(w)
20
+
16
21
  10.times do |i|
17
- mock.expect(:write, :return_value) do |message|
18
- message = message.first if message.is_a?(Array)
19
- assert_match(/^\r/, message)
20
- assert_match(/#{i}\/10/, message)
21
- end
22
+ @printer.notify(i, 10)
23
+ assert_match(/#{i}\/10/, log_expression_message(r.gets))
22
24
  end
23
-
24
- @printer.instance_variable_set(:@output, mock)
25
- 10.times { |i| @printer.notify(i, 10) }
26
- mock.verify
27
25
  end
28
26
 
29
27
  it 'always prints a bigger message' do
30
28
  @length = 0
31
- printer_mock = mock()
32
- printer_mock.expects(:write).at_least_once
33
29
 
34
30
  def assert_length(printer)
35
31
  new_length = printer.instance_variable_get(:@max_length)
@@ -37,7 +33,6 @@ describe Lhm::Printer do
37
33
  @length = new_length
38
34
  end
39
35
 
40
- @printer.instance_variable_set(:@output, printer_mock)
41
36
  @printer.notify(10, 100)
42
37
  assert_length(@printer)
43
38
  @printer.notify(0, 100)
@@ -51,27 +46,20 @@ describe Lhm::Printer do
51
46
  end
52
47
 
53
48
  it 'prints the end message' do
54
- mock = MiniTest::Mock.new
55
- mock.expect(:write, :return_value, [String])
56
- mock.expect(:write, :return_value, ["\n"])
57
-
58
- @printer.instance_variable_set(:@output, mock)
49
+ r, w = IO.pipe
50
+ Lhm.logger = Logger.new(w)
59
51
  @printer.end
60
52
 
61
- mock.verify
53
+ assert_equal(log_expression_message(r.gets), "100% complete\n")
62
54
  end
63
55
 
64
56
  it 'prints the exception message' do
65
- mock = MiniTest::Mock.new
66
- mock.expect(:write, :return_value, ["\rfailed: woops"])
67
- mock.expect(:write, :return_value, ["\n"])
68
-
57
+ r, w = IO.pipe
58
+ Lhm.logger = Logger.new(w)
69
59
  e = StandardError.new('woops')
70
-
71
- @printer.instance_variable_set(:@output, mock)
72
60
  @printer.exception(e)
73
61
 
74
- mock.verify
62
+ assert_equal(log_expression_message(r.gets), "failed: #{e}\n")
75
63
  end
76
64
  end
77
65
 
@@ -7,26 +7,22 @@ require 'lhm/sql_helper'
7
7
 
8
8
  describe Lhm::SqlHelper do
9
9
  it 'should name index with a single column' do
10
- Lhm::SqlHelper.
11
- idx_name(:users, :name).
12
- must_equal('index_users_on_name')
10
+ value(Lhm::SqlHelper.idx_name(:users, :name))
11
+ .must_equal('index_users_on_name')
13
12
  end
14
13
 
15
14
  it 'should name index with multiple columns' do
16
- Lhm::SqlHelper.
17
- idx_name(:users, [:name, :firstname]).
18
- must_equal('index_users_on_name_and_firstname')
15
+ value(Lhm::SqlHelper.idx_name(:users, [:name, :firstname]))
16
+ .must_equal('index_users_on_name_and_firstname')
19
17
  end
20
18
 
21
19
  it 'should name index with prefixed column' do
22
- Lhm::SqlHelper.
23
- idx_name(:tracks, ['title(10)', 'album']).
24
- must_equal('index_tracks_on_title_and_album')
20
+ value(Lhm::SqlHelper.idx_name(:tracks, ['title(10)', 'album']))
21
+ .must_equal('index_tracks_on_title_and_album')
25
22
  end
26
23
 
27
24
  it 'should quote column names in index specification' do
28
- Lhm::SqlHelper.
29
- idx_spec(['title(10)', 'album']).
30
- must_equal('`title`(10), `album`')
25
+ value(Lhm::SqlHelper.idx_spec(['title(10)', 'album']))
26
+ .must_equal('`title`(10), `album`')
31
27
  end
32
28
  end
@@ -11,7 +11,7 @@ describe Lhm::Table do
11
11
  describe 'names' do
12
12
  it 'should name destination' do
13
13
  @table = Lhm::Table.new('users')
14
- @table.destination_name.must_equal 'lhmn_users'
14
+ value(@table.destination_name).must_equal 'lhmn_users'
15
15
  end
16
16
  end
17
17
 
@@ -23,25 +23,25 @@ describe Lhm::Table do
23
23
  it 'should be satisfied with a single column primary key called id' do
24
24
  @table = Lhm::Table.new('table', 'id')
25
25
  set_columns(@table, { 'id' => { :type => 'int(1)' } })
26
- @table.satisfies_id_column_requirement?.must_equal true
26
+ value(@table.satisfies_id_column_requirement?).must_equal true
27
27
  end
28
28
 
29
29
  it 'should be satisfied with a primary key not called id, as long as there is still an id' do
30
30
  @table = Lhm::Table.new('table', 'uuid')
31
31
  set_columns(@table, { 'id' => { :type => 'int(1)' } })
32
- @table.satisfies_id_column_requirement?.must_equal true
32
+ value(@table.satisfies_id_column_requirement?).must_equal true
33
33
  end
34
34
 
35
35
  it 'should be satisifed if display attributes are not present (deprecated in mysql 8)' do
36
36
  @table = Lhm::Table.new('table', 'id')
37
37
  set_columns(@table, { 'id' => { :type => 'int' } })
38
- @table.satisfies_id_column_requirement?.must_equal true
38
+ value(@table.satisfies_id_column_requirement?).must_equal true
39
39
  end
40
40
 
41
41
  it 'should not be satisfied if id is not numeric' do
42
42
  @table = Lhm::Table.new('table', 'id')
43
43
  set_columns(@table, { 'id' => { :type => 'varchar(255)' } })
44
- @table.satisfies_id_column_requirement?.must_equal false
44
+ value(@table.satisfies_id_column_requirement?).must_equal false
45
45
  end
46
46
  end
47
47
  end
@@ -39,7 +39,7 @@ describe Lhm::Throttler::Slave do
39
39
  @logs = StringIO.new
40
40
  Lhm.logger = Logger.new(@logs)
41
41
 
42
- @dummy_mysql_client_config = lambda { {'username' => 'user', 'password' => 'pw', 'database' => 'db'} }
42
+ @dummy_mysql_client_config = lambda { { 'username' => 'user', 'password' => 'pw', 'database' => 'db' } }
43
43
  end
44
44
 
45
45
  describe "#client" do
@@ -64,7 +64,7 @@ describe Lhm::Throttler::Slave do
64
64
 
65
65
  describe 'with proper config' do
66
66
  it "creates a new Mysql2::Client" do
67
- expected_config = {username: 'user', password: 'pw', database: 'db', host: 'slave'}
67
+ expected_config = { username: 'user', password: 'pw', database: 'db', host: 'slave' }
68
68
  Mysql2::Client.stubs(:new).with(expected_config).returns(mock())
69
69
 
70
70
  assert Lhm::Throttler::Slave.new('slave', @dummy_mysql_client_config).connection
@@ -73,8 +73,12 @@ describe Lhm::Throttler::Slave do
73
73
 
74
74
  describe 'with active record config' do
75
75
  it 'logs and creates client' do
76
- active_record_config = {username: 'user', password: 'pw', database: 'db'}
77
- ActiveRecord::Base.stubs(:connection_pool).returns(stub(spec: stub(config: active_record_config)))
76
+ active_record_config = { username: 'user', password: 'pw', database: 'db' }
77
+ if ActiveRecord::VERSION::MAJOR > 6 || ActiveRecord::VERSION::MAJOR == 6 && ActiveRecord::VERSION::MINOR >= 1
78
+ ActiveRecord::Base.stubs(:connection_pool).returns(stub(db_config: stub(configuration_hash: active_record_config)))
79
+ else
80
+ ActiveRecord::Base.stubs(:connection_pool).returns(stub(spec: stub(config: active_record_config)))
81
+ end
78
82
 
79
83
  Mysql2::Client.stubs(:new).returns(mock())
80
84
 
@@ -92,9 +96,9 @@ describe Lhm::Throttler::Slave do
92
96
  class Connection
93
97
  def self.query(query)
94
98
  if query == Lhm::Throttler::Slave::SQL_SELECT_MAX_SLAVE_LAG
95
- [{'Seconds_Behind_Master' => 20}]
99
+ [{ 'Seconds_Behind_Master' => 20 }]
96
100
  elsif query == Lhm::Throttler::Slave::SQL_SELECT_SLAVE_HOSTS
97
- [{'host' => '1.1.1.1:80'}]
101
+ [{ 'host' => '1.1.1.1:80' }]
98
102
  end
99
103
  end
100
104
  end
@@ -104,7 +108,7 @@ describe Lhm::Throttler::Slave do
104
108
 
105
109
  class StoppedConnection
106
110
  def self.query(query)
107
- [{'Seconds_Behind_Master' => nil}]
111
+ [{ 'Seconds_Behind_Master' => nil }]
108
112
  end
109
113
  end
110
114
 
@@ -138,7 +142,7 @@ describe Lhm::Throttler::Slave do
138
142
  Lhm::Throttler::Slave.any_instance.stubs(:config).returns([])
139
143
 
140
144
  slave = Lhm::Throttler::Slave.new('slave', @dummy_mysql_client_config)
141
- assert_send([Lhm.logger, :info, "Unable to connect and/or query slave: error"])
145
+ Logger.any_instance.expects(:info).with("Unable to connect and/or query slave: Can't connect to MySQL server")
142
146
  assert_equal(0, slave.lag)
143
147
  end
144
148
  end
@@ -286,7 +290,7 @@ describe Lhm::Throttler::SlaveLag do
286
290
  describe 'with the :check_only option' do
287
291
  describe 'with a callable argument' do
288
292
  before do
289
- check_only = lambda {{'host' => '1.1.1.3'}}
293
+ check_only = lambda { { 'host' => '1.1.1.3' } }
290
294
  @throttler = Lhm::Throttler::SlaveLag.new :check_only => check_only
291
295
  end
292
296
 
@@ -300,6 +304,7 @@ describe Lhm::Throttler::SlaveLag do
300
304
  describe 'with a non-callable argument' do
301
305
  before do
302
306
  @throttler = Lhm::Throttler::SlaveLag.new :check_only => 'I cannot be called'
307
+
303
308
  def @throttler.master_slave_hosts
304
309
  ['1.1.1.1', '1.1.1.4']
305
310
  end