vncrec 1.0.1

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,40 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe 'vncrec' do
4
+ describe 'command line options' do
5
+ end
6
+
7
+ describe 'sending signals', vnc: true, port: true do
8
+ let(:ps) { `ps -A`.lines.grep(@pid.to_s) }
9
+
10
+ before do
11
+ @pid = fork do
12
+ exec 'vncrec'
13
+ end
14
+ sleep 1
15
+ end
16
+
17
+ context 'when connected' do
18
+ before { launch_vnc_server 5900 }
19
+
20
+ it 'INT stops' do
21
+ expect_no_timeout(3) do
22
+ Process.kill('INT', @pid)
23
+ Process.waitpid(@pid)
24
+ end
25
+ expect($?.exited?).to be_truthy
26
+ end
27
+ end
28
+
29
+ context 'when disconnected' do
30
+
31
+ it 'INT stops' do
32
+ expect_no_timeout(3) do
33
+ Process.kill('INT', @pid)
34
+ Process.waitpid(@pid)
35
+ end
36
+ expect($?.exited?).to be_truthy
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,338 @@
1
+ require 'spec_helper.rb'
2
+ Rec = VNCRec::Recorder
3
+
4
+ describe Rec do
5
+ describe '#intialize' do
6
+ let(:width) { r.instance_variable_get(:@w) }
7
+ let(:height) { r.instance_variable_get(:@h) }
8
+
9
+ describe 'option geometry:' do
10
+ context 'passed with wrong format' do
11
+ it 'leads to fail' do
12
+ expect { Rec.new(geometry: '1920') }.to raise_error ArgumentError
13
+ expect { Rec.new(geometry: 1920) }.to raise_error ArgumentError
14
+ end
15
+ end
16
+
17
+ context 'passed and correct' do
18
+ let(:r) { Rec.new(geometry: '800x600') }
19
+
20
+ it 'raises no error' do
21
+ expect { r }.not_to raise_error
22
+ end
23
+
24
+ it 'IVs are set' do
25
+ r
26
+ r.send(:parse_geometry)
27
+ expect(width).to eq(800)
28
+ expect(height).to eq(600)
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'encoding' do
34
+ let(:iv) { rec.instance_variable_get(:@enc) }
35
+
36
+ context 'given raw' do
37
+ let(:rec) { Rec.new(encoding: VNCRec::ENC_RAW) }
38
+
39
+ it ' sets iv to constant ENC_RAW' do
40
+ rec
41
+ expect(iv).to eq VNCRec::ENC_RAW
42
+ end
43
+ end
44
+
45
+ context 'given zrle' do
46
+ let(:rec) { Rec.new(encoding: VNCRec::ENC_ZRLE) }
47
+
48
+ it ' sets iv to constant ENC_ZRLE' do
49
+ rec
50
+ expect(iv).to eq VNCRec::ENC_ZRLE
51
+ end
52
+ end
53
+
54
+ context 'given hextile' do
55
+ let(:rec) { Rec.new(encoding: VNCRec::ENC_HEXTILE) }
56
+
57
+ it ' sets iv to constant ENC_HEXTILE' do
58
+ rec
59
+ expect(iv).to eq VNCRec::ENC_HEXTILE
60
+ end
61
+ end
62
+ end
63
+
64
+ describe 'option host:', port: true do
65
+ context 'when given' do
66
+ before do
67
+ @sock = nil
68
+ Thread.new do
69
+ @sock = TCPServer.new(5900).accept
70
+ end.run
71
+ end
72
+
73
+ let(:rec) { Rec.new host: 'localhost', port: 5900 }
74
+
75
+ it 'rec tries to connect' do
76
+ expect do
77
+ rec.run
78
+ sleep 1
79
+ end.to change { @sock.nil? }
80
+ .from(true).to(false)
81
+ end
82
+ end
83
+ context 'when not given' do
84
+ let(:rec) { Rec.new }
85
+
86
+ it 'rec listens on specified port' do
87
+ rec.run
88
+ sleep 1
89
+ expect(pid_using_port 5900).to eq Process.pid
90
+ end
91
+ end
92
+ end
93
+
94
+ describe 'option pixel format:' do
95
+ subject { rec.instance_variable_get(:@pix_fmt) }
96
+ context 'passing bgra' do
97
+ let(:rec) { Rec.new(filename: 'file.mp4', pix_fmt: 'bgra') }
98
+
99
+ it 'does not raise error' do
100
+ expect { rec }.not_to raise_error
101
+ end
102
+ it { is_expected.to eq VNCRec::PIX_FMT_BGRA }
103
+ end
104
+
105
+ context 'passing bgr8' do
106
+ let(:rec) { Rec.new(filename: 'file.mp4', pix_fmt: 'bgr8') }
107
+
108
+ it 'does not raise error' do
109
+ expect { rec }.not_to raise_error
110
+ end
111
+ it { is_expected.to eq VNCRec::PIX_FMT_BGR8 }
112
+ end
113
+
114
+ context 'passing other' do
115
+ let(:rec) { Rec.new(filename: 'file.mp4', pix_fmt: 'gbr32') }
116
+
117
+ it 'raises error' do
118
+ expect { rec }.to raise_error ArgumentError
119
+ end
120
+ end
121
+ end
122
+
123
+ describe 'option port:', port: true do
124
+ let(:rec) { Rec.new(port: port) }
125
+ context 'valid integer given' do
126
+ let(:port) { 5900 }
127
+
128
+ it 'does not raise error' do
129
+ expect { rec }.not_to raise_error
130
+ end
131
+ end
132
+ context 'integer too big' do
133
+ let(:port) { 10_000_000 }
134
+ it 'raises ArgumentError' do
135
+ expect { rec }.to raise_error ArgumentError
136
+ end
137
+ end
138
+ context 'integer too small' do
139
+ let(:port) { 0 }
140
+ it 'raises ArgumentError' do
141
+ expect { rec }.to raise_error ArgumentError
142
+ end
143
+ end
144
+ context 'non-integer' do
145
+ let(:port) { 'aa' }
146
+ it 'raises ArgumentError' do
147
+ expect { rec }.to raise_error ArgumentError
148
+ end
149
+ end
150
+ end
151
+
152
+ describe 'option filename:' do
153
+ let(:f1) { Rec.new }
154
+ let(:f2) { Rec.new(filename: 'file') }
155
+ let(:f3) { Rec.new(filename: '/root/file') }
156
+
157
+ context 'when no filename given' do
158
+ it 'file with default name exists' do
159
+ expect { f1 }.to change { File.exist?('5900.raw') }.from(false).to(true)
160
+ end
161
+ end
162
+ context 'when given' do
163
+ it 'file with specified name exists' do
164
+ expect { f2 }.to change { File.exist?('file') }.from(false).to(true)
165
+ end
166
+ end
167
+ context 'when can create file' do
168
+ it 'file exists' do
169
+ expect { f2 }.to change { File.exist?('file') }.from(false).to(true)
170
+ end
171
+ end
172
+ context 'when can not create file' do
173
+ it 'raises error' do
174
+ expect { f3 }.to raise_error(/Cannot create file .*/)
175
+ end
176
+ end
177
+ end
178
+ end
179
+ describe '#running?', port: true, vnc: true do
180
+ let(:rec) { Rec.new(filename: '/dev/null') }
181
+ context 'when not yet connected' do
182
+ it 'returns false' do
183
+ expect(rec.running?).to be_falsy
184
+ end
185
+ end
186
+
187
+ context 'when connected' do
188
+ before { rec.run }
189
+ it 'returns true' do
190
+ launch_vnc_server 5900
191
+ expect(rec.running?).to be_truthy
192
+ end
193
+ end
194
+ end
195
+ describe 'network issues', port: true, vnc: true do
196
+ subject(:rec) { Rec.new(filename: 'file.raw', port: get_free_port) }
197
+ subject(:port) { rec.instance_variable_get(:@port) }
198
+ context 'when server is not responding' do
199
+ it 'exits eventually, correct close' do
200
+ end
201
+ end
202
+ context 'when connection is lost' do
203
+ after(:each) do
204
+ `killall x11vnc &>/dev/null`
205
+ end
206
+ it 'closes correctly', port: true do
207
+ rec.run
208
+ launch_vnc_server port
209
+ sleep 4
210
+ `killall x11vnc &>/dev/null`
211
+ sleep 1
212
+ expect(rec.stopped?).to be_truthy
213
+ end
214
+ end
215
+ end
216
+ describe 'basic functionality' do
217
+ describe 'Pix format', port: true, vnc: true do
218
+ before(:each) { `rm -f rec.jpg &>/dev/null` }
219
+ let(:port) { get_free_port }
220
+ let(:geo) { '800x600' }
221
+ let(:common_options) do
222
+ {
223
+ geometry: geo,
224
+ filename: 'file.raw',
225
+ encoding: VNCRec::ENC_RAW,
226
+ port: port
227
+ }
228
+ end
229
+ let(:rec) do
230
+ Rec.new(common_options.merge(pix_fmt: f))
231
+ end
232
+ let(:run) do
233
+ rec.run
234
+ sleep 5
235
+ rec.stop
236
+ end
237
+
238
+ context 'given bgr8' do
239
+ let(:f) { 'bgr8' }
240
+
241
+ it 'produces output' do
242
+ rec
243
+ launch_vnc_server port
244
+ run
245
+ sleep 1
246
+ `ffmpeg -y -f rawvideo -s #{geo} -r 3 -pix_fmt #{f} -i file.raw \
247
+ -frames 1 rec.jpg &>/dev/null`
248
+ sleep 4
249
+ expect(File.exist?('rec.jpg')).to be_truthy
250
+ end
251
+
252
+ context 'given bgra' do
253
+ let(:f) { 'bgra' }
254
+
255
+ it 'produces output' do
256
+ rec
257
+ launch_vnc_server port
258
+ run
259
+ sleep 1
260
+ `ffmpeg -y -f rawvideo -s #{geo} -r 3 -pix_fmt #{f} -i file.raw \
261
+ -frames 1 rec.jpg &>/dev/null`
262
+ sleep 4
263
+ expect(File.exist?('rec.jpg')).to be_truthy
264
+ end
265
+ end
266
+ end
267
+ end
268
+
269
+ describe 'Transmission mode', port: true, vnc: true do
270
+ context '(with resolution 800x600)' do
271
+ let(:geo) { '800x600' }
272
+ describe 'raw' do
273
+ let(:mode) { VNCRec::ENC_RAW }
274
+ let(:port) { get_free_port }
275
+ let(:rec) do
276
+ Rec.new(
277
+ geometry: geo,
278
+ filename: 'file.raw',
279
+ encoding: mode,
280
+ port: port
281
+ )
282
+ end
283
+
284
+ it 'works' do
285
+ rec.run
286
+ launch_vnc_server port
287
+ sleep 3
288
+ rec.stop
289
+ sleep 1
290
+ expect(File.exist?('file.raw')).to be_truthy
291
+ expect(File.size('file.raw')).to be > 0
292
+ end
293
+ end
294
+
295
+ describe 'hextile', skip: 1 do
296
+ let(:mode) { VNCRec::ENC_HEXTILE }
297
+ let(:port) { get_free_port }
298
+ let(:rec) do
299
+ Rec.new(
300
+ geometry: geo,
301
+ filename: 'file.raw',
302
+ encoding: mode,
303
+ port: port
304
+ )
305
+ end
306
+
307
+ it 'works' do
308
+ rec.run
309
+ launch_vnc_server port
310
+ sleep 3
311
+ rec.stop
312
+ sleep 1
313
+ expect(File.exist?('file.raw')).to be_truthy
314
+ expect(File.size('file.raw')).to be > 0
315
+ end
316
+ end
317
+ end
318
+ end
319
+ describe '#kill', port: true, vnc: true do
320
+ subject(:rec) { Rec.new(filename: 'file.mp4', port: get_free_port) }
321
+ subject(:port) { rec.instance_variable_get(:@port) }
322
+ before(:each) do
323
+ rec.run
324
+ launch_vnc_server port
325
+ sleep 6
326
+ end
327
+ it 'recorder is dead' do
328
+ ffmpeg = rec.instance_variable_get(:@file)
329
+ expect(rec.stopped?).to be_falsy
330
+ expect(ffmpeg.closed?).to be_falsy
331
+ rec.stop
332
+ sleep 0.5
333
+ expect(rec.stopped?).to be_truthy
334
+ expect(ffmpeg.closed?).to be_truthy
335
+ end
336
+ end
337
+ end
338
+ end
@@ -0,0 +1,171 @@
1
+ require 'timeout'
2
+ require 'socket'
3
+ require 'vncrec'
4
+ require 'active_support/core_ext/numeric/time'
5
+
6
+ # This file was generated by the `rspec --init` command. Conventionally, all
7
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
8
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
9
+ # file to always be loaded, without a need to explicitly require it in any files.
10
+ #
11
+ # Given that it is always loaded, you are encouraged to keep this file as
12
+ # light-weight as possible. Requiring heavyweight dependencies from this file
13
+ # will add to the boot time of your test suite on EVERY test run, even for an
14
+ # individual file that may not need all of that loaded. Instead, make a
15
+ # separate helper file that requires this one and then use it only in the specs
16
+ # that actually need it.
17
+ #
18
+ # The `.rspec` file also contains a few flags that are not defaults but that
19
+ # users commonly want.
20
+ #
21
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
22
+ RSpec.configure do |config|
23
+ config.exclusion_filter = :interactive, :skip
24
+ config.filter_run :focus
25
+ config.run_all_when_everything_filtered = true
26
+
27
+ config.before(:all) do
28
+ `rake gem`
29
+ end
30
+
31
+ config.before(:each, port: true) do
32
+ free_port 5900
33
+ end
34
+
35
+ config.after(:each, port: true) do
36
+ free_port 5900
37
+ end
38
+
39
+ config.after(:each) do
40
+ kill_children
41
+ `rake clean &>/dev/null`
42
+ `rm -rf file* &>/dev/null`
43
+ end
44
+
45
+ config.after(:each, vnc: true) do
46
+ stop_vnc_server
47
+ end
48
+
49
+ config.after(:all) do
50
+ `rake clean`
51
+ `kill -ABRT \`pgrep x11vnc\` &>/dev/null`
52
+ begin
53
+ `kill -INT \`pgrep ruby\` \`pgrep vncrec\` &>/dev/null`
54
+ kill_children
55
+ rescue SignalException
56
+ end
57
+ `xset r &>/dev/null`
58
+ end
59
+ end
60
+
61
+ def get_free_port
62
+ gen = proc do
63
+ (rand * 1000).to_i + 5001
64
+ end
65
+ port = gen.call
66
+ port = gen.call while port_in_use? port
67
+ port
68
+ end
69
+
70
+ def port_avail?(port)
71
+ lines = File.readlines('/proc/net/tcp')
72
+ rx = /\d+: \d+:(?<port>\w+)/
73
+ !lines.map do |line|
74
+ m = line.match(rx)
75
+ m[:port].to_i(16) if m
76
+ end.include?(port)
77
+ end
78
+
79
+ def port_in_use?(port)
80
+ !port_avail?(port)
81
+ end
82
+
83
+ def pid_using_port(port)
84
+ lines = `lsof -i:#{port}`.lines
85
+ return nil if lines.empty?
86
+ col = lines[0].split(' ').index('PID')
87
+ lines.drop(1)[0].split(' ')[col].to_i
88
+ end
89
+
90
+ def free_port(port)
91
+ p = pid_using_port(port)
92
+ if Process.pid == p
93
+ close_socket_descriptors
94
+ return
95
+ end
96
+ `kill -INT #{p} &>/dev/null; sleep 1`
97
+ return unless port_in_use?(port)
98
+ `kill -QUIT #{p} &>/dev/null; sleep 1`
99
+ return unless port_in_use?(port)
100
+ `kill -KILL #{p} &>/dev/null; sleep 1`
101
+ end
102
+
103
+ def kill_children(sig = 'INT')
104
+ chids = `ps --ppid #{Process.pid} 2>/dev/null`
105
+ .lines.drop(1).map { |l| l.split(/\W+/)[0] }
106
+ chids.each do |v|
107
+ `kill -#{sig} #{v} 2>/dev/null`
108
+ end
109
+ end
110
+
111
+ def close_socket_descriptors
112
+ ObjectSpace.each_object(TCPSocket) do |o|
113
+ begin
114
+ o.close unless o.closed?
115
+ rescue
116
+ end
117
+ end
118
+ end
119
+
120
+ def read_process_timeout(cmd = '', tout = 0.2)
121
+ `rm -f log`
122
+
123
+ pid = fork do
124
+ exec "#{cmd} &>log"
125
+ end
126
+
127
+ sleep 2
128
+ `kill -INT #{pid}`
129
+ sleep 0.3
130
+
131
+ begin
132
+ Timeout.timeout(0.5) do
133
+ Process.waitpid(pid)
134
+ end
135
+ rescue Timeout::Error
136
+ `kill -TERM #{pid}`
137
+ end
138
+
139
+ f = File.open('log', 'r')
140
+ res = f.read_nonblock(1024)
141
+ f.close
142
+ `rm -f log`
143
+ res
144
+ end
145
+
146
+ def expect_timeout(time, &block)
147
+ expect do
148
+ Timeout.timeout(time) do
149
+ block.call
150
+ end
151
+ end.to raise_error Timeout::Error
152
+ end
153
+
154
+ def expect_no_timeout(time, &block)
155
+ expect do
156
+ Timeout.timeout(time) do
157
+ block.call
158
+ end
159
+ end.not_to raise_error
160
+ end
161
+
162
+ def launch_vnc_server(port, host = '0.0.0.0', debug = false)
163
+ fork do
164
+ `x11vnc --connect #{host}:#{port} #{debug ? nil : '&>/dev/null'}`
165
+ end
166
+ end
167
+
168
+ def stop_vnc_server
169
+ `killall x11vnc &>/dev/null`
170
+ 'killall -ABRT x11vnc &>/dev/null'
171
+ end