benry-unixcommand 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2517 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ libpath = File.class_eval { join(dirname(dirname(__FILE__)), 'lib') }
4
+ $LOAD_PATH << libpath unless $LOAD_PATH.include?(libpath)
5
+
6
+ require 'oktest'
7
+ require 'stringio'
8
+ require 'etc'
9
+
10
+ require 'benry/unixcommand'
11
+ require 'fileutils'
12
+
13
+
14
+ Oktest.scope do
15
+
16
+
17
+ topic Benry::UnixCommand do
18
+ include Benry::UnixCommand
19
+
20
+ before_all do
21
+ FileUtils.rm_rf "tmpdir"
22
+ FileUtils.mkdir "tmpdir"
23
+ end
24
+
25
+ after_all do
26
+ FileUtils.rm_rf "tmpdir"
27
+ end
28
+
29
+ before do
30
+ @_backto = Dir.pwd
31
+ Dir.chdir "tmpdir"
32
+ File.write("foo1.txt", "FOO1")
33
+ File.write("foo2.txt", "FOO2")
34
+ FileUtils.mkdir "d1"
35
+ File.write("d1/bar.txt", "BAR")
36
+ FileUtils.mkdir "d1/d2"
37
+ File.write("d1/d2/baz.txt", "BAZ")
38
+ end
39
+
40
+ after do
41
+ Dir.chdir @_backto
42
+ FileUtils.rm_rf Dir.glob("tmpdir/*")
43
+ end
44
+
45
+
46
+ topic 'prompt()' do
47
+ spec "[!uilyk] returns prompt string." do
48
+ ok {prompt()} == "$ "
49
+ end
50
+ end
51
+
52
+ topic 'prompt!()' do
53
+ spec "[!q992e] adds indentation after prompt." do
54
+ ok {prompt!(0)} == "$ "
55
+ ok {prompt!(1)} == "$ "
56
+ ok {prompt!(2)} == "$ "
57
+ ok {prompt!(3)} == "$ "
58
+ end
59
+ end
60
+
61
+
62
+ topic 'echoback()' do
63
+ spec "[!x7atu] prints argument string into $stdout with prompt." do
64
+ sout, serr = capture_sio() do
65
+ echoback "foo bar baz"
66
+ end
67
+ ok {sout} == "$ foo bar baz\n"
68
+ ok {serr} == ""
69
+ end
70
+ end
71
+
72
+ topic '__echoback?()' do
73
+ spec "[!ik00u] returns value of `@__BENRY_ECHOBACK` or `$BENRY_ECHOBACK`." do
74
+ bkup = $BENRY_ECHOBACK
75
+ at_end { $BENRY_ECHOBACK = bkup }
76
+ #
77
+ ok {instance_variable_defined?(:@__BENRY_ECHOBACK)} == false
78
+ $BENRY_ECHOBACK = true
79
+ ok {__echoback?()} == true
80
+ $BENRY_ECHOBACK = false
81
+ ok {__echoback?()} == false
82
+ end
83
+ spec "[!1hp69] instance var `@__BENRY_ECHOBACK` is prior than `$BENRY_ECHOBACK`." do
84
+ bkup = $BENRY_ECHOBACK
85
+ at_end { $BENRY_ECHOBACK = bkup }
86
+ #
87
+ $BENRY_ECHOBACK = true
88
+ @__BENRY_ECHOBACK = false
89
+ ok {__echoback?()} == false
90
+ $BENRY_ECHOBACK = false
91
+ @__BENRY_ECHOBACK = true
92
+ ok {__echoback?()} == true
93
+ end
94
+ end
95
+
96
+ def _sysout(command)
97
+ sout, serr = capture_sio { sys command }
98
+ ok {serr} == ""
99
+ return sout
100
+ end
101
+
102
+ topic 'echoback_on()' do
103
+ spec "[!9x2lh] enables echoback temporarily." do
104
+ bkup = $BENRY_ECHOBACK
105
+ at_end { $BENRY_ECHOBACK = bkup }
106
+ #
107
+ $BENRY_ECHOBACK = false
108
+ ok {__echoback?()} == false
109
+ ok {_sysout "echo ABC >/dev/null"} == ""
110
+ echoback_on do
111
+ ok {__echoback?()} == true
112
+ ok {_sysout "echo ABC >/dev/null"} == "$ echo ABC >/dev/null\n"
113
+ end
114
+ ok {__echoback?()} == false
115
+ ok {_sysout "echo ABC >/dev/null"} == ""
116
+ end
117
+ end
118
+
119
+ topic 'echoback_off()' do
120
+ spec "[!prkfg] disables echoback temporarily." do
121
+ bkup = $BENRY_ECHOBACK
122
+ at_end { $BENRY_ECHOBACK = bkup }
123
+ #
124
+ $BENRY_ECHOBACK = true
125
+ ok {__echoback?()} == true
126
+ ok {_sysout "echo ABC >/dev/null"} == "$ echo ABC >/dev/null\n"
127
+ echoback_off do
128
+ ok {__echoback?()} == false
129
+ ok {_sysout "echo ABC >/dev/null"} == ""
130
+ end
131
+ ok {__echoback?()} == true
132
+ ok {_sysout "echo ABC >/dev/null"} == "$ echo ABC >/dev/null\n"
133
+ end
134
+ end
135
+
136
+ topic 'echoback_switch()' do
137
+ spec "[!aw9b2] switches on/off of echoback temporarily." do
138
+ bkup = $BENRY_ECHOBACK
139
+ at_end { $BENRY_ECHOBACK = bkup }
140
+ #
141
+ $BENRY_ECHOBACK = true
142
+ ok {__echoback?()} == true
143
+ echoback_switch(false) do
144
+ ok {__echoback?()} == false
145
+ echoback_switch(true) do
146
+ ok {__echoback?()} == true
147
+ echoback_switch(false) do
148
+ ok {__echoback?()} == false
149
+ end
150
+ ok {__echoback?()} == true
151
+ end
152
+ ok {__echoback?()} == false
153
+ end
154
+ ok {__echoback?()} == true
155
+ end
156
+ end
157
+
158
+ topic 'echo()' do
159
+ spec "[!mzbdj] echoback command arguments." do
160
+ sout, serr = capture_sio do
161
+ echo "foo", "bar"
162
+ end
163
+ ok {sout} == ("$ echo foo bar\n"\
164
+ "foo bar\n")
165
+ end
166
+ spec "[!cjggd] prints arguments." do
167
+ sout, serr = capture_sio do
168
+ echo "abc", 123, true, nil
169
+ end
170
+ ok {sout} == ("$ echo abc 123 true \n"\
171
+ "abc 123 true \n")
172
+ end
173
+ spec "[!vhpw3] not print newline at end if '-n' option specified." do
174
+ sout, serr = capture_sio do
175
+ echo :n, "abc"
176
+ end
177
+ ok {sout} == ("$ echo -n abc\n"\
178
+ "abc")
179
+ end
180
+ end
181
+
182
+
183
+ topic 'sys()' do
184
+ spec "[!fb1ji] error if both array and string are specified at the same time." do
185
+ pr = proc { sys ["echo", "AA"], "BB" }
186
+ ok {pr}.raise?(ArgumentError,
187
+ "sys: Invalid argument (if arg is specified as an array, other args should not be specified).")
188
+ #
189
+ pr = proc { sys :q, ["echo", "AA"], "BB" }
190
+ ok {pr}.raise?(ArgumentError,
191
+ "sys: Invalid argument (if arg is specified as an array, other args should not be specified).")
192
+ #
193
+ pr = proc { sys! ["echo", "AA"], "BB" }
194
+ ok {pr}.raise?(ArgumentError,
195
+ "sys!: Invalid argument (if arg is specified as an array, other args should not be specified).")
196
+ end
197
+ spec "[!rqe7a] echoback command and arguments when `:p` not specified." do
198
+ sout, serr = capture_sio do
199
+ sys "echo foo bar >/dev/null"
200
+ end
201
+ ok {sout} == "$ echo foo bar >/dev/null\n"
202
+ end
203
+ spec "[!ptipz] not echoback command and arguments when `:p` specified." do
204
+ sout, serr = capture_sio do
205
+ sys :q, "echo foo bar >/dev/null"
206
+ end
207
+ ok {sout} == ""
208
+ end
209
+ spec "[!4u9lj] arguments in echoback string should be quoted or escaped." do
210
+ tmpf = "tmp.#{rand().to_s[2..6]}"
211
+ at_end { File.unlink(tmpf) if File.exist?(tmpf) }
212
+ ruby = "ruby -r ../lib/benry/unixcommand.rb"
213
+ setup = "include Benry::UnixCommand"
214
+ ## multiple string
215
+ system %Q|#{ruby} -e '#{setup}; sys "echo", "A B C"' > #{tmpf}|
216
+ ok {File.read(tmpf)} == ("$ echo \"A B C\"\n" \
217
+ "A B C\n")
218
+ ## one array
219
+ system %Q|#{ruby} -e '#{setup}; sys ["echo", "A B C"]' > #{tmpf}|
220
+ ok {File.read(tmpf)} == ("$ echo \"A B C\"\n" \
221
+ "A B C\n")
222
+ end
223
+ spec "[!dccme] accepts one string, one array, or multiple strings." do
224
+ tmpf = "tmp.#{rand().to_s[2..6]}"
225
+ at_end { File.unlink(tmpf) if File.exist?(tmpf) }
226
+ ruby = "ruby -r ../lib/benry/unixcommand.rb"
227
+ setup = "include Benry::UnixCommand"
228
+ ## multiple string
229
+ system %Q|#{ruby} -e '#{setup}; sys :q, "echo", "AA", "BB", "CC"' > #{tmpf}|
230
+ ok {File.read(tmpf)} == "AA BB CC\n"
231
+ ## one array
232
+ system %Q|#{ruby} -e '#{setup}; sys :q, ["echo", "AA", "BB", "CC"]' > #{tmpf}|
233
+ ok {File.read(tmpf)} == "AA BB CC\n"
234
+ end
235
+ spec "[!r9ne3] shell is not invoked if arg is one array or multiple string." do
236
+ tmpf = "tmp.#{rand().to_s[2..6]}"
237
+ at_end { File.unlink(tmpf) if File.exist?(tmpf) }
238
+ ruby = "ruby -r ../lib/benry/unixcommand.rb"
239
+ setup = "include Benry::UnixCommand"
240
+ ## multiple string
241
+ system %Q|#{ruby} -e '#{setup}; sys :q, "echo", "ABC", "<", ">"' > #{tmpf}|
242
+ ok {File.read(tmpf)} == "ABC < >\n"
243
+ ## one array
244
+ system %Q|#{ruby} -e '#{setup}; sys :q, ["echo", "ABC", "*", ">"]' > #{tmpf}|
245
+ ok {File.read(tmpf)} == "ABC * >\n"
246
+ ## multiple string
247
+ sout, serr = capture_sio do
248
+ pr = proc { sys "echo AA BB", " > tmp1.txt" }
249
+ ok {pr}.raise?(RuntimeError,
250
+ "Command failed with status (127): \"echo AA BB\" \" > tmp1.txt\"")
251
+ end
252
+ ok {sout} == "$ \"echo AA BB\" \" > tmp1.txt\"\n"
253
+ ok {serr} == ""
254
+ ## one array
255
+ sout, serr = capture_sio do
256
+ pr = proc { sys ["echo AA BB > tmp1.txt"] }
257
+ ok {pr}.raise?(RuntimeError,
258
+ "Command failed with status (127): \"echo AA BB > tmp1.txt\"")
259
+ end
260
+ ok {sout} == "$ \"echo AA BB > tmp1.txt\"\n"
261
+ ok {serr} == ""
262
+ end
263
+ spec "[!w6ol7] globbing is enabled when arg is multiple string." do
264
+ tmpf = "tmp.#{rand().to_s[2..6]}"
265
+ at_end { File.unlink(tmpf) if File.exist?(tmpf) }
266
+ ruby = "ruby -r ../lib/benry/unixcommand.rb"
267
+ setup = "include Benry::UnixCommand"
268
+ ## multiple string
269
+ system %Q|#{ruby} -e '#{setup}; sys "echo", "**/*.txt"' > #{tmpf}|
270
+ ok {File.read(tmpf)} == (
271
+ "$ echo **/*.txt\n"\
272
+ "d1/bar.txt d1/d2/baz.txt foo1.txt foo2.txt\n"
273
+ )
274
+ end
275
+ spec "[!ifgkd] globbing is disabled when arg is one array." do
276
+ tmpf = "tmp.#{rand().to_s[2..6]}"
277
+ at_end { File.unlink(tmpf) if File.exist?(tmpf) }
278
+ ruby = "ruby -r ../lib/benry/unixcommand.rb"
279
+ setup = "include Benry::UnixCommand"
280
+ ## one array
281
+ system %Q|#{ruby} -e '#{setup}; sys ["echo", "**/*.txt"]' > #{tmpf}|
282
+ ok {File.read(tmpf)} == (
283
+ "$ echo **/*.txt\n"\
284
+ "**/*.txt\n"
285
+ )
286
+ end
287
+ spec "[!agntr] returns process status if command succeeded." do
288
+ sout, serr = capture_sio do
289
+ ret = sys "echo foo bar >/dev/null"
290
+ ok {ret}.is_a?(Process::Status)
291
+ ok {ret.exitstatus} == 0
292
+ end
293
+ end
294
+ spec "[!clfig] yields block if command failed." do
295
+ sout, serr = capture_sio do
296
+ called = false
297
+ stat = sys "false" do |stat|
298
+ called = true
299
+ end
300
+ ok {called} == true
301
+ ok {stat.exitstatus} == 1
302
+ end
303
+ end
304
+ spec "[!deu3e] not yield block if command succeeded." do
305
+ sout, serr = capture_sio do
306
+ called = false
307
+ ret = sys "true" do |stat|
308
+ called = true
309
+ end
310
+ ok {called} == false
311
+ ok {ret.exitstatus} == 0
312
+ end
313
+ end
314
+ spec "[!chko8] block argument is process status." do
315
+ sout, serr = capture_sio do
316
+ arg = nil
317
+ ret = sys "false" do |stat|
318
+ arg = stat
319
+ true
320
+ end
321
+ ok {arg}.is_a?(Process::Status)
322
+ ok {arg}.same?(ret)
323
+ end
324
+ end
325
+ spec "[!0yy6r] (sys) not raise error if block result is truthy" do
326
+ sout, serr = capture_sio do
327
+ pr = proc { sys "false" do true end }
328
+ ok {pr}.NOT.raise?(ArgumentError)
329
+ pr = proc { sys "false" do false end }
330
+ ok {pr}.raise?(RuntimeError, "Command failed with status (1): false")
331
+ end
332
+ end
333
+ spec "[!xsspi] (sys) raises error if command failed." do
334
+ sout, serr = capture_sio do
335
+ pr = proc { sys "grep -q ^FOOBAR foo1.txt" }
336
+ ok {pr}.raise?(RuntimeError, "Command failed with status (1): grep -q ^FOOBAR foo1.txt")
337
+ end
338
+ end
339
+ end
340
+
341
+ topic 'sys!()' do
342
+ spec "[!tbfii] (sys!) returns process status if command failed." do
343
+ sout, serr = capture_sio do
344
+ ret = sys! "grep -q ^FOOBAR foo1.txt"
345
+ ok {ret}.is_a?(Process::Status)
346
+ ok {ret.exitstatus} == 1
347
+ end
348
+ end
349
+ end
350
+
351
+ topic '__system()' do
352
+ before do
353
+ @tmpf = "tmp.#{rand().to_s[2..6]}"
354
+ at_end { File.unlink(@tmpf) if File.exist?(@tmpf) }
355
+ end
356
+ spec "[!9xarc] invokes command without shell when `shell:` is falty." do
357
+ result = __system("echo A B > #{@tmpf}", shell: false)
358
+ ok {result} == nil
359
+ ok {@tmpf}.not_exist?
360
+ end
361
+ spec "[!0z33p] invokes command with shell (if necessary) when `shell:` is truthy." do
362
+ result = __system("echo A B > #{@tmpf}", shell: true)
363
+ ok {result} == true
364
+ ok {@tmpf}.file_exist?
365
+ ok {File.read(@tmpf)} == "A B\n"
366
+ end
367
+ end
368
+
369
+ topic '__build_echoback_str()' do
370
+ spec "[!4dcra] if arg is one array, quotes or escapes arguments." do
371
+ s = __build_echoback_str([["echo", "A B C", "D\"E'F\"'", "*.txt"]])
372
+ ok {s} == %q`echo "A B C" D\"E\'F\"\' *.txt`
373
+ end
374
+ spec "[!ueoov] if arg is multiple string, quotes or escapes arguments." do
375
+ s = __build_echoback_str(["echo", "A B C", "D\"E'F\"'", "*.txt"])
376
+ ok {s} == %q`echo "A B C" D\"E\'F\"\' *.txt`
377
+ end
378
+ spec "[!hnp41] if arg is one string, not quote nor escape argument." do
379
+ s = __build_echoback_str(["echo \"A B C\" D\"E'F\"' *.txt"])
380
+ ok {s} == %q`echo "A B C" D"E'F"' *.txt`
381
+ end
382
+ end
383
+
384
+ topic 'glob_if_possible()' do
385
+ spec "[!xvr32] expands file pattern matching." do
386
+ ok {glob_if_possible("*.txt")} == ["foo1.txt", "foo2.txt"]
387
+ ok {glob_if_possible("**/*.txt")} == ["d1/bar.txt", "d1/d2/baz.txt", "foo1.txt", "foo2.txt"]
388
+ ok {glob_if_possible("foo?.*", "**/bar.*")} == ["foo1.txt", "foo2.txt", "d1/bar.txt"]
389
+ end
390
+ spec "[!z38re] if pattern not matched to any files, just returns pattern as is." do
391
+ ok {glob_if_possible("blabla*", "*.xhtml")} == ["blabla*", "*.xhtml"]
392
+ ok {glob_if_possible("*.css", "*.txt")} == ["*.css", "foo1.txt", "foo2.txt"]
393
+ end
394
+ end
395
+
396
+ topic 'ruby()' do
397
+ spec "[!98qro] echoback command and args." do
398
+ sout, serr = capture_sio do
399
+ ruby "-e", "x=0"
400
+ end
401
+ ok {sout} == "$ #{RbConfig.ruby} -e x=0\n"
402
+ end
403
+ spec "[!u5f5l] run ruby command." do
404
+ sout, serr = capture_sio do
405
+ ruby "-e 'File.write(\"out1\", \"ABC\")'"
406
+ ruby "-e", "File.write(\"out2\", \"XYZ\")"
407
+ end
408
+ ok {sout} == ("$ #{RbConfig.ruby} -e 'File.write(\"out1\", \"ABC\")'\n"\
409
+ "$ #{RbConfig.ruby} -e \"File.write(\\\"out2\\\", \\\"XYZ\\\")\"\n")
410
+ ok {File.read("out1")} == "ABC"
411
+ ok {File.read("out2")} == "XYZ"
412
+ end
413
+ spec "[!2jano] returns process status object if ruby command succeeded." do
414
+ sout, serr = capture_sio do
415
+ ret = ruby "-e", "x = 1"
416
+ ok {ret}.is_a?(Process::Status)
417
+ ok {ret.exitstatus} == 0
418
+ end
419
+ end
420
+ spec "[!69clt] (ruby) error when ruby command failed." do
421
+ sout, serr = capture_sio do
422
+ pr = proc { ruby "-e '1/0' 2> err1" }
423
+ ok {pr}.raise?(RuntimeError, "Command failed with status (1): #{RbConfig.ruby} -e '1/0' 2> err1")
424
+ ok {File.read("err1")} =~ /ZeroDivisionError/
425
+ end
426
+ end
427
+ end
428
+
429
+ topic 'ruby!()' do
430
+ spec "[!z1f03] (ruby!) ignores error even when ruby command failed." do
431
+ sout, serr = capture_sio do
432
+ ret = nil
433
+ pr = proc { ret = ruby! "-e '1/0' 2> err1" }
434
+ ok {pr}.NOT.raise?(RuntimeError)
435
+ ok {File.read("err1")} =~ /ZeroDivisionError/
436
+ ok {ret}.is_a?(Process::Status)
437
+ ok {ret.exitstatus} == 1
438
+ end
439
+ end
440
+ end
441
+
442
+
443
+ topic 'popen2()', tag: 'open3' do
444
+ spec "[!8que2] calls 'Open3.popen2()'." do
445
+ expected = (" 1 AA\n"\
446
+ " 2 BB\n"\
447
+ " 3 CC\n")
448
+ #
449
+ sout, serr = capture_sio do
450
+ arr = popen2("cat -n")
451
+ ok {arr}.length(3)
452
+ stdin, stdout, wait_thread = arr
453
+ stdin.write("AA\nBB\nCC\n")
454
+ stdin.close()
455
+ output = stdout.read()
456
+ ok {output} == expected
457
+ end
458
+ ok {sout} == "$ cat -n\n"
459
+ ok {serr} == ""
460
+ #
461
+ sout, serr = capture_sio do
462
+ output2 = popen2("cat -n") do |*args|
463
+ ok {args}.length(3)
464
+ stdin, stdout, wait_thread = args
465
+ stdin.write("AA\nBB\nCC\n")
466
+ stdin.close()
467
+ stdout.read()
468
+ end
469
+ ok {output2} == expected
470
+ end
471
+ ok {sout} == "$ cat -n\n"
472
+ ok {serr} == ""
473
+ end
474
+ end
475
+
476
+ topic 'popen2e()', tag: 'open3' do
477
+ spec "[!s6g1r] calls 'Open3.popen2e()'." do
478
+ expected = (" 1 AA\n"\
479
+ " 2 BB\n"\
480
+ " 3 CC\n"\
481
+ " 0.00 real 0.00 user 0.00 sys\n")
482
+ #
483
+ sout, serr = capture_sio do
484
+ arr = popen2e("time cat -n")
485
+ ok {arr}.length(3)
486
+ stdin, stdout, wait_thread = arr
487
+ stdin.write("AA\nBB\nCC\n")
488
+ stdin.close()
489
+ output = stdout.read()
490
+ ok {output} == expected
491
+ end
492
+ ok {sout} == "$ time cat -n\n"
493
+ ok {serr} == ""
494
+ #
495
+ sout, serr = capture_sio do
496
+ output2 = popen2e("time cat -n") do |*args|
497
+ ok {args}.length(3)
498
+ stdin, stdout, wait_thread = args
499
+ stdin.write("AA\nBB\nCC\n")
500
+ stdin.close()
501
+ stdout.read()
502
+ end
503
+ ok {output2} == expected
504
+ end
505
+ ok {sout} == "$ time cat -n\n"
506
+ ok {serr} == ""
507
+ end
508
+ end
509
+
510
+ topic 'popen3()', tag: 'open3' do
511
+ spec "[!evlx7] calls 'Open3.popen3()'." do
512
+ expected1 = (" 1 AA\n"\
513
+ " 2 BB\n"\
514
+ " 3 CC\n")
515
+ expected2 = " 0.00 real 0.00 user 0.00 sys\n"
516
+ #
517
+ sout, serr = capture_sio do
518
+ arr = popen3("time cat -n")
519
+ ok {arr}.length(4)
520
+ stdin, stdout, stderr, wait_thread = arr
521
+ stdin.write("AA\nBB\nCC\n")
522
+ stdin.close()
523
+ ok {stdout.read()} == expected1
524
+ ok {stderr.read()} == expected2
525
+ end
526
+ ok {sout} == "$ time cat -n\n"
527
+ ok {serr} == ""
528
+ #
529
+ sout, serr = capture_sio do
530
+ output, error = popen3("time cat -n") do |*args|
531
+ ok {args}.length(4)
532
+ stdin, stdout, stderr, wait_thread = args
533
+ stdin.write("AA\nBB\nCC\n")
534
+ stdin.close()
535
+ [stdout.read(), stderr.read()]
536
+ end
537
+ ok {output} == expected1
538
+ ok {error} == expected2
539
+ end
540
+ ok {sout} == "$ time cat -n\n"
541
+ ok {serr} == ""
542
+ end
543
+ end
544
+
545
+ topic 'capture2()', tag: 'open3' do
546
+ spec "[!5p4dw] calls 'Open3.capture2()'." do
547
+ expected = (" 1 AA\n"\
548
+ " 2 BB\n"\
549
+ " 3 CC\n")
550
+ #
551
+ sout, serr = capture_sio do
552
+ output = capture2("cat -n", stdin_data: "AA\nBB\nCC\n")
553
+ ok {output} == expected
554
+ end
555
+ ok {sout} == "$ cat -n\n"
556
+ ok {serr} == ""
557
+ end
558
+ spec "[!2s1by] error when command failed." do
559
+ sout, serr = capture_sio do
560
+ pr = proc { capture2("grep -q FOOBAR foo1.txt") }
561
+ ok {pr}.raise?(RuntimeError, "Command failed with status (1): grep -q FOOBAR foo1.txt")
562
+ end
563
+ ok {sout} == "$ grep -q FOOBAR foo1.txt\n"
564
+ ok {serr} == ""
565
+ end
566
+ end
567
+
568
+ topic 'capture2e()', tag: 'open3' do
569
+ spec "[!jgn71] calls 'Open3.capture2e()'." do
570
+ expected = (" 1 AA\n"\
571
+ " 2 BB\n"\
572
+ " 3 CC\n"\
573
+ " 0.00 real 0.00 user 0.00 sys\n")
574
+ #
575
+ sout, serr = capture_sio do
576
+ output = capture2e("time cat -n", stdin_data: "AA\nBB\nCC\n")
577
+ ok {output} == expected
578
+ end
579
+ ok {sout} == "$ time cat -n\n"
580
+ ok {serr} == ""
581
+ end
582
+ spec "[!qr3ka] error when command failed." do
583
+ sout, serr = capture_sio do
584
+ pr = proc { capture2e("grep -q FOOBAR foo1.txt") }
585
+ ok {pr}.raise?(RuntimeError, "Command failed with status (1): grep -q FOOBAR foo1.txt")
586
+ end
587
+ ok {sout} == "$ grep -q FOOBAR foo1.txt\n"
588
+ ok {serr} == ""
589
+ end
590
+ end
591
+
592
+ topic 'capture3()', tag: 'open3' do
593
+ spec "[!n91rh] calls 'Open3.capture3()'." do
594
+ expected1 = (" 1 AA\n"\
595
+ " 2 BB\n"\
596
+ " 3 CC\n")
597
+ expected2 = " 0.00 real 0.00 user 0.00 sys\n"
598
+ #
599
+ sout, serr = capture_sio do
600
+ output, error = capture3("time cat -n", stdin_data: "AA\nBB\nCC\n")
601
+ ok {output} == expected1
602
+ ok {error} == expected2
603
+ end
604
+ ok {sout} == "$ time cat -n\n"
605
+ ok {serr} == ""
606
+ end
607
+ spec "[!thnyv] error when command failed." do
608
+ sout, serr = capture_sio do
609
+ pr = proc { capture3("grep -q FOOBAR foo1.txt") }
610
+ ok {pr}.raise?(RuntimeError, "Command failed with status (1): grep -q FOOBAR foo1.txt")
611
+ end
612
+ ok {sout} == "$ grep -q FOOBAR foo1.txt\n"
613
+ ok {serr} == ""
614
+ end
615
+ end
616
+
617
+ topic 'capture2!()', tag: 'open3' do
618
+ spec "[!357e1] ignore errors even if command failed." do
619
+ sout, serr = capture_sio do
620
+ output, process_status = capture2!("grep -q FOOBAR foo1.txt")
621
+ ok {process_status.exitstatus} == 1
622
+ ok {output} == ""
623
+ end
624
+ ok {sout} == "$ grep -q FOOBAR foo1.txt\n"
625
+ ok {serr} == ""
626
+ end
627
+ end
628
+
629
+ topic 'capture2e!()', tag: 'open3' do
630
+ spec "[!o0b7c] ignore errors even if command failed." do
631
+ sout, serr = capture_sio do
632
+ output, process_status = capture2e!("grep -q FOOBAR blabla.txt")
633
+ ok {process_status.exitstatus} == 2
634
+ ok {output} == "grep: blabla.txt: No such file or directory\n"
635
+ end
636
+ ok {sout} == "$ grep -q FOOBAR blabla.txt\n"
637
+ ok {serr} == ""
638
+ end
639
+ end
640
+
641
+ topic 'capture3()', tag: 'open3' do
642
+ spec "[!rwfiu] ignore errors even if command failed." do
643
+ sout, serr = capture_sio do
644
+ output, error, process_status = capture3!("grep -q FOOBAR blabla.txt")
645
+ ok {process_status.exitstatus} == 2
646
+ ok {output} == ""
647
+ ok {error} == "grep: blabla.txt: No such file or directory\n"
648
+ end
649
+ end
650
+ end
651
+
652
+
653
+ topic 'cd()' do
654
+ spec "[!gnmdg] expands file pattern." do
655
+ sout, serr = capture_sio do
656
+ here = Dir.pwd
657
+ cd "d?"
658
+ ok {Dir.pwd} == File.join(here, "d1")
659
+ end
660
+ end
661
+ spec "[!v7bn7] error when pattern not matched to any file." do
662
+ sout, serr = capture_sio do
663
+ pr = proc { cd "blabla*" }
664
+ ok {pr}.raise?(ArgumentError, "cd: blabla*: directory not found.")
665
+ end
666
+ end
667
+ spec "[!08wuv] error when pattern matched to multiple files." do
668
+ sout, serr = capture_sio do
669
+ pr = proc { cd "foo*" }
670
+ ok {pr}.raise?(ArgumentError, "cd: foo*: unexpectedly matched to multiple filenames (foo1.txt, foo2.txt).")
671
+ end
672
+ end
673
+ spec "[!hs7u8] error when argument is not a directory name." do
674
+ sout, serr = capture_sio do
675
+ pr = proc { cd "foo1.txt" }
676
+ ok {pr}.raise?(ArgumentError, "cd: foo1.txt: Not a directory.")
677
+ end
678
+ end
679
+ spec "[!cg5ns] changes current directory." do
680
+ here = Dir.pwd
681
+ begin
682
+ sout, serr = capture_sio() do
683
+ cd "d1/d2"
684
+ ok {Dir.pwd} == here + "/d1/d2"
685
+ end
686
+ ok {Dir.pwd} == here + "/d1/d2"
687
+ ok {sout} == <<-END.gsub(/^ */, '')
688
+ $ cd d1/d2
689
+ END
690
+ ok {serr} == ""
691
+ ensure
692
+ Dir.chdir(here)
693
+ end
694
+ end
695
+ spec "[!uit6q] if block given, then back to current dir." do
696
+ here = Dir.pwd
697
+ sout, serr = capture_sio() do
698
+ cd "d1" do
699
+ ok {Dir.pwd} == here + "/d1"
700
+ cd "d2" do
701
+ ok {Dir.pwd} == here + "/d1/d2"
702
+ end
703
+ ok {Dir.pwd} == here + "/d1"
704
+ end
705
+ ok {Dir.pwd} == here
706
+ end
707
+ ok {sout} == <<-END.gsub(/^ */, '')
708
+ $ cd d1
709
+ $ cd d2
710
+ $ cd -
711
+ $ cd -
712
+ END
713
+ ok {serr} == ""
714
+ end
715
+ spec "[!cg298] returns path before changing directory." do
716
+ here = Dir.pwd
717
+ path = nil
718
+ ret = nil
719
+ capture_sio() do
720
+ ret = cd "d1/d2" do
721
+ path = Dir.pwd
722
+ end
723
+ end
724
+ ok {ret} == here
725
+ ok {ret} != path
726
+ end
727
+ end
728
+
729
+
730
+ topic 'pushd()' do
731
+ spec "[!nvkha] expands file pattern." do
732
+ sout, serr = capture_sio do
733
+ here = Dir.pwd
734
+ pushd "d?" do
735
+ ok {Dir.pwd} == File.join(here, "d1")
736
+ end
737
+ end
738
+ end
739
+ spec "[!q3itn] error when pattern not matched to any file." do
740
+ sout, serr = capture_sio do
741
+ pr = proc { pushd "blabla*" do end }
742
+ ok {pr}.raise?(ArgumentError, "pushd: blabla*: directory not found.")
743
+ end
744
+ end
745
+ spec "[!hveaj] error when pattern matched to multiple files." do
746
+ sout, serr = capture_sio do
747
+ pr = proc { pushd "foo*" do end }
748
+ ok {pr}.raise?(ArgumentError, "pushd: foo*: unexpectedly matched to multiple filenames (foo1.txt, foo2.txt).")
749
+ end
750
+ end
751
+ spec "[!y6cq9] error when argument is not a directory name." do
752
+ sout, serr = capture_sio do
753
+ pr = proc { pushd "foo1.txt" do end }
754
+ ok {pr}.raise?(ArgumentError, "pushd: foo1.txt: Not a directory.")
755
+ end
756
+ end
757
+ #
758
+ spec "[!7ksfd] replaces home path with '~'." do
759
+ home = home2 = nil
760
+ sout, serr = capture_sio do
761
+ home = File.expand_path("~")
762
+ ok {home} != "~"
763
+ pushd home do
764
+ puts Dir.pwd
765
+ home2 = Dir.pwd
766
+ pushd "/" do
767
+ puts Dir.pwd
768
+ end
769
+ end
770
+ end
771
+ skip_when home != home2, "home directory may be a symbolic link"
772
+ ok {sout} =~ /^\$ popd \# back to ~$/
773
+ end
774
+ spec "[!xl6lg] raises error when block not given." do
775
+ pr = proc { pushd "d1/d2" }
776
+ ok {pr}.raise?(ArgumentError, "pushd: requires block argument.")
777
+ end
778
+ spec "[!rxtd0] changes directory and yields block." do
779
+ here = Dir.pwd
780
+ path = nil
781
+ sout, serr = capture_sio do
782
+ pushd "d1/d2" do
783
+ path = Dir.pwd
784
+ end
785
+ end
786
+ home = File.expand_path('~')
787
+ here2 = here.start_with?(home) ? here.sub(home, '~') : here
788
+ ok {path} != nil
789
+ ok {path} != here
790
+ ok {path} == File.join(here, "d1/d2")
791
+ ok {sout} == ("$ pushd d1/d2\n"\
792
+ "$ popd # back to #{here2}\n")
793
+ end
794
+ spec "[!9jszw] back to origin directory after yielding block." do
795
+ here = Dir.pwd
796
+ path = nil
797
+ sout, serr = capture_sio do
798
+ pushd "d1/d2" do
799
+ path = Dir.pwd
800
+ end
801
+ end
802
+ ok {path} != nil
803
+ ok {path} != here
804
+ ok {Dir.pwd} == here
805
+ end
806
+ end
807
+
808
+
809
+ topic 'cp()' do
810
+
811
+ spec "[!mtuec] echoback copy command and arguments." do
812
+ sout, serr = capture_sio do
813
+ cp "foo1.txt", "foo9.txt"
814
+ end
815
+ ok {sout} == "$ cp foo1.txt foo9.txt\n"
816
+ #
817
+ sout, serr = capture_sio do
818
+ cp :pr, "foo*.txt", to: "d1"
819
+ end
820
+ ok {sout} == "$ cp -pr foo*.txt d1\n"
821
+ end
822
+
823
+ case_when "[!u98f8] when `to:` keyword arg not specified..." do
824
+ spec "[!u39p0] error when number of arguments is not 2." do
825
+ sout, serr = capture_sio do
826
+ pr = proc { cp() }
827
+ ok {pr}.raise?(ArgumentError, "cp: requires two arguments.")
828
+ pr = proc { cp "foo1.txt" }
829
+ ok {pr}.raise?(ArgumentError, "cp: requires two arguments.")
830
+ pr = proc { cp "foo1.txt", "foo2.txt", "foo3.txt" }
831
+ ok {pr}.raise?(ArgumentError, "cp: too much arguments.")
832
+ end
833
+ end
834
+ spec "[!fux6x] error when source pattern matched to multiple files." do
835
+ sout, serr = capture_sio do
836
+ pr = proc { cp "foo?.txt", "blabla.txt" }
837
+ ok {pr}.raise?(ArgumentError, "cp: foo?.txt: unexpectedly matched to multiple files (foo1.txt, foo2.txt).")
838
+ end
839
+ end
840
+ spec "[!y74ux] error when destination pattern matched to multiple files." do
841
+ sout, serr = capture_sio do
842
+ pr = proc { cp "d1/bar.txt", "foo*.txt" }
843
+ ok {pr}.raise?(ArgumentError, "cp: foo*.txt: unexpectedly matched to multiple files (foo1.txt, foo2.txt).")
844
+ end
845
+ end
846
+ #
847
+ spec "[!qfidz] error when destination is a directory." do
848
+ sout, serr = capture_sio do
849
+ pr = proc { cp "foo1.txt", "d1" }
850
+ ok {pr}.raise?(ArgumentError, "cp: d1: cannot copy into directory (requires `to: 'd1'` keyword option).")
851
+ end
852
+ end
853
+ spec "[!073so] (cp) error when destination already exists to avoid overwriting it." do
854
+ sout, serr = capture_sio do
855
+ pr = proc { cp "foo1.txt", "foo2.txt" }
856
+ ok {pr}.raise?(ArgumentError, "cp: foo2.txt: file already exists (to overwrite it, call `cp!` instead of `cp`).")
857
+ end
858
+ end
859
+ spec "[!0tw8r] error when source is a directory but '-r' not specified." do
860
+ sout, serr = capture_sio do
861
+ pr = proc { cp "d1", "d9" }
862
+ ok {pr}.raise?(ArgumentError, "cp: d1: is a directory (requires `:-r` option).")
863
+ end
864
+ end
865
+ spec "[!lf6qi] error when target already exists." do
866
+ sout, serr = capture_sio do
867
+ dummy_dir("d9")
868
+ pr = proc { cp :r, "d1", "d9" }
869
+ ok {pr}.raise?(ArgumentError, "cp: d9: already exists.")
870
+ end
871
+ end
872
+ spec "[!4xxpe] error when source is a special file." do
873
+ sout, serr = capture_sio do
874
+ pr = proc { cp :r, "/dev/null", "d9" }
875
+ ok {pr}.raise?(ArgumentError, "cp: /dev/null: cannot copy special file.")
876
+ end
877
+ end
878
+ spec "[!lr2bj] error when source file not found and '-f' option not specified." do
879
+ sout, serr = capture_sio do
880
+ pr = proc { cp "blabla.txt", "blabla2.txt" }
881
+ ok {pr}.raise?(ArgumentError, "cp: blabla.txt: not found.")
882
+ end
883
+ end
884
+ spec "[!urh40] do nothing if source file not found and '-f' option specified." do
885
+ sout, serr = capture_sio do
886
+ cp :f, "blabla.txt", "blabla2.txt"
887
+ ok {"blabla2.txt"}.not_exist?
888
+ cp :f, "bla*.txt", "blabla2.txt"
889
+ ok {"blabla2.txt"}.not_exist?
890
+ end
891
+ end
892
+ spec "[!kqgdl] copy a directory recursively if '-r' option specified." do
893
+ sout, serr = capture_sio do
894
+ ok {"d9"}.not_exist?
895
+ cp :r, "d1", "d9"
896
+ ok {"d9"}.dir_exist?
897
+ ok {"d9/bar.txt"}.file_exist?
898
+ ok {"d9/d2/baz.txt"}.file_exist?
899
+ #
900
+ cp :r, "foo1.txt", "blabla.txt"
901
+ ok {"blabla.txt"}.file_exist?
902
+ end
903
+ end
904
+ spec "[!ko4he] copy a file into new file if '-r' option not specifieid." do
905
+ sout, serr = capture_sio do
906
+ cp "foo1.txt", "blabla.txt"
907
+ ok {"blabla.txt"}.file_exist?
908
+ end
909
+ end
910
+ spec "[!lac46] keeps file mtime if '-p' option specified." do
911
+ sout, serr = capture_sio do
912
+ ctime1 = File.ctime("d1/bar.txt")
913
+ mtime1 = File.mtime("d1/bar.txt")
914
+ atime1 = File.atime("d1/bar.txt")
915
+ #mtime2 = mtime1 - 900
916
+ #atime2 = atime1 - 600
917
+ mtime2 = (x = mtime1 - 900; Time.new(x.year, x.month, x.day, x.hour, x.min, x.sec))
918
+ atime2 = (x = atime1 - 600; Time.new(x.year, x.month, x.day, x.hour, x.min, x.sec))
919
+ #
920
+ File.utime(atime2, mtime2, "d1/bar.txt")
921
+ cp :p, "d1/bar.txt", "blabla.txt"
922
+ ok {File.atime("blabla.txt")} != atime1
923
+ ok {File.atime("blabla.txt")} == atime2 # !!!
924
+ ok {File.mtime("blabla.txt")} != mtime1
925
+ ok {File.mtime("blabla.txt")} == mtime2 # !!!
926
+ ok {File.ctime("blabla.txt")} != ctime1
927
+ #
928
+ cp :pr, "d1", "d9"
929
+ ok {File.atime("d9/bar.txt")} != atime1
930
+ ok {File.atime("d9/bar.txt")} == atime2 # !!!
931
+ ok {File.mtime("d9/bar.txt")} != mtime1
932
+ ok {File.mtime("d9/bar.txt")} == mtime2 # !!!
933
+ ok {File.ctime("d9/bar.txt")} != ctime1
934
+ end
935
+ end
936
+ spec "[!d49vw] not keep file mtime if '-p' option not specified." do
937
+ sout, serr = capture_sio do
938
+ ctime1 = File.ctime("d1/bar.txt")
939
+ mtime1 = File.mtime("d1/bar.txt")
940
+ atime1 = File.atime("d1/bar.txt")
941
+ #mtime2 = mtime1 - 900
942
+ #atime2 = atime1 - 600
943
+ mtime2 = (x = mtime1 - 900; Time.new(x.year, x.month, x.day, x.hour, x.min, x.sec))
944
+ atime2 = (x = atime1 - 600; Time.new(x.year, x.month, x.day, x.hour, x.min, x.sec))
945
+ #
946
+ File.utime(atime2, mtime2, "d1/bar.txt")
947
+ cp "d1/bar.txt", "blabla.txt"
948
+ ok {File.atime("blabla.txt")} != atime1
949
+ ok {File.atime("blabla.txt")} != atime2
950
+ ok {File.mtime("blabla.txt")} != mtime1
951
+ ok {File.mtime("blabla.txt")} != mtime2 # !!!
952
+ ok {File.ctime("blabla.txt")} != ctime1
953
+ #
954
+ cp :r, "d1", "d9"
955
+ ok {File.atime("d9/bar.txt")} != atime1
956
+ ok {File.atime("d9/bar.txt")} != atime2
957
+ ok {File.mtime("d9/bar.txt")} != mtime1
958
+ ok {File.mtime("d9/bar.txt")} != mtime2 # !!!
959
+ ok {File.ctime("d9/bar.txt")} != ctime1
960
+ end
961
+ end
962
+ spec "[!ubthp] creates hard link instead of copy if '-l' option specified." do
963
+ sout, serr = capture_sio do
964
+ cp "foo1.txt", "foo8.txt"
965
+ ok {File.identical?("foo1.txt", "foo8.txt")} == false
966
+ cp :l, "foo1.txt", "foo9.txt"
967
+ ok {File.identical?("foo1.txt", "foo9.txt")} == true
968
+ end
969
+ end
970
+ spec "[!yu51t] error when copying supecial files such as character device." do
971
+ sout, serr = capture_sio do
972
+ pr = proc { cp "/dev/null", "null" }
973
+ ok {pr}.raise?(ArgumentError, "cp: /dev/null: cannot copy special file.")
974
+ end
975
+ end
976
+ end
977
+
978
+ case_else "[!z8xce] when `to:` keyword arg specified..." do
979
+ spec "[!ms2sv] error when destination directory not exist." do
980
+ sout, serr = capture_sio do
981
+ pr = proc { cp "foo?.txt", to: "dir9" }
982
+ ok {pr}.raise?(ArgumentError, "cp: dir9: directory not found.")
983
+ end
984
+ end
985
+ spec "[!q9da3] error when destination pattern matched to multiple filenames." do
986
+ sout, serr = capture_sio do
987
+ pr = proc { cp "d1", to: "foo?.txt" }
988
+ ok {pr}.raise?(ArgumentError, "cp: foo?.txt: unexpectedly matched to multiple filenames (foo1.txt, foo2.txt).")
989
+ end
990
+ end
991
+ spec "[!lg3uz] error when destination is not a directory." do
992
+ sout, serr = capture_sio do
993
+ pr = proc { cp "d1", to: "foo1.txt" }
994
+ ok {pr}.raise?(ArgumentError, "cp: foo1.txt: Not a directory.")
995
+ end
996
+ end
997
+ spec "[!slavo] error when file not exist but '-f' option not specified." do
998
+ sout, serr = capture_sio do
999
+ pr = proc { cp "blabla", to: "d1" }
1000
+ ok {pr}.raise?(ArgumentError, "cp: blabla: file or directory not found (add '-f' option to ignore missing files).")
1001
+ end
1002
+ end
1003
+ spec "[!1ceaf] (cp) error when target file or directory already exists." do
1004
+ sout, serr = capture_sio do
1005
+ dummy_file("d1/foo1.txt", "tmp")
1006
+ pr = proc { cp "foo?.txt", to: "d1" }
1007
+ ok {pr}.raise?(ArgumentError, "cp: d1/foo1.txt: file or directory already exists (to overwrite it, call 'cp!' instead of 'cp').")
1008
+ end
1009
+ end
1010
+ #
1011
+ spec "[!bi897] error when copying directory but '-r' option not specified." do
1012
+ sout, serr = capture_sio do
1013
+ dummy_dir("d9")
1014
+ pr = proc { cp "d1", to: "d9" }
1015
+ ok {pr}.raise?(ArgumentError, "cp: d1: cannot copy directory (add '-r' option to copy it).")
1016
+ end
1017
+ end
1018
+ spec "[!654d2] copy files recursively if '-r' option specified." do
1019
+ sout, serr = capture_sio do
1020
+ dummy_dir("d9")
1021
+ ok {"d9"}.dir_exist?
1022
+ cp :r, "foo*.txt", "d1", to: "d9"
1023
+ ok {"d9/foo1.txt"}.file_exist?
1024
+ ok {"d9/foo2.txt"}.file_exist?
1025
+ ok {"d9/d1/bar.txt"}.file_exist?
1026
+ ok {"d9/d1/d2/baz.txt"}.file_exist?
1027
+ end
1028
+ end
1029
+ spec "[!i5g8r] copy files non-recursively if '-r' option not specified." do
1030
+ sout, serr = capture_sio do
1031
+ dummy_dir("d9")
1032
+ ok {"d9"}.dir_exist?
1033
+ cp "foo*.txt", "d1/**/*.txt", to: "d9"
1034
+ ok {"d9/foo1.txt"}.file_exist?
1035
+ ok {"d9/foo2.txt"}.file_exist?
1036
+ ok {"d9/bar.txt"}.file_exist?
1037
+ ok {"d9/baz.txt"}.file_exist?
1038
+ end
1039
+ end
1040
+ spec "[!k8gyx] keeps file timestamp (mtime) if '-p' option specified." do
1041
+ sout, serr = capture_sio do
1042
+ ctime1 = File.ctime("d1/bar.txt")
1043
+ mtime1 = File.mtime("d1/bar.txt")
1044
+ atime1 = File.atime("d1/bar.txt")
1045
+ #mtime2 = mtime1 - 30
1046
+ #atime2 = atime1 - 90
1047
+ mtime2 = (x = mtime1 - 900; Time.new(x.year, x.month, x.day, x.hour, x.min, x.sec))
1048
+ atime2 = (x = atime1 - 600; Time.new(x.year, x.month, x.day, x.hour, x.min, x.sec))
1049
+ File.utime(atime2, mtime2, "d1/bar.txt")
1050
+ #
1051
+ dummy_dir("d9")
1052
+ cp :p, "foo*.txt", "d1/**/*.txt", to: "d9"
1053
+ ok {File.ctime("d9/bar.txt")} != ctime1
1054
+ ok {File.mtime("d9/bar.txt")} != mtime1
1055
+ ok {File.mtime("d9/bar.txt")} == mtime2 # !!!
1056
+ ok {File.atime("d9/bar.txt")} != atime1
1057
+ ok {File.atime("d9/bar.txt")} == atime2 # !!!
1058
+ #
1059
+ cp :pr, "d1", to: "d9"
1060
+ ok {File.ctime("d9/d1/bar.txt")} != ctime1
1061
+ ok {File.mtime("d9/d1/bar.txt")} != mtime1
1062
+ ok {File.mtime("d9/d1/bar.txt")} == mtime2 # !!!
1063
+ ok {File.atime("d9/d1/bar.txt")} != atime1
1064
+ ok {File.atime("d9/d1/bar.txt")} == atime2 # !!!
1065
+ end
1066
+ end
1067
+ spec "[!zoun9] not keep file timestamp (mtime) if '-p' option not specified." do
1068
+ sout, serr = capture_sio do
1069
+ ctime1 = File.ctime("d1/bar.txt")
1070
+ mtime1 = File.mtime("d1/bar.txt")
1071
+ atime1 = File.atime("d1/bar.txt")
1072
+ mtime2 = mtime1 - 30
1073
+ atime2 = atime1 - 90
1074
+ File.utime(atime2, mtime2, "d1/bar.txt")
1075
+ #
1076
+ dummy_dir("d9")
1077
+ cp "foo*.txt", "d1/**/*.txt", to: "d9"
1078
+ ok {File.ctime("d9/bar.txt")} != ctime1
1079
+ ok {File.mtime("d9/bar.txt")} != mtime1
1080
+ ok {File.mtime("d9/bar.txt")} != mtime2 # !!!
1081
+ ok {File.atime("d9/bar.txt")} != atime1
1082
+ ok {File.atime("d9/bar.txt")} != atime2
1083
+ #
1084
+ cp :r, "d1", to: "d9"
1085
+ ok {File.ctime("d9/d1/bar.txt")} != ctime1
1086
+ ok {File.mtime("d9/d1/bar.txt")} != mtime1
1087
+ ok {File.mtime("d9/d1/bar.txt")} != mtime2 # !!!
1088
+ ok {File.atime("d9/d1/bar.txt")} != atime1
1089
+ ok {File.atime("d9/d1/bar.txt")} != atime2
1090
+ end
1091
+ end
1092
+ spec "[!p7ah8] creates hard link instead of copy if '-l' option specified." do
1093
+ sout, serr = capture_sio do
1094
+ cp :l, "foo*.txt", to: "d1"
1095
+ ok {File.identical?("foo1.txt", "d1/foo1.txt")} == true
1096
+ ok {File.identical?("foo2.txt", "d1/foo2.txt")} == true
1097
+ end
1098
+ end
1099
+ spec "[!e90ii] error when copying supecial files such as character device." do
1100
+ sout, serr = capture_sio do
1101
+ pr = proc { cp "/dev/null", to: "d1" }
1102
+ ok {pr}.raise?(ArgumentError, "cp: /dev/null: cannot copy characterSpecial file.")
1103
+ end
1104
+ end
1105
+ end
1106
+
1107
+ end
1108
+
1109
+
1110
+ topic 'cp!()' do
1111
+ spec "[!cpr7l] (cp!) overwrites existing destination file." do
1112
+ sout, serr = capture_sio do
1113
+ dummy_file("foo9.txt", "")
1114
+ ok {"foo9.txt"}.file_exist?
1115
+ cp! "foo1.txt", "foo9.txt"
1116
+ ok {"foo9.txt"}.file_exist?
1117
+ ok {File.read("foo9.txt")} == File.read("foo1.txt")
1118
+ #
1119
+ pr = proc { cp "foo1.txt", "foo9.txt" }
1120
+ ok {pr}.raise?(ArgumentError, "cp: foo9.txt: file already exists (to overwrite it, call `cp!` instead of `cp`).")
1121
+ end
1122
+ end
1123
+ spec "[!melhx] (cp!) overwrites existing files." do
1124
+ sout, serr = capture_sio do
1125
+ dummy_dir("d9")
1126
+ dummy_file("d9/foo1.txt", "")
1127
+ ok {"d9/foo1.txt"}.file_exist?
1128
+ cp! "foo1.txt", to: "d9"
1129
+ ok {"d9/foo1.txt"}.file_exist?
1130
+ ok {File.read("d9/foo1.txt")} == File.read("foo1.txt")
1131
+ #
1132
+ pr = proc { cp "foo1.txt", to: "d9" }
1133
+ ok {pr}.raise?(ArgumentError, "cp: d9/foo1.txt: file or directory already exists (to overwrite it, call 'cp!' instead of 'cp').")
1134
+ end
1135
+ end
1136
+ end
1137
+
1138
+
1139
+ topic 'mv()' do
1140
+
1141
+ spec "[!ajm59] echoback command and arguments." do
1142
+ sout, serr = capture_sio do
1143
+ mv "foo1.txt", "foo9.txt"
1144
+ mv "foo2.txt", to: "d1"
1145
+ end
1146
+ ok {sout} == ("$ mv foo1.txt foo9.txt\n"\
1147
+ "$ mv foo2.txt d1\n")
1148
+ end
1149
+
1150
+ case_when "[!g732t] when `to:` keyword argument not specified..." do
1151
+ spec "[!0f106] error when number of arguments is not 2." do
1152
+ sout, serr = capture_sio do
1153
+ pr = proc { mv() }
1154
+ ok {pr}.raise?(ArgumentError, "mv: requires two arguments.")
1155
+ pr = proc { mv "foo1.txt" }
1156
+ ok {pr}.raise?(ArgumentError, "mv: requires two arguments.")
1157
+ pr = proc { mv "foo1.txt", "foo2.txt", "foo3.txt" }
1158
+ ok {pr}.raise?(ArgumentError, "mv: too much arguments.")
1159
+ end
1160
+ end
1161
+ spec "[!xsti2] error when source pattern matched to multiple files." do
1162
+ sout, serr = capture_sio do
1163
+ pr = proc { mv "foo?.txt", "blabla.txt" }
1164
+ ok {pr}.raise?(ArgumentError, "mv: foo?.txt: unexpectedly matched to multiple files (foo1.txt, foo2.txt).")
1165
+ end
1166
+ end
1167
+ spec "[!4wam3] error when destination pattern matched to multiple files." do
1168
+ sout, serr = capture_sio do
1169
+ pr = proc { mv "d1/bar.txt", "foo*.txt" }
1170
+ ok {pr}.raise?(ArgumentError, "mv: foo*.txt: unexpectedly matched to multiple files (foo1.txt, foo2.txt).")
1171
+ end
1172
+ end
1173
+ #
1174
+ spec "[!ude1j] cannot move file into existing directory." do
1175
+ sout, serr = capture_sio do
1176
+ pr = proc { mv "foo1.txt", "d1" }
1177
+ ok {pr}.raise?(ArgumentError, "mv: cannot move file 'foo1.txt' into directory 'd1' without 'to:' keyword option.")
1178
+ end
1179
+ end
1180
+ spec "[!2aws0] cannt rename directory into existing file or directory." do
1181
+ sout, serr = capture_sio do
1182
+ pr = proc { mv "d1", "foo1.txt" }
1183
+ ok {pr}.raise?(ArgumentError, "mv: cannot rename directory 'd1' to existing file or directory.")
1184
+ end
1185
+ end
1186
+ spec "[!3fbpu] (mv) error when destination file already exists." do
1187
+ sout, serr = capture_sio do
1188
+ pr = proc { mv "foo1.txt", "foo2.txt" }
1189
+ ok {pr}.raise?(ArgumentError, "mv: foo2.txt: already exists (to overwrite it, call `mv!` instead of `mv`).")
1190
+ end
1191
+ end
1192
+ spec "[!397kn] do nothing when file or directory not found but '-f' option specified." do
1193
+ sout, serr = capture_sio do
1194
+ mv :f, "blabla.txt", "blabla2.txt"
1195
+ ok {"blabla2.txt"}.not_exist?
1196
+ end
1197
+ end
1198
+ spec "[!1z89i] error when source file or directory not found." do
1199
+ sout, serr = capture_sio do
1200
+ pr = proc { mv "blabla.txt", "blabla2.txt" }
1201
+ ok {pr}.raise?(ArgumentError, "mv: blabla.txt: not found.")
1202
+ end
1203
+ end
1204
+ spec "[!9eqt3] rename file or directory." do
1205
+ sout, serr = capture_sio do
1206
+ s = File.read("foo1.txt")
1207
+ mv "foo1.txt", "blabla.txt"
1208
+ ok {"foo1.txt"}.not_exist?
1209
+ ok {"blabla.txt"}.file_exist?
1210
+ ok {File.read("blabla.txt")} == s
1211
+ #
1212
+ mv "d1", "d9"
1213
+ ok {"d1"}.not_exist?
1214
+ ok {"d9"}.dir_exist?
1215
+ end
1216
+ end
1217
+ end
1218
+
1219
+ case_else "[!iu87y] when `to:` keyword argument specified..." do
1220
+ spec "[!wf6pc] error when destination directory not exist." do
1221
+ sout, serr = capture_sio do
1222
+ pr = proc { mv "foo?.txt", to: "dir9" }
1223
+ ok {pr}.raise?(ArgumentError, "mv: dir9: directory not found.")
1224
+ end
1225
+ end
1226
+ spec "[!8v4dn] error when destination pattern matched to multiple filenames." do
1227
+ sout, serr = capture_sio do
1228
+ pr = proc { mv "d1", to: "foo?.txt" }
1229
+ ok {pr}.raise?(ArgumentError, "mv: foo?.txt: unexpectedly matched to multiple filenames (foo1.txt, foo2.txt).")
1230
+ end
1231
+ end
1232
+ spec "[!ppr6n] error when destination is not a directory." do
1233
+ sout, serr = capture_sio do
1234
+ pr = proc { mv "d1", to: "foo1.txt" }
1235
+ ok {pr}.raise?(ArgumentError, "mv: foo1.txt: Not a directory.")
1236
+ end
1237
+ end
1238
+ spec "[!bjqwi] error when file not exist but '-f' option not specified." do
1239
+ sout, serr = capture_sio do
1240
+ pr = proc { mv "blabla", to: "d1" }
1241
+ ok {pr}.raise?(ArgumentError, "mv: blabla: file or directory not found (add '-f' option to ignore missing files).")
1242
+ end
1243
+ end
1244
+ spec "[!k21ns] (mv) error when target file or directory already exists." do
1245
+ sout, serr = capture_sio do
1246
+ dummy_file("d1/foo1.txt", "tmp")
1247
+ pr = proc { mv "foo?.txt", to: "d1" }
1248
+ ok {pr}.raise?(ArgumentError, "mv: d1/foo1.txt: file or directory already exists (to overwrite it, call 'mv!' instead of 'mv').")
1249
+ end
1250
+ end
1251
+ #
1252
+ spec "[!ri2ia] move files into existing directory." do
1253
+ sout, serr = capture_sio do
1254
+ mv "foo?.txt", to: "d1/d2"
1255
+ ok {"foo1.txt"}.not_exist?
1256
+ ok {"foo2.txt"}.not_exist?
1257
+ ok {"d1/d2/foo1.txt"}.file_exist?
1258
+ ok {"d1/d2/foo2.txt"}.file_exist?
1259
+ end
1260
+ end
1261
+ end
1262
+
1263
+ end
1264
+
1265
+
1266
+ topic 'mv!()' do
1267
+ spec "[!zpojx] (mv!) overwrites existing files." do
1268
+ sout, serr = capture_sio do
1269
+ pr = proc { mv "foo1.txt", "foo2.txt" }
1270
+ ok {pr}.raise?(ArgumentError, "mv: foo2.txt: already exists (to overwrite it, call `mv!` instead of `mv`).")
1271
+ #
1272
+ s = File.read("foo2.txt")
1273
+ mv! "foo1.txt", "foo2.txt"
1274
+ ok {"foo1.txt"}.not_exist?
1275
+ ok {"foo2.txt"}.file_exist?
1276
+ ok {File.read("foo2.txt")} != s
1277
+ end
1278
+ end
1279
+ spec "[!vcaf5] (mv!) overwrites existing files." do
1280
+ sout, serr = capture_sio do
1281
+ mv "foo1.txt", to: "d1"
1282
+ ok {"d1/foo1.txt"}.file_exist?
1283
+ #
1284
+ mv "d1/foo1.txt", "d1/foo2.txt"
1285
+ pr = proc { mv "foo2.txt", to: "d1" }
1286
+ ok {pr}.raise?(ArgumentError, "mv: d1/foo2.txt: file or directory already exists (to overwrite it, call 'mv!' instead of 'mv').")
1287
+ end
1288
+ end
1289
+ end
1290
+
1291
+
1292
+ topic 'rm()' do
1293
+ spec "[!bikrs] echoback command and arguments." do
1294
+ sout, serr = capture_sio do
1295
+ rm "foo*.txt"
1296
+ end
1297
+ ok {sout} == "$ rm foo*.txt\n"
1298
+ end
1299
+ spec "[!va1j0] error when file not exist but '-f' option not specified." do
1300
+ sout, serr = capture_sio do
1301
+ pr = proc { rm "foo*.txt", "blabla*.txt" }
1302
+ ok {pr}.raise?(ArgumentError, "rm: blabla*.txt: file or directory not found (add '-f' option to ignore missing files).")
1303
+ ok {"foo1.txt"}.file_exist?
1304
+ #
1305
+ end
1306
+ end
1307
+ spec "[!t6vhx] ignores missing files if '-f' option specified." do
1308
+ sout, serr = capture_sio do
1309
+ rm :f, "foo*.txt", "blabla*.txt"
1310
+ ok {"foo1.txt"}.not_exist?
1311
+ end
1312
+ end
1313
+ spec "[!o92yi] cannot remove directory unless '-r' option specified." do
1314
+ sout, serr = capture_sio do
1315
+ pr = proc { rm "d1" }
1316
+ ok {pr}.raise?(ArgumentError, "rm: d1: cannot remove directory (add '-r' option to remove it).")
1317
+ end
1318
+ end
1319
+ spec "[!srx8w] remove directories recursively if '-r' option specified." do
1320
+ sout, serr = capture_sio do
1321
+ ok {"d1"}.dir_exist?
1322
+ rm :r, "d1"
1323
+ ok {"d1"}.not_exist?
1324
+ end
1325
+ end
1326
+ spec "[!mdgjc] remove files if '-r' option not specified." do
1327
+ sout, serr = capture_sio do
1328
+ rm "foo*.txt"
1329
+ ok {"foo1.txt"}.not_exist?
1330
+ ok {"foo2.txt"}.not_exist?
1331
+ end
1332
+ end
1333
+ end
1334
+
1335
+
1336
+ topic 'mkdir()' do
1337
+ spec "[!wd7rm] error when mode is invalid." do
1338
+ sout, serr = capture_sio do
1339
+ pr = proc { mkdir :m, "a+x" }
1340
+ ok {pr}.raise?(ArgumentError, "mkdir: a+x: '-m' option doesn't support this style mode (use '0755' tyle instead).")
1341
+ #
1342
+ pr = proc { mkdir :m, "+x" }
1343
+ ok {pr}.raise?(ArgumentError, "mkdir: +x: invalid mode.")
1344
+ end
1345
+ end
1346
+ spec "[!xusor] raises error when argument not specified." do
1347
+ sout, serr = capture_sio do
1348
+ pr = proc { mkdir() }
1349
+ ok {pr}.raise?(ArgumentError, "mkdir: argument required.")
1350
+ end
1351
+ end
1352
+ spec "[!51pmg] error when directory already exists but '-p' option not specified." do
1353
+ sout, serr = capture_sio do
1354
+ pr = proc { mkdir "d1" }
1355
+ ok {pr}.raise?(ArgumentError, "mkdir: d1: directory already exists.")
1356
+ end
1357
+ end
1358
+ spec "[!pydy1] ignores existing directories if '-p' option specified." do
1359
+ sout, serr = capture_sio do
1360
+ ok {"d1"}.dir_exist?
1361
+ mkdir :p, "d1"
1362
+ ok {"d1"}.dir_exist?
1363
+ end
1364
+ end
1365
+ spec "[!om8a6] error when file already exists." do
1366
+ sout, serr = capture_sio do
1367
+ pr = proc { mkdir "foo1.txt" }
1368
+ ok {pr}.raise?(ArgumentError, "mkdir: foo1.txt: file exists.")
1369
+ end
1370
+ end
1371
+ spec "[!xx7mv] error when parent directory not exist but '-p' option not specified." do
1372
+ sout, serr = capture_sio do
1373
+ pr = proc { mkdir "d1/a/b" }
1374
+ ok {pr}.raise?(ArgumentError, "mkdir: d1/a/b: parent directory not exists (add '-p' to create it).")
1375
+ end
1376
+ end
1377
+ spec "[!jc8hm] '-m' option specifies mode of new directories." do
1378
+ sout, serr = capture_sio do
1379
+ ok {"d9"}.not_exist?
1380
+ mkdir :m, 0750, "d9"
1381
+ ok {"d9"}.dir_exist?
1382
+ ok {File.stat("d9").mode & 0777} == 0750
1383
+ #
1384
+ mkdir :pm, 0705, "d9/a/b"
1385
+ ok {"d9/a/b"}.dir_exist?
1386
+ ok {File.stat("d9/a/b").mode & 0777} == 0705
1387
+ end
1388
+ end
1389
+ spec "[!0zeu3] create intermediate path if '-p' option specified." do
1390
+ sout, serr = capture_sio do
1391
+ ok {"d1/a/b"}.not_exist?
1392
+ mkdir :p, "d1/a/b"
1393
+ ok {"d1/a/b"}.dir_exist?
1394
+ #
1395
+ ok {"d9/a/b"}.not_exist?
1396
+ mkdir :p, "d9/a/b"
1397
+ ok {"d9/a/b"}.dir_exist?
1398
+ end
1399
+ end
1400
+ spec "[!l0pr8] create directories if '-p' option not specified." do
1401
+ sout, serr = capture_sio do
1402
+ mkdir :p, "aa", "bb", "cc"
1403
+ ok {"aa"}.dir_exist?
1404
+ ok {"bb"}.dir_exist?
1405
+ ok {"cc"}.dir_exist?
1406
+ end
1407
+ end
1408
+ end
1409
+
1410
+
1411
+ topic 'rmdir()' do
1412
+ spec "[!bqhdd] error when argument not specified." do
1413
+ sout, serr = capture_sio do
1414
+ pr = proc { rmdir() }
1415
+ ok {pr}.raise?(ArgumentError, "rmdir: argument required.")
1416
+ end
1417
+ end
1418
+ spec "[!o1k3g] error when directory not exist." do
1419
+ sout, serr = capture_sio do
1420
+ pr = proc { rmdir "d9" }
1421
+ ok {pr}.raise?(ArgumentError, "rmdir: d9: No such file or directory.")
1422
+ end
1423
+ end
1424
+ spec "[!ch5rq] error when directory is a symbolic link." do
1425
+ sout, serr = capture_sio do
1426
+ File.symlink("foo1.txt", "foo1.lnk")
1427
+ pr = proc { rmdir "foo1.lnk" }
1428
+ ok {pr}.raise?(ArgumentError, "rmdir: foo1.lnk: Not a directory.")
1429
+ end
1430
+ end
1431
+ spec "[!igfti] error when directory is not empty." do
1432
+ sout, serr = capture_sio do
1433
+ pr = proc { rmdir "d1" }
1434
+ #ok {pr}.raise?(Errno::ENOTEMPTY, "Directory not empty @ dir_s_rmdir - d9")
1435
+ ok {pr}.raise?(ArgumentError, "rmdir: d1: Directory not empty.")
1436
+ end
1437
+ end
1438
+ spec "[!qnnqy] error when argument is not a directory." do
1439
+ sout, serr = capture_sio do
1440
+ pr = proc { rmdir "foo1.txt" }
1441
+ ok {pr}.raise?(ArgumentError, "rmdir: foo1.txt: Not a directory.")
1442
+ end
1443
+ end
1444
+ spec "[!jgmw7] remove empty directories." do
1445
+ sout, serr = capture_sio do
1446
+ dummy_dir "d9/a/b"
1447
+ ok {"d9/a/b"}.dir_exist?
1448
+ rmdir "d9/a/b"
1449
+ ok {"d9/a/b"}.not_exist?
1450
+ rmdir "d9/a"
1451
+ ok {"d9/a"}.not_exist?
1452
+ end
1453
+ end
1454
+ end
1455
+
1456
+
1457
+ topic 'ln()' do
1458
+ spec "[!ycp6e] echobacks command and arguments." do
1459
+ sout, serr = capture_sio do
1460
+ ln "foo1.txt", "foo8.txt"
1461
+ ln :s, "foo2.txt", "foo9.txt"
1462
+ end
1463
+ ok {sout} == ("$ ln -n foo1.txt foo8.txt\n"\
1464
+ "$ ln -sn foo2.txt foo9.txt\n")
1465
+ end
1466
+ spec "[!umk6m] keyword arg `to: xx` is echobacked as `-t xx`." do
1467
+ sout, serr = capture_sio do
1468
+ ln "foo*.txt", to: "d1"
1469
+ end
1470
+ ok {sout} == ("$ ln -t d1 -n foo*.txt\n")
1471
+ end
1472
+
1473
+ case_when "[!qtbp4] when `to:` keyword argument not specified..." do
1474
+ spec "[!n1zpi] error when number of arguments is not 2." do
1475
+ sout, serr = capture_sio do
1476
+ pr = proc { ln() }
1477
+ ok {pr}.raise?(ArgumentError, "ln: requires two arguments.")
1478
+ pr = proc { ln "foo1.txt" }
1479
+ ok {pr}.raise?(ArgumentError, "ln: requires two arguments.")
1480
+ end
1481
+ end
1482
+ spec "[!2rxqo] error when source pattern matched to multiple files." do
1483
+ sout, serr = capture_sio do
1484
+ pr = proc { ln "foo*.txt", "bar.txt" }
1485
+ ok {pr}.raise?(ArgumentError, "ln: foo*.txt: unexpectedly matched to multiple files (foo1.txt, foo2.txt).")
1486
+ end
1487
+ end
1488
+ spec "[!ysxdq] error when destination pattern matched to multiple files." do
1489
+ sout, serr = capture_sio do
1490
+ pr = proc { ln "d9/bar.txt", "foo*.txt" }
1491
+ ok {pr}.raise?(ArgumentError, "ln: foo*.txt: unexpectedly matched to multiple files (foo1.txt, foo2.txt).")
1492
+ end
1493
+ end
1494
+ #
1495
+ spec "[!4ry8j] (hard link) error when source file not exists." do
1496
+ sout, serr = capture_sio do
1497
+ pr = proc { ln "foo8.txt", "foo9.txt" }
1498
+ ok {pr}.raise?(ArgumentError, "ln: foo8.txt: No such file or directory.")
1499
+ end
1500
+ end
1501
+ spec "[!tf29w] (hard link) error when source is a directory." do
1502
+ sout, serr = capture_sio do
1503
+ pr = proc { ln "d1", "d2" }
1504
+ ok {pr}.raise?(ArgumentError, "ln: d1: Is a directory.")
1505
+ end
1506
+ end
1507
+ spec "[!zmijh] error when destination is a directory without `to:` keyword argument." do
1508
+ sout, serr = capture_sio do
1509
+ pr = proc { ln "foo1.txt", "d1" }
1510
+ ok {pr}.raise?(ArgumentError, "ln: d1: cannot create link under directory without `to:` keyword option.")
1511
+ end
1512
+ end
1513
+ spec "[!nzci0] (ln) error when destination already exists." do
1514
+ sout, serr = capture_sio do
1515
+ pr = proc { ln "foo1.txt", "d1" }
1516
+ ok {pr}.raise?(ArgumentError, "ln: d1: cannot create link under directory without `to:` keyword option.")
1517
+ pr = proc { ln :s, "foo1.txt", "d1" }
1518
+ ok {pr}.raise?(ArgumentError, "ln: d1: cannot create link under directory without `to:` keyword option.")
1519
+ end
1520
+ end
1521
+ spec "[!oxjqv] create symbolic link if '-s' option specified." do
1522
+ sout, serr = capture_sio do
1523
+ ln :s, "foo1.txt", "foo9.txt"
1524
+ ok {"foo9.txt"}.file_exist?
1525
+ ok {"foo9.txt"}.symlink_exist?
1526
+ ln :s, "d1", "d9"
1527
+ ok {"d9"}.dir_exist?
1528
+ ok {"d9"}.symlink_exist?
1529
+ end
1530
+ end
1531
+ spec "[!awig1] (symlink) can create symbolic link to non-existing file." do
1532
+ sout, serr = capture_sio do
1533
+ ok {"foo8.txt"}.not_exist?
1534
+ ln :s, "foo8.txt", "foo9.txt"
1535
+ ok {"foo9.txt"}.symlink_exist?
1536
+ end
1537
+ end
1538
+ spec "[!5kl3w] (symlink) can create symbolic link to directory." do
1539
+ sout, serr = capture_sio do
1540
+ ln :s, "d1", "d9"
1541
+ ok {"d9"}.symlink_exist?
1542
+ end
1543
+ end
1544
+ spec "[!sb29p] create hard link if '-s' option not specified." do
1545
+ sout, serr = capture_sio do
1546
+ ln "foo1.txt", "foo9.txt"
1547
+ ok {"foo9.txt"}.file_exist?
1548
+ ok {"foo9.txt"}.NOT.symlink_exist?
1549
+ end
1550
+ end
1551
+ end
1552
+
1553
+ case_else "[!5x2wr] when `to:` keyword argument specified..." do
1554
+ spec "[!5gfxk] error when destination directory not exist." do
1555
+ sout, serr = capture_sio do
1556
+ pr = proc { ln "foo*.txt", to: "d9" }
1557
+ ok {pr}.raise?(ArgumentError, "ln: d9: directory not found.")
1558
+ end
1559
+ end
1560
+ spec "[!euu5d] error when destination pattern matched to multiple filenames." do
1561
+ sout, serr = capture_sio do
1562
+ pr = proc { ln "d1/bar.txt", to: "foo*.txt" }
1563
+ ok {pr}.raise?(ArgumentError, "ln: foo*.txt: unexpectedly matched to multiple filenames (foo1.txt, foo2.txt).")
1564
+ end
1565
+ end
1566
+ spec "[!42nb7] error when destination is not a directory." do
1567
+ sout, serr = capture_sio do
1568
+ pr = proc { ln "foo1.txt", to: "foo2.txt" }
1569
+ ok {pr}.raise?(ArgumentError, "ln: foo2.txt: Not a directory.")
1570
+ end
1571
+ end
1572
+ #
1573
+ spec "[!x7wh5] (symlink) can create symlink to unexisting file." do
1574
+ sout, serr = capture_sio do
1575
+ ln :s, "foo8.txt", to: "d1"
1576
+ ok {"d1/foo8.txt"}.not_exist?
1577
+ ok {"d1/foo8.txt"}.symlink_exist?
1578
+ end
1579
+ end
1580
+ spec "[!ml1vm] (hard link) error when source file not exist." do
1581
+ sout, serr = capture_sio do
1582
+ pr = proc { ln "foo8.txt", to: "d1" }
1583
+ ok {pr}.raise?(ArgumentError, "ln: foo8.txt: No such file or directory.")
1584
+ end
1585
+ end
1586
+ #
1587
+ spec "[!mwukw] (ln) error when target file or directory already exists." do
1588
+ sout, serr = capture_sio do
1589
+ dummy_file("d1/foo1.txt", "foo1")
1590
+ pr = proc { ln "foo*.txt", to: "d1" } # hard link
1591
+ ok {pr}.raise?(ArgumentError, "ln: d1/foo1.txt: File exists (to overwrite it, call `ln!` instead of `ln`).")
1592
+ #
1593
+ pr = proc { ln :s, "foo*.txt", to: "d1" } # symbolic link
1594
+ ok {pr}.raise?(ArgumentError, "ln: d1/foo1.txt: File exists (to overwrite it, call `ln!` instead of `ln`).")
1595
+ end
1596
+ end
1597
+ spec "[!c8hpp] (hard link) create hard link under directory if '-s' option not specified." do
1598
+ sout, serr = capture_sio do
1599
+ ln "foo*.txt", to: "d1"
1600
+ ok {"d1/foo1.txt"}.file_exist?
1601
+ ok {"d1/foo2.txt"}.file_exist?
1602
+ ok {"d1/foo1.txt"}.NOT.symlink_exist?
1603
+ ok {"d1/foo2.txt"}.NOT.symlink_exist?
1604
+ end
1605
+ end
1606
+ spec "[!9tv9g] (symlik) create symbolic link under directory if '-s' option specified." do
1607
+ sout, serr = capture_sio do
1608
+ ln :s, "foo*.txt", to: "d1"
1609
+ ok {"d1/foo1.txt"}.symlink_exist?
1610
+ ok {"d1/foo2.txt"}.symlink_exist?
1611
+ ok {"d1/foo1.txt"}.NOT.file_exist?
1612
+ ok {"d1/foo2.txt"}.NOT.file_exist?
1613
+ end
1614
+ end
1615
+ end
1616
+ end
1617
+
1618
+
1619
+ topic 'ln!()' do
1620
+ spec "[!dkqgq] (ln!) overwrites existing destination file." do
1621
+ sout, serr = capture_sio do
1622
+ ln :s, "foo1.txt", "foo9.txt"
1623
+ ok {"foo9.txt"}.symlink_exist?
1624
+ #
1625
+ pr = proc { ln :s, "foo2.txt", "foo9.txt" } # ln, symbolic link
1626
+ ok {pr}.raise?(ArgumentError, "ln: foo9.txt: File exists (to overwrite it, call `ln!` instead of `ln`).")
1627
+ ln! :s, "foo1.txt", "foo9.txt" # ln!, symbolic link
1628
+ ok {"foo9.txt"}.symlink_exist?
1629
+ ok {File.readlink("foo9.txt")} == "foo1.txt"
1630
+ #
1631
+ pr = proc { ln "foo2.txt", "foo9.txt" } # ln, hard link
1632
+ ok {pr}.raise?(ArgumentError, "ln: foo9.txt: File exists (to overwrite it, call `ln!` instead of `ln`).")
1633
+ ln! "foo2.txt", "foo9.txt" # ln!, hard link
1634
+ ok {"foo9.txt"}.file_exist?
1635
+ ok {"foo9.txt"}.NOT.symlink_exist?
1636
+ end
1637
+ end
1638
+ spec "[!c3vwn] (ln!) error when target file is a directory." do
1639
+ sout, serr = capture_sio do
1640
+ dummy_dir("d1/foo1.txt")
1641
+ pr = proc { ln! "foo1.txt", to: "d1" } # hard link
1642
+ ok {pr}.raise?(ArgumentError, "ln!: d1/foo1.txt: directory already exists.")
1643
+ #
1644
+ pr = proc { ln! :s, "foo1.txt", to: "d1" } # symbolic link
1645
+ ok {pr}.raise?(ArgumentError, "ln!: d1/foo1.txt: directory already exists.")
1646
+ end
1647
+ end
1648
+ spec "[!bfcki] (ln!) overwrites existing symbolic links." do
1649
+ sout, serr = capture_sio do
1650
+ ln :s, "d1/bar.txt", "d1/foo1.txt"
1651
+ ln :s, "d1/bar.txt", "d1/foo2.txt"
1652
+ ok {"d1/foo1.txt"}.symlink_exist?
1653
+ ok {"d1/foo2.txt"}.symlink_exist?
1654
+ #
1655
+ pr = proc { ln "foo1.txt", to: "d1" }
1656
+ ok {pr}.raise?(ArgumentError, "ln: d1/foo1.txt: symbolic link already exists (to overwrite it, call `ln!` instead of `ln`).")
1657
+ ln! "foo1.txt", to: "d1" # hard link
1658
+ ok {"d1/foo1.txt"}.file_exist?
1659
+ ok {"d1/foo1.txt"}.NOT.symlink_exist?
1660
+ #
1661
+ pr = proc { ln :s, "foo2.txt", to: "d1" }
1662
+ ok {pr}.raise?(ArgumentError, "ln: d1/foo2.txt: symbolic link already exists (to overwrite it, call `ln!` instead of `ln`).")
1663
+ ln! :s, "foo2.txt", to: "d1" # symbolic link
1664
+ ok {"d1/foo2.txt"}.symlink_exist?
1665
+ ok {"d1/foo2.txt"}.NOT.file_exist?
1666
+ end
1667
+ end
1668
+ spec "[!ipy2c] (ln!) overwrites existing files." do
1669
+ dummy_file "d1/foo1.txt"
1670
+ dummy_file "d1/foo2.txt"
1671
+ sout, serr = capture_sio do
1672
+ ## hard link
1673
+ pr = proc { ln "foo1.txt", to: "d1" }
1674
+ ok {pr}.raise?(ArgumentError, "ln: d1/foo1.txt: File exists (to overwrite it, call `ln!` instead of `ln`).")
1675
+ ln! "foo1.txt", to: "d1"
1676
+ ok {"d1/foo1.txt"}.file_exist?
1677
+ ok {"d1/foo1.txt"}.NOT.symlink_exist?
1678
+ ## symbolic link
1679
+ pr = proc { ln :s, "foo2.txt", to: "d1" }
1680
+ ok {pr}.raise?(ArgumentError, "ln: d1/foo2.txt: File exists (to overwrite it, call `ln!` instead of `ln`).")
1681
+ ln! :s, "foo2.txt", to: "d1" # symbolic link
1682
+ ok {"d1/foo2.txt"}.symlink_exist?
1683
+ ok {"d1/foo2.txt"}.NOT.file_exist?
1684
+ end
1685
+ end
1686
+ end
1687
+
1688
+ topic 'atomic_symlink!()' do
1689
+ spec "[!gzp4a] creates temporal symlink and rename it when symlink already exists." do
1690
+ File.symlink("foo1.txt", "tmp.link")
1691
+ sout, serr = capture_sio do
1692
+ atomic_symlink! "foo2.txt", "tmp.link"
1693
+ end
1694
+ ok {File.readlink("tmp.link")} == "foo2.txt"
1695
+ ok {sout} =~ /\A\$ ln -s foo2\.txt tmp\.link\.\d+ \&\& mv -Tf tmp\.link\.\d+ tmp.link\n\z/
1696
+ end
1697
+ spec "[!lhomw] creates temporal symlink and rename it when symlink not exist." do
1698
+ sout, serr = capture_sio do
1699
+ atomic_symlink! "d1", "d1.link"
1700
+ end
1701
+ ok {File.readlink("d1.link")} == "d1"
1702
+ ok {sout} =~ /\A\$ ln -s d1 d1\.link\.\d+ \&\& mv -Tf d1\.link\.\d+ d1\.link\n\z/
1703
+ end
1704
+ spec "[!h75kp] error when destination is normal file or directory." do
1705
+ sout, serr = capture_sio do
1706
+ pr = proc { atomic_symlink! "foo1.txt", "foo2.txt" }
1707
+ ok {pr}.raise?(ArgumentError, "atomic_symlink!: foo2.txt: not a symbolic link.")
1708
+ pr = proc { atomic_symlink! "foo1.txt", "d1" }
1709
+ ok {pr}.raise?(ArgumentError, "atomic_symlink!: d1: not a symbolic link.")
1710
+ end
1711
+ end
1712
+ end
1713
+
1714
+
1715
+ topic 'pwd()' do
1716
+ spec "[!aelx6] echoback command and arguments." do
1717
+ here = Dir.pwd()
1718
+ sout, serr = capture_sio do
1719
+ pwd()
1720
+ end
1721
+ ok {sout} == ("$ pwd\n"\
1722
+ "#{here}\n")
1723
+ end
1724
+ spec "[!kh3l2] prints current directory path."do
1725
+ here = Dir.pwd()
1726
+ sout, serr = capture_sio do
1727
+ pwd()
1728
+ end
1729
+ ok {sout} == ("$ pwd\n"\
1730
+ "#{here}\n")
1731
+ end
1732
+ end
1733
+
1734
+
1735
+ topic 'touch()' do
1736
+ fixture :ts do
1737
+ ts = Time.new(2000, 1, 1, 0, 0, 0)
1738
+ File.utime(ts, ts, "foo1.txt")
1739
+ File.utime(ts, ts, "foo2.txt")
1740
+ #File.utime(ts, ts, "d1/bar.txt")
1741
+ ts
1742
+ end
1743
+ #
1744
+ spec "[!ifxob] echobacks command and arguments." do
1745
+ sout, serr = capture_sio do
1746
+ touch "foo1.txt", "foo2.txt"
1747
+ end
1748
+ ok {sout} == "$ touch foo1.txt foo2.txt\n"
1749
+ end
1750
+ spec "[!c7e51] error when reference file not exist." do
1751
+ sout, serr = capture_sio do
1752
+ pr = proc { touch :r, "foo9.txt", "foo1.txt" }
1753
+ ok {pr}.raise?(ArgumentError, "touch: foo9.txt: not exist.")
1754
+ end
1755
+ end
1756
+ spec "[!pggnv] changes both access time and modification time in default." do |ts|
1757
+ sout, serr = capture_sio do
1758
+ ok {File.atime("foo1.txt")} == ts
1759
+ ok {File.mtime("foo1.txt")} == ts
1760
+ #
1761
+ now1 = Time.now
1762
+ touch "foo1.txt"
1763
+ now2 = Time.now
1764
+ ok {File.atime("foo1.txt")}.between?(now1, now2)
1765
+ ok {File.mtime("foo1.txt")}.between?(now1, now2)
1766
+ end
1767
+ end
1768
+ spec "[!o9h74] expands file name pattern." do |ts|
1769
+ sout, serr = capture_sio do
1770
+ ok {File.atime("foo1.txt")} == ts
1771
+ ok {File.mtime("foo1.txt")} == ts
1772
+ ok {File.atime("foo2.txt")} == ts
1773
+ ok {File.mtime("foo2.txt")} == ts
1774
+ #
1775
+ now1 = Time.now
1776
+ touch "foo*.txt"
1777
+ now2 = Time.now
1778
+ ok {File.atime("foo1.txt")}.between?(now1, now2)
1779
+ ok {File.mtime("foo1.txt")}.between?(now1, now2)
1780
+ ok {File.atime("foo2.txt")}.between?(now1, now2)
1781
+ ok {File.mtime("foo2.txt")}.between?(now1, now2)
1782
+ end
1783
+ end
1784
+ spec "[!9ahsu] changes timestamp of files to current datetime." do |ts|
1785
+ sout, serr = capture_sio do
1786
+ ok {File.atime("foo1.txt")} == ts
1787
+ ok {File.mtime("foo1.txt")} == ts
1788
+ #
1789
+ now1 = Time.now
1790
+ touch "foo1.txt"
1791
+ now2 = Time.now
1792
+ ok {File.atime("foo1.txt")}.between?(now1, now2)
1793
+ ok {File.mtime("foo1.txt")}.between?(now1, now2)
1794
+ end
1795
+ end
1796
+ spec "[!wo080] if reference file specified, use it's timestamp." do |ts|
1797
+ sout, serr = capture_sio do
1798
+ ok {File.atime("foo1.txt")} == ts
1799
+ ok {File.mtime("foo1.txt")} == ts
1800
+ touch :r, "foo1.txt", "d1/bar.txt"
1801
+ ok {File.atime("d1/bar.txt")} == ts
1802
+ ok {File.mtime("d1/bar.txt")} == ts
1803
+ end
1804
+ end
1805
+ spec "[!726rq] creates empty file if file not found and '-c' option not specified." do |ts|
1806
+ sout, serr = capture_sio do
1807
+ ok {"foo9.txt"}.not_exist?
1808
+ touch "foo9.txt"
1809
+ ok {"foo9.txt"}.file_exist?
1810
+ end
1811
+ end
1812
+ spec "[!cfc40] skips non-existing files if '-c' option specified." do
1813
+ sout, serr = capture_sio do
1814
+ ok {"foo9.txt"}.not_exist?
1815
+ touch :c, "foo9.txt"
1816
+ ok {"foo9.txt"}.not_exist?
1817
+ end
1818
+ end
1819
+ spec "[!s50bp] changes only access timestamp if '-a' option specified." do |ts|
1820
+ sout, serr = capture_sio do
1821
+ ok {File.atime("foo1.txt")} == ts
1822
+ ok {File.mtime("foo1.txt")} == ts
1823
+ now1 = Time.now
1824
+ touch :a, "foo1.txt"
1825
+ now2 = Time.now
1826
+ ok {File.atime("foo1.txt")}.between?(now1, now2)
1827
+ ok {File.mtime("foo1.txt")} == ts
1828
+ end
1829
+ end
1830
+ spec "[!k7zap] changes only modification timestamp if '-m' option specified." do |ts|
1831
+ sout, serr = capture_sio do
1832
+ ok {File.atime("foo1.txt")} == ts
1833
+ ok {File.mtime("foo1.txt")} == ts
1834
+ now1 = Time.now
1835
+ touch :m, "foo1.txt"
1836
+ now2 = Time.now
1837
+ ok {File.atime("foo1.txt")} == ts
1838
+ ok {File.mtime("foo1.txt")}.between?(now1, now2)
1839
+ end
1840
+ end
1841
+ spec "[!b5c1n] changes both access and modification timestamps in default." do |ts|
1842
+ sout, serr = capture_sio do
1843
+ ok {File.atime("foo1.txt")} == ts
1844
+ ok {File.mtime("foo1.txt")} == ts
1845
+ now1 = Time.now
1846
+ touch "foo1.txt"
1847
+ now2 = Time.now
1848
+ ok {File.atime("foo1.txt")} != ts
1849
+ ok {File.mtime("foo1.txt")} != ts
1850
+ ok {File.atime("foo1.txt")}.between?(now1, now2)
1851
+ ok {File.mtime("foo1.txt")}.between?(now1, now2)
1852
+ end
1853
+ end
1854
+ end
1855
+
1856
+
1857
+ topic 'chmod()' do
1858
+ spec "[!pmmvj] echobacks command and arguments." do
1859
+ sout, serr = capture_sio do
1860
+ chmod "644", "foo1.txt", "foo2.txt"
1861
+ end
1862
+ ok {sout} == "$ chmod 644 foo1.txt foo2.txt\n"
1863
+ end
1864
+ spec "[!94hl9] error when mode not specified." do
1865
+ sout, serr = capture_sio do
1866
+ pr = proc { chmod() }
1867
+ ok {pr}.raise?(ArgumentError, "chmod: argument required.")
1868
+ end
1869
+ end
1870
+ spec "[!c8zhu] mode can be integer or octal string." do
1871
+ sout, serr = capture_sio do
1872
+ mode_i, mask = __chmod("chmod", [0644, "foo1.txt"], true)
1873
+ ok {mode_i} == 0644
1874
+ ok {mask} == nil
1875
+ mode_i, mask = __chmod("chmod", ["644", "foo1.txt"], true)
1876
+ ok {mode_i} == 0644
1877
+ ok {mask} == nil
1878
+ end
1879
+ end
1880
+ spec "[!j3nqp] error when integer mode is invalid." do
1881
+ sout, serr = capture_sio do
1882
+ pr = proc { chmod 888, "foo1.txt" }
1883
+ ok {pr}.raise?(ArgumentError, "chmod: 888: Invalid file mode.")
1884
+ end
1885
+ end
1886
+ spec "[!ox3le] converts 'u+r' style mode into mask." do
1887
+ sout, serr = capture_sio do
1888
+ [
1889
+ ["u+r", 0400], ["u+w", 0200], ["u+x", 0100], ["u+s", 04000], ["u+t", 00000],
1890
+ ["g+r", 0040], ["g+w", 0020], ["g+x", 0010], ["g+s", 02000], ["g+t", 00000],
1891
+ ["o+r", 0004], ["o+w", 0002], ["o+x", 0001], ["o+s", 00000], ["o+t", 00000],
1892
+ ["a+r", 0444], ["a+w", 0222], ["a+x", 0111], ["a+s", 06000], ["a+t", 01000],
1893
+ ].each do |mode, expected|
1894
+ mode_i, mask = __chmod("chmod", [mode, "foo1.txt"], true)
1895
+ ok {mode_i} == nil
1896
+ ok {mask} == expected
1897
+ end
1898
+ end
1899
+ end
1900
+ spec "[!axqed] error when mode is invalid." do
1901
+ sout, serr = capture_sio do
1902
+ pr = proc { chmod "888", "foo1.txt" }
1903
+ ok {pr}.raise?(ArgumentError, "chmod: 888: Invalid file mode.")
1904
+ pr = proc { chmod "+r", "foo1.txt" }
1905
+ ok {pr}.raise?(ArgumentError, "chmod: +r: Invalid file mode.")
1906
+ end
1907
+ end
1908
+ spec "[!ru371] expands file pattern." do
1909
+ sout, serr = capture_sio do
1910
+ ok {File.readable?("foo1.txt")} == true
1911
+ ok {File.readable?("foo2.txt")} == true
1912
+ chmod "u-r", "foo*.txt"
1913
+ ok {File.readable?("foo1.txt")} == false
1914
+ ok {File.readable?("foo2.txt")} == false
1915
+ end
1916
+ end
1917
+ spec "[!ou3ih] error when file not exist." do
1918
+ sout, serr = capture_sio do
1919
+ pr = proc { chmod "u+r", "blabla" }
1920
+ ok {pr}.raise?(ArgumentError, "chmod: blabla: No such file or directory.")
1921
+ end
1922
+ end
1923
+ spec "[!8sd4b] error when file pattern not matched to anything." do
1924
+ sout, serr = capture_sio do
1925
+ pr = proc { chmod "u+r", "foobar*.txt" }
1926
+ ok {pr}.raise?(ArgumentError, "chmod: foobar*.txt: No such file or directory.")
1927
+ end
1928
+ end
1929
+ spec "[!q1psx] changes file mode." do
1930
+ sout, serr = capture_sio do
1931
+ mode1 = File.stat("foo1.txt").mode
1932
+ chmod "432", "foo1.txt"
1933
+ mode2 = File.stat("foo1.txt").mode
1934
+ ok {mode2} != mode1
1935
+ ok {mode2 & 0777} == 0432
1936
+ #
1937
+ chmod "u+w", "foo1.txt"
1938
+ ok {File.stat("foo1.txt").mode & 0777} == 0632
1939
+ chmod "g-x", "foo1.txt"
1940
+ ok {File.stat("foo1.txt").mode & 0777} == 0622
1941
+ chmod "a+x", "foo1.txt"
1942
+ ok {File.stat("foo1.txt").mode & 0777} == 0733
1943
+ #
1944
+ chmod "u+s", "foo1.txt"
1945
+ ok {File.stat("foo1.txt").mode & 07777} == 04733
1946
+ chmod "g+s", "foo1.txt"
1947
+ ok {File.stat("foo1.txt").mode & 07777} == 06733
1948
+ chmod "a-s", "foo1.txt"
1949
+ ok {File.stat("foo1.txt").mode & 07777} == 00733
1950
+ chmod "o+s", "foo1.txt"
1951
+ ok {File.stat("foo1.txt").mode & 07777} == 00733
1952
+ #
1953
+ chmod "u+t", "foo1.txt"
1954
+ ok {File.stat("foo1.txt").mode & 07777} == 00733
1955
+ chmod "g+t", "foo1.txt"
1956
+ ok {File.stat("foo1.txt").mode & 07777} == 00733
1957
+ chmod "o+t", "foo1.txt"
1958
+ ok {File.stat("foo1.txt").mode & 07777} == 00733
1959
+ chmod "a+t", "foo1.txt"
1960
+ ok {File.stat("foo1.txt").mode & 07777} == 01733
1961
+ chmod "a-t", "foo1.txt"
1962
+ ok {File.stat("foo1.txt").mode & 07777} == 00733
1963
+ end
1964
+ end
1965
+ spec "[!4en6n] skips symbolic links." do
1966
+ sout, serr = capture_sio do
1967
+ File.symlink("foo1.txt", "foo1.link")
1968
+ mode = File.stat("foo1.txt").mode
1969
+ chmod 0765, "foo1.link"
1970
+ ok {File.stat("foo1.txt").mode} == mode
1971
+ ok {File.stat("foo1.txt").mode | 0777} != 0765
1972
+ end
1973
+ end
1974
+ spec "[!4e7ve] changes mode recursively if '-R' option specified." do
1975
+ sout, serr = capture_sio do
1976
+ chmod :R, 0775, "d1"
1977
+ ok {File.stat("d1" ).mode & 0777} == 0775
1978
+ ok {File.stat("d1/bar.txt" ).mode & 0777} == 0775
1979
+ ok {File.stat("d1/d2/baz.txt").mode & 0777} == 0775
1980
+ end
1981
+ end
1982
+ end
1983
+
1984
+
1985
+ topic 'chown()' do
1986
+ fixture :usr do
1987
+ ENV['USER'] # TODO
1988
+ end
1989
+ fixture :grp do
1990
+ "staff" # TODO
1991
+ end
1992
+ fixture :uid do |usr|
1993
+ Etc.getpwnam(usr).uid
1994
+ end
1995
+ fixture :gid do |grp|
1996
+ Etc.getgrnam(grp).gid
1997
+ end
1998
+ #
1999
+ spec "[!5jqqv] echobacks command and arguments." do |usr, grp|
2000
+ sout, serr = capture_sio do
2001
+ chown "#{usr}:#{grp}", "foo*.txt"
2002
+ end
2003
+ ok {sout} == "$ chown #{usr}:#{grp} foo*.txt\n"
2004
+ end
2005
+ spec "[!hkxgu] error when owner not specified." do
2006
+ sout, serr = capture_sio do
2007
+ pr = proc { chown() }
2008
+ ok {pr}.raise?(ArgumentError, "chown: argument required.")
2009
+ end
2010
+ end
2011
+ spec "[!0a35v] accepts integer as user id." do |usr, uid|
2012
+ sout, serr = capture_sio do
2013
+ ok {uid}.is_a?(Integer)
2014
+ chown uid, "foo*.txt"
2015
+ ok {File.stat("foo1.txt").uid} == uid
2016
+ end
2017
+ end
2018
+ spec "[!b5qud] accepts 'user:group' argument." do |usr, grp, uid, gid|
2019
+ sout, serr = capture_sio do
2020
+ chown "#{usr}:#{grp}", "foo*.txt"
2021
+ ok {File.stat("foo1.txt").uid} == uid
2022
+ ok {File.stat("foo1.txt").gid} == gid
2023
+ end
2024
+ end
2025
+ spec "[!18gf0] accepts 'user' argument." do |usr, grp, uid|
2026
+ sout, serr = capture_sio do
2027
+ chown usr, "foo*.txt"
2028
+ ok {File.stat("foo1.txt").uid} == uid
2029
+ #
2030
+ chown usr+":", "foo*.txt"
2031
+ ok {File.stat("foo1.txt").uid} == uid
2032
+ end
2033
+ end
2034
+ spec "[!mw5tg] accepts ':group' argument." do |usr, grp, gid|
2035
+ sout, serr = capture_sio do
2036
+ chown ":#{grp}", "foo*.txt"
2037
+ ok {File.stat("foo1.txt").gid} == gid
2038
+ end
2039
+ end
2040
+ spec "[!jyecc] converts user name into user id." do |usr, grp, uid|
2041
+ sout, serr = capture_sio do
2042
+ uid, gid = __chown("chown", [usr, "foo*.txt"], true)
2043
+ ok {uid} == uid
2044
+ end
2045
+ end
2046
+ spec "[!kt7mp] error when invalid user name specified." do |usr, grp|
2047
+ sout, serr = capture_sio do
2048
+ pr = proc { chown "honyara", "foo*.txt" }
2049
+ ok {pr}.raise?(ArgumentError, "chown: honyara: unknown user name.")
2050
+ end
2051
+ end
2052
+ spec "[!f7ye0] converts group name into group id." do |usr, grp, uid, gid|
2053
+ sout, serr = capture_sio do
2054
+ uid, gid = __chown("chown", ["#{usr}:#{grp}", "foo*.txt"], true)
2055
+ ok {uid} == uid
2056
+ ok {gid} == gid
2057
+ end
2058
+ end
2059
+ spec "[!szlsb] error when invalid group name specified." do
2060
+ sout, serr = capture_sio do
2061
+ pr = proc { chown ":honyara", "foo*.txt" }
2062
+ ok {pr}.raise?(ArgumentError, "chown: honyara: unknown group name.")
2063
+ end
2064
+ end
2065
+ spec "[!138eh] expands file pattern." do |usr, grp, uid|
2066
+ sout, serr = capture_sio do
2067
+ chown usr, "foo*.txt"
2068
+ ok {File.stat("foo1.txt").uid} == uid
2069
+ ok {File.stat("foo2.txt").uid} == uid
2070
+ end
2071
+ end
2072
+ spec "[!tvpey] error when file not exist." do |usr, grp|
2073
+ sout, serr = capture_sio do
2074
+ pr = proc { chown usr, "blabla.txt" }
2075
+ ok {pr}.raise?(ArgumentError, "chown: blabla.txt: No such file or directory.")
2076
+ end
2077
+ end
2078
+ spec "[!ovkk8] error when file pattern not matched to anything." do |usr, grp|
2079
+ sout, serr = capture_sio do
2080
+ pr = proc { chown usr, "blabla*.txt" }
2081
+ ok {pr}.raise?(ArgumentError, "chown: blabla*.txt: No such file or directory.")
2082
+ end
2083
+ end
2084
+ spec "[!7tf3k] changes file mode." do |usr, grp, uid, gid|
2085
+ sout, serr = capture_sio do
2086
+ chown "#{usr}:#{grp}", "foo1.txt"
2087
+ ok {File.stat("foo1.txt").uid} == uid
2088
+ ok {File.stat("foo1.txt").gid} == gid
2089
+ end
2090
+ end
2091
+ spec "[!m6mrg] skips symbolic links." do |usr, grp|
2092
+ sout, serr = capture_sio do
2093
+ File.symlink "foo1.txt", "foo1.link"
2094
+ File.unlink "foo1.txt"
2095
+ chown "#{usr}:#{grp}", "foo1.link" # not raise error
2096
+ end
2097
+ end
2098
+ spec "[!b07ff] changes file mode recursively if '-R' option specified." do |usr, grp, uid|
2099
+ sout, serr = capture_sio do
2100
+ chown :R, "#{usr}", "d1"
2101
+ ok {File.stat("d1/d2/baz.txt").uid} == uid
2102
+ end
2103
+ end
2104
+ end
2105
+
2106
+
2107
+ topic 'store()' do
2108
+ spec "[!9wr1o] error when `to:` keyword argument not specified." do
2109
+ sout, serr = capture_sio do
2110
+ pr = proc { store "foo*.txt", "d1" }
2111
+ ok {pr}.raise?(ArgumentError, /^missing keyword: :?to$/)
2112
+ end
2113
+ end
2114
+ spec "[!n43u2] echoback command and arguments." do
2115
+ sout, serr = capture_sio do
2116
+ store "foo*.txt", to: "d1"
2117
+ end
2118
+ ok {sout} == "$ store foo*.txt d1\n"
2119
+ end
2120
+ spec "[!588e5] error when destination directory not exist." do
2121
+ sout, serr = capture_sio do
2122
+ pr = proc { store "foo*.txt", to: "d9" }
2123
+ ok {pr}.raise?(ArgumentError, "store: d9: directory not found.")
2124
+ end
2125
+ end
2126
+ spec "[!lm43y] error when destination pattern matched to multiple filenames." do
2127
+ sout, serr = capture_sio do
2128
+ pr = proc { store "d1", to: "foo*.txt" }
2129
+ ok {pr}.raise?(ArgumentError, "store: foo*.txt: unexpectedly matched to multiple filenames (foo1.txt, foo2.txt).")
2130
+ end
2131
+ end
2132
+ spec "[!u5zoy] error when destination is not a directory." do
2133
+ sout, serr = capture_sio do
2134
+ pr = proc { store "foo*.txt", to: "d1/bar.txt" }
2135
+ ok {pr}.raise?(ArgumentError, "store: d1/bar.txt: Not a directory.")
2136
+ end
2137
+ end
2138
+ spec "[!g1duw] error when absolute path specified." do
2139
+ sout, serr = capture_sio do
2140
+ pr = proc { store "/tmp", to: "d1" }
2141
+ ok {pr}.raise?(ArgumentError, "store: /tmp: absolute path not expected (only relative path expected).")
2142
+ end
2143
+ end
2144
+ spec "[!je1i2] error when file not exist but '-f' option not specified." do
2145
+ sout, serr = capture_sio do
2146
+ pr = proc { store "blabla*.txt", to: "d1"}
2147
+ ok {pr}.raise?(ArgumentError, "store: blabla*.txt: file or directory not found (add '-f' option to ignore missing files).")
2148
+ end
2149
+ end
2150
+ spec "[!5619q] (store) error when target file or directory already exists." do
2151
+ sout, serr = capture_sio do
2152
+ dummy_file "d1/foo2.txt", "dummy"
2153
+ pr = proc { store "foo*.txt", to: "d1" }
2154
+ ok {pr}.raise?(ArgumentError, "store: d1/foo2.txt: destination file or directory already exists.")
2155
+ end
2156
+ end
2157
+ spec "[!4y4zy] copy files with keeping filepath." do
2158
+ sout, serr = capture_sio do
2159
+ dummy_dir("d9")
2160
+ store "foo*.txt", "d1", to: "d9"
2161
+ ok {"d9/foo1.txt"}.file_exist?
2162
+ ok {"d9/foo2.txt"}.file_exist?
2163
+ ok {"d9/d1/bar.txt"}.file_exist?
2164
+ ok {"d9/d1/d2/baz.txt"}.file_exist?
2165
+ end
2166
+ end
2167
+ spec "[!f0n0y] copy timestamps if '-p' option specified." do
2168
+ sout, serr = capture_sio do
2169
+ dummy_dir "d9"
2170
+ atime1 = File.atime("d1/d2/baz.txt")
2171
+ mtime1 = File.mtime("d1/d2/baz.txt")
2172
+ atime2 = (x = atime1 - 600; Time.new(x.year, x.month, x.day, x.hour, x.min, x.sec))
2173
+ mtime2 = (x = mtime1 - 900; Time.new(x.year, x.month, x.day, x.hour, x.min, x.sec))
2174
+ File.utime(atime2, mtime2, "d1/d2/baz.txt")
2175
+ store :p, "d1/**/*.txt", to: "d9"
2176
+ ok {File.atime("d1/d2/baz.txt")} != atime1
2177
+ ok {File.mtime("d1/d2/baz.txt")} != mtime1
2178
+ ok {File.atime("d1/d2/baz.txt")} == atime2 # !!!
2179
+ ok {File.mtime("d1/d2/baz.txt")} == mtime2 # !!!
2180
+ end
2181
+ end
2182
+ spec "[!w8oq6] creates hard links if '-l' option specified." do
2183
+ sout, serr = capture_sio do
2184
+ dummy_dir "d9"
2185
+ store :l, "foo*.txt", "d1/**/*.txt", to: "d9"
2186
+ ok {File.identical?("foo1.txt", "d9/foo1.txt")} == true
2187
+ ok {File.identical?("foo2.txt", "d9/foo2.txt")} == true
2188
+ ok {File.identical?("d1/bar.txt", "d9/d1/bar.txt")} == true
2189
+ ok {File.identical?("d1/d2/baz.txt", "d9/d1/d2/baz.txt")} == true
2190
+ end
2191
+ end
2192
+ spec "[!7n869] error when copying supecial files such as character device." do
2193
+ sout, serr = capture_sio do
2194
+ dummy_dir "d9"
2195
+ dir = File.join(Dir.pwd(), "d9")
2196
+ Dir.chdir "/dev" do
2197
+ pr = proc { store "./null", to: dir }
2198
+ ok {pr}.raise?(ArgumentError, "store: ./null: cannot copy characterSpecial file.")
2199
+ end
2200
+ end
2201
+ end
2202
+ end
2203
+
2204
+ topic 'store!()' do
2205
+ spec "[!cw08t] (store!) overwrites existing files." do
2206
+ dummy_file "d1/foo2.txt", "dummy"
2207
+ sout, serr = capture_sio do
2208
+ store! "foo*.txt", to: "d1"
2209
+ ok {"d1/foo2.txt"}.file_exist?
2210
+ ok {File.read("d1/foo2.txt")} != "dummy"
2211
+ ok {File.read("d1/foo2.txt")} == File.read("foo2.txt")
2212
+ end
2213
+ end
2214
+ end
2215
+
2216
+
2217
+ topic 'zip()' do
2218
+ spec "[!zzvuk] requires 'zip' gem automatically." do
2219
+ skip_when defined?(::Zip) != nil, "zip gem already required."
2220
+ ok {defined?(::Zip)} == nil
2221
+ sout, serr = capture_sio do
2222
+ zip "foo.zip", "foo*.txt"
2223
+ end
2224
+ ok {defined?(::Zip)} != false
2225
+ ok {defined?(::Zip)} == 'constant'
2226
+ end
2227
+ spec "[!zk1qt] echoback command and arguments." do
2228
+ sout, serr = capture_sio do
2229
+ zip "foo.zip", "foo*.txt"
2230
+ end
2231
+ ok {sout} == "$ zip foo.zip foo*.txt\n"
2232
+ end
2233
+ spec "[!lrnj7] zip filename required." do
2234
+ sout, serr = capture_sio do
2235
+ pr = proc { zip :r }
2236
+ ok {pr}.raise?(ArgumentError, "zip: zip filename required.")
2237
+ end
2238
+ end
2239
+ spec "[!umbal] error when zip file glob pattern matched to mutilple filenames." do
2240
+ sout, serr = capture_sio do
2241
+ dummy_file "foo1.zip"
2242
+ dummy_file "foo2.zip"
2243
+ pr = proc { zip! "foo*.zip", "foo*.txt" }
2244
+ ok {pr}.raise?(ArgumentError, "zip: foo*.zip: matched to multiple filenames (foo1.zip, foo2.zip).")
2245
+ end
2246
+ end
2247
+ spec "[!oqzna] (zip) raises error if zip file already exists." do
2248
+ sout, serr = capture_sio do
2249
+ dummy_file "foo.zip"
2250
+ pr = proc { zip "foo.zip", "foo*.txt" }
2251
+ ok {pr}.raise?(ArgumentError, "zip: foo.zip: already exists (to overwrite it, call `zip!` command instead of `zip` command).")
2252
+ end
2253
+ end
2254
+ spec "[!uu8uz] expands glob pattern." do
2255
+ sout, serr = capture_sio do
2256
+ pr = proc { zip "foo.zip", "foo*.txt" }
2257
+ ok {pr}.NOT.raise?(ArgumentError)
2258
+ end
2259
+ end
2260
+ spec "[!nahxa] error if file not exist." do
2261
+ sout, serr = capture_sio do
2262
+ pr = proc { zip "foo.zip", "blabla*.txt" }
2263
+ ok {pr}.raise?(ArgumentError, "zip: blabla*.txt: file or directory not found.")
2264
+ end
2265
+ end
2266
+ spec "[!qsp7c] cannot specify absolute path." do
2267
+ sout, serr = capture_sio do
2268
+ pr = proc { zip "foo.zip", "/tmp" }
2269
+ ok {pr}.raise?(ArgumentError, "zip: /tmp: not support absolute path.")
2270
+ end
2271
+ end
2272
+ spec "[!p8alf] creates zip file." do
2273
+ sout, serr = capture_sio do
2274
+ zip "foo.zip", "foo*.txt"
2275
+ ok {"foo.zip"}.file_exist?
2276
+ unzip_cmd = capture2 "which unzip"
2277
+ if ! unzip_cmd.strip.empty?
2278
+ output = capture2 "#{unzip_cmd.strip} -l foo.zip"
2279
+ ok {output} =~ /foo1\.txt/
2280
+ ok {output} =~ /foo2\.txt/
2281
+ end
2282
+ end
2283
+ end
2284
+ spec "[!3sxmg] supports complession level (0~9)." do
2285
+ sout, serr = capture_sio do
2286
+ dummy_file "foo3.txt", "foobar"*10
2287
+ zip :'0', "foo0.zip", "foo*.txt"
2288
+ ok {"foo0.zip"}.file_exist?
2289
+ zip :'1', "foo1.zip", "foo*.txt"
2290
+ ok {"foo1.zip"}.file_exist?
2291
+ zip :'9', "foo9.zip", "foo*.txt"
2292
+ ok {"foo9.zip"}.file_exist?
2293
+ #
2294
+ ok {File.size("foo9.zip")} <= File.size("foo1.zip")
2295
+ ok {File.size("foo1.zip")} < File.size("foo0.zip")
2296
+ end
2297
+ end
2298
+ spec "[!h7yxl] restores value of `Zip.default_compression`." do
2299
+ val = Zip.default_compression
2300
+ sout, serr = capture_sio do
2301
+ zip :'9', "foo9.zip", "foo*.txt"
2302
+ end
2303
+ ok {Zip.default_compression} == val
2304
+ end
2305
+ spec "[!bgdg7] adds files recursively into zip file if '-r' option specified." do
2306
+ sout, serr = capture_sio do
2307
+ zip :r, "foo.zip", "d1"
2308
+ unzip_cmd = capture2 "which unzip"
2309
+ if ! unzip_cmd.strip.empty?
2310
+ output = capture2 "#{unzip_cmd.strip} -l foo.zip"
2311
+ ok {output} =~ /d1\/bar\.txt/
2312
+ ok {output} =~ /d1\/d2\/baz\.txt/
2313
+ end
2314
+ end
2315
+ end
2316
+ spec "[!jgt96] error when special file specified." do
2317
+ sout, serr = capture_sio do
2318
+ here = Dir.pwd
2319
+ Dir.chdir "/dev" do
2320
+ pr = proc { zip File.join(here, "foo.zip"), "./null" }
2321
+ ok {pr}.raise?(ArgumentError, "zip: ./null: characterSpecial file not supported.")
2322
+ end
2323
+ end
2324
+ end
2325
+ spec "[!fvvn8] returns zip file object." do
2326
+ sout, serr = capture_sio do
2327
+ ret = zip "foo.zip", "foo*.txt"
2328
+ ok {ret}.is_a?(Zip::File)
2329
+ end
2330
+ end
2331
+ end
2332
+
2333
+ topic 'zip!()' do
2334
+ spec "[!khbiq] zip filename can be glob pattern." do
2335
+ sout, serr = capture_sio do
2336
+ dummy_file "foo.zip"
2337
+ pr = proc { zip! "*.zip", "foo*.txt" }
2338
+ ok {pr}.NOT.raise?(ArgumentError)
2339
+ end
2340
+ end
2341
+ spec "[!e995z] (zip!) removes zip file if exists." do
2342
+ sout, serr = capture_sio do
2343
+ dummy_file "foo.zip"
2344
+ pr = proc { zip! "foo.zip", "foo*.txt" }
2345
+ ok {pr}.NOT.raise?(ArgumentError)
2346
+ end
2347
+ end
2348
+ end
2349
+
2350
+
2351
+ topic 'unzip()' do
2352
+ spec "[!eqx48] requires 'zip' gem automatically." do
2353
+ skip_when defined?(::Zip) != nil, "zip gem already required."
2354
+ ok {defined?(::Zip)} == nil
2355
+ sout, serr = capture_sio do
2356
+ begin
2357
+ unzip "foo.zip"
2358
+ rescue
2359
+ end
2360
+ end
2361
+ ok {defined?(::Zip)} == 'constant'
2362
+ end
2363
+ spec "[!0tedi] extract zip file." do
2364
+ sout, serr = capture_sio do
2365
+ zip "foo.zip", "foo*.txt"
2366
+ rm "foo*.txt"
2367
+ ok {"foo1.txt"}.not_exist?
2368
+ ok {"foo2.txt"}.not_exist?
2369
+ unzip "foo.zip"
2370
+ ok {"foo1.txt"}.file_exist?
2371
+ ok {"foo2.txt"}.file_exist?
2372
+ end
2373
+ end
2374
+ spec "[!ednxk] echoback command and arguments." do
2375
+ sout, serr = capture_sio do
2376
+ zip "foo.zip", "foo*.txt"
2377
+ File.unlink("foo1.txt", "foo2.txt")
2378
+ unzip "foo.zip"
2379
+ end
2380
+ ok {sout} == ("$ zip foo.zip foo*.txt\n"\
2381
+ "$ unzip foo.zip\n")
2382
+ end
2383
+ spec "[!1lul7] error if zip file not specified." do
2384
+ sout, serr = capture_sio do
2385
+ pr = proc { unzip() }
2386
+ ok {pr}.raise?(ArgumentError, "unzip: zip filename required.")
2387
+ pr = proc { unzip :d }
2388
+ ok {pr}.raise?(ArgumentError, "unzip: zip filename required.")
2389
+ end
2390
+ end
2391
+ spec "[!0yyg8] target directory should not exist, or be empty." do
2392
+ sout, serr = capture_sio do
2393
+ zip "foo.zip", "foo*.txt"
2394
+ mkdir "d8"
2395
+ unzip :d, "d8", "foo.zip" # empty dir
2396
+ unzip :d, "d9", "foo.zip" # non-existing dir
2397
+ end
2398
+ end
2399
+ spec "[!1ls2h] error if target directory not empty." do
2400
+ sout, serr = capture_sio do
2401
+ zip "foo.zip", "foo*.txt"
2402
+ pr = proc { unzip :d, "d1", "foo.zip" }
2403
+ ok {pr}.raise?(ArgumentError, "unzip: d1: directory not empty.")
2404
+ end
2405
+ end
2406
+ spec "[!lb6r5] error if target directory is not a directory." do
2407
+ sout, serr = capture_sio do
2408
+ pr = proc { unzip :d, "foo1.txt", "foo2.txt" }
2409
+ ok {pr}.raise?(ArgumentError, "unzip: foo1.txt: not a directory.")
2410
+ end
2411
+ end
2412
+ spec "[!dzk7c] creates target directory if not exists." do
2413
+ sout, serr = capture_sio do
2414
+ zip "foo.zip", "foo*.txt"
2415
+ unzip :d, "d8/d9", "*.zip"
2416
+ ok {"d8/d9/foo1.txt"}.file_exist?
2417
+ ok {"d8/d9/foo2.txt"}.file_exist?
2418
+ end
2419
+ end
2420
+ spec "[!o1ot5] expands glob pattern." do
2421
+ sout, serr = capture_sio do
2422
+ zip "foo.zip", "foo*.txt"; File.unlink("foo1.txt", "foo2.txt")
2423
+ unzip "*.zip"
2424
+ ok {"foo1.txt"}.file_exist?
2425
+ ok {"foo2.txt"}.file_exist?
2426
+ end
2427
+ end
2428
+ spec "[!92bh4] error if glob pattern matched to multiple filenames." do
2429
+ sout, serr = capture_sio do
2430
+ pr = proc { unzip "*.txt" }
2431
+ ok {pr}.raise?(ArgumentError, "unzip: *.txt: matched to multiple filenames (foo1.txt foo2.txt).")
2432
+ end
2433
+ end
2434
+ spec "[!esnke] error if zip file not found." do
2435
+ sout, serr = capture_sio do
2436
+ pr = proc { unzip "*.zip" }
2437
+ ok {pr}.raise?(ArgumentError, "unzip: *.zip: zip file not found.")
2438
+ end
2439
+ end
2440
+ spec "[!ekllx] (unzip) error when file already exists." do
2441
+ sout, serr = capture_sio do
2442
+ zip "foo.zip", "foo*.txt"
2443
+ pr = proc { unzip "foo.zip" }
2444
+ ok {pr}.raise?(ArgumentError, "unzip: foo1.txt: file already exists (to overwrite it, call `unzip!` command instead of `unzip` command).")
2445
+ end
2446
+ end
2447
+ spec "[!zg60i] error if file has absolute path." do
2448
+ skip_when true, "cannot create zip file containing absolute path."
2449
+ end
2450
+ spec "[!ikq5w] if filenames are specified, extracts files matched to them." do
2451
+ sout, serr = capture_sio do
2452
+ zip "foo.zip", "foo*.txt"; File.unlink("foo1.txt", "foo2.txt")
2453
+ unzip "foo.zip", "*2.txt"
2454
+ ok {"foo1.txt"}.not_exist?
2455
+ ok {"foo2.txt"}.file_exist?
2456
+ end
2457
+ end
2458
+ spec "[!dy4r4] if '-d' option specified, extracts files under target directory." do
2459
+ sout, serr = capture_sio do
2460
+ zip "foo.zip", "foo*.txt"
2461
+ unzip :d, "d9", "foo.zip"
2462
+ ok {"d9/foo1.txt"}.file_exist?
2463
+ ok {"d9/foo2.txt"}.file_exist?
2464
+ end
2465
+ end
2466
+ spec "[!5u645] if '-d' option not specified, extracts files under current directory." do
2467
+ sout, serr = capture_sio do
2468
+ zip "foo.zip", "foo*.txt"; File.unlink("foo1.txt", "foo2.txt")
2469
+ unzip "foo.zip"
2470
+ ok {"foo1.txt"}.file_exist?
2471
+ ok {"foo2.txt"}.file_exist?
2472
+ end
2473
+ end
2474
+ end
2475
+
2476
+ topic 'unzip!()' do
2477
+ spec "[!06nyv] (unzip!) overwrites existing files." do
2478
+ sout, serr = capture_sio do
2479
+ zip "foo.zip", "foo*.txt"
2480
+ File.unlink("foo2.txt")
2481
+ ok {"foo1.txt"}.file_exist?
2482
+ ok {"foo2.txt"}.not_exist?
2483
+ pr = proc { unzip! "foo.zip" }
2484
+ ok {pr}.NOT.raise?(ArgumentError)
2485
+ ok {"foo1.txt"}.file_exist?
2486
+ ok {"foo2.txt"}.file_exist?
2487
+ end
2488
+ end
2489
+ end
2490
+
2491
+
2492
+ topic 'time()' do
2493
+ spec "[!ddl3a] measures elapsed time of block and reports into stderr." do
2494
+ sout, serr = capture_sio do
2495
+ time do
2496
+ puts "sleep 1..."
2497
+ sleep 1
2498
+ end
2499
+ end
2500
+ ok {sout} == "sleep 1...\n"
2501
+ ok {serr} =~ /\A\n 1\.\d\d\ds real 0\.\d\d\ds user 0\.\d\d\ds sys\n\z/
2502
+ end
2503
+ spec "[!sjf80] (unzip!) `Zip.on_exists_proc` should be recovered." do
2504
+ sout, serr = capture_sio do
2505
+ ok {Zip.on_exists_proc} == false
2506
+ zip "foo.zip", "foo1.txt", "foo2.txt"
2507
+ unzip! "foo.zip"
2508
+ ok {Zip.on_exists_proc} == false
2509
+ end
2510
+ end
2511
+ end
2512
+
2513
+
2514
+ end
2515
+
2516
+
2517
+ end