exec_sandbox 0.1.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,85 @@
1
+ # namespace
2
+ module ExecSandbox
3
+
4
+ # Interface to the wait4 system call using the ffi library.
5
+ module Wait4
6
+ # Waits for a process to end, and collects its exit status and resource usage.
7
+ #
8
+ # @param [Fixnum] pid the PID of the process to wait for; should be a child of
9
+ # this process
10
+ # @return [Hash] exit code and resource usage information
11
+ def self.wait4(pid)
12
+ status_ptr = FFI::MemoryPointer.new :int
13
+ rusage = ExecSandbox::Wait4::Rusage.new
14
+ returned_pid = LibC.wait4(pid, status_ptr, 0, rusage.pointer)
15
+ raise SystemCallError, FFI.errno if returned_pid < 0
16
+ status = { :bits => status_ptr.read_int }
17
+ status_ptr.free
18
+
19
+ signal_code = status[:bits] & 0x7f
20
+ status[:exit_code] = (signal_code != 0) ? -signal_code : status[:bits] >> 8
21
+ status[:user_time] = rusage[:ru_utime_sec] +
22
+ rusage[:ru_utime_usec] * 0.000_001
23
+ status[:system_time] = rusage[:ru_utime_sec] +
24
+ rusage[:ru_utime_usec] * 0.000_001
25
+ status[:rss] = rusage[:ru_maxrss] / 1024.0
26
+ return status
27
+ end
28
+
29
+ # Maps wait4 in libc.
30
+ module LibC
31
+ extend FFI::Library
32
+ ffi_lib FFI::Library::LIBC
33
+ attach_function :wait4, [:int, :pointer, :int, :pointer], :int,
34
+ :blocking => true
35
+ end # module ExecSandbox::Wait4::Libc
36
+
37
+ # Maps struct rusage in sys/resource.h, used by wait4.
38
+ class Rusage < FFI::Struct
39
+ # Total amount of user time used.
40
+ layout :ru_utime_sec, :time_t,
41
+ :ru_utime_usec, :suseconds_t,
42
+ # Total amount of system time used.
43
+ :ru_stime_sec, :time_t,
44
+ :ru_stime_usec, :suseconds_t,
45
+ # Maximum resident set size (in kilobytes).
46
+ :ru_maxrss, :long,
47
+ # Amount of sharing of text segment memory with other processes
48
+ # (kilobyte-seconds).
49
+ :ru_ixrss, :long,
50
+ # Amount of data segment memory used (kilobyte-seconds).
51
+ :ru_idrss, :long,
52
+ # Amount of stack memory used (kilobyte-seconds).
53
+ :ru_isrss, :long,
54
+ # Number of soft page faults (i.e. those serviced by reclaiming a page from
55
+ # the list of pages awaiting reallocation.
56
+ :ru_minflt, :long,
57
+ # Number of hard page faults (i.e. those that required I/O).
58
+ :ru_majflt, :long,
59
+ # Number of times a process was swapped out of physical memory.
60
+ :ru_nswap, :long,
61
+ # Number of input operations via the file system. Note: This and
62
+ # `ru_oublock' do not include operations with the cache.
63
+ :ru_inblock, :long,
64
+ # Number of output operations via the file system.
65
+ :ru_oublock, :long,
66
+ # Number of IPC messages sent.
67
+ :ru_msgsnd, :long,
68
+ # Number of IPC messages received.
69
+ :ru_msgrcv, :long,
70
+ # Number of signals delivered.
71
+ :ru_nsignals, :long,
72
+ # Number of voluntary context switches, i.e. because the process gave up the
73
+ # process before it had to (usually to wait for some resource to be
74
+ # available).
75
+ :ru_nvcsw, :long,
76
+ # Number of involuntary context switches, i.e. a higher priority process
77
+ # became runnable or the current process used up its time slice.
78
+ :ru_nivcsw, :long,
79
+ # Padding, so we don't crash if the struct gets ammended on newer OSes.
80
+ :padding, :uchar, 256
81
+ end # struct ExecSandbox::Wait4::Rusage
82
+
83
+ end # module ExecSandbox::Wait4
84
+
85
+ end # namespace ExecSandbox
@@ -0,0 +1,23 @@
1
+ # @title ExecSandbox - Run foreign binaries using POSIX sandboxing features
2
+ # @author Victor Costan
3
+
4
+ # Standard library
5
+ require 'English'
6
+ require 'etc'
7
+ require 'fcntl'
8
+ require 'fileutils'
9
+ require 'tempfile'
10
+ require 'tmpdir'
11
+
12
+ # Gems
13
+ require 'ffi'
14
+
15
+ # TODO(pwnall): documentation
16
+ module ExecSandbox
17
+ end # namespace ExecSandbox
18
+
19
+ # Code
20
+ require 'exec_sandbox/sandbox.rb'
21
+ require 'exec_sandbox/spawn.rb'
22
+ require 'exec_sandbox/users.rb'
23
+ require 'exec_sandbox/wait4.rb'
@@ -0,0 +1,138 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ExecSandbox::Sandbox do
4
+ describe 'IO redirection' do
5
+ before do
6
+ @temp_in = Tempfile.new 'exec_sandbox_rspec'
7
+ @temp_in.write "I/O test\n"
8
+ @temp_in.close
9
+ @temp_out = Tempfile.new 'exec_sandbox_rspec'
10
+ @temp_out.close
11
+
12
+ ExecSandbox.use do |s|
13
+ @result = s.run bin_fixture(:duplicate), :in => @temp_in.path,
14
+ :out => @temp_out.path
15
+ end
16
+ end
17
+ after do
18
+ @temp_in.unlink
19
+ @temp_out.unlink
20
+ end
21
+
22
+ it 'should not crash' do
23
+ @result[:exit_code].should == 0
24
+ end
25
+
26
+ it 'should produce the correct result' do
27
+ File.read(@temp_out.path).should == "I/O test\nI/O test\n"
28
+ end
29
+ end
30
+
31
+ describe 'pipe redirection' do
32
+ before do
33
+ ExecSandbox.use do |s|
34
+ @result = s.run bin_fixture(:duplicate), :in_data => "Pipe test\n"
35
+ end
36
+ end
37
+
38
+ it 'should not crash' do
39
+ @result[:exit_code].should == 0
40
+ end
41
+
42
+ it 'should produce the correct result' do
43
+ @result[:out_data].should == "Pipe test\nPipe test\n"
44
+ end
45
+ end
46
+
47
+
48
+ describe 'resource limitations' do
49
+ describe 'churn.rb' do
50
+ before do
51
+ @temp_out = Tempfile.new 'exec_sandbox_rspec'
52
+ @temp_out.close
53
+ end
54
+ after do
55
+ @temp_out.unlink
56
+ end
57
+
58
+ describe 'without limitations' do
59
+ before do
60
+ ExecSandbox.use do |s|
61
+ @result = s.run [bin_fixture(:churn), 'stdout', 3.to_s]
62
+ s.pull 'stdout', @temp_out.path
63
+ end
64
+ end
65
+
66
+ it 'should not crash' do
67
+ @result[:exit_code].should == 0
68
+ end
69
+
70
+ it 'should run for at least 2 seconds' do
71
+ (@result[:user_time] + @result[:system_time]).should > 2
72
+ end
73
+
74
+ it 'should output something' do
75
+ File.stat(@temp_out.path).size.should > 0
76
+ end
77
+ end
78
+
79
+ describe 'with CPU time limitation' do
80
+ before do
81
+ ExecSandbox.use do |s|
82
+ @result = s.run [bin_fixture(:churn), 'stdout', 3.to_s],
83
+ :limits => {:cpu => 1}
84
+ s.pull 'stdout', @temp_out.path
85
+ end
86
+ end
87
+
88
+ it 'should run for at least 0.5 seconds' do
89
+ (@result[:user_time] + @result[:system_time]).should >= 0.5
90
+ end
91
+
92
+ it 'should run for less than 2 seconds' do
93
+ (@result[:user_time] + @result[:system_time]).should < 2
94
+ end
95
+
96
+ it 'should not have a chance to output' do
97
+ File.stat(@temp_out.path).size.should == 0
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ describe '#push' do
104
+ let(:test_user) { Etc.getlogin }
105
+ let(:test_uid) { Etc.getpwnam(test_user).uid }
106
+ let(:test_gid) { Etc.getpwnam(test_user).gid }
107
+ let(:test_group) { Etc.getgrgid(test_gid).name }
108
+
109
+ before do
110
+ @sandbox = ExecSandbox.open test_user
111
+ end
112
+ after do
113
+ @sandbox.close
114
+ end
115
+
116
+ describe 'a file' do
117
+ before do
118
+ @to = @sandbox.push __FILE__
119
+ end
120
+
121
+ it 'should copy straight to the sandbox directory' do
122
+ File.dirname(@to).should == @sandbox.path
123
+ end
124
+
125
+ it 'should use the same file name' do
126
+ File.basename(@to).should == 'sandbox_spec.rb'
127
+ end
128
+
129
+ it "should set the file's owner to the admin" do
130
+ File.stat(@to).uid.should == test_uid
131
+ end
132
+
133
+ it "should not set the file's group to the admin" do
134
+ File.stat(@to).gid.should_not == test_gid
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,327 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ExecSandbox::Spawn do
4
+ let(:test_user) { Etc.getlogin }
5
+ let(:test_uid) { Etc.getpwnam(test_user).uid }
6
+ let(:test_gid) { Etc.getpwnam(test_user).gid }
7
+ let(:test_group) { Etc.getgrgid(test_gid).name }
8
+
9
+ describe '#spawn IO redirection' do
10
+ before do
11
+ @temp_in = Tempfile.new 'exec_sandbox_rspec'
12
+ @temp_in.write "Spawn IO test\n"
13
+ @temp_in.close
14
+ @temp_out = Tempfile.new 'exec_sandbox_rspec'
15
+ @temp_out.close
16
+ end
17
+ after do
18
+ @temp_in.unlink
19
+ @temp_out.unlink
20
+ end
21
+
22
+ shared_examples_for 'duplicate.rb' do
23
+ it 'should not crash' do
24
+ @status[:exit_code].should == 0
25
+ end
26
+
27
+ it 'should write successfully' do
28
+ @temp_out.open
29
+ begin
30
+ @temp_out.read.should == "Spawn IO test\nSpawn IO test\n"
31
+ ensure
32
+ @temp_out.close
33
+ end
34
+ end
35
+ end
36
+
37
+ describe 'with paths' do
38
+ before do
39
+ pid = ExecSandbox::Spawn.spawn bin_fixture(:duplicate),
40
+ {:in => @temp_in.path, :out => @temp_out.path}
41
+ @status = ExecSandbox::Wait4.wait4 pid
42
+ end
43
+
44
+ it_behaves_like 'duplicate.rb'
45
+ end
46
+
47
+ describe 'with file descriptors' do
48
+ before do
49
+ File.open(@temp_in.path, 'r') do |in_io|
50
+ File.open(@temp_out.path, 'w') do |out_io|
51
+ pid = ExecSandbox::Spawn.spawn bin_fixture(:duplicate),
52
+ {:in => in_io, :out => out_io, :err => STDERR}
53
+ @status = ExecSandbox::Wait4.wait4 pid
54
+ end
55
+ end
56
+ end
57
+
58
+ it_behaves_like 'duplicate.rb'
59
+ end
60
+
61
+ describe 'without stdout' do
62
+ before do
63
+ pid = ExecSandbox::Spawn.spawn bin_fixture(:duplicate),
64
+ {:in => @temp_in.path}
65
+ @status = ExecSandbox::Wait4.wait4 pid
66
+ end
67
+
68
+ it 'should crash' do
69
+ @status[:exit_code].should_not == 0
70
+ end
71
+ end
72
+ end
73
+
74
+ describe '#spawn principal' do
75
+ before do
76
+ @temp = Tempfile.new 'exec_sandbox_rspec'
77
+ @temp_path = @temp.path
78
+ @temp.close
79
+ end
80
+ after do
81
+ File.unlink(@temp_path) if File.exist?(@temp_path)
82
+ end
83
+
84
+ describe 'with root credentials' do
85
+ before do
86
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:write_arg),
87
+ @temp_path, "Spawn uid test\n"], {:err => STDERR},
88
+ {:uid => 0, :gid => 0}
89
+ @status = ExecSandbox::Wait4.wait4 pid
90
+ @fstat = File.stat(@temp_path)
91
+ end
92
+
93
+ it 'should not crash' do
94
+ @status[:exit_code].should == 0
95
+ end
96
+
97
+ it 'should have the UID set to root' do
98
+ @fstat.uid.should == 0
99
+ end
100
+ it 'should have the GID set to root' do
101
+ @fstat.gid.should == 0
102
+ end
103
+
104
+ it 'should have the correct output' do
105
+ File.read(@temp_path).should == "Spawn uid test\n"
106
+ end
107
+ end
108
+
109
+ describe 'with non-root credentials' do
110
+ before do
111
+ @temp.unlink
112
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:write_arg),
113
+ @temp_path, "Spawn uid test\n"], {:err => STDERR},
114
+ {:uid => test_uid, :gid => test_gid}
115
+ @status = ExecSandbox::Wait4.wait4 pid
116
+ end
117
+
118
+ it 'should not crash' do
119
+ @status[:exit_code].should == 0
120
+ end
121
+
122
+ it 'should have the UID set to the test user' do
123
+ File.stat(@temp_path).uid.should == test_uid
124
+ end
125
+ it 'should have the GID set to the test group' do
126
+ File.stat(@temp_path).gid.should == test_gid
127
+ end
128
+
129
+ it 'should have the correct output' do
130
+ File.read(@temp_path).should == "Spawn uid test\n"
131
+ end
132
+ end
133
+
134
+ describe 'with non-root credentials and a root-owned redirect file' do
135
+ before do
136
+ File.chmod 0700, @temp_path
137
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:write_arg),
138
+ @temp_path, "Spawn uid test\n"], {},
139
+ {:uid => test_uid, :gid => test_gid}
140
+ @status = ExecSandbox::Wait4.wait4 pid
141
+ end
142
+
143
+ it 'should crash (euid is set correctly)' do
144
+ @status[:exit_code].should_not == 0
145
+ end
146
+
147
+ it 'should not have the correct output' do
148
+ File.read(@temp_path).should_not == "Spawn uid test\n"
149
+ end
150
+ end
151
+
152
+ describe 'with non-root credentials and a root-owned redirect file' do
153
+ before do
154
+ File.chmod 070, @temp_path
155
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:write_arg), @temp_path,
156
+ "Spawn uid test\n"], {},
157
+ {:uid => test_uid, :gid => test_gid}
158
+ @status = ExecSandbox::Wait4.wait4 pid
159
+ end
160
+
161
+ it 'should crash (egid is set correctly)' do
162
+ @status[:exit_code].should_not == 0
163
+ end
164
+
165
+ it 'should not have the correct output' do
166
+ File.read(@temp_path).should_not == "Spawn uid test\n"
167
+ end
168
+ end
169
+
170
+ describe 'with a working directory' do
171
+ before do
172
+ @temp_dir = Dir.mktmpdir 'exec_sandbox_rspec'
173
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:pwd), @temp_path],
174
+ {}, {:dir => @temp_dir}
175
+ @status = ExecSandbox::Wait4.wait4 pid
176
+ end
177
+ after do
178
+ Dir.rmdir @temp_dir
179
+ end
180
+
181
+ it 'should not crash' do
182
+ @status[:exit_code].should == 0
183
+ end
184
+
185
+ it 'should set the working directory' do
186
+ File.read(@temp_path).should == @temp_dir
187
+ end
188
+ end
189
+ end
190
+
191
+ describe '#spawn resource limits' do
192
+ before do
193
+ @temp = Tempfile.new 'exec_sandbox_rspec'
194
+ @temp_path = @temp.path
195
+ @temp.close
196
+ end
197
+ after do
198
+ File.unlink(@temp_path) if File.exist?(@temp_path)
199
+ end
200
+
201
+ describe 'buffer.rb' do
202
+ describe 'without limitations' do
203
+ before do
204
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:buffer), @temp_path,
205
+ (100 * 1024 * 1024).to_s], {:err => STDERR}, {}, {}
206
+ @status = ExecSandbox::Wait4.wait4 pid
207
+ end
208
+
209
+ it 'should not crash' do
210
+ @status[:exit_code].should == 0
211
+ end
212
+
213
+ it 'should output 100 megs' do
214
+ File.stat(@temp_path).size.should == 100 * 1024 * 1024
215
+ end
216
+ end
217
+
218
+ describe 'with memory limitation' do
219
+ before do
220
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:buffer), @temp_path,
221
+ (100 * 1024 * 1024).to_s], {}, {}, {:data => 64 * 1024 * 1024}
222
+ @status = ExecSandbox::Wait4.wait4 pid
223
+ end
224
+
225
+ it 'should crash' do
226
+ @status[:exit_code].should_not == 0
227
+ end
228
+
229
+ it 'should not have a chance to output data' do
230
+ File.stat(@temp_path).size.should == 0
231
+ end
232
+ end
233
+
234
+ describe 'with output limitation' do
235
+ before do
236
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:buffer), @temp_path,
237
+ (100 * 1024 * 1024).to_s], {}, {}, {:file_size => 8 * 1024 * 1024}
238
+ @status = ExecSandbox::Wait4.wait4 pid
239
+ end
240
+
241
+ it 'should crash' do
242
+ @status[:exit_code].should_not == 0
243
+ end
244
+
245
+ it 'should not output more than 16 megs' do
246
+ File.stat(@temp_path).size.should <= 16 * 1024 * 1024
247
+ end
248
+ end
249
+ end
250
+
251
+ describe 'fork.rb' do
252
+ describe 'without limitations' do
253
+ before do
254
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:fork), @temp_path,
255
+ 10.to_s], {:err => STDERR}, {}, {}
256
+ @status = ExecSandbox::Wait4.wait4 pid
257
+ end
258
+
259
+ it 'should not crash' do
260
+ @status[:exit_code].should == 0
261
+ end
262
+
263
+ it 'should output 10 +es' do
264
+ File.stat(@temp_path).size.should == 10
265
+ end
266
+ end
267
+
268
+ describe 'with sub-process limitation' do
269
+ before do
270
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:fork), @temp_path,
271
+ 10.to_s], {}, {}, {:processes => 4}
272
+ @status = ExecSandbox::Wait4.wait4 pid
273
+ end
274
+
275
+ it 'should crash' do
276
+ @status[:exit_code].should_not == 0
277
+ end
278
+
279
+ it 'should output less than 5 +es' do
280
+ File.stat(@temp_path).size.should < 5
281
+ end
282
+ end
283
+ end
284
+
285
+ describe 'churn.rb' do
286
+ describe 'without limitations' do
287
+ before do
288
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:churn), @temp_path,
289
+ 3.to_s], {:err => STDERR}, {}, {}
290
+ @status = ExecSandbox::Wait4.wait4 pid
291
+ end
292
+
293
+ it 'should not crash' do
294
+ @status[:exit_code].should == 0
295
+ end
296
+
297
+ it 'should run for at least 2 seconds' do
298
+ (@status[:user_time] + @status[:system_time]).should > 2
299
+ end
300
+
301
+ it 'should output something' do
302
+ File.stat(@temp_path).size.should > 0
303
+ end
304
+ end
305
+
306
+ describe 'with CPU time limitation' do
307
+ before do
308
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:churn), @temp_path,
309
+ 10.to_s], {}, {}, {:cpu => 1}
310
+ @status = ExecSandbox::Wait4.wait4 pid
311
+ end
312
+
313
+ it 'should run for at least 0.5 seconds' do
314
+ (@status[:user_time] + @status[:system_time]).should >= 0.5
315
+ end
316
+
317
+ it 'should run for less than 2 seconds' do
318
+ (@status[:user_time] + @status[:system_time]).should < 2
319
+ end
320
+
321
+ it 'should not have a chance to output' do
322
+ File.stat(@temp_path).size.should == 0
323
+ end
324
+ end
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,125 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ExecSandbox::Users do
4
+ let(:test_user) { 'exec_sandbox_rspec' }
5
+ let(:test_group) { Etc.getgrgid(Etc.getpwnam(Etc.getlogin).gid).name }
6
+
7
+ describe '#temp' do
8
+ before { @user_name = ExecSandbox::Users.temp 'exsbx.rspec' }
9
+ after { ExecSandbox::Users.destroy @user_name }
10
+
11
+ it 'should create a user whose name starts with the prefix' do
12
+ @user_name.should match(/^exsbx\.rspec/)
13
+ end
14
+
15
+ it 'should create a user' do
16
+ Etc.getpwnam(@user_name).should_not be_nil
17
+ end
18
+
19
+ it 'should create a group with the same name' do
20
+ Etc.getgrgid(Etc.getpwnam(@user_name).gid).name.should == @user_name
21
+ end
22
+
23
+ it 'should create a home directory for the user' do
24
+ File.exist?(Etc.getpwnam(@user_name).dir).should be_true
25
+ end
26
+ end
27
+
28
+ describe '#create' do
29
+ shared_examples_for 'user creation' do
30
+ it 'should return the UID of a user with the right name' do
31
+ Etc.getpwuid(@uid).name.should == test_user
32
+ end
33
+
34
+ it "should create the new user's home directory" do
35
+ File.exist?(Etc.getpwuid(@uid).dir).should be_true
36
+ end
37
+
38
+ it "should have the new user's name in its home directory" do
39
+ Etc.getpwuid(@uid).dir.should match(test_user)
40
+ end
41
+ end
42
+
43
+ describe 'with no group' do
44
+ before do
45
+ @uid = ExecSandbox::Users.create test_user
46
+ end
47
+
48
+ after do
49
+ ExecSandbox::Users.destroy test_user
50
+ end
51
+
52
+ it_should_behave_like 'user creation'
53
+
54
+ it "should create a group with the user's name" do
55
+ Etc.getgrnam(test_user).should_not be_nil
56
+ end
57
+
58
+ it "should set the new user's GID to the group" do
59
+ Etc.getpwuid(@uid).gid.should == Etc.getgrnam(test_user).gid
60
+ end
61
+ end
62
+
63
+ describe 'with given group' do
64
+ before do
65
+ @uid = ExecSandbox::Users.create test_user, test_group
66
+ end
67
+
68
+ after do
69
+ ExecSandbox::Users.destroy test_user
70
+ end
71
+
72
+ it_should_behave_like 'user creation'
73
+
74
+ it "should not create a group with the user's name" do
75
+ lambda {
76
+ Etc.getgrnam(test_user)
77
+ }.should raise_error(ArgumentError)
78
+ end
79
+
80
+ it "should set the new user's GID to the correct group" do
81
+ Etc.getpwuid(@uid).gid.should == Etc.getgrnam(test_group).gid
82
+ end
83
+ end
84
+ end
85
+
86
+ describe '#destroy' do
87
+ describe 'with single-use group' do
88
+ before do
89
+ ExecSandbox::Users.create test_user
90
+ @home_dir = Etc.getpwnam(test_user)
91
+ ExecSandbox::Users.destroy test_user
92
+ end
93
+
94
+ it 'should remove the user' do
95
+ lambda {
96
+ Etc.getpwnam(test_user)
97
+ }.should raise_error(ArgumentError)
98
+ end
99
+
100
+ it "should remove the user's group" do
101
+ lambda {
102
+ Etc.getgrnam(test_user)
103
+ }.should raise_error(ArgumentError)
104
+ end
105
+ end
106
+
107
+ describe 'delete_user with shared group' do
108
+ before do
109
+ ExecSandbox::Users.create test_user, test_group
110
+ @home_dir = Etc.getpwnam(test_user)
111
+ ExecSandbox::Users.destroy test_user
112
+ end
113
+
114
+ it 'should remove the user' do
115
+ lambda {
116
+ Etc.getpwnam(test_user)
117
+ }.should raise_error(ArgumentError)
118
+ end
119
+
120
+ it "should not remove the generic group" do
121
+ Etc.getgrnam(test_group).should_not be_nil
122
+ end
123
+ end
124
+ end
125
+ end