Skip to content

Commit

Permalink
PoC of an unit test library
Browse files Browse the repository at this point in the history
  • Loading branch information
dwursteisen committed Feb 23, 2024
1 parent 8e9345c commit eabdde7
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import com.github.ajalt.clikt.core.Abort
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.file
import com.github.minigdx.tiny.cli.config.GameParameters
import com.github.minigdx.tiny.engine.GameEngine
Expand All @@ -19,10 +21,13 @@ import java.io.File

class RunCommand : CliktCommand(name = "run", help = "Run your game.") {

val gameDirectory by argument(help = "The directory containing all game information")
val gameDirectory by argument(help = "The directory containing all game information.")
.file(mustExist = true, canBeDir = true, canBeFile = false)
.default(File("."))

val test by option(help = "Run tests before running the game.")
.flag()

fun isOracleOrOpenJDK(): Boolean {
val vendor = System.getProperty("java.vendor")?.lowercase()
return vendor?.contains("oracle") == true || vendor?.contains("eclipse") == true || vendor?.contains("openjdk") == true
Expand Down Expand Up @@ -50,6 +55,7 @@ class RunCommand : CliktCommand(name = "run", help = "Run your game.") {
val logger = StdOutLogger("tiny-cli")
val vfs = CommonVirtualFileSystem()
val gameOption = gameParameters.toGameOptions()
.copy(runTests = test)
val gameEngine = GameEngine(
gameOptions = gameOption,
platform = GlfwPlatform(gameOption, logger, vfs, gameDirectory, LwjglGLRender(logger, gameOption)),
Expand All @@ -65,7 +71,7 @@ class RunCommand : CliktCommand(name = "run", help = "Run your game.") {

val data = File("data")
if (data.exists() && data.isDirectory) {
WorkspaceLib.DEFAULT = data.listFiles().map { JvmLocalFile(it.name, data) }
WorkspaceLib.DEFAULT = data.listFiles()?.map { JvmLocalFile(it.name, data) } ?: emptyList()
}
gameEngine.main()
} catch (ex: Exception) {
Expand Down
4 changes: 4 additions & 0 deletions tiny-cli/src/jvmMain/resources/sfx/sfx-0.sfx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tiny-sfx 2 120 255
0142FF 0143FF 0144FF 0145FF 0146FF 0150FF 0155FF 0001FF 0001FF 0001FF 0138FF 0141FF 0148FF 014CFF 0146FF 0138FF 0135FF 0001FF 0001FF 0001FF 0132FF 0133FF 0134FF 0133FF 012EFF 012AFF 0001FF 0133FF 013FFF 0148FF 014BFF 014CFF
0155FF 0156FF 0157FF 0000FF 0155FF 0152FF 0151FF 0150FF 0153FF 0157FF 015AFF 015BFF 015DFF 015DFF 015CFF 015AFF 0155FF 0154FF 0151FF 014FFF 014FFF 0151FF 0157FF 0158FF 0155FF 0154FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF
1 2 1
3 changes: 3 additions & 0 deletions tiny-cli/src/jvmMain/resources/sfx/sfx-1.sfx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tiny-sfx 1 120 255
0422FF 0423FF 0424FF 0425FF 0428FF 042BFF 0000FF 0432FF 0436FF 0438FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF
1
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class GameEngine(
resource.resourceAccess = this
// Game script will be evaluated when the boot script will exit
scripts[resource.index] = resource
resource.isValid(customizeLuaGlobal)
}

ENGINE_GAMESCRIPT -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.github.minigdx.tiny.graphic.ColorPalette
import com.github.minigdx.tiny.input.MouseProject
import com.github.minigdx.tiny.input.Vector2

class GameOptions(
data class GameOptions(
val width: Pixel,
val height: Pixel,
val palette: List<String>,
Expand All @@ -19,6 +19,7 @@ class GameOptions(
val gutter: Pair<Pixel, Pixel> = 10 to 10,
val spriteSize: Pair<Pixel, Pixel> = 8 to 8,
val hideMouseCursor: Boolean = false,
val runTests: Boolean = false,
) : MouseProject {

init {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.github.minigdx.tiny.lua

import com.github.mingdx.tiny.doc.TinyArg
import com.github.mingdx.tiny.doc.TinyCall
import com.github.mingdx.tiny.doc.TinyFunction
import com.github.mingdx.tiny.doc.TinyLib
import com.github.minigdx.tiny.resources.GameScript
import org.luaj.vm2.LuaTable
import org.luaj.vm2.LuaValue
import org.luaj.vm2.lib.OneArgFunction
import org.luaj.vm2.lib.TwoArgFunction

class TestResult(val script: String, val test: String, val passed: Boolean, val reason: String)

@TinyLib("test", "Test method utilities used when tests are run. " +
"See link:#_the_tiny_cli_run_command[Run command]")
class TestLib(private val script: GameScript) : TwoArgFunction() {

private val currentScript = script.name

private var currentTest: String = ""

private var firstFailure: String? = null

open inner class Assertor(
private val default: LuaValue? = null,
private val invert: Boolean = false,
private val message: String = "#1 expected to be equals to #2 but is not!",
) : TwoArgFunction() {
override fun call(arg1: LuaValue, arg2: LuaValue): LuaValue {
val luaValue = default ?: arg2
val result = arg1.eq_b(luaValue)
return if (invert && !result) {
BTRUE
} else if (result) {
BTRUE
} else {
if (firstFailure == null) {
val msg = message
.replace("#1", arg1.tojstring())
.replace("#2", luaValue.tojstring())
firstFailure = msg
}
BFALSE
}
}
}

@TinyFunction(name = "eq", description = "Assert that `expected` and `actual` are equals")
inner class isEqual : Assertor() {

@TinyCall("Assert that `expected` and `actual` are equals")
override fun call(@TinyArg("expected") arg1: LuaValue, @TinyArg("actual") arg2: LuaValue): LuaValue {
return super.call(arg1, arg2)
}
}

@TinyFunction(name = "neq", description = "Assert that `expected` and `actual` are __not__ equals")
inner class isNotEquals : Assertor(invert = true) {
@TinyCall("Assert that `expected` and `actual` are not equals")
override fun call(@TinyArg("expected") arg1: LuaValue, @TinyArg("actual") arg2: LuaValue): LuaValue {
return super.call(arg1, arg2)
}
}

@TinyFunction(name = "t", description = "Assert that `actual` is true")
inner class isTrue : Assertor(default = BTRUE) {
@TinyCall("Assert that `actual` is true")
override fun call(@TinyArg("actual") arg: LuaValue): LuaValue {
return super.call(arg)
}
}

@TinyFunction(name = "t", description = "Assert that `actual` is false")
inner class isFalse : Assertor(default = BFALSE) {

@TinyCall("Assert that `actual` is false")
override fun call(@TinyArg("actual") arg: LuaValue): LuaValue {
return super.call(arg)
}
}
override fun call(arg1: LuaValue, arg2: LuaValue): LuaValue {
val test = LuaTable()
test["create"] = create()
test["eq"] = isEqual()
test["neq"] = isNotEquals()
test["t"] = isTrue()
test["f"] = isFalse()
// test["advance"] = ...
test["record"] = record()
test["screen"] = screen()

arg2.set("test", test)
arg2.get("package").get("loaded").set("test", test)
return test
}

@TinyFunction("Create a new `test` named `name`")
inner class create : TwoArgFunction() {
@TinyCall("Create a new `test` named `name`")
override fun call(
@TinyArg("name", description = "The name of the test") arg1: LuaValue,
@TinyArg("test", description = "The test: it has to be a function") arg2: LuaValue,
): LuaValue {
currentTest = arg1.tojstring()

firstFailure = null
arg2.call()
firstFailure
if (firstFailure == null) {
script.testResults.add(TestResult(currentScript, currentTest, true, ""))
} else {
script.testResults.add(TestResult(currentScript, currentTest, false, firstFailure!!))
}

return NIL
}
}

inner class record : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
// TODO: get the test current name
TODO("Not yet implemented")
}
}

inner class screen : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
TODO("Not yet implemented")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.github.minigdx.tiny.engine.Exit
import com.github.minigdx.tiny.engine.GameOptions
import com.github.minigdx.tiny.engine.GameResourceAccess
import com.github.minigdx.tiny.input.InputHandler
import com.github.minigdx.tiny.log.Logger
import com.github.minigdx.tiny.lua.CtrlLib
import com.github.minigdx.tiny.lua.DebugLib
import com.github.minigdx.tiny.lua.GfxLib
Expand All @@ -16,6 +17,8 @@ import com.github.minigdx.tiny.lua.SfxLib
import com.github.minigdx.tiny.lua.ShapeLib
import com.github.minigdx.tiny.lua.SprLib
import com.github.minigdx.tiny.lua.StdLib
import com.github.minigdx.tiny.lua.TestLib
import com.github.minigdx.tiny.lua.TestResult
import com.github.minigdx.tiny.lua.TinyBaseLib
import com.github.minigdx.tiny.lua.TinyLib
import com.github.minigdx.tiny.lua.Vec2Lib
Expand Down Expand Up @@ -46,6 +49,7 @@ class GameScript(
val gameOptions: GameOptions,
val inputHandler: InputHandler,
val platform: Platform,
val logger: Logger,
override val type: ResourceType,
) : GameResource {

Expand All @@ -68,6 +72,8 @@ class GameScript(

private val tinyLib: TinyLib = TinyLib()

internal val testResults = mutableListOf<TestResult>()

class State(val args: LuaValue)

private fun createLuaGlobals(customizeLuaGlobal: GameResourceAccess.(Globals) -> Unit, forValidation: Boolean = false): Globals = Globals().apply {
Expand All @@ -94,6 +100,7 @@ class GameScript(
load(JuiceLib())
load(NotesLib())
load(WorkspaceLib(platform = platform))
load(TestLib(this@GameScript))

this@GameScript.resourceAccess.customizeLuaGlobal(this)

Expand All @@ -105,6 +112,30 @@ class GameScript(
with(createLuaGlobals(customizeLuaGlobal, forValidation = true)) {
load(content.decodeToString()).call()
get("_init").nullIfNil()?.callSuspend(valueOf(gameOptions.width), valueOf(gameOptions.height))
if (gameOptions.runTests) {
gameOptions.gameScripts.map { name ->
// use the new content for the game script evaluated
if (name == this@GameScript.name) {
content.decodeToString()
} else {
// use the cached content for the script not updated.
resourceAccess.script(name)?.content!!.decodeToString()
}
}.forEach { scriptContent ->
val globalForTest = createLuaGlobals(customizeLuaGlobal, forValidation = true)
println(scriptContent)
globalForTest.load(scriptContent).call()
globalForTest.get("_test").callSuspend()
}
logger.info("TEST") { "\uFE0F === Ran ${testResults.size} tests ===" }
testResults.forEach {
if (it.passed) {
logger.info("TEST") { "${it.script} - ${it.test}" }
} else {
logger.info("TEST") { "\uD83D\uDD34 ${it.script} - ${it.test}: ${it.reason}" }
}
}
}
}
return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ class ResourceFactory(
canUseJarPrefix = !protectedResources.contains(resourceType),
),
).map { content ->
GameScript(version++, index, name, gameOptions, inputHandler, platform, resourceType).apply {
GameScript(version++, index, name, gameOptions, inputHandler, platform, logger, resourceType).apply {
this.content = content
}
}.onEach {
Expand Down

0 comments on commit eabdde7

Please sign in to comment.