module Yamatanooroti::WindowsTestCaseModule

Constants

Bottom
DL
EventType
Left
Top
UnicodeChar

Public Instance Methods

assert_screen(expected_lines) click to toggle source
# File lib/yamatanooroti/windows.rb, line 487
def assert_screen(expected_lines)
  case expected_lines
  when Array
    assert_equal(expected_lines, @result)
  when String
    assert_equal(expected_lines, @result.join("\n").sub(/\n*\z/, "\n"))
  end
end
close() click to toggle source
# File lib/yamatanooroti/windows.rb, line 446
def close
  sleep @wait
  # read first before kill the console process including output
  @result = retrieve_screen

  free_resources
end
result() click to toggle source
# File lib/yamatanooroti/windows.rb, line 483
def result
  @result
end
start_terminal(height, width, command, wait: 1, startup_message: nil) click to toggle source
# File lib/yamatanooroti/windows.rb, line 496
def start_terminal(height, width, command, wait: 1, startup_message: nil)
  @height = height
  @width = width
  @wait = wait
  @result = nil
  setup_console(height, width)
  launch(command.map{ |c| quote_command_arg(c) }.join(' '))
  case startup_message
  when String
    check_startup_message = ->(message) { message.start_with?(startup_message) }
  when Regexp
    check_startup_message = ->(message) { startup_message.match?(message) }
  end
  if check_startup_message
    loop do
      screen = retrieve_screen.join("\n").sub(/\n*\z/, "\n")
      break if check_startup_message.(screen)
      sleep @wait
    end
  end
end
write(str) click to toggle source
# File lib/yamatanooroti/windows.rb, line 368
def write(str)
  sleep @wait
  str.force_encoding(Encoding::ASCII_8BIT).tr!("\n", "\r")
  records = Fiddle::Pointer.malloc(DL::INPUT_RECORD_WITH_KEY_EVENT.size * str.size * 2, DL::FREE)
  str.chars.each_with_index do |c, i|
    byte = c.ord
    if c.bytesize == 1 and byte.allbits?(0x80) # with Meta key
      c = (byte ^ 0x80).chr
      control_key_state = DL::LEFT_ALT_PRESSED
    else
      control_key_state = 0
    end
    record_index = i * 2
    r = DL::INPUT_RECORD_WITH_KEY_EVENT.new(records + DL::INPUT_RECORD_WITH_KEY_EVENT.size * record_index)
    set_input_record(r, c, true, control_key_state)
    record_index = i * 2 + 1
    r = DL::INPUT_RECORD_WITH_KEY_EVENT.new(records + DL::INPUT_RECORD_WITH_KEY_EVENT.size * record_index)
    set_input_record(r, c, false, control_key_state)
  end
  written_size = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DWORD, DL::FREE)
  r = DL.WriteConsoleInputW(DL.GetStdHandle(DL::STD_INPUT_HANDLE), records, str.size * 2, written_size)
  error_message(r, 'WriteConsoleInput')
end

Private Instance Methods

error_message(r, method_name) click to toggle source
# File lib/yamatanooroti/windows.rb, line 344
        def error_message(r, method_name)
  return if not r.zero?
  err = DL.GetLastError
  string = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP)
  DL.FormatMessage(
    DL::FORMAT_MESSAGE_ALLOCATE_BUFFER | DL::FORMAT_MESSAGE_FROM_SYSTEM,
    Fiddle::NULL,
    err,
    0x0,
    string,
    0,
    Fiddle::NULL
  )
  log "ERROR(#{method_name}): #{err.to_s}: #{string.ptr.to_s}"
  DL.LocalFree(string)
end
free_resources() click to toggle source
# File lib/yamatanooroti/windows.rb, line 407
        def free_resources
  h_snap = DL.CreateToolhelp32Snapshot(DL::TH32CS_SNAPPROCESS, 0)
  pe = DL::PROCESSENTRY32W.malloc
  (pe.to_ptr + 0)[0, DL::PROCESSENTRY32W.size] = "\x00" * DL::PROCESSENTRY32W.size
  pe.dwSize = DL::PROCESSENTRY32W.size
  r = DL.Process32FirstW(h_snap, pe)
  error_message(r, "Process32First")
  process_table = {}
  loop do
    #log "a #{pe.th32ParentProcessID.inspect} -> #{pe.th32ProcessID.inspect} #{wc2mb(pe.szExeFile.pack('S260')).unpack('Z*').pack('Z*')}"
    process_table[pe.th32ParentProcessID] ||= []
    process_table[pe.th32ParentProcessID] << pe.th32ProcessID
    break if DL.Process32NextW(h_snap, pe).zero?
  end
  process_table[DL.GetCurrentProcessId].each do |child_pid|
    kill_process_tree(process_table, child_pid)
  end
  #r = DL.TerminateThread(@pi.hThread, 0)
  #error_message(r, "TerminateThread")
  #sleep @wait
  r = DL.FreeConsole()
  #error_message(r, "FreeConsole")
  r = DL.AttachConsole(DL::ATTACH_PARENT_PROCESS)
  error_message(r, 'AttachConsole')
end
full_width?(c) click to toggle source
# File lib/yamatanooroti/windows.rb, line 278
        def full_width?(c)
  return false if c.nil? or c.empty?
  wc = mb2wc(c)
  type = Fiddle::Pointer.malloc(Fiddle::SIZEOF_WORD, DL::FREE)
  DL.GetStringTypeW(DL::CT_CTYPE3, wc, wc.bytesize, type)
  char_type = type[0, Fiddle::SIZEOF_WORD].unpack('S').first
  if char_type.anybits?(DL::C3_FULLWIDTH)
    true
  elsif char_type.anybits?(DL::C3_HALFWIDTH)
    false
  elsif char_type.anybits?(DL::C3_HIRAGANA)
    true
  elsif char_type.anybits?(DL::C3_IDEOGRAPH)
    true
  else
    false
  end
end
kill_process_tree(process_table, pid) click to toggle source
# File lib/yamatanooroti/windows.rb, line 433
        def kill_process_tree(process_table, pid)
  process_table[pid]&.each do |child_pid|
    kill_process_tree(process_table, child_pid)
  end
  h_proc = DL.OpenProcess(DL::PROCESS_ALL_ACCESS, 0, pid)
  if (h_proc)
    r = DL.TerminateProcess(h_proc, 0)
    error_message(r, "TerminateProcess")
    r = DL.CloseHandle(h_proc)
    error_message(r, "CloseHandle")
  end
end
launch(command) click to toggle source
# File lib/yamatanooroti/windows.rb, line 326
        def launch(command)
  command = %Q{cmd.exe /q /c "#{command}"}
  converted_command = mb2wc(command)
  @pi = DL::PROCESS_INFORMATION.malloc
  (@pi.to_ptr + 0)[0, DL::PROCESS_INFORMATION.size] = "\x00" * DL::PROCESS_INFORMATION.size
  @startup_info_ex = DL::STARTUPINFOW.malloc
  (@startup_info_ex.to_ptr + 0)[0, DL::STARTUPINFOW.size] = "\x00" * DL::STARTUPINFOW.size
  r = DL.CreateProcessW(
    Fiddle::NULL, converted_command,
    Fiddle::NULL, Fiddle::NULL, 0, 0, Fiddle::NULL, Fiddle::NULL,
    @startup_info_ex, @pi
  )
  error_message(r, 'CreateProcessW')
  sleep @wait
rescue => e
  pp e
end
log(str) click to toggle source
# File lib/yamatanooroti/windows.rb, line 361
        def log(str)
  puts str
  open('aaa', 'a') do |fp|
    fp.puts str
  end
end
mb2wc(str) click to toggle source
# File lib/yamatanooroti/windows.rb, line 264
        def mb2wc(str)
  size = DL.MultiByteToWideChar(65001, 0, str, str.bytesize, '', 0)
  converted_str = String.new("\x00" * (size * 2), encoding: 'ASCII-8BIT')
  DL.MultiByteToWideChar(65001, 0, str, str.bytesize, converted_str, converted_str.bytesize)
  converted_str
end
quote_command_arg(arg) click to toggle source
# File lib/yamatanooroti/windows.rb, line 297
        def quote_command_arg(arg)
  if not arg.match?(/[ \t"]/)
    # No quotation needed.
    return arg
  end

  if not arg.match?(/["\\]/)
    # No embedded double quotes or backlashes, so I can just wrap quote
    # marks around the whole thing.
    return %{"#{arg}"}
  end

  quote_hit = true
  result = '"'
  arg.chars.reverse.each do |c|
    result << c
    if quote_hit and c == '\\'
      result << '\\'
    elsif c == '"'
      quote_hit = true
      result << '\\'
    else
      quote_hit = false
    end
  end
  result << '"'
  result.reverse
end
retrieve_screen() click to toggle source
# File lib/yamatanooroti/windows.rb, line 454
        def retrieve_screen
  char_info_matrix = Fiddle::Pointer.to_ptr("\x00" * (DL::CHAR_INFO.size * (@height * @width)))
  region = DL::SMALL_RECT.malloc
  region.Left = 0
  region.Top = 0
  region.Right = @width
  region.Bottom = @height
  r = DL.ReadConsoleOutputW(@output_handle, char_info_matrix, @height * 65536 + @width, 0, region)
  error_message(r, "ReadConsoleOutputW")
  screen = []
  prev_c = nil
  @height.times do |y|
    line = ''
    @width.times do |x|
      index = @width * y + x
      char_info = DL::CHAR_INFO.new(char_info_matrix + DL::CHAR_INFO.size * index)
      mb = [char_info.UnicodeChar].pack('U')
      if prev_c == mb and full_width?(mb)
        prev_c = nil
      else
        line << mb
        prev_c = mb
      end
    end
    screen << line.gsub(/ *$/, '')
  end
  screen
end
set_input_record(r, c, key_down, control_key_state) click to toggle source
# File lib/yamatanooroti/windows.rb, line 392
        def set_input_record(r, c, key_down, control_key_state)
  begin
    code = c.unpack('U').first
  rescue ArgumentError
    code = c.bytes.first
  end
  r.EventType = DL::KEY_EVENT
  r.bKeyDown = key_down ? 1 : 0
  r.wRepeatCount = 1
  r.wVirtualKeyCode = DL.VkKeyScanW(code)
  r.wVirtualScanCode = DL.MapVirtualKeyW(code, 0)
  r.UnicodeChar = code
  r.dwControlKeyState = control_key_state
end
setup_console(height, width) click to toggle source
# File lib/yamatanooroti/windows.rb, line 223
          def setup_console(height, width)

    r = DL.FreeConsole
    error_message(r, 'FreeConsole')
    r = DL.AllocConsole
    error_message(r, 'AllocConsole')
    @output_handle = DL.GetStdHandle(DL::STD_OUTPUT_HANDLE)

=begin
    font = DL::CONSOLE_FONT_INFOEX.malloc
    (font.to_ptr + 0)[0, DL::CONSOLE_FONT_INFOEX.size] = "\x00" * DL::CONSOLE_FONT_INFOEX.size
    font.cbSize = DL::CONSOLE_FONT_INFOEX.size
    font.nFont = 0
    font_size = 72
    font.dwFontSize = font_size * 65536 + font_size
    font.FontFamily = 0
    font.FontWeight = 0
    font.FaceName[0] = "\x00".ord
    r = DL.SetCurrentConsoleFontEx(@output_handle, 0, font)
    error_message(r, 'SetCurrentConsoleFontEx')
=end

    rect = DL::SMALL_RECT.malloc
    rect.Left = 0
    rect.Top = 0
    rect.Right = width - 1
    rect.Bottom = height - 1
    r = DL.SetConsoleWindowInfo(@output_handle, 1, rect)
    error_message(r, 'SetConsoleWindowInfo')

    size = DL.GetSystemMetrics(DL::SM_CYMIN) * 65536 + DL.GetSystemMetrics(DL::SM_CXMIN)
    r = DL.SetConsoleScreenBufferSize(@output_handle, size)
    error_message(r, 'SetConsoleScreenBufferSize')

    size = height * 65536 + width
    r = DL.SetConsoleScreenBufferSize(@output_handle, size)
    error_message(r, 'SetConsoleScreenBufferSize')
    r = DL.ShowWindow(DL.GetConsoleWindow(), DL::SW_HIDE)
    error_message(r, 'ShowWindow')
  end
wc2mb(str) click to toggle source
# File lib/yamatanooroti/windows.rb, line 271
        def wc2mb(str)
  size = DL.WideCharToMultiByte(65001, 0, str, str.bytesize, '', 0, 0, 0)
  converted_str = "\x00" * (size * 2)
  DL.WideCharToMultiByte(65001, 0, str, str.bytesize, converted_str, converted_str.bytesize, 0, 0)
  converted_str
end