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