Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions Doc/c-api/lifecycle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,14 @@ that must be true for *B* to occur after *A*.
.. image:: lifecycle.dot.svg
:align: center
:class: invert-in-dark-mode
:alt: Diagram showing events in an object's life. Explained in detail
below.
:alt: Diagram showing events in an object's life. Explained in detail below.

.. only:: latex

.. image:: lifecycle.dot.pdf
:align: center
:class: invert-in-dark-mode
:alt: Diagram showing events in an object's life. Explained in detail
below.
:alt: Diagram showing events in an object's life. Explained in detail below.

.. container::
:name: life-events-graph-description
Expand Down
10 changes: 8 additions & 2 deletions Doc/library/csv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ The :mod:`csv` module defines the following functions:
section :ref:`csv-fmt-params`.

Each row read from the csv file is returned as a list of strings. No
automatic data type conversion is performed unless the ``QUOTE_NONNUMERIC`` format
automatic data type conversion is performed unless the :data:`QUOTE_NONNUMERIC` format
option is specified (in which case unquoted fields are transformed into floats).

A short usage example::
Expand Down Expand Up @@ -331,8 +331,14 @@ The :mod:`csv` module defines the following constants:

Instructs :class:`writer` objects to quote all non-numeric fields.

Instructs :class:`reader` objects to convert all non-quoted fields to type *float*.
Instructs :class:`reader` objects to convert all non-quoted fields to type :class:`float`.

.. note::
Some numeric types, such as :class:`bool`, :class:`~fractions.Fraction`,
or :class:`~enum.IntEnum`, have a string representation that cannot be
converted to :class:`float`.
They cannot be read in the :data:`QUOTE_NONNUMERIC` and
:data:`QUOTE_STRINGS` modes.

.. data:: QUOTE_NONE

Expand Down
3 changes: 3 additions & 0 deletions Doc/library/dbm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ functionality like crash tolerance.
* ``'s'``: Synchronized mode.
Changes to the database will be written immediately to the file.
* ``'u'``: Do not lock database.
* ``'m'``: Do not use :manpage:`mmap(2)`.
This may harm performance, but improve crash tolerance.
.. versionadded:: next

Not all flags are valid for all versions of GDBM.
See the :data:`open_flags` member for a list of supported flag characters.
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ dbm
which allow to recover unused free space previously occupied by deleted entries.
(Contributed by Andrea Oliveri in :gh:`134004`.)

* Add the ``'m'`` flag for :func:`dbm.gnu.open` which allows to disable
the use of :manpage:`mmap(2)`.
This may harm performance, but improve crash tolerance.
(Contributed by Serhiy Storchaka in :gh:`66234`.)

difflib
-------
Expand Down
48 changes: 35 additions & 13 deletions Lib/test/test_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -1122,19 +1122,22 @@ class mydialect(csv.Dialect):
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"quotechar" must be a 1-character string')
'"quotechar" must be a unicode character or None, '
'not a string of length 0')

mydialect.quotechar = "''"
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"quotechar" must be a 1-character string')
'"quotechar" must be a unicode character or None, '
'not a string of length 2')

mydialect.quotechar = 4
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"quotechar" must be string or None, not int')
'"quotechar" must be a unicode character or None, '
'not int')

def test_delimiter(self):
class mydialect(csv.Dialect):
Expand All @@ -1151,31 +1154,32 @@ class mydialect(csv.Dialect):
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"delimiter" must be a 1-character string')
'"delimiter" must be a unicode character, '
'not a string of length 3')

mydialect.delimiter = ""
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"delimiter" must be a 1-character string')
'"delimiter" must be a unicode character, not a string of length 0')

mydialect.delimiter = b","
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"delimiter" must be string, not bytes')
'"delimiter" must be a unicode character, not bytes')

mydialect.delimiter = 4
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"delimiter" must be string, not int')
'"delimiter" must be a unicode character, not int')

mydialect.delimiter = None
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"delimiter" must be string, not NoneType')
'"delimiter" must be a unicode character, not NoneType')

def test_escapechar(self):
class mydialect(csv.Dialect):
Expand All @@ -1189,20 +1193,32 @@ class mydialect(csv.Dialect):
self.assertEqual(d.escapechar, "\\")

mydialect.escapechar = ""
with self.assertRaisesRegex(csv.Error, '"escapechar" must be a 1-character string'):
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"escapechar" must be a unicode character or None, '
'not a string of length 0')

mydialect.escapechar = "**"
with self.assertRaisesRegex(csv.Error, '"escapechar" must be a 1-character string'):
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"escapechar" must be a unicode character or None, '
'not a string of length 2')

mydialect.escapechar = b"*"
with self.assertRaisesRegex(csv.Error, '"escapechar" must be string or None, not bytes'):
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"escapechar" must be a unicode character or None, '
'not bytes')

mydialect.escapechar = 4
with self.assertRaisesRegex(csv.Error, '"escapechar" must be string or None, not int'):
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"escapechar" must be a unicode character or None, '
'not int')

def test_lineterminator(self):
class mydialect(csv.Dialect):
Expand All @@ -1223,7 +1239,13 @@ class mydialect(csv.Dialect):
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"lineterminator" must be a string')
'"lineterminator" must be a string, not int')

mydialect.lineterminator = None
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
'"lineterminator" must be a string, not NoneType')

def test_invalid_chars(self):
def create_invalid(field_name, value, **kwargs):
Expand Down
27 changes: 25 additions & 2 deletions Lib/test/test_dbm_gnu.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ def test_flags(self):
# Test the flag parameter open() by trying all supported flag modes.
all = set(gdbm.open_flags)
# Test standard flags (presumably "crwn").
modes = all - set('fsu')
modes = all - set('fsum')
for mode in sorted(modes): # put "c" mode first
self.g = gdbm.open(filename, mode)
self.g.close()

# Test additional flags (presumably "fsu").
# Test additional flags (presumably "fsum").
flags = all - set('crwn')
for mode in modes:
for flag in flags:
Expand Down Expand Up @@ -217,6 +217,29 @@ def test_localized_error(self):
create_empty_file(os.path.join(d, 'test'))
self.assertRaises(gdbm.error, gdbm.open, filename, 'r')

@unittest.skipUnless('m' in gdbm.open_flags, "requires 'm' in open_flags")
def test_nommap_no_crash(self):
self.g = g = gdbm.open(filename, 'nm')
os.truncate(filename, 0)

g.get(b'a', b'c')
g.keys()
g.firstkey()
g.nextkey(b'a')
with self.assertRaises(KeyError):
g[b'a']
with self.assertRaises(gdbm.error):
len(g)

with self.assertRaises(gdbm.error):
g[b'a'] = b'c'
with self.assertRaises(gdbm.error):
del g[b'a']
with self.assertRaises(gdbm.error):
g.setdefault(b'a', b'c')
with self.assertRaises(gdbm.error):
g.reorganize()


if __name__ == '__main__':
unittest.main()
31 changes: 31 additions & 0 deletions Lib/test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,37 @@ def flush(self):
# Silence destructor error
R.flush = lambda self: None

@threading_helper.requires_working_threading()
def test_write_readline_races(self):
# gh-134908: Concurrent iteration over a file caused races
thread_count = 2
write_count = 100
read_count = 100

def writer(file, barrier):
barrier.wait()
for _ in range(write_count):
file.write("x")

def reader(file, barrier):
barrier.wait()
for _ in range(read_count):
for line in file:
self.assertEqual(line, "")

with self.open(os_helper.TESTFN, "w+") as f:
barrier = threading.Barrier(thread_count + 1)
reader = threading.Thread(target=reader, args=(f, barrier))
writers = [threading.Thread(target=writer, args=(f, barrier))
for _ in range(thread_count)]
with threading_helper.catch_threading_exception() as cm:
with threading_helper.start_threads(writers + [reader]):
pass
self.assertIsNone(cm.exc_type)

self.assertEqual(os.stat(os_helper.TESTFN).st_size,
write_count * thread_count)


class CIOTest(IOTest):

Expand Down
20 changes: 10 additions & 10 deletions Lib/test/test_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import time
import unittest
import weakref
from test.support import gc_collect
from test.support import gc_collect, bigmemtest
from test.support import import_helper
from test.support import threading_helper

Expand Down Expand Up @@ -963,33 +963,33 @@ def test_order(self):
# One producer, one consumer => results appended in well-defined order
self.assertEqual(results, inputs)

def test_many_threads(self):
@bigmemtest(size=50, memuse=100*2**20, dry_run=False)
def test_many_threads(self, size):
# Test multiple concurrent put() and get()
N = 50
q = self.q
inputs = list(range(10000))
results = self.run_threads(N, q, inputs, self.feed, self.consume)
results = self.run_threads(size, q, inputs, self.feed, self.consume)

# Multiple consumers without synchronization append the
# results in random order
self.assertEqual(sorted(results), inputs)

def test_many_threads_nonblock(self):
@bigmemtest(size=50, memuse=100*2**20, dry_run=False)
def test_many_threads_nonblock(self, size):
# Test multiple concurrent put() and get(block=False)
N = 50
q = self.q
inputs = list(range(10000))
results = self.run_threads(N, q, inputs,
results = self.run_threads(size, q, inputs,
self.feed, self.consume_nonblock)

self.assertEqual(sorted(results), inputs)

def test_many_threads_timeout(self):
@bigmemtest(size=50, memuse=100*2**20, dry_run=False)
def test_many_threads_timeout(self, size):
# Test multiple concurrent put() and get(timeout=...)
N = 50
q = self.q
inputs = list(range(1000))
results = self.run_threads(N, q, inputs,
results = self.run_threads(size, q, inputs,
self.feed, self.consume_timeout)

self.assertEqual(sorted(results), inputs)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix crash when iterating over lines in a text file on the :term:`free threaded <free threading>` build.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve error messages for incorrect types and values of :class:`csv.Dialect`
attributes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add the ``'m'`` flag for :func:`dbm.gnu.open` which allows to disable the
use of :manpage:`mmap(2)`. This may harm performance, but improve crash
tolerance.
Loading
Loading