/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.duplications.detector;
import com.google.common.collect.Lists;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.duplications.block.Block;
import org.sonar.duplications.block.ByteArray;
import org.sonar.duplications.index.CloneGroup;
import org.sonar.duplications.index.CloneIndex;
import org.sonar.duplications.index.ClonePart;
import org.sonar.duplications.index.MemoryCloneIndex;
import org.sonar.duplications.junit.TestNamePrinter;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.sonar.duplications.detector.CloneGroupMatcher.hasCloneGroup;
public abstract class DetectorTestCase {
@Rule
public TestNamePrinter testNamePrinter = new TestNamePrinter();
protected static int LINES_PER_BLOCK = 5;
/**
* To simplify testing we assume that each block starts from a new line and contains {@link #LINES_PER_BLOCK} lines,
* so we can simply use index and hash.
*/
protected static Block newBlock(String resourceId, ByteArray hash, int index) {
return Block.builder()
.setResourceId(resourceId)
.setBlockHash(hash)
.setIndexInFile(index)
.setLines(index, index + LINES_PER_BLOCK)
.build();
}
protected static ClonePart newClonePart(String resourceId, int unitStart, int cloneUnitLength) {
return new ClonePart(resourceId, unitStart, unitStart, unitStart + cloneUnitLength + LINES_PER_BLOCK - 1);
}
protected abstract List<CloneGroup> detect(CloneIndex index, Block[] fileBlocks);
/**
* Given:
* <pre>
* y: 2 3 4 5
* z: 3 4
* x: 1 2 3 4 5 6
* </pre>
* Expected:
* <pre>
* x-y (2 3 4 5)
* x-y-z (3 4)
* </pre>
*/
@Test
public void exampleFromPaper() {
CloneIndex index = createIndex(
newBlocks("y", "2 3 4 5"),
newBlocks("z", "3 4"));
Block[] fileBlocks = newBlocks("x", "1 2 3 4 5 6");
List<CloneGroup> result = detect(index, fileBlocks);
print(result);
assertEquals(2, result.size());
assertThat(result, hasCloneGroup(4,
newClonePart("x", 1, 4),
newClonePart("y", 0, 4)));
assertThat(result, hasCloneGroup(2,
newClonePart("x", 2, 2),
newClonePart("y", 1, 2),
newClonePart("z", 0, 2)));
}
/**
* Given:
* <pre>
* a: 2 3 4 5
* b: 3 4
* c: 1 2 3 4 5 6
* </pre>
* Expected:
* <pre>
* c-a (2 3 4 5)
* c-a-b (3 4)
* </pre>
*/
@Test
public void exampleFromPaperWithModifiedResourceIds() {
CloneIndex cloneIndex = createIndex(
newBlocks("a", "2 3 4 5"),
newBlocks("b", "3 4"));
Block[] fileBlocks = newBlocks("c", "1 2 3 4 5 6");
List<CloneGroup> clones = detect(cloneIndex, fileBlocks);
print(clones);
assertThat(clones.size(), is(2));
assertThat(clones, hasCloneGroup(4,
newClonePart("c", 1, 4),
newClonePart("a", 0, 4)));
assertThat(clones, hasCloneGroup(2,
newClonePart("c", 2, 2),
newClonePart("a", 1, 2),
newClonePart("b", 0, 2)));
}
/**
* Given:
* <pre>
* b: 3 4 5 6
* c: 5 6 7
* a: 1 2 3 4 5 6 7 8 9
* </pre>
* Expected:
* <pre>
* a-b (3 4 5 6)
* a-b-c (5 6)
* a-c (5 6 7)
* </pre>
*/
@Test
public void example1() {
CloneIndex index = createIndex(
newBlocks("b", "3 4 5 6"),
newBlocks("c", "5 6 7"));
Block[] fileBlocks = newBlocks("a", "1 2 3 4 5 6 7 8 9");
List<CloneGroup> result = detect(index, fileBlocks);
print(result);
assertThat(result.size(), is(3));
assertThat(result, hasCloneGroup(4,
newClonePart("a", 2, 4),
newClonePart("b", 0, 4)));
assertThat(result, hasCloneGroup(3,
newClonePart("a", 4, 3),
newClonePart("c", 0, 3)));
assertThat(result, hasCloneGroup(2,
newClonePart("a", 4, 2),
newClonePart("b", 2, 2),
newClonePart("c", 0, 2)));
}
/**
* Given:
* <pre>
* b: 1 2 3 4 1 2 3 4 1 2 3 4
* c: 1 2 3 4
* a: 1 2 3 5
* </pre>
* Expected:
* <pre>
* a-b-b-b-c (1 2 3)
* </pre>
*/
@Test
public void example2() {
CloneIndex index = createIndex(
newBlocks("b", "1 2 3 4 1 2 3 4 1 2 3 4"),
newBlocks("c", "1 2 3 4"));
Block[] fileBlocks = newBlocks("a", "1 2 3 5");
List<CloneGroup> result = detect(index, fileBlocks);
print(result);
assertThat(result.size(), is(1));
assertThat(result, hasCloneGroup(3,
newClonePart("a", 0, 3),
newClonePart("b", 0, 3),
newClonePart("b", 4, 3),
newClonePart("b", 8, 3),
newClonePart("c", 0, 3)));
}
/**
* Test for problem, which was described in original paper - same clone would be reported twice.
* Given:
* <pre>
* a: 1 2 3 1 2 4
* </pre>
* Expected only one clone:
* <pre>
* a-a (1 2)
* </pre>
*/
@Test
public void clonesInFileItself() {
CloneIndex index = createIndex();
Block[] fileBlocks = newBlocks("a", "1 2 3 1 2 4");
List<CloneGroup> result = detect(index, fileBlocks);
print(result);
assertThat(result.size(), is(1));
assertThat(result, hasCloneGroup(2,
newClonePart("a", 0, 2),
newClonePart("a", 3, 2)));
}
/**
* Given:
* <pre>
* b: 1 2 1 2
* a: 1 2 1
* </pre>
* Expected:
* <pre>
* a-b-b (1 2)
* a-b (1 2 1)
* </pre>
* "a-a-b-b (1)" should not be reported, because fully covered by "a-b (1 2 1)"
*/
@Test
public void covered() {
CloneIndex index = createIndex(
newBlocks("b", "1 2 1 2"));
Block[] fileBlocks = newBlocks("a", "1 2 1");
List<CloneGroup> result = detect(index, fileBlocks);
print(result);
assertThat(result.size(), is(2));
assertThat(result, hasCloneGroup(3,
newClonePart("a", 0, 3),
newClonePart("b", 0, 3)));
assertThat(result, hasCloneGroup(2,
newClonePart("a", 0, 2),
newClonePart("b", 0, 2),
newClonePart("b", 2, 2)));
}
/**
* Given:
* <pre>
* b: 1 2 1 2 1 2 1
* a: 1 2 1 2 1 2
* </pre>
* Expected:
* <pre>
* a-b-b (1 2 1 2 1) - note that there is overlapping among parts for "b"
* a-b (1 2 1 2 1 2)
* </pre>
*/
@Test
public void problemWithNestedCloneGroups() {
CloneIndex index = createIndex(
newBlocks("b", "1 2 1 2 1 2 1"));
Block[] fileBlocks = newBlocks("a", "1 2 1 2 1 2");
List<CloneGroup> result = detect(index, fileBlocks);
print(result);
assertThat(result.size(), is(2));
assertThat(result, hasCloneGroup(6,
newClonePart("a", 0, 6),
newClonePart("b", 0, 6)));
assertThat(result, hasCloneGroup(5,
newClonePart("a", 0, 5),
newClonePart("b", 0, 5),
newClonePart("b", 2, 5)));
}
/**
* Given:
* <pre>
* a: 1 2 3
* b: 1 2 4
* a: 1 2 5
* </pre>
* Expected:
* <pre>
* a-b (1 2) - instead of "a-a-b", which will be the case if file from index not ignored
* </pre>
*/
@Test
public void fileAlreadyInIndex() {
CloneIndex index = createIndex(
newBlocks("a", "1 2 3"),
newBlocks("b", "1 2 4"));
// Note about blocks with hashes "3", "4" and "5": those blocks here in order to not face another problem - with EOF (see separate test)
Block[] fileBlocks = newBlocks("a", "1 2 5");
List<CloneGroup> result = detect(index, fileBlocks);
print(result);
assertThat(result.size(), is(1));
assertThat(result, hasCloneGroup(2,
newClonePart("a", 0, 2),
newClonePart("b", 0, 2)));
}
/**
* Given: file with repeated hashes
* Expected: only one query of index for each unique hash
*/
@Test
public void only_one_query_of_index_for_each_unique_hash() {
CloneIndex index = spy(createIndex());
Block[] fileBlocks = newBlocks("a", "1 2 1 2");
detect(index, fileBlocks);
verify(index).getBySequenceHash(new ByteArray("01"));
verify(index).getBySequenceHash(new ByteArray("02"));
verifyNoMoreInteractions(index);
}
/**
* Given: empty list of blocks for file
* Expected: {@link Collections#EMPTY_LIST}
*/
@Test
public void shouldReturnEmptyListWhenNoBlocksForFile() {
List<CloneGroup> result = detect(null, new Block[0]);
assertThat(result, sameInstance(Collections.EMPTY_LIST));
}
/**
* Given:
* <pre>
* b: 1 2 3 4
* a: 1 2 3
* </pre>
* Expected clone which ends at the end of file "a":
* <pre>
* a-b (1 2 3)
* </pre>
*/
@Test
public void problemWithEndOfFile() {
CloneIndex cloneIndex = createIndex(
newBlocks("b", "1 2 3 4"));
Block[] fileBlocks =
newBlocks("a", "1 2 3");
List<CloneGroup> clones = detect(cloneIndex, fileBlocks);
print(clones);
assertThat(clones.size(), is(1));
assertThat(clones, hasCloneGroup(3,
newClonePart("a", 0, 3),
newClonePart("b", 0, 3)));
}
/**
* Given file with two lines, containing following statements:
* <pre>
* 0: A,B,A,B
* 1: A,B,A
* </pre>
* with block size 5 each block will span both lines, and hashes will be:
* <pre>
* A,B,A,B,A=1
* B,A,B,A,B=2
* A,B,A,B,A=1
* </pre>
* Expected: one clone with two parts, which contain exactly the same lines
*/
@Test
public void same_lines_but_different_indexes() {
CloneIndex cloneIndex = createIndex();
Block.Builder block = Block.builder()
.setResourceId("a")
.setLines(0, 1);
Block[] fileBlocks = new Block[]{
block.setBlockHash(new ByteArray("1".getBytes())).setIndexInFile(0).build(),
block.setBlockHash(new ByteArray("2".getBytes())).setIndexInFile(1).build(),
block.setBlockHash(new ByteArray("1".getBytes())).setIndexInFile(2).build()
};
List<CloneGroup> clones = detect(cloneIndex, fileBlocks);
print(clones);
assertThat(clones.size(), is(1));
Iterator<CloneGroup> clonesIterator = clones.iterator();
CloneGroup clone = clonesIterator.next();
assertThat(clone.getCloneUnitLength(), is(1));
assertThat(clone.getCloneParts().size(), is(2));
assertThat(clone.getOriginPart(), is(new ClonePart("a", 0, 0, 1)));
assertThat(clone.getCloneParts(), hasItem(new ClonePart("a", 0, 0, 1)));
assertThat(clone.getCloneParts(), hasItem(new ClonePart("a", 2, 0, 1)));
}
protected static void print(List<CloneGroup> clones) {
for (CloneGroup clone : clones) {
System.out.println(clone);
}
System.out.println();
}
protected static Block[] newBlocks(String resourceId, String hashes) {
List<Block> result = Lists.newArrayList();
int indexInFile = 0;
for (int i = 0; i < hashes.length(); i += 2) {
Block block = newBlock(resourceId, new ByteArray("0" + hashes.charAt(i)), indexInFile);
result.add(block);
indexInFile++;
}
return result.toArray(new Block[result.size()]);
}
protected static CloneIndex createIndex(Block[]... blocks) {
CloneIndex cloneIndex = new MemoryCloneIndex();
for (Block[] b : blocks) {
for (Block block : b) {
cloneIndex.insert(block);
}
}
return cloneIndex;
}
}