-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementation of loadAllElements with iterative fallback once max de…
…pth is reached
- Loading branch information
Showing
4 changed files
with
246 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,12 +15,17 @@ | |
|
||
package com.amazon.ionelement | ||
|
||
import com.amazon.ion.Decimal | ||
import com.amazon.ion.system.IonReaderBuilder | ||
import com.amazon.ionelement.api.* | ||
import com.amazon.ionelement.util.INCLUDE_LOCATION_META | ||
import com.amazon.ionelement.util.ION | ||
import com.amazon.ionelement.util.IonElementLoaderTestCase | ||
import com.amazon.ionelement.util.convertToString | ||
import java.math.BigInteger | ||
import org.junit.jupiter.api.Assertions.assertEquals | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.assertThrows | ||
import org.junit.jupiter.params.ParameterizedTest | ||
import org.junit.jupiter.params.provider.MethodSource | ||
|
||
|
@@ -45,6 +50,8 @@ class IonElementLoaderTests { | |
// Converting from IonValue to IonElement should result in an IonElement that is equivalent to the | ||
// parsed IonElement | ||
assertEquals(parsedIonValue.toIonElement(), parsedIonElement) | ||
|
||
assertEquals(tc.expectedElement, parsedIonElement) | ||
} | ||
|
||
companion object { | ||
|
@@ -57,15 +64,29 @@ class IonElementLoaderTests { | |
|
||
IonElementLoaderTestCase("1", ionInt(1)), | ||
|
||
IonElementLoaderTestCase("existence::42", ionInt(1).withAnnotations("existence")), | ||
IonElementLoaderTestCase("9223372036854775807", ionInt(Long.MAX_VALUE)), | ||
|
||
IonElementLoaderTestCase("\"some string\"", ionString("some string")), | ||
IonElementLoaderTestCase("9223372036854775808", ionInt(BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.ONE)), | ||
|
||
IonElementLoaderTestCase("existence::42", ionInt(42).withAnnotations("existence")), | ||
|
||
IonElementLoaderTestCase("0.", ionDecimal(Decimal.ZERO)), | ||
|
||
IonElementLoaderTestCase("1e0", ionFloat(1.0)), | ||
|
||
IonElementLoaderTestCase("2019-10-30T04:23:59Z", ionTimestamp("2019-10-30T04:23:59Z")), | ||
|
||
IonElementLoaderTestCase("\"some string\"", ionString("some string")), | ||
|
||
IonElementLoaderTestCase("'some symbol'", ionSymbol("some symbol")), | ||
|
||
IonElementLoaderTestCase("{{\"some clob\"}}", ionClob("some clob".encodeToByteArray())), | ||
|
||
IonElementLoaderTestCase("{{ }}", ionBlob(byteArrayOf())), | ||
|
||
IonElementLoaderTestCase("[1, 2, 3]", ionListOf(ionInt(1), ionInt(2), ionInt(3))), | ||
|
||
IonElementLoaderTestCase("(1 2 3)", ionListOf(ionInt(1), ionInt(2), ionInt(3))), | ||
IonElementLoaderTestCase("(1 2 3)", ionSexpOf(ionInt(1), ionInt(2), ionInt(3))), | ||
|
||
IonElementLoaderTestCase( | ||
"{ foo: 1, bar: 2, bat: 3 }", | ||
|
@@ -74,7 +95,79 @@ class IonElementLoaderTests { | |
"bar" to ionInt(2), | ||
"bat" to ionInt(3) | ||
) | ||
) | ||
), | ||
|
||
// Nested container cases | ||
IonElementLoaderTestCase("((null.list))", ionSexpOf(ionSexpOf(ionNull(ElementType.LIST)))), | ||
IonElementLoaderTestCase("(1 (2 3))", ionSexpOf(ionInt(1), ionSexpOf(ionInt(2), ionInt(3)))), | ||
IonElementLoaderTestCase("{foo:[1]}", ionStructOf("foo" to ionListOf(ionInt(1)))), | ||
IonElementLoaderTestCase("[{foo:1}]", ionListOf(ionStructOf("foo" to ionInt(1)))), | ||
IonElementLoaderTestCase("{foo:{bar:1}}", ionStructOf("foo" to ionStructOf("bar" to ionInt(1)))), | ||
IonElementLoaderTestCase("{foo:[{}]}", ionStructOf("foo" to ionListOf(ionStructOf(emptyList())))), | ||
IonElementLoaderTestCase("[{}]", ionListOf(ionStructOf(emptyList()))), | ||
IonElementLoaderTestCase("[{}, {}]", ionListOf(ionStructOf(emptyList()), ionStructOf(emptyList()))), | ||
IonElementLoaderTestCase("[{foo:1, bar: 2}]", ionListOf(ionStructOf("foo" to ionInt(1), "bar" to ionInt(2)))), | ||
IonElementLoaderTestCase( | ||
"{foo:[{bar:({})}]}", | ||
ionStructOf("foo" to ionListOf(ionStructOf("bar" to ionSexpOf(ionStructOf(emptyList()))))) | ||
), | ||
) | ||
} | ||
|
||
@Test | ||
fun `regardless of depth, no StackOverflowError is thrown`() { | ||
// Throws StackOverflowError in [email protected] and prior versions when there's ~4k nested containers | ||
// Run for every container type to ensure that they all correctly fall back to the iterative impl. | ||
|
||
val listData = "[".repeat(999999) + "]".repeat(999999) | ||
loadAllElements(listData) | ||
|
||
val sexpData = "(".repeat(999999) + ")".repeat(999999) | ||
loadAllElements(sexpData) | ||
|
||
val structData = "{a:".repeat(999999) + "b" + "}".repeat(999999) | ||
loadAllElements(structData) | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("parametersForDemoTest") | ||
fun `deeply nested values should be loaded correctly`(tc: IonElementLoaderTestCase) { | ||
// Wrap everything in many layers of Ion lists so that we can be sure to trigger the iterative fallback. | ||
val nestingLevels = 500 | ||
val textIon = "[".repeat(nestingLevels) + tc.textIon + "]".repeat(nestingLevels) | ||
var expectedElement = tc.expectedElement | ||
repeat(nestingLevels) { expectedElement = ionListOf(expectedElement) } | ||
|
||
val parsedIonValue = ION.singleValue(textIon) | ||
val parsedIonElement = loadSingleElement(textIon, INCLUDE_LOCATION_META) | ||
|
||
// Text generated from both should match | ||
assertEquals(convertToString(parsedIonValue), parsedIonElement.toString()) | ||
|
||
// Converting from IonElement to IonValue results in an IonValue that is equivalent to the parsed IonValue | ||
assertEquals(parsedIonElement.toIonValue(ION), parsedIonValue) | ||
|
||
// Converting from IonValue to IonElement should result in an IonElement that is equivalent to the | ||
// parsed IonElement | ||
assertEquals(parsedIonValue.toIonElement(), parsedIonElement) | ||
|
||
assertEquals(expectedElement, parsedIonElement) | ||
} | ||
|
||
@Test | ||
fun `loadCurrentElement throws exception when not positioned on a value`() { | ||
val reader = IonReaderBuilder.standard().build("foo") | ||
// We do not advance to the first value in the reader. | ||
assertThrows<IllegalArgumentException> { loadCurrentElement(reader) } | ||
} | ||
|
||
@Test | ||
fun `loadSingleElement throws exception when no values in reader`() { | ||
assertThrows<IllegalArgumentException> { loadSingleElement("") } | ||
} | ||
|
||
@Test | ||
fun `loadSingleElement throws exception when more than one values in reader`() { | ||
assertThrows<IllegalArgumentException> { loadSingleElement("a b") } | ||
} | ||
} |