mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
316 lines
16 KiB
Kotlin
316 lines
16 KiB
Kotlin
package prog8tests.compiler
|
|
|
|
import com.github.michaelbull.result.getErrorOrElse
|
|
import com.github.michaelbull.result.getOrElse
|
|
import io.kotest.assertions.throwables.shouldThrow
|
|
import io.kotest.assertions.withClue
|
|
import io.kotest.core.spec.style.FunSpec
|
|
import io.kotest.engine.spec.tempdir
|
|
import io.kotest.matchers.collections.shouldBeIn
|
|
import io.kotest.matchers.shouldBe
|
|
import io.kotest.matchers.string.shouldContain
|
|
import io.kotest.matchers.string.shouldStartWith
|
|
import prog8.ast.Program
|
|
import prog8.ast.SyntaxError
|
|
import prog8.code.INTERNED_STRINGS_MODULENAME
|
|
import prog8.code.PROG8_CONTAINER_MODULES
|
|
import prog8.code.core.IErrorReporter
|
|
import prog8.code.source.ImportFileSystem
|
|
import prog8.code.source.SourceCode
|
|
import prog8.code.target.VMTarget
|
|
import prog8.compiler.ModuleImporter
|
|
import prog8.parser.MultipleParseErrors
|
|
import prog8.parser.Prog8Parser
|
|
import prog8tests.helpers.*
|
|
import kotlin.io.path.*
|
|
|
|
|
|
class TestModuleImporter: FunSpec({
|
|
val count = listOf("1st", "2nd", "3rd", "4th", "5th")
|
|
|
|
lateinit var program: Program
|
|
|
|
beforeTest {
|
|
program = Program("foo", DummyFunctions, VMTarget())
|
|
}
|
|
|
|
fun makeImporter(errors: IErrorReporter? = null, searchIn: Iterable<String>) =
|
|
ModuleImporter(program, VMTarget(), errors ?: ErrorReporterForTests(false), searchIn.toList(), emptyList(), Path(".").absolute(), false)
|
|
|
|
fun makeImporter(errors: IErrorReporter?, vararg searchIn: String): ModuleImporter {
|
|
return makeImporter(errors, searchIn.asList())
|
|
}
|
|
|
|
context("ImportModule") {
|
|
|
|
context("WithInvalidPath") {
|
|
test("testNonexisting") {
|
|
val dirRel = assumeDirectory(".", workingDir.relativize(fixturesDir))
|
|
val importer = makeImporter(null, dirRel.invariantSeparatorsPathString)
|
|
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
|
|
val srcPathAbs = srcPathRel.absolute()
|
|
val error1 = importer.importMainModule(srcPathRel).getErrorOrElse { error("should have import error") }
|
|
withClue(".file should be normalized") {
|
|
"${error1.file}" shouldBe "${error1.file.normalize()}"
|
|
}
|
|
withClue(".file should point to specified path") {
|
|
error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
|
}
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
|
val error2 = importer.importMainModule(srcPathAbs).getErrorOrElse { error("should have import error") }
|
|
withClue(".file should be normalized") {
|
|
"${error2.file}" shouldBe "${error2.file.normalize()}"
|
|
}
|
|
withClue(".file should point to specified path") {
|
|
error2.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
|
}
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
|
}
|
|
|
|
test("testDirectory") {
|
|
val srcPathRel = assumeDirectory(workingDir.relativize(fixturesDir))
|
|
val srcPathAbs = srcPathRel.absolute()
|
|
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
|
|
val importer = makeImporter(null, searchIn)
|
|
|
|
shouldThrow<FileSystemException> { importer.importMainModule(srcPathRel) }
|
|
.let {
|
|
withClue(".file should be normalized") {
|
|
"${it.file}" shouldBe "${it.file.normalize()}"
|
|
}
|
|
withClue(".file should point to specified path") {
|
|
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
|
}
|
|
}
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
|
|
|
shouldThrow<FileSystemException> { importer.importMainModule(srcPathAbs) }
|
|
.let {
|
|
withClue(".file should be normalized") {
|
|
"${it.file}" shouldBe "${it.file.normalize()}"
|
|
}
|
|
withClue(".file should point to specified path") {
|
|
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
|
}
|
|
}
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
|
}
|
|
}
|
|
|
|
context("WithValidPath") {
|
|
|
|
test("testAbsolute") {
|
|
val searchIn = listOf(
|
|
workingDir.relativize(fixturesDir), // we do want a dot "." in front
|
|
).map { it.invariantSeparatorsPathString }
|
|
val importer = makeImporter(null, searchIn)
|
|
val fileName = "ast_simple_main.p8"
|
|
val path = assumeReadableFile(searchIn[0], fileName)
|
|
|
|
val module = importer.importMainModule(path.absolute()).getOrElse { throw it }
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1
|
|
module shouldBeIn program.modules
|
|
module.program shouldBe program
|
|
}
|
|
|
|
test("testRelativeToWorkingDir") {
|
|
val searchIn = listOf(
|
|
workingDir.relativize(fixturesDir), // we do want a dot "." in front
|
|
).map { it.invariantSeparatorsPathString }
|
|
val importer = makeImporter(null, searchIn)
|
|
val fileName = "ast_simple_main.p8"
|
|
val path = assumeReadableFile(searchIn[0], fileName)
|
|
withClue("sanity check: path should NOT be absolute") {
|
|
path.isAbsolute shouldBe false
|
|
}
|
|
|
|
val module = importer.importMainModule(path).getOrElse { throw it }
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1
|
|
module shouldBeIn program.modules
|
|
module.program shouldBe program
|
|
}
|
|
|
|
test("testRelativeTo1stDirInSearchList") {
|
|
val searchIn = (workingDir.relativize(fixturesDir)).invariantSeparatorsPathString
|
|
val importer = makeImporter(null, searchIn)
|
|
val fileName = "ast_simple_main.p8"
|
|
val path = Path(".", fileName)
|
|
assumeReadableFile(searchIn, path)
|
|
|
|
val module = importer.importMainModule(path).getOrElse { throw it }
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1
|
|
module shouldBeIn program.modules
|
|
module.program shouldBe program
|
|
}
|
|
|
|
context("WithBadFile") {
|
|
test("testWithSyntaxError") {
|
|
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
|
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
|
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
|
|
|
val act = { importer.importMainModule(srcPath) }
|
|
|
|
repeat(2) { n -> withClue(count[n] + " call") {
|
|
shouldThrow<MultipleParseErrors> { act() }.let { e ->
|
|
e.errors.first().position.file shouldBe SourceCode.relative(srcPath).toString()
|
|
withClue("line; should be 1-based") { e.errors.first().position.line shouldBe 2 }
|
|
withClue("startCol; should be 1-based") { e.errors.first().position.startCol shouldBe 5 }
|
|
withClue("endCol; should be 1-based") { e.errors.first().position.endCol shouldBe 7 }
|
|
}
|
|
}
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
|
}
|
|
}
|
|
|
|
fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
|
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
|
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
|
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
|
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
|
|
|
val act = { importer.importMainModule(importing) }
|
|
|
|
repeat(repetitions) { n -> withClue(count[n] + " call") {
|
|
shouldThrow<MultipleParseErrors> { act() }.let { e ->
|
|
e.errors.first().position.file shouldBe SourceCode.relative(imported).toString()
|
|
withClue("line; should be 1-based") { e.errors.first().position.line shouldBe 2 }
|
|
withClue("startCol; should be 1-based") { e.errors.first().position.startCol shouldBe 5 }
|
|
withClue("endCol; should be 1-based") { e.errors.first().position.endCol shouldBe 7 }
|
|
}
|
|
}
|
|
withClue("imported module with error in it should not be present") { program.modules.size shouldBe PROG8_CONTAINER_MODULES.size }
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
|
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
|
|
}
|
|
}
|
|
|
|
test("testImportingFileWithSyntaxError_once") {
|
|
doTestImportingFileWithSyntaxError(1)
|
|
}
|
|
|
|
test("testImportingFileWithSyntaxError_twice") {
|
|
doTestImportingFileWithSyntaxError(2)
|
|
}
|
|
}
|
|
|
|
context("WithBadImportArgument") {
|
|
test("import directive with expression argument gives SyntaxError instead of crashing") {
|
|
val dir = tempdir().toPath()
|
|
val srcPath = dir.resolve("badimport.p8")
|
|
srcPath.toFile().writeText("""
|
|
%import "textio" as t
|
|
main {
|
|
sub start() {
|
|
}
|
|
}
|
|
""".trimIndent())
|
|
val importer = makeImporter(null, dir.invariantSeparatorsPathString)
|
|
shouldThrow<SyntaxError> { importer.importMainModule(srcPath) }
|
|
.message shouldContain "invalid import directive"
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
context("ImportFromResourceModule") {
|
|
test("neighbor directory search is skipped for modules loaded from internal library resources") {
|
|
val importer = makeImporter(null)
|
|
val resourceModule = Prog8Parser.parseModule(ImportFileSystem.getResource("/prog8lib/cx16/textio.p8"), VMTarget())
|
|
resourceModule.position.file shouldStartWith "library:"
|
|
|
|
val (result, searched) = importer.getModuleFromFile("syslib", resourceModule)
|
|
withClue("syslib only exists as an internal library resource, not on the filesystem") {
|
|
result.getErrorOrElse { error("expected a filesystem search failure") }.file.name shouldBe "syslib"
|
|
}
|
|
withClue("no filesystem search location may be derived from the library: origin: $searched") {
|
|
searched.none { it.toString().contains("library:") } shouldBe true
|
|
}
|
|
}
|
|
}
|
|
|
|
context("ImportLibraryModule") {
|
|
context("WithInvalidName") {
|
|
test("testWithNonExistingName") {
|
|
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
|
val errors = ErrorReporterForTests(false)
|
|
val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString)
|
|
val filenameNoExt = assumeNotExists(fixturesDir, "i_do_not_exist").name
|
|
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
|
|
|
|
repeat(2) { n ->
|
|
val result = importer.importImplicitLibraryModule(filenameNoExt)
|
|
withClue(count[n] + " call / NO .p8 extension") { result shouldBe null }
|
|
withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false }
|
|
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist"
|
|
errors.report()
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
|
|
|
val result2 = importer.importImplicitLibraryModule(filenameWithExt)
|
|
withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null }
|
|
withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false }
|
|
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8"
|
|
errors.report()
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
|
}
|
|
}
|
|
}
|
|
|
|
context("WithValidName") {
|
|
context("WithBadFile") {
|
|
test("testWithSyntaxError") {
|
|
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
|
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
|
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
|
|
|
repeat(2) { n -> withClue(count[n] + " call") {
|
|
shouldThrow<MultipleParseErrors> {
|
|
importer.importImplicitLibraryModule(srcPath.nameWithoutExtension)
|
|
}.let { e ->
|
|
e.errors.first().position.file shouldBe SourceCode.relative(srcPath).toString()
|
|
withClue("line; should be 1-based") { e.errors.first().position.line shouldBe 2 }
|
|
withClue("startCol; should be 1-based") { e.errors.first().position.startCol shouldBe 5 }
|
|
withClue("endCol; should be 1-based") { e.errors.first().position.endCol shouldBe 7 }
|
|
}
|
|
}
|
|
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
|
}
|
|
}
|
|
|
|
|
|
fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
|
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
|
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
|
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
|
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
|
|
|
val act = { importer.importImplicitLibraryModule(importing.nameWithoutExtension) }
|
|
|
|
repeat(repetitions) { n -> withClue(count[n] + " call") {
|
|
shouldThrow<MultipleParseErrors> {
|
|
act() }.let { e ->
|
|
e.errors.first().position.file shouldBe SourceCode.relative(imported).toString()
|
|
withClue("line; should be 1-based") { e.errors.first().position.line shouldBe 2 }
|
|
withClue("startCol; should be 1-based") { e.errors.first().position.startCol shouldBe 5 }
|
|
withClue("endCol; should be 1-based") { e.errors.first().position.endCol shouldBe 7 }
|
|
}
|
|
}
|
|
withClue("imported module with error in it should not be present") { program.modules.size shouldBe PROG8_CONTAINER_MODULES.size }
|
|
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
|
|
importer.errors.report()
|
|
}
|
|
}
|
|
|
|
test("testImportingFileWithSyntaxError_once") {
|
|
doTestImportingFileWithSyntaxError(1)
|
|
}
|
|
|
|
test("testImportingFileWithSyntaxError_twice") {
|
|
doTestImportingFileWithSyntaxError(2)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|