| // Copyright (C) 2019 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.server.fixes.fixCalculator; |
| |
| import static com.google.gerrit.server.fixes.testing.FixResultSubject.assertThat; |
| import static com.google.gerrit.server.fixes.testing.GitEditSubject.assertThat; |
| import static com.google.gerrit.testing.GerritJUnit.assertThrows; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.gerrit.entities.Comment.Range; |
| import com.google.gerrit.entities.FixReplacement; |
| import com.google.gerrit.extensions.restapi.ResourceConflictException; |
| import com.google.gerrit.server.fixes.FixCalculator; |
| import com.google.gerrit.server.fixes.FixCalculator.FixResult; |
| import com.google.gerrit.server.patch.Text; |
| import org.eclipse.jgit.diff.Edit; |
| import org.junit.Test; |
| |
| public class FixCalculatorVariousTest { |
| private static final String multilineContentString = |
| "First line\nSecond line\nThird line\nFourth line\nFifth line\n"; |
| private static final Text multilineContent = new Text(multilineContentString.getBytes(UTF_8)); |
| |
| static FixResult calculateFixSingleReplacement( |
| String content, int startLine, int startChar, int endLine, int endChar, String replacement) |
| throws ResourceConflictException { |
| FixReplacement fixReplacement = |
| new FixReplacement( |
| "AnyPath", new Range(startLine, startChar, endLine, endChar), replacement); |
| return FixCalculator.calculateFix( |
| new Text(content.getBytes(UTF_8)), ImmutableList.of(fixReplacement), false); |
| } |
| |
| @Test |
| public void lineNumberMustBePositive() { |
| assertThrows( |
| ResourceConflictException.class, |
| () -> calculateFixSingleReplacement("First line\nSecond line", 0, 0, 0, 0, "Abc")); |
| } |
| |
| @Test |
| public void lineNumberMustExist() { |
| assertThrows( |
| ResourceConflictException.class, |
| () -> calculateFixSingleReplacement("First line\nSecond line", 4, 0, 4, 0, "Abc")); |
| } |
| |
| @Test |
| public void startOffsetMustNotBeNegative() { |
| assertThrows( |
| ResourceConflictException.class, |
| () -> calculateFixSingleReplacement("First line\nSecond line", 0, -1, 0, 0, "Abc")); |
| } |
| |
| @Test |
| public void endOffsetMustNotBeNegative() { |
| assertThrows( |
| ResourceConflictException.class, |
| () -> calculateFixSingleReplacement("First line\nSecond line", 0, 0, 0, -1, "Abc")); |
| } |
| |
| @Test |
| public void insertAtTheEndOfSingleLineContentHasEOLMarkInvalidPosition() { |
| assertThrows( |
| ResourceConflictException.class, |
| () -> calculateFixSingleReplacement("First line\n", 1, 11, 1, 11, "Abc")); |
| } |
| |
| @Test |
| public void startAfterEndOfLineMarkOfIntermediateLineThrowsAnException() { |
| assertThrows( |
| ResourceConflictException.class, |
| () -> |
| calculateFixSingleReplacement( |
| "First line\nSecond line\nThird line\n", 1, 11, 2, 6, "Abc")); |
| } |
| |
| @Test |
| public void startAfterEndOfLineMarkOfLastLineThrowsAnException() { |
| assertThrows( |
| ResourceConflictException.class, |
| () -> calculateFixSingleReplacement("First line\n", 1, 11, 2, 0, "Abc")); |
| } |
| |
| @Test |
| public void endAfterEndOfLineMarkOfIntermediateLineThrowsAnException() { |
| assertThrows( |
| ResourceConflictException.class, |
| () -> |
| calculateFixSingleReplacement( |
| "First line\nSecond line\nThird line\n", 2, 0, 2, 12, "Abc")); |
| } |
| |
| @Test |
| public void endAfterEndOfLineMarkOfLastLineThrowsAnException() { |
| assertThrows( |
| ResourceConflictException.class, |
| () -> calculateFixSingleReplacement("First line\nSecond line\n", 2, 0, 2, 12, "Abc")); |
| } |
| |
| @Test |
| public void severalChangesInTheSameLineNonSorted() throws Exception { |
| FixReplacement replace = new FixReplacement("path", new Range(2, 1, 2, 3), "ABC"); |
| FixReplacement insert = new FixReplacement("path", new Range(2, 5, 2, 5), "DEFG"); |
| FixReplacement delete = new FixReplacement("path", new Range(2, 7, 2, 9), ""); |
| FixResult result = |
| FixCalculator.calculateFix( |
| multilineContent, ImmutableList.of(replace, delete, insert), false); |
| assertThat(result) |
| .text() |
| .isEqualTo("First line\nSABConDEFGd ne\nThird line\nFourth line\nFifth line\n"); |
| assertThat(result).edits().hasSize(1); |
| Edit edit = result.edits.get(0); |
| assertThat(edit).isReplace(1, 1, 1, 1); |
| assertThat(edit).internalEdits().hasSize(3); |
| assertThat(edit).internalEdits().element(0).isReplace(1, 2, 1, 3); |
| assertThat(edit).internalEdits().element(1).isInsert(5, 6, 4); |
| assertThat(edit).internalEdits().element(2).isDelete(7, 2, 12); |
| } |
| |
| @Test |
| public void severalChangesInConsecutiveLines() throws Exception { |
| FixReplacement replace = new FixReplacement("path", new Range(2, 1, 2, 3), "ABC"); |
| FixReplacement insert = new FixReplacement("path", new Range(3, 5, 3, 5), "DEFG"); |
| FixReplacement delete = new FixReplacement("path", new Range(4, 7, 4, 9), ""); |
| FixResult result = |
| FixCalculator.calculateFix( |
| multilineContent, ImmutableList.of(replace, insert, delete), false); |
| assertThat(result) |
| .text() |
| .isEqualTo("First line\nSABCond line\nThirdDEFG line\nFourth ne\nFifth line\n"); |
| assertThat(result).edits().hasSize(1); |
| Edit edit = result.edits.get(0); |
| assertThat(edit).isReplace(1, 3, 1, 3); |
| assertThat(edit).internalEdits().hasSize(3); |
| assertThat(edit).internalEdits().element(0).isReplace(1, 2, 1, 3); |
| assertThat(edit).internalEdits().element(1).isInsert(17, 18, 4); |
| assertThat(edit).internalEdits().element(2).isDelete(30, 2, 35); |
| } |
| |
| @Test |
| public void intraline() throws Exception { |
| FixReplacement replace = new FixReplacement("path", new Range(2, 0, 2, 11), "Second ABC line"); |
| FixResult result = |
| FixCalculator.calculateFix(multilineContent, ImmutableList.of(replace), true); |
| assertThat(result) |
| .text() |
| .isEqualTo("First line\nSecond ABC line\nThird line\nFourth line\nFifth line\n"); |
| assertThat(result).edits().hasSize(1); |
| Edit edit = result.edits.get(0); |
| assertThat(edit).isReplace(1, 1, 1, 1); |
| assertThat(edit).internalEdits().hasSize(1); |
| assertThat(edit).internalEdits().element(0).isInsert(7, 7, 4); |
| } |
| |
| @Test |
| public void severalChangesInNonConsecutiveLines() throws Exception { |
| FixReplacement replace = new FixReplacement("path", new Range(1, 1, 1, 3), "ABC"); |
| FixReplacement insert = new FixReplacement("path", new Range(3, 5, 3, 5), "DEFG"); |
| FixReplacement delete = new FixReplacement("path", new Range(5, 9, 6, 0), ""); |
| FixResult result = |
| FixCalculator.calculateFix( |
| multilineContent, ImmutableList.of(replace, insert, delete), false); |
| assertThat(result) |
| .text() |
| .isEqualTo("FABCst line\nSecond line\nThirdDEFG line\nFourth line\nFifth lin"); |
| assertThat(result).edits().hasSize(3); |
| assertThat(result).edits().element(0).isReplace(0, 1, 0, 1); |
| assertThat(result).edits().element(0).internalEdits().onlyElement().isReplace(1, 2, 1, 3); |
| assertThat(result).edits().element(1).isReplace(2, 1, 2, 1); |
| assertThat(result).edits().element(1).internalEdits().onlyElement().isInsert(5, 5, 4); |
| assertThat(result).edits().element(2).isReplace(4, 1, 4, 1); |
| assertThat(result).edits().element(2).internalEdits().onlyElement().isDelete(9, 2, 9); |
| } |
| |
| @Test |
| public void multipleChanges() throws Exception { |
| String str = |
| "First line\nSecond line\nThird line\nFourth line\nFifth line\nSixth line" |
| + "\nSeventh line\nEighth line\nNinth line\nTenth line\n"; |
| Text content = new Text(str.getBytes(UTF_8)); |
| |
| FixReplacement multiLineReplace = |
| new FixReplacement("path", new Range(1, 2, 3, 3), "AB\nC\nDEFG\nQ\n"); |
| FixReplacement multiLineDelete = new FixReplacement("path", new Range(4, 8, 5, 8), ""); |
| FixReplacement singleLineInsert = new FixReplacement("path", new Range(5, 10, 5, 10), "QWERTY"); |
| |
| FixReplacement singleLineReplace = new FixReplacement("path", new Range(7, 3, 7, 7), "XY"); |
| FixReplacement multiLineInsert = |
| new FixReplacement("path", new Range(8, 7, 8, 7), "KLMNO\nASDF"); |
| |
| FixReplacement singleLineDelete = new FixReplacement("path", new Range(10, 3, 10, 7), ""); |
| |
| FixResult result = |
| FixCalculator.calculateFix( |
| content, |
| ImmutableList.of( |
| multiLineReplace, |
| multiLineDelete, |
| singleLineInsert, |
| singleLineReplace, |
| multiLineInsert, |
| singleLineDelete), |
| false); |
| assertThat(result) |
| .text() |
| .isEqualTo( |
| "FiAB\n" |
| + "C\n" |
| + "DEFG\n" |
| + "Q\n" |
| + "rd line\n" |
| + "Fourth lneQWERTY\n" |
| + "Sixth line\n" |
| + "SevXY line\n" |
| + "Eighth KLMNO\n" |
| + "ASDFline\n" |
| + "Ninth line\n" |
| + "Tenine\n"); |
| assertThat(result).edits().hasSize(3); |
| assertThat(result).edits().element(0).isReplace(0, 5, 0, 6); |
| assertThat(result) |
| .edits() |
| .element(0) |
| .internalEdits() |
| .containsExactly( |
| new Edit(2, 26, 2, 14), new Edit(42, 54, 30, 30), new Edit(56, 56, 32, 38)); |
| |
| assertThat(result).edits().element(1).isReplace(6, 2, 7, 3); |
| assertThat(result) |
| .edits() |
| .element(1) |
| .internalEdits() |
| .containsExactly(new Edit(3, 7, 3, 5), new Edit(20, 20, 18, 28)); |
| assertThat(result).edits().element(2).isReplace(9, 1, 11, 1); |
| assertThat(result).edits().element(2).internalEdits().onlyElement().isDelete(3, 4, 3); |
| } |
| |
| @Test |
| public void changesMayTouch() throws Exception { |
| FixReplacement firstReplace = new FixReplacement("path", new Range(1, 6, 2, 7), "modified "); |
| FixReplacement consecutiveReplace = |
| new FixReplacement("path", new Range(2, 7, 3, 5), "content"); |
| FixResult result = |
| FixCalculator.calculateFix( |
| multilineContent, ImmutableList.of(firstReplace, consecutiveReplace), false); |
| assertThat(result).text().isEqualTo("First modified content line\nFourth line\nFifth line\n"); |
| assertThat(result).edits().hasSize(1); |
| Edit edit = result.edits.get(0); |
| assertThat(edit).isReplace(0, 3, 0, 1); |
| // The current code creates two inline edits even though only one would be necessary. It |
| // shouldn't make a visual difference to the user and hence we can ignore this. |
| assertThat(edit).internalEdits().hasSize(2); |
| assertThat(edit).internalEdits().element(0).isReplace(6, 12, 6, 9); |
| assertThat(edit).internalEdits().element(1).isReplace(18, 10, 15, 7); |
| } |
| |
| @Test |
| public void overlappingChangesInMiddleOfLineRaisesException() throws Exception { |
| FixReplacement firstReplace = |
| new FixReplacement("path", new Range(2, 0, 3, 5), "First modification\n"); |
| FixReplacement secondReplace = |
| new FixReplacement("path", new Range(3, 4, 4, 0), "Some other modified content\n"); |
| assertThrows( |
| ResourceConflictException.class, |
| () -> |
| FixCalculator.calculateFix( |
| multilineContent, ImmutableList.of(firstReplace, secondReplace), false)); |
| } |
| |
| @Test |
| public void overlappingChangesInBeginningOfLineRaisesException() throws Exception { |
| FixReplacement firstReplace = |
| new FixReplacement("path", new Range(2, 0, 3, 1), "First modification\n"); |
| FixReplacement secondReplace = |
| new FixReplacement("path", new Range(3, 0, 4, 0), "Some other modified content\n"); |
| assertThrows( |
| ResourceConflictException.class, |
| () -> |
| FixCalculator.calculateFix( |
| multilineContent, ImmutableList.of(firstReplace, secondReplace), false)); |
| } |
| } |