diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index ae197f765d1e16..e7c757405371c3 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -2398,6 +2398,14 @@ expression support in the :mod:`re` module). Return a copy of the string with all occurrences of substring *old* replaced by *new*. If *count* is given, only the first *count* occurrences are replaced. If *count* is not specified or ``-1``, then all occurrences are replaced. + For example: + + .. doctest:: + + >>> 'spam, spam, spam'.replace('spam', 'eggs') + 'eggs, eggs, eggs' + >>> 'spam, spam, spam'.replace('spam', 'eggs', 1) + 'eggs, spam, spam' .. versionchanged:: 3.13 *count* is now supported as a keyword argument. diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index df7017e6436a69..8b25607b6d504f 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -1,6 +1,5 @@ import math import random -import platform import sys import unittest import warnings @@ -215,8 +214,8 @@ def test_pack_unpack_roundtrip_for_nans(self): # PyFloat_Pack/Unpack*() API. See also gh-130317 and # e.g. https://developercommunity.visualstudio.com/t/155064 signaling = 0 - if platform.machine().startswith('parisc'): - # HP PA RISC uses 0 for quiet, see: + if _testcapi.nan_msb_is_signaling: + # HP PA RISC and some MIPS CPUs use 0 for quiet, see: # https://en.wikipedia.org/wiki/NaN#Encoding signaling = 1 i = make_nan(size, sign, not signaling) diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py b/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py index 8342faffb94762..baf478133fc665 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py @@ -705,10 +705,6 @@ def test_filter_prompt_displayed(self): self.assertTrue(self.display.contains_text("myfilter")) -if __name__ == "__main__": - unittest.main() - - class TestLiveCollectorThreadNavigation(unittest.TestCase): """Tests for thread navigation functionality.""" diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index cceecdd526c006..bbfe19a4e0bab7 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -5,7 +5,6 @@ import math import operator import unittest -import platform import struct import sys import weakref @@ -891,6 +890,7 @@ def test_module_func(self): self.assertRaises(StopIteration, next, it) def test_half_float(self): + _testcapi = import_helper.import_module('_testcapi') # Little-endian examples from: # http://en.wikipedia.org/wiki/Half_precision_floating-point_format format_bits_float__cleanRoundtrip_list = [ @@ -935,8 +935,8 @@ def test_half_float(self): # Check that packing produces a bit pattern representing a quiet NaN: # all exponent bits and the msb of the fraction should all be 1. - if platform.machine().startswith('parisc'): - # HP PA RISC uses 0 for quiet, see: + if _testcapi.nan_msb_is_signaling: + # HP PA RISC and some MIPS CPUs use 0 for quiet, see: # https://en.wikipedia.org/wiki/NaN#Encoding expected = 0x7c else: diff --git a/Modules/_testcapi/float.c b/Modules/_testcapi/float.c index e3869134c84d43..63de77ca6b8651 100644 --- a/Modules/_testcapi/float.c +++ b/Modules/_testcapi/float.c @@ -171,5 +171,9 @@ _PyTestCapi_Init_Float(PyObject *mod) return -1; } - return 0; +#if (defined(__mips__) && !defined(__mips_nan2008)) || defined(__hppa__) + return PyModule_Add(mod, "nan_msb_is_signaling", PyBool_FromLong(1)); +#else + return PyModule_Add(mod, "nan_msb_is_signaling", PyBool_FromLong(0)); +#endif } diff --git a/Tools/check-c-api-docs/ignored_c_api.txt b/Tools/check-c-api-docs/ignored_c_api.txt index e81ffd51e193b2..e0b94edf748853 100644 --- a/Tools/check-c-api-docs/ignored_c_api.txt +++ b/Tools/check-c-api-docs/ignored_c_api.txt @@ -45,6 +45,27 @@ Py_LL Py_SAFE_DOWNCAST Py_ULL Py_VA_COPY +PYLONG_BITS_IN_DIGIT +PY_DWORD_MAX +PY_FORMAT_SIZE_T +PY_INT32_T +PY_INT64_T +PY_LITTLE_ENDIAN +PY_LLONG_MAX +PY_LLONG_MIN +PY_LONG_LONG +PY_SIZE_MAX +PY_UINT32_T +PY_UINT64_T +PY_ULLONG_MAX +# patchlevel.h +PYTHON_ABI_STRING +PYTHON_API_STRING +PY_RELEASE_LEVEL_ALPHA +PY_RELEASE_LEVEL_BETA +PY_RELEASE_LEVEL_FINAL +PY_RELEASE_LEVEL_GAMMA +PY_VERSION # unicodeobject.h Py_UNICODE_SIZE # cpython/methodobject.h @@ -91,3 +112,39 @@ Py_FrozenMain # cpython/unicodeobject.h PyUnicode_IS_COMPACT PyUnicode_IS_COMPACT_ASCII +# pythonrun.h +PyErr_Display +# cpython/objimpl.h +PyObject_GET_WEAKREFS_LISTPTR +# cpython/pythonrun.h +PyOS_Readline +# cpython/warnings.h +PyErr_Warn +# fileobject.h +PY_STDIOTEXTMODE +# structmember.h +PY_WRITE_RESTRICTED +# pythread.h +PY_TIMEOUT_T +PY_TIMEOUT_MAX +# cpython/pyctype.h +PY_CTF_ALNUM +PY_CTF_ALPHA +PY_CTF_DIGIT +PY_CTF_LOWER +PY_CTF_SPACE +PY_CTF_UPPER +PY_CTF_XDIGIT +# cpython/code.h +PY_DEF_EVENT +PY_FOREACH_CODE_EVENT +# cpython/funcobject.h +PY_DEF_EVENT +PY_FOREACH_FUNC_EVENT +# cpython/monitoring.h +PY_MONITORING_EVENT_BRANCH +# cpython/dictobject.h +PY_DEF_EVENT +PY_FOREACH_DICT_EVENT +# cpython/pystats.h +PYSTATS_MAX_UOP_ID diff --git a/Tools/check-c-api-docs/main.py b/Tools/check-c-api-docs/main.py index 6bdf80a9ae8985..3debb9ed09da78 100644 --- a/Tools/check-c-api-docs/main.py +++ b/Tools/check-c-api-docs/main.py @@ -8,6 +8,7 @@ SIMPLE_MACRO_REGEX = re.compile(r"# *define *(\w+)(\(.+\))? ") SIMPLE_INLINE_REGEX = re.compile(r"static inline .+( |\n)(\w+)") SIMPLE_DATA_REGEX = re.compile(r"PyAPI_DATA\(.+\) (\w+)") +API_NAME_REGEX = re.compile(r'\bP[yY][a-zA-Z0-9_]+') CPYTHON = Path(__file__).parent.parent.parent INCLUDE = CPYTHON / "Include" @@ -72,24 +73,10 @@ def found_ignored_documented(singular: bool) -> str: ) -def is_documented(name: str) -> bool: - """ - Is a name present in the C API documentation? - """ - for path in C_API_DOCS.iterdir(): - if path.is_dir(): - continue - if path.suffix != ".rst": - continue - - text = path.read_text(encoding="utf-8") - if name in text: - return True - - return False - - -def scan_file_for_docs(filename: str, text: str) -> tuple[list[str], list[str]]: +def scan_file_for_docs( + filename: str, + text: str, + names: set[str]) -> tuple[list[str], list[str]]: """ Scan a header file for C API functions. """ @@ -98,7 +85,7 @@ def scan_file_for_docs(filename: str, text: str) -> tuple[list[str], list[str]]: colors = _colorize.get_colors() def check_for_name(name: str) -> None: - documented = is_documented(name) + documented = name in names if documented and (name in IGNORED): documented_ignored.append(name) elif not documented and (name not in IGNORED): @@ -106,14 +93,14 @@ def check_for_name(name: str) -> None: for function in SIMPLE_FUNCTION_REGEX.finditer(text): name = function.group(2) - if not name.startswith("Py"): + if not API_NAME_REGEX.fullmatch(name): continue check_for_name(name) for macro in SIMPLE_MACRO_REGEX.finditer(text): name = macro.group(1) - if not name.startswith("Py"): + if not API_NAME_REGEX.fullmatch(name): continue if "(" in name: @@ -123,14 +110,14 @@ def check_for_name(name: str) -> None: for inline in SIMPLE_INLINE_REGEX.finditer(text): name = inline.group(2) - if not name.startswith("Py"): + if not API_NAME_REGEX.fullmatch(name): continue check_for_name(name) for data in SIMPLE_DATA_REGEX.finditer(text): name = data.group(1) - if not name.startswith("Py"): + if not API_NAME_REGEX.fullmatch(name): continue check_for_name(name) @@ -152,6 +139,14 @@ def check_for_name(name: str) -> None: def main() -> None: + print("Gathering C API names from docs...") + names = set() + for path in C_API_DOCS.glob('**/*.rst'): + text = path.read_text(encoding="utf-8") + for name in API_NAME_REGEX.findall(text): + names.add(name) + print(f"Got {len(names)} names!") + print("Scanning for undocumented C API functions...") files = [*INCLUDE.iterdir(), *(INCLUDE / "cpython").iterdir()] all_missing: list[str] = [] @@ -162,7 +157,7 @@ def main() -> None: continue assert file.exists() text = file.read_text(encoding="utf-8") - missing, ignored = scan_file_for_docs(str(file.relative_to(INCLUDE)), text) + missing, ignored = scan_file_for_docs(str(file.relative_to(INCLUDE)), text, names) all_found_ignored += ignored all_missing += missing