backup 3.0.20 → 3.0.21

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.
Files changed (178) hide show
  1. data/Gemfile +1 -5
  2. data/Gemfile.lock +46 -50
  3. data/README.md +54 -27
  4. data/lib/backup.rb +16 -39
  5. data/lib/backup/archive.rb +42 -18
  6. data/lib/backup/cleaner.rb +110 -25
  7. data/lib/backup/cli/helpers.rb +17 -32
  8. data/lib/backup/cli/utility.rb +46 -107
  9. data/lib/backup/compressor/base.rb +14 -2
  10. data/lib/backup/compressor/bzip2.rb +10 -24
  11. data/lib/backup/compressor/gzip.rb +10 -24
  12. data/lib/backup/compressor/lzma.rb +10 -23
  13. data/lib/backup/compressor/pbzip2.rb +12 -32
  14. data/lib/backup/config.rb +171 -0
  15. data/lib/backup/configuration/compressor/base.rb +1 -2
  16. data/lib/backup/configuration/compressor/pbzip2.rb +4 -4
  17. data/lib/backup/configuration/database/base.rb +2 -1
  18. data/lib/backup/configuration/database/mongodb.rb +8 -0
  19. data/lib/backup/configuration/database/mysql.rb +4 -0
  20. data/lib/backup/configuration/database/postgresql.rb +4 -0
  21. data/lib/backup/configuration/database/redis.rb +4 -0
  22. data/lib/backup/configuration/database/riak.rb +5 -1
  23. data/lib/backup/configuration/encryptor/base.rb +1 -2
  24. data/lib/backup/configuration/encryptor/open_ssl.rb +1 -1
  25. data/lib/backup/configuration/helpers.rb +7 -2
  26. data/lib/backup/configuration/notifier/base.rb +4 -28
  27. data/lib/backup/configuration/storage/base.rb +1 -1
  28. data/lib/backup/configuration/storage/dropbox.rb +14 -4
  29. data/lib/backup/configuration/syncer/base.rb +10 -0
  30. data/lib/backup/configuration/syncer/rsync/base.rb +28 -0
  31. data/lib/backup/configuration/syncer/rsync/local.rb +11 -0
  32. data/lib/backup/configuration/syncer/rsync/pull.rb +11 -0
  33. data/lib/backup/configuration/syncer/rsync/push.rb +31 -0
  34. data/lib/backup/configuration/syncer/s3.rb +0 -4
  35. data/lib/backup/database/base.rb +25 -7
  36. data/lib/backup/database/mongodb.rb +112 -75
  37. data/lib/backup/database/mysql.rb +54 -29
  38. data/lib/backup/database/postgresql.rb +60 -42
  39. data/lib/backup/database/redis.rb +61 -39
  40. data/lib/backup/database/riak.rb +35 -11
  41. data/lib/backup/dependency.rb +4 -5
  42. data/lib/backup/encryptor/base.rb +13 -1
  43. data/lib/backup/encryptor/gpg.rb +39 -39
  44. data/lib/backup/encryptor/open_ssl.rb +28 -38
  45. data/lib/backup/logger.rb +20 -11
  46. data/lib/backup/model.rb +206 -163
  47. data/lib/backup/notifier/base.rb +27 -25
  48. data/lib/backup/notifier/campfire.rb +7 -13
  49. data/lib/backup/notifier/hipchat.rb +28 -28
  50. data/lib/backup/notifier/mail.rb +24 -26
  51. data/lib/backup/notifier/presently.rb +10 -18
  52. data/lib/backup/notifier/prowl.rb +9 -17
  53. data/lib/backup/notifier/twitter.rb +11 -18
  54. data/lib/backup/package.rb +47 -0
  55. data/lib/backup/packager.rb +81 -16
  56. data/lib/backup/splitter.rb +48 -35
  57. data/lib/backup/storage/base.rb +44 -172
  58. data/lib/backup/storage/cloudfiles.rb +31 -46
  59. data/lib/backup/storage/cycler.rb +117 -0
  60. data/lib/backup/storage/dropbox.rb +92 -76
  61. data/lib/backup/storage/ftp.rb +30 -40
  62. data/lib/backup/storage/local.rb +44 -45
  63. data/lib/backup/storage/ninefold.rb +55 -49
  64. data/lib/backup/storage/rsync.rb +49 -56
  65. data/lib/backup/storage/s3.rb +33 -44
  66. data/lib/backup/storage/scp.rb +21 -48
  67. data/lib/backup/storage/sftp.rb +26 -40
  68. data/lib/backup/syncer/base.rb +7 -0
  69. data/lib/backup/syncer/rsync/base.rb +78 -0
  70. data/lib/backup/syncer/rsync/local.rb +53 -0
  71. data/lib/backup/syncer/rsync/pull.rb +38 -0
  72. data/lib/backup/syncer/rsync/push.rb +113 -0
  73. data/lib/backup/syncer/s3.rb +42 -32
  74. data/lib/backup/version.rb +1 -1
  75. data/spec/archive_spec.rb +235 -69
  76. data/spec/cleaner_spec.rb +304 -0
  77. data/spec/cli/helpers_spec.rb +142 -1
  78. data/spec/cli/utility_spec.rb +338 -13
  79. data/spec/compressor/base_spec.rb +31 -0
  80. data/spec/compressor/bzip2_spec.rb +60 -35
  81. data/spec/compressor/gzip_spec.rb +60 -35
  82. data/spec/compressor/lzma_spec.rb +60 -35
  83. data/spec/compressor/pbzip2_spec.rb +98 -37
  84. data/spec/config_spec.rb +321 -0
  85. data/spec/configuration/base_spec.rb +4 -4
  86. data/spec/configuration/compressor/bzip2_spec.rb +1 -0
  87. data/spec/configuration/compressor/gzip_spec.rb +1 -0
  88. data/spec/configuration/compressor/lzma_spec.rb +1 -0
  89. data/spec/configuration/compressor/pbzip2_spec.rb +32 -0
  90. data/spec/configuration/database/base_spec.rb +2 -1
  91. data/spec/configuration/database/mongodb_spec.rb +26 -16
  92. data/spec/configuration/database/mysql_spec.rb +4 -0
  93. data/spec/configuration/database/postgresql_spec.rb +4 -0
  94. data/spec/configuration/database/redis_spec.rb +4 -0
  95. data/spec/configuration/database/riak_spec.rb +4 -0
  96. data/spec/configuration/encryptor/gpg_spec.rb +1 -0
  97. data/spec/configuration/encryptor/open_ssl_spec.rb +1 -0
  98. data/spec/configuration/notifier/base_spec.rb +32 -0
  99. data/spec/configuration/notifier/campfire_spec.rb +1 -0
  100. data/spec/configuration/notifier/hipchat_spec.rb +1 -0
  101. data/spec/configuration/notifier/mail_spec.rb +1 -0
  102. data/spec/configuration/notifier/presently_spec.rb +1 -0
  103. data/spec/configuration/notifier/prowl_spec.rb +1 -0
  104. data/spec/configuration/notifier/twitter_spec.rb +1 -0
  105. data/spec/configuration/storage/cloudfiles_spec.rb +1 -0
  106. data/spec/configuration/storage/dropbox_spec.rb +4 -3
  107. data/spec/configuration/storage/ftp_spec.rb +1 -0
  108. data/spec/configuration/storage/local_spec.rb +1 -0
  109. data/spec/configuration/storage/ninefold_spec.rb +1 -0
  110. data/spec/configuration/storage/rsync_spec.rb +3 -1
  111. data/spec/configuration/storage/s3_spec.rb +1 -0
  112. data/spec/configuration/storage/scp_spec.rb +1 -0
  113. data/spec/configuration/storage/sftp_spec.rb +1 -0
  114. data/spec/configuration/syncer/rsync/base_spec.rb +33 -0
  115. data/spec/configuration/syncer/rsync/local_spec.rb +10 -0
  116. data/spec/configuration/syncer/rsync/pull_spec.rb +10 -0
  117. data/spec/configuration/syncer/{rsync_spec.rb → rsync/push_spec.rb} +12 -15
  118. data/spec/configuration/syncer/s3_spec.rb +2 -3
  119. data/spec/database/base_spec.rb +35 -20
  120. data/spec/database/mongodb_spec.rb +298 -119
  121. data/spec/database/mysql_spec.rb +147 -72
  122. data/spec/database/postgresql_spec.rb +155 -100
  123. data/spec/database/redis_spec.rb +200 -97
  124. data/spec/database/riak_spec.rb +82 -24
  125. data/spec/dependency_spec.rb +49 -0
  126. data/spec/encryptor/base_spec.rb +30 -0
  127. data/spec/encryptor/gpg_spec.rb +105 -28
  128. data/spec/encryptor/open_ssl_spec.rb +85 -114
  129. data/spec/logger_spec.rb +74 -8
  130. data/spec/model_spec.rb +528 -220
  131. data/spec/notifier/base_spec.rb +89 -0
  132. data/spec/notifier/campfire_spec.rb +147 -119
  133. data/spec/notifier/hipchat_spec.rb +140 -145
  134. data/spec/notifier/mail_spec.rb +190 -248
  135. data/spec/notifier/presently_spec.rb +147 -282
  136. data/spec/notifier/prowl_spec.rb +79 -111
  137. data/spec/notifier/twitter_spec.rb +87 -106
  138. data/spec/package_spec.rb +61 -0
  139. data/spec/packager_spec.rb +154 -0
  140. data/spec/spec_helper.rb +36 -13
  141. data/spec/splitter_spec.rb +90 -41
  142. data/spec/storage/base_spec.rb +95 -239
  143. data/spec/storage/cloudfiles_spec.rb +185 -75
  144. data/spec/storage/cycler_spec.rb +239 -0
  145. data/spec/storage/dropbox_spec.rb +318 -87
  146. data/spec/storage/ftp_spec.rb +165 -152
  147. data/spec/storage/local_spec.rb +206 -54
  148. data/spec/storage/ninefold_spec.rb +264 -128
  149. data/spec/storage/rsync_spec.rb +244 -163
  150. data/spec/storage/s3_spec.rb +175 -64
  151. data/spec/storage/scp_spec.rb +156 -150
  152. data/spec/storage/sftp_spec.rb +153 -135
  153. data/spec/syncer/base_spec.rb +22 -0
  154. data/spec/syncer/rsync/base_spec.rb +118 -0
  155. data/spec/syncer/rsync/local_spec.rb +121 -0
  156. data/spec/syncer/rsync/pull_spec.rb +90 -0
  157. data/spec/syncer/rsync/push_spec.rb +327 -0
  158. data/spec/syncer/s3_spec.rb +180 -91
  159. data/templates/cli/utility/config +1 -1
  160. data/templates/cli/utility/database/mongodb +4 -0
  161. data/templates/cli/utility/database/mysql +3 -0
  162. data/templates/cli/utility/database/postgresql +3 -0
  163. data/templates/cli/utility/database/redis +3 -0
  164. data/templates/cli/utility/database/riak +3 -0
  165. data/templates/cli/utility/storage/dropbox +4 -1
  166. data/templates/cli/utility/syncer/rsync_local +12 -0
  167. data/templates/cli/utility/syncer/{rsync → rsync_pull} +2 -2
  168. data/templates/cli/utility/syncer/rsync_push +17 -0
  169. data/templates/storage/dropbox/authorization_url.erb +1 -1
  170. metadata +42 -17
  171. data/lib/backup/configuration/syncer/rsync.rb +0 -45
  172. data/lib/backup/finder.rb +0 -87
  173. data/lib/backup/storage/object.rb +0 -47
  174. data/lib/backup/syncer/rsync.rb +0 -152
  175. data/spec/backup_spec.rb +0 -11
  176. data/spec/finder_spec.rb +0 -91
  177. data/spec/storage/object_spec.rb +0 -74
  178. data/spec/syncer/rsync_spec.rb +0 -195
@@ -0,0 +1,304 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('../spec_helper.rb', __FILE__)
4
+
5
+ describe 'Backup::Cleaner' do
6
+ let(:model) { Backup::Model.new(:test_trigger, 'test label') }
7
+ let(:cleaner) { Backup::Cleaner }
8
+
9
+ describe '#prepare' do
10
+ let(:error_tail) do
11
+ " Please check the log for messages and/or your notifications\n" +
12
+ " concerning this backup: 'test label (test_trigger)'\n" +
13
+ " The temporary files which had to be removed should not have existed."
14
+ end
15
+
16
+ context 'when neither the tmp_path is dirty or package files exist' do
17
+ it 'should do nothing' do
18
+ cleaner.expects(:packaging_folder_dirty?).returns(false)
19
+ cleaner.expects(:tmp_path_package_files).returns([])
20
+ FileUtils.expects(:rm_rf).never
21
+ FileUtils.expects(:rm_f).never
22
+ Backup::Logger.expects(:warn).never
23
+
24
+ cleaner.prepare(model)
25
+ end
26
+ end
27
+
28
+ context 'when the tmp_path is dirty' do
29
+ it 'should remove tmp_path and log a warning' do
30
+ cleaner.expects(:packaging_folder_dirty?).returns(true)
31
+ cleaner.expects(:tmp_path_package_files).returns([])
32
+ FileUtils.expects(:rm_f).never
33
+
34
+ FileUtils.expects(:rm_rf).with(
35
+ File.join(Backup::Config.tmp_path, 'test_trigger')
36
+ )
37
+ Backup::Logger.expects(:warn).with do |err|
38
+ err.should be_an_instance_of Backup::Errors::CleanerError
39
+ err.message.should == "CleanerError: Cleanup Warning\n" +
40
+ " The temporary backup folder still contains files!\n" +
41
+ " '#{ File.join(Backup::Config.tmp_path, 'test_trigger') }'\n" +
42
+ " These files will now be removed.\n" +
43
+ " \n" + error_tail
44
+ end
45
+
46
+ cleaner.prepare(model)
47
+ end
48
+ end
49
+
50
+ context 'when package files exist' do
51
+ it 'should remove the files and log a warning' do
52
+ cleaner.expects(:packaging_folder_dirty?).returns(false)
53
+ cleaner.expects(:tmp_path_package_files).returns(['file1', 'file2'])
54
+ FileUtils.expects(:rm_rf).never
55
+
56
+ FileUtils.expects(:rm_f).with('file1')
57
+ FileUtils.expects(:rm_f).with('file2')
58
+
59
+ Backup::Logger.expects(:warn).with do |err|
60
+ err.should be_an_instance_of Backup::Errors::CleanerError
61
+ err.message.should == "CleanerError: Cleanup Warning\n" +
62
+ " The temporary backup folder '#{ Backup::Config.tmp_path }'\n" +
63
+ " appears to contain the package files from the previous backup!\n" +
64
+ " file1\n" +
65
+ " file2\n" +
66
+ " These files will now be removed.\n" +
67
+ " \n" + error_tail
68
+ end
69
+
70
+ cleaner.prepare(model)
71
+ end
72
+ end
73
+
74
+ context 'both the tmp_path is dirty and package files exist' do
75
+ it 'should clean both and log a warning' do
76
+ cleaner.expects(:packaging_folder_dirty?).returns(true)
77
+ cleaner.expects(:tmp_path_package_files).returns(['file1', 'file2'])
78
+
79
+ FileUtils.expects(:rm_rf).with(
80
+ File.join(Backup::Config.tmp_path, 'test_trigger')
81
+ )
82
+ FileUtils.expects(:rm_f).with('file1')
83
+ FileUtils.expects(:rm_f).with('file2')
84
+
85
+ Backup::Logger.expects(:warn).with do |err|
86
+ err.should be_an_instance_of Backup::Errors::CleanerError
87
+ err.message.should == "CleanerError: Cleanup Warning\n" +
88
+ " The temporary backup folder still contains files!\n" +
89
+ " '#{ File.join(Backup::Config.tmp_path, 'test_trigger') }'\n" +
90
+ " These files will now be removed.\n" +
91
+ " \n" +
92
+ " #{ '-' * 74 }\n" +
93
+ " The temporary backup folder '#{ Backup::Config.tmp_path }'\n" +
94
+ " appears to contain the package files from the previous backup!\n" +
95
+ " file1\n" +
96
+ " file2\n" +
97
+ " These files will now be removed.\n" +
98
+ " \n" + error_tail
99
+ end
100
+
101
+ cleaner.prepare(model)
102
+ end
103
+ end
104
+
105
+ end # describe '#prepare'
106
+
107
+ describe '#remove_packaging' do
108
+ it 'should remove the packaging directory and log a message' do
109
+ Backup::Logger.expects(:message).with(
110
+ "Cleaning up the temporary files..."
111
+ )
112
+ FileUtils.expects(:rm_rf).with(
113
+ File.join(Backup::Config.tmp_path, 'test_trigger')
114
+ )
115
+
116
+ cleaner.remove_packaging(model)
117
+ end
118
+ end
119
+
120
+ describe '#remove_package' do
121
+ let(:package) { mock }
122
+ it 'should remove the files for the given package and log a message' do
123
+ package.expects(:filenames).returns(['file1', 'file2'])
124
+ Backup::Logger.expects(:message).with(
125
+ "Cleaning up the package files..."
126
+ )
127
+ FileUtils.expects(:rm_f).with(
128
+ File.join(Backup::Config.tmp_path, 'file1')
129
+ )
130
+ FileUtils.expects(:rm_f).with(
131
+ File.join(Backup::Config.tmp_path, 'file2')
132
+ )
133
+
134
+ cleaner.remove_package(package)
135
+ end
136
+ end
137
+
138
+ describe '#warnings' do
139
+ let(:error_tail) do
140
+ " Make sure you check these files before the next scheduled backup for\n" +
141
+ " 'test label (test_trigger)'\n" +
142
+ " These files will be removed at that time!"
143
+ end
144
+
145
+ context 'when neither the tmp_path is dirty or package files exist' do
146
+ it 'should do nothing' do
147
+ cleaner.expects(:packaging_folder_dirty?).returns(false)
148
+ cleaner.expects(:tmp_path_package_files).returns([])
149
+ Backup::Logger.expects(:warn).never
150
+
151
+ cleaner.warnings(model)
152
+ end
153
+ end
154
+
155
+ context 'when the tmp_path is dirty' do
156
+ it 'should remove tmp_path and log a warning' do
157
+ cleaner.expects(:packaging_folder_dirty?).returns(true)
158
+ cleaner.expects(:tmp_path_package_files).returns([])
159
+
160
+ Backup::Logger.expects(:warn).with do |err|
161
+ err.should be_an_instance_of Backup::Errors::CleanerError
162
+ err.message.should == "CleanerError: Cleanup Warning\n" +
163
+ " The temporary backup folder still contains files!\n" +
164
+ " '#{ File.join(Backup::Config.tmp_path, 'test_trigger') }'\n" +
165
+ " This folder may contain completed Archives and/or Database backups.\n" +
166
+ " \n" + error_tail
167
+ end
168
+
169
+ cleaner.warnings(model)
170
+ end
171
+ end
172
+
173
+ context 'when package files exist' do
174
+ it 'should remove the files and log a warning' do
175
+ cleaner.expects(:packaging_folder_dirty?).returns(false)
176
+ cleaner.expects(:tmp_path_package_files).returns(['file1', 'file2'])
177
+
178
+ Backup::Logger.expects(:warn).with do |err|
179
+ err.should be_an_instance_of Backup::Errors::CleanerError
180
+ err.message.should == "CleanerError: Cleanup Warning\n" +
181
+ " The temporary backup folder '#{ Backup::Config.tmp_path }'\n" +
182
+ " appears to contain the backup files which were to be stored:\n" +
183
+ " file1\n" +
184
+ " file2\n" +
185
+ " \n" + error_tail
186
+ end
187
+
188
+ cleaner.warnings(model)
189
+ end
190
+ end
191
+
192
+ context 'both the tmp_path is dirty and package files exist' do
193
+ it 'should clean both and log a warning' do
194
+ cleaner.expects(:packaging_folder_dirty?).returns(true)
195
+ cleaner.expects(:tmp_path_package_files).returns(['file1', 'file2'])
196
+
197
+ Backup::Logger.expects(:warn).with do |err|
198
+ err.should be_an_instance_of Backup::Errors::CleanerError
199
+ err.message.should == "CleanerError: Cleanup Warning\n" +
200
+ " The temporary backup folder still contains files!\n" +
201
+ " '#{ File.join(Backup::Config.tmp_path, 'test_trigger') }'\n" +
202
+ " This folder may contain completed Archives and/or Database backups.\n" +
203
+ " \n" +
204
+ " #{ '-' * 74 }\n" +
205
+ " The temporary backup folder '#{ Backup::Config.tmp_path }'\n" +
206
+ " appears to contain the backup files which were to be stored:\n" +
207
+ " file1\n" +
208
+ " file2\n" +
209
+ " \n" + error_tail
210
+ end
211
+
212
+ cleaner.warnings(model)
213
+ end
214
+ end
215
+
216
+ end # describe '#warnings'
217
+
218
+ describe '#packaging_folder_dirty?' do
219
+ before do
220
+ cleaner.instance_variable_set(:@model, model)
221
+ FileUtils.unstub(:mkdir_p)
222
+ end
223
+
224
+ context 'when files exist in the packaging folder' do
225
+ it 'should return true' do
226
+ Dir.mktmpdir do |path|
227
+ Backup::Config.update(:root_path => path)
228
+ FileUtils.mkdir_p(
229
+ File.join(Backup::Config.tmp_path, 'test_trigger', 'archives')
230
+ )
231
+ cleaner.send(:packaging_folder_dirty?).should be_true
232
+ end
233
+ end
234
+ end
235
+
236
+ context 'when files do not exist in the packaging folder' do
237
+ it 'should return false' do
238
+ Dir.mktmpdir do |path|
239
+ Backup::Config.update(:root_path => path)
240
+ FileUtils.mkdir_p(
241
+ File.join(Backup::Config.tmp_path, 'test_trigger')
242
+ )
243
+ cleaner.send(:packaging_folder_dirty?).should be_false
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ describe '#tmp_path_package_files' do
250
+ before do
251
+ cleaner.instance_variable_set(:@model, model)
252
+ FileUtils.unstub(:mkdir_p)
253
+ FileUtils.unstub(:touch)
254
+ end
255
+
256
+ context 'when packaging files exist in the tmp_path' do
257
+ it 'should return the files' do
258
+ Dir.mktmpdir do |path|
259
+ Backup::Config.update(:root_path => path)
260
+ FileUtils.mkdir_p(Backup::Config.tmp_path)
261
+
262
+ package_files = [
263
+ '2012.01.06.12.05.30.test_trigger.tar',
264
+ '2012.02.06.12.05.30.test_trigger.tar-aa',
265
+ '2012.03.06.12.05.30.test_trigger.tar.enc',
266
+ '2012.04.06.12.05.30.test_trigger.tar.enc-aa'
267
+ ].map! {|f| File.join(Backup::Config.tmp_path, f) }
268
+
269
+ other_files = [
270
+ '2012.01.06.12.05.30.test_trigger.target.tar',
271
+ '2012.01.06.12.05.30.other_trigger.tar',
272
+ 'foo.tar'
273
+ ].map! {|f| File.join(Backup::Config.tmp_path, f) }
274
+
275
+ FileUtils.touch(package_files + other_files)
276
+ Dir[File.join(Backup::Config.tmp_path, '*')].count.should be(7)
277
+
278
+ cleaner.send(:tmp_path_package_files).sort.should == package_files
279
+ end
280
+ end
281
+ end
282
+
283
+ context 'when no packaging files exist in the tmp_path' do
284
+ it 'should return an empty array' do
285
+ Dir.mktmpdir do |path|
286
+ Backup::Config.update(:root_path => path)
287
+ FileUtils.mkdir_p(Backup::Config.tmp_path)
288
+
289
+ other_files = [
290
+ '2012.01.06.12.05.30.test_trigger.target.tar',
291
+ '2012.01.06.12.05.30.other_trigger.tar',
292
+ 'foo.tar'
293
+ ].map! {|f| File.join(Backup::Config.tmp_path, f) }
294
+
295
+ FileUtils.touch(other_files)
296
+ Dir[File.join(Backup::Config.tmp_path, '*')].count.should be(3)
297
+
298
+ cleaner.send(:tmp_path_package_files).should == []
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ end
@@ -3,7 +3,148 @@
3
3
  require File.expand_path('../../spec_helper.rb', __FILE__)
4
4
 
5
5
  describe Backup::CLI::Helpers do
6
- let(:helpers) { Module.new.extend(subject) }
6
+ let(:helpers) { Module.new.extend(Backup::CLI::Helpers) }
7
+
8
+ describe '#run' do
9
+ let(:stdin) { mock }
10
+ let(:stdout) { mock }
11
+ let(:stderr) { mock }
12
+ let(:process_status) { mock }
13
+
14
+ it 'should run the given command using POpen4' do
15
+ Open4.expects(:popen4).with('/path/to/command args').
16
+ returns([123, stdin, stdout, stderr])
17
+ Process.expects(:waitpid2).with(123).returns([123, process_status])
18
+ stdout.expects(:read).returns('stdout message')
19
+ stderr.expects(:read).returns('stderr message')
20
+
21
+ helpers.expects(:command_name).with('/path/to/command args').
22
+ returns('command')
23
+ helpers.expects(:raise_if_command_failed!).with(
24
+ 'command',
25
+ {:status => process_status,
26
+ :stdout => 'stdout message',
27
+ :stderr => 'stderr message',
28
+ :ignore_exit_codes => [0]}
29
+ )
30
+
31
+ helpers.run('/path/to/command args').should == 'stdout message'
32
+ end
33
+
34
+ it 'should accept ignore_exit_codes and add 0 to the list' do
35
+ Open4.expects(:popen4).with('/path/to/command args').
36
+ returns([123, stdin, stdout, stderr])
37
+ Process.expects(:waitpid2).with(123).returns([123, process_status])
38
+ stdout.expects(:read).returns('stdout message')
39
+ stderr.expects(:read).returns('stderr message')
40
+
41
+ helpers.expects(:command_name).with('/path/to/command args').
42
+ returns('command')
43
+ helpers.expects(:raise_if_command_failed!).with(
44
+ 'command',
45
+ {:status => process_status,
46
+ :stdout => 'stdout message',
47
+ :stderr => 'stderr message',
48
+ :ignore_exit_codes => [1, 2, 0]}
49
+ )
50
+
51
+ helpers.run(
52
+ '/path/to/command args', :ignore_exit_codes => [1, 2]
53
+ ).should == 'stdout message'
54
+ end
55
+ end
56
+
57
+ describe '#utility' do
58
+ context 'when a system path for the utility is available' do
59
+ it 'should return the system path with newline removed' do
60
+ helpers.expects(:`).with('which foo 2>/dev/null').returns("system_path\n")
61
+ helpers.utility(:foo).should == 'system_path'
62
+ end
63
+
64
+ it 'should cache the returned path' do
65
+ helpers.expects(:`).once.with('which cache_me 2>/dev/null').
66
+ returns("cached_path\n")
67
+
68
+ helpers.utility(:cache_me).should == 'cached_path'
69
+ helpers.utility(:cache_me).should == 'cached_path'
70
+ end
71
+
72
+ it 'should cache the value for all extended objects' do
73
+ helpers.expects(:`).once.with('which once_only 2>/dev/null').
74
+ returns("cached_path\n")
75
+
76
+ helpers.utility(:once_only).should == 'cached_path'
77
+ Class.new.extend(Backup::CLI::Helpers).utility(:once_only).
78
+ should == 'cached_path'
79
+ end
80
+ end
81
+
82
+
83
+ context 'when a system path for the utility is not available' do
84
+ it 'should raise an error' do
85
+ helpers.expects(:`).with('which unknown 2>/dev/null').returns("\n")
86
+
87
+ expect do
88
+ helpers.utility(:unknown)
89
+ end.to raise_error(Backup::Errors::CLI::UtilityNotFoundError) {|err|
90
+ err.message.should match(/Path to 'unknown' could not be found/)
91
+ }
92
+ end
93
+
94
+ it 'should not cache any value for the utility' do
95
+ helpers.expects(:`).with('which not_cached 2>/dev/null').twice.returns("\n")
96
+
97
+ expect do
98
+ helpers.utility(:not_cached)
99
+ end.to raise_error(Backup::Errors::CLI::UtilityNotFoundError) {|err|
100
+ err.message.should match(/Path to 'not_cached' could not be found/)
101
+ }
102
+
103
+ expect do
104
+ helpers.utility(:not_cached)
105
+ end.to raise_error(Backup::Errors::CLI::UtilityNotFoundError) {|err|
106
+ err.message.should match(/Path to 'not_cached' could not be found/)
107
+ }
108
+ end
109
+ end
110
+ end # describe '#utility'
111
+
112
+ describe '#command_name' do
113
+ context 'given a command line path with no arguments' do
114
+ it 'should return the base command name' do
115
+ cmd = '/path/to/a/command'
116
+ helpers.command_name(cmd).should == 'command'
117
+ end
118
+ end
119
+
120
+ context 'given a command line path with a single argument' do
121
+ it 'should return the base command name' do
122
+ cmd = '/path/to/a/command with_args'
123
+ helpers.command_name(cmd).should == 'command'
124
+ end
125
+ end
126
+
127
+ context 'given a command line path with multiple arguments' do
128
+ it 'should return the base command name' do
129
+ cmd = '/path/to/a/command with multiple args'
130
+ helpers.command_name(cmd).should == 'command'
131
+ end
132
+ end
133
+
134
+ context 'given a command with no path and arguments' do
135
+ it 'should return the base command name' do
136
+ cmd = 'command args'
137
+ helpers.command_name(cmd).should == 'command'
138
+ end
139
+ end
140
+
141
+ context 'given a command with no path and no arguments' do
142
+ it 'should return the base command name' do
143
+ cmd = 'command'
144
+ helpers.command_name(cmd).should == 'command'
145
+ end
146
+ end
147
+ end # describe '#command_name'
7
148
 
8
149
  describe '#raise_if_command_failed!' do
9
150