yamatanooroti 0.0.4 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -1
- data/lib/yamatanooroti/version.rb +1 -1
- data/lib/yamatanooroti/vterm.rb +43 -3
- data/lib/yamatanooroti/windows.rb +84 -22
- metadata +17 -4
- data/lib/yamatanooroti/vterm.rb.orig +0 -70
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 405b57a78b93d5990c59232f331cfabec9c9c0e6bd6f0421ed4f62d7df0f93ed
|
4
|
+
data.tar.gz: e3cb1a67a2ab62d8afa3c12ff9e76b6630c4ea7bd9adc3f79074f438bcefd2eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3831e1e85b7c03ff786ec24085ba85846ef9b1aa9ce049043cf6d96e3ff39fa7981c67123f91355a7c3f8f7f721a3c827d2d93fc18d43e74875f5a940600952
|
7
|
+
data.tar.gz: e3626f863db8cdb3b4287485b068883e117f4b2946f2f1d55cd6d4eca991b9753ccf6c967d7461936c8cb5311867404ae2431f525c725380d56632196f90371c
|
data/README.md
CHANGED
@@ -75,10 +75,24 @@ Likewise, you can specify Windows command prompt test by `Yamatanooroti::Windows
|
|
75
75
|
|
76
76
|
## Method Reference
|
77
77
|
|
78
|
-
### `start_terminal(height, width, command)`
|
78
|
+
### `start_terminal(height, width, command, startup_message: nil)`
|
79
79
|
|
80
80
|
Starts terminal internally that is sized `height` and `width` with `command` to test the result. The `command` should be an array of strings with a path of command and zero or more options. This should be called in `setup` method.
|
81
81
|
|
82
|
+
If `startup_message` is given, `start_terminal` waits for the string to be printed and then returns.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
code = 'sleep 1; puts "aaa"; sleep 10; puts "bbb"'
|
86
|
+
start_terminal(5, 30, ['ruby', '-e', code], startup_message: 'aaa')
|
87
|
+
close
|
88
|
+
assert_screen(<<~EOC)
|
89
|
+
aaa
|
90
|
+
EOC
|
91
|
+
# The start_terminal method waits for the output of the "aaa" as specified by
|
92
|
+
# the startup_message option, the "bbb" after 10 seconds won't come because
|
93
|
+
# the I/O is closed immediately after it.
|
94
|
+
```
|
95
|
+
|
82
96
|
### `write(str)`
|
83
97
|
|
84
98
|
Writes `str` like inputting by a keyboard to the started terminal.
|
data/lib/yamatanooroti/vterm.rb
CHANGED
@@ -4,7 +4,7 @@ require 'pty'
|
|
4
4
|
require 'io/console'
|
5
5
|
|
6
6
|
module Yamatanooroti::VTermTestCaseModule
|
7
|
-
def start_terminal(height, width, command, wait: 0.1)
|
7
|
+
def start_terminal(height, width, command, wait: 0.1, startup_message: nil)
|
8
8
|
@wait = wait
|
9
9
|
@result = nil
|
10
10
|
|
@@ -16,12 +16,32 @@ module Yamatanooroti::VTermTestCaseModule
|
|
16
16
|
@screen = @vterm.screen
|
17
17
|
@screen.reset(true)
|
18
18
|
|
19
|
+
case startup_message
|
20
|
+
when String
|
21
|
+
@startup_message = ->(message) { message.start_with?(startup_message) }
|
22
|
+
when Regexp
|
23
|
+
@startup_message = ->(message) { startup_message.match?(message) }
|
24
|
+
else
|
25
|
+
@startup_message = nil
|
26
|
+
end
|
27
|
+
|
19
28
|
sync
|
20
29
|
end
|
21
30
|
|
22
31
|
def write(str)
|
23
32
|
sync
|
24
|
-
|
33
|
+
str_to_write = String.new(encoding: Encoding::ASCII_8BIT)
|
34
|
+
str.chars.each do |c|
|
35
|
+
byte = c.force_encoding(Encoding::ASCII_8BIT).ord
|
36
|
+
if c.bytesize == 1 and byte.allbits?(0x80) # with Meta key
|
37
|
+
c = (byte ^ 0x80).chr
|
38
|
+
str_to_write << "\e"
|
39
|
+
str_to_write << c
|
40
|
+
else
|
41
|
+
str_to_write << c
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@pty_input.write(str_to_write)
|
25
45
|
sync
|
26
46
|
end
|
27
47
|
|
@@ -30,20 +50,34 @@ module Yamatanooroti::VTermTestCaseModule
|
|
30
50
|
@pty_input.close
|
31
51
|
sync
|
32
52
|
Process.kill('KILL', @pid)
|
53
|
+
Process.waitpid(@pid)
|
33
54
|
end
|
34
55
|
|
35
56
|
private def sync
|
57
|
+
startup_message = '' if @startup_message
|
36
58
|
loop do
|
37
59
|
sleep @wait
|
38
60
|
chunk = @pty_output.read_nonblock(1024)
|
61
|
+
if @startup_message
|
62
|
+
startup_message << chunk
|
63
|
+
if @startup_message.(startup_message)
|
64
|
+
@startup_message = nil
|
65
|
+
chunk = startup_message
|
66
|
+
else
|
67
|
+
redo
|
68
|
+
end
|
69
|
+
end
|
39
70
|
@vterm.write(chunk)
|
40
71
|
chunk = @vterm.read
|
41
72
|
@pty_input.write(chunk)
|
42
73
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
74
|
+
retry if @startup_message
|
43
75
|
break
|
44
76
|
rescue Errno::EIO # EOF
|
77
|
+
retry if @startup_message
|
45
78
|
break
|
46
79
|
rescue IO::EAGAINWaitReadable # emtpy buffer
|
80
|
+
retry if @startup_message
|
47
81
|
break
|
48
82
|
end
|
49
83
|
end
|
@@ -56,7 +90,13 @@ module Yamatanooroti::VTermTestCaseModule
|
|
56
90
|
@result << ''
|
57
91
|
cols.times do |c|
|
58
92
|
cell = @screen.cell_at(r, c)
|
59
|
-
|
93
|
+
if cell.char.nil? or cell.char.empty?
|
94
|
+
# There will be no char to the left of the rendered area if moves
|
95
|
+
# the cursor.
|
96
|
+
@result.last << ' '
|
97
|
+
else
|
98
|
+
@result.last << cell.char
|
99
|
+
end
|
60
100
|
end
|
61
101
|
@result.last.gsub!(/ *$/, '')
|
62
102
|
end
|
@@ -147,6 +147,7 @@ module Yamatanooroti::WindowsDefinition
|
|
147
147
|
TH32CS_SNAPPROCESS = 0x00000002
|
148
148
|
PROCESS_ALL_ACCESS = 0x001FFFFF
|
149
149
|
SW_HIDE = 0
|
150
|
+
LEFT_ALT_PRESSED = 0x0002
|
150
151
|
|
151
152
|
# HANDLE GetStdHandle(DWORD nStdHandle);
|
152
153
|
extern 'HANDLE GetStdHandle(DWORD);', :stdcall
|
@@ -169,6 +170,10 @@ module Yamatanooroti::WindowsDefinition
|
|
169
170
|
extern 'BOOL SetConsoleWindowInfo(HANDLE, BOOL, PSMALL_RECT);', :stdcall
|
170
171
|
# BOOL WriteConsoleInputW(HANDLE hConsoleInput, const INPUT_RECORD *lpBuffer, DWORD nLength, LPDWORD lpNumberOfEventsWritten);
|
171
172
|
extern 'BOOL WriteConsoleInputW(HANDLE, const INPUT_RECORD*, DWORD, LPDWORD);', :stdcall
|
173
|
+
# SHORT VkKeyScanW(WCHAR ch);
|
174
|
+
extern 'SHORT VkKeyScanW(WCHAR);', :stdcall
|
175
|
+
# UINT MapVirtualKeyW(UINT uCode, UINT uMapType);
|
176
|
+
extern 'UINT MapVirtualKeyW(UINT, UINT);', :stdcall
|
172
177
|
# BOOL ReadConsoleOutputW(HANDLE hConsoleOutput, PCHAR_INFO lpBuffer, COORD dwBufferSize, COORD dwBufferCoord, PSMALL_RECT lpReadRegion);
|
173
178
|
extern 'BOOL ReadConsoleOutputW(HANDLE, PCHAR_INFO, COORD, COORD, PSMALL_RECT);', :stdcall
|
174
179
|
# BOOL WINAPI SetCurrentConsoleFontEx(HANDLE hConsoleOutput, BOOL bMaximumWindow, PCONSOLE_FONT_INFOEX lpConsoleCurrentFontEx);
|
@@ -289,8 +294,37 @@ module Yamatanooroti::WindowsTestCaseModule
|
|
289
294
|
end
|
290
295
|
end
|
291
296
|
|
297
|
+
private def quote_command_arg(arg)
|
298
|
+
if not arg.match?(/[ \t"]/)
|
299
|
+
# No quotation needed.
|
300
|
+
return arg
|
301
|
+
end
|
302
|
+
|
303
|
+
if not arg.match?(/["\\]/)
|
304
|
+
# No embedded double quotes or backlashes, so I can just wrap quote
|
305
|
+
# marks around the whole thing.
|
306
|
+
return %{"#{arg}"}
|
307
|
+
end
|
308
|
+
|
309
|
+
quote_hit = true
|
310
|
+
result = '"'
|
311
|
+
arg.chars.reverse.each do |c|
|
312
|
+
result << c
|
313
|
+
if quote_hit and c == '\\'
|
314
|
+
result << '\\'
|
315
|
+
elsif c == '"'
|
316
|
+
quote_hit = true
|
317
|
+
result << '\\'
|
318
|
+
else
|
319
|
+
quote_hit = false
|
320
|
+
end
|
321
|
+
end
|
322
|
+
result << '"'
|
323
|
+
result.reverse
|
324
|
+
end
|
325
|
+
|
292
326
|
private def launch(command)
|
293
|
-
command = %Q{cmd.exe /q /c "#{command
|
327
|
+
command = %Q{cmd.exe /q /c "#{command}"}
|
294
328
|
converted_command = mb2wc(command)
|
295
329
|
@pi = DL::PROCESS_INFORMATION.malloc
|
296
330
|
(@pi.to_ptr + 0)[0, DL::PROCESS_INFORMATION.size] = "\x00" * DL::PROCESS_INFORMATION.size
|
@@ -333,33 +367,43 @@ module Yamatanooroti::WindowsTestCaseModule
|
|
333
367
|
|
334
368
|
def write(str)
|
335
369
|
sleep @wait
|
336
|
-
str.tr!("\n", "\r")
|
370
|
+
str.force_encoding(Encoding::ASCII_8BIT).tr!("\n", "\r")
|
337
371
|
records = Fiddle::Pointer.malloc(DL::INPUT_RECORD_WITH_KEY_EVENT.size * str.size * 2, DL::FREE)
|
338
372
|
str.chars.each_with_index do |c, i|
|
373
|
+
byte = c.ord
|
374
|
+
if c.bytesize == 1 and byte.allbits?(0x80) # with Meta key
|
375
|
+
c = (byte ^ 0x80).chr
|
376
|
+
control_key_state = DL::LEFT_ALT_PRESSED
|
377
|
+
else
|
378
|
+
control_key_state = 0
|
379
|
+
end
|
339
380
|
record_index = i * 2
|
340
381
|
r = DL::INPUT_RECORD_WITH_KEY_EVENT.new(records + DL::INPUT_RECORD_WITH_KEY_EVENT.size * record_index)
|
341
|
-
r
|
342
|
-
r.bKeyDown = 1
|
343
|
-
r.wRepeatCount = 0
|
344
|
-
r.wVirtualKeyCode = 0
|
345
|
-
r.wVirtualScanCode = 0
|
346
|
-
r.UnicodeChar = c.unpack('U').first
|
347
|
-
r.dwControlKeyState = 0
|
382
|
+
set_input_record(r, c, true, control_key_state)
|
348
383
|
record_index = i * 2 + 1
|
349
384
|
r = DL::INPUT_RECORD_WITH_KEY_EVENT.new(records + DL::INPUT_RECORD_WITH_KEY_EVENT.size * record_index)
|
350
|
-
r
|
351
|
-
r.bKeyDown = 0
|
352
|
-
r.wRepeatCount = 0
|
353
|
-
r.wVirtualKeyCode = 0
|
354
|
-
r.wVirtualScanCode = 0
|
355
|
-
r.UnicodeChar = c.unpack('U').first
|
356
|
-
r.dwControlKeyState = 0
|
385
|
+
set_input_record(r, c, false, control_key_state)
|
357
386
|
end
|
358
387
|
written_size = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DWORD, DL::FREE)
|
359
388
|
r = DL.WriteConsoleInputW(DL.GetStdHandle(DL::STD_INPUT_HANDLE), records, str.size * 2, written_size)
|
360
389
|
error_message(r, 'WriteConsoleInput')
|
361
390
|
end
|
362
391
|
|
392
|
+
private def set_input_record(r, c, key_down, control_key_state)
|
393
|
+
begin
|
394
|
+
code = c.unpack('U').first
|
395
|
+
rescue ArgumentError
|
396
|
+
code = c.bytes.first
|
397
|
+
end
|
398
|
+
r.EventType = DL::KEY_EVENT
|
399
|
+
r.bKeyDown = key_down ? 1 : 0
|
400
|
+
r.wRepeatCount = 1
|
401
|
+
r.wVirtualKeyCode = DL.VkKeyScanW(code)
|
402
|
+
r.wVirtualScanCode = DL.MapVirtualKeyW(code, 0)
|
403
|
+
r.UnicodeChar = code
|
404
|
+
r.dwControlKeyState = control_key_state
|
405
|
+
end
|
406
|
+
|
363
407
|
private def free_resources
|
364
408
|
h_snap = DL.CreateToolhelp32Snapshot(DL::TH32CS_SNAPPROCESS, 0)
|
365
409
|
pe = DL::PROCESSENTRY32W.malloc
|
@@ -402,6 +446,12 @@ module Yamatanooroti::WindowsTestCaseModule
|
|
402
446
|
def close
|
403
447
|
sleep @wait
|
404
448
|
# read first before kill the console process including output
|
449
|
+
@result = retrieve_screen
|
450
|
+
|
451
|
+
free_resources
|
452
|
+
end
|
453
|
+
|
454
|
+
private def retrieve_screen
|
405
455
|
char_info_matrix = Fiddle::Pointer.to_ptr("\x00" * (DL::CHAR_INFO.size * (@height * @width)))
|
406
456
|
region = DL::SMALL_RECT.malloc
|
407
457
|
region.Left = 0
|
@@ -410,7 +460,7 @@ module Yamatanooroti::WindowsTestCaseModule
|
|
410
460
|
region.Bottom = @height
|
411
461
|
r = DL.ReadConsoleOutputW(@output_handle, char_info_matrix, @height * 65536 + @width, 0, region)
|
412
462
|
error_message(r, "ReadConsoleOutputW")
|
413
|
-
|
463
|
+
screen = []
|
414
464
|
prev_c = nil
|
415
465
|
@height.times do |y|
|
416
466
|
line = ''
|
@@ -425,10 +475,9 @@ module Yamatanooroti::WindowsTestCaseModule
|
|
425
475
|
prev_c = mb
|
426
476
|
end
|
427
477
|
end
|
428
|
-
|
478
|
+
screen << line.gsub(/ *$/, '')
|
429
479
|
end
|
430
|
-
|
431
|
-
free_resources
|
480
|
+
screen
|
432
481
|
end
|
433
482
|
|
434
483
|
def result
|
@@ -444,13 +493,26 @@ module Yamatanooroti::WindowsTestCaseModule
|
|
444
493
|
end
|
445
494
|
end
|
446
495
|
|
447
|
-
def start_terminal(height, width, command, wait: 1)
|
496
|
+
def start_terminal(height, width, command, wait: 1, startup_message: nil)
|
448
497
|
@height = height
|
449
498
|
@width = width
|
450
499
|
@wait = wait
|
451
500
|
@result = nil
|
452
501
|
setup_console(height, width)
|
453
|
-
launch(command.join(' '))
|
502
|
+
launch(command.map{ |c| quote_command_arg(c) }.join(' '))
|
503
|
+
case startup_message
|
504
|
+
when String
|
505
|
+
check_startup_message = ->(message) { message.start_with?(startup_message) }
|
506
|
+
when Regexp
|
507
|
+
check_startup_message = ->(message) { startup_message.match?(message) }
|
508
|
+
end
|
509
|
+
if check_startup_message
|
510
|
+
loop do
|
511
|
+
screen = retrieve_screen.join("\n").sub(/\n*\z/, "\n")
|
512
|
+
break if check_startup_message.(screen)
|
513
|
+
sleep @wait
|
514
|
+
end
|
515
|
+
end
|
454
516
|
end
|
455
517
|
end
|
456
518
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yamatanooroti
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- aycabta
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: test-unit
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: reline
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: " Yamatanooroti is a multi-platform real(?) terminal test framework.\n"
|
56
70
|
email:
|
57
71
|
- aycabta@gmail.com
|
@@ -64,7 +78,6 @@ files:
|
|
64
78
|
- lib/yamatanooroti.rb
|
65
79
|
- lib/yamatanooroti/version.rb
|
66
80
|
- lib/yamatanooroti/vterm.rb
|
67
|
-
- lib/yamatanooroti/vterm.rb.orig
|
68
81
|
- lib/yamatanooroti/windows.rb
|
69
82
|
homepage: https://github.com/aycabta/yamatanooroti
|
70
83
|
licenses:
|
@@ -85,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
98
|
- !ruby/object:Gem::Version
|
86
99
|
version: '0'
|
87
100
|
requirements: []
|
88
|
-
rubygems_version: 3.
|
101
|
+
rubygems_version: 3.2.22
|
89
102
|
signing_key:
|
90
103
|
specification_version: 4
|
91
104
|
summary: Multi-platform real(?) terminal test framework
|
@@ -1,70 +0,0 @@
|
|
1
|
-
require 'test-unit'
|
2
|
-
require 'vterm'
|
3
|
-
require 'pty'
|
4
|
-
|
5
|
-
module Yamatanooroti::VTermTestCaseModule
|
6
|
-
def start_terminal(height, width, command, wait: 0.1)
|
7
|
-
@wait = wait
|
8
|
-
|
9
|
-
@pty_output, @pty_input, @pid = PTY.spawn(*command)
|
10
|
-
|
11
|
-
@vterm = VTerm.new(height, width)
|
12
|
-
@vterm.set_utf8(true)
|
13
|
-
|
14
|
-
@screen = @vterm.screen
|
15
|
-
@screen.reset(true)
|
16
|
-
|
17
|
-
sync
|
18
|
-
end
|
19
|
-
|
20
|
-
def write(str)
|
21
|
-
sync
|
22
|
-
@pty_input.write(str)
|
23
|
-
sync
|
24
|
-
end
|
25
|
-
|
26
|
-
def close
|
27
|
-
sync
|
28
|
-
@pty_input.close
|
29
|
-
sync
|
30
|
-
end
|
31
|
-
|
32
|
-
private def sync
|
33
|
-
loop do
|
34
|
-
sleep @wait
|
35
|
-
chunk = @pty_output.read_nonblock(1024)
|
36
|
-
@vterm.write(chunk)
|
37
|
-
chunk = @vterm.read
|
38
|
-
@pty_input.write(chunk)
|
39
|
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
40
|
-
break
|
41
|
-
rescue Errno::EIO # EOF
|
42
|
-
break
|
43
|
-
rescue IO::EAGAINWaitReadable # emtpy buffer
|
44
|
-
break
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def assert_screen(expected_lines)
|
49
|
-
actual_lines = []
|
50
|
-
rows, cols = @vterm.size
|
51
|
-
rows.times do |r|
|
52
|
-
actual_lines << ''
|
53
|
-
cols.times do |c|
|
54
|
-
cell = @screen.cell_at(r, c)
|
55
|
-
actual_lines.last << cell.char if cell.char
|
56
|
-
end
|
57
|
-
actual_lines.last.gsub!(/ *$/, '')
|
58
|
-
end
|
59
|
-
case expected_lines
|
60
|
-
when Array
|
61
|
-
assert_equal(expected_lines, actual_lines)
|
62
|
-
when String
|
63
|
-
assert_equal(expected_lines, actual_lines.join("\n").sub(/\n*\z/, "\n"))
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
class Yamatanooroti::VTermTestCase < Test::Unit::TestCase
|
69
|
-
include Yamatanooroti::VTermTestCaseModule
|
70
|
-
end
|