aboutsummaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/04-term_spec.lua808
-rw-r--r--spec/05-bitflags_spec.lua114
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
2local system = require("system") 1local system = require("system")
3require("spec.helpers") 2require("spec.helpers")
4 3
5describe("Terminal:", function() 4describe("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
94end) 900end)
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 @@
1describe("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
114end)