From a6e3400b49be2f3c45bd0087e2fdb1f11b12dcea Mon Sep 17 00:00:00 2001 From: Yaroslav Ovdiienko Date: Sun, 12 Jul 2026 13:00:42 +0200 Subject: [PATCH 1/4] build: add testBalloon dependencies --- build.gradle.kts | 1 + datasize-core/build.gradle.kts | 2 ++ gradle/libs.versions.toml | 3 +++ 3 files changed, 6 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 0cb34ae..485bd84 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,4 +5,5 @@ plugins { alias(libs.plugins.bcv) apply false alias(libs.plugins.dokka) apply false alias(libs.plugins.mcentral.publish) apply false + alias(libs.plugins.test.balloon) apply false } \ No newline at end of file diff --git a/datasize-core/build.gradle.kts b/datasize-core/build.gradle.kts index 7d0bd98..570a0fd 100644 --- a/datasize-core/build.gradle.kts +++ b/datasize-core/build.gradle.kts @@ -7,6 +7,7 @@ plugins { alias(libs.plugins.dokka) alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.mcentral.publish) + alias(libs.plugins.test.balloon) } group = "io.github.ardiien.datasize" @@ -33,6 +34,7 @@ kotlin { } commonTest.dependencies { implementation(libs.kotlin.test) + implementation(libs.test.balloon) } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 612262d..9bc4c9e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,10 +5,12 @@ kotlin = "2.4.0" kotlinCollections = "0.5.0" shadow = "9.4.1" publish = "0.36.0" +testBalloon = "1.0.1-K2.4.0" [libraries] kotlinx-collections = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinCollections" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +test-balloon = { module = "de.infix.testBalloon:testBalloon-framework-core", version.ref = "testBalloon" } [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } @@ -17,3 +19,4 @@ shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } bcv = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "bcv" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } mcentral-publish = { id = "com.vanniktech.maven.publish", version.ref = "publish" } +test-balloon = {id = "de.infix.testBalloon", version.ref = "testBalloon"} From 2783ac200b0adc9d73743ed740bad090a1dad8ac Mon Sep 17 00:00:00 2001 From: Yaroslav Ovdiienko Date: Sun, 12 Jul 2026 13:01:23 +0200 Subject: [PATCH 2/4] fix: increate precision in `isGroupingUsed` for `extendFormat` --- .../ardiien/datasize/formatter/SimpleDataSizeFormatter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datasize-core/src/commonMain/kotlin/io/github/ardiien/datasize/formatter/SimpleDataSizeFormatter.kt b/datasize-core/src/commonMain/kotlin/io/github/ardiien/datasize/formatter/SimpleDataSizeFormatter.kt index 5743424..bef8982 100644 --- a/datasize-core/src/commonMain/kotlin/io/github/ardiien/datasize/formatter/SimpleDataSizeFormatter.kt +++ b/datasize-core/src/commonMain/kotlin/io/github/ardiien/datasize/formatter/SimpleDataSizeFormatter.kt @@ -97,7 +97,7 @@ public class SimpleDataSizeFormatter( internal fun extendFormat(format: DecimalFormat, number: Double, fractionDigits: Int): DecimalFormat = format.apply { maximumFractionDigits = fractionDigits.coerceAtMost(2) - isGroupingUsed = number > 9999.999999999 + isGroupingUsed = number > 9999.99999999999 } /** From 2ffc050743e89826896e939c4e18c162294f0c03 Mon Sep 17 00:00:00 2001 From: Yaroslav Ovdiienko Date: Sun, 12 Jul 2026 13:04:22 +0200 Subject: [PATCH 3/4] fix: address concurrency issue in `SimpleDataSizeFormatter` in `format` method On each concurrent call the `DecimalFormat` object is modified. A few parameters like `maximumFractionDigits` and `isGroupingUsed` applied on each `format`. This means the original object now mutated. For now, the solution is to `clone` on each `format` the formatter. --- .../ardiien/datasize/formatter/SimpleDataSizeFormatter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datasize-core/src/commonMain/kotlin/io/github/ardiien/datasize/formatter/SimpleDataSizeFormatter.kt b/datasize-core/src/commonMain/kotlin/io/github/ardiien/datasize/formatter/SimpleDataSizeFormatter.kt index bef8982..646cdf3 100644 --- a/datasize-core/src/commonMain/kotlin/io/github/ardiien/datasize/formatter/SimpleDataSizeFormatter.kt +++ b/datasize-core/src/commonMain/kotlin/io/github/ardiien/datasize/formatter/SimpleDataSizeFormatter.kt @@ -48,7 +48,7 @@ public class SimpleDataSizeFormatter( require(fractionDigits >= 0) { "fractionDigits must not be negative, but was $fractionDigits" } val number = value.toDouble(unit) - val actualFormatter = extendFormat(format, number, fractionDigits) + val actualFormatter = extendFormat(format.clone().castTo(), number, fractionDigits) return "${actualFormatter.format(number)} ${localizer.abbreviation(unit)}" } From d627e9d2e700d4608b59e7e0f3fb4bc465682c28 Mon Sep 17 00:00:00 2001 From: Yaroslav Ovdiienko Date: Sun, 12 Jul 2026 13:04:43 +0200 Subject: [PATCH 4/4] test: migrate all tests to use TestBalloon --- .../github/ardiien/datasize/DataSizeTest.kt | 787 +++++++++++------- .../formatter/SimpleDataSizeFormatterTest.kt | 85 +- .../SimpleDataSizeUnitLocalizerTest.kt | 94 +-- 3 files changed, 578 insertions(+), 388 deletions(-) diff --git a/datasize-core/src/commonTest/kotlin/io/github/ardiien/datasize/DataSizeTest.kt b/datasize-core/src/commonTest/kotlin/io/github/ardiien/datasize/DataSizeTest.kt index 643ccec..9d685ae 100644 --- a/datasize-core/src/commonTest/kotlin/io/github/ardiien/datasize/DataSizeTest.kt +++ b/datasize-core/src/commonTest/kotlin/io/github/ardiien/datasize/DataSizeTest.kt @@ -5,389 +5,590 @@ */ package io.github.ardiien.datasize +import de.infix.testBalloon.framework.core.TestConfig +import de.infix.testBalloon.framework.core.invocation +import de.infix.testBalloon.framework.core.testSuite import io.github.ardiien.datasize.DataSize.Companion.MaxValue import io.github.ardiien.datasize.DataSize.Companion.bytes import io.github.ardiien.datasize.DataSize.Companion.gibibytes +import io.github.ardiien.datasize.DataSize.Companion.gigabytes import io.github.ardiien.datasize.DataSize.Companion.kibibytes import io.github.ardiien.datasize.DataSize.Companion.kilobytes import io.github.ardiien.datasize.DataSize.Companion.mebibytes import io.github.ardiien.datasize.DataSize.Companion.megabytes import io.github.ardiien.datasize.DataSize.Companion.tebibytes +import io.github.ardiien.datasize.DataSize.Companion.terabytes import io.github.ardiien.datasize.DataSize.Companion.toDataSize -import org.junit.Assert.assertThrows -import kotlin.test.Ignore -import kotlin.test.Test +import org.junit.jupiter.api.Assertions.assertThrows import kotlin.test.assertEquals -class DataSizeTest { +val DataSizeTestSuite by testSuite { + val concurrentTestConfig = TestConfig.invocation(TestConfig.Invocation.Concurrent) - @Test - fun `negative value throws on DataSizeUnit init`() { - assertThrows(IllegalArgumentException::class.java) { - (-3).kilobytes + testSuite(name = "SI Unit") { + test("throws on negative value") { + assertThrows(IllegalArgumentException::class.java) { + (-3).kilobytes + } + assertThrows(IllegalArgumentException::class.java) { + (-3).toDataSize(SiUnit.Kilobyte) + } } - assertThrows(IllegalArgumentException::class.java) { - (-3).kibibytes + + test("zero bytes return zero for all units") { + val subject = DataSize.Zero + + val kilobytes = subject.inKilobytes + val megabytes = subject.inMegabytes + val gigabytes = subject.inGigabytes + val terabytes = subject.inTerabytes + val petabytes = subject.inPetabytes + + assertEquals(0.0, kilobytes, 0.01) + assertEquals(0.0, megabytes, 0.01) + assertEquals(0.0, gigabytes, 0.01) + assertEquals(0.0, terabytes, 0.01) + assertEquals(0.0, petabytes, 0.01) } - } - @Test - fun `negative value throws on toDataSize`() { - assertThrows(IllegalArgumentException::class.java) { - (-3).toDataSize(SiUnit.Kilobyte) + test("ByteArray converts to DataSize correctly", testConfig = concurrentTestConfig) { + val expected = "50 MB" + val result = ByteArray(50 * 1000 * 1000).bytes.toString(SiUnit.Megabyte) + + assertEquals(expected, result) } - assertThrows(IllegalArgumentException::class.java) { - (-3).toDataSize(IecUnit.Kibibyte) + + test("toDataSize megabytes converts correctly") { + val expectedBytes = 117500000L + val expectedMegaBytes = 117.5 + val subject = expectedMegaBytes.toDataSize(SiUnit.Megabyte) + + val resultBytes = subject.inBytes + assertEquals(expectedBytes, resultBytes) + + val resultMegabytes = subject.inMegabytes + assertEquals(expectedMegaBytes, resultMegabytes, 0.01) } - } - @Test - fun `zero DataSize returns zero for all units`() { - val subject = DataSize.Zero - - val bytes = subject.inBytes - val kilobytes = subject.inKilobytes - val kibibytes = subject.inKibibytes - val megabytes = subject.inMegabytes - val mebibytes = subject.inMebibytes - val gigabytes = subject.inGigabytes - val gibibytes = subject.inGibibytes - val terabytes = subject.inTerabytes - val tebibytes = subject.inTebibytes - val petabytes = subject.inPetabytes - val pebibytes = subject.inPebibytes - - assertEquals(0, bytes) - assertEquals(0.0, kilobytes, 0.01) - assertEquals(0.0, kibibytes, 0.01) - assertEquals(0.0, megabytes, 0.01) - assertEquals(0.0, mebibytes, 0.01) - assertEquals(0.0, gigabytes, 0.01) - assertEquals(0.0, gibibytes, 0.01) - assertEquals(0.0, terabytes, 0.01) - assertEquals(0.0, tebibytes, 0.01) - assertEquals(0.0, petabytes, 0.01) - assertEquals(0.0, pebibytes, 0.01) - } + test("bytes toDouble kilobytes converts correctly") { + val expectedBytes = 117500000L + val expectedKilobytes = 117500.0 // 117.5 Mb - @Test - fun `toDataSize bytes keeps value`() { - val expectedBytes = 104857600L // 100 Mb - val subject = expectedBytes.toDataSize(ByteUnit.Byte) + val subject = expectedBytes.bytes + val result = subject.toDouble(SiUnit.Kilobyte) - val result = subject.inBytes - assertEquals(expectedBytes, result) - } + assertEquals(expectedKilobytes, result, 0.01) + } - @Test - fun `toDataSize mebibytes converts correctly`() { - val expectedBytes = 123207680L - val expectedMegaBytes = 117.5 - val subject = expectedMegaBytes.toDataSize(IecUnit.Mebibyte) + test("kilobytes toLong bytes converts correctly") { + val expectedBytes = 117500000000L + val expectedKilobytes = 117500000L - val resultBytes = subject.inBytes - assertEquals(expectedBytes, resultBytes) + val subject = expectedKilobytes.kilobytes + val result = subject.toLong() - val resultMegabytes = subject.inMebibytes - assertEquals(expectedMegaBytes, resultMegabytes, 0.01) - } + assertEquals(expectedBytes, result) + } - @Test - fun `bytes toDouble kibibytes converts correctly`() { - val expectedBytes = 123207680L - val expectedKilobytes = 120320.0 // 117.5 Mb + test("plus adds kilobytes correctly") { + val expectedBytes = 4000L + val subject = 2.kilobytes + 2.kilobytes - val subject = expectedBytes.bytes - val result = subject.toDouble(IecUnit.Kibibyte) + val result = subject.inBytes + assertEquals(expectedBytes, result) + } - assertEquals(expectedKilobytes, result, 0.01) - } + test("minus subtracts megabytes correctly") { + val expectedBytes = 2000000L + val subject = 4.megabytes - 2.megabytes - @Test - fun `kibibytes toLong bytes converts correctly`() { - val expectedBytes = 126164664320L - val expectedKilobytes = 123207680L + val result = subject.inBytes + assertEquals(expectedBytes, result) + } - val subject = expectedKilobytes.kibibytes - val result = subject.toLong() + test("times int multiplies megabytes") { + val expectedMegabytes = 20.0 + val subject = 2.megabytes * 10 - assertEquals(expectedBytes, result) - } + val result = subject.inMegabytes + assertEquals(expectedMegabytes, result, 0.01) + } - @Test - fun `plus adds kibibytes correctly`() { - val expectedBytes = 4096L - val subject = 2.kibibytes + 2.kibibytes + test("times int multiplies by zero returns zero") { + val expected = DataSize.Zero + val actual = 2.megabytes * 0 - val result = subject.inBytes - assertEquals(expectedBytes, result) - } + assertEquals(expected, actual) + } - @Test - fun `plus adds max bytes correctly`() { - val expectedBytes = MaxValue.inBytes - val subject = MaxValue + MaxValue + test("div int divides kilobytes") { + val expectedKilobytes = 5.0 + val subject = 10.kilobytes / 2 - val result = subject.inBytes - assertEquals(expectedBytes, result) - } + val result = subject.inKilobytes + assertEquals(expectedKilobytes, result, 0.01) + } - @Test - fun `minus subtracts mebibytes correctly`() { - val expectedBytes = 2097152L - val subject = 4.mebibytes - 2.mebibytes + test("div by zero scalar throws") { + assertThrows(IllegalArgumentException::class.java) { + 1.kilobytes / 0 + } + } - val result = subject.inBytes - assertEquals(expectedBytes, result) - } + test("compareTo orders data correctly") { + val expectedLess = -1 + val expectedEqual = 0 + val expectedGreater = 1 - @Test - fun `minus subtracts max bytes correctly`() { - val expectedBytes = DataSize.Zero.inBytes - val subject = Long.MAX_VALUE.bytes - Long.MAX_VALUE.bytes + val lessResult = 123.kilobytes.compareTo(123.gigabytes) + val equalResult = 123.megabytes.compareTo(123.megabytes) + val greaterResult = 123.megabytes.compareTo(123.kilobytes) - val result = subject.inBytes - assertEquals(expectedBytes, result) - } + assertEquals(expectedLess, lessResult) + assertEquals(expectedEqual, equalResult) + assertEquals(expectedGreater, greaterResult) + } - @Test - fun `times int multiplies bytes`() { - val expectedBytes = MaxValue.inBytes - val subject = MaxValue * 10 + test("orZero returns zero kilobytes when null") { + val expected = 0.0 + val subject: DataSize? = null - val result = subject.inBytes - assertEquals(expectedBytes, result) - } + val actual = subject.orZero().inKilobytes + assertEquals(expected, actual, 0.0) + } - @Test - fun `times int multiplies megabytes`() { - val expectedMegabytes = 20.0 - val subject = 2.megabytes * 10 + test("orZero returns zero megabytes when null") { + val expected = 0.0 + val subject: DataSize? = null - val result = subject.inMegabytes - assertEquals(expectedMegabytes, result, 0.01) - } + val actual = subject.orZero().inMegabytes + assertEquals(expected, actual, 0.0) + } - @Test - fun `times int multiplies by zero returns zero`() { - val expected = DataSize.Zero - val actual = 2.megabytes * 0 + test("orZero returns zero gigabytes when null") { + val expected = 0.0 + val subject: DataSize? = null - assertEquals(expected, actual) - } + val actual = subject.orZero().inGigabytes + assertEquals(expected, actual, 0.0) + } - @Test - @Ignore("Unsupported operation") - fun `times decimal multiplies kilobytes`() { - //val expectedKilobytes = 3.0 - //val subject = 2.kilobytes * 1.5 + test("orZero returns zero terabytes when null") { + val expected = 0.0 + val subject: DataSize? = null - //val result = subject.inKilobytes - //assertEquals(expectedKilobytes, result, 0.01) - } + val actual = subject.orZero().inTerabytes + assertEquals(expected, actual, 0.0) + } - @Test - fun `div int divides kibibytes`() { - val expectedKilobytes = 5.0 - val subject = 10.kibibytes / 2 + test("orZero returns zero petabytes when null") { + val expected = 0.0 + val subject: DataSize? = null - val result = subject.inKibibytes - assertEquals(expectedKilobytes, result, 0.01) - } + val actual = subject.orZero().inPetabytes + assertEquals(expected, actual, 0.0) + } - @Test - @Ignore("Unsupported operation") - fun `div decimal divides megabytes`() { - //val expectedMegabytes = 7.33 - //val subject = 11.decimal.megabytes / 1.5 + test("toSiString correctly formats bytes", testConfig = concurrentTestConfig) { + val expected = "500 B" + val result = 500.bytes.toSiString() + assertEquals(expected, result) + } - //val result = subject.inMegabytes - //assertEquals(expectedMegabytes, result, 0.01) - } + test("toString megabytes no decimals", testConfig = concurrentTestConfig) { + val expected = "100 MB" + val result = 100.megabytes.toString(SiUnit.Megabyte) - @Test - fun `div by zero scalar throws`() { - assertThrows(IllegalArgumentException::class.java) { - 1.kibibytes / 0 + assertEquals(expected, result) } - assertThrows(IllegalArgumentException::class.java) { - 1.kilobytes / 0 + + test("toString megabytes with decimals", testConfig = concurrentTestConfig) { + val expected = "100,45 MB" + val result = 100.45.megabytes.toString(SiUnit.Megabyte, fractionDigits = 2) + + assertEquals(expected, result) } - } - @Test - fun `compareTo orders correctly`() { - val expectedLess = -1 - val expectedEqual = 0 - val expectedGreater = 1 + test("toString megabytes as kilobytes", testConfig = concurrentTestConfig) { + val expected = "100 000 KB" + val result = 100.megabytes.toString(SiUnit.Kilobyte) - val lessResult = 123.kibibytes.compareTo(123.gibibytes) - val equalResult = 123.mebibytes.compareTo(123.mebibytes) - val greaterResult = 123.megabytes.compareTo(123.kilobytes) + assertEquals(expected, result) + } - assertEquals(expectedLess, lessResult) - assertEquals(expectedEqual, equalResult) - assertEquals(expectedGreater, greaterResult) - } + test("toString terabytes as gigabytes", testConfig = concurrentTestConfig) { + val expected = "1000 GB" + val result = 1.terabytes.toString(SiUnit.Gigabyte) - @Test - fun `toString megabytes no decimals`() { - val expected = "100 MB" - val result = 100.megabytes.toString(SiUnit.Megabyte) + assertEquals(expected, result) + } - assertEquals(expected, result) - } + test("toString megabytes as decimal mebibytes", testConfig = concurrentTestConfig) { + val expected = "477 MB" + val result = 500.megabytes.toString(IecUnit.Mebibyte) - @Test - fun `toString mebibytes with decimals`() { - val expected = "100,55 MB" - val result = 100.55.mebibytes.toString(IecUnit.Mebibyte, fractionDigits = 2) + assertEquals(expected, result) + } - assertEquals(expected, result) - } + test("toString megabytes as decimal mebibytes with decimals", testConfig = concurrentTestConfig) { + val expected = "488,8 MB" + val result = 512.5.megabytes.toString(IecUnit.Mebibyte, fractionDigits = 1) - @Test - fun `toString mebibytes as kibibytes`() { - val expected = "102 400 KB" - val result = 100.mebibytes.toString(IecUnit.Kibibyte) + assertEquals(expected, result) + } - assertEquals(expected, result) - } + test("toString megabytes as decimal kibibytes", testConfig = concurrentTestConfig) { + val expected = "977 KB" + val result = 1.megabytes.toString(IecUnit.Kibibyte) - @Test - fun `toString tebibytes as gibibytes`() { - val expected = "1024 GB" - val result = 1.tebibytes.toString(IecUnit.Gibibyte) + assertEquals(expected, result) + } - assertEquals(expected, result) - } + test("toString terabytes as decimal gibibytes", testConfig = concurrentTestConfig) { + val expected = "255 GB" + val result = 0.249.tebibytes.toString(IecUnit.Gibibyte) - @Test - fun `toString mebibytes as decimal megabytes`() { - val expected = "500 MB" - val result = 476.84.mebibytes.toString(SiUnit.Megabyte) + assertEquals(expected, result) + } - assertEquals(expected, result) - } + test("decimal separator formats correctly", testConfig = concurrentTestConfig) { + val expected = "1,1 MB" + val result = 1.1.megabytes.toString(unit = SiUnit.Megabyte, fractionDigits = 1) - @Test - fun `toString mebibytes as decimal megabytes with decimals`() { - val expected = "512,5 MB" - val result = 488.755.mebibytes.toString(SiUnit.Megabyte, fractionDigits = 1) + assertEquals(expected, result) + } - assertEquals(expected, result) - } + test("grouping separator formats correctly", testConfig = concurrentTestConfig) { + val expected = "10 000 MB" + val result = 10_000.megabytes.toString(unit = SiUnit.Megabyte, fractionDigits = 1) - @Test - fun `toString mebibytes as decimal kilobytes`() { - val expected = "1000 KB" - val result = 0.954.mebibytes.toString(SiUnit.Kilobyte) + assertEquals(expected, result) + } - assertEquals(expected, result) - } + test("grouping with decimals formats correctly", testConfig = concurrentTestConfig) { + val expected = "100 000,5 MB" + val result = 100_000.5.megabytes.toString(unit = SiUnit.Megabyte, fractionDigits = 1) - @Test - fun `toString tebibytes as decimal gigabytes`() { - val expected = "255 GB" - val result = 0.232.tebibytes.toString(SiUnit.Gigabyte) + assertEquals(expected, result) + } - assertEquals(expected, result) + test("no grouping when size less than three", testConfig = concurrentTestConfig) { + val expected = "1000 MB" + val result = 1000.megabytes.toString(unit = SiUnit.Megabyte, fractionDigits = 1) + assertEquals(expected, result) + } } - @Test - fun `ByteArray converts to DataSize correctly`() { - val expected = "50 MB" - val result = ByteArray(50 * 1024 * 1024).bytes.toString(IecUnit.Mebibyte) + testSuite(name = "IEC Unit") { + test("throws on negative value") { + assertThrows(IllegalArgumentException::class.java) { + (-3).kibibytes + } + assertThrows(IllegalArgumentException::class.java) { + (-3).toDataSize(IecUnit.Kibibyte) + } + } - assertEquals(expected, result) - } + test("zero bytes return zero for all units") { + val subject = DataSize.Zero - @Test - fun `decimal separator formats correctly`() { - val expected = "1,1 MB" - val result = 1.1.mebibytes.toString(unit = IecUnit.Mebibyte, fractionDigits = 1) + val kibibytes = subject.inKibibytes + val mebibytes = subject.inMebibytes + val gibibytes = subject.inGibibytes + val tebibytes = subject.inTebibytes + val pebibytes = subject.inPebibytes - assertEquals(expected, result) - } + assertEquals(0.0, kibibytes, 0.01) + assertEquals(0.0, mebibytes, 0.01) + assertEquals(0.0, gibibytes, 0.01) + assertEquals(0.0, tebibytes, 0.01) + assertEquals(0.0, pebibytes, 0.01) + } - @Test - fun `grouping separator formats correctly`() { - val expected = "10 000 MB" - val result = 10_000.mebibytes.toString(unit = IecUnit.Mebibyte, fractionDigits = 1) + test("ByteArray converts to DataSize correctly", testConfig = concurrentTestConfig) { + val expected = "50 MB" + val result = ByteArray(50 * 1024 * 1024).bytes.toString(IecUnit.Mebibyte) - assertEquals(expected, result) - } + assertEquals(expected, result) + } - @Test - fun `grouping with decimals formats correctly`() { - val expected = "100 000,5 MB" - val result = 100_000.5.mebibytes.toString(unit = IecUnit.Mebibyte, fractionDigits = 1) + test("toDataSize mebibytes converts correctly") { + val expectedBytes = 123207680L + val expectedMegaBytes = 117.5 + val subject = expectedMegaBytes.toDataSize(IecUnit.Mebibyte) - assertEquals(expected, result) - } + val resultBytes = subject.inBytes + assertEquals(expectedBytes, resultBytes) - @Test - fun `no grouping when size less than three`() { - val expected = "1000 MB" - val result = 1000.mebibytes.toString(unit = IecUnit.Mebibyte, fractionDigits = 1) - assertEquals(expected, result) - } + val resultMegabytes = subject.inMebibytes + assertEquals(expectedMegaBytes, resultMegabytes, 0.01) + } - @Test - fun `toString correctly formats bytes`() { - val expected = "10 000 B" - val result = 10_000.bytes.toString(unit = ByteUnit.Byte, fractionDigits = 0) - assertEquals(expected, result) - } + test("bytes toDouble kibibytes converts correctly") { + val expectedBytes = 123207680L + val expectedKilobytes = 120320.0 // 117.5 Mb - @Test - fun `toBinaryString correctly formats bytes`() { - val expected = "500 B" - val result = 500.bytes.toIecString() - assertEquals(expected, result) - } + val subject = expectedBytes.bytes + val result = subject.toDouble(IecUnit.Kibibyte) - @Test - fun `toDecimalString correctly formats bytes`() { - val expected = "500 B" - val result = 500.bytes.toSiString() - assertEquals(expected, result) - } + assertEquals(expectedKilobytes, result, 0.01) + } - @Test - fun `orBinaryZero returns zero bytes for null`() { - val x: DataSize? = null - assertEquals(0, x.orZero().inBytes) - } + test("kibibytes toLong bytes converts correctly") { + val expectedBytes = 126164664320L + val expectedKilobytes = 123207680L - @Test - fun `orDecimalZero returns zero kilobytes for null`() { - val x: DataSize? = null - assertEquals(0.0, x.orZero().inKilobytes, 0.0) - } + val subject = expectedKilobytes.kibibytes + val result = subject.toLong() - @Test - fun `orBinaryZero returns zero mebibytes for null`() { - val x: DataSize? = null - assertEquals(0.0, x.orZero().inMebibytes, 0.0) - } + assertEquals(expectedBytes, result) + } - @Test - fun `orDecimalZero returns zero gigabytes for null`() { - val x: DataSize? = null - assertEquals(0.0, x.orZero().inGigabytes, 0.0) - } + test("plus adds kibibytes correctly") { + val expectedBytes = 4096L + val subject = 2.kibibytes + 2.kibibytes + + val result = subject.inBytes + assertEquals(expectedBytes, result) + } - @Test - fun `orBinaryZero returns zero terabytes for null`() { - val x: DataSize? = null - assertEquals(0.0, x.orZero().inTerabytes, 0.0) + test("minus subtracts mebibytes correctly") { + val expectedBytes = 2097152L + val subject = 4.mebibytes - 2.mebibytes + + val result = subject.inBytes + assertEquals(expectedBytes, result) + } + + test("times int multiplies mebibytes") { + val expectedMegabytes = 20.0 + val subject = 2.mebibytes * 10 + + val result = subject.inMebibytes + assertEquals(expectedMegabytes, result, 0.01) + } + + test("times int multiplies by zero returns zero") { + val expected = DataSize.Zero + val actual = 2.mebibytes * 0 + + assertEquals(expected, actual) + } + + test("div int divides kibibytes") { + val expectedKilobytes = 5.0 + val subject = 10.kibibytes / 2 + + val result = subject.inKibibytes + assertEquals(expectedKilobytes, result, 0.01) + } + + test("div by zero scalar throws") { + assertThrows(IllegalArgumentException::class.java) { + 1.kibibytes / 0 + } + } + + test("compareTo orders data correctly") { + val expectedLess = -1 + val expectedEqual = 0 + val expectedGreater = 1 + + val lessResult = 123.kibibytes.compareTo(123.gibibytes) + val equalResult = 123.mebibytes.compareTo(123.mebibytes) + val greaterResult = 123.mebibytes.compareTo(123.kibibytes) + + assertEquals(expectedLess, lessResult) + assertEquals(expectedEqual, equalResult) + assertEquals(expectedGreater, greaterResult) + } + + test("orZero returns zero kibibytes when null") { + val expected = 0.0 + val subject: DataSize? = null + + val actual = subject.orZero().inKibibytes + assertEquals(expected, actual, 0.0) + } + + test("orZero returns zero mebibytes when null") { + val expected = 0.0 + val subject: DataSize? = null + + val actual = subject.orZero().inMebibytes + assertEquals(expected, actual, 0.0) + } + + test("orZero returns zero gibibytes when null") { + val expected = 0.0 + val subject: DataSize? = null + + val actual = subject.orZero().inGibibytes + assertEquals(expected, actual, 0.0) + } + + test("orZero returns zero tebibytes when null") { + val expected = 0.0 + val subject: DataSize? = null + + val actual = subject.orZero().inTebibytes + assertEquals(expected, actual, 0.0) + } + + test("orZero returns zero pebibytes when null") { + val expected = 0.0 + val subject: DataSize? = null + + val actual = subject.orZero().inPebibytes + assertEquals(expected, actual, 0.0) + } + + test("toIecString correctly formats bytes", testConfig = concurrentTestConfig) { + val expected = "500 B" + val result = 500.bytes.toIecString() + assertEquals(expected, result) + } + + test("toString mebibytes no decimals", testConfig = concurrentTestConfig) { + val expected = "100 MB" + val result = 100.mebibytes.toString(IecUnit.Mebibyte) + + assertEquals(expected, result) + } + + test("toString mebibytes with decimals", testConfig = concurrentTestConfig) { + val expected = "100,55 MB" + val result = 100.55.mebibytes.toString(IecUnit.Mebibyte, fractionDigits = 2) + + assertEquals(expected, result) + } + + test("toString mebibytes as kibibytes", testConfig = concurrentTestConfig) { + val expected = "102 400 KB" + val result = 100.mebibytes.toString(IecUnit.Kibibyte) + + assertEquals(expected, result) + } + + test("toString tebibytes as gibibytes", testConfig = concurrentTestConfig) { + val expected = "1024 GB" + val result = 1.tebibytes.toString(IecUnit.Gibibyte) + + assertEquals(expected, result) + } + + test("toString mebibytes as decimal megabytes", testConfig = concurrentTestConfig) { + val expected = "500 MB" + val result = 476.84.mebibytes.toString(SiUnit.Megabyte) + + assertEquals(expected, result) + } + + test("toString mebibytes as decimal megabytes with decimals", testConfig = concurrentTestConfig) { + val expected = "512,5 MB" + val result = 488.755.mebibytes.toString(SiUnit.Megabyte, fractionDigits = 1) + + assertEquals(expected, result) + } + + test("toString mebibytes as decimal kilobytes", testConfig = concurrentTestConfig) { + val expected = "1000 KB" + val result = 0.954.mebibytes.toString(SiUnit.Kilobyte) + + assertEquals(expected, result) + } + + test("toString tebibytes as decimal gigabytes", testConfig = concurrentTestConfig) { + val expected = "255 GB" + val result = 0.232.tebibytes.toString(SiUnit.Gigabyte) + + assertEquals(expected, result) + } + + test("decimal separator formats correctly", testConfig = concurrentTestConfig) { + val expected = "1,1 MB" + val result = 1.1.mebibytes.toString(unit = IecUnit.Mebibyte, fractionDigits = 1) + + assertEquals(expected, result) + } + + test("grouping separator formats correctly", testConfig = concurrentTestConfig) { + val expected = "10 000 MB" + val result = 10_000.mebibytes.toString(unit = IecUnit.Mebibyte, fractionDigits = 1) + + assertEquals(expected, result) + } + + test("grouping with decimals formats correctly", testConfig = concurrentTestConfig) { + val expected = "100 000,5 MB" + val result = 100_000.5.mebibytes.toString(unit = IecUnit.Mebibyte, fractionDigits = 1) + + assertEquals(expected, result) + } + + test( + "no grouping when size less than three", testConfig = concurrentTestConfig + ) { + val expected = "1000 MB" + val result = 1000.mebibytes.toString(unit = IecUnit.Mebibyte, fractionDigits = 1) + assertEquals(expected, result) + } } - @Test - fun `orDecimalZero returns zero petabytes for null`() { - val x: DataSize? = null - assertEquals(0.0, x.orZero().inPetabytes, 0.0) + testSuite(name = "Unit Independent") { + test("zero bytes return zero byte unit") { + val subject = DataSize.Zero + + val actual = subject.inBytes + assertEquals(0, actual) + } + + test("toDataSize bytes keeps value") { + val expectedBytes = 104857600L // 100 Mb + val subject = expectedBytes.toDataSize(ByteUnit.Byte) + + val result = subject.inBytes + assertEquals(expectedBytes, result) + } + + test("plus adds max bytes correctly") { + val expectedBytes = MaxValue.inBytes + val subject = MaxValue + MaxValue + + val result = subject.inBytes + assertEquals(expectedBytes, result) + } + + test("minus subtracts max bytes correctly") { + val expectedBytes = DataSize.Zero.inBytes + val subject = Long.MAX_VALUE.bytes - Long.MAX_VALUE.bytes + + val result = subject.inBytes + assertEquals(expectedBytes, result) + } + + test("times int multiplies bytes") { + val expectedBytes = MaxValue.inBytes + val subject = MaxValue * 10 + + val result = subject.inBytes + assertEquals(expectedBytes, result) + } + + test("toString correctly formats bytes", testConfig = concurrentTestConfig) { + val expected = "10 000 B" + + val result = 10_000.bytes.toString(unit = ByteUnit.Byte, fractionDigits = 0) + assertEquals(expected, result) + } + + test("orZero returns zero bytes when null") { + val expected = DataSize.Zero.inBytes + val subject: DataSize? = null + + val actual = subject.orZero().inBytes + assertEquals(expected, actual) + } } -} \ No newline at end of file +} diff --git a/datasize-core/src/commonTest/kotlin/io/github/ardiien/datasize/formatter/SimpleDataSizeFormatterTest.kt b/datasize-core/src/commonTest/kotlin/io/github/ardiien/datasize/formatter/SimpleDataSizeFormatterTest.kt index 4b49f9a..1621cd3 100644 --- a/datasize-core/src/commonTest/kotlin/io/github/ardiien/datasize/formatter/SimpleDataSizeFormatterTest.kt +++ b/datasize-core/src/commonTest/kotlin/io/github/ardiien/datasize/formatter/SimpleDataSizeFormatterTest.kt @@ -5,67 +5,62 @@ */ package io.github.ardiien.datasize.formatter +import de.infix.testBalloon.framework.core.testSuite import io.github.ardiien.datasize.DataSize.Companion.bytes import io.github.ardiien.datasize.DataSize.Companion.gibibytes import io.github.ardiien.datasize.DefaultDataSizeUnitFormatter import io.github.ardiien.datasize.IecUnit -import kotlin.test.Test import kotlin.test.assertEquals -class SimpleDataSizeFormatterTest { +val SimpleDataSizeFormatterTestSuite by testSuite { + testFixture { DefaultDataSizeUnitFormatter } asContextForEach { + test("binaryFormat with zero fraction digits returns correct string") { + val expected = "5 GB" - private val formatter: SimpleDataSizeFormatter = DefaultDataSizeUnitFormatter + val size = 5.32.gibibytes + val actual = binaryFormat( + value = size, + unit = IecUnit.Gibibyte, + fractionDigits = 0, + ) - @Test - fun `binaryFormat with zero fraction digits returns correct string`() { - val expected = "5 GB" + assertEquals(expected, actual) + } - val size = 5.32.gibibytes - val actual = formatter.binaryFormat( - value = size, - unit = IecUnit.Gibibyte, - fractionDigits = 0, - ) + test("binaryFormat with one fraction digit returns correct string") { + val expected = "5,3 GB" - assertEquals(expected, actual) - } - - @Test - fun `binaryFormat with one fraction digit returns correct string`() { - val expected = "5,3 GB" + val size = 5.32.gibibytes + val actual = binaryFormat( + value = size, + unit = IecUnit.Gibibyte, + fractionDigits = 1, + ) - val size = 5.32.gibibytes - val actual = formatter.binaryFormat( - value = size, - unit = IecUnit.Gibibyte, - fractionDigits = 1, - ) + assertEquals(expected, actual) + } - assertEquals(expected, actual) - } + test("binaryFormat with two fraction digits returns correct string") { + val expected = "5,32 GB" - @Test - fun `binaryFormat with two fraction digits returns correct string`() { - val expected = "5,32 GB" + val size = 5.32.gibibytes + val actual = binaryFormat( + value = size, + unit = IecUnit.Gibibyte, + fractionDigits = 2, + ) - val size = 5.32.gibibytes - val actual = formatter.binaryFormat( - value = size, - unit = IecUnit.Gibibyte, - fractionDigits = 2, - ) - - assertEquals(expected, actual) - } + assertEquals(expected, actual) + } - @Test - fun `binaryFormat returns correct string on zero bytes`() { - val expected = "0 B" + test("binaryFormat returns correct string on zero bytes") { + val expected = "0 B" - val size = 0.bytes - val actual = formatter.binaryFormat(value = size) + val size = 0.bytes + val actual = binaryFormat(value = size) - assertEquals(expected, actual) + assertEquals(expected, actual) + } } -} \ No newline at end of file +} diff --git a/datasize-core/src/commonTest/kotlin/io/github/ardiien/datasize/localizer/SimpleDataSizeUnitLocalizerTest.kt b/datasize-core/src/commonTest/kotlin/io/github/ardiien/datasize/localizer/SimpleDataSizeUnitLocalizerTest.kt index 5ad5464..c326c01 100644 --- a/datasize-core/src/commonTest/kotlin/io/github/ardiien/datasize/localizer/SimpleDataSizeUnitLocalizerTest.kt +++ b/datasize-core/src/commonTest/kotlin/io/github/ardiien/datasize/localizer/SimpleDataSizeUnitLocalizerTest.kt @@ -5,72 +5,66 @@ */ package io.github.ardiien.datasize.localizer +import de.infix.testBalloon.framework.core.testSuite import io.github.ardiien.datasize.DataSizeTag import io.github.ardiien.datasize.DataSizeUnit import io.github.ardiien.datasize.IecUnit import io.github.ardiien.datasize.SiUnit import io.github.ardiien.datasize.util.castTo import kotlinx.collections.immutable.ImmutableList -import kotlin.test.Test import kotlin.test.assertEquals -class SimpleDataSizeUnitLocalizerTest { - - private val localizer: SimpleDataSizeUnitLocalizer = SimpleDataSizeUnitLocalizer() - - @Test - fun `name returns correct string for BinaryUnit`() { - val (expected, actual) = createExpectedActualMaps( - entries = IecUnit.entries().castTo(), - expectedString = { SimpleDataSizeUnitLocalizer.Labels[it]?.first.orEmpty() }, - actualString = { localizer.name(it) }, - ) +val SimpleDataSizeUnitLocalizerTestSuite by testSuite { + fun createExpectedActualMaps( + entries: ImmutableList, + expectedString: (DataSizeTag) -> String, + actualString: (DataSizeUnit) -> String, + ): Pair, List> { + val expected = entries.map { expectedString(it.tag()) } + val actual = entries.map { actualString(it) } - assertEquals(expected, actual) + return expected to actual } - @Test - fun `name returns correct string for DecimalUnit`() { - val (expected, actual) = createExpectedActualMaps( - entries = SiUnit.entries().castTo(), - expectedString = { SimpleDataSizeUnitLocalizer.Labels[it]?.first.orEmpty() }, - actualString = { localizer.name(it) }, - ) - - assertEquals(expected, actual) - } + testFixture { SimpleDataSizeUnitLocalizer() } asContextForEach { + test("name returns correct string for BinaryUnit") { + val (expected, actual) = createExpectedActualMaps( + entries = IecUnit.entries().castTo(), + expectedString = { SimpleDataSizeUnitLocalizer.Labels[it]?.first.orEmpty() }, + actualString = { name(it) }, + ) - @Test - fun `abbreviation returns correct string for DecimalUnit`() { - val (expected, actual) = createExpectedActualMaps( - entries = SiUnit.entries().castTo(), - expectedString = { SimpleDataSizeUnitLocalizer.Labels[it]?.second.orEmpty() }, - actualString = { localizer.abbreviation(it) }, - ) + assertEquals(expected, actual) + } + test("name returns correct string for DecimalUnit") { + val (expected, actual) = createExpectedActualMaps( + entries = SiUnit.entries().castTo(), + expectedString = { SimpleDataSizeUnitLocalizer.Labels[it]?.first.orEmpty() }, + actualString = { name(it) }, + ) - assertEquals(expected, actual) - } + assertEquals(expected, actual) + } - @Test - fun `abbreviation returns correct string for BinaryUnit`() { - val (expected, actual) = createExpectedActualMaps( - entries = IecUnit.entries().castTo(), - expectedString = { SimpleDataSizeUnitLocalizer.Labels[it]?.second.orEmpty() }, - actualString = { localizer.abbreviation(it) }, - ) + test("abbreviation returns correct string for DecimalUnit") { + val (expected, actual) = createExpectedActualMaps( + entries = SiUnit.entries().castTo(), + expectedString = { SimpleDataSizeUnitLocalizer.Labels[it]?.second.orEmpty() }, + actualString = { abbreviation(it) }, + ) - assertEquals(expected, actual) - } + assertEquals(expected, actual) + } - private fun createExpectedActualMaps( - entries: ImmutableList, - expectedString: (DataSizeTag) -> String, - actualString: (DataSizeUnit) -> String, - ): Pair, List> { - val expected = entries.map { expectedString(it.tag()) } - val actual = entries.map { actualString(it) } + test("abbreviation returns correct string for BinaryUnit") { + val (expected, actual) = createExpectedActualMaps( + entries = IecUnit.entries().castTo(), + expectedString = { SimpleDataSizeUnitLocalizer.Labels[it]?.second.orEmpty() }, + actualString = { abbreviation(it) }, + ) - return expected to actual + assertEquals(expected, actual) + } } -} \ No newline at end of file +}