Skip to content
Permalink
Browse files Browse the repository at this point in the history
XWIKI-19838: Improve handling of unexpected parser errors
  • Loading branch information
manuelleduc committed Oct 13, 2022
1 parent d4984c9 commit e5b82cd
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 44 deletions.
Expand Up @@ -59,7 +59,15 @@ public class DefaultContentParser implements ContentParser
@Override
public XDOM parse(String content, Syntax syntax) throws ParseException, MissingParserException
{
return getParser(syntax).parse(new StringReader(content == null ? "" : content));
Parser parser = getParser(syntax);
try {
return parser.parse(new StringReader(content == null ? "" : content));
} catch (StackOverflowError | Exception e) {
// All exceptions as well as stack overflow errors are captured and wrapped in parse exceptions to make sure
// that they are handled correctly by the callers. Without this, some parsing issues can be badly handled,
// leading to instability issues.
throw new ParseException(String.format("Failed to parse with syntax [%s].", syntax.toIdString()), e);
}
}

@Override
Expand Down
Expand Up @@ -19,93 +19,122 @@
*/
package org.xwiki.rendering.internal.parser;

import static org.hamcrest.CoreMatchers.any;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import static org.mockito.Mockito.when;

import java.io.Reader;
import java.util.Collections;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.xwiki.component.internal.ContextComponentManagerProvider;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.block.XDOM;
import org.xwiki.rendering.listener.MetaData;
import org.xwiki.rendering.parser.ContentParser;
import org.xwiki.rendering.parser.MissingParserException;
import org.xwiki.rendering.parser.ParseException;
import org.xwiki.rendering.parser.Parser;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.mockito.MockitoComponentMockingRule;
import org.xwiki.test.junit5.mockito.ComponentTest;
import org.xwiki.test.junit5.mockito.InjectMockComponents;
import org.xwiki.test.junit5.mockito.MockComponent;
import org.xwiki.test.mockito.MockitoComponentManager;

import static java.util.Collections.emptyList;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import static org.xwiki.rendering.syntax.Syntax.PLAIN_1_0;
import static org.xwiki.rendering.syntax.Syntax.XWIKI_2_1;

/**
* Unit tests for {@link org.xwiki.rendering.internal.parser.DefaultContentParser}.
* Unit tests for {@link DefaultContentParser}.
*
* @version $Id$
* @since 6.0M2
*/
@ComponentTest
@ComponentList(ContextComponentManagerProvider.class)
public class DefaultContentParserTest
class DefaultContentParserTest
{
@Rule
public final MockitoComponentMockingRule<ContentParser> mocker =
new MockitoComponentMockingRule<>(DefaultContentParser.class);

@Rule
public ExpectedException thrown = ExpectedException.none();

private static final DocumentReference DOCUMENT_REFERENCE = new DocumentReference("wiki", "space", "page");

private static final String SOURCE = "wiki:space.page";

@Before
public void configure() throws Exception
{
Parser parser = mocker.registerMockComponent(Parser.class, Syntax.PLAIN_1_0.toIdString());
when(parser.parse(argThat(any(Reader.class)))).thenReturn(new XDOM(Collections.<Block>emptyList()));
@InjectMockComponents
private DefaultContentParser defaultContentParser;

@MockComponent
private EntityReferenceSerializer<String> serializer;

EntityReferenceSerializer<String> serializer = mocker.getInstance(EntityReferenceSerializer.TYPE_STRING);
when(serializer.serialize(DOCUMENT_REFERENCE)).thenReturn(SOURCE);
private Parser plain10parser;

@BeforeEach
void setUp(MockitoComponentManager componentManager) throws Exception
{
this.plain10parser = componentManager.registerMockComponent(Parser.class, PLAIN_1_0.toIdString());
when(this.plain10parser.parse(argThat(CoreMatchers.any(Reader.class)))).thenReturn(new XDOM(emptyList()));
when(this.serializer.serialize(DOCUMENT_REFERENCE)).thenReturn(SOURCE);
}

@Test
public void parseHasNoMetadataSource() throws Exception
void parseHasNoMetadataSource() throws Exception
{
XDOM xdom = mocker.getComponentUnderTest().parse("", Syntax.PLAIN_1_0);
XDOM xdom = this.defaultContentParser.parse("", PLAIN_1_0);

assertThat(xdom.getMetaData().getMetaData(MetaData.SOURCE), nullValue());
}

@Test
public void parseIsAddingMetadataSource() throws Exception
void parseIsAddingMetadataSource() throws Exception
{
XDOM xdom = mocker.getComponentUnderTest().parse("", Syntax.PLAIN_1_0, DOCUMENT_REFERENCE);
XDOM xdom = this.defaultContentParser.parse("", PLAIN_1_0, DOCUMENT_REFERENCE);

assertThat(xdom.getMetaData().getMetaData(MetaData.SOURCE), equalTo(SOURCE));
}

@Test
public void parseWhenNoParser() throws Exception
void parseWhenNoParser()
{
MissingParserException missingParserException = assertThrows(MissingParserException.class,
() -> this.defaultContentParser.parse("", XWIKI_2_1, DOCUMENT_REFERENCE));
assertEquals(ComponentLookupException.class, missingParserException.getCause().getClass());
assertEquals("Failed to find a parser for syntax [XWiki 2.1]", missingParserException.getMessage());
}

@Test
void parseWhenNoParserFail() throws Exception
{
when(this.plain10parser.parse(any())).thenThrow(StackOverflowError.class);

ParseException parseErrorException =
assertThrows(ParseException.class, () -> this.defaultContentParser.parse("content", PLAIN_1_0));

assertEquals(StackOverflowError.class, parseErrorException.getCause().getClass());
assertEquals("Failed to parse with syntax [plain/1.0].", parseErrorException.getMessage());
}

@Test
void parseWhenNoParserFailWithSource() throws Exception
{
thrown.expect(MissingParserException.class);
thrown.expectMessage("Failed to find a parser for syntax [XWiki 2.1]");
thrown.expectCause(any(ComponentLookupException.class));
mocker.getComponentUnderTest().parse("", Syntax.XWIKI_2_1, DOCUMENT_REFERENCE);
when(this.plain10parser.parse(any())).thenThrow(StackOverflowError.class);

ParseException parseErrorException =
assertThrows(ParseException.class, () -> this.defaultContentParser.parse("content", PLAIN_1_0, null));

assertEquals(StackOverflowError.class, parseErrorException.getCause().getClass());
assertEquals("Failed to parse with syntax [plain/1.0].", parseErrorException.getMessage());
}

@Test
public void parseWhenNullSource() throws Exception
void parseWhenNullSource() throws Exception
{
XDOM xdom = mocker.getComponentUnderTest().parse(null, Syntax.PLAIN_1_0);
XDOM xdom = this.defaultContentParser.parse(null, Syntax.PLAIN_1_0);
assertEquals(0, xdom.getChildren().size());
}
}

0 comments on commit e5b82cd

Please sign in to comment.