diff --git a/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt b/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt index 95c3ae71a..0fe3ad9fa 100644 --- a/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt @@ -14,10 +14,10 @@ import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen import prog8.optimizer.CallGraph import java.nio.file.Path -import java.nio.file.Paths import java.time.LocalDate import java.time.LocalDateTime import java.util.* +import kotlin.io.path.* import kotlin.math.absoluteValue @@ -1315,18 +1315,23 @@ $repeatLabel lda $counterVar when(stmt.directive) { "%asminclude" -> { // TODO: handle %asminclude with SourceCode - val sourcePath = Path.of(stmt.definingModule().source!!.pathString()) // FIXME: %asminclude inside non-library, non-filesystem module - val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, sourcePath) + val includedName = stmt.args[0].str!! + val sourcePath = Path(stmt.definingModule().source!!.pathString()) // FIXME: %asminclude inside non-library, non-filesystem module + val sourcecode = loadAsmIncludeFile(includedName, sourcePath) assemblyLines.add(sourcecode.trimEnd().trimStart('\n')) } "%asmbinary" -> { + val includedName = stmt.args[0].str!! val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else "" val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else "" - // TODO: handle %asmbinary with SourceCode - val sourcePath = Path.of(stmt.definingModule().source!!.pathString()) // FIXME: %asmbinary inside non-library, non-filesystem module - val includedSourcePath = sourcePath.resolveSibling(stmt.args[0].str) - val relPath = Paths.get("").relativize(includedSourcePath) - out(" .binary \"$relPath\" $offset $length") + val sourcePath = Path(stmt.definingModule().source!!.pathString()) // FIXME: %asmbinary inside non-library, non-filesystem module + val includedPath = sourcePath.resolveSibling(includedName) + val pathForAssembler = outputDir // #54: 64tass needs the path *relative to the .asm file* + .absolute() // avoid IllegalArgumentExc due to non-absolute path .relativize(absolute path) + .relativize(includedPath) + .normalize() // avoid assembler warnings (-Wportable; only some, not all) + .toString().replace('\\', '/') + out(" .binary \"$pathForAssembler\" $offset $length") } "%breakpoint" -> { val label = "_prog8_breakpoint_${breakpointLabels.size+1}" diff --git a/compiler/test/Helpers.kt b/compiler/test/Helpers.kt index cecef98af..0ea41bf05 100644 --- a/compiler/test/Helpers.kt +++ b/compiler/test/Helpers.kt @@ -16,16 +16,32 @@ import prog8.compiler.CompilationResult import prog8.compiler.compileProgram import prog8.compiler.target.ICompilationTarget -// TODO: find a way to share with compiler/test/Helpers.kt, while still being able to amend it (-> compileFixture(..)) +// TODO: find a way to share with compilerAst/test/Helpers.kt, while still being able to amend it (-> compileFile(..)) -internal fun compileFixture( - filename: String, +internal fun CompilationResult.assertSuccess(description: String = ""): CompilationResult { + assertTrue(success, "expected successful compilation but failed $description") + return this +} + +internal fun CompilationResult.assertFailure(description: String = ""): CompilationResult { + assertFalse(success, "expected failure to compile but succeeded $description") + return this +} + +/** + * @see CompilationResult.assertSuccess + * @see CompilationResult.assertFailure + */ +internal fun compileFile( platform: ICompilationTarget, - optimize: Boolean = false + optimize: Boolean, + fileDir: Path, + fileName: String, + outputDir: Path = prog8tests.helpers.outputDir ) : CompilationResult { - val filepath = fixturesDir.resolve(filename) + val filepath = fileDir.resolve(fileName) assumeReadableFile(filepath) - val result = compileProgram( + return compileProgram( filepath, optimize, writeAssembly = true, @@ -34,14 +50,30 @@ internal fun compileFixture( libdirs = listOf(), outputDir ) - assertTrue(result.success, "compilation should succeed") - return result } -val workingDir = Path("").absolute() // Note: Path(".") does NOT work..! -val fixturesDir = workingDir.resolve("test/fixtures") -val resourcesDir = workingDir.resolve("res") -val outputDir = workingDir.resolve("build/tmp/test") +/** + * Takes a [sourceText] as a String, writes it to a temporary + * file and then runs the compiler on that. + * @see compileFile + */ +internal fun compileText( + platform: ICompilationTarget, + optimize: Boolean, + sourceText: String +) : CompilationResult { + val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8") + // we don't assumeNotExists(filePath) - should be ok to just overwrite it + filePath.toFile().writeText(sourceText) + return compileFile(platform, optimize, filePath.parent, filePath.name) +} + + + +val workingDir : Path = Path("").absolute() // Note: Path(".") does NOT work..! +val fixturesDir : Path = workingDir.resolve("test/fixtures") +val resourcesDir : Path = workingDir.resolve("res") +val outputDir : Path = workingDir.resolve("build/tmp/test") fun assumeReadable(path: Path) { assertTrue(path.isReadable(), "sanity check: should be readable: ${path.absolute()}") @@ -70,13 +102,38 @@ fun sanityCheckDirectories(workingDirName: String? = null) { } +fun mapCombinations(dim1: Iterable, dim2: Iterable, combine2: (A, B) -> R) = + sequence { + for (a in dim1) + for (b in dim2) + yield(combine2(a, b)) + }.toList() + +fun mapCombinations(dim1: Iterable, dim2: Iterable, dim3: Iterable, combine3: (A, B, C) -> R) = + sequence { + for (a in dim1) + for (b in dim2) + for (c in dim3) + yield(combine3(a, b, c)) + }.toList() + +fun mapCombinations(dim1: Iterable, dim2: Iterable, dim3: Iterable, dim4: Iterable, combine4: (A, B, C, D) -> R) = + sequence { + for (a in dim1) + for (b in dim2) + for (c in dim3) + for (d in dim4) + yield(combine4(a, b, c, d)) + }.toList() + + val DummyEncoding = object : IStringEncoding { override fun encodeString(str: String, altEncoding: Boolean): List { - TODO("Not yet implemented") + throw Exception("just a dummy - should not be called") } override fun decodeString(bytes: List, altEncoding: Boolean): String { - TODO("Not yet implemented") + throw Exception("just a dummy - should not be called") } } diff --git a/compiler/test/TestCompilerOnCharLit.kt b/compiler/test/TestCompilerOnCharLit.kt index 9773aa000..d53beb743 100644 --- a/compiler/test/TestCompilerOnCharLit.kt +++ b/compiler/test/TestCompilerOnCharLit.kt @@ -30,7 +30,8 @@ class TestCompilerOnCharLit { @Test fun testCharLitAsRomsubArg() { val platform = Cx16Target - val result = compileFixture("charLitAsRomsubArg.p8", platform) + val result = compileFile(platform, optimize = false, fixturesDir, "charLitAsRomsubArg.p8") + .assertSuccess() val program = result.programAst val startSub = program.entrypoint() @@ -46,7 +47,8 @@ class TestCompilerOnCharLit { @Test fun testCharVarAsRomsubArg() { val platform = Cx16Target - val result = compileFixture("charVarAsRomsubArg.p8", platform) + val result = compileFile(platform, optimize = false, fixturesDir, "charVarAsRomsubArg.p8") + .assertSuccess() val program = result.programAst val startSub = program.entrypoint() @@ -73,7 +75,8 @@ class TestCompilerOnCharLit { @Test fun testCharConstAsRomsubArg() { val platform = Cx16Target - val result = compileFixture("charConstAsRomsubArg.p8", platform) + val result = compileFile(platform, optimize = false, fixturesDir,"charConstAsRomsubArg.p8") + .assertSuccess() val program = result.programAst val startSub = program.entrypoint() diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 56932460a..361d5f174 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -1,17 +1,20 @@ package prog8tests import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.Disabled -import kotlin.test.* +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.DynamicTest +import org.junit.jupiter.api.DynamicTest.dynamicTest import prog8tests.helpers.* +import kotlin.io.path.* import prog8.compiler.compileProgram import prog8.compiler.target.C64Target import prog8.compiler.target.Cx16Target import prog8.compiler.target.ICompilationTarget + /** * ATTENTION: this is just kludge! * They are not really unit tests, but rather tests of the whole process, @@ -30,105 +33,95 @@ class TestCompilerOnExamples { // TODO: make assembly stage testable - in case of failure (eg of 64tass) it Process.exit s - private fun testExample(nameWithoutExt: String, platform: ICompilationTarget, optimize: Boolean) { - val filepath = examplesDir.resolve("$nameWithoutExt.p8") - val result = compileProgram( - filepath, - optimize, - writeAssembly = true, - slowCodegenWarnings = false, - compilationTarget = platform.name, - libdirs = listOf(), - outputDir - ) - assertTrue(result.success, - "compilation should succeed; ${platform.name}, optimize=$optimize: \"$filepath\"") + private fun makeDynamicCompilerTest(name: String, platform: ICompilationTarget, optimize: Boolean) : DynamicTest { + val searchIn = mutableListOf(examplesDir) + if (platform == Cx16Target) { + searchIn.add(0, examplesDir.resolve("cx16")) + } + val filepath = searchIn.map { it.resolve("$name.p8") }.first { it.exists() } + val displayName = "${examplesDir.relativize(filepath)}: ${platform.name}, optimize=$optimize" + return dynamicTest(displayName) { + compileProgram( + filepath, + optimize, + writeAssembly = true, + slowCodegenWarnings = false, + compilationTarget = platform.name, + libdirs = listOf(), + outputDir + ).assertSuccess("; $displayName") + } } + @TestFactory + @Disabled + fun bothCx16AndC64() = mapCombinations( + dim1 = listOf( + "animals", + "balls", + "cube3d", + "cube3d-float", + "cube3d-gfx", + "cxlogo", + "dirlist", + "fibonacci", + "line-circle-gfx", + "line-circle-txt", + "mandelbrot", + "mandelbrot-gfx", + "numbergame", + "primes", + "rasterbars", + "screencodes", + "sorting", + "swirl", + "swirl-float", + "tehtriz", + "test", + "textelite", + ), + dim2 = listOf(Cx16Target, C64Target), + dim3 = listOf(false, true), + combine3 = ::makeDynamicCompilerTest + ) - @Test - fun test_cxLogo_cx16_noopt() { - testExample("cxlogo", Cx16Target, false) - } - @Test - fun test_cxLogo_cx16_opt() { - testExample("cxlogo", Cx16Target, true) - } - @Test - fun test_cxLogo_c64_noopt() { - testExample("cxlogo", C64Target, false) - } - @Test - fun test_cxLogo_c64_opt() { - testExample("cxlogo", C64Target, true) - } + @TestFactory +// @Disabled + fun onlyC64() = mapCombinations( + dim1 = listOf( + "balloonflight", + "bdmusic", + "bdmusic-irq", + "charset", + "cube3d-sprites", + "plasma", + "sprites", + "turtle-gfx", + "wizzine", + ), + dim2 = listOf(C64Target), + dim3 = listOf(false, true), + combine3 = ::makeDynamicCompilerTest + ) - @Test - fun test_swirl_cx16_noopt() { - testExample("swirl", Cx16Target, false) - } - @Test - fun test_swirl_cx16_opt() { - testExample("swirl", Cx16Target, true) - } - @Test - fun test_swirl_c64_noopt() { - testExample("swirl", C64Target, false) - } - @Test - fun test_swirl_c64_opt() { - testExample("swirl", C64Target, true) - } - - @Test - fun test_animals_cx16_noopt() { - testExample("animals", Cx16Target, false) - } - @Test - fun test_animals_cx16_opt() { - testExample("animals", Cx16Target, true) - } - @Test - fun test_animals_c64_noopt() { - testExample("animals", C64Target, false) - } - @Test - fun test_animals_c64_opt() { - testExample("animals", C64Target, true) - } - - @Test - fun test_tehtriz_cx16_noopt() { - testExample("cx16/tehtriz", Cx16Target, false) - } - @Test - fun test_tehtriz_cx16_opt() { - testExample("cx16/tehtriz", Cx16Target, true) - } - @Test - fun test_tehtriz_c64_noopt() { - testExample("tehtriz", C64Target, false) - } - @Test - fun test_tehtriz_c64_opt() { - testExample("tehtriz", C64Target, true) - } - - // textelite.p8 is the largest example (~36KB) - @Test - fun test_textelite_cx16_noopt() { - testExample("textelite", Cx16Target, false) - } - @Test - fun test_textelite_cx16_opt() { - testExample("textelite", Cx16Target, true) - } - @Test - fun test_textelite_c64_noopt() { - testExample("textelite", C64Target, false) - } - @Test - fun test_textelite_c64_opt() { - testExample("textelite", C64Target, true) - } + @TestFactory +// @Disabled + fun onlyCx16() = mapCombinations( + dim1 = listOf( + "vtui/testvtui", + "amiga", + "bobs", + "cobramk3-gfx", + "colorbars", + "datetime", + "highresbitmap", + "kefrenbars", + "mandelbrot-gfx-colors", + "multipalette", + "testgfx2", + ), + dim2 = listOf(Cx16Target), + dim3 = listOf(false, true), + combine3 = ::makeDynamicCompilerTest + ) } diff --git a/compiler/test/TestCompilerOnImportsAndIncludes.kt b/compiler/test/TestCompilerOnImportsAndIncludes.kt index 4ba084a39..d166f92d3 100644 --- a/compiler/test/TestCompilerOnImportsAndIncludes.kt +++ b/compiler/test/TestCompilerOnImportsAndIncludes.kt @@ -3,6 +3,9 @@ package prog8tests import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.Test import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.TestFactory +import org.junit.jupiter.api.DynamicTest +import org.junit.jupiter.api.DynamicTest.dynamicTest import kotlin.test.* import kotlin.io.path.* import prog8tests.helpers.* @@ -36,7 +39,8 @@ class TestCompilerOnImportsAndIncludes { assumeReadableFile(imported) val platform = Cx16Target - val result = compileFixture(filepath.name, platform) + val result = compileFile(platform, false, fixturesDir, filepath.name) + .assertSuccess() val program = result.programAst val startSub = program.entrypoint() @@ -59,7 +63,8 @@ class TestCompilerOnImportsAndIncludes { assumeReadableFile(included) val platform = Cx16Target - val result = compileFixture(filepath.name, platform) + val result = compileFile(platform, false, fixturesDir, filepath.name) + .assertSuccess() val program = result.programAst val startSub = program.entrypoint() @@ -76,4 +81,55 @@ class TestCompilerOnImportsAndIncludes { assertEquals("foo_bar", lbl1.name) assertEquals("start", lbl1.definingScope().name) } -} + + @Test + fun testAsmbinaryDirectiveWithNonExistingFile() { + val p8Path = fixturesDir.resolve("asmbinaryNonExisting.p8") + val binPath = fixturesDir.resolve("i_do_not_exist.bin") + assumeReadableFile(p8Path) + assumeNotExists(binPath) + + compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir) + .assertFailure() + } + + @Test + fun testAsmbinaryDirectiveWithNonReadableFile() { + val p8Path = fixturesDir.resolve("asmbinaryNonReadable.p8") + val binPath = fixturesDir.resolve("subFolder") + assumeReadableFile(p8Path) + assumeDirectory(binPath) + + compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir) + .assertFailure() + } + + @TestFactory + fun asmbinaryDirectiveWithExistingBinFile(): Iterable = + listOf( + Triple("same ", "asmBinaryFromSameFolder.p8", "do_nothing1.bin"), + Triple("sub", "asmBinaryFromSubFolder.p8", "subFolder/do_nothing2.bin"), + ).map { + val (where, p8Str, binStr) = it + val p8Path = fixturesDir.resolve(p8Str) + val binPath = fixturesDir.resolve(binStr) + val displayName = "%asmbinary from ${where}folder" + dynamicTest(displayName) { + assumeReadableFile(p8Path) + assumeReadableFile(binPath) + assertNotEquals( // the bug we're testing for (#54) was hidden if outputDir == workinDir + workingDir.normalize().toAbsolutePath(), + outputDir.normalize().toAbsolutePath(), + "sanity check: workingDir and outputDir should not be the same folder") + + compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir) + .assertSuccess( + "argument to assembler directive .binary " + + "should be relative to the generated .asm file (in output dir), " + + "NOT relative to .p8 neither current working dir" + ) + } + } + + } + diff --git a/compiler/test/fixtures/asmBinaryFromSameFolder.p8 b/compiler/test/fixtures/asmBinaryFromSameFolder.p8 new file mode 100644 index 000000000..d48a8c6b0 --- /dev/null +++ b/compiler/test/fixtures/asmBinaryFromSameFolder.p8 @@ -0,0 +1,10 @@ +main { + sub start() { + stuff.do_nothing() + } +} + +stuff $1000 { + romsub $1000 = do_nothing() + %asmbinary "do_nothing1.bin", 0 +} \ No newline at end of file diff --git a/compiler/test/fixtures/asmBinaryFromSubFolder.p8 b/compiler/test/fixtures/asmBinaryFromSubFolder.p8 new file mode 100644 index 000000000..5b48b7892 --- /dev/null +++ b/compiler/test/fixtures/asmBinaryFromSubFolder.p8 @@ -0,0 +1,10 @@ +main { + sub start() { + stuff.do_nothing() + } +} + +stuff $1000 { + romsub $1000 = do_nothing() + %asmbinary "subFolder/do_nothing2.bin", 0 +} \ No newline at end of file diff --git a/compiler/test/fixtures/asmBinaryNonExisting.p8 b/compiler/test/fixtures/asmBinaryNonExisting.p8 new file mode 100644 index 000000000..2ff4e2f46 --- /dev/null +++ b/compiler/test/fixtures/asmBinaryNonExisting.p8 @@ -0,0 +1,10 @@ +main { + sub start() { + stuff.do_nothing() + } +} + +stuff $1000 { + romsub $1000 = do_nothing() + %asmbinary "i_do_not_exist.bin", 0 +} \ No newline at end of file diff --git a/compiler/test/fixtures/asmBinaryNonReadable.p8 b/compiler/test/fixtures/asmBinaryNonReadable.p8 new file mode 100644 index 000000000..3b7f63bfc --- /dev/null +++ b/compiler/test/fixtures/asmBinaryNonReadable.p8 @@ -0,0 +1,10 @@ +main { + sub start() { + stuff.do_nothing() + } +} + +stuff $1000 { + romsub $1000 = do_nothing() + %asmbinary "subFolder", 0 +} \ No newline at end of file diff --git a/compiler/test/fixtures/do_nothing.asm b/compiler/test/fixtures/do_nothing.asm new file mode 100644 index 000000000..6bfed22b6 --- /dev/null +++ b/compiler/test/fixtures/do_nothing.asm @@ -0,0 +1,7 @@ +; this is the source code for do_nothing1.bin and subFolder/do_nothing2.bin +; command lines: +; 64tass --ascii --nostart do_nothing.asm --output do_nothing1.bin +; 64tass --ascii --nostart do_nothing.asm --output subFolder/do_nothing2.bin +*=0 + rts + diff --git a/compiler/test/fixtures/do_nothing1.bin b/compiler/test/fixtures/do_nothing1.bin new file mode 100644 index 000000000..64845fb76 --- /dev/null +++ b/compiler/test/fixtures/do_nothing1.bin @@ -0,0 +1 @@ +` \ No newline at end of file diff --git a/compiler/test/fixtures/subFolder/do_nothing2.bin b/compiler/test/fixtures/subFolder/do_nothing2.bin new file mode 100644 index 000000000..64845fb76 --- /dev/null +++ b/compiler/test/fixtures/subFolder/do_nothing2.bin @@ -0,0 +1 @@ +` \ No newline at end of file