pt-osc 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,13 +7,23 @@ rvm:
7
7
  - 2.0.0
8
8
  - 2.1.2
9
9
  - 2.1
10
+ - 2.2
11
+ - ruby-head
10
12
  env:
11
- - CODECLIMATE_REPO_TOKEN=0b58d4e91bfac6ed6e48b31606f6792598a3a59c27bded19d58ef8406c09f57c
13
+ global:
14
+ - CODECLIMATE_REPO_TOKEN=0b58d4e91bfac6ed6e48b31606f6792598a3a59c27bded19d58ef8406c09f57c
15
+ matrix:
16
+ - PT_OSC_VERSION=LATEST
17
+ - PT_OSC_VERSION=DEVELOPMENT
18
+ - PT_OSC_VERSION=2.2.12
19
+ - PT_OSC_VERSION=2.2.11
20
+ - PT_OSC_VERSION=2.1.11
12
21
  cache:
13
22
  - bundler
14
23
  - apt
15
24
  bundler_args: --binstubs --retry=5
16
25
  before_script:
26
+ - $TRAVIS_BUILD_DIR/test/travisci/install_pt_osc.sh
17
27
  - unset RAILS_ENV
18
28
  - unset RACK_ENV
19
29
  - bundle exec rake db:test_create
@@ -22,3 +32,11 @@ script:
22
32
  addons:
23
33
  code_climate:
24
34
  repo_token: 0b58d4e91bfac6ed6e48b31606f6792598a3a59c27bded19d58ef8406c09f57c
35
+ matrix:
36
+ fast_finish: true
37
+ allow_failures:
38
+ - rvm: ruby-head
39
+ - rvm: 2.2
40
+ - env: PT_OSC_VERSION=DEVELOPMENT
41
+ - env: PT_OSC_VERSION=1.0.2
42
+ - env: PT_OSC_VERSION=2.0.5
@@ -1,3 +1,15 @@
1
+ ## 0.2.2
2
+
3
+ - fix bugs with string quoting, use shellwords instead
4
+ - added integration tests for execution of pt-osc migrations
5
+ - support `user` and `password` flags in percona config
6
+ - pull `username` and `password` from database config when available
7
+
8
+ ## 0.2.1
9
+
10
+ - properly quote string values in MySQL commands
11
+ - added additional tests
12
+
1
13
  ## 0.2.0
2
14
 
3
15
  - support for setting the [`--check-alter` flag](http://www.percona.com/doc/percona-toolkit/2.1/pt-online-schema-change.html#cmdoption-pt-online-schema-change--%5Bno%5Dcheck-alter)
data/README.md CHANGED
@@ -41,6 +41,8 @@ Additional/modified options for the `percona` hash include:
41
41
  - `check-alter`: When set to `false`, `ALTER` commands will not be checked when executing (same as [`--no-check-alter`](http://www.percona.com/doc/percona-toolkit/2.1/pt-online-schema-change.html#cmdoption-pt-online-schema-change--%5Bno%5Dcheck-alter))
42
42
  - `run_mode`: Specify `'execute'` to actually run `pt-online-schema-change` when the migration runs. Specify `'print'` to output the commands to run to STDOUT instead. Default is `'print'`.
43
43
  - `log`: Specify the file used for logging activity. Can be a relative or absolute path.
44
+ - `username`: By default, the command will use the username given in your standard database config. Specify a username here to override. Specify `false` to omit username (such as when using a `defaults-file`.
45
+ - `password`: By default, the command will use the password given in your standard database config. Specify a password here to override. Specify `false` to omit password (such as when using a `defaults-file`.
44
46
 
45
47
  #### Migrations
46
48
 
@@ -58,16 +60,23 @@ If you have migrations that you do not want to be run with `pt-online-schema-cha
58
60
 
59
61
  This gem is not considered production ready. There will be bugs.
60
62
 
61
- ##### Compatibility
63
+ ##### Requirements and Compatibility
62
64
 
63
- `pt-osc` is tested against:
64
- - ActiveRecord 3.2 branch
65
- - Ruby 1.9.3
66
- - Ruby 2.0.0
67
- - Ruby 2.1.2
68
- - Ruby 2.1 latest
65
+ ###### ActiveRecord
66
+ - 3.2 branch
69
67
 
70
- Support for other versions of Ruby or ActiveRecord is unknown and not guaranteed.
68
+ ###### Ruby
69
+ - Ruby 1.9.3
70
+ - Ruby 2.0.0
71
+ - Ruby 2.1.2
72
+ - Ruby 2.1 latest
73
+
74
+ ###### Percona Toolkit
75
+ - 2.2 branch (latest)
76
+ - 2.1 branch (latest)
77
+ - does **not** work with 2.0 or 1.0 branches
78
+
79
+ Support for other versions of these tools is unknown and not guaranteed.
71
80
 
72
81
  `pt-osc` requires the use of `pt-online-schema-change` 2.0 or later. Some options may not work with all versions.
73
82
 
@@ -1,5 +1,6 @@
1
1
  require 'active_record/migration'
2
2
  require 'active_record/connection_adapters/mysql_pt_osc_adapter'
3
+ require 'shellwords'
3
4
 
4
5
  module ActiveRecord
5
6
  class PtOscMigration < Migration
@@ -19,7 +20,18 @@ module ActiveRecord
19
20
  default: true,
20
21
  mutator: :execute_only,
21
22
  version: '>= 2.1',
22
- }
23
+ },
24
+ 'user' => {
25
+ mutator: :get_from_config,
26
+ arguments: {
27
+ key_name: 'username',
28
+ },
29
+ default: nil,
30
+ },
31
+ 'password' => {
32
+ mutator: :get_from_config,
33
+ default: nil,
34
+ },
23
35
  }.freeze
24
36
 
25
37
  def self.percona_flags
@@ -98,7 +110,7 @@ module ActiveRecord
98
110
 
99
111
  def execute_sql_for_table(execute_sql, database_name, table_name, dry_run = true)
100
112
  command = percona_command(execute_sql, database_name, table_name, execute: !dry_run)
101
- logger.info "Command is #{command}"
113
+ logger.info "Command is #{self.class.sanitize_command(command)}"
102
114
 
103
115
  success = Kernel.system command
104
116
 
@@ -122,7 +134,7 @@ module ActiveRecord
122
134
  announce 'Run the following commands:'
123
135
 
124
136
  [true, false].each do |dry_run|
125
- write percona_command(execute_sql, database_name, table_name, execute: !dry_run)
137
+ write self.class.sanitize_command(percona_command(execute_sql, database_name, table_name, execute: !dry_run))
126
138
  end
127
139
 
128
140
  end
@@ -131,7 +143,7 @@ module ActiveRecord
131
143
  end
132
144
 
133
145
  def percona_command(execute_sql, database_name, table_name, options = {})
134
- command = "pt-online-schema-change --alter '#{execute_sql}' D=#{database_name},t=#{table_name}"
146
+ command = ['pt-online-schema-change', '--alter', execute_sql || '', "D=#{database_name},t=#{table_name}"]
135
147
 
136
148
  # Whitelist
137
149
  options = HashWithIndifferentAccess.new(options)
@@ -150,7 +162,9 @@ module ActiveRecord
150
162
  options[flag] = flag_config[:default] if flag_config.key?(:default) && !options.key?(flag)
151
163
  end
152
164
 
153
- "#{command}#{run_mode_flag(options)}#{command_flags(options)}"
165
+ command_parts = command + [run_mode_flag(options)] + command_flags(options)
166
+
167
+ command_parts.shelljoin
154
168
  end
155
169
 
156
170
  def self.tool_version
@@ -158,7 +172,7 @@ module ActiveRecord
158
172
  end
159
173
 
160
174
  def database_config
161
- @db_config ||= (@connection.instance_variable_get(:@config) || ActiveRecord::Base.connection_config).with_indifferent_access
175
+ @db_config ||= raw_database_config.with_indifferent_access
162
176
  end
163
177
 
164
178
  def percona_config
@@ -178,8 +192,12 @@ module ActiveRecord
178
192
  end
179
193
 
180
194
  private
195
+ def raw_database_config
196
+ connection.pool.spec.config || ActiveRecord::Base.connection_config
197
+ end
198
+
181
199
  def command_flags(options)
182
- options.map do |key, value|
200
+ options.flat_map do |key, value|
183
201
  next if key == 'execute'
184
202
  flag_options = self.class.percona_flags[key]
185
203
 
@@ -190,7 +208,8 @@ module ActiveRecord
190
208
 
191
209
  # Mutate the value if needed
192
210
  if flag_options.try(:key?, :mutator)
193
- value = send(flag_options[:mutator], value, all_options: options, flag_name: key)
211
+ value = send(flag_options[:mutator], value, { all_options: options, flag_name: key }.merge(flag_options[:arguments] || {}))
212
+ next if value.nil? # Allow a mutator to determine the flag shouldn't be used
194
213
  end
195
214
 
196
215
  # Handle boolean flags
@@ -199,18 +218,25 @@ module ActiveRecord
199
218
  value = nil
200
219
  end
201
220
 
202
- " --#{key} #{value}"
203
- end.join('')
221
+ ["--#{key}", value]
222
+ end.compact
204
223
  end
205
224
 
206
225
  def run_mode_flag(options)
207
- options[:execute] ? ' --execute' : ' --dry-run'
226
+ options[:execute] ? '--execute' : '--dry-run'
208
227
  end
209
228
 
210
229
  def self.get_tool_version
211
230
  `pt-online-schema-change --version`
212
231
  end
213
232
 
233
+ def self.sanitize_command(command)
234
+ command_parts = command.shellsplit
235
+ password_index = command_parts.find_index('--password')
236
+ command_parts[password_index + 1] = '_hidden_' unless password_index.nil? || command_parts.length == password_index + 1
237
+ command_parts.shelljoin
238
+ end
239
+
214
240
  # Flag mutators
215
241
  def make_path_absolute(path, _ = {})
216
242
  return path if path[0] == '/'
@@ -221,5 +247,16 @@ module ActiveRecord
221
247
  def execute_only(flag, options = {})
222
248
  options[:all_options][:execute] ? flag : self.class.percona_flags[options[:flag_name]][:default]
223
249
  end
250
+
251
+ def get_from_config(flag, options = {})
252
+ case flag
253
+ when nil
254
+ database_config[options[:key_name] || options[:flag_name]]
255
+ when false
256
+ nil
257
+ else
258
+ flag
259
+ end
260
+ end
224
261
  end
225
262
  end
@@ -1,5 +1,5 @@
1
1
  module Pt
2
2
  module Osc
3
- VERSION = '0.2.0'
3
+ VERSION = '0.2.2'
4
4
  end
5
5
  end
@@ -24,7 +24,6 @@ class PtOscMigrationFunctionalTest < ActiveRecord::TestCase
24
24
  @table_name = Faker::Lorem.word
25
25
  @column_name = Faker::Lorem.word
26
26
  @index_name = Faker::Lorem.words.join('_')
27
- @index_name_2 = "#{@index_name}_2"
28
27
 
29
28
  ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS `#{@table_name}`;"
30
29
  ActiveRecord::Base.connection.execute <<-SQL
@@ -41,14 +40,22 @@ class PtOscMigrationFunctionalTest < ActiveRecord::TestCase
41
40
 
42
41
  context 'a migration with only ALTER statements' do
43
42
  setup do
43
+ @renamed_column_name = Faker::Lorem.word
44
+ @new_column_name = Faker::Lorem.word
45
+ @new_table_name = Faker::Lorem.word
46
+ @index_name_2 = "#{@index_name}_2"
47
+ @index_name_3 = "#{@index_name}_3"
48
+
44
49
  TestMigration.class_eval <<-EVAL
45
50
  def change
46
- rename_table :#{@table_name}, :#{Faker::Lorem.word}
47
- add_column :#{@table_name}, :#{Faker::Lorem.word}, :integer
51
+ rename_table :#{@table_name}, :#{@new_table_name}
52
+ add_column :#{@table_name}, :#{@new_column_name}, :integer
48
53
  change_column :#{@table_name}, :#{@column_name}, :varchar, default: 'newthing'
49
- rename_column :#{@table_name}, :#{@column_name}, :#{Faker::Lorem.word}
54
+ change_column :#{@table_name}, :#{@column_name}, :varchar, default: :newsymbol
55
+ rename_column :#{@table_name}, :#{@column_name}, :#{@renamed_column_name}
50
56
  remove_column :#{@table_name}, :#{@column_name}
51
57
  add_index :#{@table_name}, :#{@column_name}, name: :#{@index_name_2}
58
+ add_index :#{@table_name}, [:#{@new_column_name}, :#{@renamed_column_name}], name: :#{@index_name_3}, unique: true
52
59
  remove_index :#{@table_name}, name: :#{@index_name}
53
60
  end
54
61
  EVAL
@@ -58,29 +65,29 @@ class PtOscMigrationFunctionalTest < ActiveRecord::TestCase
58
65
  TestMigration.send(:remove_method, :change)
59
66
  end
60
67
 
61
- context 'ignoring schema lookups' do
68
+ context 'with suppressed output' do
62
69
  setup do
63
- # Kind of a hacky way to do this
64
- ignored_sql = ActiveRecord::SQLCounter.ignored_sql + [
65
- /^SHOW FULL FIELDS FROM/,
66
- /^SHOW COLUMNS FROM/,
67
- /^SHOW KEYS FROM/,
68
- ]
69
- ActiveRecord::SQLCounter.any_instance.stubs(:ignore).returns(ignored_sql)
70
+ @migration.stubs(:write)
71
+ @migration.stubs(:announce)
70
72
  end
71
73
 
72
74
  teardown do
73
- ActiveRecord::SQLCounter.any_instance.unstub(:ignore)
75
+ @migration.unstub(:write, :announce)
74
76
  end
75
77
 
76
- context 'with suppressed output' do
78
+ context 'ignoring schema lookups' do
77
79
  setup do
78
- @migration.stubs(:write)
79
- @migration.stubs(:announce)
80
+ # Kind of a hacky way to do this
81
+ ignored_sql = ActiveRecord::SQLCounter.ignored_sql + [
82
+ /^SHOW FULL FIELDS FROM/,
83
+ /^SHOW COLUMNS FROM/,
84
+ /^SHOW KEYS FROM/,
85
+ ]
86
+ ActiveRecord::SQLCounter.any_instance.stubs(:ignore).returns(ignored_sql)
80
87
  end
81
88
 
82
89
  teardown do
83
- @migration.unstub(:write, :announce)
90
+ ActiveRecord::SQLCounter.any_instance.unstub(:ignore)
84
91
  end
85
92
 
86
93
  should 'not execute any queries immediately' do
@@ -101,6 +108,26 @@ class PtOscMigrationFunctionalTest < ActiveRecord::TestCase
101
108
  end
102
109
  end
103
110
  end
111
+
112
+ context 'the resulting command' do
113
+ should 'have the correct pt-osc ALTER statement' do
114
+ expected_alter = <<-ALTER
115
+ RENAME TO `#{@new_table_name}`
116
+ ADD `#{@new_column_name}` int(11)
117
+ CHANGE `#{@column_name}` `#{@column_name}` varchar DEFAULT 'newthing'
118
+ CHANGE `#{@column_name}` `#{@column_name}` varchar DEFAULT 'newsymbol'
119
+ CHANGE `#{@column_name}` `#{@renamed_column_name}` varchar(255) DEFAULT NULL
120
+ DROP COLUMN `#{@column_name}`
121
+ ADD INDEX `#{@index_name_2}` (`#{@column_name}`)
122
+ ADD UNIQUE INDEX `#{@index_name_3}` (`#{@new_column_name}`, `#{@renamed_column_name}`)
123
+ DROP INDEX `#{@index_name}`
124
+ ALTER
125
+ expected_alter.strip!.gsub!(/^\s*/, '').gsub!("\n", ',')
126
+
127
+ @migration.change
128
+ assert_equal expected_alter, @migration.connection.get_commands_string(@table_name)
129
+ end
130
+ end
104
131
  end
105
132
  end
106
133
  end
@@ -0,0 +1,102 @@
1
+ require 'test_helper'
2
+
3
+ class PtOscMigrationIntegrationTest < ActiveRecord::TestCase
4
+ class TestMigration < ActiveRecord::PtOscMigration; end
5
+
6
+ context 'a migration' do
7
+ setup do
8
+ @migration = TestMigration.new
9
+ @migration.stubs(:logger).returns(stub_everything)
10
+ end
11
+
12
+ context 'connected to a pt-osc database' do
13
+ setup do
14
+ ActiveRecord::Base.establish_connection(test_spec('test_execute'))
15
+ @migration.instance_variable_set(:@connection, ActiveRecord::Base.connection)
16
+ end
17
+
18
+ context 'on an existing table with an existing column' do
19
+ setup do
20
+ @table_name = Faker::Lorem.word
21
+ @index_name = Faker::Lorem.words.join('_')
22
+
23
+ ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS `#{@table_name}`;"
24
+ # can't use "id" because it's generated by Faker::Lorem.word :)
25
+ ActiveRecord::Base.connection.execute <<-SQL
26
+ CREATE TABLE `#{@table_name}` (
27
+ `primary` int(11) NOT NULL AUTO_INCREMENT,
28
+ PRIMARY KEY (`primary`)
29
+ );
30
+ SQL
31
+ end
32
+
33
+ teardown do
34
+ ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS `#{@table_name}`;"
35
+ end
36
+
37
+ context 'new columns' do
38
+ setup do
39
+ @new_column_name = Faker::Lorem.word
40
+ @arguments = [
41
+ nil,
42
+ @migration,
43
+ @table_name,
44
+ @new_column_name,
45
+ {}
46
+ ]
47
+ end
48
+
49
+ # Rails' "magic" time, for Time columns
50
+ # https://github.com/rails/rails/blob/fcf9b712b1dbbcb8f48644e6f20676ad9480ba66/activerecord/lib/active_record/type/time.rb#L16
51
+ base_date = Time.utc(2000, 1, 1, 0, 0, 0)
52
+
53
+ datetime_value = Time.at(Time.now.to_i).utc # Date types we're testing don't have sub-second precision
54
+ date_value = Time.utc(datetime_value.year, datetime_value.month, datetime_value.day)
55
+ rails_time_value = Time.utc(base_date.year, base_date.month, base_date.day, datetime_value.hour, datetime_value.min, datetime_value.sec)
56
+
57
+ [
58
+ { type: :integer, default: 42 },
59
+ { type: :string, default: ["'foobar'", ':bazqux'], expected_default: ['foobar', 'bazqux'] },
60
+ { type: :text, default: nil }, # TEXT columns cannot have a default http://dev.mysql.com/doc/refman/5.7/en/blob.html#idm140380410069472
61
+ { type: :float, default: 3.14159 },
62
+ { type: :datetime, default: "'#{datetime_value.strftime('%F %T')}'", expected_default: datetime_value },
63
+ { type: :time, default: "'#{datetime_value.strftime('%T')}'", expected_default: rails_time_value },
64
+ { type: :date, default: "'#{datetime_value.strftime('%F')}'", expected_default: date_value },
65
+ { type: :binary, default: nil }, # BLOB columns cannot have a default http://dev.mysql.com/doc/refman/5.7/en/blob.html#idm140380410069472
66
+ { type: :boolean, default: [false, true] },
67
+ ].each do |test|
68
+
69
+ defaults = [test[:default]].flatten(1).compact # remove nils for columns that can't have a default
70
+ expected_defaults = test[:expected_default] ? [test[:expected_default]].flatten(1) : defaults
71
+
72
+ should "add a nullable #{test[:type]} column with default null" do
73
+ column_name = "#{@new_column_name}_#{test[:type]}"
74
+ @arguments[0] = "add_column :#{@table_name}, :#{column_name}, :#{test[:type]}, default: nil, null: true"
75
+ @arguments[-2] = column_name
76
+ @arguments[-1] = { type: test[:type], null: true, default: nil }
77
+ migrate_and_test_field *@arguments
78
+ end
79
+
80
+ defaults.each_with_index do |default, index|
81
+ should "add a nullable #{test[:type]} column with default value #{default}" do
82
+ column_name = "#{@new_column_name}_#{test[:type]}"
83
+ @arguments[0] = "add_column :#{@table_name}, :#{column_name}, :#{test[:type]}, default: #{default}, null: true"
84
+ @arguments[-2] = column_name
85
+ @arguments[-1] = { type: test[:type], null: true, default: expected_defaults[index] }
86
+ migrate_and_test_field *@arguments
87
+ end
88
+
89
+ should "add a not-nullable #{test[:type]} column with default value #{default}" do
90
+ column_name = "#{@new_column_name}_#{test[:type]}"
91
+ @arguments[0] = "add_column :#{@table_name}, :#{column_name}, :#{test[:type]}, default: #{default}, null: false"
92
+ @arguments[-2] = column_name
93
+ @arguments[-1] = { type: test[:type], null: false, default: expected_defaults[index] }
94
+ migrate_and_test_field *@arguments
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -18,6 +18,29 @@ def test_spec(key = 'test')
18
18
  test_spec
19
19
  end
20
20
 
21
+ def migrate_and_test_field(command, migration, table_name, field_name, assertions = {})
22
+ migration.class.class_eval "def change; #{command}; end"
23
+
24
+ migration.stubs(:write)
25
+ migration.stubs(:announce)
26
+ migration.migrate(:up)
27
+ migration.unstub(:write, :announce)
28
+
29
+ field = ActiveRecord::Base.connection.columns(table_name).find { |f| f.name == field_name }
30
+ if assertions.delete(:exists) == false
31
+ assert_nil field
32
+ else
33
+ assert_not_nil field
34
+ assert_equal field_name, field.name
35
+ assertions.each do |test, expected|
36
+ actual = field.send(test)
37
+ assert_equal expected, actual, "Expected #{command} to produce a field of #{test} #{expected}, but it was #{actual}"
38
+ end
39
+ end
40
+
41
+ migration.class.send(:remove_method, :change)
42
+ end
43
+
21
44
  # SQLCounter is part of ActiveRecord but is not distributed with the gem (used for internal tests only)
22
45
  # see https://github.com/rails/rails/blob/3-2-stable/activerecord/test/cases/helper.rb#L59
23
46
  module ActiveRecord
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+ set -ox
3
+
4
+ VERSION=$(lsb_release -c -s)
5
+ sudo apt-key adv --keyserver keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A
6
+
7
+ # Add the right repository
8
+ case $PT_OSC_VERSION in
9
+ DEVELOPMENT) echo "deb http://repo.percona.com/apt $VERSION main testing" | sudo tee -a /etc/apt/sources.list; ;;
10
+ *) echo "deb http://repo.percona.com/apt $VERSION main" | sudo tee -a /etc/apt/sources.list; ;;
11
+ esac
12
+
13
+ # Update
14
+ sudo apt-get update -qq
15
+
16
+ # Install the right version
17
+ case $PT_OSC_VERSION in
18
+ LATEST|DEVELOPMENT) sudo apt-get install -y percona-toolkit; ;;
19
+ 2.2.*)
20
+ wget http://www.percona.com/downloads/percona-toolkit/"$PT_OSC_VERSION"/deb/percona-toolkit_"$PT_OSC_VERSION"_all.deb
21
+ sudo dpkg -i percona-toolkit_"$PT_OSC_VERSION"_all.deb
22
+ sudo apt-get install -f -y
23
+ ;;
24
+ *)
25
+ wget http://www.percona.com/downloads/percona-toolkit/"$PT_OSC_VERSION"/percona-toolkit_"$PT_OSC_VERSION"_all.deb
26
+ sudo dpkg -i percona-toolkit_"$PT_OSC_VERSION"_all.deb
27
+ sudo apt-get install -f -y
28
+ ;;
29
+ esac
30
+
@@ -6,10 +6,13 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
6
6
  @migration = ActiveRecord::PtOscMigration.new
7
7
  @tool_version = states('tool_version').starts_as('100')
8
8
  ActiveRecord::PtOscMigration.stubs(:tool_version).returns(Gem::Version.new('100')).when(@tool_version.is('100'))
9
+ @migration.stubs(:write)
10
+ @migration.stubs(:announce)
9
11
  end
10
12
 
11
13
  teardown do
12
14
  ActiveRecord::PtOscMigration.unstub(:tool_version)
15
+ @migration.unstub(:write, :announce)
13
16
  end
14
17
 
15
18
  context '#percona_command' do
@@ -53,7 +56,7 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
53
56
 
54
57
  should 'set missing flags to default values' do
55
58
  flags_with_defaults = ActiveRecord::PtOscMigration.percona_flags.select do |flag, config|
56
- config.key?(:default) && flag != 'execute' && !config[:boolean]
59
+ !config[:default].nil? && flag != 'execute' && !config[:boolean]
57
60
  end
58
61
 
59
62
  command = @migration.send(:percona_command, '', '', '')
@@ -158,6 +161,98 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
158
161
  @migration.send(:percona_command, '', '', '', @options)
159
162
  end
160
163
  end
164
+
165
+ context 'user flag' do
166
+ context 'with normal database username specified' do
167
+ setup do
168
+ @migration.stubs(:raw_database_config).returns(username: 'foobar')
169
+ end
170
+
171
+ should 'add the user to the command' do
172
+ command = @migration.send(:percona_command, '', '', '')
173
+ assert command.include?('--user foobar'), "command #{command} did not include user"
174
+ end
175
+ end
176
+
177
+ context 'with user specified in percona config' do
178
+ setup do
179
+ @migration.stubs(:raw_database_config).returns(percona: { user: 'foobar' })
180
+ end
181
+
182
+ should 'add the user to the command' do
183
+ command = @migration.send(:percona_command, '', '', '')
184
+ assert command.include?('--user foobar'), "command #{command} did not include user"
185
+ end
186
+ end
187
+
188
+ context 'with user specified in database and percona config' do
189
+ setup do
190
+ @migration.stubs(:raw_database_config).returns(username: 'foobar', percona: { user: 'bazqux' })
191
+ end
192
+
193
+ should 'add the percona user to the command' do
194
+ command = @migration.send(:percona_command, '', '', '')
195
+ assert command.include?('--user bazqux'), "command #{command} did not include the right user"
196
+ end
197
+ end
198
+
199
+ context 'with user forbidden in percona config' do
200
+ setup do
201
+ @migration.stubs(:raw_database_config).returns(username: 'foobar', percona: { user: false })
202
+ end
203
+
204
+ should 'not add a user to the command' do
205
+ command = @migration.send(:percona_command, '', '', '')
206
+ assert !command.include?('--user foobar'), "command #{command} included user but should not have"
207
+ end
208
+ end
209
+ end
210
+
211
+ context 'password flag' do
212
+ context 'with normal database password specified' do
213
+ setup do
214
+ @migration.stubs(:raw_database_config).returns(password: 'foobar')
215
+ end
216
+
217
+ should 'add the password to the command' do
218
+ command = @migration.send(:percona_command, '', '', '')
219
+ assert command.include?('--password foobar'), "command #{command} did not include password"
220
+ end
221
+ end
222
+
223
+ context 'with password specified in percona config' do
224
+ setup do
225
+ @migration.stubs(:raw_database_config).returns(percona: { password: 'foobar' })
226
+ end
227
+
228
+ should 'add the password to the command' do
229
+ command = @migration.send(:percona_command, '', '', '')
230
+ assert command.include?('--password foobar'), "command #{command} did not include password"
231
+ end
232
+ end
233
+
234
+ context 'with password specified in database and percona config' do
235
+ setup do
236
+ @migration.stubs(:raw_database_config).returns(password: 'foobar', percona: { password: 'bazqux' })
237
+ end
238
+
239
+ should 'add the percona password to the command' do
240
+ command = @migration.send(:percona_command, '', '', '')
241
+ assert command.include?('--password bazqux'), "command #{command} did not include the right password"
242
+ end
243
+ end
244
+
245
+ context 'with password forbidden in percona config' do
246
+ setup do
247
+ @migration.stubs(:raw_database_config).returns(password: 'foobar', percona: { password: false })
248
+ end
249
+
250
+ should 'not add a password to the command' do
251
+ command = @migration.send(:percona_command, '', '', '')
252
+ assert !command.include?('--password foobar'), "command #{command} included password but should not have"
253
+ end
254
+ end
255
+ end
161
256
  end
162
257
 
163
258
  context 'connected to a pt-osc database in print mode' do
@@ -249,6 +344,17 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
249
344
  end
250
345
  end
251
346
 
347
+ context '#sanitize_command' do
348
+ should 'strip password out of commands' do
349
+ assert_equal 'pt-online-schema-change --password _hidden_', @migration.class.send(:sanitize_command, 'pt-online-schema-change --password supersecret')
350
+ assert_equal 'pt-online-schema-change --password _hidden_', @migration.class.send(:sanitize_command, 'pt-online-schema-change --password %&$^*(!(#)CD&`+')
351
+ assert_equal 'pt-online-schema-change --password _hidden_', @migration.class.send(:sanitize_command, 'pt-online-schema-change --password "who uses spaces in passwords?"')
352
+ assert_equal 'pt-online-schema-change --other --password _hidden_ --fields', @migration.class.send(:sanitize_command, 'pt-online-schema-change --other --password supersecret --fields')
353
+ assert_equal 'pt-online-schema-change --other --fields --password _hidden_', @migration.class.send(:sanitize_command, 'pt-online-schema-change --other --fields --password supersecret')
354
+ assert_equal 'pt-online-schema-change --password', @migration.class.send(:sanitize_command, 'pt-online-schema-change --password')
355
+ end
356
+ end
357
+
252
358
  context '#make_path_absolute' do
253
359
  context 'with an absolute path' do
254
360
  setup do
@@ -289,6 +395,28 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
289
395
  end
290
396
  end
291
397
 
398
+ context '#get_from_config' do
399
+ setup do
400
+ @migration.stubs(:raw_database_config).returns(flag: 'foobar', other_flag: 'mooshu')
401
+ end
402
+
403
+ should 'return nil if false is specified' do
404
+ assert_nil @migration.send(:get_from_config, false)
405
+ end
406
+
407
+ should 'get the value from the config if it was not specified' do
408
+ assert_equal 'foobar', @migration.send(:get_from_config, nil, flag_name: 'flag')
409
+ end
410
+
411
+ should 'prefer the key name if given explicitly' do
412
+ assert_equal 'mooshu', @migration.send(:get_from_config, nil, flag_name: 'flag', key_name: 'other_flag')
413
+ end
414
+
415
+ should 'pass string values through' do
416
+ assert_equal 'bazqux', @migration.send(:get_from_config, 'bazqux')
417
+ end
418
+ end
419
+
292
420
  context '#execute_pt_osc' do
293
421
  context 'with a pt-osc connection' do
294
422
  setup do
@@ -306,7 +434,7 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
306
434
  context 'connected to a database' do
307
435
  setup do
308
436
  @database_name = Faker::Lorem.word
309
- @migration.stubs(:database_config).returns(database: @database_name)
437
+ @migration.stubs(:raw_database_config).returns(database: @database_name)
310
438
  end
311
439
 
312
440
  teardown do
@@ -424,9 +552,9 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
424
552
  end
425
553
 
426
554
  should 'log the command' do
427
- @migration.expects(:percona_command).returns('<<percona command>>')
555
+ @migration.expects(:percona_command).returns('percona command')
428
556
  @migration.send(:execute_sql_for_table, nil, nil, nil)
429
- assert '<<percona command>>'.in?(@dummy_log.string), 'Log entry did not contain percona command'
557
+ assert 'percona command'.in?(@dummy_log.string), 'Log entry did not contain percona command'
430
558
  end
431
559
 
432
560
  context 'with successful execution' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pt-osc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-01-15 00:00:00.000000000 Z
12
+ date: 2015-02-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -229,7 +229,9 @@ files:
229
229
  - test/dummy/script/rails
230
230
  - test/functional/mysql_pt_osc_adapter_test.rb
231
231
  - test/functional/pt_osc_migration_functional_test.rb
232
+ - test/integration/pt_osc_migration_integration_test.rb
232
233
  - test/test_helper.rb
234
+ - test/travisci/install_pt_osc.sh
233
235
  - test/unit/mysql_pt_osc_adapter_test.rb
234
236
  - test/unit/pt_osc_migration_unit_test.rb
235
237
  homepage: https://github.com/steverice/pt-osc
@@ -246,7 +248,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
246
248
  version: '0'
247
249
  segments:
248
250
  - 0
249
- hash: 2714793465659180068
251
+ hash: 999232518907264640
250
252
  required_rubygems_version: !ruby/object:Gem::Requirement
251
253
  none: false
252
254
  requirements:
@@ -255,7 +257,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
255
257
  version: '0'
256
258
  segments:
257
259
  - 0
258
- hash: 2714793465659180068
260
+ hash: 999232518907264640
259
261
  requirements: []
260
262
  rubyforge_project:
261
263
  rubygems_version: 1.8.23
@@ -301,6 +303,8 @@ test_files:
301
303
  - test/dummy/script/rails
302
304
  - test/functional/mysql_pt_osc_adapter_test.rb
303
305
  - test/functional/pt_osc_migration_functional_test.rb
306
+ - test/integration/pt_osc_migration_integration_test.rb
304
307
  - test/test_helper.rb
308
+ - test/travisci/install_pt_osc.sh
305
309
  - test/unit/mysql_pt_osc_adapter_test.rb
306
310
  - test/unit/pt_osc_migration_unit_test.rb