published to our maven
This commit is contained in:
1 parent
185aa4e0cf
commit
8c6a1979ed
39 files changed
+89
-104
No files matched your search
@@ -0,0 +1,276 @@
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.Context
|
||||
import net.sergeych.lyng.ObjVoid
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Files.readAllLines
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.extension
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.fail
|
||||
|
||||
fun leftMargin(s: String): Int {
|
||||
var cnt = 0
|
||||
for (c in s) {
|
||||
when (c) {
|
||||
' ' -> cnt++
|
||||
'\t' -> cnt = (cnt / 4.0 + 0.9).toInt() * 4
|
||||
else -> break
|
||||
}
|
||||
}
|
||||
return cnt
|
||||
}
|
||||
|
||||
data class DocTest(
|
||||
val fileName: String,
|
||||
val line: Int,
|
||||
val code: String,
|
||||
val expectedOutput: String,
|
||||
val expectedResult: String,
|
||||
val expectedError: String? = null,
|
||||
val bookMode: Boolean = false
|
||||
) {
|
||||
val sourceLines by lazy { code.lines() }
|
||||
|
||||
val fileNamePart by lazy {
|
||||
Paths.get(fileName).fileName.toString()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val absPath = Paths.get(fileName).absolutePathString()
|
||||
return "DocTest: $absPath:${line + 1}"
|
||||
}
|
||||
|
||||
val detailedString by lazy {
|
||||
val codeWithLines = sourceLines.withIndex().map { (i, s) -> "${i + line + 1}: $s" }.joinToString("\n")
|
||||
var result = "$this\n$codeWithLines\n"
|
||||
if( !bookMode) {
|
||||
if (expectedOutput.isNotBlank())
|
||||
result += "--------expected output--------\n$expectedOutput\n"
|
||||
|
||||
"$result-----expected return value-----\n$expectedResult"
|
||||
}
|
||||
else result
|
||||
}
|
||||
}
|
||||
|
||||
fun parseDocTests(fileName: String, bookMode: Boolean = false): Flow<DocTest> = flow {
|
||||
val book = readAllLines(Paths.get(fileName))
|
||||
var startOffset = 0
|
||||
val block = mutableListOf<String>()
|
||||
var startIndex = 0
|
||||
for ((index, l) in book.withIndex()) {
|
||||
val off = leftMargin(l)
|
||||
when {
|
||||
off < startOffset && startOffset != 0 -> {
|
||||
if (l.isBlank()) {
|
||||
continue
|
||||
}
|
||||
// end of block or just text:
|
||||
if (block.isNotEmpty()) {
|
||||
// check/create block
|
||||
// 2 lines min
|
||||
if (block.size > 1) {
|
||||
// remove prefix
|
||||
for ((i, s) in block.withIndex()) {
|
||||
var x = s
|
||||
// could be tabs :(
|
||||
val initial = leftMargin(x)
|
||||
do {
|
||||
x = x.drop(1)
|
||||
} while (initial - leftMargin(x) != startOffset)
|
||||
block[i] = x
|
||||
}
|
||||
if (bookMode) {
|
||||
emit(
|
||||
DocTest(
|
||||
fileName, startIndex,
|
||||
block.joinToString("\n"),
|
||||
"",
|
||||
"",
|
||||
null,
|
||||
bookMode = true
|
||||
)
|
||||
)
|
||||
}
|
||||
// println(block.joinToString("\n") { "${startIndex + ii++}: $it" })
|
||||
val outStart = block.indexOfFirst { it.startsWith(">>>") }
|
||||
if (outStart < 0) {
|
||||
// println("No output at block from line ${startIndex+1}")
|
||||
} else {
|
||||
var isValid = true
|
||||
val result = mutableListOf<String>()
|
||||
while (block.size > outStart) {
|
||||
val line = block.removeAt(outStart)
|
||||
if (!line.startsWith(">>> ")) {
|
||||
println("invalid output line, must start with '>>> ', block from ${startIndex + 1}: $line")
|
||||
isValid = false
|
||||
break
|
||||
}
|
||||
result.add(line.drop(4))
|
||||
}
|
||||
if (isValid) {
|
||||
emit(
|
||||
DocTest(
|
||||
fileName, startIndex,
|
||||
block.joinToString("\n"),
|
||||
if (result.size > 1)
|
||||
result.dropLast(1).joinToString("") { it + "\n" }
|
||||
else "",
|
||||
result.last()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
// last line '>>>'
|
||||
}
|
||||
block.clear()
|
||||
startOffset = 0
|
||||
}
|
||||
}
|
||||
|
||||
off != 0 && startOffset == 0 -> {
|
||||
// start
|
||||
block.clear()
|
||||
startIndex = index
|
||||
block.add(l)
|
||||
startOffset = off
|
||||
}
|
||||
|
||||
off != 0 -> {
|
||||
block.add(l)
|
||||
}
|
||||
|
||||
off == 0 && startOffset == 0 -> {
|
||||
// skip
|
||||
}
|
||||
|
||||
else -> {
|
||||
throw RuntimeException("Unexpected line: ($off/$startOffset) $l")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
|
||||
suspend fun DocTest.test(context: Context = Context()) {
|
||||
val collectedOutput = StringBuilder()
|
||||
val currentTest = this
|
||||
context.apply {
|
||||
addFn("println") {
|
||||
if( bookMode ) {
|
||||
println("${currentTest.fileNamePart}:${currentTest.line}> ${args.joinToString(" "){it.asStr.value}}")
|
||||
}
|
||||
else {
|
||||
for ((i, a) in args.withIndex()) {
|
||||
if (i > 0) collectedOutput.append(' '); collectedOutput.append(a.asStr.value)
|
||||
collectedOutput.append('\n')
|
||||
}
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
var error: Throwable? = null
|
||||
val result = try {
|
||||
context.eval(code)
|
||||
} catch (e: Throwable) {
|
||||
error = e
|
||||
null
|
||||
}?.inspect()?.replace(Regex("@\\d+"), "@...")
|
||||
|
||||
if (bookMode) {
|
||||
if (error != null) {
|
||||
println("Sample failed: ${this.detailedString}")
|
||||
fail("book sample failed", error)
|
||||
}
|
||||
} else {
|
||||
if (error != null || expectedOutput != collectedOutput.toString() ||
|
||||
expectedResult != result
|
||||
) {
|
||||
System.err.println("\nfailed: ${this.detailedString}")
|
||||
}
|
||||
error?.let {
|
||||
fail(it.message, it)
|
||||
}
|
||||
assertEquals(expectedOutput, collectedOutput.toString(), "script output do not match")
|
||||
assertEquals(expectedResult, result.toString(), "script result does not match")
|
||||
// println("OK: $this")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun runDocTests(fileName: String, bookMode: Boolean = false) {
|
||||
val bookContext = Context()
|
||||
var count = 0
|
||||
parseDocTests(fileName, bookMode).collect { dt ->
|
||||
if (bookMode) dt.test(bookContext)
|
||||
else dt.test()
|
||||
count++
|
||||
}
|
||||
print("completed mdtest: $fileName; ")
|
||||
if (bookMode)
|
||||
println("fragments processed: $count")
|
||||
else
|
||||
println("tests passed: $count")
|
||||
}
|
||||
|
||||
class BookTest {
|
||||
|
||||
@Test
|
||||
fun testsFromTutorial() = runTest {
|
||||
runDocTests("../docs/tutorial.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testsFromMath() = runTest {
|
||||
runDocTests("../docs/math.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testsFromAdvanced() = runTest {
|
||||
runDocTests("../docs/advanced_topics.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testsFromOOP() = runTest {
|
||||
runDocTests("../docs/OOP.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromReal() = runTest {
|
||||
runDocTests("../docs/Real.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromList() = runTest {
|
||||
runDocTests("../docs/List.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromRange() = runTest {
|
||||
runDocTests("../docs/Range.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSampleBooks() = runTest {
|
||||
for (bt in Files.list(Paths.get("../docs/samples")).toList()) {
|
||||
if (bt.extension == "md") {
|
||||
runDocTests(bt.toString(), bookMode = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testArgumentBooks() = runTest {
|
||||
runDocTests("../docs/declaring_arguments.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExceptionsBooks() = runTest {
|
||||
runDocTests("../docs/exceptions_handling.md")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.datetime.Clock
|
||||
import net.sergeych.lyng.Context
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.extension
|
||||
import kotlin.test.Test
|
||||
|
||||
suspend fun executeSampleTests(fileName: String) {
|
||||
val sample = withContext(Dispatchers.IO) {
|
||||
Files.readString(Paths.get(fileName))
|
||||
}
|
||||
runBlocking {
|
||||
val c = Context()
|
||||
val start = Clock.System.now()
|
||||
c.eval(sample)
|
||||
val time = Clock.System.now() - start
|
||||
println("$time: $fileName")
|
||||
// delay(100)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
class SamplesTest {
|
||||
|
||||
@Test
|
||||
fun testSamples() = runBlocking {
|
||||
for (s in Files.list(Paths.get("../docs/samples"))) {
|
||||
if (s.extension == "lyng") executeSampleTests(s.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user