Skip to content
Snippets Groups Projects
Commit a48735a9 authored by SeeBasTStick's avatar SeeBasTStick
Browse files

added tests and fixed bugs

parent 00c51594
Branches
Tags
No related merge requests found
Showing
with 457 additions and 236 deletions
/*
* This file was generated by the Gradle 'init' task.
*
......@@ -31,12 +32,21 @@ dependencies {
// json converter
implementation("com.google.code.gson" ,"gson" ,"2.8.6")
// Use the Kotlin test library.
testImplementation("org.jetbrains.kotlin:kotlin-test")
// Use the Kotlin JUnit integration.
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-test-junit5
testImplementation(kotlin("test-junit5"))
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
testImplementation("org.junit.jupiter","junit-jupiter-engine" , "5.7.0-M1")
}
tasks.test {
useJUnitPlatform()
}
application {
// Define the main class for the application.
mainClassName = "b.language.server.AppKt"
......
/*
* This Kotlin source file was generated by the Gradle 'init' task.
*/
package b.language.server
import org.eclipse.lsp4j.jsonrpc.Launcher
......@@ -11,23 +8,14 @@ import java.io.OutputStream
import java.util.concurrent.Future
class App {
val greeting: String
get() {
return "Hello world."
}
}
fun main(args: Array<String>) {
startServer(System.`in`, System.out);
fun main() {
startServer(System.`in`, System.out)
}
fun startServer(inputStream: InputStream, outputStream: OutputStream){
val server : Server = Server()
val server = Server()
val launcher : Launcher<LanguageClient> = LSPLauncher.createServerLauncher(server, inputStream, outputStream)
val startListing : Future<*> = launcher.startListening()
server.setRemoteProxy(launcher.remoteProxy)
startListing.get()
}
package b.language.server
import b.language.server.dataStorage.Problem
import b.language.server.proBMangement.CommandCouldNotBeExecutedException
import b.language.server.proBMangement.PathCouldNotBeCreatedException
import b.language.server.proBMangement.ProBCommandLineAccess
import b.language.server.proBMangement.ProBInterface
import org.eclipse.lsp4j.*
import org.eclipse.lsp4j.services.TextDocumentService
import java.io.File
import java.net.URI
import java.util.concurrent.ConcurrentHashMap
class BDocumentService(private val server: Server) : TextDocumentService {
private val documents = ConcurrentHashMap<String, String>()
private val issueTracker : ConcurrentHashMap<String, ArrayList<String>> = ConcurrentHashMap()
private val issueTracker : ConcurrentHashMap<String, Set<String>> = ConcurrentHashMap()
/**
* The document open notification is sent from the client to the server to
......@@ -32,29 +33,35 @@ class BDocumentService(private val server: Server) : TextDocumentService {
*/
override fun didSave(params: DidSaveTextDocumentParams?) {
val uri = URI(params!!.textDocument.uri)
val path = File(uri.path)
val errorPath = File(path.parent + "/tmp/_error.json")
val errorDict = File(path.parent + "/tmp")
val clientSettings = server.getDocumentSettings(params.textDocument.uri)
clientSettings.thenAccept{ setting ->
val probInterface = ProBInterface(setting.probHome, path, errorDict, errorPath, server = server)
probInterface.createFolder()
probInterface.performActionOnDocument()
val problemHandler = ProblemHandler()
val problemList: List<Problem> = problemHandler.readProblems(errorPath.absolutePath)
val diagnostics: List<Diagnostic> = problemHandler.transformProblems(problemList)
server.languageClient.publishDiagnostics(PublishDiagnosticsParams(params.textDocument.uri, diagnostics))
val currentUri = params!!.textDocument.uri
val clientSettings = server.getDocumentSettings(currentUri)
clientSettings.thenAccept{ settings ->
val prob : ProBInterface = ProBCommandLineAccess()
try{
val diagnostics: List<Diagnostic> = prob.checkDocument(currentUri, settings)
server.languageClient.publishDiagnostics(PublishDiagnosticsParams(currentUri, diagnostics))
val filesWithProblems = diagnostics.map { diagnostic -> diagnostic.source }
calculateToInvalidate(currentUri, filesWithProblems)
.forEach{uri -> server.languageClient.publishDiagnostics(PublishDiagnosticsParams(uri, listOf()))}
issueTracker[currentUri] = filesWithProblems.toSet()
}catch (e : PathCouldNotBeCreatedException ){
server.languageClient.showMessage(MessageParams(MessageType.Error, e.message))
}catch (e : CommandCouldNotBeExecutedException){
server.languageClient.showMessage(MessageParams(MessageType.Error, e.message))
}
}
}
/**
* Gets all uris that are no longer contain problems
* @param currentUri the uri of the current main file
* @param filesWithProblems uris of files containing problems
*/
fun calculateToInvalidate(currentUri : String, filesWithProblems : List<String>) : List<String>{
val currentlyDisplayed = issueTracker[currentUri].orEmpty()
return currentlyDisplayed.subtract(filesWithProblems).toList()
}
/**
......@@ -79,13 +86,4 @@ class BDocumentService(private val server: Server) : TextDocumentService {
//Nothing
}
}
\ No newline at end of file
package b.language.server
import org.eclipse.lsp4j.MessageParams
import org.eclipse.lsp4j.MessageType
import java.io.File
import java.io.FileWriter
import java.io.InputStream
/**
* Interacts with ProB via Commandline
* @param probHome the target ProbCli
* @param fileToCheck the main machine to start evaluating
* @param errorDict the path to store errors
* @param wdActive is wd checks enabled?
* @param strictActive is strict enabled?
* @param performanceHintsActive are performance hints activated?
* @param server the server used to connect with
*/
class ProBInterface(probHome : File,
fileToCheck : File,
val errorDict : File,
val errorPath : File,
wdActive : Boolean = false,
strictActive : Boolean = false,
performanceHintsActive : Boolean = false,
private val server : Server){
private val configuration : String = " -p MAX_INITIALISATIONS 0 -version "
private val ndjson : String = " -p NDJSON_ERROR_LOG_FILE "
private val wd : String = if(wdActive){
" -wd-check -release_java_parser "
}else{
" "
}
private val strict : String = if(strictActive){
" -p STRICT_CLASH_CHECKING TRUE -p TYPE_CHECK_DEFINITIONS TRUE -lint "
}else{
" "
}
private val performanceHints : String = if(performanceHintsActive){
" -p PERFORMANCE_INFO TRUE "
}else{
" "
}
private val command : String
init {
command = probHome.path +
configuration +
performanceHints +
strict +
wd +
fileToCheck.absoluteFile +
ndjson +
errorPath.absoluteFile
}
fun createFolder() : Boolean{
errorDict.mkdirs()
FileWriter(errorPath, false).close()
return errorDict.mkdirs()
}
/**
* Executes the command and returns and sends an error message if something fails
*/
fun performActionOnDocument() {
val process : Process = Runtime.getRuntime().exec(command)
val output : InputStream = process.inputStream
process.waitFor() //we must wait here to ensure correct behavior when reading an error
val outputAsString = String(output.readAllBytes())
if(!outputAsString.contains("ProB Command Line Interface")){
server.languageClient.showMessage(
MessageParams(MessageType.Error, "Something went wrong when calling probcli $command"))
}
}
}
\ No newline at end of file
package b.language.server
import b.language.server.dataStorage.Problem
import com.google.gson.Gson
import org.eclipse.lsp4j.Diagnostic
import org.eclipse.lsp4j.DiagnosticSeverity
import org.eclipse.lsp4j.Position
import org.eclipse.lsp4j.Range
import java.io.*
/**
* Responsible for reading Errors from the corresponding JSON File and sending transforming them into a object
*/
class ProblemHandler {
/**
* Reads the errors and transforms them to java objects
* @param path the path to the error document
* @return the list of read errors
*/
fun readProblems(path : String): List<Problem> {
val jsonStrings : ArrayList<String> = ArrayList()
BufferedReader(FileReader(path)).use { br ->
var line: String?
while (br.readLine().also { line = it } != null) {
jsonStrings.add(line!!)
}
}
return jsonStrings.toList().map { string -> Gson().fromJson(string, Problem::class.java) }
}
/**
* Transforms errors to error messages
* @param problems the list of errors to transform
* @return the transformed errors
*/
fun transformProblems(problems : List<Problem>): List<Diagnostic> {
return problems.
/**
* Some errors from prob_cli have negative values when there is no exact error location - we set them
* to the first line
*/
map{ problem ->
if (problem.start.line == -1 && problem.start.character == -1 &&
problem.end.line == -1 && problem.end.character == -1)
{
Problem(problem.type,
problem.message,
problem.reason,
problem.file,
Position(1, 0),
Position(1, Integer.MAX_VALUE),
problem.version)
}
else{
problem
}
}
.map { problem -> Diagnostic(Range(problem.start, problem.end), problem.message,
when (problem.type) {
"error" -> {
DiagnosticSeverity.Error
}
"warning" -> {
DiagnosticSeverity.Warning
}
"information" -> {
DiagnosticSeverity.Information
}
else -> {
DiagnosticSeverity.Hint
}
},
problem.file,
"probcli v.${problem.version}")}
}
}
\ No newline at end of file
......@@ -19,7 +19,7 @@ class Server : LanguageServer {
lateinit var languageClient : LanguageClient
var globalSettings : Settings = Settings()
val documentSettings : HashMap<String, CompletableFuture<Settings>> = HashMap()
var configurationAbility : Boolean = true;
var configurationAbility : Boolean = true
init {
textDocumentService = BDocumentService(this)
......@@ -101,8 +101,6 @@ class Server : LanguageServer {
* @return settings of the document requested
*/
fun getDocumentSettings(uri : String) : CompletableFuture<Settings> {
this.languageClient.showMessage(
MessageParams(MessageType.Log, "uri $uri " ))
if(!configurationAbility){
val returnValue = CompletableFuture<Settings>()
returnValue.complete(globalSettings)
......
package b.language.server
import b.language.server.dataStorage.Problem
import b.language.server.dataStorage.Settings
import com.google.gson.Gson
import com.google.gson.JsonObject
import org.eclipse.lsp4j.Diagnostic
import org.eclipse.lsp4j.DiagnosticSeverity
import org.eclipse.lsp4j.Position
import org.eclipse.lsp4j.Range
import java.io.File
/**
* Takes a json and tries to cast it into a settings objects
* @param json the json object
* @return the settings object
*/
fun castJsonToSetting(json : JsonObject) : Settings {
return Settings(Gson().fromJson(json.get("maxNumberOfProblems"), Int::class.java),
Gson().fromJson(json.get("wdChecks"), Boolean::class.java),
......
package b.language.server.dataStorage
class Position(var line : Int, var col : Int)
\ No newline at end of file
package b.language.server.dataStorage
import org.eclipse.lsp4j.Position
/**
* kotlin representation of prob ndjson problems
......
package b.language.server.proBMangement
class CommandCouldNotBeExecutedException(message: String?) : Exception(message)
\ No newline at end of file
package b.language.server.proBMangement
class PathCouldNotBeCreatedException(message: String?) : Exception(message)
\ No newline at end of file
package b.language.server.proBMangement
import b.language.server.dataStorage.Problem
import b.language.server.dataStorage.Settings
import com.google.gson.Gson
import org.eclipse.lsp4j.*
import java.io.*
import java.net.URI
/**
* Access ProB via command line
*/
class ProBCommandLineAccess : ProBInterface{
/**
* Checks the given document with the help of ProB; Will setup all needed steps to ensure a clean process
* @param uri the source to check
* @return a list of all problems found
*/
override fun checkDocument(uri : String, settings: Settings): List<Diagnostic> {
val realUri = URI(uri)
val path = File(realUri.path)
val errorPath = File(path.parent + "/tmp/_error.json")
val errorDict = File(path.parent + "/tmp")
val result = createFolder(errorDict, errorPath)
if(!result){
throw PathCouldNotBeCreatedException("The Path leading to $errorPath could has not been created due some issue.")
}
val command = buildCommand(settings, path, errorPath)
performActionOnDocument(command)
val problems = readProblems(errorPath.path)
return transformProblems(problems)
}
/**
* Constructs the commandline call to proB depending on the given settings
* @param settings the settings for the document
* @param fileToCheck the current documents address
* @param errorPath the path to dump the ndjson message
* @return the execution ready command
*/
fun buildCommand(settings : Settings, fileToCheck : File, errorPath : File) : String{
val configuration = " -p MAX_INITIALISATIONS 0 -version "
val ndjson = " -p NDJSON_ERROR_LOG_FILE "
val wd : String = if(settings.wdChecks){
" -wd-check -release_java_parser "
}else{
" "
}
val strict : String = if(settings.strictChecks){
" -p STRICT_CLASH_CHECKING TRUE -p TYPE_CHECK_DEFINITIONS TRUE -lint "
}else{
" "
}
val performanceHints : String = if(settings.performanceHints){
" -p PERFORMANCE_INFO TRUE "
}else{
" "
}
val probHome = settings.probHome
return probHome.path +
configuration +
performanceHints +
strict +
wd +
fileToCheck.absoluteFile +
ndjson +
errorPath.absoluteFile
}
/**
* Creates the tmp folder and an empty ndjson file; will recreate an empty ndjson file to ensure clean messages
* @param errorDict the path of the tmp dict
* @param errorPath the path to the error file
* @return success of the action
*/
fun createFolder(errorDict : File, errorPath: File) : Boolean{
errorDict.mkdirs()
FileWriter(errorPath, false).close()
return errorDict.exists() && errorPath.exists()
}
/**
* Executes the given command
* @param command to execute
* @throws CommandCouldNotBeExecutedException the command failed to reach probcli
*/
fun performActionOnDocument(command : String) {
val process : Process = Runtime.getRuntime().exec(command)
val output : InputStream = process.inputStream
process.waitFor() //we must wait here to ensure correct behavior when reading an error
val outputAsString = String(output.readAllBytes())
if(!outputAsString.contains("ProB Command Line Interface")){
throw CommandCouldNotBeExecutedException("Error when trying to call probcli with command $command")
}
}
/**
* Reads the errors and transforms them to java objects
* @param path the path to the error document
* @return the list of read errors
*/
fun readProblems(path : String): List<Problem> {
val jsonStrings : ArrayList<String> = ArrayList()
BufferedReader(FileReader(path)).use { br ->
var line: String?
while (br.readLine().also { line = it } != null) {
jsonStrings.add(line!!)
}
}
val strings = jsonStrings.toList().map { string -> Gson().fromJson(string, Problem::class.java) }
return strings
}
/**
* Transforms errors to error messages
* @param problems the list of errors to transform
* @return the transformed errors
*/
fun transformProblems(problems : List<Problem>): List<Diagnostic> {
return problems.
/**
* Some errors from prob_cli have negative values when there is no exact error location - we set them
* to the first line
*/
map{ problem ->
if (problem.start.line == -1 && problem.start.col == -1 &&
problem.end.line == -1 && problem.end.col == -1)
{
print(Integer.MAX_VALUE)
Problem(problem.type,
problem.message,
problem.reason,
problem.file,
b.language.server.dataStorage.Position(1,0),
b.language.server.dataStorage.Position(1, Integer.MAX_VALUE),
problem.version)
}
else{
/**
* Lines are off by one when they get out of vscode
*/
problem.start.line = problem.start.line - 1
problem.end.line = problem.end.line - 1
problem
}
}
.map { problem -> Diagnostic(
Range(
Position(problem.start.line, problem.start.col),
Position(problem.end.line, problem.end.col)), problem.message,
when (problem.type) {
"error" -> {
DiagnosticSeverity.Error
}
"warning" -> {
DiagnosticSeverity.Warning
}
"information" -> {
DiagnosticSeverity.Information
}
else -> {
DiagnosticSeverity.Hint
}
},
problem.file,
" probcli v.${problem.version}")
}
}
}
\ No newline at end of file
package b.language.server.proBMangement
import b.language.server.dataStorage.Settings
import org.eclipse.lsp4j.Diagnostic
interface ProBInterface {
/**
* Checks the given document with the help of ProB
* @param uri the source to check
* @return a list of all problems found
*/
abstract fun checkDocument(uri : String, settings: Settings) : List<Diagnostic>
}
\ No newline at end of file
/*
* This Kotlin source file was generated by the Gradle 'init' task.
*/
package b.language.server
import kotlin.test.Test
import kotlin.test.assertNotNull
class AppTest {
@Test fun testAppHasAGreeting() {
val classUnderTest = App()
assertNotNull(classUnderTest.greeting, "app should have a greeting")
}
}
package b.language.server
import b.language.server.dataStorage.Position
import b.language.server.dataStorage.Problem
import b.language.server.dataStorage.Settings
import b.language.server.proBMangement.ProBCommandLineAccess
import com.google.gson.Gson
import org.eclipse.lsp4j.Diagnostic
import org.eclipse.lsp4j.DiagnosticSeverity
import org.eclipse.lsp4j.Range
import kotlin.test.Test
import org.junit.jupiter.api.io.TempDir
import java.io.File
import kotlin.test.assertEquals
class ProBCommandLineTest{
@Test
fun test_readProblems(@TempDir tempPath : File = File("tmp"))
{
val file = File(tempPath.path+"/tmp.txt")
val problemToWrite = Problem(message = "Test", file = "test.mch", reason = "test reason", version = "test",
start = Position(1,1), end = Position(1,1), type = "test")
file.writeText(Gson().toJson(problemToWrite))
val problems = ProBCommandLineAccess().readProblems(file.path)
val problem = problems.first()
assertEquals(problemToWrite.version, problem.version)
assertEquals(problemToWrite.end.col, problem.end.col)
assertEquals(problemToWrite.end.line, problemToWrite.end.line)
assertEquals(problemToWrite.start.col, problem.start.col)
assertEquals(problemToWrite.start.line, problemToWrite.start.line)
assertEquals(problemToWrite.file, problem.file)
assertEquals(problemToWrite.message, problem.message)
assertEquals(problemToWrite.reason, problem.reason)
}
@Test
fun test_buildCommand_everything_activated(@TempDir tempPath : File = File("tmp")){
val testSettings = Settings()
val tempFile = File(tempPath.path+"/m.mch")
val command = ProBCommandLineAccess().buildCommand(testSettings, tempFile, tempPath)
assertEquals("~/prob_prolog/probcli.sh -p MAX_INITIALISATIONS 0 " +
"-version " +
"-p PERFORMANCE_INFO TRUE " +
"-p STRICT_CLASH_CHECKING TRUE " +
"-p TYPE_CHECK_DEFINITIONS TRUE -lint " +
"-wd-check -release_java_parser ${tempFile.path} " +
"-p NDJSON_ERROR_LOG_FILE $tempPath", command)
}
@Test
fun test_buildCommand_everything_not_strict(@TempDir tempPath : File = File("tmp")){
val testSettings = Settings(strictChecks = false)
val tempFile = File(tempPath.path+"/m.mch")
val command = ProBCommandLineAccess().buildCommand(testSettings, tempFile, tempPath)
assertEquals("~/prob_prolog/probcli.sh -p MAX_INITIALISATIONS 0 " +
"-version " +
"-p PERFORMANCE_INFO TRUE " +
"-wd-check -release_java_parser ${tempFile.path} " +
"-p NDJSON_ERROR_LOG_FILE $tempPath", command)
}
@Test
fun test_buildCommand_everything_not_wd(@TempDir tempPath : File = File("tmp")){
val testSettings = Settings(wdChecks = false)
val tempFile = File(tempPath.path+"/m.mch")
val command = ProBCommandLineAccess().buildCommand(testSettings, tempFile, tempPath)
assertEquals("~/prob_prolog/probcli.sh -p MAX_INITIALISATIONS 0 " +
"-version " +
"-p PERFORMANCE_INFO TRUE " +
"-p STRICT_CLASH_CHECKING TRUE " +
"-p TYPE_CHECK_DEFINITIONS TRUE -lint " +
"${tempFile.path} " +
"-p NDJSON_ERROR_LOG_FILE $tempPath", command)
}
@Test
fun test_buildCommand_everything_not_performanceHints(@TempDir tempPath : File = File("tmp")){
val testSettings = Settings(performanceHints = false)
val tempFile = File(tempPath.path+"/m.mch")
val command = ProBCommandLineAccess().buildCommand(testSettings, tempFile, tempPath)
assertEquals("~/prob_prolog/probcli.sh -p MAX_INITIALISATIONS 0 " +
"-version " +
"-p STRICT_CLASH_CHECKING TRUE " +
"-p TYPE_CHECK_DEFINITIONS TRUE -lint " +
"-wd-check -release_java_parser ${tempFile.path} " +
"-p NDJSON_ERROR_LOG_FILE $tempPath", command)
}
@Test
fun test_transformProblems_negative_range(){
val problemFile = "test.mch"
val message = "Test"
val version = "test"
val testProblem = Problem(message = message, file = problemFile, reason = "test reason", version = version,
start = Position(-1,-1), end = Position(-1,-1), type = "error")
val transformedProblem = ProBCommandLineAccess().transformProblems(listOf(testProblem)).first()
val diagnostic = Diagnostic(Range(
org.eclipse.lsp4j.Position(1,0),
org.eclipse.lsp4j.Position(1, Integer.MAX_VALUE)), message, DiagnosticSeverity.Error, problemFile, " probcli v.$version" )
assertEquals(diagnostic, transformedProblem)
}
@Test
fun test_transformProblems_error(){
val problemFile = "test.mch"
val message = "Test"
val version = "test"
val testProblem = Problem(message = message, file = problemFile, reason = "test reason", version = version,
start = Position(32,54), end = Position(54,65), type = "error")
val transformedProblem = ProBCommandLineAccess().transformProblems(listOf(testProblem)).first()
val diagnostic = Diagnostic(Range(
org.eclipse.lsp4j.Position(31,54),
org.eclipse.lsp4j.Position(53, 65)), message, DiagnosticSeverity.Error, problemFile, " probcli v.$version" )
assertEquals(diagnostic, transformedProblem)
}
@Test
fun test_transformProblems_warning(){
val problemFile = "test.mch"
val message = "Test"
val version = "test"
val testProblem = Problem(message = message, file = problemFile, reason = "test reason", version = version,
start = Position(32,54), end = Position(54,65), type = "warning")
val transformedProblem = ProBCommandLineAccess().transformProblems(listOf(testProblem)).first()
val diagnostic = Diagnostic(Range(
org.eclipse.lsp4j.Position(31,54),
org.eclipse.lsp4j.Position(53, 65)), message, DiagnosticSeverity.Warning, problemFile, " probcli v.$version" )
assertEquals(diagnostic, transformedProblem)
}
@Test
fun test_transformProblems_information(){
val problemFile = "test.mch"
val message = "Test"
val version = "test"
val testProblem = Problem(message = message, file = problemFile, reason = "test reason", version = version,
start = Position(32,54), end = Position(54,65), type = "information")
val transformedProblem = ProBCommandLineAccess().transformProblems(listOf(testProblem)).first()
val diagnostic = Diagnostic(Range(
org.eclipse.lsp4j.Position(31,54),
org.eclipse.lsp4j.Position(53, 65)), message, DiagnosticSeverity.Information, problemFile, " probcli v.$version" )
assertEquals(diagnostic, transformedProblem)
}
@Test
fun test_transformProblems_hint(){
val problemFile = "test.mch"
val message = "Test"
val version = "test"
val testProblem = Problem(message = message, file = problemFile, reason = "test reason", version = version,
start = Position(32,54), end = Position(54,65), type = "something else")
val transformedProblem = ProBCommandLineAccess().transformProblems(listOf(testProblem)).first()
val diagnostic = Diagnostic(Range(
org.eclipse.lsp4j.Position(31,54),
org.eclipse.lsp4j.Position(53, 65)), message, DiagnosticSeverity.Hint, problemFile, " probcli v.$version" )
assertEquals(diagnostic, transformedProblem)
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment