/* * SonarQube * Copyright (C) 2009-2017 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.server.computation.task.projectanalysis.source; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Random; import org.junit.Rule; import org.junit.Test; import org.sonar.api.utils.log.LogTester; import org.sonar.db.protobuf.DbFileSources; import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReport.SyntaxHighlightingRule.HighlightingType; import org.sonar.scanner.protocol.output.ScannerReport.TextRange; import org.sonar.server.computation.task.projectanalysis.component.Component; import org.sonar.server.computation.task.projectanalysis.source.RangeOffsetConverter.RangeOffsetConverterException; import static com.google.common.collect.ImmutableMap.of; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.sonar.api.utils.log.LoggerLevel.WARN; import static org.sonar.db.protobuf.DbFileSources.Data.newBuilder; import static org.sonar.scanner.protocol.output.ScannerReport.SyntaxHighlightingRule.HighlightingType.ANNOTATION; import static org.sonar.scanner.protocol.output.ScannerReport.SyntaxHighlightingRule.HighlightingType.COMMENT; import static org.sonar.scanner.protocol.output.ScannerReport.SyntaxHighlightingRule.HighlightingType.CONSTANT; import static org.sonar.scanner.protocol.output.ScannerReport.SyntaxHighlightingRule.HighlightingType.CPP_DOC; import static org.sonar.scanner.protocol.output.ScannerReport.SyntaxHighlightingRule.HighlightingType.HIGHLIGHTING_STRING; import static org.sonar.scanner.protocol.output.ScannerReport.SyntaxHighlightingRule.HighlightingType.KEYWORD; import static org.sonar.server.computation.task.projectanalysis.component.ReportComponent.builder; public class HighlightingLineReaderTest { @Rule public LogTester logTester = new LogTester(); static final Component FILE = builder(Component.Type.FILE, 1).setUuid("FILE_UUID").setKey("FILE_KEY").build(); static final int DEFAULT_LINE_LENGTH = 5; static final int LINE_1 = 1; static final int LINE_2 = 2; static final int LINE_3 = 3; static final int LINE_4 = 4; static final String RANGE_LABEL_1 = "1,2"; static final String RANGE_LABEL_2 = "2,3"; static final String RANGE_LABEL_3 = "3,4"; static final String RANGE_LABEL_4 = "0,2"; static final String RANGE_LABEL_5 = "0,3"; RangeOffsetConverter rangeOffsetConverter = mock(RangeOffsetConverter.class); DbFileSources.Data.Builder sourceData = newBuilder(); DbFileSources.Line.Builder line1 = sourceData.addLinesBuilder().setSource("line1").setLine(1); DbFileSources.Line.Builder line2 = sourceData.addLinesBuilder().setSource("line2").setLine(2); DbFileSources.Line.Builder line3 = sourceData.addLinesBuilder().setSource("line3").setLine(3); DbFileSources.Line.Builder line4 = sourceData.addLinesBuilder().setSource("line4").setLine(4); @Test public void nothing_to_read() { HighlightingLineReader highlightingLineReader = newReader(Collections.<TextRange, HighlightingType>emptyMap()); DbFileSources.Line.Builder lineBuilder = newBuilder().addLinesBuilder().setLine(1); highlightingLineReader.read(lineBuilder); assertThat(lineBuilder.hasHighlighting()).isFalse(); } @Test public void read_one_line() { HighlightingLineReader highlightingLineReader = newReader(of( newSingleLineTextRangeWithExpectingLabel(LINE_1, RANGE_LABEL_1), ANNOTATION)); highlightingLineReader.read(line1); assertThat(line1.getHighlighting()).isEqualTo(RANGE_LABEL_1 + ",a"); } @Test public void read_many_lines() { HighlightingLineReader highlightingLineReader = newReader(of( newSingleLineTextRangeWithExpectingLabel(LINE_1, RANGE_LABEL_1), ANNOTATION, newSingleLineTextRangeWithExpectingLabel(LINE_2, RANGE_LABEL_2), COMMENT, newSingleLineTextRangeWithExpectingLabel(LINE_4, RANGE_LABEL_3), CONSTANT)); highlightingLineReader.read(line1); highlightingLineReader.read(line2); highlightingLineReader.read(line3); highlightingLineReader.read(line4); assertThat(line1.getHighlighting()).isEqualTo(RANGE_LABEL_1 + ",a"); assertThat(line2.getHighlighting()).isEqualTo(RANGE_LABEL_2 + ",cd"); assertThat(line4.getHighlighting()).isEqualTo(RANGE_LABEL_3 + ",c"); } @Test public void supports_highlighting_over_multiple_lines_including_an_empty_one() { List<ScannerReport.SyntaxHighlightingRule> syntaxHighlightingList = new ArrayList<>(); addHighlighting(syntaxHighlightingList, 1, 0, 1, 7, KEYWORD); // package addHighlighting(syntaxHighlightingList, 2, 0, 4, 6, CPP_DOC); // comment over 3 lines addHighlighting(syntaxHighlightingList, 5, 0, 5, 6, KEYWORD); // public addHighlighting(syntaxHighlightingList, 5, 7, 5, 12, KEYWORD); // class HighlightingLineReader highlightingLineReader = new HighlightingLineReader(FILE, syntaxHighlightingList.iterator(), new RangeOffsetConverter()); DbFileSources.Line.Builder[] builders = new DbFileSources.Line.Builder[] { addSourceLine(highlightingLineReader, 1, "package example;"), addSourceLine(highlightingLineReader, 2, "/*"), addSourceLine(highlightingLineReader, 3, ""), addSourceLine(highlightingLineReader, 4, " foo*/"), addSourceLine(highlightingLineReader, 5, "public class One {"), addSourceLine(highlightingLineReader, 6, "}") }; assertThat(builders) .extracting("highlighting") .containsExactly( "0,7,k", "0,2,cppd", "", "0,6,cppd", "0,6,k;7,12,k", ""); } private DbFileSources.Line.Builder addSourceLine(HighlightingLineReader highlightingLineReader, int line, String source) { DbFileSources.Line.Builder lineBuilder = sourceData.addLinesBuilder().setSource(source).setLine(line); highlightingLineReader.read(lineBuilder); return lineBuilder; } private void addHighlighting(List<ScannerReport.SyntaxHighlightingRule> syntaxHighlightingList, int startLine, int startOffset, int endLine, int endOffset, HighlightingType type) { TextRange.Builder textRangeBuilder = TextRange.newBuilder(); ScannerReport.SyntaxHighlightingRule.Builder ruleBuilder = ScannerReport.SyntaxHighlightingRule.newBuilder(); syntaxHighlightingList.add(ruleBuilder .setRange(textRangeBuilder .setStartLine(startLine).setEndLine(endLine) .setStartOffset(startOffset).setEndOffset(endOffset) .build()) .setType(type) .build()); } @Test public void read_many_syntax_highlighting_on_same_line() { HighlightingLineReader highlightingLineReader = newReader(of( newSingleLineTextRangeWithExpectingLabel(LINE_1, RANGE_LABEL_1), ANNOTATION, newSingleLineTextRangeWithExpectingLabel(LINE_1, RANGE_LABEL_2), COMMENT)); highlightingLineReader.read(line1); assertThat(line1.getHighlighting()).isEqualTo(RANGE_LABEL_1 + ",a;" + RANGE_LABEL_2 + ",cd"); } @Test public void read_one_syntax_highlighting_on_many_lines() { // This highlighting begin on line 1 and finish on line 3 TextRange textRange = newTextRange(LINE_1, LINE_3); when(rangeOffsetConverter.offsetToString(textRange, LINE_1, DEFAULT_LINE_LENGTH)).thenReturn(RANGE_LABEL_1); when(rangeOffsetConverter.offsetToString(textRange, LINE_2, 6)).thenReturn(RANGE_LABEL_2); when(rangeOffsetConverter.offsetToString(textRange, LINE_3, DEFAULT_LINE_LENGTH)).thenReturn(RANGE_LABEL_3); HighlightingLineReader highlightingLineReader = newReader(of(textRange, ANNOTATION)); highlightingLineReader.read(line1); DbFileSources.Line.Builder line2 = sourceData.addLinesBuilder().setSource("line 2").setLine(2); highlightingLineReader.read(line2); highlightingLineReader.read(line3); assertThat(line1.getHighlighting()).isEqualTo(RANGE_LABEL_1 + ",a"); assertThat(line2.getHighlighting()).isEqualTo(RANGE_LABEL_2 + ",a"); assertThat(line3.getHighlighting()).isEqualTo(RANGE_LABEL_3 + ",a"); } @Test public void read_many_syntax_highlighting_on_many_lines() { TextRange textRange1 = newTextRange(LINE_1, LINE_3); when(rangeOffsetConverter.offsetToString(textRange1, LINE_1, DEFAULT_LINE_LENGTH)).thenReturn(RANGE_LABEL_1); when(rangeOffsetConverter.offsetToString(textRange1, LINE_2, DEFAULT_LINE_LENGTH)).thenReturn(RANGE_LABEL_2); when(rangeOffsetConverter.offsetToString(textRange1, LINE_3, DEFAULT_LINE_LENGTH)).thenReturn(RANGE_LABEL_3); TextRange textRange2 = newTextRange(LINE_2, LINE_4); when(rangeOffsetConverter.offsetToString(textRange2, LINE_2, DEFAULT_LINE_LENGTH)).thenReturn(RANGE_LABEL_2); when(rangeOffsetConverter.offsetToString(textRange2, LINE_3, DEFAULT_LINE_LENGTH)).thenReturn(RANGE_LABEL_2); when(rangeOffsetConverter.offsetToString(textRange2, LINE_4, DEFAULT_LINE_LENGTH)).thenReturn(RANGE_LABEL_4); TextRange textRange3 = newTextRange(LINE_2, LINE_2); when(rangeOffsetConverter.offsetToString(textRange3, LINE_2, DEFAULT_LINE_LENGTH)).thenReturn(RANGE_LABEL_5); HighlightingLineReader highlightingLineReader = newReader(of( textRange1, ANNOTATION, textRange2, HIGHLIGHTING_STRING, textRange3, COMMENT)); highlightingLineReader.read(line1); highlightingLineReader.read(line2); highlightingLineReader.read(line3); highlightingLineReader.read(line4); assertThat(line1.getHighlighting()).isEqualTo(RANGE_LABEL_1 + ",a"); assertThat(line2.getHighlighting()).isEqualTo(RANGE_LABEL_2 + ",a;" + RANGE_LABEL_2 + ",s;" + RANGE_LABEL_5 + ",cd"); assertThat(line3.getHighlighting()).isEqualTo(RANGE_LABEL_3 + ",a;" + RANGE_LABEL_2 + ",s"); assertThat(line4.getHighlighting()).isEqualTo(RANGE_LABEL_4 + ",s"); } @Test public void read_highlighting_declared_on_a_whole_line() { TextRange textRange = newTextRange(LINE_1, LINE_2); when(rangeOffsetConverter.offsetToString(textRange, LINE_1, DEFAULT_LINE_LENGTH)).thenReturn(RANGE_LABEL_1); when(rangeOffsetConverter.offsetToString(textRange, LINE_2, DEFAULT_LINE_LENGTH)).thenReturn(""); HighlightingLineReader highlightingLineReader = newReader(of(textRange, ANNOTATION)); highlightingLineReader.read(line1); highlightingLineReader.read(line2); highlightingLineReader.read(line3); assertThat(line1.getHighlighting()).isEqualTo(RANGE_LABEL_1 + ",a"); // Nothing should be set on line 2 assertThat(line2.getHighlighting()).isEmpty(); assertThat(line3.getHighlighting()).isEmpty(); } @Test public void not_fail_and_stop_processing_when_range_offset_converter_throw_RangeOffsetConverterException() { TextRange textRange1 = newTextRange(LINE_1, LINE_1); doThrow(RangeOffsetConverterException.class).when(rangeOffsetConverter).offsetToString(textRange1, LINE_1, DEFAULT_LINE_LENGTH); HighlightingLineReader highlightingLineReader = newReader(of( textRange1, HighlightingType.ANNOTATION, newSingleLineTextRangeWithExpectingLabel(LINE_2, RANGE_LABEL_1), HIGHLIGHTING_STRING)); highlightingLineReader.read(line1); highlightingLineReader.read(line2); assertNoHighlighting(); assertThat(logTester.logs(WARN)).isNotEmpty(); } @Test public void keep_existing_processed_highlighting_when_range_offset_converter_throw_RangeOffsetConverterException() { TextRange textRange2 = newTextRange(LINE_2, LINE_2); doThrow(RangeOffsetConverterException.class).when(rangeOffsetConverter).offsetToString(textRange2, LINE_2, DEFAULT_LINE_LENGTH); HighlightingLineReader highlightingLineReader = newReader(of( newSingleLineTextRangeWithExpectingLabel(LINE_1, RANGE_LABEL_1), ANNOTATION, textRange2, HIGHLIGHTING_STRING)); highlightingLineReader.read(line1); highlightingLineReader.read(line2); assertThat(line1.hasHighlighting()).isTrue(); assertThat(line2.hasHighlighting()).isFalse(); assertThat(logTester.logs(WARN)).isNotEmpty(); } @Test public void display_file_key_in_warning_when_range_offset_converter_throw_RangeOffsetConverterException() { TextRange textRange1 = newTextRange(LINE_1, LINE_1); doThrow(RangeOffsetConverterException.class).when(rangeOffsetConverter).offsetToString(textRange1, LINE_1, DEFAULT_LINE_LENGTH); HighlightingLineReader highlightingLineReader = newReader(of(textRange1, ANNOTATION)); highlightingLineReader.read(line1); assertThat(logTester.logs(WARN)).containsOnly("Inconsistency detected in Highlighting data. Highlighting will be ignored for file 'FILE_KEY'"); } private HighlightingLineReader newReader(Map<TextRange, HighlightingType> textRangeByType) { List<ScannerReport.SyntaxHighlightingRule> syntaxHighlightingList = new ArrayList<>(); for (Map.Entry<TextRange, HighlightingType> entry : textRangeByType.entrySet()) { syntaxHighlightingList.add(ScannerReport.SyntaxHighlightingRule.newBuilder() .setRange(entry.getKey()) .setType(entry.getValue()) .build()); } return new HighlightingLineReader(FILE, syntaxHighlightingList.iterator(), rangeOffsetConverter); } private static TextRange newTextRange(int startLine, int enLine) { Random random = new Random(); return TextRange.newBuilder() .setStartLine(startLine).setEndLine(enLine) // Offsets are not used by the reader .setStartOffset(random.nextInt()).setEndOffset(random.nextInt()) .build(); } private TextRange newSingleLineTextRangeWithExpectingLabel(int line, String rangeLabel) { TextRange textRange = newTextRange(line, line); when(rangeOffsetConverter.offsetToString(textRange, line, DEFAULT_LINE_LENGTH)).thenReturn(rangeLabel); return textRange; } private void assertNoHighlighting() { assertThat(line1.hasHighlighting()).isFalse(); assertThat(line2.hasHighlighting()).isFalse(); assertThat(line3.hasHighlighting()).isFalse(); assertThat(line4.hasHighlighting()).isFalse(); } }