diff options
| author | Thijs Schreijer <thijs@thijsschreijer.nl> | 2024-06-20 22:43:06 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-06-20 22:43:06 +0200 |
| commit | c1a64c1b75f97cef97965b3bd9a941564a6270bd (patch) | |
| tree | b9a92dff6462abd5859c3c76f19748fad5d6c025 /spec | |
| parent | 47c24eed0191f8f72646be63dee94ac2b35eb062 (diff) | |
| parent | b87e6d6d762ee823e81dd7a8984f330eb4018fd8 (diff) | |
| download | luasystem-c1a64c1b75f97cef97965b3bd9a941564a6270bd.tar.gz luasystem-c1a64c1b75f97cef97965b3bd9a941564a6270bd.tar.bz2 luasystem-c1a64c1b75f97cef97965b3bd9a941564a6270bd.zip | |
Merge pull request #21 from lunarmodules/terminal
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) | ||
