// Copyright (C) 2017 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; import static com.google.gerrit.server.edit.tree.TreeModificationSubject.assertThatList; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.replay; import com.google.common.collect.ImmutableList; import com.google.gerrit.extensions.restapi.BinaryResult; import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.reviewdb.client.Comment.Range; import com.google.gerrit.reviewdb.client.FixReplacement; import com.google.gerrit.server.change.FileContentUtil; import com.google.gerrit.server.edit.tree.TreeModification; import com.google.gerrit.server.project.ProjectState; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import org.easymock.EasyMock; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; public class FixReplacementInterpreterTest { @Rule public ExpectedException expectedException = ExpectedException.none(); private final FileContentUtil fileContentUtil = createMock(FileContentUtil.class); private final Repository repository = createMock(Repository.class); private final ProjectState projectState = createMock(ProjectState.class); private final ObjectId patchSetCommitId = createMock(ObjectId.class); private final String filePath1 = "an/arbitrary/file.txt"; private final String filePath2 = "another/arbitrary/file.txt"; private FixReplacementInterpreter fixReplacementInterpreter; @Before public void setUp() { fixReplacementInterpreter = new FixReplacementInterpreter(fileContentUtil); } @Test public void noReplacementsResultInNoTreeModifications() throws Exception { List<TreeModification> treeModifications = toTreeModifications(); assertThatList(treeModifications).isEmpty(); } @Test public void treeModificationsTargetCorrectFiles() throws Exception { FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(1, 6, 3, 2), "Modified content"); FixReplacement fixReplacement2 = new FixReplacement(filePath1, new Range(3, 5, 3, 5), "Second modification"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); FixReplacement fixReplacement3 = new FixReplacement(filePath2, new Range(2, 0, 3, 0), "Another modified content"); mockFileContent(filePath2, "1st line\n2nd line\n3rd line\n"); replay(fileContentUtil); List<TreeModification> treeModifications = toTreeModifications(fixReplacement, fixReplacement3, fixReplacement2); List<TreeModification> sortedTreeModifications = getSortedCopy(treeModifications); assertThatList(sortedTreeModifications) .element(0) .asChangeFileContentModification() .filePath() .isEqualTo(filePath1); assertThatList(sortedTreeModifications) .element(0) .asChangeFileContentModification() .newContent() .startsWith("First"); assertThatList(sortedTreeModifications) .element(1) .asChangeFileContentModification() .filePath() .isEqualTo(filePath2); assertThatList(sortedTreeModifications) .element(1) .asChangeFileContentModification() .newContent() .startsWith("1st"); } @Test public void replacementsCanDeleteALine() throws Exception { FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(2, 0, 3, 0), ""); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); replay(fileContentUtil); List<TreeModification> treeModifications = toTreeModifications(fixReplacement); assertThatList(treeModifications) .onlyElement() .asChangeFileContentModification() .newContent() .isEqualTo("First line\nThird line\n"); } @Test public void replacementsCanAddALine() throws Exception { FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(2, 0, 2, 0), "A new line\n"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); replay(fileContentUtil); List<TreeModification> treeModifications = toTreeModifications(fixReplacement); assertThatList(treeModifications) .onlyElement() .asChangeFileContentModification() .newContent() .isEqualTo("First line\nA new line\nSecond line\nThird line\n"); } @Test public void replacementsMaySpanMultipleLines() throws Exception { FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(1, 6, 3, 1), "and t"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); replay(fileContentUtil); List<TreeModification> treeModifications = toTreeModifications(fixReplacement); assertThatList(treeModifications) .onlyElement() .asChangeFileContentModification() .newContent() .isEqualTo("First and third line\n"); } @Test public void replacementsMayOccurOnSameLine() throws Exception { FixReplacement fixReplacement1 = new FixReplacement(filePath1, new Range(2, 0, 2, 6), "A"); FixReplacement fixReplacement2 = new FixReplacement(filePath1, new Range(2, 7, 2, 11), "modification"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); replay(fileContentUtil); List<TreeModification> treeModifications = toTreeModifications(fixReplacement1, fixReplacement2); assertThatList(treeModifications) .onlyElement() .asChangeFileContentModification() .newContent() .isEqualTo("First line\nA modification\nThird line\n"); } @Test public void replacementsMayTouch() throws Exception { FixReplacement fixReplacement1 = new FixReplacement(filePath1, new Range(1, 6, 2, 7), "modified "); FixReplacement fixReplacement2 = new FixReplacement(filePath1, new Range(2, 7, 3, 5), "content"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); replay(fileContentUtil); List<TreeModification> treeModifications = toTreeModifications(fixReplacement1, fixReplacement2); assertThatList(treeModifications) .onlyElement() .asChangeFileContentModification() .newContent() .isEqualTo("First modified content line\n"); } @Test public void replacementsCanAddContentAtEndOfFile() throws Exception { FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(4, 0, 4, 0), "New content"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); replay(fileContentUtil); List<TreeModification> treeModifications = toTreeModifications(fixReplacement); assertThatList(treeModifications) .onlyElement() .asChangeFileContentModification() .newContent() .isEqualTo("First line\nSecond line\nThird line\nNew content"); } @Test public void replacementsCanModifySeveralFilesInAnyOrder() throws Exception { FixReplacement fixReplacement1 = new FixReplacement(filePath1, new Range(1, 1, 3, 2), "Modified content"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); FixReplacement fixReplacement2 = new FixReplacement(filePath2, new Range(2, 0, 3, 0), "First modification\n"); FixReplacement fixReplacement3 = new FixReplacement(filePath2, new Range(3, 0, 4, 0), "Second modification\n"); mockFileContent(filePath2, "1st line\n2nd line\n3rd line\n"); replay(fileContentUtil); List<TreeModification> treeModifications = toTreeModifications(fixReplacement3, fixReplacement1, fixReplacement2); List<TreeModification> sortedTreeModifications = getSortedCopy(treeModifications); assertThatList(sortedTreeModifications) .element(0) .asChangeFileContentModification() .newContent() .isEqualTo("FModified contentird line\n"); assertThatList(sortedTreeModifications) .element(1) .asChangeFileContentModification() .newContent() .isEqualTo("1st line\nFirst modification\nSecond modification\n"); } @Test public void lineSeparatorCanBeChanged() throws Exception { FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(2, 11, 3, 0), "\r"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); replay(fileContentUtil); List<TreeModification> treeModifications = toTreeModifications(fixReplacement); assertThatList(treeModifications) .onlyElement() .asChangeFileContentModification() .newContent() .isEqualTo("First line\nSecond line\rThird line\n"); } @Test public void replacementsDoNotNeedToBeOrderedAccordingToRange() throws Exception { FixReplacement fixReplacement1 = new FixReplacement(filePath1, new Range(1, 0, 2, 0), "1st modification\n"); FixReplacement fixReplacement2 = new FixReplacement(filePath1, new Range(3, 0, 4, 0), "2nd modification\n"); FixReplacement fixReplacement3 = new FixReplacement(filePath1, new Range(4, 0, 5, 0), "3rd modification\n"); mockFileContent(filePath1, "First line\nSecond line\nThird line\nFourth line\nFifth line\n"); replay(fileContentUtil); List<TreeModification> treeModifications = toTreeModifications(fixReplacement2, fixReplacement1, fixReplacement3); assertThatList(treeModifications) .onlyElement() .asChangeFileContentModification() .newContent() .isEqualTo( "1st modification\nSecond line\n2nd modification\n3rd modification\nFifth line\n"); } @Test public void replacementsMustNotReferToNotExistingLine() throws Exception { FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(5, 0, 5, 0), "A new line\n"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); replay(fileContentUtil); expectedException.expect(ResourceConflictException.class); toTreeModifications(fixReplacement); } @Test public void replacementsMustNotReferToZeroLine() throws Exception { FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(0, 0, 0, 0), "A new line\n"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); replay(fileContentUtil); expectedException.expect(ResourceConflictException.class); toTreeModifications(fixReplacement); } @Test public void replacementsMustNotReferToNotExistingOffsetOfIntermediateLine() throws Exception { FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(1, 0, 1, 11), "modified"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); replay(fileContentUtil); expectedException.expect(ResourceConflictException.class); toTreeModifications(fixReplacement); } @Test public void replacementsMustNotReferToNotExistingOffsetOfLastLine() throws Exception { FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(3, 0, 3, 11), "modified"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); replay(fileContentUtil); expectedException.expect(ResourceConflictException.class); toTreeModifications(fixReplacement); } @Test public void replacementsMustNotReferToNegativeOffset() throws Exception { FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(1, -1, 1, 5), "modified"); mockFileContent(filePath1, "First line\nSecond line\nThird line\n"); replay(fileContentUtil); expectedException.expect(ResourceConflictException.class); toTreeModifications(fixReplacement); } private void mockFileContent(String filePath, String fileContent) throws Exception { EasyMock.expect( fileContentUtil.getContent(repository, projectState, patchSetCommitId, filePath)) .andReturn(BinaryResult.create(fileContent)); } private List<TreeModification> toTreeModifications(FixReplacement... fixReplacements) throws Exception { return fixReplacementInterpreter.toTreeModifications( repository, projectState, patchSetCommitId, ImmutableList.copyOf(fixReplacements)); } private static List<TreeModification> getSortedCopy(List<TreeModification> treeModifications) { List<TreeModification> sortedTreeModifications = new ArrayList<>(treeModifications); sortedTreeModifications.sort(Comparator.comparing(TreeModification::getFilePath)); return sortedTreeModifications; } }