yamatanooroti 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3f7c72bc6c902dd8b5cdf5d02183954cba1180960c702e5642c208b074e2ccd5
4
+ data.tar.gz: d5dfea77ef4a055e0e278fa7447d96e1c473ce7aa4108062c99e3fc1d3e3d499
5
+ SHA512:
6
+ metadata.gz: d4242ac34c419417a1e8035968b26f4587d6d5c332852a73dad9d413b0b06447b1cd0969272b344bd3bc4a26fc4934e2a156cfc740d44fe53344f4d1f130048a
7
+ data.tar.gz: 1f2679e02d8183d15c5ded5baa273d6ca1b4b299335145a4141066c3f315a0277623ca64caa1e0f5bdffe205280ce2b9b27cfe7b1f728c71e03f8293d7211b1c
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 aycabta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # Yamatanooroti
2
+
3
+ Yamatanooroti is a multi-platform real(?) terminal testing framework.
4
+
5
+ Supporting envronments:
6
+
7
+ - vterm gem
8
+ - Windows command prompt
9
+
10
+ ## Usage
11
+
12
+ You can test the executed result and its rendering on the automatically detected environment that you have at the time, by code below:
13
+
14
+ ```ruby
15
+ require 'yamatanooroti'
16
+
17
+ class MyTest < Yamatanooroti::TestCase
18
+ def setup
19
+ start_terminal(5, 30, ['irb', '-f', '--multiline'])
20
+ end
21
+
22
+ def test_example
23
+ write(":a\n")
24
+ close
25
+ assert_screen(['irb(main):001:0> :a', '=> :a', 'irb(main):002:0>', '', ''])
26
+ end
27
+ end
28
+ ```
29
+
30
+ This code detects some real(?) terminal environments:
31
+
32
+ - vterm gem (you should install beforehand)
33
+ - Windows (you should run on command prompt)
34
+
35
+ If any real(?) terminal environments not found, it will fail with a message:
36
+
37
+ ```
38
+ $ rake
39
+ Traceback (most recent call last):
40
+ (snip traceback)
41
+ /path/to/yamatanooroti/lib/yamatanooroti.rb:71:in `inherited': Any real(?) terminal environments not found. (LoadError)
42
+ Supporting real(?) terminals:
43
+ - vterm gem
44
+ - Windows
45
+ rake aborted!
46
+ Command failed with status (1)
47
+
48
+ Tasks: TOP => default => test
49
+ (See full trace by running task with --trace)
50
+ ```
51
+
52
+ ### Advanced Usage
53
+
54
+ If you want to specify vterm environment that needs vterm gem, you can use `Yamatanooroti::VTermTestCase`:
55
+
56
+ ```ruby
57
+ require 'yamatanooroti'
58
+
59
+ class MyTest < Yamatanooroti::VTermTestCase
60
+ def setup
61
+ start_terminal(5, 30, ['irb', '-f', '--multiline'])
62
+ end
63
+
64
+ def test_example
65
+ write(":a\n")
66
+ close
67
+ assert_screen(['irb(main):001:0> :a', '=> :a', 'irb(main):002:0>', '', ''])
68
+ end
69
+ end
70
+ ```
71
+
72
+ If you haven't installed vterm gem, this code will fail with a message <q>You need vterm gem for Yamatanooroti::VTermTestCase (LoadError)</q>.
73
+
74
+ Likewise, you can specify Windows command prompt test by `Yamatanooroti::WindowsTestCase`.
75
+
76
+ ## Method Reference
77
+
78
+ ### `start_terminal(height, width, command)`
79
+
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
+
82
+ ### `write(str)`
83
+
84
+ Writes `str` like inputting by a keyboard to the started terminal.
85
+
86
+ ### `close`
87
+
88
+ Closes the terminal to take the internal rendering result. You must call it before call assertions.
89
+
90
+ ### `assert_screen(expected_lines)`
91
+
92
+ Asserts the rendering result of the terminal with `expected_lines` that should be an `Array` or a `String` of lines. The `Array` contains blank lines and doesn't contain newline characters, and the `String` contains newline characters at end of each line and doesn't contain continuous last blank lines.
93
+
94
+ ## License
95
+
96
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,89 @@
1
+ require 'test-unit'
2
+
3
+ class Yamatanooroti
4
+ def self.load_vterm
5
+ require 'vterm'
6
+ require 'yamatanooroti/vterm'
7
+ rescue LoadError
8
+ raise LoadError.new('You need vterm gem for Yamatanooroti::VTermTestCase')
9
+ end
10
+
11
+ def self.load_windows
12
+ unless win?
13
+ raise LoadError.new('You need Windows environment for Yamatanooroti::WindowsTestCase')
14
+ end
15
+ require 'yamatanooroti/windows'
16
+ end
17
+
18
+ def self.const_missing(id)
19
+ case id
20
+ when :VTermTestCase
21
+ load_vterm
22
+ Yamatanooroti::VTermTestCase
23
+ when :VTermTestCaseModule
24
+ load_vterm
25
+ Yamatanooroti::VTermTestCaseModule
26
+ when :WindowsTestCase
27
+ load_windows
28
+ Yamatanooroti::WindowsTestCase
29
+ when :WindowsTestCaseModule
30
+ load_windows
31
+ Yamatanooroti::WindowsTestCaseModule
32
+ else
33
+ raise StandardError.new("Unknown class #{id.to_s}")
34
+ end
35
+ end
36
+
37
+ def self.win?
38
+ RbConfig::CONFIG['host_os'].match?(/mswin|msys|mingw|cygwin|bccwin|wince|emc/)
39
+ end
40
+
41
+ def self.has_vterm_gem?
42
+ begin
43
+ require 'vterm'
44
+ rescue LoadError
45
+ false
46
+ else
47
+ true
48
+ end
49
+ end
50
+ end
51
+
52
+ class Yamatanooroti::TestCase < Test::Unit::TestCase
53
+ @@runners = []
54
+
55
+ def self.inherited(klass)
56
+ super
57
+ if ancestors.first == Yamatanooroti::TestCase
58
+ if Yamatanooroti.has_vterm_gem?
59
+ test_klass = Class.new(klass)
60
+ klass.const_set(:TestVTerm, test_klass)
61
+ test_klass.include Yamatanooroti::VTermTestCaseModule
62
+ @@runners << test_klass
63
+ end
64
+ if Yamatanooroti.win?
65
+ test_klass = Class.new(klass)
66
+ klass.const_set(:TestWindows, test_klass)
67
+ test_klass.include Yamatanooroti::WindowsTestCaseModule
68
+ @@runners << test_klass
69
+ end
70
+ if @@runners.empty?
71
+ raise LoadError.new(<<~EOM)
72
+ Any real(?) terminal environments not found.
73
+ Supporting real(?) terminals:
74
+ - vterm gem
75
+ - Windows
76
+ EOM
77
+ end
78
+ def klass.method_added(name)
79
+ super
80
+ if ancestors[1] == Yamatanooroti::TestCase
81
+ @@runners.each do |test_klass|
82
+ test_klass.define_method(name, instance_method(name))
83
+ end
84
+ remove_method name
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ class Yamatanooroti
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,70 @@
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
@@ -0,0 +1,436 @@
1
+ require 'test-unit'
2
+ require 'fiddle/import'
3
+ require 'fiddle/types'
4
+
5
+ module Yamatanooroti::WindowsDefinition
6
+ extend Fiddle::Importer
7
+ dlload 'kernel32.dll', 'psapi.dll', 'user32.dll'
8
+ include Fiddle::Win32Types
9
+
10
+ FREE = Fiddle::Function.new(Fiddle::RUBY_FREE, [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOID)
11
+
12
+ typealias 'SHORT', 'short'
13
+ typealias 'HPCON', 'HANDLE'
14
+ typealias 'HPCON', 'HANDLE'
15
+ typealias 'HRESULT', 'HANDLE'
16
+ typealias 'LPVOID', 'void*'
17
+ typealias 'SIZE_T', 'size_t'
18
+ typealias 'LPWSTR', 'void*'
19
+ typealias 'LPBYTE', 'void*'
20
+ typealias 'LPCWSTR', 'void*'
21
+ typealias 'LPPROC_THREAD_ATTRIBUTE_LIST', 'void*'
22
+ typealias 'PSIZE_T', 'void*'
23
+ typealias 'DWORD_PTR', 'void*'
24
+ typealias 'LPCVOID', 'void*'
25
+ typealias 'LPDWORD', 'void*'
26
+ typealias 'LPOVERLAPPED', 'void*'
27
+ typealias 'WCHAR', 'SHORT'
28
+ typealias 'LPCWCH', 'void*'
29
+ typealias 'LPSTR', 'void*'
30
+ typealias 'LPCCH', 'void*'
31
+ typealias 'LPBOOL', 'void*'
32
+ typealias 'LPWORD', 'void*'
33
+ typealias 'ULONG_PTR', 'ULONG*'
34
+ typealias 'LONG', 'int'
35
+
36
+ Fiddle::SIZEOF_HANDLE = Fiddle::SIZEOF_LONG
37
+ Fiddle::SIZEOF_HPCON = Fiddle::SIZEOF_LONG
38
+ Fiddle::SIZEOF_HRESULT = Fiddle::SIZEOF_LONG
39
+ Fiddle::SIZEOF_DWORD = Fiddle::SIZEOF_LONG
40
+ Fiddle::SIZEOF_WORD = Fiddle::SIZEOF_SHORT
41
+
42
+ COORD = struct [
43
+ 'SHORT X',
44
+ 'SHORT Y'
45
+ ]
46
+ typealias 'COORD', 'DWORD32'
47
+
48
+ SMALL_RECT = struct [
49
+ 'SHORT Left',
50
+ 'SHORT Top',
51
+ 'SHORT Right',
52
+ 'SHORT Bottom'
53
+ ]
54
+ typealias 'SMALL_RECT*', 'DWORD64*'
55
+ typealias 'PSMALL_RECT', 'SMALL_RECT*'
56
+
57
+ SECURITY_ATTRIBUTES = struct [
58
+ 'DWORD nLength',
59
+ 'LPVOID lpSecurityDescriptor',
60
+ 'BOOL bInheritHandle'
61
+ ]
62
+ typealias 'PSECURITY_ATTRIBUTES', 'SECURITY_ATTRIBUTES*'
63
+ typealias 'LPSECURITY_ATTRIBUTES', 'SECURITY_ATTRIBUTES*'
64
+
65
+ STARTUPINFOW = struct [
66
+ 'DWORD cb',
67
+ 'LPWSTR lpReserved',
68
+ 'LPWSTR lpDesktop',
69
+ 'LPWSTR lpTitle',
70
+ 'DWORD dwX',
71
+ 'DWORD dwY',
72
+ 'DWORD dwXSize',
73
+ 'DWORD dwYSize',
74
+ 'DWORD dwXCountChars',
75
+ 'DWORD dwYCountChars',
76
+ 'DWORD dwFillAttribute',
77
+ 'DWORD dwFlags',
78
+ 'WORD wShowWindow',
79
+ 'WORD cbReserved2',
80
+ 'LPBYTE lpReserved2',
81
+ 'HANDLE hStdInput',
82
+ 'HANDLE hStdOutput',
83
+ 'HANDLE hStdError'
84
+ ]
85
+ typealias 'LPSTARTUPINFOW', 'STARTUPINFOW*'
86
+
87
+ PROCESS_INFORMATION = struct [
88
+ 'HANDLE hProcess',
89
+ 'HANDLE hThread',
90
+ 'DWORD dwProcessId',
91
+ 'DWORD dwThreadId'
92
+ ]
93
+ typealias 'PPROCESS_INFORMATION', 'PROCESS_INFORMATION*'
94
+ typealias 'LPPROCESS_INFORMATION', 'PROCESS_INFORMATION*'
95
+
96
+ INPUT_RECORD_WITH_KEY_EVENT = struct [
97
+ 'WORD EventType',
98
+ 'BOOL bKeyDown',
99
+ 'WORD wRepeatCount',
100
+ 'WORD wVirtualKeyCode',
101
+ 'WORD wVirtualScanCode',
102
+ 'WCHAR UnicodeChar',
103
+ ## union 'CHAR AsciiChar',
104
+ 'DWORD dwControlKeyState'
105
+ ]
106
+
107
+ CHAR_INFO = struct [
108
+ 'WCHAR UnicodeChar',
109
+ 'WORD Attributes'
110
+ ]
111
+ typealias 'PCHAR_INFO', 'CHAR_INFO*'
112
+
113
+ PROCESSENTRY32W = struct [
114
+ 'DWORD dwSize',
115
+ 'DWORD cntUsage',
116
+ 'DWORD th32ProcessID',
117
+ 'ULONG_PTR th32DefaultHeapID',
118
+ 'DWORD th32ModuleID',
119
+ 'DWORD cntThreads',
120
+ 'DWORD th32ParentProcessID',
121
+ 'LONG pcPriClassBase',
122
+ 'DWORD dwFlags',
123
+ 'WCHAR szExeFile[260]'
124
+ ]
125
+ typealias 'LPPROCESSENTRY32W', 'PROCESSENTRY32W*'
126
+
127
+ CONSOLE_FONT_INFOEX = struct [
128
+ 'ULONG cbSize',
129
+ 'DWORD nFont',
130
+ 'DWORD32 dwFontSize',
131
+ 'UINT FontFamily',
132
+ 'UINT FontWeight',
133
+ 'WCHAR FaceName[32]'
134
+ ]
135
+ typealias 'PCONSOLE_FONT_INFOEX', 'CONSOLE_FONT_INFOEX*'
136
+
137
+ STD_INPUT_HANDLE = -10
138
+ STD_OUTPUT_HANDLE = -11
139
+ STD_ERROR_HANDLE = -12
140
+ ATTACH_PARENT_PROCESS = -1
141
+ KEY_EVENT = 0x0001
142
+ CT_CTYPE3 = 0x04
143
+ C3_HIRAGANA = 0x0020
144
+ C3_HALFWIDTH = 0x0040
145
+ C3_FULLWIDTH = 0x0080
146
+ C3_IDEOGRAPH = 0x0100
147
+ TH32CS_SNAPPROCESS = 0x00000002
148
+ PROCESS_ALL_ACCESS = 0x001FFFFF
149
+
150
+ # HANDLE GetStdHandle(DWORD nStdHandle);
151
+ extern 'HANDLE GetStdHandle(DWORD);', :stdcall
152
+ # BOOL CloseHandle(HANDLE hObject);
153
+ extern 'BOOL CloseHandle(HANDLE);', :stdcall
154
+
155
+ # BOOL FreeConsole(void);
156
+ extern 'BOOL FreeConsole(void);', :stdcall
157
+ # BOOL AllocConsole(void);
158
+ extern 'BOOL AllocConsole(void);', :stdcall
159
+ # BOOL AttachConsole(DWORD dwProcessId);
160
+ extern 'BOOL AttachConsole(DWORD);', :stdcall
161
+ # BOOL WINAPI SetConsoleScreenBufferSize(HANDLE hConsoleOutput, COORD dwSize);
162
+ extern 'BOOL SetConsoleScreenBufferSize(HANDLE, COORD);', :stdcall
163
+ # BOOL WINAPI SetConsoleWindowInfo(HANDLE hConsoleOutput, BOOL bAbsolute, const SMALL_RECT *lpConsoleWindow);
164
+ extern 'BOOL SetConsoleWindowInfo(HANDLE, BOOL, PSMALL_RECT);', :stdcall
165
+ # BOOL WriteConsoleInputW(HANDLE hConsoleInput, const INPUT_RECORD *lpBuffer, DWORD nLength, LPDWORD lpNumberOfEventsWritten);
166
+ extern 'BOOL WriteConsoleInputW(HANDLE, const INPUT_RECORD*, DWORD, LPDWORD);', :stdcall
167
+ # BOOL ReadConsoleOutputW(HANDLE hConsoleOutput, PCHAR_INFO lpBuffer, COORD dwBufferSize, COORD dwBufferCoord, PSMALL_RECT lpReadRegion);
168
+ extern 'BOOL ReadConsoleOutputW(HANDLE, PCHAR_INFO, COORD, COORD, PSMALL_RECT);', :stdcall
169
+ # BOOL WINAPI SetCurrentConsoleFontEx(HANDLE hConsoleOutput, BOOL bMaximumWindow, PCONSOLE_FONT_INFOEX lpConsoleCurrentFontEx);
170
+ extern 'BOOL SetCurrentConsoleFontEx(HANDLE, BOOL, PCONSOLE_FONT_INFOEX);', :stdcall
171
+
172
+ # BOOL CreateProcessW(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation);
173
+ extern 'BOOL CreateProcessW(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation);', :stdcall
174
+ # HANDLE CreateToolhelp32Snapshot(DWORD dwFlags, DWORD th32ProcessID);
175
+ extern 'HANDLE CreateToolhelp32Snapshot(DWORD, DWORD);', :stdcall
176
+ # BOOL Process32First(HANDLE hSnapshot, LPPROCESSENTRY32W lppe);
177
+ extern 'BOOL Process32FirstW(HANDLE, LPPROCESSENTRY32W);', :stdcall
178
+ # BOOL Process32Next(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
179
+ extern 'BOOL Process32NextW(HANDLE, LPPROCESSENTRY32W);', :stdcall
180
+ # DWORD GetCurrentProcessId();
181
+ extern 'DWORD GetCurrentProcessId();', :stdcall
182
+ # HANDLE OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);
183
+ extern 'HANDLE OpenProcess(DWORD, BOOL, DWORD);', :stdcall
184
+ # BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode);
185
+ extern 'BOOL TerminateProcess(HANDLE, UINT);', :stdcall
186
+ #BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode);
187
+ extern 'BOOL TerminateThread(HANDLE, DWORD);', :stdcall
188
+
189
+ # int MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar);
190
+ extern 'int MultiByteToWideChar(UINT, DWORD, LPCSTR, int, LPWSTR, int);', :stdcall
191
+ # int WideCharToMultiByte(UINT CodePage, DWORD dwFlags, _In_NLS_string_(cchWideChar)LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar);
192
+ extern 'int WideCharToMultiByte(UINT, DWORD, LPCWCH, int, LPSTR, int, LPCCH, LPBOOL);', :stdcall
193
+ #BOOL GetStringTypeW(DWORD dwInfoType, LPCWCH lpSrcStr, int cchSrc, LPWORD lpCharType);
194
+ extern 'BOOL GetStringTypeW(DWORD, LPCWCH, int, LPWORD);', :stdcall
195
+
196
+ typealias 'LPTSTR', 'void*'
197
+ typealias 'HLOCAL', 'HANDLE'
198
+ extern 'DWORD FormatMessage(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPTSTR lpBuffer, DWORD nSize, va_list *Arguments);', :stdcall
199
+ extern 'HLOCAL LocalFree(HLOCAL hMem);', :stdcall
200
+ extern 'DWORD GetLastError();', :stdcall
201
+ FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100
202
+ FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
203
+ LANG_NEUTRAL = 0x00
204
+ SUBLANG_DEFAULT = 0x01
205
+ extern 'int GetSystemMetrics(int);', :stdcall
206
+ SM_CXMIN = 28
207
+ SM_CYMIN = 29
208
+ end
209
+
210
+ module Yamatanooroti::WindowsTestCaseModule
211
+ DL = Yamatanooroti::WindowsDefinition
212
+
213
+ private def setup_console(height, width)
214
+
215
+ result = DL.FreeConsole
216
+ error_message(result, 'FreeConsole')
217
+ result = DL.AllocConsole
218
+ error_message(result, 'AllocConsole')
219
+ @output_handle = DL.GetStdHandle(DL::STD_OUTPUT_HANDLE)
220
+
221
+ =begin
222
+ font = DL::CONSOLE_FONT_INFOEX.malloc
223
+ (font.to_ptr + 0)[0, DL::CONSOLE_FONT_INFOEX.size] = "\x00" * DL::CONSOLE_FONT_INFOEX.size
224
+ font.cbSize = DL::CONSOLE_FONT_INFOEX.size
225
+ font.nFont = 0
226
+ font_size = 72
227
+ font.dwFontSize = font_size * 65536 + font_size
228
+ font.FontFamily = 0
229
+ font.FontWeight = 0
230
+ font.FaceName[0] = "\x00".ord
231
+ result = DL.SetCurrentConsoleFontEx(@output_handle, 0, font)
232
+ error_message(result, 'SetCurrentConsoleFontEx')
233
+ =end
234
+
235
+ rect = DL::SMALL_RECT.malloc
236
+ rect.Left = 0
237
+ rect.Top = 0
238
+ rect.Right = width - 1
239
+ rect.Bottom = height - 1
240
+ result = DL.SetConsoleWindowInfo(@output_handle, 1, rect)
241
+ error_message(result, 'SetConsoleWindowInfo')
242
+
243
+ size = DL.GetSystemMetrics(DL::SM_CYMIN) * 65536 + DL.GetSystemMetrics(DL::SM_CXMIN)
244
+ result = DL.SetConsoleScreenBufferSize(@output_handle, size)
245
+ error_message(result, 'SetConsoleScreenBufferSize')
246
+
247
+ size = height * 65536 + width
248
+ result = DL.SetConsoleScreenBufferSize(@output_handle, size)
249
+ error_message(result, 'SetConsoleScreenBufferSize')
250
+ end
251
+
252
+ private def mb2wc(str)
253
+ size = DL.MultiByteToWideChar(65001, 0, str, str.bytesize, '', 0)
254
+ converted_str = String.new("\x00" * (size * 2), encoding: 'ASCII-8BIT')
255
+ DL.MultiByteToWideChar(65001, 0, str, str.bytesize, converted_str, converted_str.bytesize)
256
+ converted_str
257
+ end
258
+
259
+ private def wc2mb(str)
260
+ size = DL.WideCharToMultiByte(65001, 0, str, str.bytesize, '', 0, 0, 0)
261
+ converted_str = "\x00" * (size * 2)
262
+ DL.WideCharToMultiByte(65001, 0, str, str.bytesize, converted_str, converted_str.bytesize, 0, 0)
263
+ converted_str
264
+ end
265
+
266
+ private def full_width?(c)
267
+ return false if c.nil? or c.empty?
268
+ wc = mb2wc(c)
269
+ type = Fiddle::Pointer.malloc(Fiddle::SIZEOF_WORD, DL::FREE)
270
+ DL.GetStringTypeW(DL::CT_CTYPE3, wc, wc.bytesize, type)
271
+ char_type = type[0, Fiddle::SIZEOF_WORD].unpack('S').first
272
+ if char_type.anybits?(DL::C3_FULLWIDTH)
273
+ true
274
+ elsif char_type.anybits?(DL::C3_HALFWIDTH)
275
+ false
276
+ elsif char_type.anybits?(DL::C3_HIRAGANA)
277
+ true
278
+ elsif char_type.anybits?(DL::C3_IDEOGRAPH)
279
+ true
280
+ else
281
+ false
282
+ end
283
+ end
284
+
285
+ private def launch(command)
286
+ command = %Q{cmd.exe /q /c "#{command.gsub('"', '\\"')}"}
287
+ converted_command = mb2wc(command)
288
+ @pi = DL::PROCESS_INFORMATION.malloc
289
+ (@pi.to_ptr + 0)[0, DL::PROCESS_INFORMATION.size] = "\x00" * DL::PROCESS_INFORMATION.size
290
+ @startup_info_ex = DL::STARTUPINFOW.malloc
291
+ (@startup_info_ex.to_ptr + 0)[0, DL::STARTUPINFOW.size] = "\x00" * DL::STARTUPINFOW.size
292
+ result = DL.CreateProcessW(
293
+ Fiddle::NULL, converted_command,
294
+ Fiddle::NULL, Fiddle::NULL, 0, 0, Fiddle::NULL, Fiddle::NULL,
295
+ @startup_info_ex, @pi
296
+ )
297
+ error_message(result, 'CreateProcessW')
298
+ sleep @wait
299
+ rescue => e
300
+ pp e
301
+ end
302
+
303
+ private def error_message(result, method_name)
304
+ return if not result.zero?
305
+ err = DL.GetLastError
306
+ string = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP)
307
+ DL.FormatMessage(
308
+ DL::FORMAT_MESSAGE_ALLOCATE_BUFFER | DL::FORMAT_MESSAGE_FROM_SYSTEM,
309
+ Fiddle::NULL,
310
+ err,
311
+ 0x0,
312
+ string,
313
+ 0,
314
+ Fiddle::NULL
315
+ )
316
+ log "ERROR(#{method_name}): #{err.to_s}: #{string.ptr.to_s}"
317
+ DL.LocalFree(string)
318
+ end
319
+
320
+ private def log(str)
321
+ puts str
322
+ open('aaa', 'a') do |fp|
323
+ fp.puts str
324
+ end
325
+ end
326
+
327
+ def write(str)
328
+ sleep @wait
329
+ str.tr!("\n", "\r")
330
+ records = Fiddle::Pointer.malloc(DL::INPUT_RECORD_WITH_KEY_EVENT.size * str.size * 2, DL::FREE)
331
+ str.chars.each_with_index do |c, i|
332
+ record_index = i * 2
333
+ r = DL::INPUT_RECORD_WITH_KEY_EVENT.new(records + DL::INPUT_RECORD_WITH_KEY_EVENT.size * record_index)
334
+ r.EventType = DL::KEY_EVENT
335
+ r.bKeyDown = 1
336
+ r.wRepeatCount = 0
337
+ r.wVirtualKeyCode = 0
338
+ r.wVirtualScanCode = 0
339
+ r.UnicodeChar = c.unpack('U').first
340
+ r.dwControlKeyState = 0
341
+ record_index = i * 2 + 1
342
+ r = DL::INPUT_RECORD_WITH_KEY_EVENT.new(records + DL::INPUT_RECORD_WITH_KEY_EVENT.size * record_index)
343
+ r.EventType = DL::KEY_EVENT
344
+ r.bKeyDown = 0
345
+ r.wRepeatCount = 0
346
+ r.wVirtualKeyCode = 0
347
+ r.wVirtualScanCode = 0
348
+ r.UnicodeChar = c.unpack('U').first
349
+ r.dwControlKeyState = 0
350
+ end
351
+ written_size = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DWORD, DL::FREE)
352
+ result = DL.WriteConsoleInputW(DL.GetStdHandle(DL::STD_INPUT_HANDLE), records, str.size * 2, written_size)
353
+ error_message(result, 'WriteConsoleInput')
354
+ end
355
+
356
+ private def free_resources
357
+ h_snap = DL.CreateToolhelp32Snapshot(DL::TH32CS_SNAPPROCESS, 0)
358
+ pe = DL::PROCESSENTRY32W.malloc
359
+ (pe.to_ptr + 0)[0, DL::PROCESSENTRY32W.size] = "\x00" * DL::PROCESSENTRY32W.size
360
+ pe.dwSize = DL::PROCESSENTRY32W.size
361
+ result = DL.Process32FirstW(h_snap, pe)
362
+ error_message(result, "Process32First")
363
+ result = DL.FreeConsole()
364
+ #error_message(result, "FreeConsole")
365
+ result = DL.AttachConsole(DL::ATTACH_PARENT_PROCESS)
366
+ error_message(result, 'AttachConsole')
367
+ loop do
368
+ #log "a #{pe.th32ParentProcessID.inspect} -> #{pe.th32ProcessID.inspect} #{wc2mb(pe.szExeFile.pack('S260')).unpack('Z*').pack('Z*')}"
369
+ if pe.th32ParentProcessID == DL.GetCurrentProcessId
370
+ h_child_proc = DL.OpenProcess(DL::PROCESS_ALL_ACCESS, 0, pe.th32ProcessID)
371
+ if (h_child_proc)
372
+ result = DL.TerminateProcess(h_child_proc, 0)
373
+ error_message(result, "TerminateProcess")
374
+ result = DL.CloseHandle(h_child_proc)
375
+ error_message(result, "CloseHandle")
376
+ end
377
+ end
378
+ break if DL.Process32NextW(h_snap, pe).zero?
379
+ end
380
+ result = DL.TerminateThread(@pi.hThread, 0)
381
+ error_message(result, "TerminateThread")
382
+ end
383
+
384
+ def close
385
+ sleep @wait
386
+ # read first before kill the console process including output
387
+ char_info_matrix = Fiddle::Pointer.to_ptr("\x00" * (DL::CHAR_INFO.size * (@height * @width)))
388
+ region = DL::SMALL_RECT.malloc
389
+ region.Left = 0
390
+ region.Top = 0
391
+ region.Right = @width
392
+ region.Bottom = @height
393
+ result = DL.ReadConsoleOutputW(@output_handle, char_info_matrix, @height * 65536 + @width, 0, region)
394
+ error_message(result, "ReadConsoleOutputW")
395
+ @result = []
396
+ prev_c = nil
397
+ @height.times do |y|
398
+ line = ''
399
+ @width.times do |x|
400
+ index = @width * y + x
401
+ char_info = DL::CHAR_INFO.new(char_info_matrix + DL::CHAR_INFO.size * index)
402
+ mb = [char_info.UnicodeChar].pack('U')
403
+ if prev_c == mb and full_width?(mb)
404
+ prev_c = nil
405
+ else
406
+ line << mb
407
+ prev_c = mb
408
+ end
409
+ end
410
+ @result << line.gsub(/ *$/, '')
411
+ end
412
+
413
+ free_resources
414
+ end
415
+
416
+ def assert_screen(expected_lines)
417
+ case expected_lines
418
+ when Array
419
+ assert_equal(expected_lines, @result)
420
+ when String
421
+ assert_equal(expected_lines, @result.join("\n").sub(/\n*\z/, "\n"))
422
+ end
423
+ end
424
+
425
+ def start_terminal(height, width, command, wait: 1)
426
+ @height = height
427
+ @width = width
428
+ @wait = wait
429
+ setup_console(height, width)
430
+ launch(command.join(' '))
431
+ end
432
+ end
433
+
434
+ class Yamatanooroti::WindowsTestCase < Test::Unit::TestCase
435
+ include Yamatanooroti::WindowsTestCaseModule
436
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yamatanooroti
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - aycabta
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-03-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: test-unit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: " Yamatanooroti is a multi-platform real(?) terminal test framework.\n"
42
+ email:
43
+ - aycabta@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - LICENSE.txt
49
+ - README.md
50
+ - lib/yamatanooroti.rb
51
+ - lib/yamatanooroti/version.rb
52
+ - lib/yamatanooroti/vterm.rb
53
+ - lib/yamatanooroti/windows.rb
54
+ homepage: https://github.com/aycabta/yamatanooroti
55
+ licenses:
56
+ - MIT
57
+ metadata: {}
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '2.5'
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubygems_version: 3.1.2
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Multi-platform real(?) terminal test framework
77
+ test_files: []