diff options
Diffstat (limited to 'Lib')
| -rw-r--r-- | Lib/_pyio.py | 3 | ||||
| -rwxr-xr-x | Lib/cgi.py | 5 | ||||
| -rw-r--r-- | Lib/collections/__init__.py | 11 | ||||
| -rw-r--r-- | Lib/configparser.py | 29 | ||||
| -rw-r--r-- | Lib/functools.py | 2 | ||||
| -rw-r--r-- | Lib/http/server.py | 3 | ||||
| -rw-r--r-- | Lib/idlelib/NEWS.txt | 13 | ||||
| -rw-r--r-- | Lib/idlelib/ScriptBinding.py | 2 | ||||
| -rw-r--r-- | Lib/idlelib/StackViewer.py | 11 | ||||
| -rw-r--r-- | Lib/idlelib/configDialog.py | 85 | ||||
| -rw-r--r-- | Lib/test/test_cgi.py | 18 | ||||
| -rw-r--r-- | Lib/test/test_collections.py | 12 | ||||
| -rw-r--r-- | Lib/test/test_configparser.py | 7 | ||||
| -rw-r--r-- | Lib/test/test_functools.py | 18 | ||||
| -rw-r--r-- | Lib/test/test_gdb.py | 52 | ||||
| -rw-r--r-- | Lib/test/test_warnings.py | 6 | ||||
| -rw-r--r-- | Lib/test/test_wsgiref.py | 5 | ||||
| -rw-r--r-- | Lib/unittest/case.py | 25 | ||||
| -rw-r--r-- | Lib/unittest/test/test_assertions.py | 15 | ||||
| -rw-r--r-- | Lib/unittest/test/test_skipping.py | 33 | 
20 files changed, 250 insertions, 105 deletions
diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 50ad9ff996..f47df91247 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -8,12 +8,13 @@ import codecs  import errno  import array  import stat +import sys  # Import _thread instead of threading to reduce startup cost  try:      from _thread import allocate_lock as Lock  except ImportError:      from _dummy_thread import allocate_lock as Lock -if os.name == 'win32': +if sys.platform in {'win32', 'cygwin'}:      from msvcrt import setmode as _setmode  else:      _setmode = None diff --git a/Lib/cgi.py b/Lib/cgi.py index 4be28ba0dc..26d2544408 100755 --- a/Lib/cgi.py +++ b/Lib/cgi.py @@ -720,6 +720,11 @@ class FieldStorage:              self.bytes_read += len(hdr_text)              parser.feed(hdr_text.decode(self.encoding, self.errors))              headers = parser.close() + +            # Some clients add Content-Length for part headers, ignore them +            if 'content-length' in headers: +                del headers['content-length'] +              part = klass(self.fp, headers, ib, environ, keep_blank_values,                           strict_parsing,self.limit-self.bytes_read,                           self.encoding, self.errors) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 80dc4f6d4e..26e5d34716 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -320,23 +320,14 @@ class {typename}(tuple):          'Return a nicely formatted representation string'          return self.__class__.__name__ + '({repr_fmt})' % self -    @property -    def __dict__(self): -        'A new OrderedDict mapping field names to their values' -        return OrderedDict(zip(self._fields, self)) -      def _asdict(self):          'Return a new OrderedDict which maps field names to their values.' -        return self.__dict__ +        return OrderedDict(zip(self._fields, self))      def __getnewargs__(self):          'Return self as a plain tuple.  Used by copy and pickle.'          return tuple(self) -    def __getstate__(self): -        'Exclude the OrderedDict from pickling' -        return None -  {field_defs}  """ diff --git a/Lib/configparser.py b/Lib/configparser.py index ecd06600b1..3a9fb56dc6 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -263,12 +263,9 @@ class InterpolationMissingOptionError(InterpolationError):      """A string substitution required a setting which was not available."""      def __init__(self, option, section, rawval, reference): -        msg = ("Bad value substitution:\n" -               "\tsection: [%s]\n" -               "\toption : %s\n" -               "\tkey    : %s\n" -               "\trawval : %s\n" -               % (section, option, reference, rawval)) +        msg = ("Bad value substitution: option {!r} in section {!r} contains " +               "an interpolation key {!r} which is not a valid option name. " +               "Raw value: {!r}".format(option, section, reference, rawval))          InterpolationError.__init__(self, option, section, msg)          self.reference = reference          self.args = (option, section, rawval, reference) @@ -286,11 +283,11 @@ class InterpolationDepthError(InterpolationError):      """Raised when substitutions are nested too deeply."""      def __init__(self, option, section, rawval): -        msg = ("Value interpolation too deeply recursive:\n" -               "\tsection: [%s]\n" -               "\toption : %s\n" -               "\trawval : %s\n" -               % (section, option, rawval)) +        msg = ("Recursion limit exceeded in value substitution: option {!r} " +               "in section {!r} contains an interpolation key which " +               "cannot be substituted in {} steps. Raw value: {!r}" +               "".format(option, section, MAX_INTERPOLATION_DEPTH, +                         rawval))          InterpolationError.__init__(self, option, section, msg)          self.args = (option, section, rawval) @@ -406,8 +403,9 @@ class BasicInterpolation(Interpolation):      def _interpolate_some(self, parser, option, accum, rest, section, map,                            depth): +        rawval = parser.get(section, option, raw=True, fallback=rest)          if depth > MAX_INTERPOLATION_DEPTH: -            raise InterpolationDepthError(option, section, rest) +            raise InterpolationDepthError(option, section, rawval)          while rest:              p = rest.find("%")              if p < 0: @@ -432,7 +430,7 @@ class BasicInterpolation(Interpolation):                      v = map[var]                  except KeyError:                      raise InterpolationMissingOptionError( -                        option, section, rest, var) from None +                        option, section, rawval, var) from None                  if "%" in v:                      self._interpolate_some(parser, option, accum, v,                                             section, map, depth + 1) @@ -466,8 +464,9 @@ class ExtendedInterpolation(Interpolation):      def _interpolate_some(self, parser, option, accum, rest, section, map,                            depth): +        rawval = parser.get(section, option, raw=True, fallback=rest)          if depth > MAX_INTERPOLATION_DEPTH: -            raise InterpolationDepthError(option, section, rest) +            raise InterpolationDepthError(option, section, rawval)          while rest:              p = rest.find("$")              if p < 0: @@ -504,7 +503,7 @@ class ExtendedInterpolation(Interpolation):                              "More than one ':' found: %r" % (rest,))                  except (KeyError, NoSectionError, NoOptionError):                      raise InterpolationMissingOptionError( -                        option, section, rest, ":".join(path)) from None +                        option, section, rawval, ":".join(path)) from None                  if "$" in v:                      self._interpolate_some(parser, opt, accum, v, sect,                                             dict(parser.items(sect, raw=True)), diff --git a/Lib/functools.py b/Lib/functools.py index 09df068020..06a4ff1366 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -567,7 +567,7 @@ def _c3_merge(sequences):                      break      # reject the current head, it appears later              else:                  break -        if not candidate: +        if candidate is None:              raise RuntimeError("Inconsistent hierarchy")          result.append(candidate)          # remove the chosen candidate diff --git a/Lib/http/server.py b/Lib/http/server.py index fd13be3731..6a0e6fe167 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -1167,8 +1167,7 @@ def test(HandlerClass=BaseHTTPRequestHandler,           ServerClass=HTTPServer, protocol="HTTP/1.0", port=8000, bind=""):      """Test the HTTP request handler class. -    This runs an HTTP server on port 8000 (or the first command line -    argument). +    This runs an HTTP server on port 8000 (or the port argument).      """      server_address = (bind, port) diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index 2d8ce54e4d..be063c4cc0 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -2,6 +2,19 @@ What's New in IDLE 3.5.0?  =========================  *Release date: 2015-09-13* ?? +- Issue #23672: Allow Idle to edit and run files with astral chars in name. +  Patch by Mohd Sanad Zaki Rizvi. + +- Issue 24745: Idle editor default font. Switch from Courier to +  platform-sensitive TkFixedFont.  This should not affect current customized +  font selections.  If there is a problem, edit $HOME/.idlerc/config-main.cfg +  and remove 'fontxxx' entries from [Editor Window].  Patch by Mark Roseman. + +- Issue #21192: Idle editor. When a file is run, put its name in the restart bar. +  Do not print false prompts. Original patch by Adnan Umer. + +- Issue #13884: Idle menus. Remove tearoff lines. Patch by Roger Serwy. +  - Issue #23184: remove unused names and imports in idlelib.    Initial patch by Al Sweigart. diff --git a/Lib/idlelib/ScriptBinding.py b/Lib/idlelib/ScriptBinding.py index e8cb2fc6b7..e5636dfe9a 100644 --- a/Lib/idlelib/ScriptBinding.py +++ b/Lib/idlelib/ScriptBinding.py @@ -69,7 +69,7 @@ class ScriptBinding:              try:                  tabnanny.process_tokens(tokenize.generate_tokens(f.readline))              except tokenize.TokenError as msg: -                msgtxt, (lineno, start) = msg +                msgtxt, (lineno, start) = msg.args                  self.editwin.gotoline(lineno)                  self.errorbox("Tabnanny Tokenizing Error",                                "Token Error: %s" % msgtxt) diff --git a/Lib/idlelib/StackViewer.py b/Lib/idlelib/StackViewer.py index b1e5e26742..ccc755ce31 100644 --- a/Lib/idlelib/StackViewer.py +++ b/Lib/idlelib/StackViewer.py @@ -10,8 +10,7 @@ from idlelib.PyShell import PyShellFileList  def StackBrowser(root, flist=None, tb=None, top=None):      if top is None: -        from tkinter import Toplevel -        top = Toplevel(root) +        top = tk.Toplevel(root)      sc = ScrolledCanvas(top, bg="white", highlightthickness=0)      sc.frame.pack(expand=1, fill="both")      item = StackTreeItem(flist, tb) @@ -108,12 +107,9 @@ class VariablesTreeItem(ObjectTreeItem):      def IsExpandable(self):          return len(self.object) > 0 -    def keys(self): -        return list(self.object.keys()) -      def GetSubList(self):          sublist = [] -        for key in self.keys(): +        for key in self.object.keys():              try:                  value = self.object[key]              except KeyError: @@ -124,6 +120,9 @@ class VariablesTreeItem(ObjectTreeItem):              sublist.append(item)          return sublist +    def keys(self):  # unused, left for possible 3rd party use +        return list(self.object.keys()) +  def _stack_viewer(parent):      root = tk.Tk()      root.title("Test StackViewer") diff --git a/Lib/idlelib/configDialog.py b/Lib/idlelib/configDialog.py index 9ed63369d3..b70cb60f41 100644 --- a/Lib/idlelib/configDialog.py +++ b/Lib/idlelib/configDialog.py @@ -1201,9 +1201,6 @@ class VerticalScrolledFrame(Frame):              # update the scrollbars to match the size of the inner frame              size = (interior.winfo_reqwidth(), interior.winfo_reqheight())              canvas.config(scrollregion="0 0 %s %s" % size) -            if interior.winfo_reqwidth() != canvas.winfo_width(): -                # update the canvas's width to fit the inner frame -                canvas.config(width=interior.winfo_reqwidth())          interior.bind('<Configure>', _configure_interior)          def _configure_canvas(event): @@ -1323,38 +1320,56 @@ class ConfigExtensionsDialog(Toplevel):      def create_widgets(self):          """Create the dialog's widgets.""" +        self.extension_names = StringVar(self)          self.rowconfigure(0, weight=1) -        self.rowconfigure(1, weight=0) -        self.columnconfigure(0, weight=1) - -        # create the tabbed pages -        self.tabbed_page_set = TabbedPageSet( -                self, page_names=self.extensions.keys(), -                n_rows=None, max_tabs_per_row=5, -                page_class=TabbedPageSet.PageRemove) -        self.tabbed_page_set.grid(row=0, column=0, sticky=NSEW) -        for ext_name in self.extensions: -            self.create_tab_page(ext_name) - -        self.create_action_buttons().grid(row=1) +        self.columnconfigure(2, weight=1) +        self.extension_list = Listbox(self, listvariable=self.extension_names, +                                      selectmode='browse') +        self.extension_list.bind('<<ListboxSelect>>', self.extension_selected) +        scroll = Scrollbar(self, command=self.extension_list.yview) +        self.extension_list.yscrollcommand=scroll.set +        self.details_frame = LabelFrame(self, width=250, height=250) +        self.extension_list.grid(column=0, row=0, sticky='nws') +        scroll.grid(column=1, row=0, sticky='ns') +        self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0]) +        self.configure(padx=10, pady=10) +        self.config_frame = {} +        self.current_extension = None + +        self.outerframe = self                      # TEMPORARY +        self.tabbed_page_set = self.extension_list  # TEMPORARY + +        # create the individual pages +        ext_names = '' +        for ext_name in sorted(self.extensions): +            self.create_extension_frame(ext_name) +            ext_names = ext_names + '{' + ext_name + '} ' +        self.extension_names.set(ext_names) +        self.extension_list.selection_set(0) +        self.extension_selected(None) +        self.create_action_buttons().grid(row=1, columnspan=3) + +    def extension_selected(self, event): +        newsel = self.extension_list.curselection() +        if newsel: +            newsel = self.extension_list.get(newsel) +        if newsel is None or newsel != self.current_extension: +            if self.current_extension: +                self.details_frame.config(text='') +                self.config_frame[self.current_extension].grid_forget() +                self.current_extension = None +        if newsel: +            self.details_frame.config(text=newsel) +            self.config_frame[newsel].grid(column=0, row=0, sticky='nsew') +            self.current_extension = newsel      create_action_buttons = ConfigDialog.create_action_buttons -    def create_tab_page(self, ext_name): -        """Create the page for an extension.""" - -        page = LabelFrame(self.tabbed_page_set.pages[ext_name].frame, -                          border=2, padx=2, relief=GROOVE, -                          text=' %s ' % ext_name) -        page.pack(fill=BOTH, expand=True, padx=12, pady=2) - -        # create the scrollable frame which will contain the entries -        scrolled_frame = VerticalScrolledFrame(page, pady=2, height=250) -        scrolled_frame.pack(side=BOTTOM, fill=BOTH, expand=TRUE) -        entry_area = scrolled_frame.interior -        entry_area.columnconfigure(0, weight=0) -        entry_area.columnconfigure(1, weight=1) - +    def create_extension_frame(self, ext_name): +        """Create a frame holding the widgets to configure one extension""" +        f = VerticalScrolledFrame(self.details_frame, height=250, width=250) +        self.config_frame[ext_name] = f +        entry_area = f.interior          # create an entry for each configuration option          for row, opt in enumerate(self.extensions[ext_name]):              # create a row with a label and entry/checkbutton @@ -1365,15 +1380,15 @@ class ConfigExtensionsDialog(Toplevel):                  Checkbutton(entry_area, textvariable=var, variable=var,                              onvalue='True', offvalue='False',                              indicatoron=FALSE, selectcolor='', width=8 -                    ).grid(row=row, column=1, sticky=W, padx=7) +                            ).grid(row=row, column=1, sticky=W, padx=7)              elif opt['type'] == 'int':                  Entry(entry_area, textvariable=var, validate='key', -                    validatecommand=(self.is_int, '%P') -                    ).grid(row=row, column=1, sticky=NSEW, padx=7) +                      validatecommand=(self.is_int, '%P') +                      ).grid(row=row, column=1, sticky=NSEW, padx=7)              else:                  Entry(entry_area, textvariable=var -                    ).grid(row=row, column=1, sticky=NSEW, padx=7) +                      ).grid(row=row, column=1, sticky=NSEW, padx=7)          return diff --git a/Lib/test/test_cgi.py b/Lib/test/test_cgi.py index a7a9d02fa6..ab9f6ab6a5 100644 --- a/Lib/test/test_cgi.py +++ b/Lib/test/test_cgi.py @@ -326,6 +326,24 @@ Content-Type: text/plain                  got = getattr(files[x], k)                  self.assertEqual(got, exp) +    def test_fieldstorage_part_content_length(self): +        BOUNDARY = "JfISa01" +        POSTDATA = """--JfISa01 +Content-Disposition: form-data; name="submit-name" +Content-Length: 5 + +Larry +--JfISa01""" +        env = { +            'REQUEST_METHOD': 'POST', +            'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY), +            'CONTENT_LENGTH': str(len(POSTDATA))} +        fp = BytesIO(POSTDATA.encode('latin-1')) +        fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1") +        self.assertEqual(len(fs.list), 1) +        self.assertEqual(fs.list[0].name, 'submit-name') +        self.assertEqual(fs.list[0].value, 'Larry') +      def test_fieldstorage_as_context_manager(self):          fp = BytesIO(b'x' * 10)          env = {'REQUEST_METHOD': 'PUT'} diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index c2d03eea74..4124f91e15 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -257,7 +257,6 @@ class TestNamedTuple(unittest.TestCase):          self.assertEqual(p._fields, ('x', 'y'))                             # test _fields attribute          self.assertEqual(p._replace(x=1), (1, 22))                          # test _replace method          self.assertEqual(p._asdict(), dict(x=11, y=22))                     # test _asdict method -        self.assertEqual(vars(p), p._asdict())                              # verify that vars() works          try:              p._replace(x=1, error=2) @@ -412,6 +411,17 @@ class TestNamedTuple(unittest.TestCase):          globals().pop('NTColor', None)          # clean-up after this test +    def test_namedtuple_subclass_issue_24931(self): +        class Point(namedtuple('_Point', ['x', 'y'])): +            pass + +        a = Point(3, 4) +        self.assertEqual(a._asdict(), OrderedDict([('x', 3), ('y', 4)])) + +        a.w = 5 +        self.assertEqual(a.__dict__, {'w': 5}) + +  ################################################################################  ### Abstract Base Classes  ################################################################################ diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 470d2cd1f3..71a8f3f8d9 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -847,7 +847,8 @@ class ConfigParserTestCase(BasicTestCase, unittest.TestCase):             "something with lots of interpolation (10 steps)")          e = self.get_error(cf, configparser.InterpolationDepthError, "Foo", "bar11")          if self.interpolation == configparser._UNSET: -            self.assertEqual(e.args, ("bar11", "Foo", "%(with1)s")) +            self.assertEqual(e.args, ("bar11", "Foo", +                "something %(with11)s lots of interpolation (11 steps)"))          elif isinstance(self.interpolation, configparser.LegacyInterpolation):              self.assertEqual(e.args, ("bar11", "Foo",                  "something %(with11)s lots of interpolation (11 steps)")) @@ -861,7 +862,7 @@ class ConfigParserTestCase(BasicTestCase, unittest.TestCase):          self.assertEqual(e.option, "name")          if self.interpolation == configparser._UNSET:              self.assertEqual(e.args, ('name', 'Interpolation Error', -                                    '', 'reference')) +                                    '%(reference)s', 'reference'))          elif isinstance(self.interpolation, configparser.LegacyInterpolation):              self.assertEqual(e.args, ('name', 'Interpolation Error',                                      '%(reference)s', 'reference')) @@ -1177,7 +1178,7 @@ class ConfigParserTestCaseExtendedInterpolation(BasicTestCase, unittest.TestCase          with self.assertRaises(exception_class) as cm:              cf['interpolated']['$trying']          self.assertEqual(cm.exception.reference, 'dollars:${sick') -        self.assertEqual(cm.exception.args[2], '}') #rawval +        self.assertEqual(cm.exception.args[2], '${dollars:${sick}}') #rawval      def test_case_sensitivity_basic(self):          ini = textwrap.dedent(""" diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index ac211c46ad..ae929eca99 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -1491,6 +1491,24 @@ class TestSingleDispatch(unittest.TestCase):          many_abcs = [c.Mapping, c.Sized, c.Callable, c.Container, c.Iterable]          self.assertEqual(mro(X, abcs=many_abcs), expected) +    def test_false_meta(self): +        # see issue23572 +        class MetaA(type): +            def __len__(self): +                return 0 +        class A(metaclass=MetaA): +            pass +        class AA(A): +            pass +        @functools.singledispatch +        def fun(a): +            return 'base A' +        @fun.register(A) +        def _(a): +            return 'fun A' +        aa = AA() +        self.assertEqual(fun(aa), 'fun A') +      def test_mro_conflicts(self):          c = collections          @functools.singledispatch diff --git a/Lib/test/test_gdb.py b/Lib/test/test_gdb.py index 0322677793..db777be39c 100644 --- a/Lib/test/test_gdb.py +++ b/Lib/test/test_gdb.py @@ -21,19 +21,34 @@ except ImportError:  from test import support  from test.support import run_unittest, findfile, python_is_optimized -try: -    gdb_version, _ = subprocess.Popen(["gdb", "-nx", "--version"], -                                      stdout=subprocess.PIPE).communicate() -except OSError: -    # This is what "no gdb" looks like.  There may, however, be other -    # errors that manifest this way too. -    raise unittest.SkipTest("Couldn't find gdb on the path") -gdb_version_number = re.search(b"^GNU gdb [^\d]*(\d+)\.(\d)", gdb_version) -gdb_major_version = int(gdb_version_number.group(1)) -gdb_minor_version = int(gdb_version_number.group(2)) +def get_gdb_version(): +    try: +        proc = subprocess.Popen(["gdb", "-nx", "--version"], +                                stdout=subprocess.PIPE, +                                universal_newlines=True) +        with proc: +            version = proc.communicate()[0] +    except OSError: +        # This is what "no gdb" looks like.  There may, however, be other +        # errors that manifest this way too. +        raise unittest.SkipTest("Couldn't find gdb on the path") + +    # Regex to parse: +    # 'GNU gdb (GDB; SUSE Linux Enterprise 12) 7.7\n' -> 7.7 +    # 'GNU gdb (GDB) Fedora 7.9.1-17.fc22\n' -> 7.9 +    # 'GNU gdb 6.1.1 [FreeBSD]\n' -> 6.1 +    # 'GNU gdb (GDB) Fedora (7.5.1-37.fc18)\n' -> 7.5 +    match = re.search(r"^GNU gdb.*?\b(\d+)\.(\d)", version) +    if match is None: +        raise Exception("unable to parse GDB version: %r" % version) +    return (version, int(match.group(1)), int(match.group(2))) + +gdb_version, gdb_major_version, gdb_minor_version = get_gdb_version()  if gdb_major_version < 7: -    raise unittest.SkipTest("gdb versions before 7.0 didn't support python embedding" -                            " Saw:\n" + gdb_version.decode('ascii', 'replace')) +    raise unittest.SkipTest("gdb versions before 7.0 didn't support python " +                            "embedding. Saw %s.%s:\n%s" +                            % (gdb_major_version, gdb_minor_version, +                               gdb_version))  if not sysconfig.is_python_build():      raise unittest.SkipTest("test_gdb only works on source builds at the moment.") @@ -59,9 +74,12 @@ def run_gdb(*args, **env_vars):      base_cmd = ('gdb', '--batch', '-nx')      if (gdb_major_version, gdb_minor_version) >= (7, 4):          base_cmd += ('-iex', 'add-auto-load-safe-path ' + checkout_hook_path) -    out, err = subprocess.Popen(base_cmd + args, -        stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, -        ).communicate() +    proc = subprocess.Popen(base_cmd + args, +                            stdout=subprocess.PIPE, +                            stderr=subprocess.PIPE, +                            env=env) +    with proc: +        out, err = proc.communicate()      return out.decode('utf-8', 'replace'), err.decode('utf-8', 'replace')  # Verify that "gdb" was built with the embedded python support enabled: @@ -880,8 +898,8 @@ class PyLocalsTests(DebuggerTests):  def test_main():      if support.verbose: -        print("GDB version:") -        for line in os.fsdecode(gdb_version).splitlines(): +        print("GDB version %s.%s:" % (gdb_major_version, gdb_minor_version)) +        for line in gdb_version.splitlines():              print(" " * 4 + line)      run_unittest(PrettyPrintTests,                   PyListTests, diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py index c7d2e5cfbb..03d9958f69 100644 --- a/Lib/test/test_warnings.py +++ b/Lib/test/test_warnings.py @@ -44,6 +44,7 @@ class BaseTest:      """Basic bookkeeping required for testing."""      def setUp(self): +        self.old_unittest_module = unittest.case.warnings          # The __warningregistry__ needs to be in a pristine state for tests          # to work properly.          if '__warningregistry__' in globals(): @@ -55,10 +56,15 @@ class BaseTest:          # The 'warnings' module must be explicitly set so that the proper          # interaction between _warnings and 'warnings' can be controlled.          sys.modules['warnings'] = self.module +        # Ensure that unittest.TestCase.assertWarns() uses the same warnings +        # module than warnings.catch_warnings(). Otherwise, +        # warnings.catch_warnings() will be unable to remove the added filter. +        unittest.case.warnings = self.module          super(BaseTest, self).setUp()      def tearDown(self):          sys.modules['warnings'] = original_warnings +        unittest.case.warnings = self.old_unittest_module          super(BaseTest, self).tearDown()  class PublicAPITests(BaseTest): diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py index 112a1b9d4b..8cca595ab1 100644 --- a/Lib/test/test_wsgiref.py +++ b/Lib/test/test_wsgiref.py @@ -1,11 +1,10 @@ -from __future__ import nested_scopes    # Backward compat for 2.1  from unittest import TestCase  from wsgiref.util import setup_testing_defaults  from wsgiref.headers import Headers  from wsgiref.handlers import BaseHandler, BaseCGIHandler  from wsgiref import util  from wsgiref.validate import validator -from wsgiref.simple_server import WSGIServer, WSGIRequestHandler, demo_app +from wsgiref.simple_server import WSGIServer, WSGIRequestHandler  from wsgiref.simple_server import make_server  from io import StringIO, BytesIO, BufferedReader  from socketserver import BaseServer @@ -14,8 +13,8 @@ from platform import python_implementation  import os  import re  import sys +import unittest -from test import support  class MockServer(WSGIServer):      """Non-socket HTTP server""" diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 7701ad3adc..ac8d67ddd1 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -583,8 +583,11 @@ class TestCase(object):              finally:                  result.stopTest(self)              return -        expecting_failure = getattr(testMethod, -                                    "__unittest_expecting_failure__", False) +        expecting_failure_method = getattr(testMethod, +                                           "__unittest_expecting_failure__", False) +        expecting_failure_class = getattr(self, +                                          "__unittest_expecting_failure__", False) +        expecting_failure = expecting_failure_class or expecting_failure_method          outcome = _Outcome(result)          try:              self._outcome = outcome @@ -1279,8 +1282,10 @@ class TestCase(object):              assert expected_regex, "expected_regex must not be empty."              expected_regex = re.compile(expected_regex)          if not expected_regex.search(text): -            msg = msg or "Regex didn't match" -            msg = '%s: %r not found in %r' % (msg, expected_regex.pattern, text) +            standardMsg = "Regex didn't match: %r not found in %r" % ( +                expected_regex.pattern, text) +            # _formatMessage ensures the longMessage option is respected +            msg = self._formatMessage(msg, standardMsg)              raise self.failureException(msg)      def assertNotRegex(self, text, unexpected_regex, msg=None): @@ -1289,11 +1294,12 @@ class TestCase(object):              unexpected_regex = re.compile(unexpected_regex)          match = unexpected_regex.search(text)          if match: -            msg = msg or "Regex matched" -            msg = '%s: %r matches %r in %r' % (msg, -                                               text[match.start():match.end()], -                                               unexpected_regex.pattern, -                                               text) +            standardMsg = 'Regex matched: %r matches %r in %r' % ( +                text[match.start() : match.end()], +                unexpected_regex.pattern, +                text) +            # _formatMessage ensures the longMessage option is respected +            msg = self._formatMessage(msg, standardMsg)              raise self.failureException(msg) @@ -1315,6 +1321,7 @@ class TestCase(object):      failIf = _deprecate(assertFalse)      assertRaisesRegexp = _deprecate(assertRaisesRegex)      assertRegexpMatches = _deprecate(assertRegex) +    assertNotRegexpMatches = _deprecate(assertNotRegex) diff --git a/Lib/unittest/test/test_assertions.py b/Lib/unittest/test/test_assertions.py index c349a95794..e6e2bc2ca7 100644 --- a/Lib/unittest/test/test_assertions.py +++ b/Lib/unittest/test/test_assertions.py @@ -133,7 +133,6 @@ class Test_Assertions(unittest.TestCase):          try:              self.assertNotRegex('Ala ma kota', r'k.t', 'Message')          except self.failureException as e: -            self.assertIn("'kot'", e.args[0])              self.assertIn('Message', e.args[0])          else:              self.fail('assertNotRegex should have failed.') @@ -329,6 +328,20 @@ class TestLongMessage(unittest.TestCase):                               "^unexpectedly identical: None$",                               "^unexpectedly identical: None : oops$"]) +    def testAssertRegex(self): +        self.assertMessages('assertRegex', ('foo', 'bar'), +                            ["^Regex didn't match:", +                             "^oops$", +                             "^Regex didn't match:", +                             "^Regex didn't match: (.*) : oops$"]) + +    def testAssertNotRegex(self): +        self.assertMessages('assertNotRegex', ('foo', 'foo'), +                            ["^Regex matched:", +                             "^oops$", +                             "^Regex matched:", +                             "^Regex matched: (.*) : oops$"]) +      def assertMessagesCM(self, methodName, args, func, errors):          """ diff --git a/Lib/unittest/test/test_skipping.py b/Lib/unittest/test/test_skipping.py index 807510f15f..71f7b70e47 100644 --- a/Lib/unittest/test/test_skipping.py +++ b/Lib/unittest/test/test_skipping.py @@ -120,6 +120,39 @@ class Test_TestSkipping(unittest.TestCase):          self.assertEqual(result.expectedFailures[0][0], test)          self.assertTrue(result.wasSuccessful()) +    def test_expected_failure_with_wrapped_class(self): +        @unittest.expectedFailure +        class Foo(unittest.TestCase): +            def test_1(self): +                self.assertTrue(False) + +        events = [] +        result = LoggingResult(events) +        test = Foo("test_1") +        test.run(result) +        self.assertEqual(events, +                         ['startTest', 'addExpectedFailure', 'stopTest']) +        self.assertEqual(result.expectedFailures[0][0], test) +        self.assertTrue(result.wasSuccessful()) + +    def test_expected_failure_with_wrapped_subclass(self): +        class Foo(unittest.TestCase): +            def test_1(self): +                self.assertTrue(False) + +        @unittest.expectedFailure +        class Bar(Foo): +            pass + +        events = [] +        result = LoggingResult(events) +        test = Bar("test_1") +        test.run(result) +        self.assertEqual(events, +                         ['startTest', 'addExpectedFailure', 'stopTest']) +        self.assertEqual(result.expectedFailures[0][0], test) +        self.assertTrue(result.wasSuccessful()) +      def test_expected_failure_subtests(self):          # A failure in any subtest counts as the expected failure of the          # whole test.  | 
