xmigra 1.5.1 → 1.6.0
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.
- 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
|