benry-unixcommand 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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