From dcc2e1dc34b5f026b8a35089e7050f7e1b938686 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Tue, 14 Apr 2015 16:31:46 +0200 Subject: [PATCH] SONAR-6258 Persist symbols into file sources --- .../source/HighlightingLineReader.java | 50 +--- .../computation/source/RangeHelper.java | 74 +++++ .../computation/source/SymbolsLineReader.java | 114 ++++++++ .../step/PersistFileSourcesStep.java | 8 +- .../computation/step/PersistSymbolsStep.java | 108 -------- .../computation/source/RangeHelperTest.java | 127 +++++++++ .../source/SymbolsLineReaderTest.java | 256 ++++++++++++++++++ .../step/PersistFileSourcesStepTest.java | 59 ++-- .../step/PersistSymbolsStepTest.java | 200 -------------- .../batch/index/SourceDataFactoryTest.java | 40 +++ 10 files changed, 662 insertions(+), 374 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/source/RangeHelper.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/source/SymbolsLineReader.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistSymbolsStep.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/source/RangeHelperTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/source/SymbolsLineReaderTest.java delete mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistSymbolsStepTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/source/HighlightingLineReader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/source/HighlightingLineReader.java index 3deab174ed0..19c8eaf4a4e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/source/HighlightingLineReader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/source/HighlightingLineReader.java @@ -35,9 +35,6 @@ import static com.google.common.collect.Lists.newArrayList; public class HighlightingLineReader implements LineReader { - private static final String OFFSET_SEPARATOR = ","; - private static final String ITEM_SEPARATOR = ";"; - private static final Map cssClassByType = ImmutableMap.builder() .put(Constants.HighlightingType.ANNOTATION, "a") .put(Constants.HighlightingType.CONSTANT, "c") @@ -70,10 +67,8 @@ public class HighlightingLineReader implements LineReader { BatchReport.SyntaxHighlighting syntaxHighlighting = syntaxHighlightingIterator.next(); BatchReport.Range range = syntaxHighlighting.getRange(); if (range.getStartLine() <= line) { - if (highlighting.length() > 0) { - highlighting.append(ITEM_SEPARATOR); - } - highlighting.append(convertHighlightingToString(syntaxHighlighting, line, lineBuilder.getSource())); + RangeHelper.appendRange(highlighting, syntaxHighlighting.getRange(), line, lineBuilder.getSource().length()); + highlighting.append(getCssClass(syntaxHighlighting.getType())); if (range.getEndLine() == line) { syntaxHighlightingIterator.remove(); } @@ -84,29 +79,6 @@ public class HighlightingLineReader implements LineReader { } } - private String convertHighlightingToString(BatchReport.SyntaxHighlighting syntaxHighlighting, int line, String sourceLine){ - BatchReport.Range range = syntaxHighlighting.getRange(); - validateStartAndEndOffset(range, line); - - StringBuilder symbolLine = new StringBuilder(); - if (range.getStartLine() == line) { - validateStartOffsetNotGreaterThanLineLength(range, sourceLine, line); - symbolLine.append(range.getStartOffset()).append(OFFSET_SEPARATOR); - } else if (range.getStartLine() < line) { - symbolLine.append(0).append(OFFSET_SEPARATOR); - } - - if (range.getEndLine() == line) { - validateEndOffsetNotGreaterThanLineLength(range, sourceLine, line); - symbolLine.append(range.getEndOffset()).append(OFFSET_SEPARATOR); - } else if (range.getEndLine() > line) { - symbolLine.append(sourceLine.length() - 1).append(OFFSET_SEPARATOR); - } - - symbolLine.append(getCssClass(syntaxHighlighting.getType())); - return symbolLine.toString(); - } - private static String getCssClass(Constants.HighlightingType type) { String cssClass = cssClassByType.get(type); if (cssClass != null) { @@ -138,22 +110,4 @@ public class HighlightingLineReader implements LineReader { return null; } - private static void validateStartAndEndOffset(BatchReport.Range range, int line){ - if (range.getStartLine() == range.getEndLine() && range.getStartOffset() > range.getEndOffset()) { - throw new IllegalArgumentException(String.format("End offset %s cannot be defined before start offset %s on line %s", range.getEndOffset(), range.getStartOffset(), line)); - } - } - - private static void validateStartOffsetNotGreaterThanLineLength(BatchReport.Range range, String sourceLine, int line){ - if (range.getStartOffset() > sourceLine.length()) { - throw new IllegalArgumentException(String.format("Start offset %s is defined outside the length (%s) of the line %s", range.getStartOffset(), sourceLine.length(), line)); - } - } - - private static void validateEndOffsetNotGreaterThanLineLength(BatchReport.Range range, String sourceLine, int line){ - if (range.getEndOffset() > sourceLine.length()) { - throw new IllegalArgumentException(String.format("End offset %s is defined outside the length (%s) of the line %s", range.getEndOffset(), sourceLine.length(), line)); - } - } - } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/source/RangeHelper.java b/server/sonar-server/src/main/java/org/sonar/server/computation/source/RangeHelper.java new file mode 100644 index 00000000000..3c460e0023a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/source/RangeHelper.java @@ -0,0 +1,74 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.source; + +import org.sonar.batch.protocol.output.BatchReport; + +public class RangeHelper { + + private static final String OFFSET_SEPARATOR = ","; + private static final String SYMBOLS_SEPARATOR = ";"; + + private RangeHelper() { + // Only static methods + } + + public static void appendRange(StringBuilder element, BatchReport.Range range, int lineIndex, int lineLength) { + validateOffsetOrder(range, lineIndex); + + if (element.length() > 0) { + element.append(SYMBOLS_SEPARATOR); + } + + if (range.getStartLine() == lineIndex) { + validateStartOffsetNotGreaterThanLineLength(range, lineLength, lineIndex); + element.append(range.getStartOffset()).append(OFFSET_SEPARATOR); + } else if (range.getStartLine() < lineIndex) { + element.append(0).append(OFFSET_SEPARATOR); + } + + if (range.getEndLine() == lineIndex) { + validateEndOffsetNotGreaterThanLineLength(range, lineLength, lineIndex); + element.append(range.getEndOffset()).append(OFFSET_SEPARATOR); + } else if (range.getEndLine() > lineIndex) { + element.append(lineLength - 1).append(OFFSET_SEPARATOR); + } + } + + private static void validateOffsetOrder(BatchReport.Range range, int line) { + if (range.getStartLine() == range.getEndLine() && range.getStartOffset() > range.getEndOffset()) { + throw new IllegalArgumentException(String.format("End offset %s cannot be defined before start offset %s on line %s", range.getEndOffset(), range.getStartOffset(), line)); + } + } + + private static void validateStartOffsetNotGreaterThanLineLength(BatchReport.Range range, int lineLength, int line) { + if (range.getStartOffset() > lineLength) { + throw new IllegalArgumentException(String.format("Start offset %s is defined outside the length (%s) of the line %s", range.getStartOffset(), lineLength, line)); + } + } + + private static void validateEndOffsetNotGreaterThanLineLength(BatchReport.Range range, int lineLength, int line) { + if (range.getEndOffset() > lineLength) { + throw new IllegalArgumentException(String.format("End offset %s is defined outside the length (%s) of the line %s", range.getEndOffset(), lineLength, line)); + } + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/source/SymbolsLineReader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/source/SymbolsLineReader.java new file mode 100644 index 00000000000..fecebb58c81 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/source/SymbolsLineReader.java @@ -0,0 +1,114 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.source; + +import org.sonar.batch.protocol.output.BatchReport; +import org.sonar.server.source.db.FileSourceDb; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Maps.newHashMap; + +public class SymbolsLineReader implements LineReader { + + private final List symbols; + private final Map idsBySymbol; + + public SymbolsLineReader(List symbols) { + this.symbols = symbols; + // Sort symbols to have deterministic results and avoid false variation that would lead to an unnecessary update of the source files + // data + Collections.sort(this.symbols, new SymbolsDuplication()); + + this.idsBySymbol = createIdsBySymbolMap(symbols); + } + + @Override + public void read(FileSourceDb.Line.Builder lineBuilder) { + int line = lineBuilder.getLine(); + List lineSymbols = findSymbolsMatchingLine(line); + for (BatchReport.Symbols.Symbol lineSymbol : lineSymbols) { + int symbolId = idsBySymbol.get(lineSymbol); + StringBuilder symbolString = new StringBuilder(lineBuilder.getSymbols()); + + appendSymbol(symbolString, lineSymbol.getDeclaration(), line, symbolId, lineBuilder.getSource()); + for (BatchReport.Range range : lineSymbol.getReferenceList()) { + appendSymbol(symbolString, range, line, symbolId, lineBuilder.getSource()); + } + + if (symbolString.length() > 0) { + lineBuilder.setSymbols(symbolString.toString()); + } + } + } + + private void appendSymbol(StringBuilder lineSymbol, BatchReport.Range range, int line, int symbolId, String sourceLine) { + if (matchLine(range, line)) { + RangeHelper.appendRange(lineSymbol, range, line, sourceLine.length()); + lineSymbol.append(symbolId); + } + } + + private List findSymbolsMatchingLine(int line) { + List lineSymbols = newArrayList(); + for (BatchReport.Symbols.Symbol symbol : symbols) { + if (matchLine(symbol.getDeclaration(), line)) { + lineSymbols.add(symbol); + } else { + for (BatchReport.Range range : symbol.getReferenceList()) { + if (matchLine(range, line)) { + lineSymbols.add(symbol); + } + } + } + } + return lineSymbols; + } + + private static boolean matchLine(BatchReport.Range range, int line) { + return range.getStartLine() <= line && range.getEndLine() >= line; + } + + private Map createIdsBySymbolMap(List symbols) { + Map map = newHashMap(); + int symbolId = 1; + for (BatchReport.Symbols.Symbol symbol : symbols) { + map.put(symbol, symbolId); + symbolId++; + } + return map; + } + + private static class SymbolsDuplication implements Comparator { + @Override + public int compare(BatchReport.Symbols.Symbol o1, BatchReport.Symbols.Symbol o2) { + if (o1.getDeclaration().getStartLine() == o2.getDeclaration().getStartLine()) { + return Integer.compare(o1.getDeclaration().getStartOffset(), o2.getDeclaration().getStartOffset()); + } else { + return Integer.compare(o1.getDeclaration().getStartLine(), o2.getDeclaration().getStartLine()); + } + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistFileSourcesStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistFileSourcesStep.java index 26e71e31af9..d6732477793 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistFileSourcesStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistFileSourcesStep.java @@ -176,6 +176,7 @@ public class PersistFileSourcesStep implements ComputationStep { File coverageFile = reportReader.readComponentCoverage(componentRef); BatchReport.Scm scmReport = reportReader.readComponentScm(componentRef); File highlightingFile = reportReader.readComponentSyntaxHighlighting(componentRef); + List symbols = reportReader.readComponentSymbols(componentRef); if (coverageFile != null) { ReportIterator coverageReportIterator = new ReportIterator<>(coverageFile, BatchReport.Coverage.PARSER); @@ -190,13 +191,16 @@ public class PersistFileSourcesStep implements ComputationStep { reportIterators.add(syntaxHighlightingReportIterator); lineReaders.add(new HighlightingLineReader(syntaxHighlightingReportIterator)); } + if (!symbols.isEmpty()) { + lineReaders.add(new SymbolsLineReader(newArrayList(symbols))); + } } - List readers(){ + List readers() { return lineReaders; } - void close(){ + void close() { for (ReportIterator reportIterator : reportIterators) { reportIterator.close(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistSymbolsStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistSymbolsStep.java deleted file mode 100644 index d082495b43d..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistSymbolsStep.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube 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. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.sonar.server.computation.step; - -import com.google.common.annotations.VisibleForTesting; -import org.sonar.api.resources.Qualifiers; -import org.sonar.batch.protocol.output.BatchReport; -import org.sonar.batch.protocol.output.BatchReportReader; -import org.sonar.server.computation.ComputationContext; - -import java.util.List; -import java.util.Map; - -import static com.google.common.collect.Maps.newHashMap; - -/** - * Nothing is persist for the moment. Only Symbols are read and not persist for the moment - */ -public class PersistSymbolsStep implements ComputationStep { - - private static final String OFFSET_SEPARATOR = ","; - private static final String SYMBOLS_SEPARATOR = ";"; - - // Temporary variable in order to be able to test that symbols are well computed. Will only contains data from last processed file - private Map symbolsByLineForLastProcessedFile; - - @Override - public String[] supportedProjectQualifiers() { - return new String[]{Qualifiers.PROJECT}; - } - - @Override - public void execute(ComputationContext context) { - int rootComponentRef = context.getReportMetadata().getRootComponentRef(); - recursivelyProcessComponent(context, rootComponentRef); - } - - private void recursivelyProcessComponent(ComputationContext context, int componentRef) { - BatchReportReader reportReader = context.getReportReader(); - BatchReport.Component component = reportReader.readComponent(componentRef); - List symbols = reportReader.readComponentSymbols(componentRef); - processSymbols(component, symbols); - - for (Integer childRef : component.getChildRefList()) { - recursivelyProcessComponent(context, childRef); - } - } - - private void processSymbols(BatchReport.Component component, List symbols) { - symbolsByLineForLastProcessedFile = newHashMap(); - if (!symbols.isEmpty()) { - int symbolId = 1; - for (BatchReport.Symbols.Symbol symbol : symbols) { - processSymbolRange(symbol.getDeclaration(), symbolId); - for (BatchReport.Range reference : symbol.getReferenceList()) { - processSymbolRange(reference, symbolId); - } - symbolId++; - } - } - } - - private void processSymbolRange(BatchReport.Range range, int symboleId){ - int startLine = range.getStartLine(); - if (startLine != range.getEndLine()) { - // TODO support symbols on multiple lines when source will be in compute, in order to be able to know the end line in this case - throw new IllegalStateException("To be implemented : Symbols on multiple lines are not supported for the moment"); - } - StringBuilder symbolLine = symbolsByLineForLastProcessedFile.get(startLine); - if (symbolLine == null) { - symbolLine = new StringBuilder(); - symbolsByLineForLastProcessedFile.put(startLine, symbolLine); - } else { - symbolLine.append(SYMBOLS_SEPARATOR); - } - symbolLine.append(range.getStartOffset()).append(OFFSET_SEPARATOR); - symbolLine.append(range.getEndOffset()).append(OFFSET_SEPARATOR); - symbolLine.append(symboleId); - } - - @VisibleForTesting - Map getSymbolsByLine(){ - return symbolsByLineForLastProcessedFile; - } - - @Override - public String getDescription() { - return "Read Symbols"; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/source/RangeHelperTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/source/RangeHelperTest.java new file mode 100644 index 00000000000..0b326c691a3 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/source/RangeHelperTest.java @@ -0,0 +1,127 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.source; + +import org.junit.Test; +import org.sonar.batch.protocol.output.BatchReport; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; + +public class RangeHelperTest { + + @Test + public void append_range() throws Exception { + StringBuilder element = new StringBuilder(); + RangeHelper.appendRange(element, BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1) + .setStartOffset(2).setEndOffset(3) + .build(), + 1, 5); + assertThat(element.toString()).isEqualTo("2,3,"); + } + + @Test + public void append_range_om_existing_element() throws Exception { + StringBuilder element = new StringBuilder("1,2,a"); + RangeHelper.appendRange(element, BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1) + .setStartOffset(3).setEndOffset(4) + .build(), + 1, 5); + assertThat(element.toString()).isEqualTo("1,2,a;3,4,"); + } + + @Test + public void append_range_not_finishing_in_current_line() throws Exception { + StringBuilder element = new StringBuilder(); + RangeHelper.appendRange(element, BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(3) + .setStartOffset(2).setEndOffset(3) + .build(), + 1, 5); + assertThat(element.toString()).isEqualTo("2,4,"); + } + + @Test + public void append_range_that_began_in_previous_line_and_finish_in_current_line() throws Exception { + StringBuilder element = new StringBuilder(); + RangeHelper.appendRange(element, BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(3) + .setStartOffset(2).setEndOffset(3) + .build(), + 3, 5); + assertThat(element.toString()).isEqualTo("0,3,"); + } + + @Test + public void append_range_that_began_in_previous_line_and_not_finishing_in_current_line() throws Exception { + StringBuilder element = new StringBuilder(); + RangeHelper.appendRange(element, BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(3) + .setStartOffset(2).setEndOffset(3) + .build(), + 2, 5); + assertThat(element.toString()).isEqualTo("0,4,"); + } + + @Test + public void fail_when_end_offset_is_before_start_offset() { + try { + RangeHelper.appendRange(new StringBuilder(), BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1) + .setStartOffset(4).setEndOffset(2) + .build(), + 1, 5); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("End offset 2 cannot be defined before start offset 4 on line 1"); + } + } + + @Test + public void fail_when_end_offset_is_higher_than_line_length() { + try { + RangeHelper.appendRange(new StringBuilder(), BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1) + .setStartOffset(4).setEndOffset(10) + .build(), + 1, 5); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("End offset 10 is defined outside the length (5) of the line 1"); + } + } + + @Test + public void fail_when_start_offset_is_higher_than_line_length() { + try { + RangeHelper.appendRange(new StringBuilder(), BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1) + .setStartOffset(10).setEndOffset(11) + .build(), + 1, 5); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Start offset 10 is defined outside the length (5) of the line 1"); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/source/SymbolsLineReaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/source/SymbolsLineReaderTest.java new file mode 100644 index 00000000000..b5e3c37d17c --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/source/SymbolsLineReaderTest.java @@ -0,0 +1,256 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.source; + +import org.junit.Test; +import org.sonar.batch.protocol.output.BatchReport; +import org.sonar.server.source.db.FileSourceDb; + +import java.util.Collections; +import java.util.List; + +import static com.google.common.collect.Lists.newArrayList; +import static org.assertj.core.api.Assertions.assertThat; + +public class SymbolsLineReaderTest { + + FileSourceDb.Data.Builder sourceData = FileSourceDb.Data.newBuilder(); + FileSourceDb.Line.Builder line1 = sourceData.addLinesBuilder().setSource("line1").setLine(1); + FileSourceDb.Line.Builder line2 = sourceData.addLinesBuilder().setSource("line2").setLine(2); + FileSourceDb.Line.Builder line3 = sourceData.addLinesBuilder().setSource("line3").setLine(3); + FileSourceDb.Line.Builder line4 = sourceData.addLinesBuilder().setSource("line4").setLine(4); + + @Test + public void read_nothing() throws Exception { + SymbolsLineReader symbolsLineReader = new SymbolsLineReader(Collections.emptyList()); + + symbolsLineReader.read(line1); + + assertThat(line1.getSymbols()).isEmpty(); + } + + @Test + public void read_symbols() throws Exception { + List symbols = newArrayList( + BatchReport.Symbols.Symbol.newBuilder() + .setDeclaration(BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1).setStartOffset(2).setEndOffset(4) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(3).setEndLine(3).setStartOffset(1).setEndOffset(3) + .build()) + .build() + ); + + SymbolsLineReader symbolsLineReader = new SymbolsLineReader(symbols); + symbolsLineReader.read(line1); + symbolsLineReader.read(line2); + symbolsLineReader.read(line3); + + assertThat(line1.getSymbols()).isEqualTo("2,4,1"); + assertThat(line2.getSymbols()).isEmpty(); + assertThat(line3.getSymbols()).isEqualTo("1,3,1"); + } + + @Test + public void read_symbols_with_reference_on_same_line() throws Exception { + List symbols = newArrayList( + BatchReport.Symbols.Symbol.newBuilder() + .setDeclaration(BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1).setStartOffset(0).setEndOffset(1) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1).setStartOffset(2).setEndOffset(3) + .build()) + .build() + ); + + SymbolsLineReader symbolsLineReader = new SymbolsLineReader(symbols); + symbolsLineReader.read(line1); + + assertThat(line1.getSymbols()).isEqualTo("0,1,1;2,3,1"); + } + + @Test + public void read_symbols_with_two_references() throws Exception { + List symbols = newArrayList( + BatchReport.Symbols.Symbol.newBuilder() + .setDeclaration(BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1).setStartOffset(2).setEndOffset(4) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(3).setEndLine(3).setStartOffset(1).setEndOffset(3) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(2).setEndLine(2).setStartOffset(0).setEndOffset(2) + .build()) + .build() + ); + + SymbolsLineReader symbolsLineReader = new SymbolsLineReader(symbols); + symbolsLineReader.read(line1); + symbolsLineReader.read(line2); + symbolsLineReader.read(line3); + + assertThat(line1.getSymbols()).isEqualTo("2,4,1"); + assertThat(line2.getSymbols()).isEqualTo("0,2,1"); + assertThat(line3.getSymbols()).isEqualTo("1,3,1"); + } + + @Test + public void read_symbols_when_reference_line_is_before_declaration_line() throws Exception { + List symbols = newArrayList( + BatchReport.Symbols.Symbol.newBuilder() + .setDeclaration(BatchReport.Range.newBuilder() + .setStartLine(2).setEndLine(2).setStartOffset(3).setEndOffset(4) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1).setStartOffset(1).setEndOffset(2) + .build()) + .build() + ); + + SymbolsLineReader symbolsLineReader = new SymbolsLineReader(symbols); + symbolsLineReader.read(line1); + symbolsLineReader.read(line2); + + assertThat(line1.getSymbols()).isEqualTo("1,2,1"); + assertThat(line2.getSymbols()).isEqualTo("3,4,1"); + } + + @Test + public void read_many_symbols_on_lines() throws Exception { + List symbols = newArrayList( + BatchReport.Symbols.Symbol.newBuilder() + .setDeclaration(BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1).setStartOffset(1).setEndOffset(2) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(3).setEndLine(3).setStartOffset(2).setEndOffset(3) + .build()) + .build(), + BatchReport.Symbols.Symbol.newBuilder() + .setDeclaration(BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1).setStartOffset(3).setEndOffset(4) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(3).setEndLine(3).setStartOffset(0).setEndOffset(1) + .build()) + .build() + ); + + SymbolsLineReader symbolsLineReader = new SymbolsLineReader(symbols); + symbolsLineReader.read(line1); + symbolsLineReader.read(line2); + symbolsLineReader.read(line3); + + assertThat(line1.getSymbols()).isEqualTo("1,2,1;3,4,2"); + assertThat(line2.getSymbols()).isEmpty(); + assertThat(line3.getSymbols()).isEqualTo("2,3,1;0,1,2"); + } + + @Test + public void symbol_declaration_should_be_sorted_by_offset() throws Exception { + List symbols = newArrayList( + BatchReport.Symbols.Symbol.newBuilder() + .setDeclaration(BatchReport.Range.newBuilder() + // This symbol begins after the second symbol, it should appear in second place + .setStartLine(1).setEndLine(1).setStartOffset(2).setEndOffset(3) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(3).setEndLine(3).setStartOffset(2).setEndOffset(3) + .build()) + .build(), + BatchReport.Symbols.Symbol.newBuilder() + .setDeclaration(BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1).setStartOffset(0).setEndOffset(1) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(3).setEndLine(3).setStartOffset(0).setEndOffset(1) + .build()) + .build()); + + SymbolsLineReader symbolsLineReader = new SymbolsLineReader(symbols); + symbolsLineReader.read(line1); + symbolsLineReader.read(line2); + symbolsLineReader.read(line3); + + assertThat(line1.getSymbols()).isEqualTo("0,1,1;2,3,2"); + assertThat(line2.getSymbols()).isEmpty(); + assertThat(line3.getSymbols()).isEqualTo("0,1,1;2,3,2"); + } + + @Test + public void symbol_declaration_should_be_sorted_by_line() throws Exception { + List symbols = newArrayList( + BatchReport.Symbols.Symbol.newBuilder() + .setDeclaration(BatchReport.Range.newBuilder() + // This symbol begins after the second symbol, it should appear in second place + .setStartLine(2).setEndLine(2).setStartOffset(0).setEndOffset(1) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(3).setEndLine(3).setStartOffset(2).setEndOffset(3) + .build()) + .build(), + BatchReport.Symbols.Symbol.newBuilder() + .setDeclaration(BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1).setStartOffset(0).setEndOffset(1) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(3).setEndLine(3).setStartOffset(0).setEndOffset(1) + .build()) + .build()); + + SymbolsLineReader symbolsLineReader = new SymbolsLineReader(symbols); + symbolsLineReader.read(line1); + symbolsLineReader.read(line2); + symbolsLineReader.read(line3); + + assertThat(line1.getSymbols()).isEqualTo("0,1,1"); + assertThat(line2.getSymbols()).isEqualTo("0,1,2"); + assertThat(line3.getSymbols()).isEqualTo("0,1,1;2,3,2"); + } + + @Test + public void read_symbols_defined_on_many_lines() throws Exception { + List symbols = newArrayList( + BatchReport.Symbols.Symbol.newBuilder() + .setDeclaration(BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(2).setStartOffset(1).setEndOffset(3) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(3).setEndLine(4).setStartOffset(1).setEndOffset(3) + .build()) + .build()); + + SymbolsLineReader symbolsLineReader = new SymbolsLineReader(symbols); + symbolsLineReader.read(line1); + symbolsLineReader.read(line2); + symbolsLineReader.read(line3); + symbolsLineReader.read(line4); + + assertThat(line1.getSymbols()).isEqualTo("1,4,1"); + assertThat(line2.getSymbols()).isEqualTo("0,3,1"); + assertThat(line3.getSymbols()).isEqualTo("1,4,1"); + assertThat(line4.getSymbols()).isEqualTo("0,3,1"); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistFileSourcesStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistFileSourcesStepTest.java index 79b3b72a9f4..b75846f1a1e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistFileSourcesStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistFileSourcesStepTest.java @@ -205,15 +205,14 @@ public class PersistFileSourcesStepTest extends BaseStepTest { BatchReportWriter writer = initBasicReport(1); writer.writeComponentScm(BatchReport.Scm.newBuilder() - .setComponentRef(FILE_REF) - .addChangeset(BatchReport.Scm.Changeset.newBuilder() - .setAuthor("john") - .setDate(123456789L) - .setRevision("rev-1") - .build()) - .addChangesetIndexByLine(0) - .build() - ); + .setComponentRef(FILE_REF) + .addChangeset(BatchReport.Scm.Changeset.newBuilder() + .setAuthor("john") + .setDate(123456789L) + .setRevision("rev-1") + .build()) + .addChangesetIndexByLine(0) + .build()); sut.execute(new ComputationContext(new BatchReportReader(reportDir), ComponentTesting.newProjectDto(PROJECT_UUID))); @@ -233,13 +232,13 @@ public class PersistFileSourcesStepTest extends BaseStepTest { BatchReportWriter writer = initBasicReport(1); writer.writeComponentSyntaxHighlighting(FILE_REF, newArrayList(BatchReport.SyntaxHighlighting.newBuilder() - .setRange(BatchReport.Range.newBuilder() - .setStartLine(1).setEndLine(1) - .setStartOffset(2).setEndOffset(4) - .build()) - .setType(Constants.HighlightingType.ANNOTATION) + .setRange(BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1) + .setStartOffset(2).setEndOffset(4) .build()) - ); + .setType(Constants.HighlightingType.ANNOTATION) + .build() + )); sut.execute(new ComputationContext(new BatchReportReader(reportDir), ComponentTesting.newProjectDto(PROJECT_UUID))); @@ -252,6 +251,34 @@ public class PersistFileSourcesStepTest extends BaseStepTest { assertThat(data.getLines(0).getHighlighting()).isEqualTo("2,4,a"); } + @Test + public void persist_symbols() throws Exception { + BatchReportWriter writer = initBasicReport(3); + + writer.writeComponentSymbols(FILE_REF, newArrayList( + BatchReport.Symbols.Symbol.newBuilder() + .setDeclaration(BatchReport.Range.newBuilder() + .setStartLine(1).setEndLine(1).setStartOffset(2).setEndOffset(4) + .build()) + .addReference(BatchReport.Range.newBuilder() + .setStartLine(3).setEndLine(3).setStartOffset(1).setEndOffset(3) + .build() + ).build() + )); + + sut.execute(new ComputationContext(new BatchReportReader(reportDir), ComponentTesting.newProjectDto(PROJECT_UUID))); + + assertThat(dbTester.countRowsOfTable("file_sources")).isEqualTo(1); + FileSourceDto fileSourceDto = dbClient.fileSourceDao().select(FILE_UUID); + FileSourceDb.Data data = FileSourceDto.decodeData(fileSourceDto.getBinaryData()); + + assertThat(data.getLinesList()).hasSize(3); + + assertThat(data.getLines(0).getSymbols()).isEqualTo("2,4,1"); + assertThat(data.getLines(1).getSymbols()).isEmpty(); + assertThat(data.getLines(2).getSymbols()).isEqualTo("1,3,1"); + } + @Test public void not_update_sources_when_nothing_has_changed() throws Exception { // Existing sources @@ -379,7 +406,7 @@ public class PersistFileSourcesStepTest extends BaseStepTest { .build()); List lines = newArrayList(); - for (int i=1; i<=numberOfLines; i++) { + for (int i = 1; i <= numberOfLines; i++) { lines.add("line" + i); } FileUtils.writeLines(writer.getFileStructure().fileFor(FileStructure.Domain.SOURCE, FILE_REF), lines); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistSymbolsStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistSymbolsStepTest.java deleted file mode 100644 index 327fde4db25..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistSymbolsStepTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube 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. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.sonar.server.computation.step; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.rules.TemporaryFolder; -import org.sonar.batch.protocol.Constants; -import org.sonar.batch.protocol.output.BatchReport; -import org.sonar.batch.protocol.output.BatchReportReader; -import org.sonar.batch.protocol.output.BatchReportWriter; -import org.sonar.core.component.ComponentDto; -import org.sonar.server.computation.ComputationContext; -import org.sonar.test.DbTests; - -import java.io.File; -import java.io.IOException; - -import static com.google.common.collect.Lists.newArrayList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -@Category(DbTests.class) -public class PersistSymbolsStepTest extends BaseStepTest { - - private static final Integer FILE_REF = 3; - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - File reportDir; - - PersistSymbolsStep step; - - @Before - public void setup() throws Exception { - reportDir = temp.newFolder(); - step = new PersistSymbolsStep(); - } - - @Override - protected ComputationStep step() throws IOException { - return step; - } - - @Test - public void compute_no_symbol() throws Exception { - initReport(); - - step.execute(new ComputationContext(new BatchReportReader(reportDir), mock(ComponentDto.class))); - - assertThat(step.getSymbolsByLine()).isEmpty(); - } - - @Test - public void compute_one_symbol() throws Exception { - BatchReportWriter writer = initReport(); - - writer.writeComponentSymbols(FILE_REF, newArrayList(BatchReport.Symbols.Symbol.newBuilder() - .setDeclaration(BatchReport.Range.newBuilder() - .setStartLine(1) - .setStartOffset(3) - .setEndLine(1) - .setEndOffset(5) - .build()) - .addReference(BatchReport.Range.newBuilder() - .setStartLine(10) - .setStartOffset(15) - .setEndLine(10) - .setEndOffset(17) - .build()) - .addReference(BatchReport.Range.newBuilder() - .setStartLine(11) - .setStartOffset(7) - .setEndLine(11) - .setEndOffset(9) - .build()) - .build())); - - step.execute(new ComputationContext(new BatchReportReader(reportDir), mock(ComponentDto.class))); - - assertThat(step.getSymbolsByLine()).hasSize(3); - assertThat(step.getSymbolsByLine().get(1).toString()).isEqualTo("3,5,1"); - assertThat(step.getSymbolsByLine().get(10).toString()).isEqualTo("15,17,1"); - assertThat(step.getSymbolsByLine().get(11).toString()).isEqualTo("7,9,1"); - } - - @Test - public void compute_two_symbols() throws Exception { - BatchReportWriter writer = initReport(); - - writer.writeComponentSymbols(FILE_REF, newArrayList( - BatchReport.Symbols.Symbol.newBuilder() - .setDeclaration(BatchReport.Range.newBuilder() - .setStartLine(1) - .setEndLine(1) - .setStartOffset(3) - .setEndOffset(5) - .build()) - .addReference(BatchReport.Range.newBuilder() - .setStartLine(10) - .setStartOffset(15) - .setEndLine(10) - .setEndOffset(16) - .build()) - .build(), - BatchReport.Symbols.Symbol.newBuilder() - .setDeclaration(BatchReport.Range.newBuilder() - .setStartLine(1) - .setStartOffset(5) - .setEndLine(1) - .setEndOffset(6) - .build()) - .addReference(BatchReport.Range.newBuilder() - .setStartLine(10) - .setStartOffset(9) - .setEndLine(10) - .setEndOffset(10) - .build()) - .build()) - ); - - step.execute(new ComputationContext(new BatchReportReader(reportDir), mock(ComponentDto.class))); - - assertThat(step.getSymbolsByLine()).hasSize(2); - assertThat(step.getSymbolsByLine().get(1).toString()).isEqualTo("3,5,1;5,6,2"); - assertThat(step.getSymbolsByLine().get(10).toString()).isEqualTo("15,16,1;9,10,2"); - } - - @Test(expected = IllegalStateException.class) - public void fail_when_symbol_is_defined_on_different_line() throws Exception { - BatchReportWriter writer = initReport(); - - writer.writeComponentSymbols(FILE_REF, newArrayList(BatchReport.Symbols.Symbol.newBuilder() - .setDeclaration(BatchReport.Range.newBuilder() - .setStartLine(1) - .setStartOffset(3) - .setEndLine(2) - .setEndOffset(1) - .build()) - .addReference(BatchReport.Range.newBuilder() - .setStartLine(10) - .setStartOffset(15) - .setEndLine(10) - .setEndOffset(17) - .build()) - .build())); - - step.execute(new ComputationContext(new BatchReportReader(reportDir), mock(ComponentDto.class))); - } - - private BatchReportWriter initReport() { - BatchReportWriter writer = new BatchReportWriter(reportDir); - writer.writeMetadata(BatchReport.Metadata.newBuilder() - .setRootComponentRef(1) - .setProjectKey("PROJECT_KEY") - .setAnalysisDate(150000000L) - .build()); - - writer.writeComponent(BatchReport.Component.newBuilder() - .setRef(1) - .setType(Constants.ComponentType.PROJECT) - .setUuid("PROJECT_A") - .addChildRef(2) - .build()); - writer.writeComponent(BatchReport.Component.newBuilder() - .setRef(2) - .setType(Constants.ComponentType.MODULE) - .setUuid("BCDE") - .addChildRef(FILE_REF) - .build()); - writer.writeComponent(BatchReport.Component.newBuilder() - .setRef(FILE_REF) - .setType(Constants.ComponentType.FILE) - .setUuid("FILE_A") - .build()); - return writer; - } - -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/SourceDataFactoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/SourceDataFactoryTest.java index 3d003ecc8a3..b1c45ac1bd8 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/SourceDataFactoryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/SourceDataFactoryTest.java @@ -363,6 +363,46 @@ public class SourceDataFactoryTest { assertThat(data.getLines(2).getSymbols()).isEqualTo("4,5,1;0,2,2"); } + @Test + public void applySymbolReferences2() throws Exception { + batchReportWriter.writeComponentSymbols(1, Arrays.asList( + newSymbol(2, 0, 2, 2, + 1, 1, 1, 2, + 3, 0, 3, 2), + newSymbol(3, 1, 3, 2, + 1, 0, 1, 1, + 3, 4, 3, 5) + )); + inputFile.setOriginalLineOffsets(new int[] {0, 4, 7}); + + sut.applySymbolReferences(inputFile, output); + + FileSourceDb.Data data = output.build(); + assertThat(data.getLines(0).getSymbols()).isEqualTo("1,2,1;0,1,2"); + assertThat(data.getLines(1).getSymbols()).isEqualTo("0,2,1"); + assertThat(data.getLines(2).getSymbols()).isEqualTo("0,2,1;1,2,2;4,5,2"); + } + + @Test + public void applySymbolReferences3() throws Exception { + batchReportWriter.writeComponentSymbols(1, Arrays.asList( + newSymbol(2, 0, 2, 2, + 1, 0, 1, 1, + 3, 0, 3, 2), + newSymbol(3, 1, 3, 2, + 1, 1, 1, 2, + 3, 4, 3, 5) + )); + inputFile.setOriginalLineOffsets(new int[] {0, 4, 7}); + + sut.applySymbolReferences(inputFile, output); + + FileSourceDb.Data data = output.build(); + assertThat(data.getLines(0).getSymbols()).isEqualTo("0,1,1;1,2,2"); + assertThat(data.getLines(1).getSymbols()).isEqualTo("0,2,1"); + assertThat(data.getLines(2).getSymbols()).isEqualTo("0,2,1;1,2,2;4,5,2"); + } + @Test public void applySymbolReferences_declaration_order_is_not_important() throws Exception { batchReportWriter.writeComponentSymbols(1, Arrays.asList( -- 2.39.5