From d51e13ad8018b350ffc574fd97e3917c9fca4cb0 Mon Sep 17 00:00:00 2001 From: Rune Tynan Date: Wed, 27 May 2020 21:33:07 -0400 Subject: [PATCH] Add stubtest to Travis for windows (#4113) --- .travis.yml | 38 +++++++++++++++++ stdlib/2and3/socket.pyi | 8 ++-- tests/stubtest_test.py | 12 ++++++ tests/stubtest_whitelists/linux-py35.txt | 9 ++++ tests/stubtest_whitelists/linux-py36.txt | 8 ++++ tests/stubtest_whitelists/linux-py37.txt | 7 +++ tests/stubtest_whitelists/linux-py38.txt | 8 ++++ tests/stubtest_whitelists/linux.txt | 54 ++++++++++++++++++++++++ tests/stubtest_whitelists/py35.txt | 6 --- tests/stubtest_whitelists/py36.txt | 5 --- tests/stubtest_whitelists/py37.txt | 9 +--- tests/stubtest_whitelists/py38.txt | 10 +---- tests/stubtest_whitelists/py39.txt | 2 + tests/stubtest_whitelists/py3_common.txt | 47 --------------------- tests/stubtest_whitelists/win32-py35.txt | 13 ++++++ tests/stubtest_whitelists/win32-py36.txt | 13 ++++++ tests/stubtest_whitelists/win32-py37.txt | 1 + tests/stubtest_whitelists/win32-py38.txt | 2 + tests/stubtest_whitelists/win32.txt | 44 +++++++++++++++++++ 19 files changed, 220 insertions(+), 76 deletions(-) create mode 100644 tests/stubtest_whitelists/linux-py35.txt create mode 100644 tests/stubtest_whitelists/linux-py36.txt create mode 100644 tests/stubtest_whitelists/linux-py37.txt create mode 100644 tests/stubtest_whitelists/linux-py38.txt create mode 100644 tests/stubtest_whitelists/linux.txt create mode 100644 tests/stubtest_whitelists/win32-py35.txt create mode 100644 tests/stubtest_whitelists/win32-py36.txt create mode 100644 tests/stubtest_whitelists/win32-py37.txt create mode 100644 tests/stubtest_whitelists/win32-py38.txt create mode 100644 tests/stubtest_whitelists/win32.txt diff --git a/.travis.yml b/.travis.yml index a3d71247d..554cbe227 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,16 @@ dist: bionic language: python python: 3.8 +aliases: + test_windows: &test_windows + os: windows + language: shell + env: &env_windows + PATH: /c/Python38:/c/Python38/Scripts:/c/Python37:/c/Python37/Scripts:/c/Python36:/c/Python36/Scripts:/c/Python35:/c/Python35/Scripts:$PATH + before_install: + - choco install python --version $PYTHON_VERSION + - python -m pip install -U pip + jobs: include: - name: "pytype" @@ -49,3 +59,31 @@ jobs: python: 3.5 install: pip install -U git+git://github.com/python/mypy@b3d43984 script: ./tests/stubtest_test.py + - name: "stubtest py38 (Windows)" + <<: *test_windows + env: + <<: *env_windows + PYTHON_VERSION: 3.8.3 + install: pip install -U git+git://github.com/python/mypy@b3d43984 + script: python ./tests/stubtest_test.py + - name: "stubtest py37 (Windows)" + <<: *test_windows + env: + <<: *env_windows + PYTHON_VERSION: 3.7.7 + install: pip install -U git+git://github.com/python/mypy@b3d43984 + script: python ./tests/stubtest_test.py + - name: "stubtest py36 (Windows)" + <<: *test_windows + env: + <<: *env_windows + PYTHON_VERSION: 3.6.8 + install: pip install -U git+git://github.com/python/mypy@b3d43984 + script: python ./tests/stubtest_test.py + - name: "stubtest py35 (Windows)" + <<: *test_windows + env: + <<: *env_windows + PYTHON_VERSION: 3.5.4 + install: pip install -U git+git://github.com/python/mypy@b3d43984 + script: python ./tests/stubtest_test.py diff --git a/stdlib/2and3/socket.pyi b/stdlib/2and3/socket.pyi index 56e484595..fc7bbf629 100644 --- a/stdlib/2and3/socket.pyi +++ b/stdlib/2and3/socket.pyi @@ -773,6 +773,8 @@ def setdefaulttimeout(timeout: Optional[float]) -> None: ... if sys.version_info >= (3, 3): if sys.platform != "win32": def sethostname(name: str) -> None: ... - def if_nameindex() -> List[Tuple[int, str]]: ... - def if_nametoindex(name: str) -> int: ... - def if_indextoname(index: int) -> str: ... + # Windows added these in 3.8, but didn't have them before + if sys.platform != "win32" or sys.version_info >= (3, 8): + def if_nameindex() -> List[Tuple[int, str]]: ... + def if_nametoindex(name: str) -> int: ... + def if_indextoname(index: int) -> str: ... diff --git a/tests/stubtest_test.py b/tests/stubtest_test.py index 77f860add..fba0634fe 100755 --- a/tests/stubtest_test.py +++ b/tests/stubtest_test.py @@ -18,6 +18,8 @@ import sys def run_stubtest(typeshed_dir: Path) -> int: whitelist_dir = typeshed_dir / "tests" / "stubtest_whitelists" version_whitelist = "py{}{}.txt".format(sys.version_info.major, sys.version_info.minor) + platform_whitelist = "{}.txt".format(sys.platform) + combined_whitelist = "{}-py{}{}.txt".format(sys.platform, sys.version_info.major, sys.version_info.minor) cmd = [ sys.executable, @@ -35,6 +37,16 @@ def run_stubtest(typeshed_dir: Path) -> int: "--whitelist", str(whitelist_dir / version_whitelist), ] + if (whitelist_dir / platform_whitelist).exists(): + cmd += [ + "--whitelist", + str(whitelist_dir / platform_whitelist), + ] + if (whitelist_dir / combined_whitelist).exists(): + cmd += [ + "--whitelist", + str(whitelist_dir / combined_whitelist), + ] if sys.version_info < (3, 9): # As discussed in https://github.com/python/typeshed/issues/3693, we only aim for # positional-only arg accuracy for the latest Python version. diff --git a/tests/stubtest_whitelists/linux-py35.txt b/tests/stubtest_whitelists/linux-py35.txt new file mode 100644 index 000000000..7d740ae17 --- /dev/null +++ b/tests/stubtest_whitelists/linux-py35.txt @@ -0,0 +1,9 @@ +asyncio.unix_events._UnixSelectorEventLoop.create_unix_server +pwd.getpwnam +socket.NETLINK_CRYPTO +site.getsitepackages +site.getuserbase +site.getusersitepackages +ssl.PROTOCOL_SSLv3 +ssl.RAND_egd +typing.IO.closed \ No newline at end of file diff --git a/tests/stubtest_whitelists/linux-py36.txt b/tests/stubtest_whitelists/linux-py36.txt new file mode 100644 index 000000000..751683bd1 --- /dev/null +++ b/tests/stubtest_whitelists/linux-py36.txt @@ -0,0 +1,8 @@ +asyncio.unix_events._UnixSelectorEventLoop.create_unix_server +pwd.getpwnam +site.getsitepackages +site.getuserbase +site.getusersitepackages +ssl.PROTOCOL_SSLv3 +ssl.RAND_egd +typing.IO.closed \ No newline at end of file diff --git a/tests/stubtest_whitelists/linux-py37.txt b/tests/stubtest_whitelists/linux-py37.txt new file mode 100644 index 000000000..4b1772e9e --- /dev/null +++ b/tests/stubtest_whitelists/linux-py37.txt @@ -0,0 +1,7 @@ +pwd.getpwnam +site.getsitepackages +site.getuserbase +site.getusersitepackages +time.CLOCK_PROF +time.CLOCK_UPTIME +typing.IO.closed # Incorrect definition in CPython, fixed in bpo-39493 \ No newline at end of file diff --git a/tests/stubtest_whitelists/linux-py38.txt b/tests/stubtest_whitelists/linux-py38.txt new file mode 100644 index 000000000..4e0d114e9 --- /dev/null +++ b/tests/stubtest_whitelists/linux-py38.txt @@ -0,0 +1,8 @@ +os.MFD_HUGE_32MB +os.MFD_HUGE_512MB +site.getsitepackages +site.getuserbase +site.getusersitepackages +time.CLOCK_PROF +time.CLOCK_UPTIME +typing.IO.closed # Incorrect definition in CPython, fixed in bpo-39493 \ No newline at end of file diff --git a/tests/stubtest_whitelists/linux.txt b/tests/stubtest_whitelists/linux.txt new file mode 100644 index 000000000..c64df77a1 --- /dev/null +++ b/tests/stubtest_whitelists/linux.txt @@ -0,0 +1,54 @@ + +_posixsubprocess.cloexec_pipe +builtins.WindowsError +curses.COLORS +curses.COLOR_PAIRS +curses.COLS +curses.LINES +distutils.command.bdist_msi +grp.getgrgid +grp.struct_group._asdict +grp.struct_group._make +grp.struct_group._replace +ntpath.realpath +os.EX_NOTFOUND +os.SF_MNOWAIT +os.SF_NODISKIO +os.SF_SYNC +os.chflags +os.lchflags +os.lchmod +os.listxattr +os.plock +posix.EX_NOTFOUND +select.EPOLL_RDHUP +socket.socketpair +spwd.getspnam +spwd.struct_spwd._asdict +spwd.struct_spwd._make +spwd.struct_spwd._replace +sys.getwindowsversion +time.CLOCK_HIGHRES +urllib.request.proxy_bypass + +# ========== +# Whitelist entries that cannot or should not be fixed +# ========== + +# Modules that do not exist on Linux systems +_msi +_winapi +asyncio.windows_events +asyncio.windows_utils +ctypes.wintypes +msilib(.[a-z]+)? +msvcrt +winreg +winsound + +# NamedTuple like, but not actually NamedTuples +posix.[a-z]+_(param|result)._(asdict|make|replace) + +# Platform differences that cannot be captured by the type system +fcntl.[A-Z0-9_]+ +os.SCHED_[A-Z_]+ diff --git a/tests/stubtest_whitelists/py35.txt b/tests/stubtest_whitelists/py35.txt index 485b4893f..a711590e0 100644 --- a/tests/stubtest_whitelists/py35.txt +++ b/tests/stubtest_whitelists/py35.txt @@ -13,7 +13,6 @@ asyncio.futures._TracebackLogger.__init__ asyncio.runners asyncio.tasks.Task.__init__ asyncio.tasks.Task._wakeup -asyncio.unix_events._UnixSelectorEventLoop.create_unix_server bdb.GENERATOR_AND_COROUTINE_FLAGS builtins.str.maketrans cmath.log @@ -46,14 +45,9 @@ nntplib._NNTPBase.starttls os.DirEntry os.utime plistlib.Dict.__init__ -pwd.getpwnam pyexpat.XMLParserType.ExternalEntityParserCreate -site.getsitepackages -site.getuserbase -site.getusersitepackages smtpd.SMTPChannel.__init__ smtpd.SMTPServer.__init__ -socket.NETLINK_CRYPTO sre_compile.dis ssl.SSLSocket.__init__ typing.AbstractSet.isdisjoint diff --git a/tests/stubtest_whitelists/py36.txt b/tests/stubtest_whitelists/py36.txt index 0af59edf2..8abd0c35c 100644 --- a/tests/stubtest_whitelists/py36.txt +++ b/tests/stubtest_whitelists/py36.txt @@ -3,7 +3,6 @@ asyncio.exceptions asyncio.futures.Future.__init__ asyncio.futures._TracebackLogger.__init__ asyncio.runners -asyncio.unix_events._UnixSelectorEventLoop.create_unix_server builtins.str.maketrans cmath.log codecs.StreamRecoder.seek @@ -40,12 +39,8 @@ multiprocessing.shared_memory nntplib._NNTPBase.starttls os.utime plistlib.Dict.__init__ -pwd.getpwnam pyexpat.XMLParserType.ExternalEntityParserCreate secrets.SystemRandom.getstate -site.getsitepackages -site.getuserbase -site.getusersitepackages smtplib.SMTP.sendmail sre_compile.dis ssl.SSLSocket.__init__ diff --git a/tests/stubtest_whitelists/py37.txt b/tests/stubtest_whitelists/py37.txt index 0667aa206..03ff5b36f 100644 --- a/tests/stubtest_whitelists/py37.txt +++ b/tests/stubtest_whitelists/py37.txt @@ -49,23 +49,18 @@ macurl2path multiprocessing.shared_memory nntplib._NNTPBase.starttls os.utime -pwd.getpwnam pyexpat.XMLParserType.ExternalEntityParserCreate queue.SimpleQueue.__init__ secrets.SystemRandom.getstate -site.getsitepackages -site.getuserbase -site.getusersitepackages smtplib.SMTP.sendmail sre_constants.RANGE_IGNORE -time.CLOCK_PROF -time.CLOCK_UPTIME +ssl.PROTOCOL_SSLv3 +ssl.RAND_egd tkinter.Menu.tk_bindForTraversal tkinter.Misc.tk_menuBar types.ClassMethodDescriptorType.__get__ types.MethodDescriptorType.__get__ types.WrapperDescriptorType.__get__ -typing.IO.closed # Incorrect definition in CPython, fixed in bpo-39493 typing.NamedTuple._asdict typing.NamedTuple._make typing.NamedTuple._replace diff --git a/tests/stubtest_whitelists/py38.txt b/tests/stubtest_whitelists/py38.txt index c0c50ae5f..d264cfbf0 100644 --- a/tests/stubtest_whitelists/py38.txt +++ b/tests/stubtest_whitelists/py38.txt @@ -69,28 +69,22 @@ multiprocessing.pool.RUN multiprocessing.pool.TERMINATE multiprocessing.spawn._main nntplib._NNTPBase.starttls -os.MFD_HUGE_32MB -os.MFD_HUGE_512MB pickle.Pickler.reducer_override platform.DEV_NULL queue.SimpleQueue.__init__ secrets.SystemRandom.getstate -site.getsitepackages -site.getuserbase -site.getusersitepackages smtplib.SMTP.sendmail sre_constants.RANGE_IGNORE +ssl.PROTOCOL_SSLv3 +ssl.RAND_egd sys.UnraisableHookArgs threading.ExceptHookArgs -time.CLOCK_PROF -time.CLOCK_UPTIME tkinter.Menu.tk_bindForTraversal tkinter.Misc.tk_menuBar types.ClassMethodDescriptorType.__get__ types.CodeType.replace types.MethodDescriptorType.__get__ types.WrapperDescriptorType.__get__ -typing.IO.closed # Incorrect definition in CPython, fixed in bpo-39493 typing.NamedTuple.__new__ typing.NamedTuple._asdict typing.NamedTuple._make diff --git a/tests/stubtest_whitelists/py39.txt b/tests/stubtest_whitelists/py39.txt index a4ba9273f..bdbcd0641 100644 --- a/tests/stubtest_whitelists/py39.txt +++ b/tests/stubtest_whitelists/py39.txt @@ -182,6 +182,8 @@ smtplib.LMTP.__init__ smtplib.SMTP.sendmail sre_constants.RANGE_IGNORE ssl.AF_INET +ssl.PROTOCOL_SSLv3 +ssl.RAND_egd symtable.SymbolTable.has_exec sys.UnraisableHookArgs threading.ExceptHookArgs diff --git a/tests/stubtest_whitelists/py3_common.txt b/tests/stubtest_whitelists/py3_common.txt index 9ec8b5a64..280c9beb3 100644 --- a/tests/stubtest_whitelists/py3_common.txt +++ b/tests/stubtest_whitelists/py3_common.txt @@ -3,7 +3,6 @@ _csv.Dialect.__init__ _dummy_threading _importlib_modulespec _operator.methodcaller -_posixsubprocess.cloexec_pipe _threading_local.local.__new__ _types _weakref.CallableProxyType.__getattr__ @@ -31,10 +30,7 @@ asyncio.selector_events.BaseSelectorEventLoop.sock_recv asyncio.streams.FlowControlMixin.__init__ asyncio.tasks.Task.get_stack asyncio.tasks.Task.print_stack -asyncio.windows_events -asyncio.windows_utils bdb.Bdb.__init__ -builtins.WindowsError builtins.bytearray.__float__ builtins.bytearray.__int__ builtins.bytearray.append @@ -165,18 +161,12 @@ ctypes.memmove ctypes.memset ctypes.pointer ctypes.string_at -ctypes.wintypes ctypes.wstring_at -curses.COLORS -curses.COLOR_PAIRS -curses.COLS -curses.LINES dbm.error dbm.ndbm difflib.SequenceMatcher.__init__ distutils.archive_util.make_archive distutils.archive_util.make_tarball -distutils.command.bdist_msi distutils.command.bdist_packager distutils.core.Extension.__init__ distutils.debug.DEBUG @@ -229,10 +219,6 @@ gettext.dngettext gettext.ldngettext gettext.lngettext gettext.ngettext -grp.getgrgid -grp.struct_group._asdict -grp.struct_group._make -grp.struct_group._replace hmac.HMAC.__init__ html.parser.HTMLParser.feed http.HTTPStatus.description @@ -380,7 +366,6 @@ multiprocessing.synchronize.SemLock.release netrc.NetrcParseError.__init__ netrc.netrc.__init__ ntpath.join -ntpath.realpath numbers.Number.__hash__ operator.methodcaller optparse.HelpFormatter._format__Text @@ -388,25 +373,15 @@ optparse.OptionParser.__init__ optparse.Values.__getattr__ optparse.Values.read_file optparse.Values.read_module -os.EX_NOTFOUND -os.SF_MNOWAIT -os.SF_NODISKIO -os.SF_SYNC os._Environ.__init__ os._Environ.setdefault os._wrap_close.__init__ -os.chflags -os.lchflags -os.lchmod -os.listxattr -os.plock pickle.Pickler.persistent_id pickle.Unpickler.find_class pickle.Unpickler.persistent_load pipes.Template.copy pkgutil.ImpImporter.__init__ poplib.POP3_SSL.stls -posix.EX_NOTFOUND pydoc.HTMLDoc.docdata pydoc.HTMLDoc.docproperty pydoc.HTMLDoc.docroutine @@ -423,7 +398,6 @@ random.triangular re.error.__init__ runpy.run_path sched.Event.__doc__ -select.EPOLL_RDHUP select.poll selectors.DevpollSelector selectors.KqueueSelector @@ -437,14 +411,9 @@ smtpd.MailmanProxy.process_message smtpd.PureProxy.process_message smtplib.SMTP.send_message socket.create_connection -socket.socketpair socketserver.BaseServer.fileno socketserver.BaseServer.get_request socketserver.BaseServer.server_bind -spwd.getspnam -spwd.struct_spwd._asdict -spwd.struct_spwd._make -spwd.struct_spwd._replace sqlite3.Row.__len__ sqlite3.dbapi2.Row.__len__ sqlite3.dbapi2.register_adapters_and_converters @@ -453,9 +422,7 @@ sqlite3.register_adapters_and_converters sqlite3.version_info sre_constants.error.__init__ ssl.PROTOCOL_SSLv2 -ssl.PROTOCOL_SSLv3 ssl.Purpose.__new__ -ssl.RAND_egd ssl.SSLContext.__new__ ssl.SSLObject.__init__ ssl.SSLSocket.connect @@ -474,7 +441,6 @@ sunau.Au_write.setmark symtable.Symbol.__init__ symtable.SymbolTable.__init__ sys.gettotalrefcount -sys.getwindowsversion sys.implementation sysconfig.is_python_build sysconfig.parse_config_h @@ -491,7 +457,6 @@ threading.Lock threading.Semaphore.__enter__ threading.Semaphore.acquire threading.Thread.__init__ -time.CLOCK_HIGHRES timeit.main trace.CoverageResults.__init__ traceback.FrameSummary.__init__ @@ -540,7 +505,6 @@ urllib.error.URLError.__init__ urllib.parse._DefragResultBase.__new__ urllib.request.BaseHandler.http_error_nnn urllib.request.HTTPPasswordMgrWithPriorAuth.__init__ -urllib.request.proxy_bypass urllib.response.addinfo.__init__ urllib.response.addinfourl.__init__ urllib.robotparser.RobotFileParser.can_fetch @@ -594,7 +558,6 @@ typing.[A-Z]\w+ inspect.Parameter.__init__ inspect.Signature.__init__ os.[a-z]+_(param|result)._(asdict|make|replace) # NamedTuple like, but not actually NamedTuples -posix.[a-z]+_(param|result)._(asdict|make|replace) # NamedTuple like, but not actually NamedTuples # sys attributes that are not always defined sys.last_traceback sys.last_type @@ -610,19 +573,9 @@ urllib.response.addbase.\w+ # Platform differences that cannot be captured by the type system errno.[A-Z0-9]+ -fcntl.[A-Z0-9_]+ os.O_[A-Z_]+ -os.SCHED_[A-Z_]+ (posix.O_[A-Z_]+)? (posix.ST_[A-Z]+)? socket.AF_DECnet socket.[A-Z0-9_]+ (termios.[A-Z0-9_]+)? - -# Modules that do not exist on Linux systems and cannot be checked by our CI process. -(_msi)? -(_winapi)? -(msilib(.[a-z]+)?)? -(msvcrt)? -(winreg)? -(winsound)? diff --git a/tests/stubtest_whitelists/win32-py35.txt b/tests/stubtest_whitelists/win32-py35.txt new file mode 100644 index 000000000..c73b3a611 --- /dev/null +++ b/tests/stubtest_whitelists/win32-py35.txt @@ -0,0 +1,13 @@ +_codecs.oem_decode +_codecs.oem_encode +_winapi.GetACP +_winapi.GetFileType +asyncio.IocpProactor.recv_into +asyncio.IocpProactor.sendfile +asyncio.windows_events.IocpProactor.recv_into +asyncio.windows_events.IocpProactor.sendfile +ntpath.splitunc +os.path.splitunc +os.path.splitunc +posixpath.splitunc +typing.IO.closed \ No newline at end of file diff --git a/tests/stubtest_whitelists/win32-py36.txt b/tests/stubtest_whitelists/win32-py36.txt new file mode 100644 index 000000000..b25220672 --- /dev/null +++ b/tests/stubtest_whitelists/win32-py36.txt @@ -0,0 +1,13 @@ +_winapi.GetACP +_winapi.GetFileType +asyncio.IocpProactor.recv_into +asyncio.IocpProactor.sendfile +asyncio.windows_events.IocpProactor.recv_into +asyncio.windows_events.IocpProactor.sendfile +hashlib.scrypt +ntpath.splitunc +os.path.splitunc +os.startfile +os.path.splitunc +posixpath.splitunc +typing.IO.closed \ No newline at end of file diff --git a/tests/stubtest_whitelists/win32-py37.txt b/tests/stubtest_whitelists/win32-py37.txt new file mode 100644 index 000000000..902e132e9 --- /dev/null +++ b/tests/stubtest_whitelists/win32-py37.txt @@ -0,0 +1 @@ +os.startfile \ No newline at end of file diff --git a/tests/stubtest_whitelists/win32-py38.txt b/tests/stubtest_whitelists/win32-py38.txt new file mode 100644 index 000000000..6927638e4 --- /dev/null +++ b/tests/stubtest_whitelists/win32-py38.txt @@ -0,0 +1,2 @@ +os._AddedDllDirectory.__init__ # Parameters mismatch +importlib.metadata.DistributionFinder.Context.pattern \ No newline at end of file diff --git a/tests/stubtest_whitelists/win32.txt b/tests/stubtest_whitelists/win32.txt new file mode 100644 index 000000000..440956305 --- /dev/null +++ b/tests/stubtest_whitelists/win32.txt @@ -0,0 +1,44 @@ +ctypes.GetLastError # Is actually a pointer +ctypes.WinError # Parameter name mismatch +locale.[A-Z0-9_]+ # Constants that should be moved to _locale and re-exported conditionally +locale.nl_langinfo # Function that should be moved to _locale and re-exported conditionally +os.path.join # Parameter name mismatch +posixpath.altsep # Type mismatch +posixpath.realpath # Parameter name mismatch +sys.abiflags # Not present +sys.setdlopenflags # Not present +tempfile.TemporaryFile # TODO: Weird inconsistency +urllib.request.pathname2url # Parameter name mismatch +urllib.request.url2pathname # Same +winreg.[A-Za-z]+ # Should be made positional only + +# ========== +# Whitelist entries that cannot or should not be fixed +# ========== + +# Modules that do not exist on Windows systems +_curses +_posixsubprocess +asyncio.unix_events +crypt +dbm.gnu +fcntl +grp +nis +posix +pwd +readline +resource +spwd +syslog +termios + +# Modules that rely on _curses +curses +curses.ascii +curses.panel +curses.textpad + +# Modules that rely on termios +pty +tty