win32-file-stat 1.5.1 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,677 +1,677 @@
1
- #####################################################################
2
- # test_file_stat.rb
3
- #
4
- # Test case for stat related methods of win32-file. You should use
5
- # the 'rake test' task to run these tests.
6
- #####################################################################
7
- require 'etc'
8
- require 'ffi'
9
- require 'test-unit'
10
- require 'win32/file/stat'
11
- require 'win32/security'
12
- require 'pathname'
13
-
14
- class TC_Win32_File_Stat < Test::Unit::TestCase
15
- extend FFI::Library
16
- ffi_lib :kernel32
17
-
18
- attach_function :GetDriveType, :GetDriveTypeA, [:string], :ulong
19
- attach_function :GetFileAttributes, :GetFileAttributesA, [:string], :ulong
20
- attach_function :SetFileAttributes, :SetFileAttributesA, [:string, :ulong], :bool
21
-
22
- DRIVE_REMOVABLE = 2
23
- DRIVE_CDROM = 5
24
- DRIVE_RAMDISK = 6
25
-
26
- def self.startup
27
- @@block_dev = nil
28
-
29
- 'A'.upto('Z'){ |volume|
30
- volume += ":\\"
31
- case GetDriveType(volume)
32
- when DRIVE_REMOVABLE, DRIVE_CDROM, DRIVE_RAMDISK
33
- @@block_dev = volume
34
- break
35
- end
36
- }
37
-
38
- @@txt_file = File.join(File.expand_path(File.dirname(__FILE__)), 'test_file.txt')
39
- @@exe_file = File.join(File.expand_path(File.dirname(__FILE__)), 'test_file.exe')
40
- @@sys_file = 'C:/pagefile.sys'
41
- @@elevated = Win32::Security.elevated_security?
42
- @@jruby = RUBY_PLATFORM == 'java'
43
-
44
- File.open(@@txt_file, "w"){ |fh| fh.print "This is a test\nHello" }
45
- File.open(@@exe_file, "wb"){ |fh| fh.print "This is a test" }
46
- end
47
-
48
- def setup
49
- @dir = Dir.pwd
50
- @stat = File::Stat.new(@@txt_file)
51
- @temp = 'win32_file_stat.tmp'
52
- File.open(@temp, 'w'){}
53
- @attr = GetFileAttributes(@@txt_file)
54
- end
55
-
56
- test "version is set to expected value" do
57
- assert_equal('1.5.1', File::Stat::WIN32_FILE_STAT_VERSION)
58
- end
59
-
60
- test "constructor does not modify argument" do
61
- expected = File.join(File.expand_path(File.dirname(__FILE__)), 'test_file.txt')
62
- File::Stat.new(@@txt_file)
63
- assert_equal(expected, @@txt_file)
64
- end
65
-
66
- test "constructor allows arguments that implement to_path" do
67
- assert_nothing_raised{ File::Stat.new(Pathname.new(Dir.pwd)) }
68
- end
69
-
70
- test "archive? method basic functionality" do
71
- assert_respond_to(@stat, :archive?)
72
- assert_nothing_raised{ @stat.archive? }
73
- end
74
-
75
- test "archive? method returns a boolean value" do
76
- assert_boolean(@stat.archive?)
77
- end
78
-
79
- test "atime method basic functionality" do
80
- assert_respond_to(@stat, :atime)
81
- assert_nothing_raised{ @stat.atime }
82
- end
83
-
84
- test "atime method returns expected value" do
85
- assert_kind_of(Time, @stat.atime)
86
- assert_true(@stat.atime.to_i > 0)
87
- end
88
-
89
- test "mtime method basic functionality" do
90
- assert_respond_to(@stat, :mtime)
91
- assert_nothing_raised{ @stat.mtime }
92
- end
93
-
94
- test "mtime method returns expected value" do
95
- assert_kind_of(Time, @stat.mtime)
96
- assert_true(@stat.mtime.to_i > 0)
97
- end
98
-
99
- test "ctime method basic functionality" do
100
- assert_respond_to(@stat, :ctime)
101
- assert_nothing_raised{ @stat.ctime }
102
- end
103
-
104
- test "ctime method returns expected value" do
105
- assert_kind_of(Time, @stat.ctime)
106
- assert_true(@stat.ctime.to_i > 0)
107
- end
108
-
109
- test "blksize basic functionality" do
110
- assert_respond_to(@stat, :blksize)
111
- assert_kind_of(Fixnum, @stat.blksize)
112
- end
113
-
114
- test "blksize returns expected value" do
115
- assert_equal(4096, @stat.blksize)
116
- assert_equal(4096, File::Stat.new("C:\\").blksize)
117
- end
118
-
119
- test "blockdev? basic functionality" do
120
- assert_respond_to(@stat, :blockdev?)
121
- assert_boolean(@stat.blockdev?)
122
- end
123
-
124
- test "blockdev? returns the expected value for a non-block device" do
125
- assert_false(@stat.blockdev?)
126
- assert_false(File::Stat.new('NUL').blockdev?)
127
- end
128
-
129
- # In unusual situations this could fail.
130
- test "blockdev? returns the expected value for a block device" do
131
- omit_unless(@@block_dev)
132
- assert_true(File::Stat.new(@@block_dev).blockdev?)
133
- end
134
-
135
- test "blocks basic functionality" do
136
- assert_respond_to(@stat, :blocks)
137
- assert_kind_of(Fixnum, @stat.blocks)
138
- end
139
-
140
- test "blocks method returns expected value" do
141
- assert_equal(1, @stat.blocks)
142
- end
143
-
144
- test "chardev? custom method basic functionality" do
145
- assert_respond_to(@stat, :chardev?)
146
- assert_boolean(@stat.chardev?)
147
- end
148
-
149
- test "chardev? custom method returns expected value" do
150
- assert_true(File::Stat.new("NUL").chardev?)
151
- assert_false(File::Stat.new("C:\\").chardev?)
152
- end
153
-
154
- test "custom comparison method basic functionality" do
155
- assert_respond_to(@stat, :<=>)
156
- assert_nothing_raised{ @stat <=> File::Stat.new(@@exe_file) }
157
- end
158
-
159
- test "custom comparison method works as expected" do
160
- assert_equal(0, @stat <=> @stat)
161
- end
162
-
163
- test "compressed? basic functionality" do
164
- assert_respond_to(@stat, :compressed?)
165
- assert_boolean(@stat.compressed?)
166
- end
167
-
168
- test "compressed? returns expected value" do
169
- assert_false(@stat.compressed?)
170
- end
171
-
172
- test "dev custom method basic functionality" do
173
- assert_respond_to(@stat, :rdev)
174
- assert_kind_of(Numeric, @stat.rdev)
175
- end
176
-
177
- test "dev custom method returns expected value" do
178
- notify "May fail on JRuby" if @@jruby
179
- assert_equal(2, File::Stat.new("C:\\").dev)
180
- assert_equal(-1, File::Stat.new("NUL").dev)
181
- end
182
-
183
- test "dev custom method accepts an optional argument" do
184
- assert_nothing_raised{ File::Stat.new("C:\\").dev(true) }
185
- assert_kind_of(String, File::Stat.new("C:\\").dev(true))
186
- end
187
-
188
- test "dev custom method with optional argument returns expected value" do
189
- notify "May fail on JRuby" if @@jruby
190
- assert_equal("C:", File::Stat.new("C:\\").dev(true))
191
- assert_nil(File::Stat.new("NUL").dev(true))
192
- end
193
-
194
- test "dev_major defined and always returns nil" do
195
- omit_if(@@jruby) # https://github.com/jnr/jnr-posix/issues/23
196
- assert_respond_to(@stat, :dev_major)
197
- assert_nil(@stat.dev_major)
198
- end
199
-
200
- test "dev_minor defined and always returns nil" do
201
- omit_if(@@jruby) # https://github.com/jnr/jnr-posix/issues/23
202
- assert_respond_to(@stat, :dev_minor)
203
- assert_nil(@stat.dev_minor)
204
- end
205
-
206
- test "directory? custom method basic functionality" do
207
- assert_respond_to(@stat, :directory?)
208
- assert_boolean(@stat.directory?)
209
- end
210
-
211
- test "directory? custom method returns expected value" do
212
- assert_false(@stat.directory?)
213
- assert_true(File::Stat.new("C:\\").directory?)
214
- end
215
-
216
- test "executable? custom method basic functionality" do
217
- assert_respond_to(@stat, :executable?)
218
- assert_boolean(@stat.executable?)
219
- end
220
-
221
- test "executable? custom method returns expected value" do
222
- assert_false(@stat.executable?)
223
- assert_true(File::Stat.new(@@exe_file).executable?)
224
- end
225
-
226
- test "executable_real? is an alias for executable?" do
227
- assert_respond_to(@stat, :executable_real?)
228
- assert_alias_method(@stat, :executable?, :executable_real?)
229
- end
230
-
231
- test "file? custom method basic functionality" do
232
- assert_respond_to(@stat, :file?)
233
- assert_boolean(@stat.file?)
234
- end
235
-
236
- test "file? custom method returns expected value" do
237
- assert_true(@stat.file?)
238
- assert_true(File::Stat.new(@@exe_file).file?)
239
- assert_true(File::Stat.new(Dir.pwd).file?)
240
- assert_false(File::Stat.new('NUL').file?)
241
- end
242
-
243
- test "ftype custom method basic functionality" do
244
- assert_respond_to(@stat, :ftype)
245
- assert_kind_of(String, @stat.ftype)
246
- end
247
-
248
- test "ftype custom method returns expected value" do
249
- assert_equal('file', @stat.ftype)
250
- assert_equal('characterSpecial', File::Stat.new('NUL').ftype)
251
- assert_equal('directory', File::Stat.new(Dir.pwd).ftype)
252
- end
253
-
254
- test "encrypted? basic functionality" do
255
- assert_respond_to(@stat, :encrypted?)
256
- assert_boolean(@stat.encrypted?)
257
- end
258
-
259
- test "encrypted? returns the expected value" do
260
- assert_false(@stat.encrypted?)
261
- end
262
-
263
- test "gid method basic functionality" do
264
- assert_respond_to(@stat, :gid)
265
- assert_nothing_raised{ @stat.gid }
266
- assert_kind_of(Fixnum, @stat.gid)
267
- end
268
-
269
- test "gid returns a sane result" do
270
- assert_true(@stat.gid >= 0 && @stat.gid <= 10000)
271
- end
272
-
273
- test "gid returns a string argument if true argument provided" do
274
- assert_nothing_raised{ @stat.gid(true) }
275
- assert_match("S-1-", @stat.gid(true))
276
- end
277
-
278
- test "grpowned? defined and always returns true" do
279
- assert_respond_to(@stat, :grpowned?)
280
- end
281
-
282
- test "hidden? basic functionality" do
283
- assert_respond_to(@stat, :hidden?)
284
- assert_boolean(@stat.hidden?)
285
- end
286
-
287
- test "hidden? returns expected value" do
288
- assert_false(@stat.hidden?)
289
- end
290
-
291
- test "indexed? basic functionality" do
292
- assert_respond_to(@stat, :indexed?)
293
- assert_boolean(@stat.indexed?)
294
- end
295
-
296
- test "indexed? returns expected value" do
297
- assert_true(@stat.indexed?)
298
- end
299
-
300
- test "content_indexed? is an alias for indexed?" do
301
- assert_respond_to(@stat, :content_indexed?)
302
- assert_alias_method(@stat, :indexed?, :content_indexed?)
303
- end
304
-
305
- test "ino method basic functionality" do
306
- assert_respond_to(@stat, :ino)
307
- assert_nothing_raised{ @stat.ino }
308
- assert_kind_of(Numeric, @stat.ino)
309
- end
310
-
311
- test "ino method returns a sane value" do
312
- assert_true(@stat.ino > 1000)
313
- end
314
-
315
- test "ino method returns nil on a special device" do
316
- assert_nil(File::Stat.new("NUL").ino)
317
- end
318
-
319
- test "inspect custom method basic functionality" do
320
- assert_respond_to(@stat, :inspect)
321
- end
322
-
323
- test "inspect string contains expected values" do
324
- assert_match('File::Stat', @stat.inspect)
325
- assert_match('compressed', @stat.inspect)
326
- assert_match('normal', @stat.inspect)
327
- end
328
-
329
- test "mode custom method basic functionality" do
330
- assert_respond_to(@stat, :mode)
331
- assert_kind_of(Fixnum, @stat.mode)
332
- end
333
-
334
- test "mode custom method returns the expected value" do
335
- assert_equal(33188, File::Stat.new(@@txt_file).mode)
336
- assert_equal(33261, File::Stat.new(@@exe_file).mode)
337
- assert_equal(16877, File::Stat.new(@dir).mode)
338
- end
339
-
340
- test "mode custom method returns expected value for readonly file" do
341
- SetFileAttributes(@@txt_file, 1) # Set to readonly.
342
- assert_equal(33060, File::Stat.new(@@txt_file).mode)
343
- end
344
-
345
- test "nlink basic functionality" do
346
- assert_respond_to(@stat, :nlink)
347
- assert_kind_of(Fixnum, @stat.nlink)
348
- end
349
-
350
- test "nlink returns the expected value" do
351
- assert_equal(1, @stat.nlink)
352
- assert_equal(1, File::Stat.new(Dir.pwd).nlink)
353
- assert_equal(1, File::Stat.new('NUL').nlink)
354
- end
355
-
356
- test "normal? basic functionality" do
357
- assert_respond_to(@stat, :normal?)
358
- assert_boolean(@stat.normal?)
359
- end
360
-
361
- test "normal? returns expected value" do
362
- assert_false(@stat.normal?)
363
- end
364
-
365
- test "offline? method basic functionality" do
366
- assert_respond_to(@stat, :offline?)
367
- assert_boolean(@stat.offline?)
368
- end
369
-
370
- test "offline? method returns expected value" do
371
- assert_false(@stat.offline?)
372
- end
373
-
374
- test "owned? method basic functionality" do
375
- assert_respond_to(@stat, :owned?)
376
- assert_boolean(@stat.owned?)
377
- end
378
-
379
- test "owned? returns the expected results" do
380
- if @@elevated
381
- assert_false(@stat.owned?)
382
- else
383
- assert_true(@stat.owned?)
384
- end
385
- assert_false(File::Stat.new(@@sys_file).owned?)
386
- end
387
-
388
- test "pipe? custom method basic functionality" do
389
- assert_respond_to(@stat, :pipe?)
390
- assert_boolean(@stat.pipe?)
391
- end
392
-
393
- test "pipe? custom method returns expected value" do
394
- assert_false(@stat.pipe?)
395
- end
396
-
397
- test "socket? is an alias for pipe?" do
398
- assert_respond_to(@stat, :socket?)
399
- assert_alias_method(@stat, :socket?, :pipe?)
400
- end
401
-
402
- test "readable? basic functionality" do
403
- assert_respond_to(@stat, :readable?)
404
- assert_boolean(@stat.readable?)
405
- end
406
-
407
- test "readable? returns expected value" do
408
- assert_true(@stat.readable?)
409
- assert_true(File::Stat.new(Dir.pwd).readable?)
410
- assert_false(File::Stat.new(@@sys_file).readable?)
411
- end
412
-
413
- test "readable_real? basic functionality" do
414
- assert_respond_to(@stat, :readable_real?)
415
- assert_boolean(@stat.readable_real?)
416
- end
417
-
418
- test "readable_real? returns expected value" do
419
- assert_true(@stat.readable_real?)
420
- end
421
-
422
- test "readonly? basic functionality" do
423
- assert_respond_to(@stat, :readonly?)
424
- assert_boolean(@stat.readonly?)
425
- end
426
-
427
- test "readonly? returns the expected value" do
428
- assert_false(@stat.readonly?)
429
- SetFileAttributes(@@txt_file, 1)
430
- assert_true(File::Stat.new(@@txt_file).readonly?)
431
- end
432
-
433
- test "read_only? is an alias for readonly?" do
434
- assert_respond_to(@stat, :read_only?)
435
- assert_alias_method(@stat, :readonly?, :read_only?)
436
- end
437
-
438
- test "reparse_point? basic functionality" do
439
- assert_respond_to(@stat, :reparse_point?)
440
- assert_boolean(@stat.reparse_point?)
441
- end
442
-
443
- test "reparse_point returns expected value" do
444
- assert_false(@stat.reparse_point?)
445
- end
446
-
447
- test "rdev basic functionality" do
448
- assert_respond_to(@stat, :rdev)
449
- assert_nothing_raised{ @stat.rdev }
450
- assert_kind_of(Numeric, @stat.rdev)
451
- end
452
-
453
- test "rdev returns a sane value" do
454
- assert_true(File::Stat.new("C:\\Program Files").rdev > 1000)
455
- end
456
-
457
- test "rdev returns nil on special files" do
458
- assert_equal(nil, File::Stat.new("NUL").rdev)
459
- end
460
-
461
- # Not sure how to test properly in a generic way, but works on my local network
462
- test "rdev works on unc path" do
463
- omit_unless(Etc.getlogin == "djberge" && File.exist?("//scipio/users"))
464
- assert_true(File::Stat.new("//scipio/users").rdev > 1000)
465
- end
466
-
467
- test "rdev_major defined and always returns nil" do
468
- omit_if(@@jruby) # https://github.com/jnr/jnr-posix/issues/23
469
- assert_respond_to(@stat, :rdev_major)
470
- assert_nil(@stat.rdev_major)
471
- end
472
-
473
- test "rdev_minor defined and always returns nil" do
474
- omit_if(@@jruby) # https://github.com/jnr/jnr-posix/issues/23
475
- assert_respond_to(@stat, :rdev_minor)
476
- assert_nil(@stat.rdev_minor)
477
- end
478
-
479
- test "setgid is set to false" do
480
- assert_respond_to(@stat, :setgid?)
481
- assert_false(@stat.setgid?)
482
- end
483
-
484
- test "setuid is set to false" do
485
- assert_respond_to(@stat, :setuid?)
486
- assert_false(@stat.setuid?)
487
- end
488
-
489
- test "size custom method basic functionality" do
490
- assert_respond_to(@stat, :size)
491
- assert_kind_of(Numeric, @stat.size)
492
- end
493
-
494
- test "size custom method returns expected value" do
495
- assert_equal(21, @stat.size)
496
- @stat = File::Stat.new(@temp)
497
- assert_equal(0, @stat.size)
498
- end
499
-
500
- test "size custom method works on system files" do
501
- assert_nothing_raised{ File::Stat.new(@@sys_file).size }
502
- end
503
-
504
- test "size? method basic functionality" do
505
- assert_respond_to(@stat, :size?)
506
- assert_kind_of(Numeric, @stat.size)
507
- end
508
-
509
- test "size? method returns integer if size greater than zero" do
510
- assert_equal(21, @stat.size?)
511
- end
512
-
513
- test "size? method returns nil if size is zero" do
514
- @stat = File::Stat.new(@temp)
515
- assert_nil(@stat.size?)
516
- end
517
-
518
- test "sparse? basic fucntionality" do
519
- assert_respond_to(@stat, :sparse?)
520
- assert_boolean(@stat.sparse?)
521
- end
522
-
523
- test "sparse? returns expected value" do
524
- assert_false(@stat.sparse?)
525
- end
526
-
527
- test "sticky is always set to false" do
528
- assert_respond_to(@stat, :sticky?)
529
- assert_false(@stat.sticky?)
530
- end
531
-
532
- test "streams basic functionality" do
533
- assert_respond_to(@stat, :streams)
534
- assert_kind_of(Array, @stat.streams)
535
- end
536
-
537
- test "streams returns expected value" do
538
- assert_equal("::$DATA", @stat.streams[0])
539
- end
540
-
541
- test "symlink? basic functionality" do
542
- assert_respond_to(@stat, :symlink?)
543
- assert_boolean(@stat.symlink?)
544
- end
545
-
546
- test "symlink? returns expected value" do
547
- assert_false(@stat.symlink?)
548
- end
549
-
550
- test "system? basic functionality" do
551
- assert_respond_to(@stat, :system?)
552
- assert_boolean(@stat.system?)
553
- end
554
-
555
- test "system? returns expected value" do
556
- assert_false(@stat.system?)
557
- end
558
-
559
- test "temporary? basic functionality" do
560
- assert_respond_to(@stat, :temporary?)
561
- assert_boolean(@stat.temporary?)
562
- end
563
-
564
- test "temporary? returns expected value" do
565
- assert_false(@stat.temporary?)
566
- end
567
-
568
- test "uid basic functionality" do
569
- assert_respond_to(@stat, :uid)
570
- assert_nothing_raised{ @stat.uid }
571
- assert_kind_of(Fixnum, @stat.uid)
572
- end
573
-
574
- test "uid returns a sane result" do
575
- assert_true(@stat.uid >= 0 && @stat.uid <= 10000)
576
- end
577
-
578
- test "uid returns a string argument if true argument provided" do
579
- assert_nothing_raised{ @stat.uid(true) }
580
- assert_match("S-1-", @stat.uid(true))
581
- end
582
-
583
- test "world_readable? basic functionality" do
584
- assert_respond_to(@stat, :world_readable?)
585
- assert_boolean(@stat.world_readable?)
586
- end
587
-
588
- # TODO: Find or create a file that returns true.
589
- test "world_readable? returns expected result" do
590
- assert_false(@stat.world_readable?)
591
- assert_false(File::Stat.new("C:/").world_readable?)
592
- end
593
-
594
- test "world_writable? basic functionality" do
595
- assert_respond_to(@stat, :world_writable?)
596
- assert_boolean(@stat.world_writable?)
597
- end
598
-
599
- # TODO: Find or create a file that returns true.
600
- test "world_writable? returns expected result" do
601
- assert_false(@stat.world_writable?)
602
- assert_false(File::Stat.new("C:/").world_writable?)
603
- end
604
-
605
- test "writable? basic functionality" do
606
- assert_respond_to(@stat, :writable?)
607
- assert_boolean(@stat.writable?)
608
- end
609
-
610
- test "writable? returns expected value" do
611
- assert_true(@stat.writable?)
612
- assert_true(File::Stat.new(Dir.pwd).writable?)
613
- assert_false(File::Stat.new(@@sys_file).writable?)
614
- end
615
-
616
- test "writable? returns expected result for system directory" do
617
- dir = "C:/Program Files"
618
- if @@elevated
619
- assert_true(File::Stat.new(dir).writable?)
620
- else
621
- assert_false(File::Stat.new(dir).writable?)
622
- end
623
- end
624
-
625
- test "a file marked as readonly is not considered writable" do
626
- File.chmod(0644, @@txt_file)
627
- assert_true(File::Stat.new(@@txt_file).writable?)
628
- File.chmod(0444, @@txt_file)
629
- assert_false(File::Stat.new(@@txt_file).writable?)
630
- end
631
-
632
- test "writable_real? basic functionality" do
633
- assert_respond_to(@stat, :writable_real?)
634
- assert_boolean(@stat.writable_real?)
635
- end
636
-
637
- test "writable_real? returns expected value" do
638
- assert_true(@stat.writable_real?)
639
- end
640
-
641
- test "zero? method basic functionality" do
642
- assert_respond_to(@stat, :zero?)
643
- assert_boolean(@stat.zero?)
644
- end
645
-
646
- test "zero? method returns expected value" do
647
- assert_false(@stat.zero?)
648
- @stat = File::Stat.new(@temp)
649
- assert_true(@stat.zero?)
650
- end
651
-
652
- test "ffi functions are private" do
653
- assert_not_respond_to(@stat, :CloseHandle)
654
- assert_not_respond_to(File::Stat, :CloseHandle)
655
- end
656
-
657
- def teardown
658
- SetFileAttributes(@@txt_file, @attr) # Set file back to normal
659
- File.delete(@temp) if File.exist?(@temp)
660
- @dir = nil
661
- @stat = nil
662
- @attr = nil
663
- @temp = nil
664
- end
665
-
666
- def self.shutdown
667
- File.delete(@@txt_file) if File.exist?(@@txt_file)
668
- File.delete(@@exe_file) if File.exist?(@@exe_file)
669
-
670
- @@block_dev = nil
671
- @@txt_file = nil
672
- @@exe_file = nil
673
- @@sys_file = nil
674
- @@elevated = nil
675
- @@jruby = nil
676
- end
677
- end
1
+ #####################################################################
2
+ # test_file_stat.rb
3
+ #
4
+ # Test case for stat related methods of win32-file. You should use
5
+ # the 'rake test' task to run these tests.
6
+ #####################################################################
7
+ require 'etc'
8
+ require 'ffi'
9
+ require 'test-unit'
10
+ require 'win32/file/stat'
11
+ require 'win32/security'
12
+ require 'pathname'
13
+
14
+ class TC_Win32_File_Stat < Test::Unit::TestCase
15
+ extend FFI::Library
16
+ ffi_lib :kernel32
17
+
18
+ attach_function :GetDriveType, :GetDriveTypeA, [:string], :ulong
19
+ attach_function :GetFileAttributes, :GetFileAttributesA, [:string], :ulong
20
+ attach_function :SetFileAttributes, :SetFileAttributesA, [:string, :ulong], :bool
21
+
22
+ DRIVE_REMOVABLE = 2
23
+ DRIVE_CDROM = 5
24
+ DRIVE_RAMDISK = 6
25
+
26
+ def self.startup
27
+ @@block_dev = nil
28
+
29
+ 'A'.upto('Z'){ |volume|
30
+ volume += ":\\"
31
+ case GetDriveType(volume)
32
+ when DRIVE_REMOVABLE, DRIVE_CDROM, DRIVE_RAMDISK
33
+ @@block_dev = volume
34
+ break
35
+ end
36
+ }
37
+
38
+ @@txt_file = File.join(File.expand_path(File.dirname(__FILE__)), 'test_file.txt')
39
+ @@exe_file = File.join(File.expand_path(File.dirname(__FILE__)), 'test_file.exe')
40
+ @@sys_file = 'C:/pagefile.sys'
41
+ @@elevated = Win32::Security.elevated_security?
42
+ @@jruby = RUBY_PLATFORM == 'java'
43
+
44
+ File.open(@@txt_file, "w"){ |fh| fh.print "This is a test\nHello" }
45
+ File.open(@@exe_file, "wb"){ |fh| fh.print "This is a test" }
46
+ end
47
+
48
+ def setup
49
+ @dir = Dir.pwd
50
+ @stat = File::Stat.new(@@txt_file)
51
+ @temp = 'win32_file_stat.tmp'
52
+ File.open(@temp, 'w'){}
53
+ @attr = GetFileAttributes(@@txt_file)
54
+ end
55
+
56
+ test "version is set to expected value" do
57
+ assert_equal('1.5.2', File::Stat::WIN32_FILE_STAT_VERSION)
58
+ end
59
+
60
+ test "constructor does not modify argument" do
61
+ expected = File.join(File.expand_path(File.dirname(__FILE__)), 'test_file.txt')
62
+ File::Stat.new(@@txt_file)
63
+ assert_equal(expected, @@txt_file)
64
+ end
65
+
66
+ test "constructor allows arguments that implement to_path" do
67
+ assert_nothing_raised{ File::Stat.new(Pathname.new(Dir.pwd)) }
68
+ end
69
+
70
+ test "archive? method basic functionality" do
71
+ assert_respond_to(@stat, :archive?)
72
+ assert_nothing_raised{ @stat.archive? }
73
+ end
74
+
75
+ test "archive? method returns a boolean value" do
76
+ assert_boolean(@stat.archive?)
77
+ end
78
+
79
+ test "atime method basic functionality" do
80
+ assert_respond_to(@stat, :atime)
81
+ assert_nothing_raised{ @stat.atime }
82
+ end
83
+
84
+ test "atime method returns expected value" do
85
+ assert_kind_of(Time, @stat.atime)
86
+ assert_true(@stat.atime.to_i > 0)
87
+ end
88
+
89
+ test "mtime method basic functionality" do
90
+ assert_respond_to(@stat, :mtime)
91
+ assert_nothing_raised{ @stat.mtime }
92
+ end
93
+
94
+ test "mtime method returns expected value" do
95
+ assert_kind_of(Time, @stat.mtime)
96
+ assert_true(@stat.mtime.to_i > 0)
97
+ end
98
+
99
+ test "ctime method basic functionality" do
100
+ assert_respond_to(@stat, :ctime)
101
+ assert_nothing_raised{ @stat.ctime }
102
+ end
103
+
104
+ test "ctime method returns expected value" do
105
+ assert_kind_of(Time, @stat.ctime)
106
+ assert_true(@stat.ctime.to_i > 0)
107
+ end
108
+
109
+ test "blksize basic functionality" do
110
+ assert_respond_to(@stat, :blksize)
111
+ assert_kind_of(Fixnum, @stat.blksize)
112
+ end
113
+
114
+ test "blksize returns expected value" do
115
+ assert_equal(4096, @stat.blksize)
116
+ assert_equal(4096, File::Stat.new("C:\\").blksize)
117
+ end
118
+
119
+ test "blockdev? basic functionality" do
120
+ assert_respond_to(@stat, :blockdev?)
121
+ assert_boolean(@stat.blockdev?)
122
+ end
123
+
124
+ test "blockdev? returns the expected value for a non-block device" do
125
+ assert_false(@stat.blockdev?)
126
+ assert_false(File::Stat.new('NUL').blockdev?)
127
+ end
128
+
129
+ # In unusual situations (such as VM's that set A:) this could fail.
130
+ test "blockdev? returns the expected value for a block device" do
131
+ omit_unless(@@block_dev)
132
+ assert_true(File::Stat.new(@@block_dev).blockdev?)
133
+ end
134
+
135
+ test "blocks basic functionality" do
136
+ assert_respond_to(@stat, :blocks)
137
+ assert_kind_of(Fixnum, @stat.blocks)
138
+ end
139
+
140
+ test "blocks method returns expected value" do
141
+ assert_equal(1, @stat.blocks)
142
+ end
143
+
144
+ test "chardev? custom method basic functionality" do
145
+ assert_respond_to(@stat, :chardev?)
146
+ assert_boolean(@stat.chardev?)
147
+ end
148
+
149
+ test "chardev? custom method returns expected value" do
150
+ assert_true(File::Stat.new("NUL").chardev?)
151
+ assert_false(File::Stat.new("C:\\").chardev?)
152
+ end
153
+
154
+ test "custom comparison method basic functionality" do
155
+ assert_respond_to(@stat, :<=>)
156
+ assert_nothing_raised{ @stat <=> File::Stat.new(@@exe_file) }
157
+ end
158
+
159
+ test "custom comparison method works as expected" do
160
+ assert_equal(0, @stat <=> @stat)
161
+ end
162
+
163
+ test "compressed? basic functionality" do
164
+ assert_respond_to(@stat, :compressed?)
165
+ assert_boolean(@stat.compressed?)
166
+ end
167
+
168
+ test "compressed? returns expected value" do
169
+ assert_false(@stat.compressed?)
170
+ end
171
+
172
+ test "dev custom method basic functionality" do
173
+ assert_respond_to(@stat, :rdev)
174
+ assert_kind_of(Numeric, @stat.rdev)
175
+ end
176
+
177
+ test "dev custom method returns expected value" do
178
+ notify "May fail on JRuby" if @@jruby
179
+ assert_equal(2, File::Stat.new("C:\\").dev)
180
+ assert_equal(-1, File::Stat.new("NUL").dev)
181
+ end
182
+
183
+ test "dev custom method accepts an optional argument" do
184
+ assert_nothing_raised{ File::Stat.new("C:\\").dev(true) }
185
+ assert_kind_of(String, File::Stat.new("C:\\").dev(true))
186
+ end
187
+
188
+ test "dev custom method with optional argument returns expected value" do
189
+ notify "May fail on JRuby" if @@jruby
190
+ assert_equal("C:", File::Stat.new("C:\\").dev(true))
191
+ assert_nil(File::Stat.new("NUL").dev(true))
192
+ end
193
+
194
+ test "dev_major defined and always returns nil" do
195
+ omit_if(@@jruby) # https://github.com/jnr/jnr-posix/issues/23
196
+ assert_respond_to(@stat, :dev_major)
197
+ assert_nil(@stat.dev_major)
198
+ end
199
+
200
+ test "dev_minor defined and always returns nil" do
201
+ omit_if(@@jruby) # https://github.com/jnr/jnr-posix/issues/23
202
+ assert_respond_to(@stat, :dev_minor)
203
+ assert_nil(@stat.dev_minor)
204
+ end
205
+
206
+ test "directory? custom method basic functionality" do
207
+ assert_respond_to(@stat, :directory?)
208
+ assert_boolean(@stat.directory?)
209
+ end
210
+
211
+ test "directory? custom method returns expected value" do
212
+ assert_false(@stat.directory?)
213
+ assert_true(File::Stat.new("C:\\").directory?)
214
+ end
215
+
216
+ test "executable? custom method basic functionality" do
217
+ assert_respond_to(@stat, :executable?)
218
+ assert_boolean(@stat.executable?)
219
+ end
220
+
221
+ test "executable? custom method returns expected value" do
222
+ assert_false(@stat.executable?)
223
+ assert_true(File::Stat.new(@@exe_file).executable?)
224
+ end
225
+
226
+ test "executable_real? is an alias for executable?" do
227
+ assert_respond_to(@stat, :executable_real?)
228
+ assert_alias_method(@stat, :executable?, :executable_real?)
229
+ end
230
+
231
+ test "file? custom method basic functionality" do
232
+ assert_respond_to(@stat, :file?)
233
+ assert_boolean(@stat.file?)
234
+ end
235
+
236
+ test "file? custom method returns expected value" do
237
+ assert_true(@stat.file?)
238
+ assert_true(File::Stat.new(@@exe_file).file?)
239
+ assert_false(File::Stat.new(Dir.pwd).file?)
240
+ assert_false(File::Stat.new('NUL').file?)
241
+ end
242
+
243
+ test "ftype custom method basic functionality" do
244
+ assert_respond_to(@stat, :ftype)
245
+ assert_kind_of(String, @stat.ftype)
246
+ end
247
+
248
+ test "ftype custom method returns expected value" do
249
+ assert_equal('file', @stat.ftype)
250
+ assert_equal('characterSpecial', File::Stat.new('NUL').ftype)
251
+ assert_equal('directory', File::Stat.new(Dir.pwd).ftype)
252
+ end
253
+
254
+ test "encrypted? basic functionality" do
255
+ assert_respond_to(@stat, :encrypted?)
256
+ assert_boolean(@stat.encrypted?)
257
+ end
258
+
259
+ test "encrypted? returns the expected value" do
260
+ assert_false(@stat.encrypted?)
261
+ end
262
+
263
+ test "gid method basic functionality" do
264
+ assert_respond_to(@stat, :gid)
265
+ assert_nothing_raised{ @stat.gid }
266
+ assert_kind_of(Fixnum, @stat.gid)
267
+ end
268
+
269
+ test "gid returns a sane result" do
270
+ assert_true(@stat.gid >= 0 && @stat.gid <= 10000)
271
+ end
272
+
273
+ test "gid returns a string argument if true argument provided" do
274
+ assert_nothing_raised{ @stat.gid(true) }
275
+ assert_match("S-1-", @stat.gid(true))
276
+ end
277
+
278
+ test "grpowned? defined and always returns true" do
279
+ assert_respond_to(@stat, :grpowned?)
280
+ end
281
+
282
+ test "hidden? basic functionality" do
283
+ assert_respond_to(@stat, :hidden?)
284
+ assert_boolean(@stat.hidden?)
285
+ end
286
+
287
+ test "hidden? returns expected value" do
288
+ assert_false(@stat.hidden?)
289
+ end
290
+
291
+ test "indexed? basic functionality" do
292
+ assert_respond_to(@stat, :indexed?)
293
+ assert_boolean(@stat.indexed?)
294
+ end
295
+
296
+ test "indexed? returns expected value" do
297
+ assert_true(@stat.indexed?)
298
+ end
299
+
300
+ test "content_indexed? is an alias for indexed?" do
301
+ assert_respond_to(@stat, :content_indexed?)
302
+ assert_alias_method(@stat, :indexed?, :content_indexed?)
303
+ end
304
+
305
+ test "ino method basic functionality" do
306
+ assert_respond_to(@stat, :ino)
307
+ assert_nothing_raised{ @stat.ino }
308
+ assert_kind_of(Numeric, @stat.ino)
309
+ end
310
+
311
+ test "ino method returns a sane value" do
312
+ assert_true(@stat.ino > 1000)
313
+ end
314
+
315
+ test "ino method returns nil on a special device" do
316
+ assert_nil(File::Stat.new("NUL").ino)
317
+ end
318
+
319
+ test "inspect custom method basic functionality" do
320
+ assert_respond_to(@stat, :inspect)
321
+ end
322
+
323
+ test "inspect string contains expected values" do
324
+ assert_match('File::Stat', @stat.inspect)
325
+ assert_match('compressed', @stat.inspect)
326
+ assert_match('normal', @stat.inspect)
327
+ end
328
+
329
+ test "mode custom method basic functionality" do
330
+ assert_respond_to(@stat, :mode)
331
+ assert_kind_of(Fixnum, @stat.mode)
332
+ end
333
+
334
+ test "mode custom method returns the expected value" do
335
+ assert_equal(33188, File::Stat.new(@@txt_file).mode)
336
+ assert_equal(33261, File::Stat.new(@@exe_file).mode)
337
+ assert_equal(16877, File::Stat.new(@dir).mode)
338
+ end
339
+
340
+ test "mode custom method returns expected value for readonly file" do
341
+ SetFileAttributes(@@txt_file, 1) # Set to readonly.
342
+ assert_equal(33060, File::Stat.new(@@txt_file).mode)
343
+ end
344
+
345
+ test "nlink basic functionality" do
346
+ assert_respond_to(@stat, :nlink)
347
+ assert_kind_of(Fixnum, @stat.nlink)
348
+ end
349
+
350
+ test "nlink returns the expected value" do
351
+ assert_equal(1, @stat.nlink)
352
+ assert_equal(1, File::Stat.new(Dir.pwd).nlink)
353
+ assert_equal(1, File::Stat.new('NUL').nlink)
354
+ end
355
+
356
+ test "normal? basic functionality" do
357
+ assert_respond_to(@stat, :normal?)
358
+ assert_boolean(@stat.normal?)
359
+ end
360
+
361
+ test "normal? returns expected value" do
362
+ assert_false(@stat.normal?)
363
+ end
364
+
365
+ test "offline? method basic functionality" do
366
+ assert_respond_to(@stat, :offline?)
367
+ assert_boolean(@stat.offline?)
368
+ end
369
+
370
+ test "offline? method returns expected value" do
371
+ assert_false(@stat.offline?)
372
+ end
373
+
374
+ test "owned? method basic functionality" do
375
+ assert_respond_to(@stat, :owned?)
376
+ assert_boolean(@stat.owned?)
377
+ end
378
+
379
+ test "owned? returns the expected results" do
380
+ if @@elevated
381
+ assert_false(@stat.owned?)
382
+ else
383
+ assert_true(@stat.owned?)
384
+ end
385
+ assert_false(File::Stat.new(@@sys_file).owned?)
386
+ end
387
+
388
+ test "pipe? custom method basic functionality" do
389
+ assert_respond_to(@stat, :pipe?)
390
+ assert_boolean(@stat.pipe?)
391
+ end
392
+
393
+ test "pipe? custom method returns expected value" do
394
+ assert_false(@stat.pipe?)
395
+ end
396
+
397
+ test "socket? custom method basic functionality" do
398
+ assert_respond_to(@stat, :socket?)
399
+ assert_boolean(@stat.socket?)
400
+ end
401
+
402
+ test "readable? basic functionality" do
403
+ assert_respond_to(@stat, :readable?)
404
+ assert_boolean(@stat.readable?)
405
+ end
406
+
407
+ test "readable? returns expected value" do
408
+ assert_true(@stat.readable?)
409
+ assert_true(File::Stat.new(Dir.pwd).readable?)
410
+ assert_false(File::Stat.new(@@sys_file).readable?)
411
+ end
412
+
413
+ test "readable_real? basic functionality" do
414
+ assert_respond_to(@stat, :readable_real?)
415
+ assert_boolean(@stat.readable_real?)
416
+ end
417
+
418
+ test "readable_real? returns expected value" do
419
+ assert_true(@stat.readable_real?)
420
+ end
421
+
422
+ test "readonly? basic functionality" do
423
+ assert_respond_to(@stat, :readonly?)
424
+ assert_boolean(@stat.readonly?)
425
+ end
426
+
427
+ test "readonly? returns the expected value" do
428
+ assert_false(@stat.readonly?)
429
+ SetFileAttributes(@@txt_file, 1)
430
+ assert_true(File::Stat.new(@@txt_file).readonly?)
431
+ end
432
+
433
+ test "read_only? is an alias for readonly?" do
434
+ assert_respond_to(@stat, :read_only?)
435
+ assert_alias_method(@stat, :readonly?, :read_only?)
436
+ end
437
+
438
+ test "reparse_point? basic functionality" do
439
+ assert_respond_to(@stat, :reparse_point?)
440
+ assert_boolean(@stat.reparse_point?)
441
+ end
442
+
443
+ test "reparse_point returns expected value" do
444
+ assert_false(@stat.reparse_point?)
445
+ end
446
+
447
+ test "rdev basic functionality" do
448
+ assert_respond_to(@stat, :rdev)
449
+ assert_nothing_raised{ @stat.rdev }
450
+ assert_kind_of(Numeric, @stat.rdev)
451
+ end
452
+
453
+ test "rdev returns a sane value" do
454
+ assert_true(File::Stat.new("C:\\Program Files").rdev > 1000)
455
+ end
456
+
457
+ test "rdev returns nil on special files" do
458
+ assert_equal(nil, File::Stat.new("NUL").rdev)
459
+ end
460
+
461
+ # Not sure how to test properly in a generic way, but works on my local network
462
+ test "rdev works on unc path" do
463
+ omit_unless(Etc.getlogin == "djberge" && File.exist?("//scipio/users"))
464
+ assert_true(File::Stat.new("//scipio/users").rdev > 1000)
465
+ end
466
+
467
+ test "rdev_major defined and always returns nil" do
468
+ omit_if(@@jruby) # https://github.com/jnr/jnr-posix/issues/23
469
+ assert_respond_to(@stat, :rdev_major)
470
+ assert_nil(@stat.rdev_major)
471
+ end
472
+
473
+ test "rdev_minor defined and always returns nil" do
474
+ omit_if(@@jruby) # https://github.com/jnr/jnr-posix/issues/23
475
+ assert_respond_to(@stat, :rdev_minor)
476
+ assert_nil(@stat.rdev_minor)
477
+ end
478
+
479
+ test "setgid is set to false" do
480
+ assert_respond_to(@stat, :setgid?)
481
+ assert_false(@stat.setgid?)
482
+ end
483
+
484
+ test "setuid is set to false" do
485
+ assert_respond_to(@stat, :setuid?)
486
+ assert_false(@stat.setuid?)
487
+ end
488
+
489
+ test "size custom method basic functionality" do
490
+ assert_respond_to(@stat, :size)
491
+ assert_kind_of(Numeric, @stat.size)
492
+ end
493
+
494
+ test "size custom method returns expected value" do
495
+ assert_equal(21, @stat.size)
496
+ @stat = File::Stat.new(@temp)
497
+ assert_equal(0, @stat.size)
498
+ end
499
+
500
+ test "size custom method works on system files" do
501
+ assert_nothing_raised{ File::Stat.new(@@sys_file).size }
502
+ end
503
+
504
+ test "size? method basic functionality" do
505
+ assert_respond_to(@stat, :size?)
506
+ assert_kind_of(Numeric, @stat.size)
507
+ end
508
+
509
+ test "size? method returns integer if size greater than zero" do
510
+ assert_equal(21, @stat.size?)
511
+ end
512
+
513
+ test "size? method returns nil if size is zero" do
514
+ @stat = File::Stat.new(@temp)
515
+ assert_nil(@stat.size?)
516
+ end
517
+
518
+ test "sparse? basic fucntionality" do
519
+ assert_respond_to(@stat, :sparse?)
520
+ assert_boolean(@stat.sparse?)
521
+ end
522
+
523
+ test "sparse? returns expected value" do
524
+ assert_false(@stat.sparse?)
525
+ end
526
+
527
+ test "sticky is always set to false" do
528
+ assert_respond_to(@stat, :sticky?)
529
+ assert_false(@stat.sticky?)
530
+ end
531
+
532
+ test "streams basic functionality" do
533
+ assert_respond_to(@stat, :streams)
534
+ assert_kind_of(Array, @stat.streams)
535
+ end
536
+
537
+ test "streams returns expected value" do
538
+ assert_equal("::$DATA", @stat.streams[0])
539
+ end
540
+
541
+ test "symlink? basic functionality" do
542
+ assert_respond_to(@stat, :symlink?)
543
+ assert_boolean(@stat.symlink?)
544
+ end
545
+
546
+ test "symlink? returns expected value" do
547
+ assert_false(@stat.symlink?)
548
+ end
549
+
550
+ test "system? basic functionality" do
551
+ assert_respond_to(@stat, :system?)
552
+ assert_boolean(@stat.system?)
553
+ end
554
+
555
+ test "system? returns expected value" do
556
+ assert_false(@stat.system?)
557
+ end
558
+
559
+ test "temporary? basic functionality" do
560
+ assert_respond_to(@stat, :temporary?)
561
+ assert_boolean(@stat.temporary?)
562
+ end
563
+
564
+ test "temporary? returns expected value" do
565
+ assert_false(@stat.temporary?)
566
+ end
567
+
568
+ test "uid basic functionality" do
569
+ assert_respond_to(@stat, :uid)
570
+ assert_nothing_raised{ @stat.uid }
571
+ assert_kind_of(Fixnum, @stat.uid)
572
+ end
573
+
574
+ test "uid returns a sane result" do
575
+ assert_true(@stat.uid >= 0 && @stat.uid <= 10000)
576
+ end
577
+
578
+ test "uid returns a string argument if true argument provided" do
579
+ assert_nothing_raised{ @stat.uid(true) }
580
+ assert_match("S-1-", @stat.uid(true))
581
+ end
582
+
583
+ test "world_readable? basic functionality" do
584
+ assert_respond_to(@stat, :world_readable?)
585
+ assert_boolean(@stat.world_readable?)
586
+ end
587
+
588
+ # TODO: Find or create a file that returns true.
589
+ test "world_readable? returns expected result" do
590
+ assert_false(@stat.world_readable?)
591
+ assert_false(File::Stat.new("C:/").world_readable?)
592
+ end
593
+
594
+ test "world_writable? basic functionality" do
595
+ assert_respond_to(@stat, :world_writable?)
596
+ assert_boolean(@stat.world_writable?)
597
+ end
598
+
599
+ # TODO: Find or create a file that returns true.
600
+ test "world_writable? returns expected result" do
601
+ assert_false(@stat.world_writable?)
602
+ assert_false(File::Stat.new("C:/").world_writable?)
603
+ end
604
+
605
+ test "writable? basic functionality" do
606
+ assert_respond_to(@stat, :writable?)
607
+ assert_boolean(@stat.writable?)
608
+ end
609
+
610
+ test "writable? returns expected value" do
611
+ assert_true(@stat.writable?)
612
+ assert_true(File::Stat.new(Dir.pwd).writable?)
613
+ assert_false(File::Stat.new(@@sys_file).writable?)
614
+ end
615
+
616
+ test "writable? returns expected result for system directory" do
617
+ dir = "C:/Program Files"
618
+ if @@elevated
619
+ assert_true(File::Stat.new(dir).writable?)
620
+ else
621
+ assert_false(File::Stat.new(dir).writable?)
622
+ end
623
+ end
624
+
625
+ test "a file marked as readonly is not considered writable" do
626
+ File.chmod(0644, @@txt_file)
627
+ assert_true(File::Stat.new(@@txt_file).writable?)
628
+ File.chmod(0444, @@txt_file)
629
+ assert_false(File::Stat.new(@@txt_file).writable?)
630
+ end
631
+
632
+ test "writable_real? basic functionality" do
633
+ assert_respond_to(@stat, :writable_real?)
634
+ assert_boolean(@stat.writable_real?)
635
+ end
636
+
637
+ test "writable_real? returns expected value" do
638
+ assert_true(@stat.writable_real?)
639
+ end
640
+
641
+ test "zero? method basic functionality" do
642
+ assert_respond_to(@stat, :zero?)
643
+ assert_boolean(@stat.zero?)
644
+ end
645
+
646
+ test "zero? method returns expected value" do
647
+ assert_false(@stat.zero?)
648
+ @stat = File::Stat.new(@temp)
649
+ assert_true(@stat.zero?)
650
+ end
651
+
652
+ test "ffi functions are private" do
653
+ assert_not_respond_to(@stat, :CloseHandle)
654
+ assert_not_respond_to(File::Stat, :CloseHandle)
655
+ end
656
+
657
+ def teardown
658
+ SetFileAttributes(@@txt_file, @attr) # Set file back to normal
659
+ File.delete(@temp) if File.exist?(@temp)
660
+ @dir = nil
661
+ @stat = nil
662
+ @attr = nil
663
+ @temp = nil
664
+ end
665
+
666
+ def self.shutdown
667
+ File.delete(@@txt_file) if File.exist?(@@txt_file)
668
+ File.delete(@@exe_file) if File.exist?(@@exe_file)
669
+
670
+ @@block_dev = nil
671
+ @@txt_file = nil
672
+ @@exe_file = nil
673
+ @@sys_file = nil
674
+ @@elevated = nil
675
+ @@jruby = nil
676
+ end
677
+ end