Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<IllegalArgumentException> {
"a".decodeBuffered(
decoder,
throwOnOverflow = true,
maxBufSize = testSize,
action = { _, _, _ -> error("Should not make it here") },
)
}

assertFailsWith<IllegalArgumentException> {
"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<EncodingSizeException> {
"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())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<EncodingSizeException> { config.decodeOutMaxSize(-1L) }
assertFailsWith<EncodingSizeException> { config.encodeOutMaxSize(-1L) }
}

@Test
Expand Down Expand Up @@ -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<EncodingSizeException> { config.encodeOutMaxSize(Long.MAX_VALUE - 10L) }
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,44 +34,58 @@ class EncoderDecoderFeedUnitTest {
}

@Test
fun givenDecoderFeed_whenClosed_thenConsumeAndDoFinalThrowException() {
fun givenDecoderFeed_whenClosed_thenThrowsClosedException() {
val feed = TestEncoderDecoder(TestConfig()).newDecoderFeed {}

feed.close()

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()

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"))
}
}

Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -137,7 +179,7 @@ class EncoderDecoderFeedUnitTest {
try {
feed.consume(char)
fail()
} catch (_: EncodingException) {
} catch (_: MalformedEncodingException) {
assertTrue(feed.isClosed())
}
}
Expand Down Expand Up @@ -190,7 +232,7 @@ class EncoderDecoderFeedUnitTest {
try {
feed.consume('g')
fail()
} catch (_: EncodingException) {
} catch (_: MalformedEncodingException) {
assertTrue(feed.isClosed())
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestConfig>.Feed) -> Unit)? = null,
private val decoderDoFinal: ((Decoder<TestConfig>.Feed) -> Unit)? = null,
): EncoderDecoder<TestConfig>(config) {
Expand All @@ -33,15 +35,19 @@ class TestEncoderDecoder(

inner class DecoderFeed(out: Decoder.OutFeed): Decoder<TestConfig>.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)
}
}

inner class EncoderFeed(out: Encoder.OutFeed): Encoder<TestConfig>.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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,4 @@ class DecoderInputUnitTest {
@Suppress("DEPRECATION_ERROR")
config.decodeOutMaxSizeOrFail(DecoderInput(validInput))
}


}
Loading