[WASM] Add kotlin.math tests
This commit is contained in:
committed by
igoriakovlev
parent
adee0f1cb0
commit
c9a92d71ae
@@ -0,0 +1,114 @@
|
||||
package generators.math
|
||||
|
||||
import templates.capitalize
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import kotlin.reflect.KCallable
|
||||
|
||||
internal class MathTestsGenerator(
|
||||
private val outputFile: File,
|
||||
private val testPoints: List<Double>,
|
||||
private val functions: List<Model>
|
||||
) {
|
||||
sealed class Model(val exact: Boolean) {
|
||||
abstract val function: Function<*>
|
||||
}
|
||||
class ModelFunction1(override val function: Function1<Double, Double>, exact: Boolean = true, val customTestPoint: Double? = null) : Model(exact)
|
||||
class ModelFunction2(override val function: Function2<Double, Double, Double>, exact: Boolean = true) : Model(exact)
|
||||
|
||||
private val doubleSpecialPoints = listOf(
|
||||
Double.NEGATIVE_INFINITY,
|
||||
Double.POSITIVE_INFINITY,
|
||||
Double.MIN_VALUE,
|
||||
Double.MAX_VALUE,
|
||||
Double.NaN,
|
||||
123456.789123e200, //Just a random point
|
||||
)
|
||||
|
||||
private fun mutatePoints(points: List<Double>): Sequence<Double> = sequence {
|
||||
for (pt in points) {
|
||||
yield(pt)
|
||||
yield(pt + 0.1e-10)
|
||||
yield(pt - 0.1e-10)
|
||||
yield(pt * 0.5)
|
||||
yield(pt / 0.5)
|
||||
yield(pt * 0.25)
|
||||
yield(pt / 0.25)
|
||||
yield(-pt)
|
||||
yield(-pt + 0.1e-10)
|
||||
yield(-pt - 0.1e-10)
|
||||
yield(-pt * 0.5)
|
||||
yield(-pt / 0.5)
|
||||
yield(-pt * 0.25)
|
||||
yield(-pt / 0.25)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generatePoints(): Sequence<Double> =
|
||||
doubleSpecialPoints.asSequence() + mutatePoints(testPoints)
|
||||
|
||||
private fun generate2dPoints(): Sequence<Pair<Double, Double>> =
|
||||
generatePoints().flatMap { lhsElem -> generatePoints().map { rhsElem -> lhsElem to rhsElem } }
|
||||
|
||||
private fun Double.toULongString() = "0x${toBits().toULong().toString(16)}UL"
|
||||
|
||||
private fun Sequence<Double>.toULongVariableList(name: String): String = buildString {
|
||||
appendLine("val $name = arrayOf(")
|
||||
this@toULongVariableList.forEachIndexed { i, d ->
|
||||
append("${d.toULongString()}, ".prependIndent())
|
||||
if (i % 4 == 3) appendLine()
|
||||
}
|
||||
appendLine()
|
||||
appendLine(")")
|
||||
}
|
||||
|
||||
private fun generateTestMethods(): String = buildString {
|
||||
functions.forEach { model ->
|
||||
appendLine()
|
||||
appendLine("@Test")
|
||||
val function = model.function
|
||||
val name = (function as KCallable<*>).name
|
||||
appendLine("fun test${name.capitalize()}() {")
|
||||
val answers = when (model) {
|
||||
is ModelFunction1 -> generatePoints().map { arg -> model.function(arg) }
|
||||
is ModelFunction2 -> generate2dPoints().map { arg -> model.function(arg.first, arg.second) }
|
||||
}
|
||||
appendLineWithIndent(answers.toULongVariableList("answers"))
|
||||
appendLine()
|
||||
appendLineWithIndent("checkAnswers(::$name, arguments, answers, ${model.exact})")
|
||||
val specialFunctionPoint = (model as? ModelFunction1)?.customTestPoint
|
||||
if (specialFunctionPoint != null) {
|
||||
appendLineWithIndent(mutatePoints(listOf(specialFunctionPoint)).toULongVariableList("specialFunctionPointArguments"))
|
||||
appendLineWithIndent(
|
||||
mutatePoints(listOf(specialFunctionPoint)).map(model.function).toULongVariableList("specialFunctionPointResults")
|
||||
)
|
||||
appendLineWithIndent("checkAnswers(::$name, specialFunctionPointArguments, specialFunctionPointResults, ${model.exact})")
|
||||
}
|
||||
appendLine("}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun StringBuilder.appendLineWithIndent(string: String) {
|
||||
appendLine(string.prependIndent())
|
||||
}
|
||||
|
||||
private fun FileWriter.writeTestClass() {
|
||||
appendLine(
|
||||
"""
|
||||
class ${outputFile.nameWithoutExtension.capitalize()}Test {
|
||||
${("private " + generatePoints().toULongVariableList("arguments")).prependIndent()}
|
||||
${generateTestMethods().prependIndent()}
|
||||
}
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
fun generate() {
|
||||
FileWriter(outputFile).use { writer ->
|
||||
writer.writeHeader(outputFile)
|
||||
writer.appendLine()
|
||||
writer.writeTestClass()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package generators.math
|
||||
|
||||
import templates.COPYRIGHT_NOTICE
|
||||
import templates.autoGeneratedWarning
|
||||
import templates.readCopyrightNoticeFromProfile
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import kotlin.math.*
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val baseDir: File
|
||||
when (args.size) {
|
||||
1 -> {
|
||||
baseDir = File(args[0])
|
||||
}
|
||||
else -> {
|
||||
println("Parameters: <kotlin-base-dir> - generates sources for std math tests")
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
||||
COPYRIGHT_NOTICE =
|
||||
readCopyrightNoticeFromProfile { Thread.currentThread().contextClassLoader.getResourceAsStream("apache.xml")!!.reader() }
|
||||
|
||||
generateUtils(baseDir.resolve("mathTestUtils.kt"))
|
||||
|
||||
val trigonometryFunctions = listOf(
|
||||
MathTestsGenerator.ModelFunction1(::sin),
|
||||
MathTestsGenerator.ModelFunction1(::cos),
|
||||
MathTestsGenerator.ModelFunction1(::tan, exact = false),
|
||||
MathTestsGenerator.ModelFunction1(::asin),
|
||||
MathTestsGenerator.ModelFunction1(::atan),
|
||||
MathTestsGenerator.ModelFunction2(::atan2),
|
||||
MathTestsGenerator.ModelFunction1(::sinh),
|
||||
MathTestsGenerator.ModelFunction1(::cosh),
|
||||
MathTestsGenerator.ModelFunction1(::tanh),
|
||||
MathTestsGenerator.ModelFunction1(::asinh, exact = false),
|
||||
MathTestsGenerator.ModelFunction1(::acosh, exact = false),
|
||||
MathTestsGenerator.ModelFunction1(::atanh, exact = false),
|
||||
)
|
||||
MathTestsGenerator(
|
||||
outputFile = baseDir.resolve("trigonometry.kt"),
|
||||
testPoints = listOf(0.0, 1.0, PI),
|
||||
functions = trigonometryFunctions
|
||||
).generate()
|
||||
|
||||
//workaround about method with receiver
|
||||
fun powWrapper(x: Double, y: Double): Double = x.pow(y)
|
||||
|
||||
val powerFunctions = listOf(
|
||||
MathTestsGenerator.ModelFunction2(::hypot),
|
||||
MathTestsGenerator.ModelFunction1(::sqrt),
|
||||
MathTestsGenerator.ModelFunction1(::exp, exact = false),
|
||||
MathTestsGenerator.ModelFunction1(::expm1),
|
||||
MathTestsGenerator.ModelFunction2(::log, exact = false),
|
||||
MathTestsGenerator.ModelFunction1(::ln, exact = false, customTestPoint = E),
|
||||
MathTestsGenerator.ModelFunction1(::log10, exact = false, customTestPoint = 10.0),
|
||||
MathTestsGenerator.ModelFunction1(::log2, exact = false, customTestPoint = 2.0),
|
||||
MathTestsGenerator.ModelFunction1(::ln1p, customTestPoint = E),
|
||||
MathTestsGenerator.ModelFunction2(::powWrapper, exact = false),
|
||||
)
|
||||
MathTestsGenerator(
|
||||
outputFile = baseDir.resolve("powers.kt"),
|
||||
testPoints = listOf(0.0, 1.0),
|
||||
functions = powerFunctions
|
||||
).generate()
|
||||
|
||||
|
||||
val roundingFunctions = listOf(
|
||||
MathTestsGenerator.ModelFunction1(::ceil),
|
||||
MathTestsGenerator.ModelFunction1(::floor),
|
||||
MathTestsGenerator.ModelFunction1(::truncate),
|
||||
MathTestsGenerator.ModelFunction1(::round),
|
||||
)
|
||||
MathTestsGenerator(
|
||||
outputFile = baseDir.resolve("roundings.kt"),
|
||||
testPoints = listOf(0.4, 0.5, 0.6, 1.0),
|
||||
functions = roundingFunctions
|
||||
).generate()
|
||||
|
||||
}
|
||||
|
||||
internal fun FileWriter.writeHeader(outputFile: File) {
|
||||
println("Generating file: $outputFile")
|
||||
appendLine(COPYRIGHT_NOTICE)
|
||||
appendLine("package test.math")
|
||||
appendLine()
|
||||
appendLine(autoGeneratedWarning("mathTestGeneratorMain.kt"))
|
||||
appendLine()
|
||||
appendLine("import kotlin.math.*")
|
||||
appendLine("import kotlin.test.*")
|
||||
appendLine()
|
||||
}
|
||||
|
||||
private fun generateUtils(outputFile: File) {
|
||||
FileWriter(outputFile).use { writer ->
|
||||
writer.writeHeader(outputFile)
|
||||
writer.appendLine()
|
||||
writer.write("""
|
||||
|
||||
//workaround about method with receiver
|
||||
internal fun powWrapper(x: Double, y: Double): Double = x.pow(y)
|
||||
|
||||
private fun getMantissa(d: Double): Double = Double.fromBits(((d.toBits().toULong() and 0x800fffffffffffffUL) or 0x3ff0000000000000UL).toLong())
|
||||
private fun getExp(d: Double): Int = (((d.toBits() shr 52) and 0x7FF) - 1023).toInt()
|
||||
|
||||
private fun compare(arg1: Double, arg2: Double?, result1: Double, result2: Double, exact: Boolean = false) {
|
||||
val difference: Any? = when {
|
||||
result1.isNaN() -> if (result2.isNaN()) null else result1
|
||||
result1.isInfinite() -> if (result2.isInfinite() && result1.sign == result2.sign) null else result1
|
||||
result2.isNaN() -> result2
|
||||
result2.isInfinite() -> result2
|
||||
else -> {
|
||||
if (exact) {
|
||||
if (result1.toBits() == result2.toBits()) null else abs(result1 - result2)
|
||||
} else {
|
||||
val (toCompare1, toCompare2) = when (getExp(result1) - getExp(result2)) {
|
||||
0 -> getMantissa(result1) to getMantissa(result2)
|
||||
1 -> getMantissa(result1) to getMantissa(result2) / 2.0
|
||||
-1 -> getMantissa(result1) / 2.0 to getMantissa(result2)
|
||||
else -> result1 to result2
|
||||
}
|
||||
abs(toCompare1 - toCompare2).takeIf { it > 1e-14 }
|
||||
}
|
||||
}
|
||||
}
|
||||
assertNull(difference, "ARG1 = " + arg1 + (if (arg2 == null) "" else " and ARG2 = " + arg2))
|
||||
}
|
||||
|
||||
internal fun checkAnswers(function: Function1<Double, Double>, arguments: Array<ULong>, answers: Array<ULong>, exact: Boolean) {
|
||||
arguments.forEachIndexed { i, x ->
|
||||
val argument1 = Double.fromBits(x.toLong())
|
||||
val answer = Double.fromBits(answers[i].toLong())
|
||||
compare(argument1, null, answer, function(argument1), exact = exact)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun checkAnswers(function: Function2<Double, Double, Double>, arguments: Array<ULong>, answers: Array<ULong>, exact: Boolean) {
|
||||
arguments
|
||||
.flatMap { lhsElem -> arguments.map { rhsElem -> lhsElem to rhsElem } }
|
||||
.forEachIndexed { i, x ->
|
||||
val argument1 = Double.fromBits(x.first.toLong())
|
||||
val argument2 = Double.fromBits(x.second.toLong())
|
||||
val answer = Double.fromBits(answers[i].toLong())
|
||||
compare(argument1, argument2, answer, function(argument1, argument2), exact = exact)
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user