diff options
Diffstat (limited to 'sonar-duplications/src/test/java/org/sonar/duplications')
32 files changed, 3260 insertions, 0 deletions
diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/DuplicationsTestUtil.java b/sonar-duplications/src/test/java/org/sonar/duplications/DuplicationsTestUtil.java new file mode 100644 index 00000000000..c1a37bba1ae --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/DuplicationsTestUtil.java @@ -0,0 +1,32 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications; + +import java.io.File; + +public class DuplicationsTestUtil { + + public static final File fileDir = new File("src/test/files/"); + + public static File findFile(String relativePathToFile) { + return new File(fileDir, relativePathToFile); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/block/BlockChunkerTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/block/BlockChunkerTest.java new file mode 100644 index 00000000000..548b8e0c436 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/block/BlockChunkerTest.java @@ -0,0 +1,63 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.block; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.List; + +import org.junit.Test; +import org.sonar.duplications.statement.Statement; + +public class BlockChunkerTest extends BlockChunkerTestCase { + + @Override + protected BlockChunker createChunkerWithBlockSize(int blockSize) { + return new BlockChunker(blockSize); + } + + /** + * Rolling hash must produce exactly the same values as without rolling behavior. + * Moreover those values must always be the same (without dependency on JDK). + */ + @Test + public void shouldCalculateHashes() { + List<Statement> statements = createStatementsFromStrings("aaaaaa", "bbbbbb", "cccccc", "dddddd", "eeeeee"); + BlockChunker blockChunker = createChunkerWithBlockSize(3); + List<Block> blocks = blockChunker.chunk("resource", statements); + assertThat(blocks.get(0).getBlockHash(), equalTo(hash("aaaaaa", "bbbbbb", "cccccc"))); + assertThat(blocks.get(1).getBlockHash(), equalTo(hash("bbbbbb", "cccccc", "dddddd"))); + assertThat(blocks.get(2).getBlockHash(), equalTo(hash("cccccc", "dddddd", "eeeeee"))); + assertThat(blocks.get(0).getBlockHash().toString(), is("fffffeb6ae1af4c0")); + assertThat(blocks.get(1).getBlockHash().toString(), is("fffffebd8512d120")); + assertThat(blocks.get(2).getBlockHash().toString(), is("fffffec45c0aad80")); + } + + private ByteArray hash(String... statements) { + long hash = 0; + for (String statement : statements) { + hash = hash * 31 + statement.hashCode(); + } + return new ByteArray(hash); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/block/BlockChunkerTestCase.java b/sonar-duplications/src/test/java/org/sonar/duplications/block/BlockChunkerTestCase.java new file mode 100644 index 00000000000..90413bb2287 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/block/BlockChunkerTestCase.java @@ -0,0 +1,145 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.block; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertThat; + +import java.util.Collections; +import java.util.List; + +import org.junit.Test; +import org.sonar.duplications.statement.Statement; + +import com.google.common.collect.Lists; + +/** + * Any implementation of {@link BlockChunker} should pass these test scenarios. + */ +public abstract class BlockChunkerTestCase { + + /** + * Factory method. + */ + protected abstract BlockChunker createChunkerWithBlockSize(int blockSize); + + /** + * Given: + * <pre> + * String[][] data = { + * {"a", "a"}, + * {"a", "a"}, + * {"a"}, + * {"a", "a"}, + * {"a", "a"} + * }; + * + * Statements (where L - literal, C - comma): "LCL", "C", "LCL", "C", "L", "C", "LCL", "C", "LCL" + * Block size is 5. + * First block: "LCL", "C", "LCL", "C", "L" + * Last block: "L", "C", "LCL", "C", "LCL" + * </pre> + * Expected: different hashes for first and last blocks + */ + @Test + public void testSameChars() { + List<Statement> statements = createStatementsFromStrings("LCL", "C", "LCL", "C", "L", "C", "LCL", "C", "LCL"); + BlockChunker chunker = createChunkerWithBlockSize(5); + List<Block> blocks = chunker.chunk("resource", statements); + assertThat("first and last block should have different hashes", blocks.get(0).getBlockHash(), not(equalTo(blocks.get(blocks.size() - 1).getBlockHash()))); + } + + /** + * TODO Godin: should we allow empty statements in general? + */ + @Test + public void testEmptyStatements() { + List<Statement> statements = createStatementsFromStrings("1", "", "1", "1", ""); + BlockChunker chunker = createChunkerWithBlockSize(3); + List<Block> blocks = chunker.chunk("resource", statements); + assertThat("first and last block should have different hashes", blocks.get(0).getBlockHash(), not(equalTo(blocks.get(blocks.size() - 1).getBlockHash()))); + } + + /** + * Given: 5 statements, block size is 3 + * Expected: 4 blocks with correct index and with line numbers + */ + @Test + public void shouldBuildBlocksFromStatements() { + List<Statement> statements = createStatementsFromStrings("1", "2", "3", "4", "5", "6"); + BlockChunker chunker = createChunkerWithBlockSize(3); + List<Block> blocks = chunker.chunk("resource", statements); + assertThat(blocks.size(), is(4)); + assertThat(blocks.get(0).getIndexInFile(), is(0)); + assertThat(blocks.get(0).getFirstLineNumber(), is(0)); + assertThat(blocks.get(0).getLastLineNumber(), is(2)); + assertThat(blocks.get(1).getIndexInFile(), is(1)); + assertThat(blocks.get(1).getFirstLineNumber(), is(1)); + assertThat(blocks.get(1).getLastLineNumber(), is(3)); + } + + @Test + public void testHashes() { + List<Statement> statements = createStatementsFromStrings("1", "2", "1", "2"); + BlockChunker chunker = createChunkerWithBlockSize(2); + List<Block> blocks = chunker.chunk("resource", statements); + assertThat("blocks 0 and 2 should have same hash", blocks.get(0).getBlockHash(), equalTo(blocks.get(2).getBlockHash())); + assertThat("blocks 0 and 1 should have different hash", blocks.get(0).getBlockHash(), not(equalTo(blocks.get(1).getBlockHash()))); + } + + /** + * Given: 0 statements + * Expected: 0 blocks + */ + @Test + public void shouldNotBuildBlocksWhenNoStatements() { + List<Statement> statements = Collections.emptyList(); + BlockChunker blockChunker = createChunkerWithBlockSize(2); + List<Block> blocks = blockChunker.chunk("resource", statements); + assertThat(blocks, sameInstance(Collections.EMPTY_LIST)); + } + + /** + * Given: 1 statement, block size is 2 + * Expected: 0 blocks + */ + @Test + public void shouldNotBuildBlocksWhenNotEnoughStatements() { + List<Statement> statements = createStatementsFromStrings("statement"); + BlockChunker blockChunker = createChunkerWithBlockSize(2); + List<Block> blocks = blockChunker.chunk("resource", statements); + assertThat(blocks, sameInstance(Collections.EMPTY_LIST)); + } + + /** + * Creates list of statements from Strings, each statement on a new line starting from 0. + */ + protected static List<Statement> createStatementsFromStrings(String... values) { + List<Statement> result = Lists.newArrayList(); + for (int i = 0; i < values.length; i++) { + result.add(new Statement(i, i, values[i])); + } + return result; + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/block/BlockTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/block/BlockTest.java new file mode 100644 index 00000000000..3aeba301d5a --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/block/BlockTest.java @@ -0,0 +1,80 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.block; + +import org.junit.Test; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +public class BlockTest { + + @Test + public void fieldsTest() { + String fileName = "someFile"; + int statementIndex = 4; + ByteArray hash = new ByteArray(12345); + Block tuple = new Block(fileName, hash, statementIndex, 0, 10); + assertThat(tuple.getResourceId(), equalTo(fileName)); + assertThat(tuple.getIndexInFile(), equalTo(statementIndex)); + assertEquals(tuple.getBlockHash(), hash); + } + + @Test + public void tupleEqualsTest() { + Block tuple1 = new Block("somefile", new ByteArray(123), 1, 1, 10); + Block tuple2 = new Block("somefile", new ByteArray(123), 1, 1, 10); + Block tupleArr = new Block("somefile", new ByteArray(333), 1, 1, 10); + Block tupleIndex = new Block("somefile", new ByteArray(123), 2, 1, 10); + Block tupleName = new Block("other", new ByteArray(123), 1, 1, 10); + + assertTrue(tuple1.equals(tuple2)); + assertThat(tuple1.toString(), is(tuple2.toString())); + + assertFalse(tuple1.equals(tupleArr)); + assertThat(tuple1.toString(), not(equalTo(tupleArr.toString()))); + + assertFalse(tuple1.equals(tupleIndex)); + assertThat(tuple1.toString(), not(equalTo(tupleIndex.toString()))); + + assertFalse(tuple1.equals(tupleName)); + assertThat(tuple1.toString(), not(equalTo(tupleName.toString()))); + } + + @Test + public void hashCodeTest() { + String[] files = {"file1", "file2"}; + int[] unitIndexes = {1, 2}; + ByteArray[] arrays = {new ByteArray(123), new ByteArray(321)}; + + // fileName is in hashCode() + int defaultTupleHashCode = new Block(files[0], arrays[0], unitIndexes[0], 1, 10).hashCode(); + int fileNameTupleHashCode = new Block(files[1], arrays[0], unitIndexes[0], 1, 10).hashCode(); + assertThat(defaultTupleHashCode, not(equalTo(fileNameTupleHashCode))); + + // statementIndex is in hashCode() + int indexTupleHashCode = new Block(files[0], arrays[0], unitIndexes[1], 1, 10).hashCode(); + assertThat(defaultTupleHashCode, not(equalTo(indexTupleHashCode))); + + // sequenceHash is in hashCode() + int sequenceHashTupleHashCode = new Block(files[0], arrays[1], unitIndexes[0], 1, 10).hashCode(); + assertThat(defaultTupleHashCode, not(equalTo(sequenceHashTupleHashCode))); + } +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/block/ByteArrayTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/block/ByteArrayTest.java new file mode 100644 index 00000000000..c237439f42e --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/block/ByteArrayTest.java @@ -0,0 +1,66 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.block; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class ByteArrayTest { + + @Test + public void shouldCreateFromInt() { + int value = 0x12FF8413; + ByteArray byteArray = new ByteArray(value); + assertThat(byteArray.toString(), is(Integer.toHexString(value))); + } + + @Test + public void shouldCreateFromLong() { + long value = 0x12FF841344567899L; + ByteArray byteArray = new ByteArray(value); + assertThat(byteArray.toString(), is(Long.toHexString(value))); + } + + @Test + public void shouldCreateFromHexString() { + String value = "12FF841344567899"; + ByteArray byteArray = new ByteArray(value); + assertThat(byteArray.toString(), is(value.toLowerCase())); + } + + @Test + public void shouldCreateFromIntArray() { + ByteArray byteArray = new ByteArray(new int[] { 0x04121986 }); + assertThat(byteArray.toString(), is("04121986")); + } + + @Test + public void shouldConvertToIntArray() { + // number of bytes is enough to create exactly one int (4 bytes) + ByteArray byteArray = new ByteArray(new byte[] { 0x04, 0x12, 0x19, (byte) 0x86 }); + assertThat(byteArray.toIntArray(), is(new int[] { 0x04121986 })); + // number of bytes is more than 4, but less than 8, so anyway 2 ints + byteArray = new ByteArray(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x31 }); + assertThat(byteArray.toIntArray(), is(new int[] { 0x00000000, 0x31000000 })); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/detector/original/BlocksGroupTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/detector/original/BlocksGroupTest.java new file mode 100644 index 00000000000..a8c44f67784 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/detector/original/BlocksGroupTest.java @@ -0,0 +1,200 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.detector.original; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; +import org.sonar.duplications.block.Block; + +public class BlocksGroupTest { + + /** + * {@link BlocksGroup} uses only resourceId and index from block, thus we can simplify testing. + */ + private static Block newBlock(String resourceId, int indexInFile) { + return new Block(resourceId, null, indexInFile, indexInFile, indexInFile); + } + + public static BlocksGroup newBlocksGroup(Block... blocks) { + BlocksGroup result = BlocksGroup.empty(); + for (Block block : blocks) { + result.blocks.add(block); + } + return result; + } + + @Test + public void shouldReturnSize() { + BlocksGroup group = newBlocksGroup(newBlock("a", 1), newBlock("b", 2)); + assertThat(group.size(), is(2)); + } + + @Test + public void shouldCreateEmptyGroup() { + assertThat(BlocksGroup.empty().size(), is(0)); + } + + @Test + public void testSubsumedBy() { + BlocksGroup group1 = newBlocksGroup(newBlock("a", 1), newBlock("b", 2)); + BlocksGroup group2 = newBlocksGroup(newBlock("a", 2), newBlock("b", 3), newBlock("c", 4)); + // block "c" from group2 does not have corresponding block in group1 + assertThat(group2.subsumedBy(group1, 1), is(false)); + } + + @Test + public void testSubsumedBy2() { + BlocksGroup group1 = newBlocksGroup(newBlock("a", 1), newBlock("b", 2)); + BlocksGroup group2 = newBlocksGroup(newBlock("a", 2), newBlock("b", 3)); + BlocksGroup group3 = newBlocksGroup(newBlock("a", 3), newBlock("b", 4)); + BlocksGroup group4 = newBlocksGroup(newBlock("a", 4), newBlock("b", 5)); + + assertThat(group2.subsumedBy(group1, 1), is(true)); // correction of index - 1 + + assertThat(group3.subsumedBy(group1, 2), is(true)); // correction of index - 2 + assertThat(group3.subsumedBy(group2, 1), is(true)); // correction of index - 1 + + assertThat(group4.subsumedBy(group1, 3), is(true)); // correction of index - 3 + assertThat(group4.subsumedBy(group2, 2), is(true)); // correction of index - 2 + assertThat(group4.subsumedBy(group3, 1), is(true)); // correction of index - 1 + } + + @Test + public void testIntersect() { + BlocksGroup group1 = newBlocksGroup(newBlock("a", 1), newBlock("b", 2)); + BlocksGroup group2 = newBlocksGroup(newBlock("a", 2), newBlock("b", 3)); + BlocksGroup intersection = group1.intersect(group2); + assertThat(intersection.size(), is(2)); + } + + /** + * Results for this test taken from results of work of naive implementation. + */ + @Test + public void testSubsumedBy3() { + // ['a'[2|2-7]:3, 'b'[0|0-5]:3] subsumedBy ['a'[1|1-6]:2] false + assertThat(newBlocksGroup(newBlock("a", 2), newBlock("b", 0)) + .subsumedBy(newBlocksGroup(newBlock("a", 1)), 1), + is(false)); + + // ['a'[3|3-8]:4, 'b'[1|1-6]:4] subsumedBy ['a'[1|1-6]:2] false + assertThat(newBlocksGroup(newBlock("a", 3), newBlock("b", 1)) + .subsumedBy(newBlocksGroup(newBlock("a", 1)), 1), + is(false)); + + // ['a'[4|4-9]:5, 'b'[2|2-7]:5] subsumedBy ['a'[1|1-6]:2] false + assertThat(newBlocksGroup(newBlock("a", 4), newBlock("b", 2)) + .subsumedBy(newBlocksGroup(newBlock("a", 1)), 1), + is(false)); + + // ['a'[5|5-10]:6, 'b'[3|3-8]:6] subsumedBy ['a'[1|1-6]:2] false + assertThat(newBlocksGroup(newBlock("a", 5), newBlock("b", 3)) + .subsumedBy(newBlocksGroup(newBlock("a", 1)), 1), + is(false)); + + // ['a'[3|3-8]:4, 'b'[1|1-6]:4] subsumedBy ['a'[2|2-7]:3, 'b'[0|0-5]:3] true + assertThat(newBlocksGroup(newBlock("a", 3), newBlock("b", 1)) + .subsumedBy(newBlocksGroup(newBlock("a", 2), newBlock("b", 0)), 1), + is(true)); + + // ['a'[4|4-9]:5, 'b'[2|2-7]:5, 'c'[0|0-5]:5] subsumedBy ['a'[3|3-8]:4, 'b'[1|1-6]:4] false + assertThat(newBlocksGroup(newBlock("a", 4), newBlock("b", 2), newBlock("c", 0)) + .subsumedBy(newBlocksGroup(newBlock("a", 3), newBlock("b", 1)), 1), + is(false)); + + // ['a'[5|5-10]:6, 'b'[3|3-8]:6, 'c'[1|1-6]:6] subsumedBy ['a'[3|3-8]:4, 'b'[1|1-6]:4] false + assertThat(newBlocksGroup(newBlock("a", 5), newBlock("b", 3), newBlock("c", 1)) + .subsumedBy(newBlocksGroup(newBlock("a", 3), newBlock("b", 1)), 1), + is(false)); + + // ['a'[6|6-11]:7, 'c'[2|2-7]:7] subsumedBy ['a'[3|3-8]:4, 'b'[1|1-6]:4] false + assertThat(newBlocksGroup(newBlock("a", 6), newBlock("c", 2)) + .subsumedBy(newBlocksGroup(newBlock("a", 3), newBlock("b", 1)), 1), + is(false)); + + // ['a'[5|5-10]:6, 'b'[3|3-8]:6, 'c'[1|1-6]:6] subsumedBy ['a'[4|4-9]:5, 'b'[2|2-7]:5, 'c'[0|0-5]:5] true + assertThat(newBlocksGroup(newBlock("a", 5), newBlock("b", 3), newBlock("c", 1)) + .subsumedBy(newBlocksGroup(newBlock("a", 4), newBlock("b", 2), newBlock("c", 0)), 1), + is(true)); + + // ['a'[6|6-11]:7, 'c'[2|2-7]:7] subsumedBy ['a'[5|5-10]:6, 'b'[3|3-8]:6, 'c'[1|1-6]:6] true + assertThat(newBlocksGroup(newBlock("a", 6), newBlock("c", 2)) + .subsumedBy(newBlocksGroup(newBlock("a", 5), newBlock("b", 3), newBlock("c", 1)), 1), + is(true)); + } + + /** + * Results for this test taken from results of work of naive implementation. + */ + @Test + public void testIntersect2() { + // ['a'[2|2-7]:3, 'b'[0|0-5]:3] + // intersect ['a'[3|3-8]:4, 'b'[1|1-6]:4] + // as ['a'[3|3-8]:4, 'b'[1|1-6]:4] + assertThat(newBlocksGroup(newBlock("a", 2), newBlock("b", 0)) + .intersect(newBlocksGroup(newBlock("a", 3), newBlock("b", 1))) + .size(), is(2)); + + // ['a'[3|3-8]:4, 'b'[1|1-6]:4] + // intersect ['a'[4|4-9]:5, 'b'[2|2-7]:5, 'c'[0|0-5]:5] + // as ['a'[4|4-9]:5, 'b'[2|2-7]:5] + assertThat(newBlocksGroup(newBlock("a", 3), newBlock("b", 1)) + .intersect(newBlocksGroup(newBlock("a", 4), newBlock("b", 2), newBlock("c", 0))) + .size(), is(2)); + + // ['a'[4|4-9]:5, 'b'[2|2-7]:5] + // intersect ['a'[5|5-10]:6, 'b'[3|3-8]:6, 'c'[1|1-6]:6] + // as ['a'[5|5-10]:6, 'b'[3|3-8]:6] + assertThat(newBlocksGroup(newBlock("a", 4), newBlock("b", 2)) + .intersect(newBlocksGroup(newBlock("a", 5), newBlock("b", 3), newBlock("c", 1))) + .size(), is(2)); + + // ['a'[5|5-10]:6, 'b'[3|3-8]:6] + // intersect ['a'[6|6-11]:7, 'c'[2|2-7]:7] + // as ['a'[6|6-11]:7] + assertThat(newBlocksGroup(newBlock("a", 5), newBlock("b", 3)) + .intersect(newBlocksGroup(newBlock("a", 6), newBlock("c", 2))) + .size(), is(1)); + + // ['a'[4|4-9]:5, 'b'[2|2-7]:5, 'c'[0|0-5]:5] + // intersect ['a'[5|5-10]:6, 'b'[3|3-8]:6, 'c'[1|1-6]:6] + // as ['a'[5|5-10]:6, 'b'[3|3-8]:6, 'c'[1|1-6]:6] + assertThat(newBlocksGroup(newBlock("a", 4), newBlock("b", 2), newBlock("c", 0)) + .intersect(newBlocksGroup(newBlock("a", 5), newBlock("b", 3), newBlock("c", 1))) + .size(), is(3)); + + // ['a'[5|5-10]:6, 'b'[3|3-8]:6, 'c'[1|1-6]:6] + // intersect ['a'[6|6-11]:7, 'c'[2|2-7]:7] + // as ['a'[6|6-11]:7, 'c'[2|2-7]:7] + assertThat(newBlocksGroup(newBlock("a", 5), newBlock("b", 3), newBlock("c", 1)) + .intersect(newBlocksGroup(newBlock("a", 6), newBlock("c", 2))) + .size(), is(2)); + + // ['a'[6|6-11]:7, 'c'[2|2-7]:7] + // intersect ['a'[7|7-12]:8] + // as ['a'[7|7-12]:8] + assertThat(newBlocksGroup(newBlock("a", 6), newBlock("c", 7)) + .intersect(newBlocksGroup(newBlock("a", 7))) + .size(), is(1)); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/detector/original/FilterTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/detector/original/FilterTest.java new file mode 100644 index 00000000000..9b6198d345f --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/detector/original/FilterTest.java @@ -0,0 +1,193 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.detector.original; + +import static org.hamcrest.Matchers.is; +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 java.util.Arrays; + +import org.junit.Test; +import org.sonar.duplications.index.CloneGroup; +import org.sonar.duplications.index.ClonePart; + +public class FilterTest { + + /** + * Given: + * <pre> + * c1: a[1-1] + * c2: a[1-1] + * </pre> + * Expected: + * reflexive - c1 in c1, + * antisymmetric - c1 in c2, c2 in c1, because c1 = c2 + */ + @Test + public void reflexive_and_antisymmetric() { + CloneGroup c1 = newCloneGroup(1, + newClonePart("a", 1)); + CloneGroup c2 = newCloneGroup(1, + newClonePart("a", 1)); + + assertThat(Filter.containsIn(c1, c1), is(true)); + assertThat(Filter.containsIn(c1, c2), is(true)); + assertThat(Filter.containsIn(c2, c1), is(true)); + } + + /** + * Given: + * <pre> + * c1: a[1-1] + * c2: a[2-2] + * </pre> + * Expected: c1 not in c2, c2 not in c1 + */ + @Test + public void start_index_in_C1_less_than_in_C2() { + CloneGroup c1 = newCloneGroup(1, + newClonePart("a", 1)); + CloneGroup c2 = newCloneGroup(1, + newClonePart("a", 2)); + + assertThat(Filter.containsIn(c1, c2), is(false)); + } + + /** + * Given: + * <pre> + * c1: a[0-0], a[2-2], b[0-0], b[2-2] + * c2: a[0-2], b[0-2] + * </pre> + * Expected: + * <pre> + * c1 in c2 (all parts of c1 covered by parts of c2 and all resources the same) + * c2 not in c1 (not all parts of c2 covered by parts of c1 and all resources the same) + * </pre> + */ + @Test + public void one_part_of_C2_covers_two_parts_of_C1() { + // Note that line numbers don't matter for method which we test. + CloneGroup c1 = newCloneGroup(1, + newClonePart("a", 0), + newClonePart("a", 2), + newClonePart("b", 0), + newClonePart("b", 2)); + CloneGroup c2 = newCloneGroup(3, + newClonePart("a", 0), + newClonePart("b", 0)); + + assertThat(Filter.containsIn(c1, c2), is(true)); + assertThat(Filter.containsIn(c2, c1), is(false)); + } + + /** + * Given: + * <pre> + * c1: a[0-0], a[2-2] + * c2: a[0-2], b[0-2] + * </pre> + * Expected: + * <pre> + * c1 not in c2 (all parts of c1 covered by parts of c2, but different resources) + * c2 not in c1 (not all parts of c2 covered by parts of c1 and different resources) + * </pre> + */ + @Test + public void different_resources() { + CloneGroup c1 = newCloneGroup(1, + newClonePart("a", 0), + newClonePart("a", 2)); + CloneGroup c2 = newCloneGroup(3, + newClonePart("a", 0), + newClonePart("b", 0)); + + assertThat(Filter.containsIn(c1, c2), is(false)); + assertThat(Filter.containsIn(c2, c1), is(false)); + } + + /** + * Given: + * <pre> + * c1: a[2-2] + * c2: a[0-1], a[2-3] + * </pre> + * Expected: + * <pre> + * c1 in c2 + * c2 not in c1 + * </pre> + */ + @Test + public void second_part_of_C2_covers_first_part_of_C1() { + CloneGroup c1 = newCloneGroup(1, + newClonePart("a", 2)); + CloneGroup c2 = newCloneGroup(2, + newClonePart("a", 0), + newClonePart("a", 2)); + + assertThat(Filter.containsIn(c1, c2), is(true)); + assertThat(Filter.containsIn(c2, c1), is(false)); + } + + /** + * Given: + * <pre> + * c1: a[0-2] + * c2: a[0-0] + * </pre> + * Expected: + * <pre> + * c1 not in c2 + * </pre> + */ + @Test + public void length_of_C1_bigger_than_length_of_C2() { + CloneGroup c1 = spy(newCloneGroup(3, + newClonePart("a", 0))); + CloneGroup c2 = spy(newCloneGroup(1, + newClonePart("a", 0))); + + assertThat(Filter.containsIn(c1, c2), is(false)); + // containsIn method should check only origin and length - no need to compare all parts + verify(c1).getCloneUnitLength(); + verify(c2).getCloneUnitLength(); + verifyNoMoreInteractions(c1); + verifyNoMoreInteractions(c2); + } + + /** + * Creates new part with specified resourceId and unitStart, and 0 for lineStart and lineEnd. + */ + private ClonePart newClonePart(String resourceId, int unitStart) { + return new ClonePart(resourceId, unitStart, 0, 0); + } + + /** + * Creates new group from list of parts, origin - is a first part from list. + */ + private CloneGroup newCloneGroup(int len, ClonePart... parts) { + return new CloneGroup(len, parts[0], Arrays.asList(parts)); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/detector/original/OriginalCloneDetectionAlgorithmTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/detector/original/OriginalCloneDetectionAlgorithmTest.java new file mode 100644 index 00000000000..e22faf753a9 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/detector/original/OriginalCloneDetectionAlgorithmTest.java @@ -0,0 +1,512 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.detector.original; + +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.sameInstance; +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 java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +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 com.google.common.collect.Lists; + +public class OriginalCloneDetectionAlgorithmTest { + + @Rule + public TestNamePrinter name = new TestNamePrinter(); + + private 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. + */ + private static Block newBlock(String resourceId, ByteArray hash, int index) { + return new Block(resourceId, hash, index, index, index + LINES_PER_BLOCK); + } + + private static ClonePart newClonePart(String resourceId, int unitStart, int cloneUnitLength) { + return new ClonePart(resourceId, unitStart, unitStart, unitStart + cloneUnitLength + LINES_PER_BLOCK - 1); + } + + /** + * 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 cloneIndex = createIndex( + blocksForResource("y").withHashes("2", "3", "4", "5"), + blocksForResource("z").withHashes("3", "4")); + List<Block> fileBlocks = blocksForResource("x").withHashes("1", "2", "3", "4", "5", "6"); + List<CloneGroup> clones = OriginalCloneDetectionAlgorithm.detect(cloneIndex, fileBlocks); + print(clones); + assertThat(clones.size(), is(2)); + Iterator<CloneGroup> clonesIterator = clones.iterator(); + + CloneGroup clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(4)); + assertThat(clone.getCloneParts().size(), is(2)); + assertThat(clone.getOriginPart(), is(newClonePart("x", 1, 4))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("x", 1, 4))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("y", 0, 4))); + + clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(2)); + assertThat(clone.getCloneParts().size(), is(3)); + assertThat(clone.getOriginPart(), is(newClonePart("x", 2, 2))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("x", 2, 2))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("y", 1, 2))); + assertThat(clone.getCloneParts(), hasItem(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( + blocksForResource("a").withHashes("2", "3", "4", "5"), + blocksForResource("b").withHashes("3", "4")); + List<Block> fileBlocks = blocksForResource("c").withHashes("1", "2", "3", "4", "5", "6"); + List<CloneGroup> clones = OriginalCloneDetectionAlgorithm.detect(cloneIndex, fileBlocks); + print(clones); + assertThat(clones.size(), is(2)); + Iterator<CloneGroup> clonesIterator = clones.iterator(); + + CloneGroup clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(4)); + assertThat(clone.getCloneParts().size(), is(2)); + assertThat(clone.getOriginPart(), is(newClonePart("c", 1, 4))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("c", 1, 4))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 0, 4))); + + clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(2)); + assertThat(clone.getCloneParts().size(), is(3)); + assertThat(clone.getOriginPart(), is(newClonePart("c", 2, 2))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("c", 2, 2))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 1, 2))); + assertThat(clone.getCloneParts(), hasItem(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 cloneIndex = createIndex( + blocksForResource("b").withHashes("3", "4", "5", "6"), + blocksForResource("c").withHashes("5", "6", "7")); + List<Block> fileBlocks = + blocksForResource("a").withHashes("1", "2", "3", "4", "5", "6", "7", "8", "9"); + List<CloneGroup> clones = OriginalCloneDetectionAlgorithm.detect(cloneIndex, fileBlocks); + print(clones); + assertThat(clones.size(), is(3)); + Iterator<CloneGroup> clonesIterator = clones.iterator(); + + CloneGroup clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(4)); + assertThat(clone.getCloneParts().size(), is(2)); + assertThat(clone.getOriginPart(), is(newClonePart("a", 2, 4))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 2, 4))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("b", 0, 4))); + + clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(2)); + assertThat(clone.getCloneParts().size(), is(3)); + assertThat(clone.getOriginPart(), is(newClonePart("a", 4, 2))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 4, 2))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("b", 2, 2))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("c", 0, 2))); + + clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(3)); + assertThat(clone.getCloneParts().size(), is(2)); + assertThat(clone.getOriginPart(), is(newClonePart("a", 4, 3))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 4, 3))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("c", 0, 3))); + } + + /** + * Given: + * <pre> + * b: 1 2 3 4 1 2 3 4 1 2 3 4 + * c: 1 2 3 4 + * a: 1 2 3 4 5 + * </pre> + * Expected: + * <pre> + * a-b-b-b-c (1 2 3 4) + * </pre> + */ + @Test + public void example2() { + CloneIndex cloneIndex = createIndex( + blocksForResource("b").withHashes("1", "2", "3", "4", "1", "2", "3", "4", "1", "2", "3", "4"), + blocksForResource("c").withHashes("1", "2", "3", "4")); + List<Block> fileBlocks = + blocksForResource("a").withHashes("1", "2", "3", "5"); + List<CloneGroup> clones = OriginalCloneDetectionAlgorithm.detect(cloneIndex, fileBlocks); + print(clones); + assertThat(clones.size(), is(1)); + Iterator<CloneGroup> clonesIterator = clones.iterator(); + + CloneGroup clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(3)); + assertThat(clone.getCloneParts().size(), is(5)); + assertThat(clone.getOriginPart(), is(newClonePart("a", 0, 3))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 0, 3))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("b", 0, 3))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("b", 4, 3))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("b", 8, 3))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("c", 0, 3))); + } + + /** + * 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( + blocksForResource("b").withHashes("1", "2", "3", "4")); + List<Block> fileBlocks = + blocksForResource("a").withHashes("1", "2", "3"); + List<CloneGroup> clones = OriginalCloneDetectionAlgorithm.detect(cloneIndex, fileBlocks); + print(clones); + assertThat(clones.size(), is(1)); + Iterator<CloneGroup> clonesIterator = clones.iterator(); + + CloneGroup clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(3)); + assertThat(clone.getCloneParts().size(), is(2)); + assertThat(clone.getOriginPart(), is(newClonePart("a", 0, 3))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 0, 3))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("b", 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 cloneIndex = createIndex(); + List<Block> fileBlocks = + blocksForResource("a").withHashes("1", "2", "3", "1", "2", "4"); + List<CloneGroup> clones = OriginalCloneDetectionAlgorithm.detect(cloneIndex, fileBlocks); + print(clones); + + assertThat(clones.size(), is(1)); + Iterator<CloneGroup> clonesIterator = clones.iterator(); + + CloneGroup clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(2)); + assertThat(clone.getCloneParts().size(), is(2)); + assertThat(clone.getOriginPart(), is(newClonePart("a", 0, 2))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 0, 2))); + assertThat(clone.getCloneParts(), hasItem(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 cloneIndex = createIndex( + blocksForResource("b").withHashes("1", "2", "1", "2")); + List<Block> fileBlocks = + blocksForResource("a").withHashes("1", "2", "1"); + List<CloneGroup> clones = OriginalCloneDetectionAlgorithm.detect(cloneIndex, fileBlocks); + print(clones); + + assertThat(clones.size(), is(2)); + Iterator<CloneGroup> clonesIterator = clones.iterator(); + + CloneGroup clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(2)); + assertThat(clone.getOriginPart(), is(newClonePart("a", 0, 2))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 0, 2))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("b", 0, 2))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("b", 2, 2))); + + clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(3)); + assertThat(clone.getOriginPart(), is(newClonePart("a", 0, 3))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 0, 3))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("b", 0, 3))); + } + + /** + * 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 cloneIndex = createIndex( + blocksForResource("b").withHashes("1", "2", "1", "2", "1", "2", "1")); + List<Block> fileBlocks = + blocksForResource("a").withHashes("1", "2", "1", "2", "1", "2"); + List<CloneGroup> clones = OriginalCloneDetectionAlgorithm.detect(cloneIndex, fileBlocks); + print(clones); + + assertThat(clones.size(), is(2)); + Iterator<CloneGroup> clonesIterator = clones.iterator(); + + CloneGroup clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(5)); + assertThat(clone.getCloneParts().size(), is(3)); + assertThat(clone.getOriginPart(), is(newClonePart("a", 0, 5))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 0, 5))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("b", 0, 5))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("b", 2, 5))); + + clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(6)); + assertThat(clone.getCloneParts().size(), is(2)); + assertThat(clone.getOriginPart(), is(newClonePart("a", 0, 6))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 0, 6))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("b", 0, 6))); + } + + /** + * 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 cloneIndex = createIndex( + blocksForResource("a").withHashes("1", "2", "3"), + blocksForResource("b").withHashes("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) + List<Block> fileBlocks = + blocksForResource("a").withHashes("1", "2", "5"); + List<CloneGroup> clones = OriginalCloneDetectionAlgorithm.detect(cloneIndex, fileBlocks); + print(clones); + + assertThat(clones.size(), is(1)); + Iterator<CloneGroup> clonesIterator = clones.iterator(); + + CloneGroup clone = clonesIterator.next(); + assertThat(clone.getCloneUnitLength(), is(2)); + assertThat(clone.getCloneParts().size(), is(2)); + assertThat(clone.getOriginPart(), is(newClonePart("a", 0, 2))); + assertThat(clone.getCloneParts(), hasItem(newClonePart("a", 0, 2))); + assertThat(clone.getCloneParts(), hasItem(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()); + List<Block> fileBlocks = + blocksForResource("a").withHashes("1", "2", "1", "2"); + OriginalCloneDetectionAlgorithm.detect(index, fileBlocks); + + verify(index).getBySequenceHash(new ByteArray("1".getBytes())); + verify(index).getBySequenceHash(new ByteArray("2".getBytes())); + verifyNoMoreInteractions(index); + } + + /** + * 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(); + List<Block> fileBlocks = Arrays.asList( + new Block("a", new ByteArray("1".getBytes()), 0, 0, 1), + new Block("a", new ByteArray("2".getBytes()), 1, 0, 1), + new Block("a", new ByteArray("1".getBytes()), 2, 0, 1)); + List<CloneGroup> clones = OriginalCloneDetectionAlgorithm.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))); + } + + /** + * Given: empty list of blocks for file + * Expected: {@link Collections#EMPTY_LIST} + */ + @Test + public void shouldReturnEmptyListWhenNoBlocksForFile() { + List<CloneGroup> result = OriginalCloneDetectionAlgorithm.detect(null, new ArrayList<Block>()); + assertThat(result, sameInstance(Collections.EMPTY_LIST)); + } + + private void print(List<CloneGroup> clones) { + for (CloneGroup clone : clones) { + System.out.println(clone); + } + System.out.println(); + } + + private static CloneIndex createIndex(List<Block>... blocks) { + CloneIndex cloneIndex = new MemoryCloneIndex(); + for (List<Block> b : blocks) { + for (Block block : b) { + cloneIndex.insert(block); + } + } + return cloneIndex; + } + + private static BlocksBuilder blocksForResource(String resourceId) { + return new BlocksBuilder(resourceId); + } + + private static class BlocksBuilder { + String resourceId; + + public BlocksBuilder(String resourceId) { + this.resourceId = resourceId; + } + + List<Block> withHashes(String... hashes) { + ByteArray[] arrays = new ByteArray[hashes.length]; + for (int i = 0; i < hashes.length; i++) { + arrays[i] = new ByteArray(hashes[i].getBytes()); + } + return withHashes(arrays); + } + + List<Block> withHashes(ByteArray... hashes) { + List<Block> result = Lists.newArrayList(); + int index = 0; + for (ByteArray hash : hashes) { + result.add(newBlock(resourceId, hash, index)); + index++; + } + return result; + } + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/index/DataUtilsTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/index/DataUtilsTest.java new file mode 100644 index 00000000000..c210a4c8e05 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/index/DataUtilsTest.java @@ -0,0 +1,90 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.index; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; + +import org.junit.Test; + +public class DataUtilsTest { + + @Test + public void testSort() { + int[] expected = new int[200]; + int[] actual = new int[expected.length]; + for (int i = 0; i < expected.length; i++) { + expected[i] = (int) (Math.random() * 900); + actual[i] = expected[i]; + } + Arrays.sort(expected); + DataUtils.sort(new SimpleSortable(actual, actual.length)); + assertThat(actual, equalTo(expected)); + } + + @Test + public void testSearch() { + int[] a = new int[] { 1, 2, 4, 4, 4, 5, 0 }; + SimpleSortable sortable = new SimpleSortable(a, a.length - 1); + // search 4 + a[a.length - 1] = 4; + assertThat(DataUtils.binarySearch(sortable), is(2)); + // search 5 + a[a.length - 1] = 5; + assertThat(DataUtils.binarySearch(sortable), is(5)); + // search -5 + a[a.length - 1] = -5; + assertThat(DataUtils.binarySearch(sortable), is(0)); + // search 10 + a[a.length - 1] = 10; + assertThat(DataUtils.binarySearch(sortable), is(6)); + // search 3 + a[a.length - 1] = 3; + assertThat(DataUtils.binarySearch(sortable), is(2)); + } + + class SimpleSortable implements DataUtils.Sortable { + private final int[] a; + private final int size; + + public SimpleSortable(int[] a, int size) { + this.a = a; + this.size = size; + } + + public int size() { + return size; + } + + public void swap(int i, int j) { + int tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } + + public boolean isLess(int i, int j) { + return a[i] < a[j]; + } + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/index/MemoryCloneIndexTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/index/MemoryCloneIndexTest.java new file mode 100644 index 00000000000..fc44357d3aa --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/index/MemoryCloneIndexTest.java @@ -0,0 +1,102 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.index; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.ByteArray; + +public class MemoryCloneIndexTest { + + private CloneIndex cloneIndex; + + @Before + public void initialize() { + cloneIndex = new MemoryCloneIndex(); + } + + @Test + public void byFileName() { + Block tuple1 = new Block("a", new ByteArray(0), 0, 0, 10); + Block tuple2 = new Block("a", new ByteArray(0), 1, 10, 20); + + assertThat(cloneIndex.getByResourceId("a").size(), is(0)); + + cloneIndex.insert(tuple1); + assertThat(cloneIndex.getByResourceId("a").size(), is(1)); + + cloneIndex.insert(tuple2); + assertThat(cloneIndex.getByResourceId("a").size(), is(2)); + } + + @Test + public void bySequenceHash() { + Block tuple1 = new Block("a", new ByteArray(0), 0, 0, 5); + Block tuple2 = new Block("a", new ByteArray(0), 1, 1, 6); + + assertThat(cloneIndex.getBySequenceHash(new ByteArray(0)).size(), is(0)); + + cloneIndex.insert(tuple1); + assertThat(cloneIndex.getBySequenceHash(new ByteArray(0)).size(), is(1)); + + cloneIndex.insert(tuple2); + assertThat(cloneIndex.getBySequenceHash(new ByteArray(0)).size(), is(2)); + } + + @Test + public void insertSame() { + Block tuple = new Block("a", new ByteArray(0), 0, 0, 5); + Block tupleSame = new Block("a", new ByteArray(0), 0, 0, 5); + + assertThat(cloneIndex.getByResourceId("a").size(), is(0)); + assertThat(cloneIndex.getBySequenceHash(new ByteArray(0)).size(), is(0)); + + cloneIndex.insert(tuple); + assertThat(cloneIndex.getByResourceId("a").size(), is(1)); + assertThat(cloneIndex.getBySequenceHash(new ByteArray(0)).size(), is(1)); + + cloneIndex.insert(tupleSame); + assertThat(cloneIndex.getByResourceId("a").size(), is(1)); + assertThat(cloneIndex.getBySequenceHash(new ByteArray(0)).size(), is(1)); + } + + @Test + public void testSorted() { + for (int i = 0; i < 10; i++) { + cloneIndex.insert(new Block("a", new ByteArray(1), 10 - i, i, i + 5)); + } + assertThat(cloneIndex.getByResourceId("a").size(), is(10)); + assertThat(cloneIndex.getBySequenceHash(new ByteArray(1)).size(), is(10)); + + Collection<Block> set = cloneIndex.getByResourceId("a"); + int prevStatementIndex = 0; + for (Block tuple : set) { + assertTrue(tuple.getIndexInFile() > prevStatementIndex); + prevStatementIndex = tuple.getIndexInFile(); + } + } +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/index/PackedMemoryCloneIndexTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/index/PackedMemoryCloneIndexTest.java new file mode 100644 index 00000000000..4fc2c2b11a4 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/index/PackedMemoryCloneIndexTest.java @@ -0,0 +1,116 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.index; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertThat; + +import java.util.Collection; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.ByteArray; + +public class PackedMemoryCloneIndexTest { + + private PackedMemoryCloneIndex index; + + @Before + public void setUp() { + index = new PackedMemoryCloneIndex(); + } + + @Test + public void test() { + index.insert(newBlock("a", 1)); + index.insert(newBlock("a", 2)); + index.insert(newBlock("b", 1)); + index.insert(newBlock("c", 1)); + index.insert(newBlock("d", 1)); + index.insert(newBlock("e", 1)); + index.insert(newBlock("e", 2)); + index.insert(newBlock("e", 3)); + + assertThat(index.getBySequenceHash(new ByteArray(1L)).size(), is(5)); + assertThat(index.getBySequenceHash(new ByteArray(2L)).size(), is(2)); + assertThat(index.getBySequenceHash(new ByteArray(3L)).size(), is(1)); + assertThat(index.getBySequenceHash(new ByteArray(4L)).size(), is(0)); + assertThat(index.getByResourceId("a").size(), is(2)); + assertThat(index.getByResourceId("b").size(), is(1)); + assertThat(index.getByResourceId("e").size(), is(3)); + assertThat(index.getByResourceId("does not exist").size(), is(0)); + } + + /** + * When: query by a hash value. + * Expected: all blocks should have same hash, which presented in the form of the same object. + */ + @Test + public void should_construct_blocks_with_normalized_hash() { + index.insert(newBlock("a", 1)); + index.insert(newBlock("b", 1)); + index.insert(newBlock("c", 1)); + ByteArray requestedHash = new ByteArray(1L); + Collection<Block> blocks = index.getBySequenceHash(requestedHash); + assertThat(blocks.size(), is(3)); + for (Block block : blocks) { + assertThat(block.getBlockHash(), sameInstance(requestedHash)); + } + } + + /** + * Given: index with initial capacity 1. + * Expected: size and capacity should be increased after insertion of two blocks. + */ + @Test + public void should_increase_capacity() { + CloneIndex index = new PackedMemoryCloneIndex(8, 1); + index.insert(newBlock("a", 1)); + index.insert(newBlock("a", 2)); + assertThat(index.getByResourceId("a").size(), is(2)); + } + + /** + * Given: index, which accepts blocks with 4-byte hash. + * Expected: exception during insertion of block with 8-byte hash. + */ + @Test(expected = IllegalArgumentException.class) + public void attempt_to_insert_hash_of_incorrect_size() { + CloneIndex index = new PackedMemoryCloneIndex(4, 1); + index.insert(newBlock("a", 1)); + } + + /** + * Given: index, which accepts blocks with 4-byte hash. + * Expected: exception during search by 8-byte hash. + */ + @Test(expected = IllegalArgumentException.class) + public void attempt_to_find_hash_of_incorrect_size() { + CloneIndex index = new PackedMemoryCloneIndex(4, 1); + index.getBySequenceHash(new ByteArray(1L)); + } + + private static Block newBlock(String resourceId, long hash) { + return new Block(resourceId, new ByteArray(hash), 1, 1, 1); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/java/JavaStatementBuilderTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/java/JavaStatementBuilderTest.java new file mode 100644 index 00000000000..2267fa8a125 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/java/JavaStatementBuilderTest.java @@ -0,0 +1,160 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.java; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.number.OrderingComparisons.greaterThan; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.util.List; + +import org.junit.Test; +import org.sonar.duplications.DuplicationsTestUtil; +import org.sonar.duplications.statement.Statement; +import org.sonar.duplications.statement.StatementChunker; +import org.sonar.duplications.token.TokenChunker; + +public class JavaStatementBuilderTest { + + private TokenChunker tokenChunker = JavaTokenProducer.build(); + private StatementChunker statementChunker = JavaStatementBuilder.build(); + + private List<Statement> chunk(String sourceCode) { + return statementChunker.chunk(tokenChunker.chunk(sourceCode)); + } + + @Test + public void shouldIgnoreImportStatement() { + assertThat(chunk("import org.sonar.duplications.java;").size(), is(0)); + } + + @Test + public void shouldIgnorePackageStatement() { + assertThat(chunk("package org.sonar.duplications.java;").size(), is(0)); + } + + @Test + public void shouldHandleAnnotation() { + List<Statement> statements = chunk("" + + "@Entity" + + "@Table(name = \"properties\")" + + "@Column(updatable = true, nullable = true)"); + assertThat(statements.size(), is(3)); + assertThat(statements.get(0).getValue(), is("@Entity")); + assertThat(statements.get(1).getValue(), is("@Table(name=$CHARS)")); + assertThat(statements.get(2).getValue(), is("@Column(updatable=true,nullable=true)")); + } + + @Test + public void shouldHandleIf() { + List<Statement> statements = chunk("if (a > b) { something(); }"); + assertThat(statements.size(), is(2)); + assertThat(statements.get(0).getValue(), is("if(a>b)")); + assertThat(statements.get(1).getValue(), is("something()")); + + statements = chunk("if (a > b) { something(); } else { somethingOther(); }"); + assertThat(statements.size(), is(4)); + assertThat(statements.get(0).getValue(), is("if(a>b)")); + assertThat(statements.get(1).getValue(), is("something()")); + assertThat(statements.get(2).getValue(), is("else")); + assertThat(statements.get(3).getValue(), is("somethingOther()")); + + statements = chunk("if (a > 0) { something(); } else if (a == 0) { somethingOther(); }"); + assertThat(statements.size(), is(4)); + assertThat(statements.get(0).getValue(), is("if(a>$NUMBER)")); + assertThat(statements.get(1).getValue(), is("something()")); + assertThat(statements.get(2).getValue(), is("elseif(a==$NUMBER)")); + assertThat(statements.get(3).getValue(), is("somethingOther()")); + } + + @Test + public void shouldHandleFor() { + List<Statement> statements = chunk("for (int i = 0; i < 10; i++) { something(); }"); + assertThat(statements.size(), is(2)); + assertThat(statements.get(0).getValue(), is("for(inti=$NUMBER;i<$NUMBER;i++)")); + assertThat(statements.get(1).getValue(), is("something()")); + + statements = chunk("for (Item item : items) { something(); }"); + assertThat(statements.size(), is(2)); + assertThat(statements.get(0).getValue(), is("for(Itemitem:items)")); + assertThat(statements.get(1).getValue(), is("something()")); + } + + @Test + public void shouldHandleWhile() { + List<Statement> statements = chunk("while (i < args.length) { something(); }"); + assertThat(statements.size(), is(2)); + assertThat(statements.get(0).getValue(), is("while(i<args.length)")); + assertThat(statements.get(1).getValue(), is("something()")); + + statements = chunk("while (true);"); + assertThat(statements.size(), is(1)); + assertThat(statements.get(0).getValue(), is("while(true)")); + } + + @Test + public void shouldHandleDoWhile() { + List<Statement> statements = chunk("do { something(); } while (true);"); + assertThat(statements.size(), is(3)); + assertThat(statements.get(0).getValue(), is("do")); + assertThat(statements.get(1).getValue(), is("something()")); + assertThat(statements.get(2).getValue(), is("while(true)")); + } + + @Test + public void shouldHandleSwitch() { + List<Statement> statements = chunk("" + + "switch (month) {" + + " case 1 : monthString=\"January\"; break;" + + " case 2 : monthString=\"February\"; break;" + + " default: monthString=\"Invalid\";"); + assertThat(statements.size(), is(9)); + assertThat(statements.get(0).getValue(), is("switch(month)")); + assertThat(statements.get(1).getValue(), is("case$NUMBER:")); + assertThat(statements.get(2).getValue(), is("monthString=$CHARS")); + assertThat(statements.get(3).getValue(), is("break")); + assertThat(statements.get(4).getValue(), is("case$NUMBER:")); + assertThat(statements.get(5).getValue(), is("monthString=$CHARS")); + assertThat(statements.get(6).getValue(), is("break")); + assertThat(statements.get(7).getValue(), is("default:")); + assertThat(statements.get(8).getValue(), is("monthString=$CHARS")); + } + + @Test + public void shouldHandleArray() { + List<Statement> statements = chunk("new Integer[][] { { 1, 2 }, {3, 4} };"); + assertThat(statements.size(), is(4)); + assertThat(statements.get(0).getValue(), is("newInteger[][]")); + assertThat(statements.get(1).getValue(), is("$NUMBER,$NUMBER")); + assertThat(statements.get(2).getValue(), is(",")); + assertThat(statements.get(3).getValue(), is("$NUMBER,$NUMBER")); + } + + @Test + public void realExamples() { + File testFile = DuplicationsTestUtil.findFile("/java/MessageResources.java"); + assertThat(statementChunker.chunk(tokenChunker.chunk(testFile)).size(), greaterThan(0)); + + testFile = DuplicationsTestUtil.findFile("/java/RequestUtils.java"); + assertThat(statementChunker.chunk(tokenChunker.chunk(testFile)).size(), greaterThan(0)); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/java/JavaTokenProducerTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/java/JavaTokenProducerTest.java new file mode 100644 index 00000000000..285cdf2c61e --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/java/JavaTokenProducerTest.java @@ -0,0 +1,293 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.java; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.number.OrderingComparisons.greaterThan; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +import org.hamcrest.Matcher; +import org.junit.Test; +import org.sonar.duplications.DuplicationsTestUtil; +import org.sonar.duplications.token.Token; +import org.sonar.duplications.token.TokenChunker; + +import com.google.common.collect.Lists; + +/** + * See <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html">The Java Language Specification, Third Edition: Lexical Structure</a> + * + * TODO Java 7 features: Binary Integer Literals, Using Underscore Characters in Numeric Literals + * TODO add more complex example + */ +public class JavaTokenProducerTest { + + private TokenChunker chunker = JavaTokenProducer.build(); + + /** + * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.6">White Space</a> + */ + @Test + public void shouldIgnoreWhitespaces() { + assertThat(chunk(" \t\f\n\r"), isTokens()); + } + + /** + * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.7">Comments</a> + */ + @Test + public void shouldIgnoreEndOfLineComment() { + assertThat(chunk("// This is a comment"), isTokens()); + assertThat(chunk("// This is a comment \n and_this_is_not"), isTokens(new Token("and_this_is_not", 2, 1))); + } + + @Test + public void shouldIgnoreTraditionalComment() { + assertThat(chunk("/* This is a comment \n and the second line */"), isTokens()); + assertThat(chunk("/** This is a javadoc \n and the second line */"), isTokens()); + assertThat(chunk("/* this \n comment /* \n // /** ends \n here: */"), isTokens()); + } + + /** + * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.8">Identifiers</a> + */ + @Test + public void shouldPreserveIdentifiers() { + assertThat(chunk("String"), isTokens(new Token("String", 1, 0))); + assertThat(chunk("i3"), isTokens(new Token("i3", 1, 0))); + assertThat(chunk("MAX_VALUE"), isTokens(new Token("MAX_VALUE", 1, 0))); + assertThat(chunk("isLetterOrDigit"), isTokens(new Token("isLetterOrDigit", 1, 0))); + + assertThat(chunk("_"), isTokens(new Token("_", 1, 0))); + assertThat(chunk("_123_"), isTokens(new Token("_123_", 1, 0))); + assertThat(chunk("_Field"), isTokens(new Token("_Field", 1, 0))); + assertThat(chunk("_Field5"), isTokens(new Token("_Field5", 1, 0))); + + assertThat(chunk("$"), isTokens(new Token("$", 1, 0))); + assertThat(chunk("$field"), isTokens(new Token("$field", 1, 0))); + + assertThat(chunk("i2j"), isTokens(new Token("i2j", 1, 0))); + assertThat(chunk("from1to4"), isTokens(new Token("from1to4", 1, 0))); + + assertThat("identifier with unicode", chunk("αβγ"), isTokens(new Token("αβγ", 1, 0))); + } + + /** + * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.9">Keywords</a> + */ + @Test + public void shouldPreserverKeywords() { + assertThat(chunk("private static final"), isTokens(new Token("private", 1, 0), new Token("static", 1, 8), new Token("final", 1, 15))); + } + + /** + * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.1">Integer Literals</a> + */ + @Test + public void shouldNormalizeDecimalIntegerLiteral() { + assertThat(chunk("543"), isNumericLiteral()); + assertThat(chunk("543l"), isNumericLiteral()); + assertThat(chunk("543L"), isNumericLiteral()); + } + + @Test + public void shouldNormalizeOctalIntegerLiteral() { + assertThat(chunk("077"), isNumericLiteral()); + assertThat(chunk("077l"), isNumericLiteral()); + assertThat(chunk("077L"), isNumericLiteral()); + } + + @Test + public void shouldNormalizeHexIntegerLiteral() { + assertThat(chunk("0xFF"), isNumericLiteral()); + assertThat(chunk("0xFFl"), isNumericLiteral()); + assertThat(chunk("0xFFL"), isNumericLiteral()); + } + + /** + * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.2">Floating-Point Literals</a> + */ + @Test + public void shouldNormalizeDecimalFloatingPointLiteral() { + // with dot at the end + assertThat(chunk("1234."), isNumericLiteral()); + assertThat(chunk("1234.E1"), isNumericLiteral()); + assertThat(chunk("1234.e+1"), isNumericLiteral()); + assertThat(chunk("1234.E-1"), isNumericLiteral()); + assertThat(chunk("1234.f"), isNumericLiteral()); + + // with dot between + assertThat(chunk("12.34"), isNumericLiteral()); + assertThat(chunk("12.34E1"), isNumericLiteral()); + assertThat(chunk("12.34e+1"), isNumericLiteral()); + assertThat(chunk("12.34E-1"), isNumericLiteral()); + + assertThat(chunk("12.34f"), isNumericLiteral()); + assertThat(chunk("12.34E1F"), isNumericLiteral()); + assertThat(chunk("12.34E+1d"), isNumericLiteral()); + assertThat(chunk("12.34e-1D"), isNumericLiteral()); + + // with dot at the beginning + assertThat(chunk(".1234"), isNumericLiteral()); + assertThat(chunk(".1234e1"), isNumericLiteral()); + assertThat(chunk(".1234E+1"), isNumericLiteral()); + assertThat(chunk(".1234E-1"), isNumericLiteral()); + + assertThat(chunk(".1234f"), isNumericLiteral()); + assertThat(chunk(".1234E1F"), isNumericLiteral()); + assertThat(chunk(".1234e+1d"), isNumericLiteral()); + assertThat(chunk(".1234E-1D"), isNumericLiteral()); + + // without dot + assertThat(chunk("1234e1"), isNumericLiteral()); + assertThat(chunk("1234E+1"), isNumericLiteral()); + assertThat(chunk("1234E-1"), isNumericLiteral()); + + assertThat(chunk("1234E1f"), isNumericLiteral()); + assertThat(chunk("1234e+1d"), isNumericLiteral()); + assertThat(chunk("1234E-1D"), isNumericLiteral()); + } + + @Test + public void shouldNormalizeHexadecimalFloatingPointLiteral() { + // with dot at the end + assertThat(chunk("0xAF."), isNumericLiteral()); + assertThat(chunk("0XAF.P1"), isNumericLiteral()); + assertThat(chunk("0xAF.p+1"), isNumericLiteral()); + assertThat(chunk("0XAF.p-1"), isNumericLiteral()); + assertThat(chunk("0xAF.f"), isNumericLiteral()); + + // with dot between + assertThat(chunk("0XAF.BC"), isNumericLiteral()); + assertThat(chunk("0xAF.BCP1"), isNumericLiteral()); + assertThat(chunk("0XAF.BCp+1"), isNumericLiteral()); + assertThat(chunk("0xAF.BCP-1"), isNumericLiteral()); + + assertThat(chunk("0xAF.BCf"), isNumericLiteral()); + assertThat(chunk("0xAF.BCp1F"), isNumericLiteral()); + assertThat(chunk("0XAF.BCP+1d"), isNumericLiteral()); + assertThat(chunk("0XAF.BCp-1D"), isNumericLiteral()); + + // without dot + assertThat(chunk("0xAFp1"), isNumericLiteral()); + assertThat(chunk("0XAFp+1"), isNumericLiteral()); + assertThat(chunk("0xAFp-1"), isNumericLiteral()); + + assertThat(chunk("0XAFp1f"), isNumericLiteral()); + assertThat(chunk("0xAFp+1d"), isNumericLiteral()); + assertThat(chunk("0XAFp-1D"), isNumericLiteral()); + } + + /** + * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.3">Boolean Literals</a> + */ + @Test + public void shouldPreserveBooleanLiterals() { + assertThat(chunk("true false"), isTokens(new Token("true", 1, 0), new Token("false", 1, 5))); + } + + /** + * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.4">Character Literals</a> + */ + @Test + public void shouldNormalizeCharacterLiterals() { + assertThat("single character", chunk("'a'"), isStringLiteral()); + assertThat("escaped LF", chunk("'\\n'"), isStringLiteral()); + assertThat("escaped quote", chunk("'\\''"), isStringLiteral()); + assertThat("octal escape", chunk("'\\177'"), isStringLiteral()); + assertThat("unicode escape", chunk("'\\u03a9'"), isStringLiteral()); + } + + /** + * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.5">String Literals</a> + */ + @Test + public void shouldNormalizeStringLiterals() { + assertThat("regular string", chunk("\"string\""), isStringLiteral()); + assertThat("empty string", chunk("\"\""), isStringLiteral()); + assertThat("escaped LF", chunk("\"\\n\""), isStringLiteral()); + assertThat("escaped double quotes", chunk("\"string, which contains \\\"escaped double quotes\\\"\""), isStringLiteral()); + assertThat("octal escape", chunk("\"string \\177\""), isStringLiteral()); + assertThat("unicode escape", chunk("\"string \\u03a9\""), isStringLiteral()); + } + + /** + * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.7">The Null Literal</a> + */ + @Test + public void shouldPreserverNullLiteral() { + assertThat(chunk("null"), isTokens(new Token("null", 1, 0))); + } + + /** + * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.11">Separators</a> + */ + @Test + public void shouldPreserveSeparators() { + assertThat(chunk("(){}[];,."), isTokens( + new Token("(", 1, 0), new Token(")", 1, 1), + new Token("{", 1, 2), new Token("}", 1, 3), + new Token("[", 1, 4), new Token("]", 1, 5), + new Token(";", 1, 6), new Token(",", 1, 7), + new Token(".", 1, 8))); + } + + /** + * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.12">Operators</a> + */ + @Test + public void shouldPreserveOperators() { + assertThat(chunk("+="), isTokens(new Token("+", 1, 0), new Token("=", 1, 1))); + assertThat(chunk("--"), isTokens(new Token("-", 1, 0), new Token("-", 1, 1))); + } + + @Test + public void realExamples() { + File testFile = DuplicationsTestUtil.findFile("/java/MessageResources.java"); + assertThat(chunker.chunk(testFile).size(), greaterThan(0)); + + testFile = DuplicationsTestUtil.findFile("/java/RequestUtils.java"); + assertThat(chunker.chunk(testFile).size(), greaterThan(0)); + } + + private static Matcher<List<Token>> isNumericLiteral() { + return isTokens(new Token("$NUMBER", 1, 0)); + } + + private static Matcher<List<Token>> isStringLiteral() { + return isTokens(new Token("$CHARS", 1, 0)); + } + + /** + * @return matcher for list of tokens + */ + private static Matcher<List<Token>> isTokens(Token... tokens) { + return is(Arrays.asList(tokens)); + } + + private List<Token> chunk(String sourceCode) { + return Lists.newArrayList(chunker.chunk(sourceCode)); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/junit/TestNamePrinter.java b/sonar-duplications/src/test/java/org/sonar/duplications/junit/TestNamePrinter.java new file mode 100644 index 00000000000..bc7097f4b3b --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/junit/TestNamePrinter.java @@ -0,0 +1,32 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.junit; + +import org.junit.rules.TestWatchman; +import org.junit.runners.model.FrameworkMethod; + +public class TestNamePrinter extends TestWatchman { + + @Override + public void starting(FrameworkMethod method) { + System.out.println("Executing " + method.getName()); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/statement/StatementChannelDisptacherTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/statement/StatementChannelDisptacherTest.java new file mode 100644 index 00000000000..c7d7e53f770 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/statement/StatementChannelDisptacherTest.java @@ -0,0 +1,70 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.statement; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyListOf; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.sonar.duplications.statement.matcher.TokenMatcher; +import org.sonar.duplications.token.Token; +import org.sonar.duplications.token.TokenQueue; + +public class StatementChannelDisptacherTest { + + @Test(expected = IllegalStateException.class) + public void shouldThrowAnException() { + TokenMatcher tokenMatcher = mock(TokenMatcher.class); + StatementChannel channel = StatementChannel.create(tokenMatcher); + StatementChannelDisptacher dispatcher = new StatementChannelDisptacher(Arrays.asList(channel)); + TokenQueue tokenQueue = mock(TokenQueue.class); + when(tokenQueue.peek()).thenReturn(new Token("a", 1, 0)).thenReturn(null); + List<Statement> statements = mock(List.class); + + dispatcher.consume(tokenQueue, statements); + } + + @Test + public void shouldConsume() { + TokenMatcher tokenMatcher = mock(TokenMatcher.class); + when(tokenMatcher.matchToken(any(TokenQueue.class), anyListOf(Token.class))).thenReturn(true); + StatementChannel channel = StatementChannel.create(tokenMatcher); + StatementChannelDisptacher dispatcher = new StatementChannelDisptacher(Arrays.asList(channel)); + TokenQueue tokenQueue = mock(TokenQueue.class); + when(tokenQueue.peek()).thenReturn(new Token("a", 1, 0)).thenReturn(null); + List<Statement> statements = mock(List.class); + + assertThat(dispatcher.consume(tokenQueue, statements), is(true)); + verify(tokenQueue, times(2)).peek(); + verifyNoMoreInteractions(tokenQueue); + verifyNoMoreInteractions(statements); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/statement/StatementChannelTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/statement/StatementChannelTest.java new file mode 100644 index 00000000000..35ed1954fac --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/statement/StatementChannelTest.java @@ -0,0 +1,101 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.statement; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.sonar.duplications.statement.matcher.AnyTokenMatcher; +import org.sonar.duplications.statement.matcher.TokenMatcher; +import org.sonar.duplications.token.Token; +import org.sonar.duplications.token.TokenQueue; + +public class StatementChannelTest { + + @Test(expected = IllegalArgumentException.class) + public void shouldNotAcceptNull() { + StatementChannel.create((TokenMatcher[]) null); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldNotAcceptEmpty() { + StatementChannel.create(new TokenMatcher[] {}); + } + + @Test + public void shouldPushForward() { + TokenQueue tokenQueue = mock(TokenQueue.class); + TokenMatcher matcher = mock(TokenMatcher.class); + List<Statement> output = mock(List.class); + StatementChannel channel = StatementChannel.create(matcher); + + assertThat(channel.consume(tokenQueue, output), is(false)); + ArgumentCaptor<List> matchedTokenList = ArgumentCaptor.forClass(List.class); + verify(matcher).matchToken(Mockito.eq(tokenQueue), matchedTokenList.capture()); + verifyNoMoreInteractions(matcher); + verify(tokenQueue).pushForward(matchedTokenList.getValue()); + verifyNoMoreInteractions(tokenQueue); + verifyNoMoreInteractions(output); + } + + @Test + public void shouldCreateStatement() { + Token token = new Token("a", 1, 1); + TokenQueue tokenQueue = spy(new TokenQueue(Arrays.asList(token))); + TokenMatcher matcher = spy(new AnyTokenMatcher()); + StatementChannel channel = StatementChannel.create(matcher); + List<Statement> output = mock(List.class); + + assertThat(channel.consume(tokenQueue, output), is(true)); + verify(matcher).matchToken(Mockito.eq(tokenQueue), Mockito.anyList()); + verifyNoMoreInteractions(matcher); + ArgumentCaptor<Statement> statement = ArgumentCaptor.forClass(Statement.class); + verify(output).add(statement.capture()); + assertThat(statement.getValue().getValue(), is("a")); + assertThat(statement.getValue().getStartLine(), is(1)); + assertThat(statement.getValue().getEndLine(), is(1)); + verifyNoMoreInteractions(output); + } + + @Test + public void shouldNotCreateStatement() { + TokenQueue tokenQueue = spy(new TokenQueue(Arrays.asList(new Token("a", 1, 1)))); + TokenMatcher matcher = spy(new AnyTokenMatcher()); + StatementChannel channel = StatementChannel.create(matcher); + List<Statement> output = mock(List.class); + + assertThat(channel.consume(tokenQueue, output), is(true)); + verify(matcher).matchToken(Mockito.eq(tokenQueue), Mockito.anyList()); + verifyNoMoreInteractions(matcher); + verify(output).add(Mockito.any(Statement.class)); + verifyNoMoreInteractions(output); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/statement/StatementChunkerTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/statement/StatementChunkerTest.java new file mode 100644 index 00000000000..83b567abaff --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/statement/StatementChunkerTest.java @@ -0,0 +1,31 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.statement; + +import org.junit.Test; + +public class StatementChunkerTest { + + @Test(expected = IllegalArgumentException.class) + public void shouldNotAcceptNull() { + StatementChunker.builder().build().chunk(null); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/statement/StatementTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/statement/StatementTest.java new file mode 100644 index 00000000000..522b21f3496 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/statement/StatementTest.java @@ -0,0 +1,51 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.statement; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Test; +import org.sonar.duplications.token.Token; + +public class StatementTest { + + @Test(expected = IllegalArgumentException.class) + public void shouldNotAcceptNull() { + new Statement(null); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldNotAcceptEmpty() { + new Statement(new ArrayList<Token>()); + } + + @Test + public void shouldCreateStatementFromListOfTokens() { + Statement statement = new Statement(Arrays.asList(new Token("a", 1, 1), new Token("b", 2, 1))); + assertThat(statement.getValue(), is("ab")); + assertThat(statement.getStartLine(), is(1)); + assertThat(statement.getEndLine(), is(2)); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/statement/TokenMatcherFactoryTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/statement/TokenMatcherFactoryTest.java new file mode 100644 index 00000000000..20be1e85869 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/statement/TokenMatcherFactoryTest.java @@ -0,0 +1,48 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.statement; + +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +import org.junit.Test; +import org.sonar.duplications.statement.matcher.AnyTokenMatcher; +import org.sonar.duplications.statement.matcher.BridgeTokenMatcher; +import org.sonar.duplications.statement.matcher.ExactTokenMatcher; +import org.sonar.duplications.statement.matcher.ForgetLastTokenMatcher; +import org.sonar.duplications.statement.matcher.OptTokenMatcher; +import org.sonar.duplications.statement.matcher.TokenMatcher; +import org.sonar.duplications.statement.matcher.UptoTokenMatcher; + +public class TokenMatcherFactoryTest { + + @Test + public void shouldCreateMatchers() { + assertThat(TokenMatcherFactory.anyToken(), instanceOf(AnyTokenMatcher.class)); + assertThat(TokenMatcherFactory.bridge("(", ")"), instanceOf(BridgeTokenMatcher.class)); + assertThat(TokenMatcherFactory.forgetLastToken(), instanceOf(ForgetLastTokenMatcher.class)); + assertThat(TokenMatcherFactory.from("if"), instanceOf(ExactTokenMatcher.class)); + assertThat(TokenMatcherFactory.opt(mock(TokenMatcher.class)), instanceOf(OptTokenMatcher.class)); + assertThat(TokenMatcherFactory.to(";"), instanceOf(UptoTokenMatcher.class)); + assertThat(TokenMatcherFactory.token(";"), instanceOf(ExactTokenMatcher.class)); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/AnyTokenMatcherTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/AnyTokenMatcherTest.java new file mode 100644 index 00000000000..e607af5ce64 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/AnyTokenMatcherTest.java @@ -0,0 +1,53 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.statement.matcher; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.sonar.duplications.token.Token; +import org.sonar.duplications.token.TokenQueue; + +public class AnyTokenMatcherTest { + + @Test + public void shouldMatch() { + Token t1 = new Token("a", 1, 1); + Token t2 = new Token("b", 2, 1); + TokenQueue tokenQueue = spy(new TokenQueue(Arrays.asList(t1, t2))); + List<Token> output = mock(List.class); + AnyTokenMatcher matcher = new AnyTokenMatcher(); + + assertThat(matcher.matchToken(tokenQueue, output), is(true)); + verify(tokenQueue).poll(); + verifyNoMoreInteractions(tokenQueue); + verify(output).add(t1); + verifyNoMoreInteractions(output); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/BridgeTokenMatcherTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/BridgeTokenMatcherTest.java new file mode 100644 index 00000000000..2f73d4a55de --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/BridgeTokenMatcherTest.java @@ -0,0 +1,106 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.statement.matcher; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.sonar.duplications.token.Token; +import org.sonar.duplications.token.TokenQueue; + +public class BridgeTokenMatcherTest { + + @Test(expected = IllegalArgumentException.class) + public void shouldNotAcceptNullAsLeft() { + new BridgeTokenMatcher(null, ")"); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldNotAcceptNullAsRight() { + new BridgeTokenMatcher("(", null); + } + + @Test + public void shouldMatch() { + Token t1 = new Token("(", 1, 1); + Token t2 = new Token("a", 2, 1); + Token t3 = new Token("(", 3, 1); + Token t4 = new Token("b", 4, 1); + Token t5 = new Token(")", 5, 1); + Token t6 = new Token("c", 6, 1); + Token t7 = new Token(")", 7, 1); + TokenQueue tokenQueue = spy(new TokenQueue(Arrays.asList(t1, t2, t3, t4, t5, t6, t7))); + List<Token> output = mock(List.class); + BridgeTokenMatcher matcher = new BridgeTokenMatcher("(", ")"); + + assertThat(matcher.matchToken(tokenQueue, output), is(true)); + verify(tokenQueue, times(1)).isNextTokenValue("("); + verify(tokenQueue, times(7)).poll(); + verify(tokenQueue, times(7)).peek(); + verifyNoMoreInteractions(tokenQueue); + verify(output).add(t1); + verify(output).add(t2); + verify(output).add(t3); + verify(output).add(t4); + verify(output).add(t5); + verify(output).add(t6); + verify(output).add(t7); + verifyNoMoreInteractions(output); + } + + @Test + public void shouldNotMatchWhenNoLeft() { + Token t1 = new Token("a", 1, 1); + TokenQueue tokenQueue = spy(new TokenQueue(Arrays.asList(t1))); + List<Token> output = mock(List.class); + BridgeTokenMatcher matcher = new BridgeTokenMatcher("(", ")"); + + assertThat(matcher.matchToken(tokenQueue, output), is(false)); + verify(tokenQueue).isNextTokenValue("("); + verifyNoMoreInteractions(tokenQueue); + verifyNoMoreInteractions(output); + } + + @Test + public void shouldNotMatchWhenNoRight() { + Token t1 = new Token("(", 1, 1); + TokenQueue tokenQueue = spy(new TokenQueue(Arrays.asList(t1))); + List<Token> output = mock(List.class); + BridgeTokenMatcher matcher = new BridgeTokenMatcher("(", ")"); + + assertThat(matcher.matchToken(tokenQueue, output), is(false)); + verify(tokenQueue, times(1)).isNextTokenValue("("); + verify(tokenQueue, times(1)).poll(); + verify(tokenQueue, times(2)).peek(); + verifyNoMoreInteractions(tokenQueue); + verify(output).add(t1); + verifyNoMoreInteractions(output); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/ExactTokenMatcherTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/ExactTokenMatcherTest.java new file mode 100644 index 00000000000..925d1d9ca63 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/ExactTokenMatcherTest.java @@ -0,0 +1,73 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.statement.matcher; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.sonar.duplications.token.Token; +import org.sonar.duplications.token.TokenQueue; + +public class ExactTokenMatcherTest { + + @Test(expected = IllegalArgumentException.class) + public void shouldNotAcceptNull() { + new ExactTokenMatcher(null); + } + + @Test + public void shouldMatch() { + Token t1 = new Token("a", 1, 1); + Token t2 = new Token("b", 2, 1); + TokenQueue tokenQueue = spy(new TokenQueue(Arrays.asList(t1, t2))); + List<Token> output = mock(List.class); + ExactTokenMatcher matcher = new ExactTokenMatcher("a"); + + assertThat(matcher.matchToken(tokenQueue, output), is(true)); + verify(tokenQueue).isNextTokenValue("a"); + verify(tokenQueue).poll(); + verifyNoMoreInteractions(tokenQueue); + verify(output).add(t1); + verifyNoMoreInteractions(output); + } + + @Test + public void shouldNotMatch() { + Token t1 = new Token("a", 1, 1); + Token t2 = new Token("b", 2, 1); + TokenQueue tokenQueue = spy(new TokenQueue(Arrays.asList(t1, t2))); + List<Token> output = mock(List.class); + ExactTokenMatcher matcher = new ExactTokenMatcher("b"); + + assertThat(matcher.matchToken(tokenQueue, output), is(false)); + verify(tokenQueue).isNextTokenValue("b"); + verifyNoMoreInteractions(tokenQueue); + verifyNoMoreInteractions(output); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/ForgetLastTokenMatcherTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/ForgetLastTokenMatcherTest.java new file mode 100644 index 00000000000..1050e7a10a3 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/ForgetLastTokenMatcherTest.java @@ -0,0 +1,52 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.statement.matcher; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.junit.Test; +import org.sonar.duplications.token.Token; +import org.sonar.duplications.token.TokenQueue; + +public class ForgetLastTokenMatcherTest { + + @Test + public void shouldMatch() { + TokenQueue tokenQueue = spy(new TokenQueue()); + List<Token> output = mock(List.class); + when(output.size()).thenReturn(4); + ForgetLastTokenMatcher matcher = new ForgetLastTokenMatcher(); + + assertThat(matcher.matchToken(tokenQueue, output), is(true)); + verifyNoMoreInteractions(tokenQueue); + verify(output).size(); + verify(output).remove(3); + verifyNoMoreInteractions(output); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/OptTokenMatcherTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/OptTokenMatcherTest.java new file mode 100644 index 00000000000..acddf6df379 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/OptTokenMatcherTest.java @@ -0,0 +1,56 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.statement.matcher; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.util.List; + +import org.junit.Test; +import org.sonar.duplications.token.Token; +import org.sonar.duplications.token.TokenQueue; + +public class OptTokenMatcherTest { + + @Test(expected = IllegalArgumentException.class) + public void shouldNotAcceptNull() { + new OptTokenMatcher(null); + } + + @Test + public void shouldMatch() { + TokenQueue tokenQueue = spy(new TokenQueue()); + TokenMatcher delegate = mock(TokenMatcher.class); + OptTokenMatcher matcher = new OptTokenMatcher(delegate); + List<Token> output = mock(List.class); + + assertThat(matcher.matchToken(tokenQueue, output), is(true)); + verify(delegate).matchToken(tokenQueue, output); + verifyNoMoreInteractions(delegate); + verifyNoMoreInteractions(tokenQueue); + verifyNoMoreInteractions(output); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/UptoTokenMatcherTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/UptoTokenMatcherTest.java new file mode 100644 index 00000000000..917c3948043 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/statement/matcher/UptoTokenMatcherTest.java @@ -0,0 +1,106 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.statement.matcher; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.sonar.duplications.token.Token; +import org.sonar.duplications.token.TokenQueue; + +public class UptoTokenMatcherTest { + + @Test(expected = IllegalArgumentException.class) + public void shouldNotAcceptNull() { + new UptoTokenMatcher(null); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldNotAcceptEmpty() { + new UptoTokenMatcher(new String[] {}); + } + + @Test + public void shouldMatch() { + Token t1 = new Token("a", 1, 1); + Token t2 = new Token(";", 2, 1); // should stop on this token + Token t3 = new Token(";", 3, 1); + TokenQueue tokenQueue = spy(new TokenQueue(Arrays.asList(t1, t2, t3))); + List<Token> output = mock(List.class); + UptoTokenMatcher matcher = new UptoTokenMatcher(new String[] { ";" }); + + assertThat(matcher.matchToken(tokenQueue, output), is(true)); + verify(tokenQueue, times(2)).poll(); + verify(tokenQueue).peek(); + verifyNoMoreInteractions(tokenQueue); + verify(output).add(t1); + verify(output).add(t2); + verifyNoMoreInteractions(output); + } + + @Test + public void shouldMatchAnyOfProvidedTokens() { + Token t1 = new Token("a", 1, 1); + Token t2 = new Token("{", 2, 1); + Token t3 = new Token("b", 3, 1); + Token t4 = new Token("}", 4, 1); + TokenQueue tokenQueue = spy(new TokenQueue(Arrays.asList(t1, t2, t3, t4))); + List<Token> output = mock(List.class); + UptoTokenMatcher matcher = new UptoTokenMatcher(new String[] { "{", "}" }); + + assertThat(matcher.matchToken(tokenQueue, output), is(true)); + assertThat(matcher.matchToken(tokenQueue, output), is(true)); + verify(tokenQueue, times(4)).poll(); + verify(tokenQueue, times(2)).peek(); + verifyNoMoreInteractions(tokenQueue); + verify(output).add(t1); + verify(output).add(t2); + verify(output).add(t3); + verify(output).add(t4); + verifyNoMoreInteractions(output); + } + + @Test + public void shouldNotMatch() { + Token t1 = new Token("a", 1, 1); + Token t2 = new Token("b", 2, 1); + TokenQueue tokenQueue = spy(new TokenQueue(Arrays.asList(t1, t2))); + List<Token> output = mock(List.class); + UptoTokenMatcher matcher = new UptoTokenMatcher(new String[] { ";" }); + + assertThat(matcher.matchToken(tokenQueue, output), is(false)); + verify(tokenQueue, times(2)).poll(); + verify(tokenQueue, times(2)).peek(); + verifyNoMoreInteractions(tokenQueue); + verify(output).add(t1); + verify(output).add(t2); + verifyNoMoreInteractions(output); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/token/BlackHoleTokenChannelTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/token/BlackHoleTokenChannelTest.java new file mode 100644 index 00000000000..7f540256541 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/token/BlackHoleTokenChannelTest.java @@ -0,0 +1,44 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.token; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyZeroInteractions; + +import org.junit.Test; +import org.sonar.channel.CodeReader; + +public class BlackHoleTokenChannelTest { + + @Test + public void shouldConsume() { + BlackHoleTokenChannel channel = new BlackHoleTokenChannel("ABC"); + TokenQueue output = mock(TokenQueue.class); + CodeReader codeReader = new CodeReader("ABCD"); + + assertThat(channel.consume(codeReader, output), is(true)); + assertThat(codeReader.getLinePosition(), is(1)); + assertThat(codeReader.getColumnPosition(), is(3)); + verifyZeroInteractions(output); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/token/TokenChannelTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/token/TokenChannelTest.java new file mode 100644 index 00000000000..17baea189b9 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/token/TokenChannelTest.java @@ -0,0 +1,92 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.token; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.channel.CodeReader; + +public class TokenChannelTest { + + @Test + public void shouldConsume() { + TokenChannel channel = new TokenChannel("ABC"); + TokenQueue output = mock(TokenQueue.class); + CodeReader codeReader = new CodeReader("ABCD"); + + assertThat(channel.consume(codeReader, output), is(true)); + ArgumentCaptor<Token> token = ArgumentCaptor.forClass(Token.class); + verify(output).add(token.capture()); + assertThat(token.getValue(), is(new Token("ABC", 1, 0))); + verifyNoMoreInteractions(output); + assertThat(codeReader.getLinePosition(), is(1)); + assertThat(codeReader.getColumnPosition(), is(3)); + } + + @Test + public void shouldNormalize() { + TokenChannel channel = new TokenChannel("ABC", "normalized"); + TokenQueue output = mock(TokenQueue.class); + CodeReader codeReader = new CodeReader("ABCD"); + + assertThat(channel.consume(codeReader, output), is(true)); + ArgumentCaptor<Token> token = ArgumentCaptor.forClass(Token.class); + verify(output).add(token.capture()); + assertThat(token.getValue(), is(new Token("normalized", 1, 0))); + verifyNoMoreInteractions(output); + assertThat(codeReader.getLinePosition(), is(1)); + assertThat(codeReader.getColumnPosition(), is(3)); + } + + @Test + public void shouldNotConsume() { + TokenChannel channel = new TokenChannel("ABC"); + TokenQueue output = mock(TokenQueue.class); + CodeReader codeReader = new CodeReader("123"); + + assertThat(channel.consume(new CodeReader("123"), output), is(false)); + verifyZeroInteractions(output); + assertThat(codeReader.getLinePosition(), is(1)); + assertThat(codeReader.getColumnPosition(), is(0)); + } + + @Test + public void shouldCorrectlyDeterminePositionWhenTokenSpansMultipleLines() { + TokenChannel channel = new TokenChannel("AB\nC"); + TokenQueue output = mock(TokenQueue.class); + CodeReader codeReader = new CodeReader("AB\nCD"); + + assertThat(channel.consume(codeReader, output), is(true)); + ArgumentCaptor<Token> token = ArgumentCaptor.forClass(Token.class); + verify(output).add(token.capture()); + assertThat(token.getValue(), is(new Token("AB\nC", 1, 0))); + verifyNoMoreInteractions(output); + assertThat(codeReader.getLinePosition(), is(2)); + assertThat(codeReader.getColumnPosition(), is(1)); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/token/TokenChunkerTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/token/TokenChunkerTest.java new file mode 100644 index 00000000000..d9a421ac4d0 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/token/TokenChunkerTest.java @@ -0,0 +1,46 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.token; + +import org.junit.Test; + +public class TokenChunkerTest { + + /** + * In fact this test does not guarantee that we will be able to consume even more great comments, + * because {@link org.sonar.channel.CodeBuffer} does not expand dynamically - see issue SONAR-2632. + * But at least guarantees that we able to consume source files from JDK 1.6, + * because buffer capacity has been increased in comparison with default value, + * which is {@link org.sonar.channel.CodeReaderConfiguration#DEFAULT_BUFFER_CAPACITY}. + */ + @Test(timeout = 5000) + public void shouldConsumeBigComments() { + int capacity = 80000; + StringBuilder sb = new StringBuilder(capacity); + sb.append("/"); + for (int i = 3; i < capacity; i++) { + sb.append('*'); + } + sb.append("/"); + TokenChunker chunker = TokenChunker.builder().token("/.*/", "LITERAL").build(); + chunker.chunk(sb.toString()); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/token/TokenQueueTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/token/TokenQueueTest.java new file mode 100644 index 00000000000..bdb6ba00c58 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/token/TokenQueueTest.java @@ -0,0 +1,68 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.token; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +public class TokenQueueTest { + + TokenQueue tokenQueue; + + @Before + public void initTest() { + List<Token> tokenList = new ArrayList<Token>(); + tokenList.add(new Token("a", 1, 0)); + tokenList.add(new Token("bc", 1, 2)); + tokenList.add(new Token("def", 1, 5)); + tokenQueue = new TokenQueue(tokenList); + } + + @Test + public void shouldPeekToken() { + Token token = tokenQueue.peek(); + assertThat(token, is(new Token("a", 1, 0))); + assertThat(tokenQueue.size(), is(3)); + } + + @Test + public void shouldPollToken() { + Token token = tokenQueue.poll(); + assertThat(token, is(new Token("a", 1, 0))); + assertThat(tokenQueue.size(), is(2)); + } + + @Test + public void shouldPushTokenAtBegining() { + Token pushedToken = new Token("push", 1, 0); + List<Token> pushedTokenList = new ArrayList<Token>(); + pushedTokenList.add(pushedToken); + tokenQueue.pushForward(pushedTokenList); + assertThat(tokenQueue.peek(), is(pushedToken)); + assertThat(tokenQueue.size(), is(4)); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/token/TokenTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/token/TokenTest.java new file mode 100644 index 00000000000..9e4e7785d19 --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/token/TokenTest.java @@ -0,0 +1,49 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.token; + +import org.junit.Test; + +import static org.junit.Assert.assertThat; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +public class TokenTest { + + @Test + public void shouldBeEqual() { + Token firstToken = new Token("MyValue", 1, 3); + Token secondToken = new Token("MyValue", 1, 3); + + assertThat(firstToken, is(secondToken)); + } + + @Test + public void shouldNotBeEqual() { + Token firstToken = new Token("MyValue", 1, 3); + Token secondToken = new Token("MySecondValue", 1, 3); + Token thirdToken = new Token("MyValue", 3, 3); + + assertThat(firstToken, not(is(secondToken))); + assertThat(firstToken, not(is(thirdToken))); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/utils/FastStringComparatorTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/utils/FastStringComparatorTest.java new file mode 100644 index 00000000000..ddc050ce2bc --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/utils/FastStringComparatorTest.java @@ -0,0 +1,70 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.utils; + +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThan; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.sonar.duplications.utils.FastStringComparator; + +public class FastStringComparatorTest { + + @Test + public void sameHashCode() { + // Next two Strings have same hash code in Java - see http://www.drmaciver.com/2008/07/javalangstringhashcode/ + String s1 = "Od"; + String s2 = "PE"; + assertTrue("same hash code", s1.hashCode() == s2.hashCode()); + assertThat("s1 < s2", FastStringComparator.INSTANCE.compare(s1, s2), lessThan(0)); + assertThat("s2 > s1", FastStringComparator.INSTANCE.compare(s2, s1), greaterThan(0)); + } + + @Test + public void differentHashCode() { + String s1 = "a"; + String s2 = "c"; + assertTrue("different hash code", s1.hashCode() != s2.hashCode()); + assertThat("s1 < s2", FastStringComparator.INSTANCE.compare(s1, s2), is(-1)); + assertThat("s2 > s1", FastStringComparator.INSTANCE.compare(s2, s1), is(1)); + } + + @Test + public void sameObject() { + String s1 = "a"; + String s2 = s1; + assertTrue("same objects", s1 == s2); + assertThat("s1 = s2", FastStringComparator.INSTANCE.compare(s1, s2), is(0)); + assertThat("s2 = s1", FastStringComparator.INSTANCE.compare(s2, s1), is(0)); + } + + @Test + public void sameString() { + String s1 = new String("a"); + String s2 = new String("a"); + assertTrue("different objects", s1 != s2); + assertThat("s1 = s2", FastStringComparator.INSTANCE.compare(s1, s2), is(0)); + assertThat("s2 = s1", FastStringComparator.INSTANCE.compare(s2, s1), is(0)); + } + +} diff --git a/sonar-duplications/src/test/java/org/sonar/duplications/utils/SortedListsUtilsTest.java b/sonar-duplications/src/test/java/org/sonar/duplications/utils/SortedListsUtilsTest.java new file mode 100644 index 00000000000..cf099af6eab --- /dev/null +++ b/sonar-duplications/src/test/java/org/sonar/duplications/utils/SortedListsUtilsTest.java @@ -0,0 +1,60 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.duplications.utils; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import org.junit.Test; + +public class SortedListsUtilsTest { + + @Test + public void testContains() { + List<Integer> c1 = Arrays.asList(1, 2, 3); + List<Integer> c2 = Arrays.asList(1, 2); + List<Integer> c3 = Arrays.asList(1, 3); + + assertThat(SortedListsUtils.contains(c1, c1, IntegerComparator.INSTANCE), is(true)); + assertThat(SortedListsUtils.contains(c1, c2, IntegerComparator.INSTANCE), is(true)); + assertThat(SortedListsUtils.contains(c1, c3, IntegerComparator.INSTANCE), is(true)); + + assertThat(SortedListsUtils.contains(c2, c1, IntegerComparator.INSTANCE), is(false)); + assertThat(SortedListsUtils.contains(c2, c2, IntegerComparator.INSTANCE), is(true)); + assertThat(SortedListsUtils.contains(c2, c3, IntegerComparator.INSTANCE), is(false)); + + assertThat(SortedListsUtils.contains(c3, c1, IntegerComparator.INSTANCE), is(false)); + assertThat(SortedListsUtils.contains(c3, c2, IntegerComparator.INSTANCE), is(false)); + assertThat(SortedListsUtils.contains(c3, c3, IntegerComparator.INSTANCE), is(true)); + } + + private static class IntegerComparator implements Comparator<Integer> { + public static final IntegerComparator INSTANCE = new IntegerComparator(); + + public int compare(Integer o1, Integer o2) { + return o1 - o2; + } + } + +} |