/*
* SonarQube Java
* Copyright (C) 2012-2016 SonarSource SA
* mailto:contact 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.java.xml;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.sonar.java.AnalyzerMessage;
import org.sonar.java.SonarComponents;
import org.sonar.plugins.java.api.JavaCheck;
import org.sonar.squidbridge.api.AnalysisException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
public class XmlCheckContextImplTest {
private static String reportedMessage;
private SonarComponents sonarComponents;
private XmlCheckContext context;
private XPath xPath;
private static final File XML_FILE = new File("src/test/files/xml/parsing.xml");
private static final XmlCheck CHECK = new XmlCheck() {
@Override
public void scanFile(XmlCheckContext context) {
}
};
private static final int LINE = 42;
@Before
public void setup() {
reportedMessage = null;
sonarComponents = createSonarComponentsMock();
xPath = XPathFactory.newInstance().newXPath();
context = new XmlCheckContextImpl(XmlParser.parseXML(XML_FILE), XML_FILE, xPath, sonarComponents);
}
@Test
public void can_retrieve_file_from_context() {
assertThat(context.getFile()).isEqualTo(XML_FILE);
}
@Test
public void can_retrieve_sonar_component_from_context() {
assertThat(((XmlCheckContextImpl) context).getSonarComponents()).isEqualTo(sonarComponents);
}
@Test
public void can_use_xPath() throws Exception {
nodesMatchingXPathExpression("assembly-descriptor", 1);
nodesMatchingXPathExpression("//interceptor-binding", 1);
nodesMatchingXPathExpression("//test2/item", 3);
nodesMatchingXPathExpression("//unknownNode", 0);
}
private void nodesMatchingXPathExpression(String expression, int expectedChildren) throws Exception {
assertThat(context.evaluateOnDocument(context.compile(expression))).hasSize(expectedChildren);
}
@Test
public void can_use_xPath_from_node() throws Exception {
nodesMatchingXPathExpressionFromNode("assembly-descriptor", "test", 1);
nodesMatchingXPathExpressionFromNode("//interceptor-binding", "exclude-default-interceptors", 1);
nodesMatchingXPathExpressionFromNode("assembly-descriptor", "test2/item", 3);
}
private void nodesMatchingXPathExpressionFromNode(String expressionOnDocument, String expressionOnNode, int expectedChildren) throws Exception {
XPathExpression xPathExprOnNode = context.compile(expressionOnNode);
assertThat(context.evaluate(xPathExprOnNode, firstNode(context, expressionOnDocument))).hasSize(expectedChildren);
}
private static Node firstNode(XmlCheckContext context, String expression) throws XPathExpressionException {
Node result = null;
for (Node node : context.evaluateOnDocument(context.compile(expression))) {
result = node;
break;
}
return result;
}
@Test(expected = UnsupportedOperationException.class)
public void should_fail_when_trying_to_remove_nodes() throws Exception {
Iterable<Node> items = context.evaluateOnDocument(context.compile("//test2/item"));
items.iterator().remove();
}
@Test(expected = NoSuchElementException.class)
public void should_fail_when_trying_to_access_more_items() throws Exception {
Iterable<Node> items = context.evaluateOnDocument(context.compile("//test2/item"));
Iterator<Node> iterator = items.iterator();
for (int i = 0; i < 5; i++) {
iterator.next();
}
}
@Test(expected = AnalysisException.class)
public void should_fail_when_compiling_bad_XPathExpression() throws Exception {
context.compile("");
}
@Test(expected = AnalysisException.class)
public void should_fail_when_unable_to_evaluate_XPathExpression() throws Exception {
context = new XmlCheckContextImpl(null, new File("."), xPath, sonarComponents);
context.evaluateOnDocument(context.compile("tag"));
}
@Test
public void should_report_issue_on_line() {
context.reportIssue(CHECK, LINE, "message");
assertThat(reportedMessage).isEqualTo("onLine:message");
}
@Test
public void should_report_issue_on_file() {
context.reportIssueOnFile(CHECK, "message");
assertThat(reportedMessage).isEqualTo("onFile:message");
}
@Test
public void should_report_issue_on_node() throws Exception {
Node node = firstNode(context, "//exclude-default-interceptors");
int expectedLine = XmlCheckUtils.nodeLine(node);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
reportedMessage = "onNode:" + (String) invocation.getArguments()[3];
return null;
}
}).when(sonarComponents).addIssue(any(File.class), eq(CHECK), eq(expectedLine), anyString(), eq((Integer) null));
context.reportIssue(CHECK, node, "message");
assertThat(reportedMessage).isEqualTo("onNode:message");
}
@Test
public void should_report_issue_on_node_with_secondary() throws Exception {
Node node = firstNode(context, "//test2");
int nodeLine = XmlCheckUtils.nodeLine(node);
Node childNode = node.getFirstChild();
int childNodeLine = XmlCheckUtils.nodeLine(childNode);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
AnalyzerMessage analyzerMessage = (AnalyzerMessage) invocation.getArguments()[0];
reportedMessage = "onNode:" + analyzerMessage.getMessage() + "(" + analyzerMessage.getLine() + ")";
for (AnalyzerMessage secondary : analyzerMessage.flows.stream().map(l -> l.get(0)).collect(Collectors.toList())) {
reportedMessage += ";onChild:" + secondary.getMessage() + "(" + secondary.getLine() + ")";
}
return null;
}
}).when(sonarComponents).reportIssue(any(AnalyzerMessage.class));
context.reportIssue(CHECK, node, "message1", Lists.newArrayList(new XmlCheckContext.XmlDocumentLocation("message2", childNode)));
String expectedMessage = "onNode:message1(" + nodeLine + ");onChild:message2(" + childNodeLine + ")";
assertThat(reportedMessage).isEqualTo(expectedMessage);
}
@Test
public void should_report_issue_on_node_with_secondary_and_cost() throws Exception {
Node node = firstNode(context, "//test2");
int nodeLine = XmlCheckUtils.nodeLine(node);
Node childNode = node.getFirstChild();
int childNodeLine = XmlCheckUtils.nodeLine(childNode);
int cost = 42;
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
AnalyzerMessage analyzerMessage = (AnalyzerMessage) invocation.getArguments()[0];
reportedMessage = "onNode:" + analyzerMessage.getMessage() + "(" + analyzerMessage.getLine() + ")[" + analyzerMessage.getCost() + "]";
for (AnalyzerMessage secondary : analyzerMessage.flows.stream().map(l -> l.get(0)).collect(Collectors.toList())) {
reportedMessage += ";onChild:" + secondary.getMessage() + "(" + secondary.getLine() + ")";
}
return null;
}
}).when(sonarComponents).reportIssue(any(AnalyzerMessage.class));
context.reportIssue(CHECK, node, "message1", Lists.newArrayList(new XmlCheckContext.XmlDocumentLocation("message2", childNode)), cost);
String expectedMessage = "onNode:message1(" + nodeLine + ")[42.0];onChild:message2(" + childNodeLine + ")";
assertThat(reportedMessage).isEqualTo(expectedMessage);
}
@Test
public void should_not_report_issue_on_unknown_node() throws Exception {
// manual parsing
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(context.getFile());
// uses document with recorded lines
Node node = Iterables.get(context.evaluate(context.compile("//exclude-default-interceptors"), doc), 0);
context.reportIssue(CHECK, node, "message");
verify(sonarComponents, never()).addIssue(any(File.class), any(JavaCheck.class), anyInt(), anyString(), anyInt());
}
@Test
public void should_not_report_issue_on_unknown_node_with_secondaries() throws Exception {
// manual parsing
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(context.getFile());
// uses document with recorded lines
Node node = Iterables.get(context.evaluate(context.compile("//exclude-default-interceptors"), doc), 0);
context.reportIssue(CHECK, node, "message1", Lists.newArrayList(new XmlCheckContext.XmlDocumentLocation("message2", node.getFirstChild())));
verify(sonarComponents, never()).reportIssue(any(AnalyzerMessage.class));
}
@Test
public void should_not_report_unknown_secondaries() throws Exception {
// manual parsing
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(context.getFile());
Node node = firstNode(context, "//test2");
int nodeLine = XmlCheckUtils.nodeLine(node);
// uses document with recorded lines
Node child = Iterables.get(context.evaluate(context.compile("//test2/item"), doc), 0);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
AnalyzerMessage analyzerMessage = (AnalyzerMessage) invocation.getArguments()[0];
reportedMessage = "onNode:" + analyzerMessage.getMessage() + "(" + analyzerMessage.getLine() + ")";
for (AnalyzerMessage secondary : analyzerMessage.flows.stream().map(l -> l.get(0)).collect(Collectors.toList())) {
reportedMessage += ";onChild:" + secondary.getMessage() + "(" + secondary.getLine() + ")";
}
return null;
}
}).when(sonarComponents).reportIssue(any(AnalyzerMessage.class));
context.reportIssue(CHECK, node, "message1", Lists.newArrayList(new XmlCheckContext.XmlDocumentLocation("message2", child)));
assertThat(reportedMessage).isEqualTo("onNode:message1(" + nodeLine + ")");
}
@Test
public void should_not_report_issue_on_node_text_node() throws Exception {
Node textNode = firstNode(context, "//exclude-default-interceptors").getFirstChild();
context.reportIssue(CHECK, textNode, "message");
Mockito.verify(sonarComponents, never()).addIssue(any(File.class), any(JavaCheck.class), anyInt(), anyString(), anyInt());
}
private static SonarComponents createSonarComponentsMock() {
SonarComponents sonarComponents = mock(SonarComponents.class);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
reportedMessage = "onLine:" + invocation.getArguments()[3];
return null;
}
}).when(sonarComponents).addIssue(any(File.class), eq(CHECK), eq(LINE), anyString(), eq(null));
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
reportedMessage = "onFile:" + invocation.getArguments()[3];
return null;
}
}).when(sonarComponents).addIssue(any(File.class), eq(CHECK), eq(-1), anyString(), eq(null));
return sonarComponents;
}
}