All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.ch.tutteli.atrium.specs.integration.FloatingPointWithErrorToleranceExpectationsSpec.kt Maven / Gradle / Ivy

package ch.tutteli.atrium.specs.integration

import ch.tutteli.atrium.api.fluent.en_GB.message
import ch.tutteli.atrium.api.fluent.en_GB.notToContain
import ch.tutteli.atrium.api.fluent.en_GB.toContain
import ch.tutteli.atrium.api.fluent.en_GB.toThrow
import ch.tutteli.atrium.api.verbs.internal.expect
import ch.tutteli.atrium.core.polyfills.format
import ch.tutteli.atrium.core.polyfills.formatFloatingPointNumber
import ch.tutteli.atrium.core.polyfills.fullName
import ch.tutteli.atrium.specs.Fun2
import ch.tutteli.atrium.specs.SubjectLessSpec
import ch.tutteli.atrium.specs.forSubjectLess
import ch.tutteli.atrium.reporting.reportables.descriptions.DescriptionFloatingPointProof.*
import org.spekframework.spek2.Spek
import org.spekframework.spek2.dsl.Root
import org.spekframework.spek2.style.specification.describe
import kotlin.math.absoluteValue

abstract class FloatingPointWithErrorToleranceExpectationsSpec(
    toEqualWithErrorToleranceFloat: Fun2,
    toEqualWithErrorToleranceDouble: Fun2,
    describePrefix: String = "[Atrium] "
) : Spek({

    include(object : SubjectLessSpec(
        "$describePrefix[Float] ",
        toEqualWithErrorToleranceFloat.forSubjectLess(1.0f, 0.01f)
    ) {})
    include(object : SubjectLessSpec(
        "$describePrefix[Double] ",
        toEqualWithErrorToleranceDouble.forSubjectLess(1.0, 0.01)
    ) {})

    fun  Root.describeFun(
        pair: Fun2,
        withFailureNotice: Boolean,
        absDiff: (T, T) -> T,
        testData: List>
    ) = this.checkFloatingPoint(describePrefix, pair, withFailureNotice, absDiff, testData)

    //@formatter:off
    describeFun(toEqualWithErrorToleranceFloat, true, { a: Float, b: Float -> (a - b).absoluteValue }, listOf(
        TestData(0.001f, 0.001f, listOf(0.002f, 0.0f, -0.0f, 0.00001f), listOf(0.0021f, -0.0000001f)),
        TestData(9.999f, 0.001f, listOf(9.998f, 9.9989f, 9.9988f), listOf(1.1f, 1.001f, 1.001f, 9.997f, /* due to precision */ 10.0f)),
        //should give out scientific notation
        TestData(0.000_000_01f, 0.000_000_002f, listOf(0.000_000_011f, 0.000_000_009f), listOf(0.000_000_013f, 0.000_000_007f, /* due to precision */ 0.000_000_012f, 0.000_000_008f))
    ))
    describeFun(toEqualWithErrorToleranceDouble, true, { a, b -> (a - b).absoluteValue }, listOf(
        TestData(1.0, 0.01, listOf(1.009), listOf(0.98, 1.02, 1.011, /* due to precision */ 1.01, 0.99)),
        TestData(0.001, 0.001, listOf(0.002, 0.0, -0.0, 0.00001), listOf(0.0021, -0.0000001)),
        TestData(9.99999, 0.00001, listOf(10.0, 9.99998), listOf(1.1, 1.001, 1.001, 9.99997)),
        //should give out scientific notation
        TestData(0.000_000_01, 0.000_000_002, listOf(0.000_000_011, 0.000_000_012, 0.000_000_009, 0.000_000_008), listOf(0.000_000_013, 0.000_000_007))
    ))
    //@formatter:on

}) {
    data class TestData(val subject: T, val tolerance: T, val holding: List, val failing: List)
}

fun  Root.checkFloatingPoint(
    describePrefix: String,
    pair: Fun2,
    withFailureNotice: Boolean,
    absDiff: (T, T) -> T,
    testData: List>
) {
    val (name, toEqualWithErrorTolerance) = pair

    describe("$describePrefix $name") {
        testData.forEach { (subject, tolerance, holding, failing) ->
            context("tolerance is $tolerance and subject is $subject ...") {
                it("... compare to $subject does not throw") {
                    expect(subject).toEqualWithErrorTolerance(subject, tolerance)
                }

                holding.forEach { num ->
                    it("... compare to $num does not throw") {
                        expect(subject).toEqualWithErrorTolerance(num, tolerance)
                    }
                }

                val toEqualInclErrorToleranceDescr = TO_EQUAL_WITH_ERROR_TOLERANCE.string.format(tolerance)
                val failureNotice = FAILURE_DUE_TO_FLOATING_POINT_NUMBER.string.format(subject::class.fullName)
                failing.forEach { num ->
                    it("... compare to $num throws AssertionError") {
                        expect {
                            expect(subject).toEqualWithErrorTolerance(num, tolerance)
                        }.toThrow {
                            message {
                                val exactCheck = TO_EQUAL_WITH_ERROR_TOLERANCE_EXPLAINED.string.format(
                                    @Suppress("DEPRECATION")
                                    formatFloatingPointNumber(subject),
                                    @Suppress("DEPRECATION")
                                    formatFloatingPointNumber(num),
                                    @Suppress("DEPRECATION")
                                    formatFloatingPointNumber(absDiff(subject, num)),
                                    @Suppress("DEPRECATION")
                                    formatFloatingPointNumber(tolerance)
                                )
                                toContainSubject(subject)
                                toContainDescr(toEqualInclErrorToleranceDescr, num)
                                toContain(exactCheck)
                                if (withFailureNotice) {
                                    toContain(failureNotice)
                                } else {
                                    notToContain(failureNotice)
                                }
                            }
                        }

                    }
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy