Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize vterm: adopt data types and add annotations. Fix tests #547

Merged

Conversation

penguinolog
Copy link
Collaborator

  • TermModes - make dataclass as it's used

  • TermScroller - deprecate and use deque instead
      (faster both side access, automatic size limit control)

  • utf8_buffer - use bytearray as mutual storage for bytes
      (no re-allocation if no crazy size changes)

  • CSI_COMMANDS - use namedtuple types for better type management

  • use native string escape sequences instead of hex encoded one
      (easier to read and understand, internally the same)

  • not elif after return (follow actual static checkers rules)

  • _target_encoding = 'ascii' as default (no re-cast)

  • add extra verbose version of tests for scrolling region

Fix #544

Checklist
  • I've ensured that similar functionality has not already been implemented
  • I've ensured that similar functionality has not earlier been proposed and declined
  • I've branched off the master or python-dual-support branch
  • I've merged fresh upstream into my branch recently
  • I've ran tox successfully in local environment

@@ -238,6 +243,58 @@ def test_erase_display(self):
self.write(r'98765\e[8D\e[1Jx')
self.expect(' x5a98765')

def test_scrolling_region_simple_incremental(self):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this extra verbose tests maybe removed or replace "short" versions.
@wardi which approach is better?

@@ -74,7 +74,7 @@ def detect_encoding() -> str:
else:
assert 0, "It worked!"

_target_encoding = None
_target_encoding = 'ascii'
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's set implicit, make explicit


KEY_TRANSLATIONS = {
'enter': chr(13),
'enter': "\r",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more human-readable with the same internals

@@ -98,7 +101,19 @@
'f5': f"{ESC}[15~",
}

CSI_COMMANDS = {

class CSIAlias(typing.NamedTuple):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

named tuples less complex to deal with

self.utf8_eat_bytes = None
self.utf8_buffer = b''
self.utf8_eat_bytes: int | None = None
self.utf8_buffer = bytearray()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bytearray changed in-place
bytes is immutable and new object on any change

if self.term_modes.lfnl and key == "\x0d":
key += "\x0a"

key = key.encode(self.encoding, 'ignore')
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not change type of variable just for 1 line, make in-place during call

@penguinolog penguinolog changed the title ptimize vterm: adopt data types and add annotations. Fix tests Optimize vterm: adopt data types and add annotations. Fix tests Apr 30, 2023
@penguinolog penguinolog force-pushed the vterm_annotations_optimizations branch 2 times, most recently from b980cec to f40c05e Compare May 1, 2023 12:15
@@ -140,7 +145,7 @@ def expect(
else:
desc += '\n'
desc += f'Expected:\n{what!r}\nGot:\n{got!r}'
self.assertEqual(got, what, desc)
self.assertEqual(what, got, desc)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most unittest parsers expect, that expected value is first

@@ -1690,16 +1755,14 @@ def keypress(self, size: tuple[int, int], key: str) -> str | None:
key = chr(ord(key[-1]) - ord('A') + 1)
else:
if self.term_modes.keys_decckm and key in KEY_TRANSLATIONS_DECCKM:
key = KEY_TRANSLATIONS_DECCKM.get(key)
key = KEY_TRANSLATIONS_DECCKM[key]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already know that it in KEY_TRANSLATIONS_DECCKM

@@ -60,6 +63,7 @@ def read(self, size: int) -> bytes:

def write(self, data: bytes) -> None:
os.write(self.writer, data)
sleep(0.025)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fond of this. Never really fixes flaky tests — merely shifts flake probability.

Did you try os.fdopen(self.writer).flush() instead ?

Or better yet, disabling buffering completely with `unbuffered_pipe = os.fdopen(self.writer, 'wb', 0)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can try unbuffered pipe, but stdout will be still buffered

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

os.fdopen fails in 10 from 10 runs with Bad file descriptor
os.fsync also disagree to deal with pipe

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That can be disabled too.

In a unit test though, why is there a dependency onto sys.stdout at all? Can't it allocate a private PTY ?

... Even that seems redundant, as .read() passes the output of Terminal.render() to asserts directly:

def read(self, raw: bool = False, focus: bool = False) -> bytes | list[list[bytes, typing.Any, typing.Any]]:
self.term.wait_and_feed()
rendered = self.term.render(self.termsize, focus=focus)
if raw:
is_empty = lambda c: c == (None, None, b' ')
content = list(rendered.content())
lines = (tuple(dropwhile(is_empty, reversed(line))) for line in content)
return [list(reversed(line)) for line in lines if line]
else:
content = rendered.text
lines = (line.rstrip() for line in content)
return b'\n'.join(lines).rstrip()

I don't see any need to use sys.stdout at all.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Render spawn process, which execute pty.fork().
Data read from fork stdout and rendered.

Copy link
Collaborator

@ulidtko ulidtko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming of added tests is incomprehensible.

  • test_scrolling_region_simple_incremental
  • test_cursor_scrolling_region_incremental
  • test_scrolling_region_simple_with_focus_incremental
  • test_cursor_scrolling_region_with_focus_incremental

When seen side-by-side — one can sort-of-make-out-maybe-guesstimate how they differ.

When seen pages apart — no chance 😕

@penguinolog
Copy link
Collaborator Author

The naming of added tests is incomprehensible.

  • test_scrolling_region_simple_incremental
  • test_cursor_scrolling_region_incremental
  • test_scrolling_region_simple_with_focus_incremental
  • test_cursor_scrolling_region_with_focus_incremental

When seen side-by-side — one can sort-of-make-out-maybe-guesstimate how they differ.

When seen pages apart — no chance 😕

Tests was added only for debug purposes and now it's possible to decide: keep or drop

@penguinolog
Copy link
Collaborator Author

penguinolog commented May 1, 2023 via email

@ulidtko
Copy link
Collaborator

ulidtko commented May 2, 2023

Terminal.spawn call pty.fork, which accept 0 arguments.

Yes, that's the Terminal's internal pty. This is fine and expected.

What I'm talking about is DummyCommand in test_vterm touching sys.stdout. It a dummy "command" running in the terminal and doing IO to its PTY just for tests, why does it need sys.stdout?

@penguinolog
Copy link
Collaborator Author

Terminal.spawn call pty.fork, which accept 0 arguments.

Yes, that's the Terminal's internal pty. This is fine and expected.

What I'm talking about is DummyCommand in test_vterm touching sys.stdout. It a dummy "command" running in the terminal and doing IO to its PTY just for tests, why does it need sys.stdout?

DummyCommand sys.stdout output points to PTY, not to system real stdout

* `TermModes` - make `dataclass` as it's used
* `TermScroller` - deprecate and use `deque` instead
  (faster both side access, automatic size limit control)
* `utf8_buffer` - use `bytearray` as mutual storage for bytes
  (no re-allocation if no crazy size changes)
* `CSI_COMMANDS` - use `namedtuple` types for better type management
* use native string escape sequences instead of hex encoded one
  (easier to read and understand, internally the same)
* not `elif` after `return` (follow actual static checkers rules)

* `_target_encoding = 'ascii'` as default (no re-cast)
@penguinolog penguinolog force-pushed the vterm_annotations_optimizations branch from f40c05e to f00e7ba Compare May 5, 2023 08:52
@penguinolog
Copy link
Collaborator Author

Removed test changes: at this time I have lack time for refactoring it

@penguinolog penguinolog merged commit 2bd0ead into urwid:master Jun 12, 2023
5 checks passed
@penguinolog penguinolog deleted the vterm_annotations_optimizations branch June 12, 2023 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Stable tests failure on CI for test_vterm
3 participants