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 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