patch-fixer 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patch-fixer
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Fixes erroneous git apply patches to the best of its ability.
5
5
  Maintainer-email: Alex Mueller <amueller474@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -22,6 +22,7 @@ Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
23
  Requires-Dist: GitPython
24
24
  Provides-Extra: test
25
+ Requires-Dist: hypothesis; extra == "test"
25
26
  Requires-Dist: pytest; extra == "test"
26
27
  Requires-Dist: requests; extra == "test"
27
28
  Dynamic: license-file
@@ -71,7 +72,7 @@ pytest
71
72
  ```
72
73
  From version `0.3.0` onward (at least until version `1.0`), some test failures are expected
73
74
  in bugfix versions as I like to use test-driven development to build out new features.
74
- Please only report test failures if the same test passed in the most recent `0.x.0` version.
75
+ Please only report test failures if the same test existed and passed in the most recent `0.x.0` version.
75
76
 
76
77
  ## License
77
78
 
@@ -43,7 +43,7 @@ pytest
43
43
  ```
44
44
  From version `0.3.0` onward (at least until version `1.0`), some test failures are expected
45
45
  in bugfix versions as I like to use test-driven development to build out new features.
46
- Please only report test failures if the same test passed in the most recent `0.x.0` version.
46
+ Please only report test failures if the same test existed and passed in the most recent `0.x.0` version.
47
47
 
48
48
  ## License
49
49
 
@@ -25,9 +25,40 @@ class MissingHunkError(Exception):
25
25
  pass
26
26
 
27
27
 
28
+ class BadCarriageReturn(ValueError):
29
+ pass
30
+
31
+
28
32
  def normalize_line(line):
29
- # preserve whitespace, only normalize line endings
30
- return line.rstrip("\r\n") + "\n"
33
+ """Normalize line endings while preserving whitespace."""
34
+ if not isinstance(line, str):
35
+ raise TypeError(f"Cannot normalize non-string object {line}")
36
+
37
+ # edge case: empty string
38
+ if line == "":
39
+ return "\n"
40
+
41
+ # special malformed ending: ...\n\r
42
+ if line.endswith("\n\r"):
43
+ raise BadCarriageReturn(f"carriage return after line feed: {line}")
44
+
45
+ # handle CRLF and simple CR/LF endings
46
+ if line.endswith("\r\n"):
47
+ core = line[:-2]
48
+ elif line.endswith("\r"):
49
+ core = line[:-1]
50
+ elif line.endswith("\n"):
51
+ core = line[:-1]
52
+ else:
53
+ core = line
54
+
55
+ # check for interior CR/LF (anything before the final terminator)
56
+ if "\n" in core:
57
+ raise ValueError(f"line feed in middle of line: {line}")
58
+ if "\r" in core:
59
+ raise BadCarriageReturn(f"carriage return in middle of line: {line}")
60
+
61
+ return core + "\n"
31
62
 
32
63
 
33
64
  def find_hunk_start(context_lines, original_lines):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patch-fixer
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Fixes erroneous git apply patches to the best of its ability.
5
5
  Maintainer-email: Alex Mueller <amueller474@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -22,6 +22,7 @@ Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
23
  Requires-Dist: GitPython
24
24
  Provides-Extra: test
25
+ Requires-Dist: hypothesis; extra == "test"
25
26
  Requires-Dist: pytest; extra == "test"
26
27
  Requires-Dist: requests; extra == "test"
27
28
  Dynamic: license-file
@@ -71,7 +72,7 @@ pytest
71
72
  ```
72
73
  From version `0.3.0` onward (at least until version `1.0`), some test failures are expected
73
74
  in bugfix versions as I like to use test-driven development to build out new features.
74
- Please only report test failures if the same test passed in the most recent `0.x.0` version.
75
+ Please only report test failures if the same test existed and passed in the most recent `0.x.0` version.
75
76
 
76
77
  ## License
77
78
 
@@ -8,4 +8,5 @@ patch_fixer.egg-info/SOURCES.txt
8
8
  patch_fixer.egg-info/dependency_links.txt
9
9
  patch_fixer.egg-info/requires.txt
10
10
  patch_fixer.egg-info/top_level.txt
11
+ tests/test_norm.py
11
12
  tests/test_repos.py
@@ -1,5 +1,6 @@
1
1
  GitPython
2
2
 
3
3
  [test]
4
+ hypothesis
4
5
  pytest
5
6
  requests
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "patch-fixer"
7
- version = "0.3.0"
7
+ version = "0.3.1"
8
8
  description = "Fixes erroneous git apply patches to the best of its ability."
9
9
  maintainers = [
10
10
  {name = "Alex Mueller", email="amueller474@gmail.com"},
@@ -34,6 +34,7 @@ license-files = [
34
34
 
35
35
  [project.optional-dependencies]
36
36
  test = [
37
+ "hypothesis",
37
38
  "pytest",
38
39
  "requests"
39
40
  ]
@@ -0,0 +1,87 @@
1
+ import pytest
2
+ from hypothesis import given, strategies as st
3
+
4
+ from patch_fixer.patch_fixer import normalize_line, BadCarriageReturn
5
+
6
+ # --- Good cases --------------------------------------------------
7
+
8
+ @pytest.mark.parametrize("line, expected", [
9
+ ("", "\n"), # empty string -> newline
10
+ ("foo", "foo\n"), # no terminator
11
+ ("foo\r", "foo\n"), # CR terminator normalized
12
+ ("foo\n", "foo\n"), # LF terminator unchanged
13
+ ("foo\r\n", "foo\n"), # CRLF normalized
14
+ ])
15
+ def test_normalize_good(line, expected):
16
+ assert normalize_line(line) == expected
17
+
18
+
19
+ # --- Type errors -------------------------------------------------
20
+
21
+ @pytest.mark.parametrize("bad", [
22
+ 123,
23
+ 4.56,
24
+ None,
25
+ True,
26
+ ["list"],
27
+ {"set"},
28
+ {"dict": "val"},
29
+ ("tuple",),
30
+ ])
31
+ def test_normalize_type_error(bad):
32
+ with pytest.raises(TypeError):
33
+ normalize_line(bad)
34
+
35
+
36
+ # --- Bad endings -------------------------------------------------
37
+
38
+ @pytest.mark.parametrize("line", [
39
+ "foo\n\r", # LF then CR
40
+ "foo\rx", # CR not followed by LF at end
41
+ ])
42
+ def test_normalize_bad_endings(line):
43
+ with pytest.raises(BadCarriageReturn):
44
+ normalize_line(line)
45
+
46
+
47
+ # --- Interior CR/LF ----------------------------------------------
48
+
49
+ def test_interior_lf_raises():
50
+ line = "bad\nline\n"
51
+ with pytest.raises(ValueError):
52
+ normalize_line(line)
53
+
54
+ def test_interior_cr_raises():
55
+ line = "bad\rcarriage\n"
56
+ with pytest.raises(BadCarriageReturn):
57
+ normalize_line(line)
58
+
59
+ # --- Hypothesis testing ------------------------------------------
60
+
61
+ # generate arbitrary strings including \r and \n
62
+ line_strategy = st.text(alphabet=st.characters(), min_size=0, max_size=100)
63
+
64
+ @given(line=line_strategy)
65
+ def test_normalize_line_hypothesis(line):
66
+ # we want to see that normalize_line either:
67
+ # 1. returns a string ending with exactly one "\n", or
68
+ # 2. raises ValueError for interior LF, or
69
+ # 3. raises BadCarriageReturn for interior CR or malformed endings
70
+ try:
71
+ result = normalize_line(line)
72
+ except BadCarriageReturn:
73
+ # must have an interior CR somewhere, or malformed ending
74
+ cr_condition = (("\r" in line[:-2])
75
+ or (line.endswith("\r") and not line.endswith("\r\n"))
76
+ or line.endswith("\n\r"))
77
+ assert cr_condition, f"BadCarriageReturn raised unexpectedly for line: {line!r}"
78
+ except ValueError:
79
+ # must have an interior LF somewhere
80
+ assert "\n" in line[:-1], f"ValueError raised unexpectedly for line: {line!r}"
81
+ else:
82
+ # function returned normally
83
+ assert result.endswith("\n"), f"Returned line does not end with \\n: {result!r}"
84
+
85
+ core = result[:-1]
86
+ assert "\n" not in core
87
+ assert "\r" not in core
@@ -27,11 +27,20 @@ import pytest
27
27
  from patch_fixer import fix_patch
28
28
 
29
29
  REPOS = {
30
+ ("apache", "airflow"): ("26f6e54","2136f56"), # big repo
30
31
  ("asottile", "astpretty"): ("5b68c7e", "5a8296f"),
32
+ ("astral-sh", "ruff"): ("7fee877", "11dae2c"),
33
+ ("gabrielecirulli", "2048"): ("878098f", "478b6ec"), # adds binary files
34
+ ("mrdoob", "three.js"): ("5f3a718", "b97f111"), # replaces images
35
+ ("myriadrf", "LimeSDR-Mini"): ("0bb75e7", "fb012c8"), # gigantic diffs
31
36
  ("numpy", "numpy"): ("dca33b3", "5f82966"),
32
37
  ("pallets", "click"): ("93c6966", "e11a1ef"),
38
+ ("psf", "black"): ("8d9d18c", "903bef5"), # whole year's worth of changes
39
+ ("PyCQA", "flake8"): ("8bdec0b", "d45bdc0"), # two years of changes
33
40
  ("scipy", "scipy"): ("c2220c0", "4ca6dd9"),
41
+ ("tox-dev", "tox"): ("fb3fe66", "01442da"), # four years
34
42
  ("yaml", "pyyaml"): ("48838a3", "a2d19c0"),
43
+ ("zertovitch", "hac"): ("c563d18", "17207ee") # renamed binary files
35
44
  }
36
45
 
37
46
  CACHE_DIR = Path.home() / ".patch-testing"
File without changes
File without changes