shan-shaji / ansi-escapes-dart

ANSI escape codes for manipulating the terminal
https://pub.dev/packages/ansi_escapes
MIT License
4 stars 2 forks source link

Add Unit tests #1

Open shan-shaji opened 3 years ago

rickgladwin commented 2 years ago

I've been giving this some thought – how to confirm the result of the actions performed by the methods?

Two options occurred to me:

  1. using the terminal "scrollback" buffer i. print a custom marker before/after every test ii. perform the action iii. parse the content of the terminal "scrollback" buffer directly, from the last marker forward iv. confirm that it matches expected "known good" content
  2. using the clipboard and a target text file i. clear the terminal window between tests ii. (print any necessary characters then) perform the action iii. copy all terminal content to the clipboard (e.g. maybe using some variation on e]52 or \033]52 (OSC52)?) iv. copy the clipboard to a temporary file (e.g. using pbpaste | cat >> test_target.txt) v. parse the content of the temporary file vi. confirm that the file content matches expected "known good" content

Option 1 requires either API or file access to the terminal's scrollback buffer. I checked iTerm2 briefly, and couldn't find any such access. Plus I suspect option 1 would be slightly different for every terminal out there.

Option 2 requires extra steps and would be slower, and would require a dependency on the 'dart:io' core package, but dependencies on core modules (without mocking) are acceptable in unit testing. It's safe to assume the core modules are all working correctly.

When I've performed this type of testing before on a terminal app, I've used a (readable) output buffer in the app itself that is always automatically printed to the terminal unchanged, so it's assumed that if the output buffer matches a "known good" string, the app's output will pass the test.

That said, I think option 2 would be more universal and easier to implement, especially if we wanted to support multiple terminals and operating systems.

Can't promise I'll have time to implement this any time soon, but wanted to put some notes down here.

rickgladwin commented 2 years ago

Actually I'm working on a third option for a different project that may apply to this one:

    final process = await Process.start('dart', [mainPath]);
    final lineStream = process.stdout;

Run an app in a Process and capture Process.stdout as a stream (and apply .transform() as needed). By default this will be a Stream of UTF-8 codes (I think?) and we can read this and match it to a known good string.

In my current project I've got it working for ASCII characters but it's choking on multi-byte unicode characters [Edit: multi-byte character codes wasn't the issue. See comment below]. I suspect it's just a matter of concatenation and interpretation though.

rickgladwin commented 2 years ago

Solved it. The issue wasn't multi-byte unicode characters but the representation of the command codes themselves that came out of the process.stdout stream.

There's a way of capturing the stream's codeUnits, in a StringBuffer, splitting the string, and filtering out the command codes. That leaves us with a string that represents the result of applying the command codes, so it can be compared to a Known Good string.

It's fiddly, but it allows us to write true acceptance tests based on what we expect to see in the terminal emulator, in a way that could be modified for different emulators.

I've got a quick implementation of it here: https://github.com/rickgladwin/dart_sudoku/blob/master/test/lib/src/main_test.dart If I have time to contribute tests to ansi-escapes-dart, that's likely where I'll start.

shan-shaji commented 2 years ago

@rickgladwin Sorry for replying late. Wow, This is awesome I will take a look into this.

rickgladwin commented 1 year ago

Started working on this in a fork. It's odd – the solution I used in the other repo, writing strings/control characters to a StringBuffer in sequence and then printing the buffer, isn't showing me what I expect in this repo. [EDIT: it's because, in the other repo, only the sequence of the printed characters is being tested, which corresponds to their position in the app's output 1 to 1, therefore the tests pass. That's not the case here.]

This might require one of those first options I explored after all, unless there's a way to capture the result of a character sequence from stdout. Let me know if you've got any insight.