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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +24 -15
- data/.gitignore +1 -6
- data/Appraisals +24 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +66 -0
- data/README.md +55 -4
- data/Rakefile +11 -0
- data/dev.yml +31 -6
- data/docker-compose.yml +58 -0
- data/gemfiles/activerecord_5.2.gemfile +9 -0
- data/gemfiles/activerecord_5.2.gemfile.lock +65 -0
- data/gemfiles/activerecord_6.0.gemfile +7 -0
- data/gemfiles/activerecord_6.0.gemfile.lock +67 -0
- data/gemfiles/activerecord_6.1.gemfile +7 -0
- data/gemfiles/activerecord_6.1.gemfile.lock +66 -0
- data/gemfiles/activerecord_7.0.0.alpha2.gemfile +7 -0
- data/gemfiles/activerecord_7.0.0.alpha2.gemfile.lock +64 -0
- data/lhm.gemspec +7 -3
- data/lib/lhm/atomic_switcher.rb +5 -11
- data/lib/lhm/chunk_insert.rb +7 -10
- data/lib/lhm/chunker.rb +21 -10
- data/lib/lhm/cleanup/current.rb +9 -12
- data/lib/lhm/connection.rb +108 -0
- data/lib/lhm/entangler.rb +8 -13
- data/lib/lhm/invoker.rb +6 -4
- data/lib/lhm/locked_switcher.rb +2 -0
- data/lib/lhm/migrator.rb +2 -0
- data/lib/lhm/printer.rb +10 -6
- data/lib/lhm/proxysql_helper.rb +10 -0
- data/lib/lhm/sql_retry.rb +129 -10
- data/lib/lhm/throttler/slave_lag.rb +19 -2
- data/lib/lhm/version.rb +1 -1
- data/lib/lhm.rb +41 -16
- data/scripts/helpers/wait-for-dbs.sh +21 -0
- data/scripts/mysql/reader/create_replication.sql +10 -0
- data/scripts/mysql/writer/create_test_db.sql +1 -0
- data/scripts/mysql/writer/create_users.sql +6 -0
- data/scripts/proxysql/proxysql.cnf +117 -0
- data/spec/integration/atomic_switcher_spec.rb +53 -17
- data/spec/integration/chunk_insert_spec.rb +3 -2
- data/spec/integration/chunker_spec.rb +18 -16
- data/spec/integration/cleanup_spec.rb +49 -38
- data/spec/integration/database.yml +25 -0
- data/spec/integration/entangler_spec.rb +7 -5
- data/spec/integration/integration_helper.rb +25 -10
- data/spec/integration/lhm_spec.rb +114 -40
- data/spec/integration/lock_wait_timeout_spec.rb +2 -2
- data/spec/integration/locked_switcher_spec.rb +4 -4
- data/spec/integration/proxysql_spec.rb +34 -0
- data/spec/integration/sql_retry/db_connection_helper.rb +52 -0
- data/spec/integration/sql_retry/lock_wait_spec.rb +8 -6
- data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +17 -4
- data/spec/integration/sql_retry/proxysql_helper.rb +22 -0
- data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +109 -0
- data/spec/integration/table_spec.rb +11 -19
- data/spec/integration/toxiproxy_helper.rb +40 -0
- data/spec/test_helper.rb +24 -0
- data/spec/unit/atomic_switcher_spec.rb +4 -6
- data/spec/unit/chunk_insert_spec.rb +7 -2
- data/spec/unit/chunker_spec.rb +47 -42
- data/spec/unit/connection_spec.rb +111 -0
- data/spec/unit/entangler_spec.rb +85 -22
- data/spec/unit/intersection_spec.rb +4 -4
- data/spec/unit/lhm_spec.rb +23 -6
- data/spec/unit/locked_switcher_spec.rb +13 -18
- data/spec/unit/migrator_spec.rb +17 -19
- data/spec/unit/printer_spec.rb +14 -26
- data/spec/unit/sql_helper_spec.rb +8 -12
- data/spec/unit/table_spec.rb +5 -5
- data/spec/unit/throttler/slave_lag_spec.rb +14 -9
- data/spec/unit/throttler_spec.rb +12 -12
- data/spec/unit/unit_helper.rb +13 -0
- metadata +85 -14
- data/bin/.gitkeep +0 -0
- data/dbdeployer/config.json +0 -32
- data/dbdeployer/install.sh +0 -64
- data/gemfiles/ar-2.3_mysql.gemfile +0 -6
- data/gemfiles/ar-3.2_mysql.gemfile +0 -5
- data/gemfiles/ar-3.2_mysql2.gemfile +0 -5
- data/gemfiles/ar-4.0_mysql2.gemfile +0 -5
- data/gemfiles/ar-4.1_mysql2.gemfile +0 -5
- data/gemfiles/ar-4.2_mysql2.gemfile +0 -5
- data/gemfiles/ar-5.0_mysql2.gemfile +0 -5
data/spec/unit/entangler_spec.rb
CHANGED
@@ -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
|
-
|
66
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
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
|
data/spec/unit/lhm_spec.rb
CHANGED
@@ -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
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
data/spec/unit/migrator_spec.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/spec/unit/printer_spec.rb
CHANGED
@@ -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
|
-
|
18
|
+
r, w = IO.pipe
|
19
|
+
Lhm.logger = Logger.new(w)
|
20
|
+
|
16
21
|
10.times do |i|
|
17
|
-
|
18
|
-
|
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
|
-
|
55
|
-
|
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
|
-
|
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
|
-
|
66
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/spec/unit/table_spec.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
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::
|
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
|
-
|
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
|