Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-terminado for 
openSUSE:Factory checked in at 2022-10-01 17:43:43
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-terminado (Old)
 and      /work/SRC/openSUSE:Factory/.python-terminado.new.2275 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-terminado"

Sat Oct  1 17:43:43 2022 rev:17 rq:1007303 version:0.16.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-terminado/python-terminado.changes        
2022-07-13 13:46:01.514086059 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-terminado.new.2275/python-terminado.changes  
    2022-10-01 17:44:09.229778957 +0200
@@ -1,0 +2,11 @@
+Fri Sep 30 15:16:04 UTC 2022 - Arun Persaud <a...@gmx.de>
+
+- update to version 0.16.0:
+  * Bugs fixed
+    + Fix issue where large stdin writes can cause Tornado to hang
+      #189 (@KoopaKing)
+  * Maintenance and upkeep improvements
+    + Switch to using hatch version #186 (@blink1073)
+    + Fix flake8 v5 compat #179 (@blink1073)
+
+-------------------------------------------------------------------

Old:
----
  terminado-0.15.0.tar.gz

New:
----
  terminado-0.16.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-terminado.spec ++++++
--- /var/tmp/diff_new_pack.f0nljH/_old  2022-10-01 17:44:12.057784100 +0200
+++ /var/tmp/diff_new_pack.f0nljH/_new  2022-10-01 17:44:12.061784107 +0200
@@ -16,11 +16,8 @@
 #
 
 
-%{?!python_module:%define python_module() python-%{**} python3-%{**}}
-# Disable tests until random testing race condition fixed, see: 
https://github.com/jupyter/terminado/issues/21
-%bcond_with     tests
 Name:           python-terminado
-Version:        0.15.0
+Version:        0.16.0
 Release:        0
 Summary:        Terminals served to termjs using Tornado websockets
 License:        BSD-2-Clause
@@ -38,8 +35,8 @@
 Requires:       python-tornado >= 4
 BuildArch:      noarch
 # SECTION test requirements
-BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module pytest-timeout}
+BuildRequires:  %{python_module pytest}
 # /SECTION
 %python_subpackages
 
@@ -53,6 +50,7 @@
 
 %prep
 %setup -q -n terminado-%{version}
+sed -i '/addopts/ s/--durations 10 --color=yes//' pyproject.toml
 
 %build
 %pyproject_wheel

++++++ terminado-0.15.0.tar.gz -> terminado-0.16.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/.github/workflows/check-release.yml 
new/terminado-0.16.0/.github/workflows/check-release.yml
--- old/terminado-0.15.0/.github/workflows/check-release.yml    2020-02-02 
01:00:00.000000000 +0100
+++ new/terminado-0.16.0/.github/workflows/check-release.yml    2020-02-02 
01:00:00.000000000 +0100
@@ -11,10 +11,6 @@
 jobs:
   check_release:
     runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        group: [check_release, link_check]
-      fail-fast: false
     steps:
       - uses: actions/checkout@v2
       - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
@@ -22,10 +18,6 @@
         run: |
           pip install -e .
       - name: Check Release
-        if: ${{ matrix.group == 'check_release' }}
         uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v1
         with:
           token: ${{ secrets.GITHUB_TOKEN }}
-      - name: Run Link Check
-        if: ${{ matrix.group == 'link_check' }}
-        uses: jupyter-server/jupyter_releaser/.github/actions/check-links@v1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/.github/workflows/test.yml 
new/terminado-0.16.0/.github/workflows/test.yml
--- old/terminado-0.15.0/.github/workflows/test.yml     2020-02-02 
01:00:00.000000000 +0100
+++ new/terminado-0.16.0/.github/workflows/test.yml     2020-02-02 
01:00:00.000000000 +0100
@@ -94,14 +94,30 @@
       - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
       - uses: jupyterlab/maintainer-tools/.github/actions/test-sdist@v1
 
+  check-links:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+      - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1
+
+  jupyter_server_terminals:
+    runs-on: ubuntu-latest
+    timeout-minutes: 10
+    steps:
+      - uses: actions/checkout@v2
+      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+      - uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1
+        with:
+          package_name: jupyter_server_terminals
+
   # Run "pre-commit run --all-files --hook-stage=manual"
   pre-commit:
     name: Run pre-commit hook
     runs-on: ubuntu-latest
     timeout-minutes: 5
     steps:
-      - name: Checkout
-        uses: actions/checkout@v2
+      - uses: actions/checkout@v2
       - name: Set up Python
         uses: actions/setup-python@v3
       - name: Run pre-commit
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/.pre-commit-config.yaml 
new/terminado-0.16.0/.pre-commit-config.yaml
--- old/terminado-0.15.0/.pre-commit-config.yaml        2020-02-02 
01:00:00.000000000 +0100
+++ new/terminado-0.16.0/.pre-commit-config.yaml        2020-02-02 
01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.2.0
+    rev: v4.3.0
     hooks:
       - id: end-of-file-fixer
       - id: check-case-conflict
@@ -16,7 +16,7 @@
       - id: trailing-whitespace
 
   - repo: https://github.com/psf/black
-    rev: 22.3.0
+    rev: 22.8.0
     hooks:
       - id: black
         args: ["--line-length", "100"]
@@ -29,43 +29,39 @@
         args: [--profile=black]
 
   - repo: https://github.com/pre-commit/mirrors-prettier
-    rev: v2.6.2
+    rev: v3.0.0-alpha.0
     hooks:
       - id: prettier
 
   - repo: https://github.com/asottile/pyupgrade
-    rev: v2.32.1
+    rev: v2.38.2
     hooks:
       - id: pyupgrade
         args: [--py37-plus]
 
   - repo: https://github.com/PyCQA/doc8
-    rev: 0.11.1
+    rev: v1.0.0
     hooks:
       - id: doc8
         args: [--max-line-length=200]
         stages: [manual]
 
   - repo: https://github.com/pycqa/flake8
-    rev: 4.0.1
+    rev: 5.0.4
     hooks:
       - id: flake8
         additional_dependencies:
-          [
-            "flake8-bugbear==20.1.4",
-            "flake8-logging-format==0.6.0",
-            "flake8-implicit-str-concat==0.2.0",
-          ]
+          ["flake8-bugbear==22.6.22", "flake8-implicit-str-concat==0.2.0"]
         stages: [manual]
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: "v0.950"
+    rev: "v0.971"
     hooks:
       - id: mypy
         stages: [manual]
 
   - repo: https://github.com/sirosen/check-jsonschema
-    rev: 0.14.3
+    rev: 0.18.3
     hooks:
       - id: check-jsonschema
         name: "Check GitHub Workflows"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/CHANGELOG.md 
new/terminado-0.16.0/CHANGELOG.md
--- old/terminado-0.15.0/CHANGELOG.md   2020-02-02 01:00:00.000000000 +0100
+++ new/terminado-0.16.0/CHANGELOG.md   2020-02-02 01:00:00.000000000 +0100
@@ -2,12 +2,31 @@
 
 <!-- <START NEW CHANGELOG ENTRY> -->
 
-## 0.15.0
+## 0.16.0
 
-No merged PRs
+([Full 
Changelog](https://github.com/jupyter/terminado/compare/v0.15.0...7210e82a94596d7d8a00577169c09198efbe4633))
+
+### Bugs fixed
+
+- Fix issue where large stdin writes can cause Tornado to hang 
[#189](https://github.com/jupyter/terminado/pull/189) 
([@KoopaKing](https://github.com/KoopaKing))
+
+### Maintenance and upkeep improvements
+
+- Switch to using hatch version 
[#186](https://github.com/jupyter/terminado/pull/186) 
([@blink1073](https://github.com/blink1073))
+- Fix flake8 v5 compat [#179](https://github.com/jupyter/terminado/pull/179) 
([@blink1073](https://github.com/blink1073))
+
+### Contributors to this release
+
+([GitHub contributors page for this 
release](https://github.com/jupyter/terminado/graphs/contributors?from=2022-05-16&to=2022-09-29&type=c))
+
+[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fterminado+involves%3Ablink1073+updated%3A2022-05-16..2022-09-29&type=Issues)
 | 
[@KoopaKing](https://github.com/search?q=repo%3Ajupyter%2Fterminado+involves%3AKoopaKing+updated%3A2022-05-16..2022-09-29&type=Issues)
 | 
[@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fterminado+involves%3Apre-commit-ci+updated%3A2022-05-16..2022-09-29&type=Issues)
 
 <!-- <END NEW CHANGELOG ENTRY> -->
 
+## 0.15.0
+
+No merged PRs
+
 ## 0.13.3
 
 ([Full 
Changelog](https://github.com/jupyter/terminado/compare/v0.13.2...2cccad61af7e7ec88e4db769664d8814985179d4))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/PKG-INFO 
new/terminado-0.16.0/PKG-INFO
--- old/terminado-0.15.0/PKG-INFO       2020-02-02 01:00:00.000000000 +0100
+++ new/terminado-0.16.0/PKG-INFO       2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: terminado
-Version: 0.15.0
+Version: 0.16.0
 Summary: Tornado websocket backend for the Xterm.js Javascript terminal 
emulator library.
 Project-URL: Homepage, https://github.com/jupyter/terminado
 Author-email: Jupyter Development Team <jupy...@googlegroups.com>
@@ -31,6 +31,7 @@
         #  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
OR TORT
         #  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
USE OF THIS
         #  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+License-File: LICENSE
 Classifier: Environment :: Web Environment
 Classifier: License :: OSI Approved :: BSD License
 Classifier: Programming Language :: Python :: 3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/RELEASE.md 
new/terminado-0.16.0/RELEASE.md
--- old/terminado-0.15.0/RELEASE.md     2020-02-02 01:00:00.000000000 +0100
+++ new/terminado-0.16.0/RELEASE.md     2020-02-02 01:00:00.000000000 +0100
@@ -4,14 +4,13 @@
 
 ```
 git clean -dffx
-python setup.py sdist
-python setup.py bdist_wheel
-export script_version=`python setup.py --version 2>/dev/null`
+pip install pipx
+pipx run build
+export script_version=`pipx run hatch version 2>/dev/null`
 git commit -a -m "Release $script_version"
 git tag $script_version
 git push --all
 git push --tags
-pip install twine
-twine check dist/*
-twine upload dist/*
+pipx run twine check dist/*
+pipx run twine upload dist/*
 ```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/demos/custom_exec.py 
new/terminado-0.16.0/demos/custom_exec.py
--- old/terminado-0.15.0/demos/custom_exec.py   1970-01-01 01:00:00.000000000 
+0100
+++ new/terminado-0.16.0/demos/custom_exec.py   2020-02-02 01:00:00.000000000 
+0100
@@ -0,0 +1,43 @@
+"""Using a custom thread pool for subprocess writes.
+"""
+from concurrent import futures
+
+import tornado.web
+
+# This demo requires tornado_xstatic and XStatic-term.js
+import tornado_xstatic
+from common_demo_stuff import STATIC_DIR, TEMPLATE_DIR, run_and_show_browser
+
+from terminado import SingleTermManager, TermSocket
+
+
+class TerminalPageHandler(tornado.web.RequestHandler):
+    def get(self):
+        return self.render(
+            "termpage.html",
+            static=self.static_url,
+            xstatic=self.application.settings["xstatic_url"],
+            ws_url_path="/websocket",
+        )
+
+
+def main(argv):
+    with futures.ThreadPoolExecutor(max_workers=2) as custom_exec:
+        term_manager = SingleTermManager(shell_command=["bash"], 
blocking_io_executor=custom_exec)
+        handlers = [
+            (r"/websocket", TermSocket, {"term_manager": term_manager}),
+            (r"/", TerminalPageHandler),
+            (r"/xstatic/(.*)", tornado_xstatic.XStaticFileHandler, 
{"allowed_modules": ["termjs"]}),
+        ]
+        app = tornado.web.Application(
+            handlers,
+            static_path=STATIC_DIR,
+            template_path=TEMPLATE_DIR,
+            xstatic_url=tornado_xstatic.url_maker("/xstatic/"),
+        )
+        app.listen(8765, "localhost")
+        run_and_show_browser("http://localhost:8765/";, term_manager)
+
+
+if __name__ == "__main__":
+    main([])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/demos/templates/termpage.html 
new/terminado-0.16.0/demos/templates/termpage.html
--- old/terminado-0.15.0/demos/templates/termpage.html  2020-02-02 
01:00:00.000000000 +0100
+++ new/terminado-0.16.0/demos/templates/termpage.html  2020-02-02 
01:00:00.000000000 +0100
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
 <head>
   <meta charset="UTF-8" />
   <title>pyxterm</title>
@@ -40,11 +40,11 @@
       function calculate_size(element) {
         var rows = Math.max(
           2,
-          Math.floor(element.innerHeight / termRowHeight) - 1
+          Math.floor(element.innerHeight / termRowHeight) - 1,
         );
         var cols = Math.max(
           3,
-          Math.floor(element.innerWidth / termColWidth) - 1
+          Math.floor(element.innerWidth / termColWidth) - 1,
         );
         console.log(
           "resize:",
@@ -53,7 +53,7 @@
           element.innerHeight,
           element.innerWidth,
           rows,
-          cols
+          cols,
         );
         return { rows: rows, cols: cols };
       }
@@ -71,7 +71,7 @@
             geom.cols,
             window.innerHeight,
             window.innerWidth,
-          ])
+          ]),
         );
       };
     };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/demos/templates/uimod.html 
new/terminado-0.16.0/demos/templates/uimod.html
--- old/terminado-0.15.0/demos/templates/uimod.html     2020-02-02 
01:00:00.000000000 +0100
+++ new/terminado-0.16.0/demos/templates/uimod.html     2020-02-02 
01:00:00.000000000 +0100
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
 <head>
   <meta charset="UTF-8" />
   <title>Terminado UIModule demo</title>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/pyproject.toml 
new/terminado-0.16.0/pyproject.toml
--- old/terminado-0.15.0/pyproject.toml 2020-02-02 01:00:00.000000000 +0100
+++ new/terminado-0.16.0/pyproject.toml 2020-02-02 01:00:00.000000000 +0100
@@ -1,10 +1,10 @@
 [build-system]
-requires = ["hatchling>=0.25"]
+requires = ["hatchling>=1.5"]
 build-backend = "hatchling.build"
 
 [project]
 name = "terminado"
-version = "0.15.0"
+dynamic = ["version"]
 license = { file = "LICENSE" }
 description = "Tornado websocket backend for the Xterm.js Javascript terminal 
emulator library."
 classifiers = [ "Environment :: Web Environment", "License :: OSI Approved :: 
BSD License", "Programming Language :: Python :: 3", "Topic :: Terminals :: 
Terminal Emulators/X Terminals",]
@@ -28,22 +28,8 @@
 [tool.jupyter-releaser]
 skip = ["check-links"]
 
-[tool.tbump.version]
-current = "0.15.0"
-regex = '''
-  (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
-  ((?P<channel>a|b|rc|.dev)(?P<release>\d+))?
-'''
-
-[tool.tbump.git]
-message_template = "Bump to {new_version}"
-tag_template = "v{new_version}"
-
-[[tool.tbump.file]]
-src = "terminado/__init__.py"
-
-[[tool.tbump.file]]
-src = "pyproject.toml"
+[tool.hatch.version]
+path = "terminado/__init__.py"
 
 [tool.pytest.ini_options]
 addopts = "-raXs --durations 10 --color=yes --doctest-modules"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/terminado/__init__.py 
new/terminado-0.16.0/terminado/__init__.py
--- old/terminado-0.15.0/terminado/__init__.py  2020-02-02 01:00:00.000000000 
+0100
+++ new/terminado-0.16.0/terminado/__init__.py  2020-02-02 01:00:00.000000000 
+0100
@@ -10,4 +10,4 @@
 from .management import UniqueTermManager  # noqa
 from .websocket import TermSocket  # noqa
 
-__version__ = "0.15.0"
+__version__ = "0.16.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/terminado/_static/terminado.js 
new/terminado-0.16.0/terminado/_static/terminado.js
--- old/terminado-0.15.0/terminado/_static/terminado.js 2020-02-02 
01:00:00.000000000 +0100
+++ new/terminado-0.16.0/terminado/_static/terminado.js 2020-02-02 
01:00:00.000000000 +0100
@@ -18,7 +18,7 @@
         size.cols,
         window.innerHeight,
         window.innerWidth,
-      ])
+      ]),
     );
     term.on("data", function (data) {
       ws.send(JSON.stringify(["stdin", data]));
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/terminado/management.py 
new/terminado-0.16.0/terminado/management.py
--- old/terminado-0.15.0/terminado/management.py        2020-02-02 
01:00:00.000000000 +0100
+++ new/terminado-0.16.0/terminado/management.py        2020-02-02 
01:00:00.000000000 +0100
@@ -14,6 +14,7 @@
 import signal
 import warnings
 from collections import deque
+from concurrent import futures
 
 try:
     from ptyprocess import PtyProcessUnicode
@@ -153,7 +154,13 @@
     """Base class for a terminal manager."""
 
     def __init__(
-        self, shell_command, server_url="", term_settings=None, 
extra_env=None, ioloop=None
+        self,
+        shell_command,
+        server_url="",
+        term_settings=None,
+        extra_env=None,
+        ioloop=None,
+        blocking_io_executor=None,
     ):
         self.shell_command = shell_command
         self.server_url = server_url
@@ -163,6 +170,13 @@
 
         self.ptys_by_fd = {}
 
+        if blocking_io_executor is None:
+            self._blocking_io_executor_is_external = False
+            self.blocking_io_executor = 
futures.ThreadPoolExecutor(max_workers=1)
+        else:
+            self._blocking_io_executor_is_external = True
+            self.blocking_io_executor = blocking_io_executor
+
         if ioloop is not None:
             warnings.warn(
                 f"Setting {self.__class__.__name__}.ioloop is deprecated and 
ignored",
@@ -259,6 +273,8 @@
 
     async def shutdown(self):
         await self.kill_all()
+        if not self._blocking_io_executor_is_external:
+            self.blocking_io_executor.shutdown(wait=False, cancel_futures=True)
 
     async def kill_all(self):
         futures = []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/terminado/tests/basic_test.py 
new/terminado-0.16.0/terminado/tests/basic_test.py
--- old/terminado-0.15.0/terminado/tests/basic_test.py  2020-02-02 
01:00:00.000000000 +0100
+++ new/terminado-0.16.0/terminado/tests/basic_test.py  2020-02-02 
01:00:00.000000000 +0100
@@ -1,287 +1,330 @@
-# basic_tests.py -- Basic unit tests for Terminado
-
-# Copyright (c) Jupyter Development Team
-# Copyright (c) 2014, Ramalingam Saravanan <sar...@sarava.net>
-# Distributed under the terms of the Simplified BSD License.
-
-
-import asyncio
-import datetime
-import json
-import os
-import re
-
-# We must set the policy for python >=3.8, see 
https://www.tornadoweb.org/en/stable/#installation
-# Snippet from 
https://github.com/tornadoweb/tornado/issues/2608#issuecomment-619524992
-import sys
-import unittest
-from sys import platform
-
-import pytest
-import tornado
-import tornado.httpserver
-import tornado.testing
-from tornado.ioloop import IOLoop
-
-from terminado import NamedTermManager, SingleTermManager, TermSocket, 
UniqueTermManager
-
-if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and 
sys.platform.startswith("win"):
-    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
-
-#
-# The timeout we use to assume no more messages are coming
-# from the sever.
-#
-DONE_TIMEOUT = 1.0
-os.environ["ASYNC_TEST_TIMEOUT"] = "20"  # Global test case timeout
-
-MAX_TERMS = 3  # Testing thresholds
-
-
-class TestTermClient:
-    """Test connection to a terminal manager"""
-
-    __test__ = False
-
-    def __init__(self, websocket):
-        self.ws = websocket
-        self.pending_read = None
-
-    async def read_msg(self):
-
-        # Because the Tornado Websocket client has no way to cancel
-        # a pending read, we have to keep track of them...
-        if self.pending_read is None:
-            self.pending_read = self.ws.read_message()
-
-        response = await self.pending_read
-        self.pending_read = None
-        if response:
-            response = json.loads(response)
-        return response
-
-    async def read_all_msg(self, timeout=DONE_TIMEOUT):
-        """Read messages until read times out"""
-        msglist: list = []
-        delta = datetime.timedelta(seconds=timeout)
-        while True:
-            try:
-                mf = self.read_msg()
-                msg = await tornado.gen.with_timeout(delta, mf)
-            except tornado.gen.TimeoutError:
-                return msglist
-
-            msglist.append(msg)
-
-    async def write_msg(self, msg):
-        await self.ws.write_message(json.dumps(msg))
-
-    async def read_stdout(self, timeout=DONE_TIMEOUT):
-        """Read standard output until timeout read reached,
-        return stdout and any non-stdout msgs received."""
-        msglist = await self.read_all_msg(timeout)
-        stdout = "".join([msg[1] for msg in msglist if msg[0] == "stdout"])
-        othermsg = [msg for msg in msglist if msg[0] != "stdout"]
-        return (stdout, othermsg)
-
-    async def write_stdin(self, data):
-        """Write to terminal stdin"""
-        await self.write_msg(["stdin", data])
-
-    async def get_pid(self):
-        """Get process ID of terminal shell process"""
-        await self.read_stdout()  # Clear out any pending
-        await self.write_stdin("echo $$\r")
-        (stdout, extra) = await self.read_stdout()
-        if os.name == "nt":
-            match = re.search(r"echo \$\$\\.*?\\r\\n(\d+)", repr(stdout))
-            assert match is not None
-            pid = int(match.groups()[0])
-        else:
-            pid = int(stdout.split("\n")[1])
-        return pid
-
-    def close(self):
-        self.ws.close()
-
-
-class TermTestCase(tornado.testing.AsyncHTTPTestCase):
-
-    # Factory for TestTermClient, because it has to be async
-    # See:  https://github.com/tornadoweb/tornado/issues/1161
-    async def get_term_client(self, path):
-        port = self.get_http_port()
-        url = "ws://127.0.0.1:%d%s" % (port, path)
-        request = tornado.httpclient.HTTPRequest(
-            url, headers={"Origin": "http://127.0.0.1:%d"; % port}
-        )
-
-        ws = await tornado.websocket.websocket_connect(request)
-        return TestTermClient(ws)
-
-    async def get_term_clients(self, paths):
-        return await asyncio.gather(*(self.get_term_client(path) for path in 
paths))
-
-    async def get_pids(self, tm_list):
-        pids = []
-        for tm in tm_list:  # Must be sequential, in case terms are shared
-            pid = await tm.get_pid()
-            pids.append(pid)
-
-        return pids
-
-    def tearDown(self):
-        run = IOLoop.current().run_sync
-        run(self.named_tm.kill_all)
-        run(self.single_tm.kill_all)
-        run(self.unique_tm.kill_all)
-        super().tearDown()
-
-    def get_app(self):
-        self.named_tm = NamedTermManager(
-            shell_command=["bash"],
-            max_terminals=MAX_TERMS,
-        )
-
-        self.single_tm = SingleTermManager(shell_command=["bash"])
-
-        self.unique_tm = UniqueTermManager(
-            shell_command=["bash"],
-            max_terminals=MAX_TERMS,
-        )
-
-        named_tm = self.named_tm
-
-        class NewTerminalHandler(tornado.web.RequestHandler):
-            """Create a new named terminal, return redirect"""
-
-            def get(self):
-                name, terminal = named_tm.new_named_terminal()
-                self.redirect("/named/" + name, permanent=False)
-
-        return tornado.web.Application(
-            [
-                (r"/new", NewTerminalHandler),
-                (r"/named/(\w+)", TermSocket, {"term_manager": self.named_tm}),
-                (r"/single", TermSocket, {"term_manager": self.single_tm}),
-                (r"/unique", TermSocket, {"term_manager": self.unique_tm}),
-            ],
-            debug=True,
-        )
-
-    test_urls = ("/named/term1", "/unique") + (("/single",) if os.name != "nt" 
else ())
-
-
-class CommonTests(TermTestCase):
-    @tornado.testing.gen_test
-    async def test_basic(self):
-        for url in self.test_urls:
-            tm = await self.get_term_client(url)
-            response = await tm.read_msg()
-            self.assertEqual(response, ["setup", {}])
-
-            # Check for initial shell prompt
-            response = await tm.read_msg()
-            self.assertEqual(response[0], "stdout")
-            self.assertGreater(len(response[1]), 0)
-            tm.close()
-
-    @tornado.testing.gen_test
-    async def test_basic_command(self):
-        for url in self.test_urls:
-            tm = await self.get_term_client(url)
-            await tm.read_all_msg()
-            await tm.write_stdin("whoami\n")
-            (stdout, other) = await tm.read_stdout()
-            if os.name == "nt":
-                assert "whoami" in stdout
-            else:
-                assert stdout.startswith("who")
-            assert other == []
-            tm.close()
-
-
-class NamedTermTests(TermTestCase):
-    def test_new(self):
-        response = self.fetch("/new", follow_redirects=False)
-        self.assertEqual(response.code, 302)
-        url = response.headers["Location"]
-
-        # Check that the new terminal was created
-        name = url.split("/")[2]
-        self.assertIn(name, self.named_tm.terminals)
-
-    @tornado.testing.gen_test
-    async def test_namespace(self):
-        names = ["/named/1"] * 2 + ["/named/2"] * 2
-        tms = await self.get_term_clients(names)
-        pids = await self.get_pids(tms)
-
-        self.assertEqual(pids[0], pids[1])
-        self.assertEqual(pids[2], pids[3])
-        self.assertNotEqual(pids[0], pids[3])
-
-        terminal = self.named_tm.terminals["1"]
-        killed = await terminal.terminate(True)
-        assert killed
-        assert not terminal.ptyproc.isalive()
-        assert terminal.ptyproc.closed
-
-    @tornado.testing.gen_test
-    @pytest.mark.skipif("linux" not in platform, reason="It only works on 
Linux")
-    async def test_max_terminals(self):
-        urls = ["/named/%d" % i for i in range(MAX_TERMS + 1)]
-        tms = await self.get_term_clients(urls[:MAX_TERMS])
-        _ = await self.get_pids(tms)
-
-        # MAX_TERMS+1 should fail
-        tm = await self.get_term_client(urls[MAX_TERMS])
-        msg = await tm.read_msg()
-        self.assertEqual(msg, None)  # Connection closed
-
-
-class SingleTermTests(TermTestCase):
-    @tornado.testing.gen_test
-    async def test_single_process(self):
-        tms = await self.get_term_clients(["/single", "/single"])
-        pids = await self.get_pids(tms)
-        self.assertEqual(pids[0], pids[1])
-
-        assert self.single_tm.terminal is not None
-        killed = await self.single_tm.terminal.terminate(True)
-        assert killed
-        assert self.single_tm.terminal.ptyproc.closed
-
-
-class UniqueTermTests(TermTestCase):
-    @tornado.testing.gen_test
-    async def test_unique_processes(self):
-        tms = await self.get_term_clients(["/unique", "/unique"])
-        pids = await self.get_pids(tms)
-        self.assertNotEqual(pids[0], pids[1])
-
-    @tornado.testing.gen_test
-    @pytest.mark.skipif("linux" not in platform, reason="It only works on 
Linux")
-    async def test_max_terminals(self):
-        tms = await self.get_term_clients(["/unique"] * MAX_TERMS)
-        pids = await self.get_pids(tms)
-        self.assertEqual(len(set(pids)), MAX_TERMS)  # All PIDs unique
-
-        # MAX_TERMS+1 should fail
-        tm = await self.get_term_client("/unique")
-        msg = await tm.read_msg()
-        self.assertEqual(msg, None)  # Connection closed
-
-        # Close one
-        tms[0].close()
-        msg = await tms[0].read_msg()  # Closed
-        self.assertEqual(msg, None)
-
-        # Should be able to open back up to MAX_TERMS
-        tm = await self.get_term_client("/unique")
-        msg = await tm.read_msg()
-        self.assertEqual(msg[0], "setup")
-
-
-if __name__ == "__main__":
-    unittest.main()
+# basic_tests.py -- Basic unit tests for Terminado
+
+# Copyright (c) Jupyter Development Team
+# Copyright (c) 2014, Ramalingam Saravanan <sar...@sarava.net>
+# Distributed under the terms of the Simplified BSD License.
+
+
+import asyncio
+import datetime
+import json
+import os
+import re
+
+# We must set the policy for python >=3.8, see 
https://www.tornadoweb.org/en/stable/#installation
+# Snippet from 
https://github.com/tornadoweb/tornado/issues/2608#issuecomment-619524992
+import sys
+import unittest
+from sys import platform
+
+import pytest
+import tornado
+import tornado.httpserver
+import tornado.testing
+from tornado.ioloop import IOLoop
+
+from terminado import NamedTermManager, SingleTermManager, TermSocket, 
UniqueTermManager
+
+if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and 
sys.platform.startswith("win"):
+    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
+
+#
+# The timeout we use to assume no more messages are coming
+# from the sever.
+#
+DONE_TIMEOUT = 1.0
+ASYNC_TEST_TIMEOUT = 30
+os.environ["ASYNC_TEST_TIMEOUT"] = str(ASYNC_TEST_TIMEOUT)
+
+MAX_TERMS = 3  # Testing thresholds
+
+
+class TestTermClient:
+    """Test connection to a terminal manager"""
+
+    __test__ = False
+
+    def __init__(self, websocket):
+        self.ws = websocket
+        self.pending_read = None
+
+    async def read_msg(self):
+
+        # Because the Tornado Websocket client has no way to cancel
+        # a pending read, we have to keep track of them...
+        if self.pending_read is None:
+            self.pending_read = self.ws.read_message()
+
+        response = await self.pending_read
+        self.pending_read = None
+        if response:
+            response = json.loads(response)
+        return response
+
+    async def read_all_msg(self, timeout=DONE_TIMEOUT):
+        """Read messages until read times out"""
+        msglist: list = []
+        delta = datetime.timedelta(seconds=timeout)
+        while True:
+            try:
+                mf = self.read_msg()
+                msg = await tornado.gen.with_timeout(delta, mf)
+            except tornado.gen.TimeoutError:
+                return msglist
+
+            msglist.append(msg)
+
+    async def write_msg(self, msg):
+        await self.ws.write_message(json.dumps(msg))
+
+    async def read_stdout(self, timeout=DONE_TIMEOUT):
+        """Read standard output until timeout read reached,
+        return stdout and any non-stdout msgs received."""
+        msglist = await self.read_all_msg(timeout)
+        stdout = "".join([msg[1] for msg in msglist if msg[0] == "stdout"])
+        othermsg = [msg for msg in msglist if msg[0] != "stdout"]
+        return (stdout, othermsg)
+
+    async def discard_stdout(self, timeout=DONE_TIMEOUT):
+        """Read standard output messages, discarding the data
+        as it's received. Return the number of bytes discarded
+        and any non-stdout msgs"""
+        othermsg: list = []
+        bytes_discarded = 0
+        delta = datetime.timedelta(seconds=timeout)
+        while True:
+            try:
+                mf = self.read_msg()
+                msg = await tornado.gen.with_timeout(delta, mf)
+            except tornado.gen.TimeoutError:
+                return bytes_discarded, othermsg
+            if msg[0] == "stdout":
+                bytes_discarded += len(msg[1])
+            else:
+                othermsg.append(msg)
+
+    async def write_stdin(self, data):
+        """Write to terminal stdin"""
+        await self.write_msg(["stdin", data])
+
+    async def get_pid(self):
+        """Get process ID of terminal shell process"""
+        await self.read_stdout()  # Clear out any pending
+        await self.write_stdin("echo $$\r")
+        (stdout, extra) = await self.read_stdout()
+        if os.name == "nt":
+            match = re.search(r"echo \$\$\\.*?\\r\\n(\d+)", repr(stdout))
+            assert match is not None
+            pid = int(match.groups()[0])
+        else:
+            pid = int(stdout.split("\n")[1])
+        return pid
+
+    def close(self):
+        self.ws.close()
+
+
+class TermTestCase(tornado.testing.AsyncHTTPTestCase):
+
+    # Factory for TestTermClient, because it has to be async
+    # See:  https://github.com/tornadoweb/tornado/issues/1161
+    async def get_term_client(self, path):
+        port = self.get_http_port()
+        url = "ws://127.0.0.1:%d%s" % (port, path)
+        request = tornado.httpclient.HTTPRequest(
+            url, headers={"Origin": "http://127.0.0.1:%d"; % port}
+        )
+
+        ws = await tornado.websocket.websocket_connect(request)
+        return TestTermClient(ws)
+
+    async def get_term_clients(self, paths):
+        return await asyncio.gather(*(self.get_term_client(path) for path in 
paths))
+
+    async def get_pids(self, tm_list):
+        pids = []
+        for tm in tm_list:  # Must be sequential, in case terms are shared
+            pid = await tm.get_pid()
+            pids.append(pid)
+
+        return pids
+
+    def tearDown(self):
+        run = IOLoop.current().run_sync
+        run(self.named_tm.kill_all)
+        run(self.single_tm.kill_all)
+        run(self.unique_tm.kill_all)
+        super().tearDown()
+
+    def get_app(self):
+        self.named_tm = NamedTermManager(
+            shell_command=["bash"],
+            max_terminals=MAX_TERMS,
+        )
+
+        self.single_tm = SingleTermManager(shell_command=["bash"])
+
+        self.unique_tm = UniqueTermManager(
+            shell_command=["bash"],
+            max_terminals=MAX_TERMS,
+        )
+
+        named_tm = self.named_tm
+
+        class NewTerminalHandler(tornado.web.RequestHandler):
+            """Create a new named terminal, return redirect"""
+
+            def get(self):
+                name, terminal = named_tm.new_named_terminal()
+                self.redirect("/named/" + name, permanent=False)
+
+        return tornado.web.Application(
+            [
+                (r"/new", NewTerminalHandler),
+                (r"/named/(\w+)", TermSocket, {"term_manager": self.named_tm}),
+                (r"/single", TermSocket, {"term_manager": self.single_tm}),
+                (r"/unique", TermSocket, {"term_manager": self.unique_tm}),
+            ],
+            debug=True,
+        )
+
+    test_urls = ("/named/term1", "/unique") + (("/single",) if os.name != "nt" 
else ())
+
+
+class CommonTests(TermTestCase):
+    @tornado.testing.gen_test
+    async def test_basic(self):
+        for url in self.test_urls:
+            tm = await self.get_term_client(url)
+            response = await tm.read_msg()
+            self.assertEqual(response, ["setup", {}])
+
+            # Check for initial shell prompt
+            response = await tm.read_msg()
+            self.assertEqual(response[0], "stdout")
+            self.assertGreater(len(response[1]), 0)
+            tm.close()
+
+    @tornado.testing.gen_test
+    async def test_basic_command(self):
+        for url in self.test_urls:
+            tm = await self.get_term_client(url)
+            await tm.read_all_msg()
+            await tm.write_stdin("whoami\n")
+            (stdout, other) = await tm.read_stdout()
+            if os.name == "nt":
+                assert "whoami" in stdout
+            else:
+                assert stdout.startswith("who")
+            assert other == []
+            tm.close()
+
+
+class NamedTermTests(TermTestCase):
+    def test_new(self):
+        response = self.fetch("/new", follow_redirects=False)
+        self.assertEqual(response.code, 302)
+        url = response.headers["Location"]
+
+        # Check that the new terminal was created
+        name = url.split("/")[2]
+        self.assertIn(name, self.named_tm.terminals)
+
+    @tornado.testing.gen_test
+    async def test_namespace(self):
+        names = ["/named/1"] * 2 + ["/named/2"] * 2
+        tms = await self.get_term_clients(names)
+        pids = await self.get_pids(tms)
+
+        self.assertEqual(pids[0], pids[1])
+        self.assertEqual(pids[2], pids[3])
+        self.assertNotEqual(pids[0], pids[3])
+
+        terminal = self.named_tm.terminals["1"]
+        killed = await terminal.terminate(True)
+        assert killed
+        assert not terminal.ptyproc.isalive()
+        assert terminal.ptyproc.closed
+
+    @tornado.testing.gen_test
+    @pytest.mark.skipif("linux" not in platform, reason="It only works on 
Linux")
+    async def test_max_terminals(self):
+        urls = ["/named/%d" % i for i in range(MAX_TERMS + 1)]
+        tms = await self.get_term_clients(urls[:MAX_TERMS])
+        _ = await self.get_pids(tms)
+
+        # MAX_TERMS+1 should fail
+        tm = await self.get_term_client(urls[MAX_TERMS])
+        msg = await tm.read_msg()
+        self.assertEqual(msg, None)  # Connection closed
+
+
+class SingleTermTests(TermTestCase):
+    @tornado.testing.gen_test
+    async def test_single_process(self):
+        tms = await self.get_term_clients(["/single", "/single"])
+        pids = await self.get_pids(tms)
+        self.assertEqual(pids[0], pids[1])
+
+        assert self.single_tm.terminal is not None
+        killed = await self.single_tm.terminal.terminate(True)
+        assert killed
+        assert self.single_tm.terminal.ptyproc.closed
+
+
+class UniqueTermTests(TermTestCase):
+    @tornado.testing.gen_test
+    async def test_unique_processes(self):
+        tms = await self.get_term_clients(["/unique", "/unique"])
+        pids = await self.get_pids(tms)
+        self.assertNotEqual(pids[0], pids[1])
+
+    @tornado.testing.gen_test
+    @pytest.mark.skipif("linux" not in platform, reason="It only works on 
Linux")
+    async def test_max_terminals(self):
+        tms = await self.get_term_clients(["/unique"] * MAX_TERMS)
+        pids = await self.get_pids(tms)
+        self.assertEqual(len(set(pids)), MAX_TERMS)  # All PIDs unique
+
+        # MAX_TERMS+1 should fail
+        tm = await self.get_term_client("/unique")
+        msg = await tm.read_msg()
+        self.assertEqual(msg, None)  # Connection closed
+
+        # Close one
+        tms[0].close()
+        msg = await tms[0].read_msg()  # Closed
+        self.assertEqual(msg, None)
+
+        # Should be able to open back up to MAX_TERMS
+        tm = await self.get_term_client("/unique")
+        msg = await tm.read_msg()
+        self.assertEqual(msg[0], "setup")
+
+    @tornado.testing.gen_test
+    @pytest.mark.timeout(timeout=ASYNC_TEST_TIMEOUT, method="thread")
+    async def test_large_io_doesnt_hang(self):
+        # This is a regression test for an error where Terminado hangs when
+        # the PTY buffer size is exceeded. While the buffer size varies from
+        # OS to OS, 30KBish seems like a reasonable amount and will trigger
+        # this on both OSX and Debian.
+        massive_payload = "ten bytes " * 3000
+        massive_payload = "echo " + massive_payload + "\n"
+        tm = await self.get_term_client("/unique")
+        # Clear all startup messages.
+        await tm.read_all_msg()
+        # Write a payload that doesn't fit in a single PTY buffer.
+        await tm.write_stdin(massive_payload)
+        # Verify that the server didn't hang when responding, and that
+        # we got a reasonable amount of data back (to tell us the read
+        # didn't just timeout.
+        bytes_discarded, other = await tm.discard_stdout()
+        # Echo wont actually output anything on Windows.
+        if "win" not in platform:
+            assert bytes_discarded > 10000
+        assert other == []
+        tm.close()
+
+
+if __name__ == "__main__":
+    unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/terminado/uimod_embed.js 
new/terminado-0.16.0/terminado/uimod_embed.js
--- old/terminado-0.15.0/terminado/uimod_embed.js       2020-02-02 
01:00:00.000000000 +0100
+++ new/terminado-0.16.0/terminado/uimod_embed.js       2020-02-02 
01:00:00.000000000 +0100
@@ -17,5 +17,5 @@
       make_terminal(container, { rows: rows, cols: cols }, ws_url);
     }
   },
-  false
+  false,
 );
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/terminado-0.15.0/terminado/websocket.py 
new/terminado-0.16.0/terminado/websocket.py
--- old/terminado-0.15.0/terminado/websocket.py 2020-02-02 01:00:00.000000000 
+0100
+++ new/terminado-0.16.0/terminado/websocket.py 2020-02-02 01:00:00.000000000 
+0100
@@ -4,12 +4,13 @@
 # Copyright (c) 2014, Ramalingam Saravanan <sar...@sarava.net>
 # Distributed under the terms of the Simplified BSD License.
 
-
 import json
 import logging
 import os
 
 import tornado.websocket
+from tornado import gen
+from tornado.concurrent import run_on_executor
 
 
 def _cast_unicode(s):
@@ -26,6 +27,7 @@
         self.term_name = ""
         self.size = (None, None)
         self.terminal = None
+        self._blocking_io_executor = term_manager.blocking_io_executor
 
         self._logger = logging.getLogger(__name__)
         self._user_command = ""
@@ -78,6 +80,7 @@
             if content[0] == "stdout" and isinstance(content[1], str):
                 self.log_terminal_output(f"STDOUT: {content[1]}")
 
+    @gen.coroutine
     def on_message(self, message):
         """Handle incoming websocket message
 
@@ -89,7 +92,7 @@
         msg_type = command[0]
         assert self.terminal is not None
         if msg_type == "stdin":
-            self.terminal.ptyproc.write(command[1])
+            yield self.stdin_to_ptyproc(command[1])
             if self._enable_output_logging:
                 if command[1] == "\r":
                     self.log_terminal_output(f"STDIN: {self._user_command}")
@@ -125,3 +128,14 @@
         :return:
         """
         self._logger.debug(log)
+
+    @run_on_executor(executor="_blocking_io_executor")
+    def stdin_to_ptyproc(self, text):
+        """Handles stdin messages sent on the websocket.
+
+        This is a blocking call that should NOT be performed inside the
+        server primary event loop thread. Messages must be handled
+        asynchronously to prevent blocking on the PTY buffer.
+        """
+        if self.terminal is not None:
+            self.terminal.ptyproc.write(text)

Reply via email to