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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c009e6dc5f113b62e9e62a6c1ce27130b2f2100f9ab0aa2a959afb893eace993
4
- data.tar.gz: dd24b52416f695388dda859d1f538662d15bead1e16b6b0b5702baf40702a54e
3
+ metadata.gz: 405b57a78b93d5990c59232f331cfabec9c9c0e6bd6f0421ed4f62d7df0f93ed
4
+ data.tar.gz: e3cb1a67a2ab62d8afa3c12ff9e76b6630c4ea7bd9adc3f79074f438bcefd2eb
5
5
  SHA512:
6
- metadata.gz: 5a412b98347bd3e0b251905fa8f2de3dd54763899dcd80f50635d5d13f9166a2d0dbe80daa06faabd03a5ef000862db7d42951b935c15ecb75fd278b7e79c834
7
- data.tar.gz: 137f7d47e13c565daf6ffaf3cf56b47f00937ac6c61255bab821013fc408e14318b5b9110edf80e3c8796d7866e498665a190ff444fc28bea3704f9efb67afe6
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.
@@ -1,3 +1,3 @@
1
1
  class Yamatanooroti
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.8'
3
3
  end
@@ -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
- @pty_input.write(str)
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
- @result.last << cell.char if cell.char
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.gsub('"', '\\"')}"}
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.EventType = DL::KEY_EVENT
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.EventType = DL::KEY_EVENT
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
- @result = []
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
- @result << line.gsub(/ *$/, '')
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
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: 2020-09-08 00:00:00.000000000 Z
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.1.2
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