diff options
Diffstat (limited to 'spec')
-rw-r--r-- | spec/04-term_spec.lua | 808 | ||||
-rw-r--r-- | spec/05-bitflags_spec.lua | 114 |
2 files changed, 921 insertions, 1 deletions
diff --git a/spec/04-term_spec.lua b/spec/04-term_spec.lua index a2034aa..57fb4d0 100644 --- a/spec/04-term_spec.lua +++ b/spec/04-term_spec.lua | |||
@@ -1,9 +1,21 @@ | |||
1 | -- Import the library that contains the environment-related functions | ||
2 | local system = require("system") | 1 | local system = require("system") |
3 | require("spec.helpers") | 2 | require("spec.helpers") |
4 | 3 | ||
5 | describe("Terminal:", function() | 4 | describe("Terminal:", function() |
6 | 5 | ||
6 | local wincodepage | ||
7 | |||
8 | setup(function() | ||
9 | wincodepage = system.getconsoleoutputcp() | ||
10 | assert(system.setconsoleoutputcp(system.CODEPAGE_UTF8)) -- set to UTF8 | ||
11 | end) | ||
12 | |||
13 | teardown(function() | ||
14 | assert(system.setconsoleoutputcp(wincodepage)) | ||
15 | end) | ||
16 | |||
17 | |||
18 | |||
7 | describe("isatty()", function() | 19 | describe("isatty()", function() |
8 | 20 | ||
9 | local newtmpfile = require("pl.path").tmpname | 21 | local newtmpfile = require("pl.path").tmpname |
@@ -91,4 +103,798 @@ describe("Terminal:", function() | |||
91 | 103 | ||
92 | end) | 104 | end) |
93 | 105 | ||
106 | |||
107 | |||
108 | describe("getconsoleflags()", function() | ||
109 | |||
110 | win_it("returns the consoleflags #manual", function() | ||
111 | local flags, err = system.getconsoleflags(io.stdout) | ||
112 | assert.is_nil(err) | ||
113 | assert.is_userdata(flags) | ||
114 | assert.equals("bitflags:", tostring(flags):sub(1,9)) | ||
115 | end) | ||
116 | |||
117 | |||
118 | nix_it("returns the consoleflags, always value 0", function() | ||
119 | local flags, err = system.getconsoleflags(io.stdout) | ||
120 | assert.is_nil(err) | ||
121 | assert.is_userdata(flags) | ||
122 | assert.equals("bitflags:", tostring(flags):sub(1,9)) | ||
123 | assert.equals(0, flags:value()) | ||
124 | end) | ||
125 | |||
126 | |||
127 | it("returns an error if called with an invalid argument", function() | ||
128 | assert.has.error(function() | ||
129 | system.getconsoleflags("invalid") | ||
130 | end, "bad argument #1 to 'getconsoleflags' (FILE* expected, got string)") | ||
131 | end) | ||
132 | |||
133 | end) | ||
134 | |||
135 | |||
136 | |||
137 | describe("setconsoleflags()", function() | ||
138 | |||
139 | win_it("sets the consoleflags #manual", function() | ||
140 | local old_flags = assert(system.getconsoleflags(io.stdout)) | ||
141 | finally(function() | ||
142 | system.setconsoleflags(io.stdout, old_flags) -- ensure we restore the original ones | ||
143 | end) | ||
144 | |||
145 | local new_flags | ||
146 | if old_flags:has_all_of(system.COF_VIRTUAL_TERMINAL_PROCESSING) then | ||
147 | new_flags = old_flags - system.COF_VIRTUAL_TERMINAL_PROCESSING | ||
148 | else | ||
149 | new_flags = old_flags + system.COF_VIRTUAL_TERMINAL_PROCESSING | ||
150 | end | ||
151 | |||
152 | local success, err = system.setconsoleflags(io.stdout, new_flags) | ||
153 | assert.is_nil(err) | ||
154 | assert.is_true(success) | ||
155 | |||
156 | local updated_flags = assert(system.getconsoleflags(io.stdout)) | ||
157 | assert.equals(new_flags:value(), updated_flags:value()) | ||
158 | end) | ||
159 | |||
160 | |||
161 | nix_it("sets the consoleflags, always succeeds", function() | ||
162 | assert(system.setconsoleflags(io.stdout, system.getconsoleflags(io.stdout))) | ||
163 | end) | ||
164 | |||
165 | |||
166 | it("returns an error if called with an invalid argument", function() | ||
167 | assert.has.error(function() | ||
168 | system.setconsoleflags("invalid") | ||
169 | end, "bad argument #1 to 'setconsoleflags' (FILE* expected, got string)") | ||
170 | end) | ||
171 | |||
172 | end) | ||
173 | |||
174 | |||
175 | |||
176 | describe("tcgetattr()", function() | ||
177 | |||
178 | nix_it("gets the terminal flags #manual", function() | ||
179 | local flags, err = system.tcgetattr(io.stdin) | ||
180 | assert.is_nil(err) | ||
181 | assert.is_table(flags) | ||
182 | assert.equals("bitflags:", tostring(flags.iflag):sub(1,9)) | ||
183 | assert.equals("bitflags:", tostring(flags.oflag):sub(1,9)) | ||
184 | assert.equals("bitflags:", tostring(flags.lflag):sub(1,9)) | ||
185 | assert.equals("bitflags:", tostring(flags.cflag):sub(1,9)) | ||
186 | assert.not_equal(0, flags.iflag:value()) | ||
187 | assert.not_equal(0, flags.oflag:value()) | ||
188 | assert.not_equal(0, flags.lflag:value()) | ||
189 | assert.not_equal(0, flags.cflag:value()) | ||
190 | assert.is.table(flags.cc) | ||
191 | end) | ||
192 | |||
193 | |||
194 | win_it("gets the terminal flags, always 0", function() | ||
195 | local flags, err = system.tcgetattr(io.stdin) | ||
196 | assert.is_nil(err) | ||
197 | assert.is_table(flags) | ||
198 | assert.equals("bitflags:", tostring(flags.iflag):sub(1,9)) | ||
199 | assert.equals("bitflags:", tostring(flags.oflag):sub(1,9)) | ||
200 | assert.equals("bitflags:", tostring(flags.lflag):sub(1,9)) | ||
201 | assert.equals("bitflags:", tostring(flags.cflag):sub(1,9)) | ||
202 | assert.equals(0, flags.iflag:value()) | ||
203 | assert.equals(0, flags.oflag:value()) | ||
204 | assert.equals(0, flags.lflag:value()) | ||
205 | assert.equals(0, flags.cflag:value()) | ||
206 | assert.same({}, flags.cc) | ||
207 | end) | ||
208 | |||
209 | |||
210 | it("returns an error if called with an invalid argument", function() | ||
211 | assert.has.error(function() | ||
212 | system.tcgetattr("invalid") | ||
213 | end, "bad argument #1 to 'tcgetattr' (FILE* expected, got string)") | ||
214 | end) | ||
215 | |||
216 | end) | ||
217 | |||
218 | |||
219 | |||
220 | describe("tcsetattr()", function() | ||
221 | |||
222 | nix_it("sets the terminal flags, if called with flags #manual", function() | ||
223 | -- system.listtermflags(io.stdin) | ||
224 | local old_flags = assert(system.tcgetattr(io.stdin)) | ||
225 | finally(function() | ||
226 | system.tcsetattr(io.stdin, system.TCSANOW, old_flags) -- ensure we restore the original ones | ||
227 | end) | ||
228 | |||
229 | local new_flags = assert(system.tcgetattr(io.stdin)) -- just get them again, and then update | ||
230 | -- change iflags | ||
231 | local flag_to_change = system.I_IGNCR | ||
232 | if new_flags.iflag:has_all_of(flag_to_change) then | ||
233 | new_flags.iflag = new_flags.iflag - flag_to_change | ||
234 | else | ||
235 | new_flags.iflag = new_flags.iflag + flag_to_change | ||
236 | end | ||
237 | |||
238 | -- change oflags | ||
239 | flag_to_change = system.O_OPOST | ||
240 | if new_flags.oflag:has_all_of(flag_to_change) then | ||
241 | new_flags.oflag = new_flags.oflag - flag_to_change | ||
242 | else | ||
243 | new_flags.oflag = new_flags.oflag + flag_to_change | ||
244 | end | ||
245 | |||
246 | -- change lflags | ||
247 | flag_to_change = system.L_ECHO | ||
248 | if new_flags.lflag:has_all_of(flag_to_change) then | ||
249 | new_flags.lflag = new_flags.lflag - flag_to_change | ||
250 | else | ||
251 | new_flags.lflag = new_flags.lflag + flag_to_change | ||
252 | end | ||
253 | |||
254 | assert(system.tcsetattr(io.stdin, system.TCSANOW, new_flags)) | ||
255 | |||
256 | local updated_flags = assert(system.tcgetattr(io.stdin)) | ||
257 | assert.equals(new_flags.iflag:value(), updated_flags.iflag:value()) | ||
258 | assert.equals(new_flags.oflag:value(), updated_flags.oflag:value()) | ||
259 | assert.equals(new_flags.lflag:value(), updated_flags.lflag:value()) | ||
260 | end) | ||
261 | |||
262 | |||
263 | win_it("sets the terminal flags, if called with flags, always succeeds", function() | ||
264 | local success, err = system.tcsetattr(io.stdin, system.TCSANOW, system.tcgetattr(io.stdin)) | ||
265 | assert.is_nil(err) | ||
266 | assert.is_true(success) | ||
267 | end) | ||
268 | |||
269 | |||
270 | it("returns an error if called with an invalid first argument", function() | ||
271 | assert.has.error(function() | ||
272 | system.tcsetattr("invalid", system.TCSANOW, {}) | ||
273 | end, "bad argument #1 to 'tcsetattr' (FILE* expected, got string)") | ||
274 | end) | ||
275 | |||
276 | |||
277 | it("returns an error if called with an invalid second argument", function() | ||
278 | assert.has.error(function() | ||
279 | system.tcsetattr(io.stdin, "invalid", {}) | ||
280 | end, "bad argument #2 to 'tcsetattr' (number expected, got string)") | ||
281 | end) | ||
282 | |||
283 | |||
284 | it("returns an error if called with an invalid third argument #manual", function() | ||
285 | assert.has.error(function() | ||
286 | system.tcsetattr(io.stdin, system.TCSANOW, "invalid") | ||
287 | end, "bad argument #3 to 'tcsetattr' (table expected, got string)") | ||
288 | end) | ||
289 | |||
290 | |||
291 | it("returns an error if iflag is not a bitflags object #manual", function() | ||
292 | local flags = assert(system.tcgetattr(io.stdin)) | ||
293 | flags.iflag = 0 | ||
294 | assert.has.error(function() | ||
295 | system.tcsetattr(io.stdin, system.TCSANOW, flags) | ||
296 | end, "bad argument #3, field 'iflag' must be a bitflag object") | ||
297 | end) | ||
298 | |||
299 | |||
300 | it("returns an error if oflag is not a bitflags object #manual", function() | ||
301 | local flags = assert(system.tcgetattr(io.stdin)) | ||
302 | flags.oflag = 0 | ||
303 | assert.has.error(function() | ||
304 | system.tcsetattr(io.stdin, system.TCSANOW, flags) | ||
305 | end, "bad argument #3, field 'oflag' must be a bitflag object") | ||
306 | end) | ||
307 | |||
308 | |||
309 | it("returns an error if lflag is not a bitflags object #manual", function() | ||
310 | local flags = assert(system.tcgetattr(io.stdin)) | ||
311 | flags.lflag = 0 | ||
312 | assert.has.error(function() | ||
313 | system.tcsetattr(io.stdin, system.TCSANOW, flags) | ||
314 | end, "bad argument #3, field 'lflag' must be a bitflag object") | ||
315 | end) | ||
316 | |||
317 | end) | ||
318 | |||
319 | |||
320 | |||
321 | describe("getconsolecp()", function() | ||
322 | |||
323 | win_it("gets the console codepage", function() | ||
324 | local cp, err = system.getconsolecp() | ||
325 | assert.is_nil(err) | ||
326 | assert.is_number(cp) | ||
327 | end) | ||
328 | |||
329 | nix_it("gets the console codepage, always 65001 (utf8)", function() | ||
330 | local cp, err = system.getconsolecp() | ||
331 | assert.is_nil(err) | ||
332 | assert.equals(65001, cp) | ||
333 | end) | ||
334 | |||
335 | end) | ||
336 | |||
337 | |||
338 | |||
339 | describe("setconsolecp()", function() | ||
340 | |||
341 | win_it("sets the console codepage", function() | ||
342 | local old_cp = assert(system.getconsolecp()) | ||
343 | finally(function() | ||
344 | system.setconsolecp(old_cp) -- ensure we restore the original one | ||
345 | end) | ||
346 | |||
347 | local new_cp | ||
348 | if old_cp ~= system.CODEPAGE_UTF8 then | ||
349 | new_cp = system.CODEPAGE_UTF8 -- set to UTF8 | ||
350 | else | ||
351 | new_cp = 850 -- another common one | ||
352 | end | ||
353 | |||
354 | local success, err = system.setconsolecp(new_cp) | ||
355 | assert.is_nil(err) | ||
356 | assert.is_true(success) | ||
357 | |||
358 | local updated_cp = assert(system.getconsolecp()) | ||
359 | assert.equals(new_cp, updated_cp) | ||
360 | end) | ||
361 | |||
362 | |||
363 | nix_it("sets the console codepage, always succeeds", function() | ||
364 | assert(system.setconsolecp(850)) | ||
365 | end) | ||
366 | |||
367 | |||
368 | it("returns an error if called with an invalid argument", function() | ||
369 | assert.has.error(function() | ||
370 | system.setconsolecp("invalid") | ||
371 | end, "bad argument #1 to 'setconsolecp' (number expected, got string)") | ||
372 | end) | ||
373 | |||
374 | end) | ||
375 | |||
376 | |||
377 | |||
378 | describe("getconsoleoutputcp()", function() | ||
379 | |||
380 | win_it("gets the console output codepage", function() | ||
381 | local cp, err = system.getconsoleoutputcp() | ||
382 | assert.is_nil(err) | ||
383 | assert.is_number(cp) | ||
384 | end) | ||
385 | |||
386 | nix_it("gets the console output codepage, always 65001 (utf8)", function() | ||
387 | local cp, err = system.getconsoleoutputcp() | ||
388 | assert.is_nil(err) | ||
389 | assert.equals(65001, cp) | ||
390 | end) | ||
391 | |||
392 | end) | ||
393 | |||
394 | |||
395 | |||
396 | describe("setconsoleoutputcp()", function() | ||
397 | |||
398 | win_it("sets the console output codepage", function() | ||
399 | local old_cp = assert(system.getconsoleoutputcp()) | ||
400 | finally(function() | ||
401 | system.setconsoleoutputcp(old_cp) -- ensure we restore the original one | ||
402 | end) | ||
403 | |||
404 | local new_cp | ||
405 | if old_cp ~= system.CODEPAGE_UTF8 then | ||
406 | new_cp = system.CODEPAGE_UTF8 -- set to UTF8 | ||
407 | else | ||
408 | new_cp = 850 -- another common one | ||
409 | end | ||
410 | |||
411 | local success, err = system.setconsoleoutputcp(new_cp) | ||
412 | assert.is_nil(err) | ||
413 | assert.is_true(success) | ||
414 | |||
415 | local updated_cp = assert(system.getconsoleoutputcp()) | ||
416 | assert.equals(new_cp, updated_cp) | ||
417 | end) | ||
418 | |||
419 | |||
420 | nix_it("sets the console output codepage, always succeeds", function() | ||
421 | assert(system.setconsoleoutputcp(850)) | ||
422 | end) | ||
423 | |||
424 | |||
425 | it("returns an error if called with an invalid argument", function() | ||
426 | assert.has.error(function() | ||
427 | system.setconsoleoutputcp("invalid") | ||
428 | end, "bad argument #1 to 'setconsoleoutputcp' (number expected, got string)") | ||
429 | end) | ||
430 | |||
431 | end) | ||
432 | |||
433 | |||
434 | |||
435 | describe("getnonblock()", function() | ||
436 | |||
437 | nix_it("gets the non-blocking flag", function() | ||
438 | local nb, err = system.getnonblock(io.stdin) | ||
439 | assert.is_nil(err) | ||
440 | assert.is_boolean(nb) | ||
441 | end) | ||
442 | |||
443 | |||
444 | win_it("gets the non-blocking flag, always false", function() | ||
445 | local nb, err = system.getnonblock(io.stdin) | ||
446 | assert.is_nil(err) | ||
447 | assert.is_false(nb) | ||
448 | end) | ||
449 | |||
450 | |||
451 | it("returns an error if called with an invalid argument", function() | ||
452 | assert.has.error(function() | ||
453 | system.getnonblock("invalid") | ||
454 | end, "bad argument #1 to 'getnonblock' (FILE* expected, got string)") | ||
455 | end) | ||
456 | |||
457 | end) | ||
458 | |||
459 | |||
460 | |||
461 | describe("setnonblock()", function() | ||
462 | |||
463 | nix_it("sets the non-blocking flag", function() | ||
464 | local old_nb = system.getnonblock(io.stdin) | ||
465 | assert.is.boolean(old_nb) | ||
466 | |||
467 | finally(function() | ||
468 | system.setnonblock(io.stdin, old_nb) -- ensure we restore the original one | ||
469 | end) | ||
470 | |||
471 | local new_nb = not old_nb | ||
472 | |||
473 | local success, err = system.setnonblock(io.stdin, new_nb) | ||
474 | assert.is_nil(err) | ||
475 | assert.is_true(success) | ||
476 | |||
477 | local updated_nb = assert(system.getnonblock(io.stdin)) | ||
478 | assert.equals(new_nb, updated_nb) | ||
479 | end) | ||
480 | |||
481 | |||
482 | win_it("sets the non-blocking flag, always succeeds", function() | ||
483 | local success, err = system.setnonblock(io.stdin, true) | ||
484 | assert.is_nil(err) | ||
485 | assert.is_true(success) | ||
486 | end) | ||
487 | |||
488 | |||
489 | it("returns an error if called with an invalid argument", function() | ||
490 | assert.has.error(function() | ||
491 | system.setnonblock("invalid") | ||
492 | end, "bad argument #1 to 'setnonblock' (FILE* expected, got string)") | ||
493 | end) | ||
494 | |||
495 | end) | ||
496 | |||
497 | |||
498 | |||
499 | describe("termsize() #manual", function() | ||
500 | |||
501 | it("gets the terminal size", function() | ||
502 | local rows, columns = system.termsize() | ||
503 | assert.is_number(rows) | ||
504 | assert.is_number(columns) | ||
505 | end) | ||
506 | |||
507 | end) | ||
508 | |||
509 | |||
510 | |||
511 | describe("utf8cwidth()", function() | ||
512 | |||
513 | local ch1 = string.char(226, 130, 172) -- "€" single | ||
514 | local ch2 = string.char(240, 159, 154, 128) -- "🚀" double | ||
515 | local ch3 = string.char(228, 189, 160) -- "你" double | ||
516 | local ch4 = string.char(229, 165, 189) -- "好" double | ||
517 | |||
518 | it("handles zero width characters", function() | ||
519 | assert.same({0}, {system.utf8cwidth("")}) -- empty string returns 0-size | ||
520 | assert.same({nil, 'Character width determination failed'}, {system.utf8cwidth("\a")}) -- bell character | ||
521 | assert.same({nil, 'Character width determination failed'}, {system.utf8cwidth("\27")}) -- escape character | ||
522 | end) | ||
523 | |||
524 | it("handles single width characters", function() | ||
525 | assert.same({1}, {system.utf8cwidth("a")}) | ||
526 | assert.same({1}, {system.utf8cwidth(ch1)}) | ||
527 | end) | ||
528 | |||
529 | it("handles double width characters", function() | ||
530 | assert.same({2}, {system.utf8cwidth(ch2)}) | ||
531 | assert.same({2}, {system.utf8cwidth(ch3)}) | ||
532 | assert.same({2}, {system.utf8cwidth(ch4)}) | ||
533 | end) | ||
534 | |||
535 | it("returns the width of the first character in the string", function() | ||
536 | assert.same({nil, 'Character width determination failed'}, {system.utf8cwidth("\a" .. ch1)}) -- bell character + EURO | ||
537 | assert.same({1}, {system.utf8cwidth(ch1 .. ch2)}) | ||
538 | assert.same({2}, {system.utf8cwidth(ch2 .. ch3 .. ch4)}) | ||
539 | end) | ||
540 | |||
541 | end) | ||
542 | |||
543 | |||
544 | |||
545 | describe("utf8swidth()", function() | ||
546 | |||
547 | local ch1 = string.char(226, 130, 172) -- "€" single | ||
548 | local ch2 = string.char(240, 159, 154, 128) -- "🚀" double | ||
549 | local ch3 = string.char(228, 189, 160) -- "你" double | ||
550 | local ch4 = string.char(229, 165, 189) -- "好" double | ||
551 | |||
552 | it("handles zero width characters", function() | ||
553 | assert.same({0}, {system.utf8swidth("")}) -- empty string returns 0-size | ||
554 | assert.same({nil, 'Character width determination failed'}, {system.utf8swidth("\a")}) -- bell character | ||
555 | assert.same({nil, 'Character width determination failed'}, {system.utf8swidth("\27")}) -- escape character | ||
556 | end) | ||
557 | |||
558 | it("handles multi-character UTF8 strings", function() | ||
559 | assert.same({15}, {system.utf8swidth("hello " .. ch1 .. ch2 .. " world")}) | ||
560 | assert.same({16}, {system.utf8swidth("hello " .. ch3 .. ch4 .. " world")}) | ||
561 | end) | ||
562 | |||
563 | end) | ||
564 | |||
565 | |||
566 | |||
567 | describe("termbackup() & termrestore()", function() | ||
568 | |||
569 | -- this is all Lua code, so testing one platform should be good enough | ||
570 | win_it("creates and restores a backup", function() | ||
571 | local backup = system.termbackup() | ||
572 | |||
573 | local old_cp = assert(system.getconsoleoutputcp()) | ||
574 | finally(function() | ||
575 | system.setconsoleoutputcp(old_cp) -- ensure we restore the original one | ||
576 | end) | ||
577 | |||
578 | -- get the console page... | ||
579 | local new_cp | ||
580 | if old_cp ~= system.CODEPAGE_UTF8 then | ||
581 | new_cp = system.CODEPAGE_UTF8 -- set to UTF8 | ||
582 | else | ||
583 | new_cp = 850 -- another common one | ||
584 | end | ||
585 | |||
586 | -- change the console page... | ||
587 | local success, err = system.setconsoleoutputcp(new_cp) | ||
588 | assert.is_nil(err) | ||
589 | assert.is_true(success) | ||
590 | -- ... and check it | ||
591 | local updated_cp = assert(system.getconsoleoutputcp()) | ||
592 | assert.equals(new_cp, updated_cp) | ||
593 | |||
594 | -- restore the console page | ||
595 | system.termrestore(backup) | ||
596 | local restored_cp = assert(system.getconsoleoutputcp()) | ||
597 | assert.equals(old_cp, restored_cp) | ||
598 | end) | ||
599 | |||
600 | |||
601 | it("termrestore() fails on bad input", function() | ||
602 | assert.has.error(function() | ||
603 | system.termrestore("invalid") | ||
604 | end, "arg #1 to termrestore, expected backup table, got string") | ||
605 | end) | ||
606 | |||
607 | end) | ||
608 | |||
609 | |||
610 | |||
611 | describe("termwrap()", function() | ||
612 | |||
613 | local old_backup | ||
614 | local old_restore | ||
615 | local result | ||
616 | |||
617 | setup(function() | ||
618 | old_backup = system.termbackup | ||
619 | old_restore = system.termrestore | ||
620 | system.termbackup = function() | ||
621 | table.insert(result,"backup") | ||
622 | end | ||
623 | |||
624 | system.termrestore = function() | ||
625 | table.insert(result,"restore") | ||
626 | end | ||
627 | end) | ||
628 | |||
629 | |||
630 | before_each(function() | ||
631 | result = {} | ||
632 | end) | ||
633 | |||
634 | |||
635 | teardown(function() | ||
636 | system.termbackup = old_backup | ||
637 | system.termrestore = old_restore | ||
638 | end) | ||
639 | |||
640 | |||
641 | |||
642 | it("calls both backup and restore", function() | ||
643 | system.termwrap(function() | ||
644 | table.insert(result,"wrapped") | ||
645 | end)() | ||
646 | |||
647 | assert.are.same({"backup", "wrapped", "restore"}, result) | ||
648 | end) | ||
649 | |||
650 | |||
651 | it("passes all args", function() | ||
652 | system.termwrap(function(...) | ||
653 | table.insert(result,{...}) | ||
654 | end)(1, 2, 3) | ||
655 | |||
656 | assert.are.same({"backup", {1,2,3}, "restore"}, result) | ||
657 | end) | ||
658 | |||
659 | |||
660 | it("returns all results", function() | ||
661 | local a, b, c = system.termwrap(function(...) | ||
662 | return 1, nil, 3 -- ensure nil is passed as well | ||
663 | end)() | ||
664 | |||
665 | assert.are.same({"backup", "restore"}, result) | ||
666 | assert.equals(1, a) | ||
667 | assert.is_nil(b) | ||
668 | assert.equals(3, c) | ||
669 | end) | ||
670 | |||
671 | end) | ||
672 | |||
673 | |||
674 | |||
675 | describe("autotermrestore()", function() | ||
676 | |||
677 | local old_backup | ||
678 | local old_restore | ||
679 | local result | ||
680 | |||
681 | |||
682 | before_each(function() | ||
683 | _G._TEST = true | ||
684 | |||
685 | package.loaded["system"] = nil | ||
686 | system = require("system") | ||
687 | |||
688 | old_backup = system.termbackup | ||
689 | old_restore = system.termrestore | ||
690 | system.termbackup = function(...) | ||
691 | table.insert(result,"backup") | ||
692 | return old_backup(...) | ||
693 | end | ||
694 | |||
695 | system.termrestore = function(...) | ||
696 | table.insert(result,"restore") | ||
697 | return old_restore(...) | ||
698 | end | ||
699 | |||
700 | result = {} | ||
701 | end) | ||
702 | |||
703 | |||
704 | after_each(function() | ||
705 | -- system.termbackup = old_backup | ||
706 | -- system.termrestore = old_restore | ||
707 | _G._TEST = false | ||
708 | |||
709 | package.loaded["system"] = nil | ||
710 | system = require("system") | ||
711 | end) | ||
712 | |||
713 | |||
714 | |||
715 | it("calls backup", function() | ||
716 | local ok, err = system.autotermrestore() | ||
717 | assert.is_nil(err) | ||
718 | assert.is_true(ok) | ||
719 | |||
720 | assert.are.same({"backup"}, result) | ||
721 | end) | ||
722 | |||
723 | |||
724 | it("returns an error on the second call", function() | ||
725 | local ok, err = system.autotermrestore() | ||
726 | assert.is_nil(err) | ||
727 | assert.is_true(ok) | ||
728 | |||
729 | local ok, err = system.autotermrestore() | ||
730 | assert.are.equal("global terminal backup was already set up", err) | ||
731 | assert.is_nil(ok) | ||
732 | end) | ||
733 | |||
734 | |||
735 | it("calls restore upon being garbage collected", function() | ||
736 | local ok, err = system.autotermrestore() | ||
737 | assert.is_nil(err) | ||
738 | assert.is_true(ok) | ||
739 | |||
740 | -- ensure tables from previous tests are GC'ed | ||
741 | collectgarbage() | ||
742 | collectgarbage() | ||
743 | -- clear references | ||
744 | result = {} | ||
745 | system._reset_global_backup() | ||
746 | collectgarbage() | ||
747 | collectgarbage() | ||
748 | |||
749 | assert.are.same({"restore"}, result) | ||
750 | end) | ||
751 | |||
752 | end) | ||
753 | |||
754 | |||
755 | |||
756 | describe("keyboard input", function() | ||
757 | |||
758 | local old_readkey = system._readkey | ||
759 | local current_buffer | ||
760 | local function setbuffer(str) | ||
761 | assert(type(str) == "string", "setbuffer() expects a string") | ||
762 | if str == "" then | ||
763 | current_buffer = nil | ||
764 | else | ||
765 | current_buffer = str | ||
766 | end | ||
767 | end | ||
768 | |||
769 | |||
770 | setup(function() | ||
771 | system._readkey = function() | ||
772 | if not current_buffer then | ||
773 | return nil | ||
774 | end | ||
775 | local ch = current_buffer:byte(1, 1) | ||
776 | if #current_buffer == 1 then | ||
777 | current_buffer = nil | ||
778 | else | ||
779 | current_buffer = current_buffer:sub(2, -1) | ||
780 | end | ||
781 | return ch | ||
782 | end | ||
783 | end) | ||
784 | |||
785 | |||
786 | teardown(function() | ||
787 | system._readkey = old_readkey | ||
788 | end) | ||
789 | |||
790 | |||
791 | |||
792 | describe("readkey()", function() | ||
793 | |||
794 | it("fails without a timeout", function() | ||
795 | assert.has.error(function() | ||
796 | system.readkey() | ||
797 | end, "arg #1 to readkey, expected timeout in seconds, got nil") | ||
798 | end) | ||
799 | |||
800 | |||
801 | it("reads a single byte for single-byte characters", function() | ||
802 | setbuffer("abc") | ||
803 | assert.equals(string.byte("a"), system.readkey(0)) | ||
804 | assert.equals(string.byte("b"), system.readkey(0)) | ||
805 | assert.equals(string.byte("c"), system.readkey(0)) | ||
806 | end) | ||
807 | |||
808 | |||
809 | it("reads a single byte for multi-byte chars", function() | ||
810 | setbuffer(string.char(226, 130, 172) .. -- "€" single | ||
811 | string.char(240, 159, 154, 128)) -- "🚀" double | ||
812 | assert.equals(226, system.readkey(0)) | ||
813 | assert.equals(130, system.readkey(0)) | ||
814 | assert.equals(172, system.readkey(0)) | ||
815 | assert.equals(240, system.readkey(0)) | ||
816 | assert.equals(159, system.readkey(0)) | ||
817 | assert.equals(154, system.readkey(0)) | ||
818 | assert.equals(128, system.readkey(0)) | ||
819 | end) | ||
820 | |||
821 | |||
822 | it("times out", function() | ||
823 | setbuffer("") | ||
824 | local timing = system.gettime() | ||
825 | assert.same({ nil, "timeout" }, { system.readkey(0.5) }) | ||
826 | |||
827 | timing = system.gettime() - timing | ||
828 | -- assert.is.near(0.5, timing, 0.1) -- this works in CI for Unix and Windows, but not MacOS (locally it passes) | ||
829 | assert.is.near(1, timing, 0.5) -- this also works for MacOS in CI | ||
830 | end) | ||
831 | |||
832 | end) | ||
833 | |||
834 | |||
835 | |||
836 | describe("readansi()", function() | ||
837 | |||
838 | it("fails without a timeout", function() | ||
839 | assert.has.error(function() | ||
840 | system.readansi() | ||
841 | end, "arg #1 to readansi, expected timeout in seconds, got nil") | ||
842 | end) | ||
843 | |||
844 | |||
845 | it("reads a single byte for single-byte characters", function() | ||
846 | setbuffer("abc") | ||
847 | assert.are.same({"a", "char"}, {system.readansi(0)}) | ||
848 | assert.are.same({"b", "char"}, {system.readansi(0)}) | ||
849 | assert.are.same({"c", "char"}, {system.readansi(0)}) | ||
850 | end) | ||
851 | |||
852 | |||
853 | it("reads a multi-byte characters one at a time", function() | ||
854 | setbuffer(string.char(226, 130, 172) .. -- "€" single | ||
855 | string.char(240, 159, 154, 128)) -- "🚀" double | ||
856 | assert.are.same({"€", "char"}, {system.readansi(0)}) | ||
857 | assert.are.same({"🚀", "char"}, {system.readansi(0)}) | ||
858 | end) | ||
859 | |||
860 | |||
861 | it("reads ANSI escape sequences, marked by '<esc>['", function() | ||
862 | setbuffer("\27[A\27[B\27[C\27[D") | ||
863 | assert.are.same({"\27[A", "ansi"}, {system.readansi(0)}) | ||
864 | assert.are.same({"\27[B", "ansi"}, {system.readansi(0)}) | ||
865 | assert.are.same({"\27[C", "ansi"}, {system.readansi(0)}) | ||
866 | assert.are.same({"\27[D", "ansi"}, {system.readansi(0)}) | ||
867 | end) | ||
868 | |||
869 | |||
870 | it("reads ANSI escape sequences, marked by '<esc>O'", function() | ||
871 | setbuffer("\27OA\27OB\27OC\27OD") | ||
872 | assert.are.same({"\27OA", "ansi"}, {system.readansi(0)}) | ||
873 | assert.are.same({"\27OB", "ansi"}, {system.readansi(0)}) | ||
874 | assert.are.same({"\27OC", "ansi"}, {system.readansi(0)}) | ||
875 | assert.are.same({"\27OD", "ansi"}, {system.readansi(0)}) | ||
876 | end) | ||
877 | |||
878 | |||
879 | it("returns a single <esc> character if no sequence is found", function() | ||
880 | setbuffer("\27\27[A") | ||
881 | assert.are.same({"\27", "char"}, {system.readansi(0)}) | ||
882 | assert.are.same({"\27[A", "ansi"}, {system.readansi(0)}) | ||
883 | end) | ||
884 | |||
885 | |||
886 | it("times out", function() | ||
887 | setbuffer("") | ||
888 | local timing = system.gettime() | ||
889 | assert.same({ nil, "timeout" }, { system.readansi(0.5) }) | ||
890 | |||
891 | timing = system.gettime() - timing | ||
892 | -- assert.is.near(0.5, timing, 0.1) -- this works in CI for Unix and Windows, but not MacOS (locally it passes) | ||
893 | assert.is.near(1, timing, 0.5) -- this also works for MacOS in CI | ||
894 | end) | ||
895 | |||
896 | end) | ||
897 | |||
898 | end) | ||
899 | |||
94 | end) | 900 | end) |
diff --git a/spec/05-bitflags_spec.lua b/spec/05-bitflags_spec.lua new file mode 100644 index 0000000..8eea27f --- /dev/null +++ b/spec/05-bitflags_spec.lua | |||
@@ -0,0 +1,114 @@ | |||
1 | describe("BitFlags library", function() | ||
2 | |||
3 | local sys = require("system") | ||
4 | |||
5 | it("creates new flag objects", function() | ||
6 | local bf = sys.bitflag(255) | ||
7 | assert.is_not_nil(bf) | ||
8 | assert.are.equal(255, bf:value()) | ||
9 | assert.is.userdata(bf) | ||
10 | end) | ||
11 | |||
12 | it("converts to a hex string", function() | ||
13 | local bf = sys.bitflag(255) | ||
14 | assert.are.equal("bitflags: 255", tostring(bf)) | ||
15 | end) | ||
16 | |||
17 | it("handles OR/ADD operations", function() | ||
18 | -- one at a time | ||
19 | local bf1 = sys.bitflag(1) -- b0001 | ||
20 | local bf2 = sys.bitflag(2) -- b0010 | ||
21 | local bf3 = bf1 + bf2 -- b0011 | ||
22 | assert.are.equal(3, bf3:value()) | ||
23 | -- multiple at once | ||
24 | local bf4 = sys.bitflag(4+8) -- b1100 | ||
25 | local bf5 = bf3 + bf4 -- b1111 | ||
26 | assert.are.equal(15, bf5:value()) | ||
27 | -- multiple that were already set | ||
28 | local bf6 = sys.bitflag(15) -- b1111 | ||
29 | local bf7 = sys.bitflag(8+2) -- b1010 | ||
30 | local bf8 = bf6 + bf7 -- b1111 | ||
31 | assert.are.equal(15, bf8:value()) | ||
32 | end) | ||
33 | |||
34 | it("handles AND-NOT/SUBSTRACT operations", function() | ||
35 | -- one at a time | ||
36 | local bf1 = sys.bitflag(3) -- b0011 | ||
37 | local bf2 = sys.bitflag(1) -- b0001 | ||
38 | local bf3 = bf1 - bf2 -- b0010 | ||
39 | assert.are.equal(2, bf3:value()) | ||
40 | -- multiple at once | ||
41 | local bf4 = sys.bitflag(15) -- b1111 | ||
42 | local bf5 = sys.bitflag(8+2) -- b1010 | ||
43 | local bf6 = bf4 - bf5 -- b0101 | ||
44 | assert.are.equal(5, bf6:value()) | ||
45 | -- multiple that were not set | ||
46 | local bf7 = sys.bitflag(3) -- b0011 | ||
47 | local bf8 = sys.bitflag(15) -- b1111 | ||
48 | local bf9 = bf7 - bf8 -- b0000 | ||
49 | assert.are.equal(0, bf9:value()) | ||
50 | end) | ||
51 | |||
52 | it("checks for equality", function() | ||
53 | local bf1 = sys.bitflag(4) | ||
54 | local bf2 = sys.bitflag(4) | ||
55 | local bf3 = sys.bitflag(5) | ||
56 | assert.is.True(bf1 == bf2) | ||
57 | assert.is.False(bf1 == bf3) | ||
58 | end) | ||
59 | |||
60 | it("indexes bits correctly", function() | ||
61 | local bf = sys.bitflag(4) -- b100 | ||
62 | assert.is_true(bf[2]) | ||
63 | assert.is_false(bf[1]) | ||
64 | end) | ||
65 | |||
66 | it("errors on reading invalid bit indexes", function() | ||
67 | local bf = sys.bitflag(4) | ||
68 | assert.has_error(function() return bf[-10] end, "index out of range") | ||
69 | assert.has_error(function() return bf[10000] end, "index out of range") | ||
70 | assert.has_no_error(function() return bf.not_a_number end) | ||
71 | end) | ||
72 | |||
73 | it("sets and clears bits correctly", function() | ||
74 | local bf = sys.bitflag(8) -- b1000 | ||
75 | bf[1] = true | ||
76 | assert.is_true(bf[1]) -- b1010 | ||
77 | assert.equals(10, bf:value()) | ||
78 | bf[1] = false | ||
79 | assert.is_false(bf[1]) -- b1000 | ||
80 | assert.equals(8, bf:value()) | ||
81 | end) | ||
82 | |||
83 | it("errors on setting invalid bit indexes", function() | ||
84 | local bf = sys.bitflag(0) | ||
85 | assert.has_error(function() bf[-10] = true end, "index out of range") | ||
86 | assert.has_error(function() bf[10000] = true end, "index out of range") | ||
87 | assert.has_error(function() bf.not_a_number = true end, "index must be a number") | ||
88 | end) | ||
89 | |||
90 | it("checks for a subset using 'has_all_of'", function() | ||
91 | local bf1 = sys.bitflag(3) -- b0011 | ||
92 | local bf2 = sys.bitflag(3) -- b0011 | ||
93 | local bf3 = sys.bitflag(15) -- b1111 | ||
94 | local bf0 = sys.bitflag(0) -- b0000 | ||
95 | assert.is_true(bf1:has_all_of(bf2)) -- equal | ||
96 | assert.is_true(bf3:has_all_of(bf1)) -- is a subset, and has more flags | ||
97 | assert.is_false(bf1:has_all_of(bf3)) -- not a subset, bf3 has more flags | ||
98 | assert.is_false(bf1:has_all_of(bf0)) -- bf0 is unset, always returns false | ||
99 | end) | ||
100 | |||
101 | it("checks for a subset using 'has_any_of'", function() | ||
102 | local bf1 = sys.bitflag(3) -- b0011 | ||
103 | local bf2 = sys.bitflag(3) -- b0011 | ||
104 | local bf3 = sys.bitflag(7) -- b0111 | ||
105 | local bf4 = sys.bitflag(8) -- b1000 | ||
106 | local bf0 = sys.bitflag(0) -- b0000 | ||
107 | assert.is_true(bf1:has_any_of(bf2)) -- equal | ||
108 | assert.is_true(bf3:has_any_of(bf1)) -- is a subset, and has more flags | ||
109 | assert.is_false(bf3:has_any_of(bf4)) -- no overlap in flags | ||
110 | assert.is_true(bf1:has_any_of(bf3)) -- not a subset, bf3 has more flags but still some overlap | ||
111 | assert.is_false(bf1:has_all_of(bf0)) -- bf0 is unset, always returns false | ||
112 | end) | ||
113 | |||
114 | end) | ||