pt-osc 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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