Skip to content

Commit

Permalink
Merge pull request #3410 from bdarnell/util-test
Browse files Browse the repository at this point in the history
test: Verify that tornado.version and version_info match
  • Loading branch information
bdarnell committed Jun 29, 2024
2 parents 9c06f98 + 9b5ca51 commit 2801961
Showing 1 changed file with 88 additions and 17 deletions.
105 changes: 88 additions & 17 deletions tornado/test/util_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from io import StringIO
import re
import sys
import datetime
import textwrap
import unittest

import tornado
Expand All @@ -16,11 +16,7 @@
re_unescape,
)

import typing
from typing import cast

if typing.TYPE_CHECKING:
from typing import Dict, Any # noqa: F401
from typing import cast, Dict, Any


class RaiseExcInfoTest(unittest.TestCase):
Expand Down Expand Up @@ -217,16 +213,35 @@ def test_unicode_escapes(self):


class ExecInTest(unittest.TestCase):
# TODO(bdarnell): make a version of this test for one of the new
# future imports available in python 3.
@unittest.skip("no testable future imports")
def test_no_inherit_future(self):
# This file has from __future__ import print_function...
f = StringIO()
print("hello", file=f)
# ...but the template doesn't
exec_in('print >> f, "world"', dict(f=f))
self.assertEqual(f.getvalue(), "hello\nworld\n")
# Two files: the first has "from __future__ import annotations", and it executes the second
# which doesn't. The second file should not be affected by the first's __future__ imports.
#
# The annotations future became available in python 3.7 but has been replaced by PEP 649, so
# it should remain supported but off-by-default for the foreseeable future.
code1 = textwrap.dedent(
"""
from __future__ import annotations
from tornado.util import exec_in
exec_in(code2, globals())
"""
)

code2 = textwrap.dedent(
"""
def f(x: int) -> int:
return x + 1
output[0] = f.__annotations__
"""
)

# Make a mutable container to pass the result back to the caller
output = [None]
exec_in(code1, dict(code2=code2, output=output))
# If the annotations future were in effect, these would be strings instead of the int type
# object.
self.assertEqual(output[0], {"x": int, "return": int})


class ArgReplacerTest(unittest.TestCase):
Expand All @@ -238,7 +253,7 @@ def function(x, y, callback=None, z=None):

def test_omitted(self):
args = (1, 2)
kwargs = dict() # type: Dict[str, Any]
kwargs: Dict[str, Any] = dict()
self.assertIsNone(self.replacer.get_old_value(args, kwargs))
self.assertEqual(
self.replacer.replace("new", args, kwargs),
Expand All @@ -247,7 +262,7 @@ def test_omitted(self):

def test_position(self):
args = (1, 2, "old", 3)
kwargs = dict() # type: Dict[str, Any]
kwargs: Dict[str, Any] = dict()
self.assertEqual(self.replacer.get_old_value(args, kwargs), "old")
self.assertEqual(
self.replacer.replace("new", args, kwargs),
Expand Down Expand Up @@ -300,3 +315,59 @@ def test_re_unescape_raises_error_on_invalid_input(self):
re_unescape("\\b")
with self.assertRaises(ValueError):
re_unescape("\\Z")


class VersionInfoTest(unittest.TestCase):
def assert_version_info_compatible(self, version, version_info):
# We map our version identifier string (a subset of
# https://packaging.python.org/en/latest/specifications/version-specifiers/#public-version-identifiers)
# to a 4-tuple of integers for easy comparisons. The last component is
# 0 for a final release, negative for a pre-release, and would be positive for a
# post-release if we did any of those. This test is not a promise that these are the
# only formats we will ever use, but it does catch accidents like
# https://github.com/tornadoweb/tornado/issues/3406.
major = minor = patch = "0"
is_pre = False
if m := re.fullmatch(r"(\d+)\.(\d+)\.(\d+)", version):
# Regular 3-component version number
major, minor, patch = m.groups()
elif m := re.fullmatch(r"(\d+)\.(\d+)", version):
# Two-component version number, equivalent to major.minor.0
major, minor = m.groups()
elif m := re.fullmatch(r"(\d+)\.(\d+)\.(\d+)(?:\.dev|a|b|rc)\d+", version):
# Pre-release 3-component version number.
major, minor, patch = m.groups()
is_pre = True
elif m := re.fullmatch(r"(\d+)\.(\d+)(?:\.dev|a|b|rc)\d+", version):
# Pre-release 2-component version number.
major, minor = m.groups()
is_pre = True
else:
self.fail(f"Unrecognized version format: {version}")

self.assertEqual(version_info[:3], (int(major), int(minor), int(patch)))
if is_pre:
self.assertLess(int(version_info[3]), 0)
else:
self.assertEqual(int(version_info[3]), 0)

def test_version_info_compatible(self):
self.assert_version_info_compatible("6.5.0", (6, 5, 0, 0))
self.assert_version_info_compatible("6.5", (6, 5, 0, 0))
self.assert_version_info_compatible("6.5.1", (6, 5, 1, 0))
self.assert_version_info_compatible("6.6.dev1", (6, 6, 0, -100))
self.assert_version_info_compatible("6.6a1", (6, 6, 0, -100))
self.assert_version_info_compatible("6.6b1", (6, 6, 0, -100))
self.assert_version_info_compatible("6.6rc1", (6, 6, 0, -100))
self.assertRaises(
AssertionError, self.assert_version_info_compatible, "6.5.0", (6, 5, 0, 1)
)
self.assertRaises(
AssertionError, self.assert_version_info_compatible, "6.5.0", (6, 4, 0, 0)
)
self.assertRaises(
AssertionError, self.assert_version_info_compatible, "6.5.1", (6, 5, 0, 1)
)

def test_current_version(self):
self.assert_version_info_compatible(tornado.version, tornado.version_info)

0 comments on commit 2801961

Please sign in to comment.