LocationOfError1180Test.java
package com.fasterxml.jackson.core.tofix;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.async.ByteArrayFeeder;
import com.fasterxml.jackson.core.exc.StreamReadException;
import com.fasterxml.jackson.core.testutil.failure.ExpectedPassingTestCasePredicate;
import com.fasterxml.jackson.core.testutil.failure.JacksonTestFailureExpected;
import static com.fasterxml.jackson.core.JUnit5TestBase.a2q;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests that the {@link JsonLocation} attached to a thrown {@link StreamReadException}
* due to invalid JSON points to the correct character.
*/
class LocationOfError1180Test
{
static final JsonFactory JSON_F = new JsonFactory();
/** Represents the different parser backends */
public enum ParserVariant
{
BYTE_ARRAY(
(String input) -> JSON_F.createParser(input.getBytes(StandardCharsets.UTF_8)),
true, // supports byte offsets in reported location
false, // supports character offsets in reported location
true // supports column numbers in reported location
),
CHAR_ARRAY(
(String input) -> JSON_F.createParser(input.toCharArray()),
false,
true,
true
),
ASYNC(
(String input) -> {
JsonParser parser = JSON_F.createNonBlockingByteArrayParser();
ByteArrayFeeder feeder = (ByteArrayFeeder) parser.getNonBlockingInputFeeder();
assertTrue(feeder.needMoreInput());
byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
feeder.feedInput(inputBytes, 0, inputBytes.length);
feeder.endOfInput();
return parser;
},
true,
false,
true
)
;
ParserVariant(
ParserGenerator parserGenerator,
boolean supportsByteOffset,
boolean supportsCharOffset,
boolean supportsColumnNr
)
{
_parserGenerator = parserGenerator;
this.supportsByteOffset = supportsByteOffset;
this.supportsCharOffset = supportsCharOffset;
this.supportsColumnNr = supportsColumnNr;
}
public JsonParser createParser(String input) throws Exception
{
return _parserGenerator.createParser(input);
}
private final ParserGenerator _parserGenerator;
public final boolean supportsByteOffset;
public final boolean supportsCharOffset;
public final boolean supportsColumnNr;
}
/** Collection of differing invalid JSON input cases to test */
private static final List<InvalidJson> INVALID_JSON_CASES = Arrays.asList(
new InvalidJson(
"Incorrect case for false literal",
"{\"isThisValidJson\": FALSE}",
24,
24,
1,
25
),
new InvalidJson(
"Incorrect case for true literal",
"{\"shouldYouAvoidWritingJsonLikeThis\": TRUE}",
41,
41,
1,
42
),
new InvalidJson(
"Incorrect case for null literal",
"{\"licensePlate\": NULL}",
20,
20,
1,
21
),
// NOTE: to be removed, eventually
new InvalidJson(
"Invalid JSON with raw unicode character",
// javac will parse the 3-byte unicode control sequence, it will be passed to the parser as a raw unicode character
a2q("{'validJson':'\u274c','right', 'here'}"),
26,
24,
1,
25
)
);
@JacksonTestFailureExpected(expectedPassingTestCasePredicate = ShouldPredicate1180Test.class)
@ParameterizedTest
@MethodSource("_generateTestData")
void parserBackendWithInvalidJson(ParserVariant variant, InvalidJson invalidJson)
throws Exception
{
try (JsonParser parser = variant.createParser(invalidJson.input))
{
StreamReadException e = assertThrows(
StreamReadException.class,
() -> {
// Blindly advance the parser through the end of input
while (parser.nextToken() != null) {}
}
);
JsonLocation location = e.getLocation();
assertEquals(invalidJson.lineNr, location.getLineNr());
final String msg = e.getOriginalMessage();
if (variant.supportsByteOffset)
{
assertEquals(invalidJson.byteOffset, location.getByteOffset(), "Incorrect byte offset (for '"+msg+"')");
}
if (variant.supportsCharOffset)
{
assertEquals(invalidJson.charOffset, location.getCharOffset(), "Incorrect char offset (for '"+msg+"')");
}
if (variant.supportsColumnNr)
{
assertEquals(invalidJson.columnNr, location.getColumnNr(), "Incorrect column (for '"+msg+"')");
}
}
}
private static Stream<Arguments> _generateTestData()
{
return Arrays.stream(ParserVariant.values())
.flatMap(parserVariant -> INVALID_JSON_CASES.stream().map(
invalidJson -> Arguments.of(parserVariant, invalidJson)
));
}
@FunctionalInterface
public interface ParserGenerator
{
JsonParser createParser(String input) throws Exception;
}
static class InvalidJson
{
InvalidJson(String name, String input, int byteOffset, int charOffset,
int lineNr, int columnNr)
{
_name = name;
this.input = input;
this.byteOffset = byteOffset;
this.charOffset = charOffset;
this.lineNr = lineNr;
this.columnNr = columnNr;
}
@Override
public String toString()
{
return _name;
}
protected final String _name;
public final String input;
public final int byteOffset;
public final int charOffset;
public final int lineNr;
public final int columnNr;
}
public static class ShouldPredicate1180Test
implements ExpectedPassingTestCasePredicate
{
@Override
public boolean shouldPass(List<Object> arguments) {
if (arguments.get(0) == ParserVariant.CHAR_ARRAY
&& Objects.equals(((InvalidJson) (arguments.get(1)))._name, "Invalid JSON with raw unicode character")
) {
return true;
}
return false;
}
}
}