Repository: coala/coala

Issue:
diff: diff.add_lines doesn't append newline to the lines passed as a list
example:
```
diff = Diff([])
diff.add_lines(0, ["1", "2", "3"])
print(diff.unified_diff)
  --- 
  +++ 
  @@ -0,0 +1,3 @@
  +1+2+3

```
while, for `add_lines(line_br, list)` current implementation requires user to append new lines, we should check if the lines in the list endswith a `\n` if not append one implicitly

Opened via [gitter](https://gitter.im/coala/coala/?at=58b8338f1465c46a56d78e16) by @meetmangukiya


Discussion:
Means, do we have to check that is there Line break exists or not in  the list?
This can get problematic if the added line is the last line, that would be valid^^
@Makman2 how can this be problematic, AFAIK it is encouraged to end a file with a EOF new line character ?
yeah, but technically you may want a patch without a newline at EOF. I think we should support that.
How is adding a newline to a file problematic. Sorry, but this is turning out kinda confusing.
The `Diff` class is only responsible for containing a delta. Imo this class shouldn't make assumptions on preferred behaviour we suggest for code files, as `Diff` may also be used on files which may require no EOF at end (though I don't know a usecase, I believe it's better from technical perspective).

So that's why we need to cover that special case that a missing \n in the last line is valid.
We could slice the list (like `list[:-1]`), then check each remaining element with string.endswith('\n') or something similar and add it, if it is missing.

Do we need to differentiate between LF and CRLF or is that done automatically? 
LF and CRLF is handled implicitly by python while writing the file afaik
Do you think my suggestion is sensible? If so, I would like to work on this one.
Wouldn't it make sense to perform the same procedure on creation of the diff?
Or do all use-cases (except tests) use "files" with linebreaks included? 
it would also make sense on instantiation, so we allow always non-\n-input and \n-input but convert it properly internally. Good catch :+1:
And on modify_line?
Same. Depending where the line is modified (last line or somewhere before) `\n` is appended or not :+1:
Should the appending of newlines on initialization also happen for the last line (i.e. original eof?)
If not, we have an issue when new lines are added at the end afterwards, e.g.

`diff = Diff('1','2')
diff.add_lines(2,['3','4'])
`
will result in 
`['1\n', '2', '3\n', '4']`

This could be resolved by either applying newlines to all elements in initialization of the diff, or applying newlines to the output (i.e. diff.modified). 
The latter could cause some issues, i.e. with `Result.__json__` and possibly other scenarios I have not yet found.

Another option would be to reapply newlines to the original, if the addition/modification happens at the eof, but that probably require more complex logic.

For now I will go with adding newlines to all files on init, but please leave your thoughts.
I would apply newlines at EOF if needed. So in your example `Diff` should be smart enough to make it `2\n`.

Hm yeah I see where it's going... :P
Yeah, alright.
Since a second addition at the end cannot be performed anyways, its simple enough to do this.
At least until I run into other issues ;D
Should diff.original also include the added linebreaks, or should it still correspond to the list that was used to create the diff?
I would say convert line endings there too, so if a user compares those directly, he doesn't wonder why suddenly `\n`'s are appearing in the modified version which weren't added explicitly. But if he sees them already in the original, he would understand (at least I think I would :3).
I'm currently trying a different approach, which is to only append the linebreaks in the output methods (modifed, original, unified_diff). 
This works fine, after I've changed the respective tests to expect linebreaks, but I have a few problems left:

1) Can someone verify, if modify_line is working as expected, when "merging" multiple changes in the same line (see [this section](https://github.com/coala/coala/blob/master/coalib/results/Diff.py#L523-L532)).

Currently it will create diffs from the earlier and newer change by using `Diff.from_string_arrays`.
However, the values given to the method are strings, instead of arrays, which are then iterated. This results in a diff with lines for each single character and breaks with the addition of the linebreaks (obviously).
I'm guessing its an undetected bug.

2) ResultFilterTest.test_affected_code is failing and the test method is a bit ... long and confusing. Not sure what is going on there and how it should work. Can someone explain?

Other stuff:
I have to tweak some of the tests, because the premises are no longer true:
E.g. `diff.original` will never be equal to the original list that was used, except if it featured  respective linebreaks.

Additionally, I'm removing `diff.change_line` from all the tests, that are not explicitly tests for the method, because it is deprecated in favor of `diff.modify_line`. 
Fixed the issues I had.
Using strings in `modify_line` seems to be intended to be able to merge multiple changes to a single line.
To avoid my issue with breaking this behaviour, I had to split the `modified` property into an internal and external version, where only the external version applies the linebreaks.
The internal version has the of  `modified` behaves exactly the same as before and can be used safely.

This change also fixes my problems with the ResultFilterTest.

Resolving patch:
diff --git a/coalib/results/Diff.py b/coalib/results/Diff.py
--- a/coalib/results/Diff.py
+++ b/coalib/results/Diff.py
@@ -27,6 +27,7 @@ def __init__(self, file_list, rename=False, delete=False):
         """
         self._changes = {}
         self._file = list(file_list)
+        self._original = self._generate_linebreaks(self._file)
         self.rename = rename
         self.delete = delete
 
@@ -61,8 +62,7 @@ def from_string_arrays(cls, file_array_1, file_array_2, rename=False):
                     result.add_lines(a_index_1,
                                      file_array_2[b_index_1:b_index_2])
                 elif tag == 'replace':
-                    result.change_line(a_index_1+1,
-                                       file_array_1[a_index_1],
+                    result.modify_line(a_index_1+1,
                                        file_array_2[b_index_1])
                     result.add_lines(a_index_1+1,
                                      file_array_2[b_index_1+1:b_index_2])
@@ -244,10 +244,9 @@ def original(self):
         """
         Retrieves the original file.
         """
-        return self._file
+        return self._original
 
-    @property
-    def modified(self):
+    def _raw_modified(self):
         """
         Calculates the modified file, after applying the Diff to the original.
         """
@@ -277,19 +276,38 @@ def modified(self):
 
         return result
 
+    @property
+    def modified(self):
+        """
+        Calculates the modified file, after applying the Diff to the original.
+
+        This property also adds linebreaks at the end of each line.
+        If no newline was present at the end of file before, this state will
+        be preserved, except if the last line is deleted.
+        """
+        return self._generate_linebreaks(self._raw_modified())
+
     @property
     def unified_diff(self):
         """
         Generates a unified diff corresponding to this patch.
 
+        Each change will be displayed on its own line. Additionally, the
+        unified diff preserves the EOF-state of the original file. This
+        means that the ``Diff`` will only have a linebreak on the last line,
+        if that was also present in the original file.
+
         Note that the unified diff is not deterministic and thus not suitable
         for equality comparison.
         """
-        return ''.join(difflib.unified_diff(
-            self.original,
-            self.modified,
+
+        list_unified_diff = list(difflib.unified_diff(
+            self._file,
+            self._raw_modified(),
             tofile=self.rename if isinstance(self.rename, str) else ''))
 
+        return ''.join(self._generate_linebreaks(list_unified_diff))
+
     def __json__(self):
         """
         Override JSON export, using the unified diff is the easiest thing for
@@ -413,7 +431,7 @@ def __add__(self, other):
             if change.add_after is not False:
                 result.add_lines(line_nr, change.add_after)
             if change.change is not False:
-                result.change_line(line_nr, change.change[0], change.change[1])
+                result.modify_line(line_nr, change.change[1])
 
         return result
 
@@ -432,7 +450,7 @@ def __bool__(self):
         """
         return (self.rename is not False or
                 self.delete is True or
-                self.modified != self._file)
+                self.modified != self.original)
 
     def delete_line(self, line_nr):
         """
@@ -524,11 +542,12 @@ def modify_line(self, line_nr, replacement):
             if len(replacement) == len(linediff.change[1]) == 1:
                 raise ConflictError('Cannot merge the given line changes.')
 
+            # The following diffs are created from strings, instead of lists.
             orig_diff = Diff.from_string_arrays(linediff.change[0],
                                                 linediff.change[1])
             new_diff = Diff.from_string_arrays(linediff.change[0],
                                                replacement)
-            replacement = ''.join((orig_diff + new_diff).modified)
+            replacement = ''.join((orig_diff + new_diff)._raw_modified())
 
         linediff.change = (self._file[line_nr-1], replacement)
         self._changes[line_nr] = linediff
@@ -565,8 +584,8 @@ def replace(self, range, replacement):
         """
         # Remaining parts of the lines not affected by the replace.
         first_part = (
-            self.original[range.start.line - 1][:range.start.column - 1])
-        last_part = self.original[range.end.line - 1][range.end.column - 1:]
+            self._file[range.start.line - 1][:range.start.column - 1])
+        last_part = self._file[range.end.line - 1][range.end.column - 1:]
 
         self.delete_lines(range.start.line, range.end.line)
         self.add_lines(range.start.line - 1,
@@ -616,3 +635,32 @@ def remove(self, range):
         :param range: The range to delete.
         """
         self.replace(range, '')
+
+    @staticmethod
+    def _add_linebreaks(lines):
+        """
+        Validate that each line in lines ends with a
+        newline character and appends one if that is not the case.
+
+        :param lines: A list of strings, representing lines.
+        """
+
+        return [line
+                if line.endswith('\n')
+                else line + '\n'
+                for line in lines]
+
+    @staticmethod
+    def _generate_linebreaks(lines):
+        """
+        Validate that each line in lines ends with a
+        newline character and appends one if that is not the case.
+        Exception is the last line in the list.
+
+        :param lines: A list of strings, representing lines.
+        """
+
+        if lines == []:
+            return []
+
+        return Diff._add_linebreaks(lines[:-1]) + [lines[-1]]


Test patch:
diff --git a/tests/results/DiffTest.py b/tests/results/DiffTest.py
--- a/tests/results/DiffTest.py
+++ b/tests/results/DiffTest.py
@@ -23,7 +23,7 @@ def test_add_lines(self):
     def test_add_line(self):
         self.uut.add_line(0, 't')
         self.assertRaises(ConflictError, self.uut.add_line, 0, 't')
-        self.assertEqual(self.uut.modified, ['t', '1', '2', '3', '4'])
+        self.assertEqual(self.uut.modified, ['t\n', '1\n', '2\n', '3\n', '4'])
 
     def test_double_addition(self):
         self.uut.add_lines(0, ['t'])
@@ -74,11 +74,11 @@ def test_capture_warnings(self):
             'use modify_line method, without the original_line argument'])
 
     def test_double_changes_with_same_diff(self):
-        self.uut.change_line(2, '1', '2')
+        self.uut.modify_line(2, '2')
 
         # Double addition when diff is equal is allowed
         try:
-            self.uut.change_line(2, '1', '2')
+            self.uut.modify_line(2, '2')
         except Exception:
             self.fail('We should not have a conflict on same diff!')
 
@@ -113,7 +113,7 @@ def test_len(self):
         self.assertEqual(len(self.uut), 1)
         self.uut.add_lines(2, ['2.3', '2.5', '2.6'])
         self.assertEqual(len(self.uut), 4)
-        self.uut.change_line(1, '1', '1.1')
+        self.uut.modify_line(1, '1.1')
         self.assertEqual(len(self.uut), 6)
 
     def test_stats(self):
@@ -121,27 +121,27 @@ def test_stats(self):
         self.assertEqual(self.uut.stats(), (0, 1))
         self.uut.add_lines(2, ['2.3', '2.5', '2.6'])
         self.assertEqual(self.uut.stats(), (3, 1))
-        self.uut.change_line(1, '1', '1.1')
+        self.uut.modify_line(1, '1.1')
         self.assertEqual(self.uut.stats(), (4, 2))
 
     def test_modified(self):
-        result_file = ['0.1',
-                       '0.2',
-                       '1',
-                       '1.1',
-                       '3.changed',
+        result_file = ['0.1\n',
+                       '0.2\n',
+                       '1\n',
+                       '1.1\n',
+                       '3.changed\n',
                        '4']
 
         self.uut.delete_line(2)
         self.uut.add_lines(0, ['0.1', '0.2'])
         self.uut.add_lines(1, ['1.1'])
-        self.uut.change_line(3, '3', '3.changed')
+        self.uut.modify_line(3, '3.changed')
 
         self.assertEqual(self.uut.modified, result_file)
-        self.assertEqual(self.uut.original, self.file)
 
         self.uut.delete_line(len(self.file))
-        del result_file[len(result_file) - 1]
+        del result_file[-1]
+        result_file[-1] = result_file[-1].rstrip('\n')
         self.assertEqual(self.uut.modified, result_file)
 
         self.uut.delete_line(1)
@@ -154,6 +154,8 @@ def test_bool(self):
         self.assertTrue(self.uut)
         self.uut.delete_line(4)
         self.assertFalse(self.uut)
+        self.uut.modify_line(1, '1\n')
+        self.assertFalse(self.uut)
 
         # test if it works with tuples.
         uutuple = Diff(('1', '2', '3', '4'))
@@ -163,22 +165,24 @@ def test_bool(self):
         self.assertTrue(uutuple)
         uutuple.delete_line(4)
         self.assertFalse(uutuple)
+        uutuple.modify_line(1, '1\n')
+        self.assertFalse(uutuple)
 
     def test_addition(self):
         self.assertRaises(TypeError, self.uut.__add__, 5)
 
-        result_file = ['1',
-                       '2',
+        result_file = ['1\n',
+                       '2\n',
                        '2']
 
         other = Diff(self.file)
         other.delete_line(1)
-        other.change_line(2, '1', '2')
+        other.modify_line(2, '2')
         other.add_lines(0, ['1'])
 
         self.uut.delete_line(1)
         self.uut.delete_line(3)
-        self.uut.change_line(4, '4', '2')
+        self.uut.modify_line(4, '2')
         result = self.uut + other
 
         self.assertEqual(result.modified, result_file)
@@ -200,34 +204,34 @@ def test_addition_rename(self):
         self.assertRaises(ConflictError, other.__add__, uut)
 
     def test_from_string_arrays(self):
-        a = ['q', 'a', 'b', 'x', 'c', 'd']
-        b = ['a', 'b', 'y', 'c', 'd', 'f']
+        a = ['q\n', 'a\n', 'b\n', 'x\n', 'c\n', 'd\n']
+        b = ['a\n', 'b\n', 'y\n', 'c\n', 'd\n', 'f\n']
         self.uut = Diff.from_string_arrays(a, b)
         self.assertEqual(self.uut.modified, b)
 
-        a = ['first', 'fourth']
-        b = ['first', 'second', 'third', 'fourth']
+        a = ['first\n', 'fourth\n']
+        b = ['first\n', 'second\n', 'third\n', 'fourth\n']
         self.uut = Diff.from_string_arrays(a, b)
         self.assertEqual(self.uut.modified, b)
 
-        a = ['first', 'fourth']
-        b = ['first_changed', 'second', 'third', 'fourth']
+        a = ['first\n', 'fourth\n']
+        b = ['first_changed\n', 'second\n', 'third\n', 'fourth\n']
         self.uut = Diff.from_string_arrays(a, b)
         self.assertEqual(self.uut.modified, b)
 
-        a = ['first', 'second', 'third', 'fourth']
-        b = ['first', 'fourth']
+        a = ['first\n', 'second\n', 'third\n', 'fourth\n']
+        b = ['first\n', 'fourth\n']
         self.uut = Diff.from_string_arrays(a, b)
         self.assertEqual(self.uut.modified, b)
 
-        a = ['first', 'second', 'third', 'fourth']
-        b = ['first_changed', 'second_changed', 'fourth']
+        a = ['first\n', 'second\n', 'third\n', 'fourth\n']
+        b = ['first_changed\n', 'second_changed\n', 'fourth\n']
         self.uut = Diff.from_string_arrays(a, b)
         self.assertEqual(self.uut.modified, b)
 
     def test_from_unified_diff_single_addition(self):
         source = ['single line']
-        target = ['single line', 'another line added']
+        target = ['single line\n', 'another line added']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1 +1,2 @@',
@@ -239,8 +243,8 @@ def test_from_unified_diff_single_addition(self):
         self.assertEqual(self.uut.modified, target)
 
     def test_from_unified_diff_single_deletion(self):
-        source = ['two lines', 'to be removed']
-        target = ['two lines']
+        source = ['two lines\n', 'to be removed']
+        target = ['two lines\n']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,2 +1 @@',
@@ -252,8 +256,8 @@ def test_from_unified_diff_single_deletion(self):
         self.assertEqual(self.uut.modified, target)
 
     def test_from_unified_diff_single_modification(self):
-        source = ['first', 'second']
-        target = ['only_first_changed', 'second']
+        source = ['first\n', 'second']
+        target = ['only_first_changed\n', 'second']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,2 +1,2 @@',
@@ -266,8 +270,8 @@ def test_from_unified_diff_single_modification(self):
         self.assertEqual(self.uut.modified, target)
 
     def test_from_unified_diff_multiple_additions_different_orderings(self):
-        source = ['A', 'B', 'C']
-        target = ['A', 'Y', 'Z', 'B', 'C']
+        source = ['A\n', 'B\n', 'C']
+        target = ['A\n', 'Y\n', 'Z\n', 'B\n', 'C']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,3 +1,5 @@',
@@ -281,8 +285,8 @@ def test_from_unified_diff_multiple_additions_different_orderings(self):
         self.assertEqual(self.uut.original, source)
         self.assertEqual(self.uut.modified, target)
 
-        source = ['A', 'B', 'C']
-        target = ['A', 'Y', 'Z', 'C']
+        source = ['A\n', 'B\n', 'C']
+        target = ['A\n', 'Y\n', 'Z\n', 'C']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,3 +1,5 @@',
@@ -296,8 +300,8 @@ def test_from_unified_diff_multiple_additions_different_orderings(self):
         self.assertEqual(self.uut.original, source)
         self.assertEqual(self.uut.modified, target)
 
-        source = ['A', 'B', 'C']
-        target = ['Y', 'Z', 'C']
+        source = ['A\n', 'B\n', 'C']
+        target = ['Y\n', 'Z\n', 'C']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,3 +1,5 @@',
@@ -311,8 +315,8 @@ def test_from_unified_diff_multiple_additions_different_orderings(self):
         self.assertEqual(self.uut.original, source)
         self.assertEqual(self.uut.modified, target)
 
-        source = ['A', 'B', 'C']
-        target = ['A', 'B', 'C', 'Y', 'Z']
+        source = ['A\n', 'B\n', 'C']
+        target = ['A\n', 'B\n', 'C\n', 'Y\n', 'Z']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -3 +3,3 @@',
@@ -325,8 +329,8 @@ def test_from_unified_diff_multiple_additions_different_orderings(self):
         self.assertEqual(self.uut.modified, target)
 
     def test_from_unified_diffrent_beginning_line_types(self):
-        source = ['A', 'B', 'C']
-        target = ['A', 'Y', 'B', 'C']
+        source = ['A\n', 'B\n', 'C']
+        target = ['A\n', 'Y\n', 'B\n', 'C']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,3 +1,4 @@',
@@ -339,8 +343,8 @@ def test_from_unified_diffrent_beginning_line_types(self):
         self.assertEqual(self.uut.original, source)
         self.assertEqual(self.uut.modified, target)
 
-        source = ['A', 'B', 'C']
-        target = ['B', 'C']
+        source = ['A\n', 'B\n', 'C']
+        target = ['B\n', 'C']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,3 +1,2 @@',
@@ -352,8 +356,8 @@ def test_from_unified_diffrent_beginning_line_types(self):
         self.assertEqual(self.uut.original, source)
         self.assertEqual(self.uut.modified, target)
 
-        source = ['A', 'B', 'C']
-        target = ['Z', 'A', 'B', 'C']
+        source = ['A\n', 'B\n', 'C']
+        target = ['Z\n', 'A\n', 'B\n', 'C']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,2 +1,3 @@',
@@ -366,8 +370,8 @@ def test_from_unified_diffrent_beginning_line_types(self):
         self.assertEqual(self.uut.modified, target)
 
     def test_from_unified_diff_multiple_modifications(self):
-        source = ['first', 'second']
-        target = ['first_changed', 'second_changed']
+        source = ['first\n', 'second']
+        target = ['first_changed\n', 'second_changed']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,2 +1,2 @@',
@@ -381,8 +385,8 @@ def test_from_unified_diff_multiple_modifications(self):
         self.assertEqual(self.uut.modified, target)
 
     def test_from_unified_diff_multiple_hunks(self):
-        source = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
-        target = ['A', 'C', 'D', 'E', 'F']
+        source = ['A\n', 'B\n', 'C\n', 'D\n', 'E\n', 'F\n', 'G']
+        target = ['A\n', 'C\n', 'D\n', 'E\n', 'F\n']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,2 +1,1 @@',
@@ -400,8 +404,8 @@ def test_from_unified_diff_multiple_hunks(self):
         self.assertEqual(self.uut.modified, target)
 
     def test_from_unified_diff_incomplete_hunks_multiple_deletions(self):
-        source = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
-        target = ['A', 'C', 'D', 'E', 'F']
+        source = ['A\n', 'B\n', 'C\n', 'D\n', 'E\n', 'F\n', 'G']
+        target = ['A\n', 'C\n', 'D\n', 'E\n', 'F\n']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,2 +1,1 @@',
@@ -417,8 +421,8 @@ def test_from_unified_diff_incomplete_hunks_multiple_deletions(self):
         self.assertEqual(self.uut.modified, target)
 
     def test_from_unified_diff_incomplete_hunks_multiple_additions(self):
-        source = ['A', 'C', 'D', 'E', 'G']
-        target = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
+        source = ['A\n', 'C\n', 'D\n', 'E\n', 'G']
+        target = ['A\n', 'B\n', 'C\n', 'D\n', 'E\n', 'F\n', 'G']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,1 +1,2 @@',
@@ -434,8 +438,8 @@ def test_from_unified_diff_incomplete_hunks_multiple_additions(self):
         self.assertEqual(self.uut.modified, target)
 
     def test_from_unified_diff_incomplete_hunks_multiple_modifications(self):
-        source = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
-        target = ['A', 'B', 'Z', 'D', 'E', 'F', 'K']
+        source = ['A\n', 'B\n', 'C\n', 'D\n', 'E\n', 'F\n', 'G']
+        target = ['A\n', 'B\n', 'Z\n', 'D\n', 'E\n', 'F\n', 'K']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,3 +1,3 @@',
@@ -497,8 +501,8 @@ def test_from_unified_diff_unmatched_context_line(self):
             Diff.from_unified_diff(diff_string, source)
 
     def test_from_unified_diff_no_changes(self):
-        source = ['first', 'second']
-        target = ['first', 'second']
+        source = ['first\n', 'second']
+        target = ['first\n', 'second']
         diff = ['--- a/testfile',
                 '+++ b/testfile',
                 '@@ -1,2 +1,2 @@',
@@ -510,8 +514,8 @@ def test_from_unified_diff_no_changes(self):
         self.assertEqual(self.uut.modified, target)
 
     def test_from_unified_diff_no_changes_empty_diff(self):
-        source = ['first', 'second']
-        target = ['first', 'second']
+        source = ['first\n', 'second']
+        target = ['first\n', 'second']
         diff_string = ''
         self.uut = Diff.from_unified_diff(diff_string, source)
         self.assertEqual(self.uut.original, source)
@@ -626,3 +630,46 @@ def test_delete(self):
         self.uut.delete = True
         self.assertEqual(self.uut.modified, [])
         self.uut.delete = False
+
+    def test_add_linebreaks(self):
+        expected = ['1\n', '2\n', '3\n']
+
+        self.assertEqual(
+            Diff._add_linebreaks(['1', '2', '3']),
+            expected)
+
+        self.assertEqual(
+            Diff._add_linebreaks(['1', '2\n', '3']),
+            expected)
+
+        self.assertEqual(
+            Diff._add_linebreaks(expected),
+            expected)
+
+        self.assertEqual(Diff._add_linebreaks([]), [])
+
+    def test_generate_linebreaks(self):
+        eof_ln = ['1\n', '2\n', '3\n']
+        no_eof_ln = ['1\n', '2\n', '3']
+
+        self.assertEqual(
+            Diff._generate_linebreaks(['1', '2', '3']),
+            no_eof_ln)
+
+        self.assertEqual(
+            Diff._generate_linebreaks(['1', '2', '3\n']),
+            eof_ln)
+
+        self.assertEqual(
+            Diff._generate_linebreaks(['1', '2\n', '3']),
+            no_eof_ln)
+
+        self.assertEqual(
+            Diff._generate_linebreaks(no_eof_ln),
+            no_eof_ln)
+
+        self.assertEqual(
+            Diff._generate_linebreaks(eof_ln),
+            eof_ln)
+
+        self.assertEqual(Diff._generate_linebreaks([]), [])
diff --git a/tests/results/ResultTest.py b/tests/results/ResultTest.py
--- a/tests/results/ResultTest.py
+++ b/tests/results/ResultTest.py
@@ -84,12 +84,12 @@ def test_apply(self):
             'f_b': ['1', '2', '3']
         }
         expected_file_dict = {
-            'f_a': ['1', '3_changed'],
+            'f_a': ['1\n', '3_changed'],
             'f_b': ['1', '2', '3']
         }
         diff = Diff(file_dict['f_a'])
         diff.delete_line(2)
-        diff.change_line(3, '3', '3_changed')
+        diff.modify_line(3, '3_changed')
 
         uut = Result('origin', 'msg', diffs={'f_a': diff})
         uut.apply(file_dict)
@@ -103,8 +103,8 @@ def test_add(self):
             'f_c': ['1', '2', '3']
         }
         expected_file_dict = {
-            'f_a': ['1', '3_changed'],
-            'f_b': ['1', '2', '3_changed'],
+            'f_a': ['1\n', '3_changed'],
+            'f_b': ['1\n', '2\n', '3_changed'],
             'f_c': ['1', '2', '3']
         }
 
@@ -113,11 +113,11 @@ def test_add(self):
         uut1 = Result('origin', 'msg', diffs={'f_a': diff})
 
         diff = Diff(file_dict['f_a'])
-        diff.change_line(3, '3', '3_changed')
+        diff.modify_line(3, '3_changed')
         uut2 = Result('origin', 'msg', diffs={'f_a': diff})
 
         diff = Diff(file_dict['f_b'])
-        diff.change_line(3, '3', '3_changed')
+        diff.modify_line(3, '3_changed')
         uut3 = Result('origin', 'msg', diffs={'f_b': diff})
 
         uut1 += uut2 + uut3
@@ -177,13 +177,17 @@ def test_json_diff(self):
         }
         diff = Diff(file_dict['f_a'])
         diff.delete_line(2)
-        diff.change_line(3, '3', '3_changed')
+        diff.modify_line(3, '3_changed')
         uut = Result('origin', 'msg', diffs={'f_a': diff}).__json__(True)
         self.assertEqual(uut['diffs']['f_a'].__json__(), '--- \n'
                                                          '+++ \n'
                                                          '@@ -1,3 +1,2 @@\n'
-                                                         ' 1-2-3+3_changed')
+                                                         ' 1\n'
+                                                         '-2\n'
+                                                         '-3\n'
+                                                         '+3_changed')
         JSONEncoder = create_json_encoder(use_relpath=True)
         json_dump = json.dumps(diff, cls=JSONEncoder, sort_keys=True)
         self.assertEqual(
-            json_dump, '"--- \\n+++ \\n@@ -1,3 +1,2 @@\\n 1-2-3+3_changed"')
+            json_dump,
+            '"--- \\n+++ \\n@@ -1,3 +1,2 @@\\n 1\\n-2\\n-3\\n+3_changed"')