rally_workspace_utils 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest.txt ADDED
@@ -0,0 +1,17 @@
1
+ Manifest.txt
2
+ README.txt
3
+ Rakefile
4
+ setup.rb
5
+ bin/translate_workspace
6
+ bin/copy_workspace
7
+ lib/translate/copy.rb
8
+ lib/translate/version.rb
9
+ lib/translate/translate.rb
10
+ lib/translate/translation_audit.rb
11
+ lib/translate/copy/rest_object.rb
12
+ lib/translate/copy/story_copy.rb
13
+ lib/translate/copy/test_case_result_copy.rb
14
+ lib/translate/copy/defect_copy.rb
15
+ lib/translate/copy/object_copy.rb
16
+ lib/translate/copy/test_case_copy.rb
17
+ lib/rally_workspace_utils.rb
data/README.txt ADDED
@@ -0,0 +1,3 @@
1
+ README for rally_workspace_utils
2
+ ================================
3
+
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ require 'hoe'
11
+ include FileUtils
12
+ require File.join(File.dirname(__FILE__), 'lib', 'translate', 'version')
13
+
14
+ AUTHOR = "bcotton" # can also be an array of Authors
15
+ EMAIL = "bcotton@rallydev.com"
16
+ DESCRIPTION = "A utility to translate a UseCase workspace to a UserStory workspace"
17
+ GEM_NAME = "rally_workspace_utils" # what ppl will type to install your gem
18
+ RUBYFORGE_PROJECT = "rally-rest-api" # The unix name for your project
19
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
20
+ RELEASE_TYPES = %w( gem ) # can use: gem, tar, zip
21
+
22
+
23
+ NAME = "rally_workspace_utils"
24
+ REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
25
+ VERS = ENV['VERSION'] || (Translate::VERSION::STRING + (REV ? ".#{REV}" : ""))
26
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
27
+ RDOC_OPTS = ['--quiet', '--title', "translate documentation",
28
+ "--opname", "index.html",
29
+ "--line-numbers",
30
+ "--main", "README",
31
+ "--inline-source"]
32
+
33
+ # Generate all the Rake tasks
34
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
35
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
36
+ p.author = AUTHOR
37
+ p.description = DESCRIPTION
38
+ p.email = EMAIL
39
+ p.summary = DESCRIPTION
40
+ p.url = HOMEPATH
41
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
42
+ p.test_globs = ["test/**/*_test.rb"]
43
+ p.clean_globs = CLEAN #An array of file patterns to delete on clean.
44
+
45
+ p.extra_deps = []
46
+ p.extra_deps << ["rally_rest_api", ">= 0.6.2"]
47
+ p.extra_deps << ["activesupport"]
48
+
49
+
50
+ # == Optional
51
+ #p.changes - A description of the release's latest changes.
52
+ #p.extra_deps - An array of rubygem dependencies.
53
+ #p.spec_extras - A hash of extra values to set in the gemspec.
54
+ end
55
+
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + '/../lib'
4
+
5
+ require 'getoptlong'
6
+ require 'logger'
7
+ require 'rubygems'
8
+ require 'rally_rest_api'
9
+
10
+ require 'rally_workspace_utils'
11
+
12
+ opts = GetoptLong.new(
13
+ ["--base_url", "-b", GetoptLong::REQUIRED_ARGUMENT],
14
+ ["--debug", "-d", GetoptLong::NO_ARGUMENT],
15
+ ["--from_workspace", "-f", GetoptLong::REQUIRED_ARGUMENT],
16
+ ["--to_workspace", "-t", GetoptLong::REQUIRED_ARGUMENT],
17
+ ["--username", "-u", GetoptLong::REQUIRED_ARGUMENT],
18
+ ["--password", "-p", GetoptLong::REQUIRED_ARGUMENT],
19
+ ["--audit", "-a", GetoptLong::NO_ARGUMENT],
20
+ ["--logfile", "-l", GetoptLong::REQUIRED_ARGUMENT],
21
+ ["--help", "-h", GetoptLong::NO_ARGUMENT]
22
+ )
23
+
24
+ def usage
25
+ puts "usage: #{$0}"
26
+ puts " -b|--base_url <url> The base URL of the system to translate. Defaults to 'https://rally1.rallydev.com/slm'"
27
+ puts " -d|--debug Turn debugging on"
28
+ puts " -f|--from_workspace <workspace name> The name of the workspace we are translating. Must be a UseCase style workspace"
29
+ puts " -t|--to_workspace <workspace name> The name of the workspace we are tranlating to. Must be a UserStory style workspace"
30
+ puts " -a|--audit Compare the old workspace against the new one"
31
+ puts " -l|--logfile <filename> A file the logger should log to. Defaults to STDOUT"
32
+ puts " -u|--username <username> The rally login of a subscription administrator."
33
+ puts " -p|--password <password> The password of the above user."
34
+ puts " -h|--help This help"
35
+ exit
36
+ end
37
+
38
+ args = {:base_url => "https://rally1.rallydev.com/slm"}
39
+
40
+ debug = false
41
+ logfile = STDOUT
42
+
43
+ opts.each do |opt, arg|
44
+ case opt
45
+ when "--base_url"
46
+ args[:base_url] = arg
47
+ when "--from_workspace"
48
+ puts "arg = #{arg}"
49
+ args[:from_workspace] = arg
50
+ when "--to_workspace"
51
+ args[:to_workspace] = arg
52
+ when "--username"
53
+ args[:username] = arg
54
+ when "--password"
55
+ args[:password] = arg
56
+ when "--debug"
57
+ debug = true
58
+ RestBuilder::DEBUG = true
59
+ when "--logfile"
60
+ logfile = arg
61
+ when "--help"
62
+ usage
63
+ else
64
+ puts "Unknown argument #{opt}"
65
+ usage
66
+ end
67
+ end
68
+
69
+
70
+
71
+ [:base_url, :to_workspace, :from_workspace, :username, :password].each do |arg|
72
+ unless args[arg]
73
+ puts "#{arg} is a required argument"
74
+ usage
75
+ end
76
+ end
77
+
78
+ logger = Logger.new(logfile)
79
+ if debug
80
+ logger.level = Logger::DEBUG
81
+ else
82
+ logger.level = Logger::INFO
83
+ end
84
+ args[:logger] = logger
85
+
86
+ translate = Translate.new(args)
87
+ puts translate.inspect
88
+ translate.run
89
+
90
+
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + '/translate/translate'
2
+
@@ -0,0 +1,75 @@
1
+ class DefectCopy < ObjectCopy
2
+
3
+ def shallow_copy(object, new_workspace)
4
+ values = super
5
+ values.delete(:submitted_by) unless user_exists? @object.submitted_by
6
+ values
7
+ end
8
+
9
+ def tangle(new_workspace)
10
+ values = {}
11
+ new_defect = fetch_object(:defect, @object.oid, new_workspace)
12
+
13
+ # If the original defect is in a different project from the stories' card,
14
+ # then create a new user story, as the parent of this new user story, and make
15
+ # the requirement on the defect this new story
16
+ if @object.requirement != nil && @object.requirement.cards != nil && # The defect has a requirement, that is scheduled
17
+ @object.project != nil && # The defect in not in the parent project
18
+ @object.requirement.cards.first.project != @object.project # The card in in a different project from the defect
19
+
20
+ story_name = @object.requirement.name
21
+ new_requirement = fetch_object(:artifact, @object.requirement.oid, new_workspace)
22
+ if new_requirement.parent && new_requirement.parent.project.nil?
23
+ parent_story = new_requirement.parent
24
+ else
25
+ parent_story = new_workspace.rally_rest.create(:hierarchical_requirement,
26
+ :workspace => new_workspace,
27
+ :name => "Parent story for UserStory '#{story_name}' to hold defects in project #{@object.project.name}")
28
+ @object.rally_rest.logger.info "During the tangle of defect '#{@object.name}' we needed to create a new UserStory and make it the parent of this defect's requirement. This is because the original Story was scheduled into a project that was diffetent than the project this defect is in."
29
+ end
30
+ new_requirement.update(:parent => parent_story)
31
+ values[:requirement] = parent_story
32
+ else
33
+
34
+ # if the old defect has a test case
35
+ unless @object.test_case.nil?
36
+ # get the new test_case
37
+ new_test_case = fetch_object(:test_case, @object.test_case.oid, new_workspace)
38
+ # update
39
+ values[:test_case] = new_test_case
40
+
41
+ # if the new defect is in the Parent Project and
42
+ # the its related test_case is not in the same project as the defect,
43
+ # then put the defect in the same project as the test_case
44
+ if new_defect.project.nil? && new_defect.project != new_test_case.project
45
+ values[:project] = new_test_case.project
46
+ end
47
+ end
48
+
49
+ # if the old defect has a requirement
50
+ if !@object.requirement.nil? && @object.requirement.type == "Story"
51
+ # get the requiement, copied from this defect's requirement
52
+ new_story = fetch_object(:artifact, @object.requirement.oid, new_workspace)
53
+ # update the new defect
54
+ values[:requirement] = new_story
55
+
56
+ # if the new defect is in the Parent Project and
57
+ # the new requirement is not in the same project as the defect,
58
+ # then put the defect in the same project as the requirement
59
+ if new_defect.project.nil? && new_defect.project != new_story.project
60
+ values[:project] = new_story.project
61
+ end
62
+ end
63
+ end
64
+
65
+ # tangle the duplicate defects
66
+ unless @object.duplicates.nil?
67
+ dups = []
68
+ @object.duplicates.values.each do |dup|
69
+ dups << fetch_object(:defect, dup.oid, new_workspace)
70
+ end
71
+ values[:duplicates] = dups
72
+ end
73
+ new_defect.update(values) unless values.length == 0
74
+ end
75
+ end
@@ -0,0 +1,97 @@
1
+ require 'pp'
2
+
3
+ class ObjectCopy
4
+ def initialize(object)
5
+ @object = object
6
+ end
7
+
8
+ def copy(new_workspace, additional_values = {})
9
+ values = shallow_copy(@object, new_workspace)
10
+ values.merge! additional_values
11
+
12
+ values[:workspace] = new_workspace
13
+ values[:project] = find_project_in_new_workspace(new_workspace)
14
+
15
+ @new_object = create_object(new_workspace.rally_rest, values)
16
+ remember @object, @new_object
17
+ copy_children(new_workspace)
18
+
19
+ @new_object
20
+ end
21
+
22
+ def remember old, new
23
+ $TRANSLATED ||= {}
24
+ $TRANSLATED[old.oid] = new.oid
25
+ end
26
+
27
+ def create_object(rally_rest, values)
28
+ rally_rest.create(@object.type_as_symbol, values)
29
+ end
30
+
31
+ def find_project_in_new_workspace(new_workspace)
32
+ # I'm assuming (after talking to nate) that if the project is nil, there there is more then one project in the
33
+ # workspace. At least for single project workspaces that were migrated for project scoping, this is true.
34
+ if @object.project.nil?
35
+ return nil
36
+ else
37
+ fetch_project(@object.project.name, new_workspace)
38
+ end
39
+ end
40
+
41
+ def fetch_project(project_name, workspace)
42
+ @@cached_projects ||= {}
43
+ return @@cached_projects[project_name] if @@cached_projects.key? project_name
44
+ project = workspace.rally_rest.find(:project, :workspace => workspace) { equal :name, project_name }.first
45
+ @@cached_projects[project_name] = project
46
+ end
47
+
48
+ def shallow_copy(object, new_workspace)
49
+ keys_to_exclude = excluded_attributes(object)
50
+ values = object.elements.reject { |key, value| keys_to_exclude.include? key }
51
+ values.delete(:owner) unless user_exists? @object.owner
52
+
53
+ # Here we need to fix any deleted custom dropdown values that have been deleted
54
+ values.each do |attribute, value|
55
+ if @object.typedef.custom_dropdown_attributes.key? attribute
56
+ attrdef = @object.typedef.custom_dropdown_attributes[attribute]
57
+ unless attrdef.allowed_values.include? value
58
+ values[attribute] = nil
59
+ puts "Deleteing #{attribute} = #{value} from @{object.name} because it no longer exists"
60
+ end
61
+ end
62
+ end
63
+
64
+ values
65
+ end
66
+
67
+ def excluded_attributes(object)
68
+ object.typedef.reference_attributes(true).keys
69
+ end
70
+
71
+ def copy_children(new_workspace)
72
+ end
73
+
74
+ def user_exists?(username)
75
+ @@cached_users ||= {}
76
+
77
+ return true if username.nil?
78
+ return @@cached_users[username] if @@cached_users.key? username
79
+
80
+ result = @object.rally_rest.find(:user) { equal :login_name, username }
81
+ if result.total_result_count > 0
82
+ @@cached_users[username] = true
83
+ else
84
+ @@cached_users[username] = false
85
+ end
86
+ end
87
+
88
+ def fetch_object(type, oid, workspace)
89
+ @@cached_objects ||= {}
90
+ new_oid = $TRANSLATED[oid]
91
+ return @@cached_objects[new_oid] if @@cached_objects.key? oid
92
+
93
+ object = @object.rally_rest.find(type, :workspace => workspace) { equal :object_i_d, new_oid }.first
94
+ @@cached_objects[new_oid] = object
95
+ end
96
+ end
97
+
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'rally_rest_api'
3
+
4
+ class RestObject
5
+ def copy(new_workspace, additional_values = {})
6
+ copy_class.new(self).copy(new_workspace, additional_values)
7
+ end
8
+
9
+ def tangle(new_workspace)
10
+ copy_class.new(self).tangle(new_workspace)
11
+ end
12
+
13
+ def excepted_attributes
14
+ [:object_id, :creation_date, :formatted_id, :owner] + self.typedef.reference_attributes(true).keys + self.typedef.custom_dropdown_attributes.keys
15
+ end
16
+
17
+ def copy_class
18
+ case self.type_as_symbol
19
+ when :card : CardCopy
20
+ when :story : StoryCopy
21
+ when :test_case : TestCaseCopy
22
+ when :defect : DefectCopy
23
+ when :test_case_result : TestCaseResultCopy
24
+ else
25
+ ObjectCopy
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,44 @@
1
+ class StoryCopy < ObjectCopy
2
+ def shallow_copy(object, new_workspace)
3
+ values = super
4
+
5
+ unless object.cards.nil?
6
+ values[:rank] = object.cards.first.rank
7
+ values[:schedule_state] = object.cards.first.state
8
+
9
+ unless object.cards.first.iteration.nil?
10
+ iteration_name = object.cards.first.iteration.name
11
+ iteration = fetch_object(:iteration, object.cards.first.iteration.oid, new_workspace)
12
+ values[:iteration] = iteration if iteration
13
+ end
14
+
15
+ unless object.cards.first.release.nil?
16
+ release_name = object.cards.first.release.name
17
+ release = fetch_object(:release, object.cards.first.release.oid, new_workspace)
18
+ values[:release] = release if release
19
+ end
20
+
21
+ end
22
+ values
23
+ end
24
+
25
+ def find_project_in_new_workspace(new_workspace)
26
+ if @object.cards.nil?
27
+ return super
28
+ elsif @object.cards.first.project.nil?
29
+ return super
30
+ else
31
+ fetch_project(@object.cards.first.project.name, new_workspace)
32
+ end
33
+ end
34
+
35
+ def create_object(rally_rest, values)
36
+ rally_rest.create(:hierarchical_requirement, values)
37
+ end
38
+
39
+ def copy_children(new_workspace)
40
+ @object.cards.first.tasks.values.flatten.each do |task|
41
+ task.copy(new_workspace, :work_product => @new_object)
42
+ end if !@object.cards.nil? && !@object.cards.first.tasks.nil?
43
+ end
44
+ end
@@ -0,0 +1,53 @@
1
+
2
+ class TestCaseCopy < ObjectCopy
3
+ def copy_children(new_workspace)
4
+ @object.results.each do |result|
5
+ result.copy(new_workspace, :test_case => @new_object)
6
+ end unless @object.results.nil?
7
+
8
+ @object.steps.each do |step|
9
+ step.copy(new_workspace, :test_case => @new_object)
10
+ end unless @object.steps.nil?
11
+ end
12
+
13
+ def tangle(new_workspace)
14
+
15
+ values = {}
16
+ if !@object.work_product.nil? && @object.work_product.type == "Story"
17
+ # if the old test case has a work product
18
+ new_test_case = fetch_object(:test_case, @object.oid, new_workspace)
19
+ # get the new test case copied from this testcase
20
+ new_work_product = fetch_object(:artifact, @object.work_product.oid, new_workspace)
21
+
22
+ values[:work_product] = new_work_product
23
+
24
+
25
+
26
+ # If the original test_case is in a different project from the stories' card,
27
+ # then create a new user story, as the parent of this new user story, and make
28
+ # the work_product on the test case the new parent
29
+ if @object.work_product != nil && @object.work_product.cards != nil && # The TC has a work_product, that is scheduled
30
+ @object.project != nil && # The TC in not in the parent project
31
+ @object.work_product.cards.first.project != @object.project # The card in in a different project from the test_case
32
+
33
+ story_name = @object.work_product.name
34
+ parent_story = new_workspace.rally_rest.create(:hierarchical_requirement,
35
+ :workspace => new_workspace,
36
+ :project => nil,
37
+ :name => "Parent story for #{story_name} to hold test cases in project #{@object.project.name}")
38
+ new_work_product.update(:parent => parent_story)
39
+ values[:work_product] = parent_story
40
+
41
+ # if the new test case is in the Parent Project and
42
+ # the new work_product is not in the same project as the test case,
43
+ # then put the test case in the same project as the work_product
44
+ elsif new_test_case.project.nil? && new_test_case.project != new_work_product.project
45
+ values[:project] = new_work_product.project
46
+ end
47
+
48
+
49
+ # update the new test case
50
+ new_test_case.update(values) unless values.length == 0
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,7 @@
1
+ class TestCaseResultCopy < ObjectCopy
2
+ def shallow_copy(object, new_workspace)
3
+ values = super
4
+ values.delete(:tester) unless user_exists? @object.tester
5
+ values
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'rally_rest_api'
3
+
4
+ require 'translate/copy/rest_object'
5
+ require 'translate/copy/object_copy'
6
+ require 'translate/copy/defect_copy'
7
+ require 'translate/copy/story_copy'
8
+ require 'translate/copy/test_case_copy'
9
+ require 'translate/copy/test_case_result_copy'
@@ -0,0 +1,69 @@
1
+ require File.dirname(__FILE__) + '/copy'
2
+ require File.dirname(__FILE__) + '/translation_audit'
3
+
4
+ class Translate
5
+ include TranslationAudit
6
+
7
+ def initialize(args)
8
+ @base_url = args[:base_url]
9
+ @username = args[:username]
10
+ @password = args[:password]
11
+ @from_workspace_name = args[:from_workspace]
12
+ @to_workspace_name = args[:to_workspace]
13
+ @logger = args[:logger] if args[:logger]
14
+ $TRANSLATED ||= {}
15
+ end
16
+
17
+ def run
18
+ @slm = RallyRestAPI.new(:base_url => @base_url, :username => @username, :password => @password, :logger => @logger)
19
+
20
+ @from_workspace = @slm.user.subscription.workspaces.values.flatten.find { |w| w.name == @from_workspace_name }
21
+ @to_workspace = @slm.user.subscription.workspaces.values.flatten.find { |w| w.name == @to_workspace_name }
22
+
23
+ raise "No such workspace #{@from_workspace_name}" unless @from_workspace
24
+ raise "No such workspace #{@to_workspace_name}" unless @to_workspace
25
+
26
+
27
+ if File::exists? "TRANSLATED"
28
+ File.open("TRANSLATED", "r") do |f|
29
+ $TRANSLATED = Marshal.load(f)
30
+ end
31
+
32
+ if $TRANSLATED[:from] != @from_workspace.oid && $TRANSLATED[:to] != @to_workspace.oid
33
+ $TRANSLATED = {}
34
+ $TRANSLATED[:from] = @from_workspace.oid
35
+ $TRANSLATED[:to] = @to_workspace.oid
36
+ end
37
+ end
38
+
39
+ # now do the copy
40
+ [:iteration, :release, :story, :defect, :test_case].each do |type|
41
+ @slm.find_all(type, :workspace => @from_workspace).each do |o|
42
+ @logger.info "Copying #{o.type} -- #{o.name}"
43
+ o.copy(@to_workspace)
44
+ end
45
+ end unless $TRANSLATED[:copy]
46
+ # mark these workspaces as copied
47
+ $TRANSLATED[:copy] = true
48
+ # dump the oids to disk
49
+ dump_oids
50
+
51
+ # Now tangle the objects
52
+ [:test_case, :defect].each do |type|
53
+ @slm.find_all(type, :workspace => @from_workspace).each do |o|
54
+ @logger.info "Tangle #{o.type} -- #{o.name}"
55
+ o.tangle(@to_workspace)
56
+ end
57
+ end unless $TRANSLATED[:tangle]
58
+
59
+ $TRANSLATED[:tangle] = true
60
+ dump_oids
61
+ end
62
+
63
+ def dump_oids
64
+ File.open("TRANSLATED", "w+") do |f|
65
+ Marshal.dump($TRANSLATED, f)
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,76 @@
1
+
2
+ module TranslationAudit
3
+ def audit
4
+ all_copied_fields_should_match_for :iteration
5
+ all_copied_fields_should_match_for :release
6
+ all_copied_fields_should_match_for :defect
7
+ all_copied_fields_should_match_for :defect, [:last_run]
8
+ all_copied_fields_should_match_for :test_case_result, [:test_case]
9
+ compare_each :test_case_result do |old, new|
10
+ if old.test_case.name != new.test_case.name
11
+ puts "#{old.type} -- #{old.name} is different"
12
+ end
13
+ end
14
+
15
+ all_copied_fields_should_match_for :test_case_step, [:test_case]
16
+ compare_each :test_case_step do |old, new|
17
+ if old.test_case.name != new.test_case.name
18
+ puts "#{old.type} -- #{old.name} is different"
19
+ end
20
+ end
21
+ all_copied_fields_should_match_for :task, [:work_product]
22
+ compare_each :task do |old, new|
23
+ if old.card.work_product.name != new.work_product.name
24
+ puts "#{old.type} -- #{old.name} is different"
25
+ end
26
+ end
27
+
28
+ compare_each :defect do |old, new|
29
+ if !old.requirement.nil? && old.requirement.type == "Story"
30
+ if new.requirement.children
31
+ old.requirement.name.should == new.requirement.children.values.first.name
32
+ else
33
+ old.requirement.name.should == new.requirement.name
34
+ end
35
+ end
36
+ end
37
+
38
+ compare_each :test_case do |old, new|
39
+ if !old.work_product.nil? && old.work_product.type == "Story"
40
+ if new.work_product.children
41
+ old.work_product.name.should == new.work_product.children.values.first.name
42
+ else
43
+ old.work_product.name.should == new.work_product.name
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+
50
+ def compare_each(type)
51
+ @slm.find_all(type, :workspace => @from_workspace).each do |old_object|
52
+ new_object = @slm.find(type, :workspace => @to_workspace) { equal :object_i_d, $TRANSLATED[old_object.oid] }.first
53
+ if new_object.nil?
54
+ puts "Cound not find an copy for object #{old_object.type} -- #{old_object.name} -- #{old_object.oid}"
55
+ next
56
+ end
57
+ yield old_object, new_object
58
+ end
59
+ end
60
+
61
+ def all_copied_fields_should_match_for(type, additional_exceptions = [])
62
+ compare_each(type) do |old_object, new_object|
63
+ excepted_attributes = old_object.excepted_attributes + additional_exceptions
64
+ old_hash = old_object.elements.reject { |k, v| excepted_attributes.include?(k) }
65
+ new_hash = {}
66
+ old_hash.each_key do |k|
67
+ new_hash[k] = new_object.elements[k]
68
+ end
69
+ diff = old_hash.diff(new_hash)
70
+ if diff.length > 0
71
+ puts "#{old_hash.inspect} shoud == #{new_hash.inspect}"
72
+ puts "all_copied_fields_should_match_for #{type} diff = #{diff.inspect}"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,9 @@
1
+ module Translate #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end