rfuse 1.0.5 → 1.1.0.RC0
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.
- checksums.yaml +7 -0
- data/.yardopts +2 -0
- data/CHANGES.md +12 -0
- data/README.md +7 -9
- data/ext/rfuse/context.c +13 -2
- data/ext/rfuse/file_info.c +4 -1
- data/ext/rfuse/filler.c +1 -1
- data/ext/rfuse/rfuse.c +24 -6
- data/lib/rfuse/version.rb +1 -1
- data/lib/rfuse.rb +362 -41
- data/rfuse.gemspec +1 -1
- data/sample/test-ruby.rb +7 -36
- data/spec/basic_spec.rb +4 -6
- data/spec/fuse_file_info_spec.rb +6 -6
- data/spec/main_spec.rb +161 -0
- data/spec/options_spec.rb +22 -22
- data/spec/ruby_loop_spec.rb +15 -17
- data/spec/run_spec.rb +60 -0
- data/spec/signals_spec.rb +108 -0
- data/spec/spec_helper.rb +56 -10
- data/spec/xattr_spec.rb +1 -1
- metadata +29 -49
- data/.travis.disabled.yml +0 -8
data/spec/basic_spec.rb
CHANGED
@@ -12,7 +12,6 @@ describe RFuse::Fuse do
|
|
12
12
|
|
13
13
|
context "links" do
|
14
14
|
it "should create and resolve symbolic links"
|
15
|
-
|
16
15
|
it "should create and resolve hard links"
|
17
16
|
|
18
17
|
end
|
@@ -31,9 +30,9 @@ describe RFuse::Fuse do
|
|
31
30
|
|
32
31
|
it "should list directories" do
|
33
32
|
|
34
|
-
mockfs.should_receive(:readdir) do | ctx, path, filler,offset,ffi |
|
33
|
+
mockfs.should_receive(:readdir) do | ctx, path, filler,offset,ffi |
|
35
34
|
filler.push("hello",nil,0)
|
36
|
-
|
35
|
+
filler.push("world",nil,0)
|
37
36
|
end
|
38
37
|
|
39
38
|
with_fuse(mountpoint,mockfs) do
|
@@ -93,7 +92,7 @@ describe RFuse::Fuse do
|
|
93
92
|
it "should set file access and modification times subsecond resolution" do
|
94
93
|
atime,mtime = usec_times(60,600)
|
95
94
|
|
96
|
-
atime_ns = (atime.to_i * (10**9)) + (atime.nsec)
|
95
|
+
atime_ns = (atime.to_i * (10**9)) + (atime.nsec)
|
97
96
|
mtime_ns = (mtime.to_i * (10**9)) + (mtime.nsec)
|
98
97
|
|
99
98
|
mockfs.stub(:getattr).with(anything(),"/usec").and_return(file_stat)
|
@@ -169,7 +168,6 @@ describe RFuse::Fuse do
|
|
169
168
|
val = f.gets
|
170
169
|
val.should == "\000\000"
|
171
170
|
val.size.should == 2
|
172
|
-
puts val
|
173
171
|
end
|
174
172
|
end
|
175
173
|
end
|
@@ -192,7 +190,7 @@ describe RFuse::Fuse do
|
|
192
190
|
it "should report filesystem statistics" do
|
193
191
|
mockfs.stub(:getattr).with(anything(),"/test").and_return(dir_stat)
|
194
192
|
mockfs.stub(:getattr).with(anything(),"/test/statfs").and_return(file_stat)
|
195
|
-
|
193
|
+
|
196
194
|
mockfs.should_receive(:statfs).with(anything(),"/test/statfs").and_return(statvfs)
|
197
195
|
|
198
196
|
#also exercise StatVfs
|
data/spec/fuse_file_info_spec.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe RFuse::Fuse do
|
4
|
-
|
4
|
+
|
5
5
|
let(:dir_stat) { RFuse::Stat.directory(0444) }
|
6
6
|
let(:file_stat) { RFuse::Stat.file(0444) }
|
7
7
|
let!(:mockfs) { m = mock("fuse"); m.stub(:getattr).and_return(nil); m }
|
8
8
|
let(:mountpoint) { tempmount() }
|
9
9
|
let(:open_files) { Hash.new() }
|
10
|
-
|
10
|
+
|
11
11
|
it "should pass fileinfo to #release" do
|
12
12
|
|
13
13
|
file_handle = Object.new()
|
@@ -18,15 +18,15 @@ describe RFuse::Fuse do
|
|
18
18
|
|
19
19
|
mockfs.should_receive(:open).with(anything(),"/ffirelease",anything()) { |ctx,path,ffi|
|
20
20
|
stored_ffi = ffi
|
21
|
-
ffi.fh = file_handle
|
21
|
+
ffi.fh = file_handle
|
22
22
|
}
|
23
|
-
|
23
|
+
|
24
24
|
mockfs.should_receive(:release).with(anything(),"/ffirelease",anything()) { |ctx,path,ffi|
|
25
25
|
# the return value of release is ignore, so exceptions here are lost
|
26
26
|
begin
|
27
27
|
ffi.fh.should == file_handle
|
28
28
|
ffi.should == stored_ffi
|
29
|
-
rescue
|
29
|
+
rescue => ex
|
30
30
|
captured_ex = ex
|
31
31
|
end
|
32
32
|
}
|
@@ -64,7 +64,7 @@ describe RFuse::Fuse do
|
|
64
64
|
f2 = File.new("#{mountpoint}/two")
|
65
65
|
|
66
66
|
val = f1.gets
|
67
|
-
val.should == "hello world"
|
67
|
+
val.should == "hello world"
|
68
68
|
f2.gets.should == "hello world"
|
69
69
|
|
70
70
|
f1.close()
|
data/spec/main_spec.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class MockFuse < RFuse::Fuse; end
|
4
|
+
class DuckFuse
|
5
|
+
def initialize(val); end;
|
6
|
+
end
|
7
|
+
|
8
|
+
describe RFuse do
|
9
|
+
|
10
|
+
# required to capture stderr output of help text from C: library functions
|
11
|
+
matcher :print_to_stderr do |expected|
|
12
|
+
supports_block_expectations
|
13
|
+
|
14
|
+
match do |actual|
|
15
|
+
#$stdout.puts "STDERR=#{$stderr} #{$stderr.fileno}"
|
16
|
+
@captured = File.open(Dir.tmpdir + "/rfuse.stderr","w+") do |f|
|
17
|
+
f.sync
|
18
|
+
old_stderr = STDERR.dup
|
19
|
+
old_f = f.dup
|
20
|
+
begin
|
21
|
+
STDERR.reopen(f)
|
22
|
+
actual.call
|
23
|
+
ensure
|
24
|
+
STDERR.reopen(old_stderr)
|
25
|
+
end
|
26
|
+
old_f.rewind
|
27
|
+
old_f.read
|
28
|
+
end
|
29
|
+
expected.match(@captured)
|
30
|
+
end
|
31
|
+
|
32
|
+
match_when_negated do |actual|
|
33
|
+
!matches?(actual)
|
34
|
+
end
|
35
|
+
|
36
|
+
failure_message do
|
37
|
+
"Expected stderr output...\n#{@captured}\nto match RE - #{expected}"
|
38
|
+
end
|
39
|
+
|
40
|
+
failure_message_when_negated do
|
41
|
+
"Expected stderr output...\n#{@captured}\nto not match RE - #{expected}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:mockfs) { m = double("fuse"); m.stub(:getattr).and_return(nil); m }
|
46
|
+
let(:mountpoint) { tempmount() }
|
47
|
+
|
48
|
+
describe ".main" do
|
49
|
+
|
50
|
+
let(:re_usage) { Regexp.new("^Usage:\n.*-h.*-d.*\n",Regexp::MULTILINE) }
|
51
|
+
let(:re_help) { Regexp.new("^Fuse options: \\(\\d.\\d\\)\n.*help.*\n.*debug.*\n\n",Regexp::MULTILINE) }
|
52
|
+
let(:re_fuse) { Regexp.new(".*(^\\s+-o.*$)+.*",Regexp::MULTILINE) }
|
53
|
+
let(:re_extra_header) { Regexp.new("Filesystem options:\n",Regexp::MULTILINE) }
|
54
|
+
|
55
|
+
# self.main(argv=ARGV,extra_options=[],extra_option_usage="",device=nil,exec=File.basename($0))
|
56
|
+
context "with no mountpoint" do
|
57
|
+
it "prints usage information to stdout" do
|
58
|
+
expect { RFuse.main([]) { } }.to output(re_usage).to_stdout
|
59
|
+
end
|
60
|
+
it "prints mountpoint failure on stderr" do
|
61
|
+
expect { RFuse.main([]) { } }.to output("rfuse: failed, no mountpoint provided\n").to_stderr
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "with help option" do
|
66
|
+
it "does not start filesystem" do
|
67
|
+
expect(mockfs).to receive(:init).exactly(0).times
|
68
|
+
RFuse.main([mountpoint,"-h"]) { mockfs }
|
69
|
+
end
|
70
|
+
|
71
|
+
it "prints usage and kernel options" do
|
72
|
+
# TODO: In Fuse 3.0, this will get complicated because help output moves to stdout
|
73
|
+
re = Regexp.new(re_help.to_s + re_fuse.to_s)
|
74
|
+
expect { RFuse.main([mountpoint,"-h"]) { mockfs } }.to print_to_stderr(re)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "does not print filesystem options header if no extra options" do
|
78
|
+
re = Regexp.new(re_help.to_s + re_fuse.to_s + re_extra_header.to_s)
|
79
|
+
expect { RFuse.main([mountpoint,"-h"]) { mockfs } }.not_to print_to_stderr(re)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "prints local option header if there are local options" do
|
83
|
+
re = Regexp.new(re_help.to_s + re_fuse.to_s + re_extra_header.to_s + "TestOptionUsage")
|
84
|
+
expect { RFuse.main([mountpoint,"-h"],[:myoption],"TestOptionUsage") { mockfs } }.to print_to_stderr(re)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
it "yields parsed options and cleaned argv" do
|
90
|
+
# We know that main uses parse_options which is tested elsewhere
|
91
|
+
expect { |b| RFuse.main([mountpoint,"-o","myoption"],[:myoption],&b) }.to \
|
92
|
+
yield_with_args(a_hash_including(:mountpoint,:myoption),[mountpoint])
|
93
|
+
end
|
94
|
+
|
95
|
+
context "yield raises exception" do
|
96
|
+
it "prints usage output" do
|
97
|
+
expect { RFuse.main([mountpoint]) { raise RFuse::Error, "MyError"}}.to output(re_usage).to_stdout
|
98
|
+
end
|
99
|
+
|
100
|
+
it "prints the error message" do
|
101
|
+
expect { RFuse.main([mountpoint]) { raise RFuse::Error, "MyError"}}.to output("rfuse failed: MyError\n").to_stderr
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it "creates and runs the yielded Fuse filesystem" do
|
106
|
+
# We test create and run separately
|
107
|
+
# expect RFuse.create then mounted? and run
|
108
|
+
fuse = double(fuse)
|
109
|
+
fuse.stub(:mounted?).and_return(true,false)
|
110
|
+
expect(fuse).to receive(:run)
|
111
|
+
expect(RFuse).to receive(:create).with(fuse,[mountpoint],{:mountpoint => mountpoint},[]) { fuse }
|
112
|
+
RFuse.main([mountpoint]) { fuse }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe ".create" do
|
117
|
+
let(:argv) { [mountpoint] }
|
118
|
+
context "with a Fuse object" do
|
119
|
+
let(:fs) { MockFuse.new(mountpoint) }
|
120
|
+
it "returns the object" do
|
121
|
+
fuse = RFuse.create(fs)
|
122
|
+
expect(fuse).to be(fs)
|
123
|
+
expect(fuse).to be_mounted
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "with a subclass of Fuse" do
|
128
|
+
let(:fs) { MockFuse }
|
129
|
+
|
130
|
+
it "creates a new Fuse object from subclass of Fuse" do
|
131
|
+
fuse = RFuse.create(fs,[mountpoint])
|
132
|
+
expect(fuse).to be_kind_of(MockFuse)
|
133
|
+
expect(fuse.mountpoint).to eq(mountpoint)
|
134
|
+
expect(fuse).to be_mounted
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "with a Class" do
|
139
|
+
let(:fs) { DuckFuse }
|
140
|
+
it "creates a FuseDelegator around a new instance" do
|
141
|
+
expect(fs).to receive(:new).with("OptionValue").and_call_original()
|
142
|
+
expect(RFuse::FuseDelegator).to receive(:new).with(an_instance_of(DuckFuse),mountpoint).and_call_original()
|
143
|
+
fuse = RFuse.create(fs,[mountpoint],{:myopt => "OptionValue"},[:myopt])
|
144
|
+
expect(fuse).to be_kind_of(RFuse::FuseDelegator)
|
145
|
+
expect(fuse.mountpoint).to eq(mountpoint)
|
146
|
+
expect(fuse).to be_mounted
|
147
|
+
end
|
148
|
+
end
|
149
|
+
context "with a non-Fuse Object" do
|
150
|
+
let(:fs) { mockfs }
|
151
|
+
it "starts FuseDelegator with non Fuse object returned from yield" do
|
152
|
+
expect(RFuse::FuseDelegator).to receive(:new).with(mockfs,mountpoint).and_call_original()
|
153
|
+
fuse = RFuse.create(fs,[mountpoint])
|
154
|
+
expect(fuse).to be_kind_of(RFuse::FuseDelegator)
|
155
|
+
expect(fuse.mountpoint).to eq(mountpoint)
|
156
|
+
expect(fuse).to be_mounted
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
data/spec/options_spec.rb
CHANGED
@@ -12,25 +12,25 @@ describe RFuse do
|
|
12
12
|
context "mount options" do
|
13
13
|
it "should handle -h" do
|
14
14
|
fuse = RFuse::FuseDelegator.new(mockfs,mountpoint,"-h")
|
15
|
-
fuse.mounted?.should
|
15
|
+
fuse.mounted?.should be_falsey
|
16
16
|
lambda { fuse.loop }.should raise_error(RFuse::Error)
|
17
17
|
end
|
18
18
|
|
19
19
|
it "should behave sensibly for bad mountpoint" do
|
20
20
|
fuse = RFuse::FuseDelegator.new(mockfs,"bad/mount/point")
|
21
|
-
fuse.mounted?.should
|
21
|
+
fuse.mounted?.should be_falsey
|
22
22
|
lambda { fuse.loop }.should raise_error(RFuse::Error)
|
23
23
|
end
|
24
24
|
|
25
25
|
it "should behave sensibly for bad options" do
|
26
|
-
fuse = RFuse::FuseDelegator.new(mockfs,mountpoint,"-eviloption")
|
27
|
-
fuse.mounted?.should
|
26
|
+
fuse = RFuse::FuseDelegator.new(mockfs,mountpoint,"-eviloption")
|
27
|
+
fuse.mounted?.should be_falsey
|
28
28
|
lambda { fuse.loop }.should raise_error(RFuse::Error)
|
29
29
|
end
|
30
30
|
|
31
31
|
it "should handle a Pathname as a mountpoint" do
|
32
32
|
fuse = RFuse::FuseDelegator.new(mockfs,Pathname.new(mountpoint))
|
33
|
-
fuse.mounted?.should
|
33
|
+
fuse.mounted?.should be(true)
|
34
34
|
fuse.unmount()
|
35
35
|
end
|
36
36
|
end
|
@@ -40,46 +40,46 @@ describe RFuse do
|
|
40
40
|
it "should detect -h" do
|
41
41
|
argv = [ "/mountpoint","-h" ]
|
42
42
|
result = RFuse.parse_options(argv)
|
43
|
-
|
44
|
-
result[:help].should
|
43
|
+
|
44
|
+
result[:help].should be(true)
|
45
45
|
result[:mountpoint].should == "/mountpoint"
|
46
|
-
result[:debug].should
|
46
|
+
result[:debug].should be_falsey
|
47
47
|
end
|
48
48
|
|
49
49
|
it "should detect -h mixed with -o" do
|
50
50
|
argv = [ "/mountpoint","-h", "-o", "debug" ]
|
51
51
|
result = RFuse.parse_options(argv)
|
52
|
-
|
53
|
-
result[:help].should
|
52
|
+
|
53
|
+
result[:help].should be(true)
|
54
54
|
result[:mountpoint].should == "/mountpoint"
|
55
|
-
result[:debug].should
|
55
|
+
result[:debug].should be(true)
|
56
56
|
end
|
57
57
|
|
58
58
|
it "should detect debug" do
|
59
59
|
argv = [ "/mountpoint","-o","debug" ]
|
60
60
|
result = RFuse.parse_options(argv)
|
61
61
|
|
62
|
-
result[:debug].should
|
63
|
-
result[:help].should
|
62
|
+
result[:debug].should be(true)
|
63
|
+
result[:help].should be_falsey
|
64
64
|
|
65
65
|
argv = [ "/mountpoint","-o","default_permissions,debug" ]
|
66
66
|
result = RFuse.parse_options(argv)
|
67
|
-
result[:debug].should
|
67
|
+
result[:debug].should be(true)
|
68
68
|
end
|
69
69
|
|
70
70
|
it "detects debug as -d" do
|
71
71
|
argv = [ "/mountpoint","-o","someopt","-d" ]
|
72
72
|
result = RFuse.parse_options(argv)
|
73
|
-
result[:debug].should
|
73
|
+
result[:debug].should be(true)
|
74
74
|
end
|
75
75
|
|
76
76
|
it "should remove local options" do
|
77
77
|
argv = [ "/mountpoint","-o","debug,myoption" ]
|
78
|
-
|
78
|
+
|
79
79
|
result = RFuse.parse_options(argv,:myoption)
|
80
80
|
|
81
|
-
result[:debug].should
|
82
|
-
result[:myoption].should
|
81
|
+
result[:debug].should be(true)
|
82
|
+
result[:myoption].should be(true)
|
83
83
|
|
84
84
|
argv.should == [ "/mountpoint", "-o", "debug" ]
|
85
85
|
end
|
@@ -89,7 +89,7 @@ describe RFuse do
|
|
89
89
|
|
90
90
|
result = RFuse.parse_options(argv,:myoption)
|
91
91
|
|
92
|
-
result[:myoption].should
|
92
|
+
result[:myoption].should be(true)
|
93
93
|
argv.should == [ "/mountpoint" ]
|
94
94
|
end
|
95
95
|
|
@@ -124,9 +124,9 @@ describe RFuse do
|
|
124
124
|
|
125
125
|
result[:device].should == "a device"
|
126
126
|
result[:mountpoint].should == "/mountpoint"
|
127
|
-
result[:rw].should
|
128
|
-
result[:debug].should
|
129
|
-
result[:default_permissions].should
|
127
|
+
result[:rw].should be(true)
|
128
|
+
result[:debug].should be(true)
|
129
|
+
result[:default_permissions].should be(true)
|
130
130
|
|
131
131
|
argv.should == [ "/mountpoint" , "-o", "rw,debug,default_permissions" ]
|
132
132
|
end
|
data/spec/ruby_loop_spec.rb
CHANGED
@@ -1,22 +1,22 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe RFuse do
|
3
|
+
describe RFuse::Fuse do
|
4
4
|
|
5
5
|
let(:mockfs) { m = mock("fuse"); m.stub(:getattr).and_return(nil) ; m }
|
6
6
|
let(:mountpoint) { tempmount() }
|
7
7
|
|
8
|
-
context "
|
8
|
+
context "#loop" do
|
9
9
|
it "should exit from another thread and allow multiple loops" do
|
10
|
-
|
10
|
+
|
11
11
|
fuse = RFuse::FuseDelegator.new(mockfs,mountpoint)
|
12
|
-
t = Thread.new { sleep 0.
|
12
|
+
t = Thread.new { sleep 0.2; fuse.exit }
|
13
13
|
fuse.loop()
|
14
14
|
t.join
|
15
|
-
t = Thread.new { sleep 0.
|
15
|
+
t = Thread.new { sleep 0.2; fuse.exit }
|
16
16
|
fuse.loop
|
17
17
|
t.join
|
18
18
|
fuse.unmount
|
19
|
-
fuse.mounted?.should
|
19
|
+
fuse.mounted?.should be(false)
|
20
20
|
end
|
21
21
|
|
22
22
|
|
@@ -25,38 +25,36 @@ describe RFuse do
|
|
25
25
|
#
|
26
26
|
# mockfs = mock("fuse")
|
27
27
|
# mockfs.stub(:getattr).and_return(nil)
|
28
|
-
|
28
|
+
#
|
29
29
|
# fuse = RFuse::FuseDelegator.new(mockfs,"/tmp/rfuse-spec")
|
30
|
-
|
30
|
+
#
|
31
31
|
# t = Thread.new { sleep 0.5 ; File.stat("/tmp/rfuse-spec/thread") ; fuse.exit }
|
32
|
-
|
32
|
+
#
|
33
33
|
# fuse.loop()
|
34
34
|
# fuse.unmount()
|
35
35
|
#end
|
36
36
|
|
37
37
|
it "should allow other threads to be scheduled" do
|
38
|
-
|
38
|
+
|
39
39
|
file_stat = RFuse::Stat.file(0444)
|
40
40
|
|
41
41
|
thread_ran = false
|
42
42
|
|
43
43
|
mockfs.stub(:getattr).with(anything(),"/before") {
|
44
|
-
|
45
|
-
thread_ran.should be_false
|
44
|
+
thread_ran.should be(false)
|
46
45
|
file_stat
|
47
46
|
}
|
48
47
|
|
49
48
|
mockfs.stub(:getattr).with(anything(),"/missing") {
|
50
|
-
|
51
|
-
|
52
|
-
thread_ran.should be_true
|
49
|
+
#GC.start()
|
50
|
+
thread_ran.should be(true)
|
53
51
|
file_stat
|
54
52
|
}
|
55
53
|
|
56
|
-
t = Thread.new() { sleep
|
54
|
+
t = Thread.new() { sleep 0.5 ; thread_ran = true }
|
57
55
|
with_fuse(mountpoint,mockfs) do
|
58
56
|
File.stat("#{mountpoint}/before");
|
59
|
-
sleep
|
57
|
+
sleep 1;
|
60
58
|
File.stat("#{mountpoint}/missing");
|
61
59
|
end
|
62
60
|
end
|
data/spec/run_spec.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RFuse::Fuse do
|
4
|
+
context "#run" do
|
5
|
+
|
6
|
+
let(:file_stat) { RFuse::Stat.file(0444) }
|
7
|
+
let(:mountpoint) { tempmount() }
|
8
|
+
let(:mockfs) { m = double("fuse"); allow(m).to receive(:getattr) { |ctx,path| puts "#{ctx},#{path}"}; m }
|
9
|
+
let(:fuse) { RFuse::FuseDelegator.new(mockfs,mountpoint) }
|
10
|
+
|
11
|
+
it "runs a mounted filesystem with default traps" do
|
12
|
+
|
13
|
+
# expect traps to be set
|
14
|
+
expect(mockfs).to receive(:sighup) { }
|
15
|
+
expect(mockfs).to receive(:getattr).with(anything(),"/run").and_return(file_stat)
|
16
|
+
|
17
|
+
# Need to call this before we fork..
|
18
|
+
expect(fuse.mountpoint).to eq(mountpoint)
|
19
|
+
|
20
|
+
pid = Process.pid
|
21
|
+
|
22
|
+
fpid = Kernel.fork do
|
23
|
+
File.stat("#{mountpoint}/run")
|
24
|
+
Process.kill("HUP",pid)
|
25
|
+
sleep(0.1)
|
26
|
+
Process.kill("TERM",pid)
|
27
|
+
end
|
28
|
+
|
29
|
+
fuse.run
|
30
|
+
|
31
|
+
rpid,result = Process.waitpid2(fpid)
|
32
|
+
expect(result).to be_success
|
33
|
+
|
34
|
+
expect(fuse).not_to be_mounted
|
35
|
+
expect(Signal.trap("HUP","DEFAULT")).to eq("DEFAULT")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "unmounts the filesystem and resets traps on exception" do
|
39
|
+
expect(mockfs).to receive(:getattr).with(anything(),"/run").and_return(file_stat)
|
40
|
+
# raising an error in a sighandler will exit the loop..
|
41
|
+
expect(mockfs).to receive(:sighup).and_raise("oh noes")
|
42
|
+
pid = Process.pid
|
43
|
+
# Need to call this before we fork..
|
44
|
+
expect(fuse.mountpoint).to eq(mountpoint)
|
45
|
+
|
46
|
+
fpid = Kernel.fork do
|
47
|
+
File.stat("#{mountpoint}/run")
|
48
|
+
Process.kill("HUP",pid)
|
49
|
+
end
|
50
|
+
|
51
|
+
expect { fuse.run }.to raise_error
|
52
|
+
|
53
|
+
pid,result = Process.waitpid2(fpid)
|
54
|
+
|
55
|
+
expect(fuse).not_to be_mounted
|
56
|
+
expect(Signal.trap("HUP","DEFAULT")).to eq("DEFAULT")
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RFuse::Fuse do
|
4
|
+
|
5
|
+
let(:mountpoint) { tempmount() }
|
6
|
+
let(:fuse) { RFuse::Fuse.new(mountpoint) }
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
# Override traps (note rspec itself traps "INT") with "DEFAULT"
|
10
|
+
@traps = ["INT","TERM","HUP","USR1","USR2"].inject({}) { |h,signame| h[signame] = Signal.trap(signame,"DEFAULT"); h }
|
11
|
+
end
|
12
|
+
|
13
|
+
after(:each) do
|
14
|
+
fuse.unmount()
|
15
|
+
# Restore previously set traps
|
16
|
+
@traps.each_pair { |signame,prev_trap| Signal.trap(signame,prev_trap) }
|
17
|
+
end
|
18
|
+
|
19
|
+
context "#trap_signals" do
|
20
|
+
|
21
|
+
it "returns the list of signals trapped" do
|
22
|
+
|
23
|
+
allow(fuse).to receive(:sighup)
|
24
|
+
|
25
|
+
expect(fuse.trap_signals("HUP")).to include("HUP")
|
26
|
+
expect(Signal.trap("HUP","DEFAULT")).not_to eq("DEFAULT")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "does not override previously set traps" do
|
30
|
+
|
31
|
+
allow(fuse).to receive(:sighup)
|
32
|
+
|
33
|
+
Signal.trap("HUP","HUPCOMMAND")
|
34
|
+
|
35
|
+
expect(fuse.trap_signals()).not_to include("HUP")
|
36
|
+
expect(Signal.trap("HUP","DEFAULT")).to eq("HUPCOMMAND")
|
37
|
+
end
|
38
|
+
|
39
|
+
it "only traps the specified signals" do
|
40
|
+
|
41
|
+
allow(fuse).to receive(:sighup)
|
42
|
+
allow(fuse).to receive(:sigusr2)
|
43
|
+
|
44
|
+
expect(fuse.trap_signals("HUP")).to include("HUP")
|
45
|
+
expect(Signal.trap("HUP","DEFAULT")).not_to eq("DEFAULT")
|
46
|
+
expect(Signal.trap("USR2","DEFAULT")).to eq("DEFAULT")
|
47
|
+
end
|
48
|
+
|
49
|
+
it "allows signals to be handled by the filesystem" do
|
50
|
+
expect(fuse).to receive(:sighup) { fuse.exit }
|
51
|
+
|
52
|
+
expect(fuse.trap_signals("HUP")).to include("HUP")
|
53
|
+
|
54
|
+
pid = Process.pid
|
55
|
+
fork_fuse(fuse) { Process.kill("HUP",pid) }
|
56
|
+
# Exitted loop, but still mounted - ie not with fusermount -u
|
57
|
+
expect(fuse).to be_mounted
|
58
|
+
end
|
59
|
+
|
60
|
+
it "exits on INT by default" do
|
61
|
+
pid = Process.pid
|
62
|
+
expect(fuse.trap_signals("INT")).to include("INT")
|
63
|
+
|
64
|
+
fork_fuse(fuse) { Process.kill("INT",pid) }
|
65
|
+
expect(fuse).to be_mounted
|
66
|
+
end
|
67
|
+
|
68
|
+
it "exits on TERM by default" do
|
69
|
+
pid = Process.pid
|
70
|
+
expect(fuse.trap_signals("TERM")).to include("TERM")
|
71
|
+
|
72
|
+
fork_fuse(fuse) { Process.kill("TERM",pid) }
|
73
|
+
expect(fuse).to be_mounted
|
74
|
+
end
|
75
|
+
|
76
|
+
context "with a delegated filesystem" do
|
77
|
+
let(:mockfs) { m = mock("fuse"); m.stub(:getattr).and_return(nil); m }
|
78
|
+
let(:fuse) { RFuse::FuseDelegator.new(mockfs,mountpoint) }
|
79
|
+
|
80
|
+
it "enables FuseDelegator debugging on USR1" do
|
81
|
+
|
82
|
+
pid = Process.pid
|
83
|
+
expect(fuse.trap_signals("USR1","INT")).to include("USR1")
|
84
|
+
|
85
|
+
expect(fuse.debug?).to be(false)
|
86
|
+
|
87
|
+
fork_fuse(fuse) do
|
88
|
+
Process.kill("USR1",pid)
|
89
|
+
sleep(0.1)
|
90
|
+
Process.kill("INT",pid)
|
91
|
+
end
|
92
|
+
|
93
|
+
expect(fuse.debug?).to be(true)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "allows delegate filesystem to override default sig handler method" do
|
97
|
+
expect(mockfs).to receive(:sigint) { fuse.exit }
|
98
|
+
|
99
|
+
pid = Process.pid
|
100
|
+
fuse.trap_signals("INT")
|
101
|
+
|
102
|
+
fork_fuse(fuse) do
|
103
|
+
Process.kill("INT",pid)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|