xmigra 1.5.1 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/xmigra/db_support/mssql.rb +43 -0
- data/lib/xmigra/db_support/psql.rb +45 -0
- data/lib/xmigra/declarative_migration.rb +160 -0
- data/lib/xmigra/declarative_support/table.rb +590 -0
- data/lib/xmigra/declarative_support.rb +158 -0
- data/lib/xmigra/impdecl_migration_adder.rb +249 -0
- data/lib/xmigra/migration.rb +10 -3
- data/lib/xmigra/migration_chain.rb +22 -8
- data/lib/xmigra/migration_conflict.rb +27 -0
- data/lib/xmigra/new_access_artifact_adder.rb +44 -0
- data/lib/xmigra/new_index_adder.rb +33 -0
- data/lib/xmigra/new_migration_adder.rb +10 -6
- data/lib/xmigra/permission_script_writer.rb +11 -5
- data/lib/xmigra/program.rb +231 -23
- data/lib/xmigra/schema_updater.rb +28 -5
- data/lib/xmigra/vcs_support/git.rb +189 -8
- data/lib/xmigra/vcs_support/svn.rb +107 -1
- data/lib/xmigra/version.rb +1 -1
- data/lib/xmigra.rb +47 -2
- data/test/git_vcs.rb +64 -4
- data/test/new_files.rb +14 -0
- data/test/runner.rb +49 -4
- data/test/structure_declarative.rb +811 -0
- data/test/utils.rb +17 -2
- metadata +10 -2
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'xmigra/console'
|
2
|
+
require 'xmigra/migration_chain'
|
2
3
|
|
3
4
|
module XMigra
|
4
5
|
module GitSpecifics
|
@@ -7,6 +8,7 @@ module XMigra
|
|
7
8
|
MASTER_HEAD_ATTRIBUTE = 'xmigra-master'
|
8
9
|
MASTER_BRANCH_SUBDIR = 'xmigra-master'
|
9
10
|
PRODUCTION_CHAIN_EXTENSION_COMMAND = 'xmigra-on-production-chain-extended'
|
11
|
+
ATTRIBUTE_UNSPECIFIED = 'unspecified'
|
10
12
|
|
11
13
|
class AttributesFile
|
12
14
|
def initialize(effect_root, access=:shared)
|
@@ -161,7 +163,16 @@ module XMigra
|
|
161
163
|
end
|
162
164
|
|
163
165
|
def git(*args)
|
164
|
-
|
166
|
+
_path = begin
|
167
|
+
self.path
|
168
|
+
rescue NameError
|
169
|
+
begin
|
170
|
+
self.schema_dir
|
171
|
+
rescue NameError
|
172
|
+
Pathname(self.file_path).dirname
|
173
|
+
end
|
174
|
+
end
|
175
|
+
Dir.chdir(_path) do |pwd|
|
165
176
|
GitSpecifics.run_git(*args)
|
166
177
|
end
|
167
178
|
end
|
@@ -211,7 +222,13 @@ module XMigra
|
|
211
222
|
end
|
212
223
|
|
213
224
|
def branch_identifier
|
214
|
-
|
225
|
+
for_production = begin
|
226
|
+
self.production
|
227
|
+
rescue NameError
|
228
|
+
false
|
229
|
+
end
|
230
|
+
|
231
|
+
return (if for_production
|
215
232
|
self.git_branch_info[0]
|
216
233
|
else
|
217
234
|
return @git_branch_identifier if defined? @git_branch_identifier
|
@@ -227,9 +244,25 @@ module XMigra
|
|
227
244
|
if commit
|
228
245
|
self.git_fetch_master_branch
|
229
246
|
|
230
|
-
# If there are
|
231
|
-
# *commit* is production-ish
|
232
|
-
|
247
|
+
# If there are commits between the master head and *commit*, then
|
248
|
+
# *commit* is not production-ish
|
249
|
+
if self.git_commits_in? self.git_master_local_branch..commit
|
250
|
+
return :development
|
251
|
+
end
|
252
|
+
|
253
|
+
# Otherwise, look to see if all migrations in the migration chain for
|
254
|
+
# commit are in the master head with no diffs -- the migration chain
|
255
|
+
# is a "prefix" of the chain in the master head:
|
256
|
+
migration_chain = RepoStoredMigrationChain.new(
|
257
|
+
commit,
|
258
|
+
Pathname(path).join(SchemaManipulator::STRUCTURE_SUBDIR),
|
259
|
+
)
|
260
|
+
return :production if self.git(
|
261
|
+
:diff, '--name-only',
|
262
|
+
self.git_master_local_branch, commit, '--',
|
263
|
+
*migration_chain.map(&:file_path)
|
264
|
+
).empty?
|
265
|
+
return :development
|
233
266
|
end
|
234
267
|
|
235
268
|
return nil unless self.git_master_head(:required=>false)
|
@@ -253,6 +286,15 @@ module XMigra
|
|
253
286
|
return nil
|
254
287
|
end
|
255
288
|
|
289
|
+
def vcs_contents(path, options={})
|
290
|
+
args = []
|
291
|
+
|
292
|
+
commit = options.fetch(:revision, 'HEAD')
|
293
|
+
args << "#{commit}:#{path}"
|
294
|
+
|
295
|
+
git(:show, *args)
|
296
|
+
end
|
297
|
+
|
256
298
|
def vcs_prod_chain_extension_handler
|
257
299
|
attr_val = GitSpecifics.attr_values(
|
258
300
|
PRODUCTION_CHAIN_EXTENSION_COMMAND,
|
@@ -273,6 +315,124 @@ module XMigra
|
|
273
315
|
return attr_val
|
274
316
|
end
|
275
317
|
|
318
|
+
def vcs_uncommitted?
|
319
|
+
git_status == '??'
|
320
|
+
end
|
321
|
+
|
322
|
+
class VersionComparator
|
323
|
+
# vcs_object.kind_of?(GitSpecifics)
|
324
|
+
def initialize(vcs_object, options={})
|
325
|
+
@object = vcs_object
|
326
|
+
@expected_content_method = options[:expected_content_method]
|
327
|
+
@path_statuses = Hash.new do |h, file_path|
|
328
|
+
file_path = Pathname(file_path).expand_path
|
329
|
+
next h[file_path] if h.has_key?(file_path)
|
330
|
+
h[file_path] = @object.git_retrieve_status(file_path)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def relative_version(file_path)
|
335
|
+
# Comparing @object.file_path (a) to file_path (b)
|
336
|
+
#
|
337
|
+
# returns: :newer, :equal, :older, or :missing
|
338
|
+
|
339
|
+
b_status = @path_statuses[file_path]
|
340
|
+
|
341
|
+
return :missing if b_status.nil? || b_status.include?('D')
|
342
|
+
|
343
|
+
a_status = @path_statuses[@object.file_path]
|
344
|
+
|
345
|
+
if a_status == '??' || a_status[0] == 'A'
|
346
|
+
if b_status == '??' || b_status[0] == 'A' || b_status.include?('M')
|
347
|
+
return relative_version_by_content(file_path)
|
348
|
+
end
|
349
|
+
|
350
|
+
return :older
|
351
|
+
elsif a_status == ' '
|
352
|
+
return :newer unless b_status == ' '
|
353
|
+
|
354
|
+
return begin
|
355
|
+
a_commit = latest_commit(@object.file_path)
|
356
|
+
b_commit = latest_commit(file_path)
|
357
|
+
|
358
|
+
if @object.git_commits_in? a_commit..b_commit, file_path
|
359
|
+
:newer
|
360
|
+
elsif @object.git_commits_in? b_commit..a_commit, @object.file_path
|
361
|
+
:older
|
362
|
+
else
|
363
|
+
:equal
|
364
|
+
end
|
365
|
+
end
|
366
|
+
elsif b_status == ' '
|
367
|
+
return :older
|
368
|
+
else
|
369
|
+
return relative_version_by_content(file_path)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def latest_commit(file_path)
|
374
|
+
@object.git(
|
375
|
+
:log,
|
376
|
+
'--pretty=format:%H',
|
377
|
+
'-1',
|
378
|
+
'--',
|
379
|
+
file_path
|
380
|
+
)
|
381
|
+
end
|
382
|
+
|
383
|
+
def relative_version_by_content(file_path)
|
384
|
+
ec_method = @expected_content_method
|
385
|
+
if !ec_method || @object.send(ec_method, file_path)
|
386
|
+
return :equal
|
387
|
+
else
|
388
|
+
return :newer
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def vcs_comparator(options={})
|
394
|
+
VersionComparator.new(self, options)
|
395
|
+
end
|
396
|
+
|
397
|
+
def vcs_latest_revision(a_file=nil)
|
398
|
+
if a_file.nil? && defined? @vcs_latest_revision
|
399
|
+
return @vcs_latest_revision
|
400
|
+
end
|
401
|
+
|
402
|
+
git(
|
403
|
+
:log,
|
404
|
+
'-n1',
|
405
|
+
'--pretty=format:%H',
|
406
|
+
'--',
|
407
|
+
a_file || file_path,
|
408
|
+
:quiet=>true
|
409
|
+
).chomp.tap do |val|
|
410
|
+
@vcs_latest_revision = val if a_file.nil?
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
def vcs_changes_from(from_commit, file_path)
|
415
|
+
git(:diff, from_commit, '--', file_path)
|
416
|
+
end
|
417
|
+
|
418
|
+
def vcs_most_recent_committed_contents(file_path)
|
419
|
+
git(:show, "HEAD:#{file_path}", :quiet=>true)
|
420
|
+
end
|
421
|
+
|
422
|
+
def git_status
|
423
|
+
@git_status ||= git_retrieve_status(file_path)
|
424
|
+
end
|
425
|
+
|
426
|
+
def git_retrieve_status(a_path)
|
427
|
+
return nil unless Pathname(a_path).exist?
|
428
|
+
|
429
|
+
if git('status', '--porcelain', a_path.to_s) =~ /^.+?(?= \S)/
|
430
|
+
$&
|
431
|
+
else
|
432
|
+
' '
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
276
436
|
def production_pattern
|
277
437
|
".+"
|
278
438
|
end
|
@@ -333,17 +493,17 @@ module XMigra
|
|
333
493
|
:required=>options[:required]
|
334
494
|
)
|
335
495
|
return nil if master_head.nil?
|
336
|
-
return @git_master_head = master_head
|
496
|
+
return @git_master_head = (master_head if master_head != GitSpecifics::ATTRIBUTE_UNSPECIFIED)
|
337
497
|
end
|
338
498
|
|
339
499
|
def git_branch
|
340
500
|
return @git_branch if defined? @git_branch
|
341
|
-
return @git_branch = git('rev-parse', %w{--abbrev-ref HEAD}).chomp
|
501
|
+
return @git_branch = git('rev-parse', %w{--abbrev-ref HEAD}, :quiet=>true).chomp
|
342
502
|
end
|
343
503
|
|
344
504
|
def git_schema_commit
|
345
505
|
return @git_commit if defined? @git_commit
|
346
|
-
reported_commit = git(:log, %w{-n1 --format=%H --}, self.path).chomp
|
506
|
+
reported_commit = git(:log, %w{-n1 --format=%H --}, self.path, :quiet=>true).chomp
|
347
507
|
raise VersionControlError, "Schema not committed" if reported_commit.empty?
|
348
508
|
return @git_commit = reported_commit
|
349
509
|
end
|
@@ -412,5 +572,26 @@ module XMigra
|
|
412
572
|
path || self.path
|
413
573
|
) != ''
|
414
574
|
end
|
575
|
+
|
576
|
+
class RepoStoredMigrationChain < MigrationChain
|
577
|
+
def initialize(branch, path, options={})
|
578
|
+
@branch = branch
|
579
|
+
options[:vcs_specifics] = GitSpecifics
|
580
|
+
super(path, options)
|
581
|
+
end
|
582
|
+
|
583
|
+
protected
|
584
|
+
def yaml_of_file(fpath)
|
585
|
+
fdir, fname = Pathname(fpath).split
|
586
|
+
file_contents = Dir.chdir(fdir) do |pwd|
|
587
|
+
GitSpecifics.run_git(:show, "#{@branch}:./#{fname}")
|
588
|
+
end
|
589
|
+
begin
|
590
|
+
YAML.load(file_contents, fpath.to_s)
|
591
|
+
rescue
|
592
|
+
raise XMigra::Error, "Error loading/parsing #{fpath}"
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
415
596
|
end
|
416
597
|
end
|
@@ -31,7 +31,7 @@ module XMigra
|
|
31
31
|
)
|
32
32
|
cmd_str = cmd_parts.join(' ')
|
33
33
|
|
34
|
-
output = `#{cmd_str}`
|
34
|
+
output = `#{cmd_str} 2>/dev/null`
|
35
35
|
raise(VersionControlError, "Subversion command failed with exit code #{$?.exitstatus}") unless $?.success?
|
36
36
|
return output if raw_result && !no_result
|
37
37
|
return REXML::Document.new(output) unless no_result
|
@@ -222,6 +222,75 @@ END_OF_MESSAGE
|
|
222
222
|
subversion(:resolve, '--accept=working', path, :get_result=>false)
|
223
223
|
end
|
224
224
|
|
225
|
+
def vcs_uncommitted?
|
226
|
+
status = subversion_retrieve_status(file_path).elements['entry/wc-status']
|
227
|
+
status.nil? || status.attributes['item'] == 'unversioned'
|
228
|
+
end
|
229
|
+
|
230
|
+
class VersionComparator
|
231
|
+
# vcs_object.kind_of?(SubversionSpecifics)
|
232
|
+
def initialize(vcs_object, options={})
|
233
|
+
@object = vcs_object
|
234
|
+
@expected_content_method = options[:expected_content_method]
|
235
|
+
@path_status = Hash.new do |h, file_path|
|
236
|
+
file_path = Pathname(file_path).expand_path
|
237
|
+
next h[file_path] if h.has_key?(file_path)
|
238
|
+
h[file_path] = @object.subversion_retrieve_status(file_path)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def relative_version(file_path)
|
243
|
+
# Comparing @object.file_path (a) to file_path (b)
|
244
|
+
#
|
245
|
+
# returns: :newer, :equal, :older, or :missing
|
246
|
+
|
247
|
+
b_status = @path_status[file_path].elements['entry/wc-status']
|
248
|
+
|
249
|
+
return :missing if b_status.nil? || ['deleted', 'missing'].include?(b_status.attributes['item'])
|
250
|
+
|
251
|
+
a_status = @path_status[@object.file_path].elements['entry/wc-status']
|
252
|
+
|
253
|
+
if ['unversioned', 'added'].include? a_status.attributes['item']
|
254
|
+
if ['unversioned', 'added', 'modified'].include? b_status.attributes['item']
|
255
|
+
return relative_version_by_content(file_path)
|
256
|
+
end
|
257
|
+
|
258
|
+
return :older
|
259
|
+
elsif a_status.attributes['item'] == 'normal'
|
260
|
+
return :newer unless b_status.attributes['item'] == 'normal'
|
261
|
+
|
262
|
+
return begin
|
263
|
+
a_revision = a_status.elements['commit'].attributes['revision'].to_i
|
264
|
+
b_revision = b_status.elements['commit'].attributes['revision'].to_i
|
265
|
+
|
266
|
+
if a_revision < b_revision
|
267
|
+
:newer
|
268
|
+
elsif b_revision < a_revision
|
269
|
+
:older
|
270
|
+
else
|
271
|
+
:equal
|
272
|
+
end
|
273
|
+
end
|
274
|
+
elsif b_status.attributes['item'] == 'normal'
|
275
|
+
return :older
|
276
|
+
else
|
277
|
+
return relative_version_by_content(file_path)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def relative_version_by_content(file_path)
|
282
|
+
ec_method = @expected_content_method
|
283
|
+
if !ec_method || @object.send(ec_method, file_path)
|
284
|
+
return :equal
|
285
|
+
else
|
286
|
+
return :newer
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def vcs_comparator(options={})
|
292
|
+
VersionComparator.new(self, options)
|
293
|
+
end
|
225
294
|
|
226
295
|
def vcs_move(old_path, new_path)
|
227
296
|
subversion(:move, old_path, new_path, :get_result=>false)
|
@@ -264,6 +333,43 @@ END_OF_MESSAGE
|
|
264
333
|
subversion(:cat, "-r#{tracer.earliest_loaded_revision}", path.to_s)
|
265
334
|
end
|
266
335
|
end
|
336
|
+
|
337
|
+
def vcs_contents(path, options={})
|
338
|
+
args = []
|
339
|
+
|
340
|
+
if options[:revision]
|
341
|
+
args << "-r#{options[:revision]}"
|
342
|
+
end
|
343
|
+
|
344
|
+
args << path.to_s
|
345
|
+
|
346
|
+
subversion(:cat, *args)
|
347
|
+
end
|
348
|
+
|
349
|
+
def vcs_latest_revision(a_file=nil)
|
350
|
+
if a_file.nil? && defined? @vcs_latest_revision
|
351
|
+
return @vcs_latest_revision
|
352
|
+
end
|
353
|
+
|
354
|
+
val = subversion(:status, '-v', a_file || file_path).elements[
|
355
|
+
'string(status/target/entry/wc-status/commit/@revision)'
|
356
|
+
]
|
357
|
+
(val.nil? ? val : val.to_i).tap do |val|
|
358
|
+
@vcs_latest_revision = val if a_file.nil?
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def vcs_changes_from(from_revision, file_path)
|
363
|
+
subversion(:diff, '-r', from_revision, file_path, :raw=>true)
|
364
|
+
end
|
365
|
+
|
366
|
+
def vcs_most_recent_committed_contents(file_path)
|
367
|
+
subversion(:cat, file_path)
|
368
|
+
end
|
369
|
+
|
370
|
+
def subversion_retrieve_status(file_path)
|
371
|
+
subversion(:status, '-v', file_path).elements['status/target']
|
372
|
+
end
|
267
373
|
end
|
268
374
|
|
269
375
|
class SvnHistoryTracer
|
data/lib/xmigra/version.rb
CHANGED
data/lib/xmigra.rb
CHANGED
@@ -251,8 +251,47 @@ module XMigra
|
|
251
251
|
end
|
252
252
|
end
|
253
253
|
|
254
|
-
def secure_digest(s)
|
255
|
-
|
254
|
+
def secure_digest(s, options={:encoding=>:base64})
|
255
|
+
digest_value = Digest::MD5.digest(s)
|
256
|
+
case options[:encoding]
|
257
|
+
when nil
|
258
|
+
digest_value
|
259
|
+
when :base64
|
260
|
+
[digest_value].pack('m0').chomp
|
261
|
+
when :base32
|
262
|
+
base32encoding(digest_value)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
BASE_32_ENCODING = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
267
|
+
def base32encoding(bytes)
|
268
|
+
carry = 0
|
269
|
+
carry_bits = 0
|
270
|
+
''.tap do |result|
|
271
|
+
bytes.each_byte do |b|
|
272
|
+
# From b we need the (5 - carry_bits) most significant bits
|
273
|
+
needed_bits = 5 - carry_bits
|
274
|
+
code_unit = (carry << needed_bits) | b >> (8 - needed_bits)
|
275
|
+
result << BASE_32_ENCODING[code_unit]
|
276
|
+
|
277
|
+
if needed_bits <= 3
|
278
|
+
# Extra character out of this byte
|
279
|
+
code_unit = (b >> (3 - needed_bits)) & 0x1F
|
280
|
+
result << BASE_32_ENCODING[code_unit]
|
281
|
+
carry_bits = (3 - needed_bits)
|
282
|
+
else
|
283
|
+
carry_bits = 8 - needed_bits
|
284
|
+
end
|
285
|
+
carry = b & ((1 << carry_bits) - 1)
|
286
|
+
end
|
287
|
+
|
288
|
+
if carry_bits > 0
|
289
|
+
code_unit = carry << (5 - carry_bits)
|
290
|
+
result << BASE_32_ENCODING[code_unit]
|
291
|
+
end
|
292
|
+
|
293
|
+
result << '=' * (7 - ((result.length + 7) % 8))
|
294
|
+
end
|
256
295
|
end
|
257
296
|
end
|
258
297
|
|
@@ -300,6 +339,9 @@ module XMigra
|
|
300
339
|
ARGV,
|
301
340
|
:error=>proc do |e|
|
302
341
|
STDERR.puts("#{e} (#{e.class})") unless e.is_a?(XMigra::Program::QuietError)
|
342
|
+
if e.class.const_defined? :COMMAND_LINE_HELP
|
343
|
+
STDERR.puts(XMigra.program_message(e.class::COMMAND_LINE_HELP))
|
344
|
+
end
|
303
345
|
exit(2) if e.is_a?(OptionParser::ParseError)
|
304
346
|
exit(2) if e.is_a?(XMigra::Program::ArgumentError)
|
305
347
|
exit(1)
|
@@ -322,6 +364,7 @@ require 'xmigra/function'
|
|
322
364
|
require 'xmigra/plugin'
|
323
365
|
|
324
366
|
require 'xmigra/access_artifact_collection'
|
367
|
+
require 'xmigra/impdecl_migration_adder'
|
325
368
|
require 'xmigra/index'
|
326
369
|
require 'xmigra/index_collection'
|
327
370
|
require 'xmigra/migration'
|
@@ -331,6 +374,8 @@ require 'xmigra/branch_upgrade'
|
|
331
374
|
require 'xmigra/schema_manipulator'
|
332
375
|
require 'xmigra/schema_updater'
|
333
376
|
require 'xmigra/new_migration_adder'
|
377
|
+
require 'xmigra/new_index_adder'
|
378
|
+
require 'xmigra/new_access_artifact_adder'
|
334
379
|
require 'xmigra/permission_script_writer'
|
335
380
|
require 'xmigra/source_tree_initializer'
|
336
381
|
|
data/test/git_vcs.rb
CHANGED
@@ -167,10 +167,6 @@ if GIT_PRESENT
|
|
167
167
|
|
168
168
|
`git clone "#{upstream.expand_path}" "#{repo}" 2>/dev/null`
|
169
169
|
|
170
|
-
Dir.chdir(upstream) do
|
171
|
-
commit_a_migration "foo table"
|
172
|
-
end
|
173
|
-
|
174
170
|
Dir.chdir(repo) do
|
175
171
|
commit_a_migration "bar table"
|
176
172
|
|
@@ -268,4 +264,68 @@ if GIT_PRESENT
|
|
268
264
|
end
|
269
265
|
end
|
270
266
|
end
|
267
|
+
|
268
|
+
run_test "XMigra does not put grants in upgrade by default" do
|
269
|
+
1.temp_dirs do |repo|
|
270
|
+
initialize_git_repo(repo)
|
271
|
+
|
272
|
+
Dir.chdir(repo) do
|
273
|
+
commit_a_migration "first table"
|
274
|
+
File.open(XMigra::SchemaManipulator::PERMISSIONS_FILE, 'w') do |grants_file|
|
275
|
+
YAML.dump(
|
276
|
+
{
|
277
|
+
"foo" => {
|
278
|
+
"alice" => "ALL",
|
279
|
+
"bob" => "SELECT",
|
280
|
+
"candace" => ["INSERT", "SELECT", "UPDATE"],
|
281
|
+
},
|
282
|
+
},
|
283
|
+
grants_file
|
284
|
+
)
|
285
|
+
end
|
286
|
+
|
287
|
+
XMigra::SchemaUpdater.new('.').tap do |tool|
|
288
|
+
sql = tool.update_sql
|
289
|
+
assert_not_include sql, /GRANT\s+ALL.*?alice/
|
290
|
+
assert_not_include sql, /GRANT\s+SELECT.*?bob/
|
291
|
+
assert_not_include sql, /GRANT\s+INSERT.*?candace/
|
292
|
+
assert_not_include sql, /GRANT\s+SELECT.*?candace/
|
293
|
+
assert_not_include sql, /GRANT\s+UPDATE.*?candace/
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
run_test "XMigra puts grants in upgrade when requested" do
|
300
|
+
1.temp_dirs do |repo|
|
301
|
+
initialize_git_repo(repo)
|
302
|
+
|
303
|
+
Dir.chdir(repo) do
|
304
|
+
commit_a_migration "first table"
|
305
|
+
File.open(XMigra::SchemaManipulator::PERMISSIONS_FILE, 'w') do |grants_file|
|
306
|
+
YAML.dump(
|
307
|
+
{
|
308
|
+
"foo" => {
|
309
|
+
"alice" => "ALL",
|
310
|
+
"bob" => "SELECT",
|
311
|
+
"candace" => ["INSERT", "SELECT", "UPDATE"],
|
312
|
+
},
|
313
|
+
},
|
314
|
+
grants_file
|
315
|
+
)
|
316
|
+
end
|
317
|
+
|
318
|
+
XMigra::SchemaUpdater.new('.').tap do |tool|
|
319
|
+
tool.include_grants = true
|
320
|
+
|
321
|
+
sql = tool.update_sql
|
322
|
+
assert_include sql, /GRANT\s+ALL.*?alice/
|
323
|
+
assert_include sql, /GRANT\s+SELECT.*?bob/
|
324
|
+
assert_include sql, /GRANT\s+INSERT.*?candace/
|
325
|
+
assert_include sql, /GRANT.*?SELECT.*?candace/
|
326
|
+
assert_include sql, /GRANT.*?UPDATE.*?candace/
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
271
331
|
end
|
data/test/new_files.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
XMigra::DatabaseSupportModules.each do |db_module|
|
3
|
+
["migration", "index", "view", "function", "procedure"].each do |file_type_flag|
|
4
|
+
run_test "#{db_module::SYSTEM_NAME} support for new #{file_type_flag} file" do
|
5
|
+
in_xmigra_schema(:db_info=>{'system'=>db_module::SYSTEM_NAME}) do
|
6
|
+
begin
|
7
|
+
XMigra::Program.run(["new", "--no-edit", "--#{file_type_flag}", "foobarbaz"])
|
8
|
+
rescue XMigra::NewAccessArtifactAdder::UnsupportedArtifactType
|
9
|
+
# This is an acceptable error
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/test/runner.rb
CHANGED
@@ -8,6 +8,8 @@ require 'tmpdir'
|
|
8
8
|
TESTS = %w[
|
9
9
|
git_vcs
|
10
10
|
reversions
|
11
|
+
new_files
|
12
|
+
structure_declarative
|
11
13
|
]
|
12
14
|
|
13
15
|
$:.unshift Pathname(__FILE__).expand_path.dirname.dirname + 'lib'
|
@@ -38,7 +40,13 @@ def run_test(name, &block)
|
|
38
40
|
|
39
41
|
$test_count += 1
|
40
42
|
|
43
|
+
msg_receiver, msg_sender = IO.pipe
|
44
|
+
|
41
45
|
if child_pid = Process.fork
|
46
|
+
msg_sender.close
|
47
|
+
|
48
|
+
# Must read to EOF or child may hang if pipe is filled
|
49
|
+
test_message = msg_receiver.read
|
42
50
|
Process.wait(child_pid)
|
43
51
|
|
44
52
|
if $?.success?
|
@@ -48,7 +56,14 @@ def run_test(name, &block)
|
|
48
56
|
print 'F'
|
49
57
|
$tests_failed << name
|
50
58
|
end
|
59
|
+
|
60
|
+
if test_message.length > 0
|
61
|
+
($test_messages ||= {})[name] = test_message
|
62
|
+
end
|
63
|
+
msg_receiver.close
|
51
64
|
else
|
65
|
+
msg_receiver.close
|
66
|
+
|
52
67
|
begin
|
53
68
|
prev_stdout = $stdout
|
54
69
|
$stdout = StringIO.new
|
@@ -59,11 +74,16 @@ def run_test(name, &block)
|
|
59
74
|
end
|
60
75
|
exit! 0
|
61
76
|
rescue AssertionFailure
|
77
|
+
msg_sender.puts $!
|
62
78
|
exit! 2
|
63
79
|
rescue
|
64
|
-
puts
|
65
|
-
puts
|
66
|
-
|
80
|
+
msg_sender.puts "#{$!.class}: #{$!}"
|
81
|
+
msg_sender.puts $!.backtrace
|
82
|
+
$!.each_causing_exception do |ex|
|
83
|
+
msg_sender.puts
|
84
|
+
msg_sender.puts "Caused by #{ex.class}: #{ex}"
|
85
|
+
msg_sender.puts ex.backtrace
|
86
|
+
end
|
67
87
|
exit! 1
|
68
88
|
end
|
69
89
|
end
|
@@ -96,8 +116,25 @@ def assert_neq(actual, expected)
|
|
96
116
|
assert(proc {"Value #{actual.inspect} was unexpected"}) {actual != expected}
|
97
117
|
end
|
98
118
|
|
119
|
+
def include_test(container, item)
|
120
|
+
case
|
121
|
+
when item.kind_of?(Regexp) && container.kind_of?(String)
|
122
|
+
item.match container
|
123
|
+
else
|
124
|
+
container.include? item
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
99
128
|
def assert_include(container, item)
|
100
|
-
assert(proc {"#{item.inspect} was not in #{container.inspect}"})
|
129
|
+
assert(proc {"#{item.inspect} was not in #{container.inspect}"}) do
|
130
|
+
include_test container, item
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def assert_not_include(container, item)
|
135
|
+
assert(proc {"#{item.inspect} was in #{container.inspect}"}) do
|
136
|
+
!include_test container, item
|
137
|
+
end
|
101
138
|
end
|
102
139
|
|
103
140
|
def assert_raises(expected_exception)
|
@@ -155,3 +192,11 @@ puts
|
|
155
192
|
puts "#{$test_successes}/#{$test_count} succeeded" if $test_options.show_counts
|
156
193
|
puts "Failed tests:" unless $tests_failed.empty?
|
157
194
|
$tests_failed.each {|name| puts " #{name}"}
|
195
|
+
|
196
|
+
($test_messages || {}).each_pair do |test_name, message|
|
197
|
+
puts
|
198
|
+
puts "----- #{test_name} -----"
|
199
|
+
message.each_line do |msg_line|
|
200
|
+
puts msg_line.chomp
|
201
|
+
end
|
202
|
+
end
|