Skip to content

Commit 0a8b2c5

Browse files
authored
upgrade_pythoncapi: try to preserve newlines when patching (#141)
In case we use \n on Windows or \r\n on Linux we don't want patching those files change every line due to newlines being adjusted to the platform defaults. Instead pass 'newline=""' to all open() calls to preserve newlines. And when adding new lines use the first type of newline found in the file. The test changes make the patching code reuseable and adds a second test with multiple different newlines in the input.
1 parent 3082742 commit 0a8b2c5

File tree

2 files changed

+55
-34
lines changed

2 files changed

+55
-34
lines changed

tests/test_upgrade_pythoncapi.py

+47-30
Original file line numberDiff line numberDiff line change
@@ -43,33 +43,12 @@ def reformat(source):
4343
class Tests(unittest.TestCase):
4444
maxDiff = 80 * 30
4545

46-
def _test_patch_file(self, tmp_dir):
47-
# test Patcher.patcher()
48-
source = """
49-
PyTypeObject*
50-
test_type(PyObject *obj, PyTypeObject *type)
51-
{
52-
Py_TYPE(obj) = type;
53-
return Py_TYPE(obj);
54-
}
55-
"""
56-
expected = """
57-
#include "pythoncapi_compat.h"
58-
59-
PyTypeObject*
60-
test_type(PyObject *obj, PyTypeObject *type)
61-
{
62-
Py_SET_TYPE(obj, type);
63-
return Py_TYPE(obj);
64-
}
65-
"""
66-
source = reformat(source)
67-
expected = reformat(expected)
68-
46+
def _patch_file(self, source, tmp_dir=None):
47+
# test Patcher.patcher()
6948
filename = tempfile.mktemp(suffix='.c', dir=tmp_dir)
7049
old_filename = filename + ".old"
7150
try:
72-
with open(filename, "w", encoding="utf-8") as fp:
51+
with open(filename, "w", encoding="utf-8", newline="") as fp:
7352
fp.write(source)
7453

7554
old_stderr = sys.stderr
@@ -93,10 +72,10 @@ def _test_patch_file(self, tmp_dir):
9372
sys.stderr = old_stderr
9473
sys.argv = old_argv
9574

96-
with open(filename, encoding="utf-8") as fp:
75+
with open(filename, encoding="utf-8", newline="") as fp:
9776
new_contents = fp.read()
9877

99-
with open(old_filename, encoding="utf-8") as fp:
78+
with open(old_filename, encoding="utf-8", newline="") as fp:
10079
old_contents = fp.read()
10180
finally:
10281
try:
@@ -108,15 +87,53 @@ def _test_patch_file(self, tmp_dir):
10887
except FileNotFoundError:
10988
pass
11089

111-
self.assertEqual(new_contents, expected)
11290
self.assertEqual(old_contents, source)
91+
return new_contents
11392

11493
def test_patch_file(self):
115-
self._test_patch_file(None)
94+
source = """
95+
PyTypeObject*
96+
test_type(PyObject *obj, PyTypeObject *type)
97+
{
98+
Py_TYPE(obj) = type;
99+
return Py_TYPE(obj);
100+
}
101+
"""
102+
expected = """
103+
#include "pythoncapi_compat.h"
104+
105+
PyTypeObject*
106+
test_type(PyObject *obj, PyTypeObject *type)
107+
{
108+
Py_SET_TYPE(obj, type);
109+
return Py_TYPE(obj);
110+
}
111+
"""
112+
source = reformat(source)
113+
expected = reformat(expected)
114+
115+
new_contents = self._patch_file(source)
116+
self.assertEqual(new_contents, expected)
116117

117-
def test_patch_directory(self):
118118
with tempfile.TemporaryDirectory() as tmp_dir:
119-
self._test_patch_file(tmp_dir)
119+
new_contents = self._patch_file(source, tmp_dir)
120+
self.assertEqual(new_contents, expected)
121+
122+
def test_patch_file_preserve_newlines(self):
123+
source = """
124+
Py_ssize_t get_size(PyVarObject *obj)\r\n\
125+
\n\
126+
{ return obj->ob_size; }\r\
127+
"""
128+
expected = """
129+
Py_ssize_t get_size(PyVarObject *obj)\r\n\
130+
\n\
131+
{ return Py_SIZE(obj); }\r\
132+
"""
133+
source = reformat(source)
134+
expected = reformat(expected)
135+
new_contents = self._patch_file(source)
136+
self.assertEqual(new_contents, expected)
120137

121138
def check_replace(self, source, expected, **kwargs):
122139
source = reformat(source)

upgrade_pythoncapi.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -554,13 +554,17 @@ def _get_operations(self, parser):
554554
return operations
555555

556556
def add_line(self, content, line):
557-
line = line + '\n'
557+
# Use the first matching newline
558+
match = re.search(r'(?:\r\n|\n|\r)', content)
559+
newline = match.group(0) if match else '\n'
560+
561+
line = line + newline
558562
# FIXME: tolerate trailing spaces
559563
if line not in content:
560564
# FIXME: add macro after the first header comment
561565
# FIXME: add macro after includes
562566
# FIXME: add macro after: #define PY_SSIZE_T_CLEAN
563-
return line + '\n' + content
567+
return line + newline + content
564568
else:
565569
return content
566570

@@ -601,7 +605,7 @@ def patch_file(self, filename):
601605
encoding = "utf-8"
602606
errors = "surrogateescape"
603607

604-
with open(filename, encoding=encoding, errors=errors) as fp:
608+
with open(filename, encoding=encoding, errors=errors, newline="") as fp:
605609
old_contents = fp.read()
606610

607611
new_contents, operations = self._patch(old_contents)
@@ -620,7 +624,7 @@ def patch_file(self, filename):
620624
# If old_filename already exists, replace it
621625
os.replace(filename, old_filename)
622626

623-
with open(filename, "w", encoding=encoding, errors=errors) as fp:
627+
with open(filename, "w", encoding=encoding, errors=errors, newline="") as fp:
624628
fp.write(new_contents)
625629

626630
self.applied_operations |= set(operations)

0 commit comments

Comments
 (0)