right_publish 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,282 @@
1
+ require 'right_publish/repo'
2
+ require 'spec_helper'
3
+
4
+ describe RightPublish::YumRepo do
5
+
6
+ def pkg_parts(path)
7
+ result = {:name=>nil, :version=>nil, :arch=>nil}
8
+ if /([A-Za-z\-+]+)-([0-9][A-Za-z0-9\-\.]*)\.(#{RightPublish::YumRepo::PKG_TYPES.join('|')})\.rpm\Z/.match(path)
9
+ result[:name] = $1
10
+ result[:version] = $2
11
+ result[:arch] = $3
12
+ end
13
+ result
14
+ end
15
+
16
+
17
+ before(:each) do
18
+ @repo_subdir = 'fake/'
19
+ @repo = flexmock(RightPublish::RepoManager.get_repository(:yum_repo)) do |obj|
20
+ obj.should_receive(:fetch)
21
+ obj.should_receive(:store)
22
+ obj.should_receive(:do_in_subdir).and_return {|dir,block| block.call}
23
+ obj.should_receive(:yum_prune)
24
+ obj.should_receive(:pkg_parts).and_return { |pkg| pkg_parts(pkg) }
25
+ end
26
+
27
+ flexmock(RightPublish::Profile) do |obj|
28
+ obj.should_receive(:log).and_return { |str,dbg| }
29
+ end
30
+
31
+ flexmock(File) { |obj| obj.should_receive(:file?).and_return(true) }
32
+ end
33
+
34
+ [false, true].each do |should_sign|
35
+ context "#publish#{" and sign" if should_sign}" do
36
+
37
+ # Test the EPEL (not flat) repo profile type
38
+ context "EPEL layout" do
39
+
40
+ def build_expectations(pkgs, expected_dists)
41
+ pkgs = Array(pkgs)
42
+
43
+ pkgs.each do |pkg|
44
+ pkg_arch = pkg_parts(pkg)[:arch]
45
+ dest_archs = case pkg_arch
46
+ when RightPublish::YumRepo::SRC_ALL_ARCH
47
+ [RightPublish::YumRepo::SRC_ALL_PATH]
48
+ when RightPublish::YumRepo::BIN_ALL_ARCH
49
+ RightPublish::YumRepo::ARCHITECTURES
50
+ else
51
+ [pkg_arch]
52
+ end
53
+
54
+ expected_dists.each_pair do |dist,releases|
55
+ releases.each do |release|
56
+ dest_archs.each do |arch|
57
+ dist_path = File.join(@repo_subdir, @epel_version.to_s, dist.to_s, release, arch)
58
+ @install_expectations[dist_path] ||= []
59
+ @install_expectations[dist_path] << pkg
60
+ end
61
+ end
62
+ end
63
+ end
64
+ @repo.should_receive(:install_file).and_return do |pkg,dir|
65
+ @install_expectations.should be_has_key(dir)
66
+ @install_expectations[dir].delete(pkg)
67
+ @install_expectations.delete(dir) if @install_expectations[dir].empty?
68
+ end
69
+ system_calls = @install_expectations.size
70
+ system_sign_calls = (@profile.config[:yum_repo][:gpg_key_id]) ? 1 : 0
71
+ @repo.should_receive(:system).times(system_calls).and_return(0)
72
+ @repo.should_receive(:shellout_with_password).times(system_sign_calls).and_return(0)
73
+ end
74
+
75
+ before(:each) do
76
+ @epel_dists = {:el=>['5','6'],:fc=>['12','13']}
77
+ @epel_version = 1
78
+ @install_expectations = {}
79
+ @profile = flexmock(RightPublish::Profile) do |obj|
80
+ obj.should_receive(:config).and_return {
81
+ { :verbose=>false,
82
+ :remote_storage=>{:provider=>'mockremote'},
83
+ :local_storage=>{:provider=>'mocklocal'},
84
+ :yum_repo=>{
85
+ :epel=>@epel_version,
86
+ :subdir=>@repo_subdir,
87
+ :dists=>@epel_dists,
88
+ :gpg_key_id=>should_sign,
89
+ :gpg_password=>'fakepass'}
90
+ }
91
+ }
92
+ end
93
+ end
94
+
95
+ after(:each) do
96
+ @install_expectations.should be_empty
97
+ end
98
+
99
+ # Test for publish noarch packages (multicasting)
100
+ context "NoArch Packages" do
101
+ it "installs a single noarch rpm file to all distribution/architectures" do
102
+ rpm_file = 'foo-1.noarch.rpm'
103
+ build_expectations(rpm_file,@epel_dists)
104
+ @repo.publish(rpm_file, nil)
105
+ end
106
+
107
+ it "installs a single src rpm file to all dists" do
108
+ rpm_file = 'foo-1.src.rpm'
109
+ build_expectations(rpm_file,@epel_dists)
110
+ @repo.publish(rpm_file, nil)
111
+ end
112
+
113
+ it "installs a list of noarch rpm files to all distribution/architectures" do
114
+ rpm_files = ['foo-1.noarch.rpm', 'bar-2.noarch.rpm']
115
+ build_expectations(rpm_files,@epel_dists)
116
+ @repo.publish(rpm_files, nil)
117
+ end
118
+
119
+ it "globs and installs a directory of noarch rpm files to all distributions/architectures" do
120
+ rpm_files = ['foo-1.noarch.rpm', 'bar-2.src.rpm']
121
+ build_expectations(rpm_files,@epel_dists)
122
+ flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
123
+ flexmock(Dir) { |obj| obj.should_receive(:glob).and_return(rpm_files) }
124
+ @repo.publish('somedir/', nil)
125
+ end
126
+
127
+ it "fails with no files in a dir" do
128
+ flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
129
+ flexmock(Dir) { |obj| obj.should_receive(:glob).and_return([]) }
130
+ @repo.should_receive(:system).never
131
+ expect { @repo.publish('some_dir/', nil) }.to raise_error(RuntimeError)
132
+ end
133
+
134
+ it "fails with files missing a rpm extenstion" do
135
+ rpm_files = ['foo-1.noarch.rpm', 'bar-2.noarch.deb']
136
+ @repo.should_receive(:system).never
137
+ expect { @repo.publish(rpm_files, nil) }.to raise_error(RuntimeError)
138
+ end
139
+
140
+ it "fails with a mix of noarch and binary rpms" do
141
+ rpm_files = ['foo-1.noarch.rpm', 'bar-2.x86_64.rpm']
142
+ @repo.should_receive(:system).never
143
+ expect { @repo.publish(rpm_files, nil) }.to raise_error(RuntimeError)
144
+ end
145
+ end # end NoArch context
146
+
147
+ context "Architecture Dependant Packages" do
148
+ before(:each) do
149
+ @target_distribution_map = {:fc=>['12']}
150
+ @target_distribution_str = 'fc/12'
151
+ end
152
+
153
+ it "installs a single x86_64 rpm file to a dist" do
154
+ rpm_file = 'foo-1.x86_64.rpm'
155
+ build_expectations(rpm_file,@target_distribution_map)
156
+ @repo.publish(rpm_file, @target_distribution_str)
157
+ end
158
+
159
+ it "installs a list of x86_64 rpms to a dist" do
160
+ rpm_files = ['foo-1.x86_64.rpm', 'bar-2.x86_64.rpm']
161
+ build_expectations(rpm_files,@target_distribution_map)
162
+ @repo.publish(rpm_files, @target_distribution_str)
163
+ end
164
+
165
+ it "installs a directory mixed of x86_64 and i386 rpms to a dist" do
166
+ rpm_files = ['foo-1.x86_64.rpm', 'bar-2.i386.rpm']
167
+ build_expectations(rpm_files,@target_distribution_map)
168
+ flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
169
+ flexmock(Dir) { |obj| obj.should_receive(:glob).and_return(rpm_files) }
170
+ @repo.publish('some_dir/', @target_distribution_str)
171
+ end
172
+
173
+ it "fails with no files in a dir" do
174
+ flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
175
+ flexmock(Dir) { |obj| obj.should_receive(:glob).and_return([]) }
176
+ @repo.should_receive(:system).never
177
+ expect { @repo.publish('some_dir/', @target_distribution_str) }.to raise_error(RuntimeError)
178
+ end
179
+
180
+ it "fails with files missing a rpm extension" do
181
+ rpm_files = ['foo-1.x86_64.rpm', 'bar-2.i386.deb']
182
+ @repo.should_receive(:system).never
183
+ expect { @repo.publish(rpm_files, @target_distribution_str) }.to raise_error(RuntimeError)
184
+ end
185
+
186
+ it "fails with unknown architectures" do
187
+ rpm_files = ['foo-1.ppc.rpm', 'bar-2.ppc.rpm']
188
+ @repo.should_receive(:system).never
189
+ expect { @repo.publish(rpm_files, @target_distribution_str) }.to raise_error(RuntimeError)
190
+ end
191
+
192
+ it "fails when not provided with a target" do
193
+ rpm_files = ['foo-1.x86_64.rpm', 'bar-2.i386.rpm']
194
+ @repo.should_receive(:system).never
195
+ expect { @repo.publish(rpm_files, nil) }.to raise_error(RuntimeError)
196
+ end
197
+ end # end Architecture Dependant context
198
+ end # end EPEL context
199
+
200
+ # Test the Flat repo profile type
201
+ context "Flat layout" do
202
+ def build_expectations(pkgs)
203
+ @install_expectations = Array(pkgs)
204
+ @repo.should_receive(:install_file).and_return do |pkg,dir|
205
+ @install_expectations.delete(pkg)
206
+ end
207
+ system_calls = 1
208
+ system_sign_calls = (@profile.config[:yum_repo][:gpg_key_id]) ? 1 : 0
209
+ @repo.should_receive(:system).times(system_calls).and_return(0)
210
+ @repo.should_receive(:shellout_with_password).times(system_sign_calls).and_return(0)
211
+ end
212
+
213
+ before(:each) do
214
+ @epel_version = false
215
+ @install_expectations = []
216
+ @profile = flexmock(RightPublish::Profile) do |obj|
217
+ obj.should_receive(:config).and_return {
218
+ { :verbose=>false,
219
+ :remote_storage=>{:provider=>'mockremote'},
220
+ :local_storage=>{:provider=>'mocklocal'},
221
+ :yum_repo=>{
222
+ :epel=>@epel_version,
223
+ :subdir=>@repo_subdir,
224
+ :gpg_key_id=>should_sign,
225
+ :gpg_password=>'fakepass'}
226
+ }
227
+ }
228
+ end
229
+ end
230
+
231
+ after(:each) do
232
+ @install_expectations.should be_empty
233
+ end
234
+
235
+ it "installs a single rpm file to repo" do
236
+ rpm_file = 'foo-1.noarch.rpm'
237
+ build_expectations(rpm_file)
238
+ @repo.publish(rpm_file, nil)
239
+ end
240
+
241
+ it "installs a single src rpm file to repo" do
242
+ rpm_file = 'foo-1.src.rpm'
243
+ build_expectations(rpm_file)
244
+ @repo.publish(rpm_file, nil)
245
+ end
246
+
247
+ it "installs a list of rpm files to repo" do
248
+ rpm_files = ['foo-1.x86_64.rpm', 'bar-2.x86_64.rpm']
249
+ build_expectations(rpm_files)
250
+ @repo.publish(rpm_files, nil)
251
+ end
252
+
253
+ it "globs and installs a directory of rpm files to repo" do
254
+ rpm_files = ['foo-1.noarch.rpm', 'bar-2.src.rpm']
255
+ build_expectations(rpm_files)
256
+ flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
257
+ flexmock(Dir) { |obj| obj.should_receive(:glob).and_return(rpm_files) }
258
+ @repo.publish('somedir/', nil)
259
+ end
260
+
261
+ it "succeeds with a mix of noarch and binary rpms" do
262
+ rpm_files = ['foo-1.noarch.rpm', 'bar-2.x86_64.rpm']
263
+ build_expectations(rpm_files)
264
+ @repo.publish(rpm_files, nil)
265
+ end
266
+
267
+ it "fails with no files in a dir" do
268
+ flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
269
+ flexmock(Dir) { |obj| obj.should_receive(:glob).and_return([]) }
270
+ @repo.should_receive(:system).never
271
+ expect { @repo.publish('some_dir/', nil) }.to raise_error(RuntimeError)
272
+ end
273
+
274
+ it "fails with files missing a rpm extenstion" do
275
+ rpm_files = ['foo-1.i386.rpm', 'bar-2.noarch.deb']
276
+ @repo.should_receive(:system).never
277
+ expect { @repo.publish('some_dir/', nil) }.to raise_error(RuntimeError)
278
+ end
279
+ end # end Flat context
280
+ end # end #publish context
281
+ end
282
+ end
@@ -0,0 +1,30 @@
1
+ # Copyright (c) 2012- RightScale Inc
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'rubygems'
23
+ require 'bundler/setup'
24
+ require 'flexmock'
25
+ require 'ruby-debug'
26
+ require 'simplecov' unless RUBY_VERSION =~ /1\.8/
27
+
28
+ RSpec.configure do |config|
29
+ config.mock_with :flexmock
30
+ end
@@ -0,0 +1,71 @@
1
+ require 'right_publish/storage'
2
+ require 'spec_helper'
3
+
4
+ describe RightPublish::StorageManager do
5
+
6
+ def is_registered?(name, key)
7
+ @storage_types = RightPublish::StorageManager.storage_types
8
+ @storage_types.should have_key(name)
9
+ @storage_types[name].should equal(key)
10
+ RightPublish::StorageManager.get_storage(key)::STORAGE_KEY.should be_equal(key)
11
+ end
12
+
13
+ it "should provide a hash of known storage types" do
14
+ RightPublish::StorageManager.storage_types.should be_instance_of(Hash)
15
+ end
16
+
17
+ it "returns nil for unknown storage types" do
18
+ RightPublish::StorageManager.get_storage(:doesnotexist_storage).should be_equal(nil)
19
+ end
20
+
21
+ it "should know about the local storage" do
22
+ is_registered?('local', :local_storage)
23
+ end
24
+
25
+ it "should know about the s3 storage" do
26
+ is_registered?('s3', :s3_storage)
27
+ end
28
+
29
+ context ".register_storage" do
30
+ it "rejects storage without a STORAGE_KEY" do
31
+ class MissingKey
32
+ end
33
+ expect { RightPublish::StorageManager.register_storage(MissingKey) }.to raise_error(NameError)
34
+ end
35
+
36
+ it "rejects storage whose key does not end in _storage" do
37
+ class BadKey
38
+ STORAGE_KEY = :bad_key
39
+ end
40
+ expect { RightPublish::StorageManager.register_storage(BadKey) }.to raise_error(TypeError)
41
+ end
42
+
43
+ it "rejects storage who do not provide .get_directories" do
44
+ class NoDirectories
45
+ STORAGE_KEY = :nooptions_storage
46
+ end
47
+ expect { RightPublish::StorageManager.register_storage(NoDirectories) }.to raise_error(TypeError)
48
+ end
49
+
50
+ it "rejects storage who do not provide options" do
51
+ class NoOptions
52
+ STORAGE_KEY = :nooptions_storage
53
+ def self.get_directories()
54
+ end
55
+ end
56
+ expect { RightPublish::StorageManager.register_storage(NoOptions) }.to raise_error(NameError)
57
+ end
58
+
59
+ it "accpets storage who are properly defined" do
60
+ class GoodStorage
61
+ STORAGE_KEY = :good_storage
62
+ STORAGE_OPTIONS = {}
63
+ def self.get_directories()
64
+ end
65
+ end
66
+ RightPublish::StorageManager.register_storage(GoodStorage).should be_equal(nil)
67
+ is_registered?('good', :good_storage)
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,317 @@
1
+ require 'right_publish/storage'
2
+ require 'spec_helper'
3
+
4
+ describe RightPublish::Storage do
5
+
6
+ # Create a File entity.
7
+ def make_file(name, expects, copy, destroy)
8
+ file = flexmock("File:#{name}")
9
+
10
+ # We can set some expectations on it like if we believe we're
11
+ # going to be removed from Storage.
12
+ destroy_calls = 0
13
+ munge_body = false
14
+ if expects.instance_of? Hash
15
+ destroy_calls = (expects.has_key?(:destroy) && expects[:destroy] && 1) || 0
16
+ munge_body = (expects.has_key?(:munge_body) && expects[:munge_body]) || false
17
+ end
18
+
19
+ # For simplicity use the filename as the "contents" but provide a
20
+ # way to corrupt it to force testing content, not name matching.
21
+ file.should_receive(:body).and_return { (munge_body && 'data_mismatch') || name }
22
+
23
+ # Use the copy callback to insert a copy of ourselves at a different
24
+ # path (Remote duplication).
25
+ file.should_receive(:copy).and_return do |dir_key,dest|
26
+ new_file = make_file(dest, nil, copy, destroy)
27
+ copy.call(new_file)
28
+ end
29
+
30
+ # Use the destroy callback to remove outselves from the Files obj.
31
+ file.should_receive(:destroy).times(destroy_calls).and_return { destroy.call(name) }
32
+ file.should_receive(:key).and_return(name)
33
+ file
34
+ end
35
+
36
+ # Create a Fog Storage Mock
37
+ def make_storage(path, file_hash, expectations)
38
+ # List of File mocks bound to this storage instance
39
+ files = []
40
+
41
+ # These procedures are used as callbacks for the File mocks to copy
42
+ # or destroy themselves and have a side-effect on the about Array
43
+ create_proc = Proc.new { |file| files << file }
44
+ delete_proc = Proc.new { |file| files.delete_if { |f| f.key == file } }
45
+
46
+ # Create the File mocks and store in the Array
47
+ file_hash.each_pair do |name,expects|
48
+ files << make_file(name, expects, create_proc, delete_proc )
49
+ end
50
+
51
+ # Fog has a weird notion of a 'Files' and 'Directory' object being
52
+ # distinct. We care here because create is called on the 'Files'
53
+ # rather then on the 'Directory' and it is Enumerable.
54
+ file_list = flexmock(files)
55
+ file_list.should_receive(:create).
56
+ times(expectations[:create_call]).
57
+ and_return do |args|
58
+ new_file = make_file( args[:key], nil, create_proc, delete_proc )
59
+ files << new_file
60
+ new_file
61
+ end
62
+
63
+ # The only thing ever called on the Storage object is the
64
+ # #get_directories interface, or at least that's all we use.
65
+ obj = flexmock('Storage')
66
+ obj.should_receive(:get_directories).and_return do
67
+ # The Directory object is synonymous to an S3 bucket object
68
+ # and is responsible for providing the Files enumerable and
69
+ # calculating the MD5 hash of a given file.
70
+ f = flexmock('Directory')
71
+ f.should_receive(:compute_md5).and_return {|file| Digest::MD5.hexdigest(File.basename(file.body))}
72
+ f.should_receive(:files).and_return(file_list)
73
+ f.should_receive(:key).and_return(path)
74
+ f
75
+ end
76
+ obj
77
+ end
78
+
79
+ # In order to match we just make sure that the sizes of the Files
80
+ # objects match and that every File within A is also within B. We don't
81
+ # have to check the reverse since the sizes implies it.
82
+ def storage_match?(a,b)
83
+ a_files = a.get_directories.files
84
+ b_files = b.get_directories.files
85
+
86
+ return false if a_files.count != b_files.count
87
+ a_files.all? { |a_f|
88
+ name = a_f.key
89
+ b_files.any? { |b_f| name == b_f.key }
90
+ }
91
+ end
92
+
93
+ before(:each) do
94
+ flexmock(RightPublish::Profile) do |obj|
95
+ obj.should_receive(:log).and_return { |str,debug|
96
+ }
97
+ end
98
+ end
99
+
100
+ context ".sync_dirs" do
101
+ it "handles source AND destination storages that are empty" do
102
+ src_storage = make_storage(
103
+ 'foo',
104
+ {},
105
+ {:create_call=>0} )
106
+
107
+ # First copy of file1 will use create, second will be duped
108
+ dest_storage = make_storage(
109
+ 'bar',
110
+ {},
111
+ {:create_call=>0} )
112
+
113
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
114
+ storage_match?(src_storage, dest_storage).should be_true
115
+ end
116
+
117
+ it "does nothing with identical directories" do
118
+ src_storage = make_storage(
119
+ 'foo',
120
+ { 'dir1/file1'=>nil,
121
+ 'dir1/file2'=>nil,
122
+ 'file3'=>nil },
123
+ {:create_call=>0} )
124
+
125
+ dest_storage = make_storage(
126
+ 'bar',
127
+ { 'dir1/file1'=>nil,
128
+ 'dir1/file2'=>nil,
129
+ 'file3'=>nil },
130
+ {:create_call=>0} )
131
+
132
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage)
133
+ storage_match?(src_storage, dest_storage).should be_true
134
+ end
135
+
136
+ it "copies a new file to the destination" do
137
+ src_storage = make_storage(
138
+ 'foo',
139
+ { 'dir1/file1'=>nil,
140
+ 'dir1/file2'=>nil,
141
+ 'file3'=>nil,
142
+ 'file4'=>nil },
143
+ {:create_call=>0} )
144
+
145
+ dest_storage = make_storage(
146
+ 'bar',
147
+ { 'dir1/file1'=>nil,
148
+ 'dir1/file2'=>nil,
149
+ 'file3'=>nil },
150
+ {:create_call=>1} )
151
+
152
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage)
153
+ storage_match?(src_storage, dest_storage).should be_true
154
+ end
155
+
156
+ it "handles source storages that are empty" do
157
+ src_storage = make_storage(
158
+ 'foo',
159
+ {},
160
+ {:create_call=>0} )
161
+
162
+ # First copy of file1 will use create, second will be duped
163
+ dest_storage = make_storage(
164
+ 'bar',
165
+ { 'dir1/file1'=>{:destroy=>true} },
166
+ {:create_call=>0} )
167
+
168
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
169
+ storage_match?(src_storage, dest_storage).should be_true
170
+ end
171
+
172
+ it "handles destination storages that are empty" do
173
+ src_storage = make_storage(
174
+ 'foo',
175
+ { 'dir1/file1'=>nil },
176
+ {:create_call=>0} )
177
+
178
+ # First copy of file1 will use create, second will be duped
179
+ dest_storage = make_storage(
180
+ 'bar',
181
+ {},
182
+ {:create_call=>1} )
183
+
184
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
185
+ storage_match?(src_storage, dest_storage).should be_true
186
+ end
187
+
188
+ it "leaves an extraneous file in the destination w/o :sweep" do
189
+ src_storage = make_storage(
190
+ 'foo',
191
+ { 'dir1/file1'=>nil,
192
+ 'dir1/file2'=>nil,
193
+ 'file3'=>nil },
194
+ {:create_call=>0} )
195
+
196
+ dest_storage = make_storage(
197
+ 'bar',
198
+ { 'dir1/file1'=>nil,
199
+ 'dir1/file2'=>nil,
200
+ 'file3'=>nil,
201
+ 'file4'=>nil },
202
+ {:create_call=>0} )
203
+
204
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage)
205
+
206
+ # We should NOT match here. Not sure what the usecase of this
207
+ # is, but it's tested anyways.
208
+ storage_match?(src_storage, dest_storage).should be_false
209
+ end
210
+
211
+ it "destroys an extraneous file from the destination w/ :sweep" do
212
+ src_storage = make_storage(
213
+ 'foo',
214
+ { 'dir1/file1'=>nil,
215
+ 'dir1/file2'=>nil,
216
+ 'file3'=>nil },
217
+ {:create_call=>0} )
218
+
219
+ dest_storage = make_storage(
220
+ 'bar',
221
+ { 'dir1/file1'=>nil,
222
+ 'dir1/file2'=>nil,
223
+ 'file3'=>nil,
224
+ 'file4'=>{:destroy=>true} },
225
+ {:create_call=>0} )
226
+
227
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
228
+ storage_match?(src_storage, dest_storage).should be_true
229
+ end
230
+
231
+ it "destroys and copies files as needed to synchronize directories" do
232
+ src_storage = make_storage(
233
+ 'foo',
234
+ { 'dir1/file1'=>nil,
235
+ 'dir1/file2'=>nil,
236
+ 'file3'=>nil,
237
+ 'file4'=>nil,
238
+ 'file6'=>nil },
239
+ {:create_call=>0} )
240
+
241
+ dest_storage = make_storage(
242
+ 'bar',
243
+ { 'dir1/file1'=>nil,
244
+ 'dir1/file2'=>nil,
245
+ 'file3'=>nil,
246
+ 'file5'=>{:destroy=>true},
247
+ 'dir2/file2'=>{:destroy=>true} },
248
+ {:create_call=>2} )
249
+
250
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
251
+ storage_match?(src_storage, dest_storage).should be_true
252
+ end
253
+
254
+ it "uses the remote copy facilities when making duplicates" do
255
+ src_storage = make_storage(
256
+ 'foo',
257
+ { 'dir1/file1'=>nil,
258
+ 'dir1/file2'=>nil,
259
+ 'dir2/file1'=>nil,
260
+ 'file4'=>nil },
261
+ {:create_call=>0} )
262
+
263
+ # There are two duplicates, 'file1', in the local store. To save
264
+ # time/data we just call copy on the file that already exists in
265
+ # the remote, notice the '0' create_calls.
266
+ dest_storage = make_storage(
267
+ 'bar',
268
+ { 'dir1/file1'=>nil,
269
+ 'dir1/file2'=>nil,
270
+ 'file4'=>nil },
271
+ {:create_call=>0} )
272
+
273
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
274
+ storage_match?(src_storage, dest_storage).should be_true
275
+ end
276
+
277
+ it "uses the content of the file, not the name to determine equality" do
278
+ src_storage = make_storage(
279
+ 'foo',
280
+ { 'dir1/file1'=>nil,
281
+ 'dir1/file2'=>nil,
282
+ 'file4'=>nil },
283
+ {:create_call=>0} )
284
+
285
+ # :munge_body cause the MD5 sum to mismatch the file name
286
+ dest_storage = make_storage(
287
+ 'bar',
288
+ { 'dir1/file1'=>{:munge_body=>true,:destroy=>true},
289
+ 'dir1/file2'=>nil,
290
+ 'file4'=>nil },
291
+ {:create_call=>1} )
292
+
293
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
294
+ storage_match?(src_storage, dest_storage).should be_true
295
+ end
296
+
297
+ it "uses create AND duplicate routines with multiple new identical files" do
298
+ src_storage = make_storage(
299
+ 'foo',
300
+ { 'dir1/file1'=>nil,
301
+ 'dir1/file2'=>nil,
302
+ 'dir2/dir3/file2'=>nil,
303
+ 'file2'=>nil },
304
+ {:create_call=>0} )
305
+
306
+ # First copy of file1 will use create, the rest will be duped
307
+ dest_storage = make_storage(
308
+ 'bar',
309
+ { 'dir1/file1'=>nil },
310
+ {:create_call=>1} )
311
+
312
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
313
+ storage_match?(src_storage, dest_storage).should be_true
314
+ end
315
+ end
316
+
317
+ end