yamatanooroti 0.0.4 → 0.0.8
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 +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
|