diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/DecodeBufferedUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/DecodeBufferedUnitTest.kt new file mode 100644 index 0000000..4f11936 --- /dev/null +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/DecodeBufferedUnitTest.kt @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2025 Matthew Nelson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +package io.matthewnelson.encoding.core + +import io.matthewnelson.encoding.core.Decoder.Companion.decodeBuffered +import io.matthewnelson.encoding.core.EncoderDecoder.Companion.DEFAULT_BUFFER_SIZE +import io.matthewnelson.encoding.core.helpers.TestConfig +import io.matthewnelson.encoding.core.helpers.TestEncoderDecoder +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.fail + +class DecodeBufferedUnitTest { + + @Test + fun givenBufferSize_whenLessThanOrEqualToConfigMaxDecodeEmit_thenThrowsIllegalArgumentException() { + val decoder = TestEncoderDecoder(TestConfig( + maxDecodeEmit = 255, + decodeInputReturn = { error("Should not make it here") } + )) + + intArrayOf(254, 255).forEach { testSize -> + assertFailsWith { + "a".decodeBuffered( + decoder, + throwOnOverflow = true, + maxBufSize = testSize, + action = { _, _, _ -> error("Should not make it here") }, + ) + } + + assertFailsWith { + "a".decodeBuffered( + decoder, + throwOnOverflow = true, + buf = ByteArray(testSize), + action = { _, _, _ -> error("Should not make it here") }, + ) + } + } + } + + @Test + fun givenDecodeInputNonSizeException_whenConfigThrows_thenIsNeverIgnored() { + val expected = "Config implementation has some sort of checksum at end of encoding and it did not pass" + val decoder = TestEncoderDecoder(TestConfig( + decodeInputReturn = { throw EncodingException(expected) } + )) + try { + "a".decodeBuffered(decoder, throwOnOverflow = true) { _, _, _ -> error("Should not make it here") } + fail() + } catch (e: EncodingException) { + assertEquals(expected, e.message) + } + try { + "a".decodeBuffered(decoder, throwOnOverflow = false) { _, _, _ -> error("Should not make it here") } + fail() + } catch (e: EncodingException) { + assertEquals(expected, e.message) + } + } + + @Test + fun givenDecodeInputSizeException_whenThrowOnOverflowTrue_thenEncodingSizeExceptionIsRethrown() { + val decoder = TestEncoderDecoder(TestConfig()) + assertFailsWith { + "a".decodeBuffered( + decoder, + throwOnOverflow = true, + action = { _, _, _ -> error("Should not make it here") } + ) + } + } + + @Test + fun givenDecodeInputSizeException_whenThrowOnOverflowFalse_thenEncodingSizeExceptionIsIgnored() { + var invocationInput = 0 + val decoder = TestEncoderDecoder(TestConfig( + decodeInputReturn = { invocationInput++; -1 }, + )) + + var invocationAction = 0 + object : CharSequence { + override val length: Int = DEFAULT_BUFFER_SIZE + 50 + override fun get(index: Int): Char = 'a' + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { error("unused") } + }.decodeBuffered( + decoder, + throwOnOverflow = false, + action = { buf, _, _ -> + invocationAction++ + assertEquals(DEFAULT_BUFFER_SIZE, buf.size) + } + ) + assertEquals(1, invocationInput) + // Confirms that it went into stream decoding b/c was flushed 2 times + assertEquals(2, invocationAction) + } + + @Test + fun givenDecodeInputSize_whenLessThanBufferSize_thenOneShotDecodesWithSmallerSize() { + val expectedSize = 2 + val expectedInputSize = DEFAULT_BUFFER_SIZE + 50 + var invocationConsume = 0 + val decoder = TestEncoderDecoder( + config = TestConfig( + decodeInputReturn = { expectedSize }, + ), + decoderConsume = { invocationConsume++ }, + decoderDoFinal = { (it as TestEncoderDecoder.DecoderFeed).getOut().output(1) }, + ) + + var invocationAction = 0 + val result = object : CharSequence { + override val length: Int = expectedInputSize + override fun get(index: Int): Char = 'a' + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = error("unused") + }.decodeBuffered( + decoder, + throwOnOverflow = true, + maxBufSize = expectedSize * 10, + action = { buf, _, len -> + invocationAction++ + assertEquals(expectedSize, buf.size) + assertEquals(1, len) + } + ) + assertEquals(1, invocationAction) + assertEquals(invocationConsume, expectedInputSize) + assertEquals(1L, result) + } + + @Test + fun givenConfigMaxDecodeEmit_whenDecodingBuffered_thenFlushesIfLastIndexWouldBeExceeded() { + val decoder = TestEncoderDecoder(TestConfig(maxDecodeEmit = 50)) + + var invocationAction = 0 + var invocationActionAssertion = 0 + var actualLen = 0 + val result = object : CharSequence { + override val length: Int = DEFAULT_BUFFER_SIZE + override fun get(index: Int): Char = 'a' + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = error("unused") + }.decodeBuffered( + decoder, + throwOnOverflow = false, + maxBufSize = DEFAULT_BUFFER_SIZE, + action = { buf, _, len -> + invocationAction++ + actualLen += len + assertEquals(DEFAULT_BUFFER_SIZE, buf.size) + if (invocationAction == 1) { + invocationActionAssertion++ + assertEquals(DEFAULT_BUFFER_SIZE - decoder.config.maxDecodeEmit + 1, len, "invocationAction[$invocationAction]") + } + if (invocationAction == 2) { + invocationActionAssertion++ + assertEquals(decoder.config.maxDecodeEmit, len) + } + } + ) + assertEquals(2, invocationAction) + assertEquals(2, invocationActionAssertion) + // TestEncoderDecoder.doFinal default value will output one 1 byte + assertEquals((DEFAULT_BUFFER_SIZE + 1).toLong(), result) + assertEquals(result, actualLen.toLong()) + } +} diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderConfigUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderConfigUnitTest.kt index 783a66b..6a4941e 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderConfigUnitTest.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderConfigUnitTest.kt @@ -28,29 +28,18 @@ class EncoderDecoderConfigUnitTest { fun givenConfig_whenNegativeValuesSent_thenThrowsEncodingSizeExceptionBeforePassingToProtected() { val config = TestConfig( encodeReturn = { - fail("Should not make it here") + error("Should not make it here") }, decodeInputReturn = { - fail("Should not make it here") + error("Should not make it here") }, decodeReturn = { - fail("Should not make it here") + error("Should not make it here") } ) - try { - config.decodeOutMaxSize(-1L) - fail() - } catch (_: EncodingSizeException) { - // pass - } - - try { - config.encodeOutMaxSize(-1L) - fail() - } catch (_: EncodingSizeException) { - // pass - } + assertFailsWith { config.decodeOutMaxSize(-1L) } + assertFailsWith { config.encodeOutMaxSize(-1L) } } @Test @@ -160,13 +149,7 @@ class EncoderDecoderConfigUnitTest { @Test fun givenLineBreakInterval_whenSizeIncreaseWouldExceedMaxValue_thenThrowsEncodingSizeException() { val config = TestConfig(isLenient = true, lineBreakInterval = 10, encodeReturn = { it }) - - try { - config.encodeOutMaxSize(Long.MAX_VALUE - 10L) - fail() - } catch (_: EncodingSizeException) { - // pass - } + assertFailsWith { config.encodeOutMaxSize(Long.MAX_VALUE - 10L) } } @Test diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderFeedUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderFeedUnitTest.kt index c4e9607..1a09072 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderFeedUnitTest.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderFeedUnitTest.kt @@ -34,7 +34,7 @@ class EncoderDecoderFeedUnitTest { } @Test - fun givenDecoderFeed_whenClosed_thenConsumeAndDoFinalThrowException() { + fun givenDecoderFeed_whenClosed_thenThrowsClosedException() { val feed = TestEncoderDecoder(TestConfig()).newDecoderFeed {} feed.close() @@ -42,20 +42,27 @@ class EncoderDecoderFeedUnitTest { try { feed.consume('d') fail() - } catch (_: EncodingException) { - // pass + } catch (e: EncodingException) { + assertTrue(e.message.contains("is closed")) + } + + try { + feed.flush() + fail() + } catch (e: EncodingException) { + assertTrue(e.message.contains("is closed")) } try { feed.doFinal() fail() - } catch (_: EncodingException) { - // pass + } catch (e: EncodingException) { + assertTrue(e.message.contains("is closed")) } } @Test - fun givenEncoderFeed_whenClosed_thenConsumeAndDoFinalThrowException() { + fun givenEncoderFeed_whenClosed_thenThrowsClosedException() { val feed = TestEncoderDecoder(TestConfig()).newEncoderFeed {} feed.close() @@ -63,15 +70,22 @@ class EncoderDecoderFeedUnitTest { try { feed.consume(5) fail() - } catch (_: EncodingException) { - // pass + } catch (e: EncodingException) { + assertTrue(e.message.contains("is closed")) + } + + try { + feed.flush() + fail() + } catch (e: EncodingException) { + assertTrue(e.message.contains("is closed")) } try { feed.doFinal() fail() - } catch (_: EncodingException) { - // pass + } catch (e: EncodingException) { + assertTrue(e.message.contains("is closed")) } } @@ -89,6 +103,20 @@ class EncoderDecoderFeedUnitTest { } } + @Test + fun givenDecoderFeed_whenFlushThrowsException_thenFeedClosesAutomaticallyBeforeThrowing() { + val feed = TestEncoderDecoder(TestConfig()).newDecoderFeed { + throw IllegalStateException("") + } + + try { + feed.flush() + fail() + } catch (_: IllegalStateException) { + assertTrue(feed.isClosed()) + } + } + @Test fun givenEncoderFeed_whenConsumeThrowsException_thenFeedClosesAutomaticallyBeforeThrowing() { val feed = TestEncoderDecoder(TestConfig()).newEncoderFeed { @@ -103,6 +131,20 @@ class EncoderDecoderFeedUnitTest { } } + @Test + fun givenEncoderFeed_whenFlushThrowsException_thenFeedClosesAutomaticallyBeforeThrowing() { + val feed = TestEncoderDecoder(TestConfig()).newEncoderFeed { + throw IllegalStateException("") + } + + try { + feed.flush() + fail() + } catch (_: IllegalStateException) { + assertTrue(feed.isClosed()) + } + } + @Test fun givenDecoderFeed_whenIsLenientTrue_thenSpacesAndNewLinesAreNotSubmitted() { var out: Byte = 0 @@ -137,7 +179,7 @@ class EncoderDecoderFeedUnitTest { try { feed.consume(char) fail() - } catch (_: EncodingException) { + } catch (_: MalformedEncodingException) { assertTrue(feed.isClosed()) } } @@ -190,7 +232,7 @@ class EncoderDecoderFeedUnitTest { try { feed.consume('g') fail() - } catch (_: EncodingException) { + } catch (_: MalformedEncodingException) { assertTrue(feed.isClosed()) } } diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/helpers/TestEncoderDecoder.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/helpers/TestEncoderDecoder.kt index bddafc8..6936adc 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/helpers/TestEncoderDecoder.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/helpers/TestEncoderDecoder.kt @@ -22,6 +22,8 @@ import io.matthewnelson.encoding.core.Decoder class TestEncoderDecoder( config: TestConfig, + private val encoderConsume: Encoder.OutFeed.(input: Byte) -> Unit = { output(Char.MAX_VALUE) }, + private val decoderConsume: Decoder.OutFeed.(input: Char) -> Unit = { output(Byte.MAX_VALUE) }, private val encoderDoFinal: ((Encoder.Feed) -> Unit)? = null, private val decoderDoFinal: ((Decoder.Feed) -> Unit)? = null, ): EncoderDecoder(config) { @@ -33,7 +35,9 @@ class TestEncoderDecoder( inner class DecoderFeed(out: Decoder.OutFeed): Decoder.Feed(_out = out) { fun getOut(): Decoder.OutFeed = _out - override fun consumeProtected(input: Char) { _out.output(Byte.MAX_VALUE) } + override fun consumeProtected(input: Char) { + decoderConsume(_out, input) + } override fun doFinalProtected() { decoderDoFinal?.invoke(this) ?: _out.output(Byte.MIN_VALUE) } @@ -41,7 +45,9 @@ class TestEncoderDecoder( inner class EncoderFeed(out: Encoder.OutFeed): Encoder.Feed(_out = out) { fun getOut(): Encoder.OutFeed = _out - override fun consumeProtected(input: Byte) { _out.output(Char.MAX_VALUE) } + override fun consumeProtected(input: Byte) { + encoderConsume(_out, input) + } override fun doFinalProtected() { encoderDoFinal?.invoke(this) ?: _out.output(Char.MIN_VALUE) } diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/DecoderInputUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/DecoderInputUnitTest.kt index 7fa1e10..09b2c09 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/DecoderInputUnitTest.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/DecoderInputUnitTest.kt @@ -136,6 +136,4 @@ class DecoderInputUnitTest { @Suppress("DEPRECATION_ERROR") config.decodeOutMaxSizeOrFail(DecoderInput(validInput)) } - - } diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/FeedBufferUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/FeedBufferUnitTest.kt index 9400e0c..cd377ed 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/FeedBufferUnitTest.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/FeedBufferUnitTest.kt @@ -17,11 +17,12 @@ package io.matthewnelson.encoding.core.util import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.fail class FeedBufferUnitTest { - private inner class TestBuffer( + private class TestBuffer( blockSize: Int, flush: Flush = Flush { _ -> }, finalize: Finalize = Finalize { _, _ -> }, @@ -31,12 +32,7 @@ class FeedBufferUnitTest { @Test fun givenFeedBuffer_whenBlockSizeLessThanOrEqualToZero_thenThrowsException() { - try { - TestBuffer(blockSize = 0) - fail() - } catch (_: IllegalArgumentException) { - // pass - } + assertFailsWith { TestBuffer(0) } } @Test @@ -51,6 +47,7 @@ class FeedBufferUnitTest { } }) + @Suppress("UNUSED") for (j in 0 until i) { buffer.update(i) } diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/TextUtilUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/TextUtilUnitTest.kt index f8658f4..7f48761 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/TextUtilUnitTest.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/TextUtilUnitTest.kt @@ -19,6 +19,7 @@ import io.matthewnelson.encoding.core.internal.commonWipe import kotlin.test.Test import kotlin.test.assertEquals +// See also nonJsTest TextUtilNonJsUnitTest @Suppress("DEPRECATION") class TextUtilUnitTest {