Merge branch 'bug_asmbinary' into testability_steps_1_2_3_again

This commit is contained in:
meisl 2021-07-17 15:08:32 +02:00
commit ac37319d20
12 changed files with 291 additions and 128 deletions

View File

@ -14,10 +14,10 @@ import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
import prog8.optimizer.CallGraph import prog8.optimizer.CallGraph
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
import kotlin.io.path.*
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ -1315,18 +1315,23 @@ $repeatLabel lda $counterVar
when(stmt.directive) { when(stmt.directive) {
"%asminclude" -> { "%asminclude" -> {
// TODO: handle %asminclude with SourceCode // TODO: handle %asminclude with SourceCode
val sourcePath = Path.of(stmt.definingModule().source!!.pathString()) // FIXME: %asminclude inside non-library, non-filesystem module val includedName = stmt.args[0].str!!
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, sourcePath) 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')) assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
} }
"%asmbinary" -> { "%asmbinary" -> {
val includedName = stmt.args[0].str!!
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else "" val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else "" val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
// TODO: handle %asmbinary with SourceCode val sourcePath = Path(stmt.definingModule().source!!.pathString()) // FIXME: %asmbinary inside non-library, non-filesystem module
val sourcePath = Path.of(stmt.definingModule().source!!.pathString()) // FIXME: %asmbinary inside non-library, non-filesystem module val includedPath = sourcePath.resolveSibling(includedName)
val includedSourcePath = sourcePath.resolveSibling(stmt.args[0].str) val pathForAssembler = outputDir // #54: 64tass needs the path *relative to the .asm file*
val relPath = Paths.get("").relativize(includedSourcePath) .absolute() // avoid IllegalArgumentExc due to non-absolute path .relativize(absolute path)
out(" .binary \"$relPath\" $offset $length") .relativize(includedPath)
.normalize() // avoid assembler warnings (-Wportable; only some, not all)
.toString().replace('\\', '/')
out(" .binary \"$pathForAssembler\" $offset $length")
} }
"%breakpoint" -> { "%breakpoint" -> {
val label = "_prog8_breakpoint_${breakpointLabels.size+1}" val label = "_prog8_breakpoint_${breakpointLabels.size+1}"

View File

@ -16,16 +16,32 @@ import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8.compiler.target.ICompilationTarget 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( internal fun CompilationResult.assertSuccess(description: String = ""): CompilationResult {
filename: String, 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, platform: ICompilationTarget,
optimize: Boolean = false optimize: Boolean,
fileDir: Path,
fileName: String,
outputDir: Path = prog8tests.helpers.outputDir
) : CompilationResult { ) : CompilationResult {
val filepath = fixturesDir.resolve(filename) val filepath = fileDir.resolve(fileName)
assumeReadableFile(filepath) assumeReadableFile(filepath)
val result = compileProgram( return compileProgram(
filepath, filepath,
optimize, optimize,
writeAssembly = true, writeAssembly = true,
@ -34,14 +50,30 @@ internal fun compileFixture(
libdirs = listOf(), libdirs = listOf(),
outputDir outputDir
) )
assertTrue(result.success, "compilation should succeed")
return result
} }
val workingDir = Path("").absolute() // Note: Path(".") does NOT work..! /**
val fixturesDir = workingDir.resolve("test/fixtures") * Takes a [sourceText] as a String, writes it to a temporary
val resourcesDir = workingDir.resolve("res") * file and then runs the compiler on that.
val outputDir = workingDir.resolve("build/tmp/test") * @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) { fun assumeReadable(path: Path) {
assertTrue(path.isReadable(), "sanity check: should be readable: ${path.absolute()}") assertTrue(path.isReadable(), "sanity check: should be readable: ${path.absolute()}")
@ -70,13 +102,38 @@ fun sanityCheckDirectories(workingDirName: String? = null) {
} }
fun <A, B, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, combine2: (A, B) -> R) =
sequence {
for (a in dim1)
for (b in dim2)
yield(combine2(a, b))
}.toList()
fun <A, B, C, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, dim3: Iterable<C>, 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 <A, B, C, D, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, dim3: Iterable<C>, dim4: Iterable<D>, 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 { val DummyEncoding = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> { override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
TODO("Not yet implemented") throw Exception("just a dummy - should not be called")
} }
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String { override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
TODO("Not yet implemented") throw Exception("just a dummy - should not be called")
} }
} }

View File

@ -30,7 +30,8 @@ class TestCompilerOnCharLit {
@Test @Test
fun testCharLitAsRomsubArg() { fun testCharLitAsRomsubArg() {
val platform = Cx16Target val platform = Cx16Target
val result = compileFixture("charLitAsRomsubArg.p8", platform) val result = compileFile(platform, optimize = false, fixturesDir, "charLitAsRomsubArg.p8")
.assertSuccess()
val program = result.programAst val program = result.programAst
val startSub = program.entrypoint() val startSub = program.entrypoint()
@ -46,7 +47,8 @@ class TestCompilerOnCharLit {
@Test @Test
fun testCharVarAsRomsubArg() { fun testCharVarAsRomsubArg() {
val platform = Cx16Target val platform = Cx16Target
val result = compileFixture("charVarAsRomsubArg.p8", platform) val result = compileFile(platform, optimize = false, fixturesDir, "charVarAsRomsubArg.p8")
.assertSuccess()
val program = result.programAst val program = result.programAst
val startSub = program.entrypoint() val startSub = program.entrypoint()
@ -73,7 +75,8 @@ class TestCompilerOnCharLit {
@Test @Test
fun testCharConstAsRomsubArg() { fun testCharConstAsRomsubArg() {
val platform = Cx16Target val platform = Cx16Target
val result = compileFixture("charConstAsRomsubArg.p8", platform) val result = compileFile(platform, optimize = false, fixturesDir,"charConstAsRomsubArg.p8")
.assertSuccess()
val program = result.programAst val program = result.programAst
val startSub = program.entrypoint() val startSub = program.entrypoint()

View File

@ -1,17 +1,20 @@
package prog8tests package prog8tests
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestFactory
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Disabled 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 prog8tests.helpers.*
import kotlin.io.path.*
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget import prog8.compiler.target.ICompilationTarget
/** /**
* ATTENTION: this is just kludge! * ATTENTION: this is just kludge!
* They are not really unit tests, but rather tests of the whole process, * 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 // 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) { private fun makeDynamicCompilerTest(name: String, platform: ICompilationTarget, optimize: Boolean) : DynamicTest {
val filepath = examplesDir.resolve("$nameWithoutExt.p8") val searchIn = mutableListOf(examplesDir)
val result = compileProgram( if (platform == Cx16Target) {
filepath, searchIn.add(0, examplesDir.resolve("cx16"))
optimize, }
writeAssembly = true, val filepath = searchIn.map { it.resolve("$name.p8") }.first { it.exists() }
slowCodegenWarnings = false, val displayName = "${examplesDir.relativize(filepath)}: ${platform.name}, optimize=$optimize"
compilationTarget = platform.name, return dynamicTest(displayName) {
libdirs = listOf(), compileProgram(
outputDir filepath,
) optimize,
assertTrue(result.success, writeAssembly = true,
"compilation should succeed; ${platform.name}, optimize=$optimize: \"$filepath\"") 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 @TestFactory
fun test_cxLogo_cx16_noopt() { // @Disabled
testExample("cxlogo", Cx16Target, false) fun onlyC64() = mapCombinations(
} dim1 = listOf(
@Test "balloonflight",
fun test_cxLogo_cx16_opt() { "bdmusic",
testExample("cxlogo", Cx16Target, true) "bdmusic-irq",
} "charset",
@Test "cube3d-sprites",
fun test_cxLogo_c64_noopt() { "plasma",
testExample("cxlogo", C64Target, false) "sprites",
} "turtle-gfx",
@Test "wizzine",
fun test_cxLogo_c64_opt() { ),
testExample("cxlogo", C64Target, true) dim2 = listOf(C64Target),
} dim3 = listOf(false, true),
combine3 = ::makeDynamicCompilerTest
)
@Test @TestFactory
fun test_swirl_cx16_noopt() { // @Disabled
testExample("swirl", Cx16Target, false) fun onlyCx16() = mapCombinations(
} dim1 = listOf(
@Test "vtui/testvtui",
fun test_swirl_cx16_opt() { "amiga",
testExample("swirl", Cx16Target, true) "bobs",
} "cobramk3-gfx",
@Test "colorbars",
fun test_swirl_c64_noopt() { "datetime",
testExample("swirl", C64Target, false) "highresbitmap",
} "kefrenbars",
@Test "mandelbrot-gfx-colors",
fun test_swirl_c64_opt() { "multipalette",
testExample("swirl", C64Target, true) "testgfx2",
} ),
dim2 = listOf(Cx16Target),
@Test dim3 = listOf(false, true),
fun test_animals_cx16_noopt() { combine3 = ::makeDynamicCompilerTest
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)
}
} }

View File

@ -3,6 +3,9 @@ package prog8tests
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.BeforeAll 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.test.*
import kotlin.io.path.* import kotlin.io.path.*
import prog8tests.helpers.* import prog8tests.helpers.*
@ -36,7 +39,8 @@ class TestCompilerOnImportsAndIncludes {
assumeReadableFile(imported) assumeReadableFile(imported)
val platform = Cx16Target val platform = Cx16Target
val result = compileFixture(filepath.name, platform) val result = compileFile(platform, false, fixturesDir, filepath.name)
.assertSuccess()
val program = result.programAst val program = result.programAst
val startSub = program.entrypoint() val startSub = program.entrypoint()
@ -59,7 +63,8 @@ class TestCompilerOnImportsAndIncludes {
assumeReadableFile(included) assumeReadableFile(included)
val platform = Cx16Target val platform = Cx16Target
val result = compileFixture(filepath.name, platform) val result = compileFile(platform, false, fixturesDir, filepath.name)
.assertSuccess()
val program = result.programAst val program = result.programAst
val startSub = program.entrypoint() val startSub = program.entrypoint()
@ -76,4 +81,55 @@ class TestCompilerOnImportsAndIncludes {
assertEquals("foo_bar", lbl1.name) assertEquals("foo_bar", lbl1.name)
assertEquals("start", lbl1.definingScope().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<DynamicTest> =
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"
)
}
}
}

View File

@ -0,0 +1,10 @@
main {
sub start() {
stuff.do_nothing()
}
}
stuff $1000 {
romsub $1000 = do_nothing()
%asmbinary "do_nothing1.bin", 0
}

View File

@ -0,0 +1,10 @@
main {
sub start() {
stuff.do_nothing()
}
}
stuff $1000 {
romsub $1000 = do_nothing()
%asmbinary "subFolder/do_nothing2.bin", 0
}

View File

@ -0,0 +1,10 @@
main {
sub start() {
stuff.do_nothing()
}
}
stuff $1000 {
romsub $1000 = do_nothing()
%asmbinary "i_do_not_exist.bin", 0
}

View File

@ -0,0 +1,10 @@
main {
sub start() {
stuff.do_nothing()
}
}
stuff $1000 {
romsub $1000 = do_nothing()
%asmbinary "subFolder", 0
}

7
compiler/test/fixtures/do_nothing.asm vendored Normal file
View File

@ -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

View File

@ -0,0 +1 @@
`

View File

@ -0,0 +1 @@
`