From bcdd5a5f899e3100803d790e0fdd9e7416d707ca Mon Sep 17 00:00:00 2001 From: Arseniy Volynets Date: Sun, 28 Aug 2022 19:42:02 +0300 Subject: [PATCH] prototype cmake generation - CMake file is generated, it assembles tests similar to makefiles generated by UTBOT but simpler - Wrappers are sent to client in the same way as stubs - CMake file is adopted in CLion: the tests subdir is added to the root CMakeLists.txt and GTest is optionally installed. --- .../actions/SyncWrappersAndStubsAction.kt | 14 ++ .../utbot/cpp/clion/plugin/client/Client.kt | 101 +++++++------ .../cpp/clion/plugin/client/ManagedClient.kt | 4 + .../testsStreamHandler/CMakePrinter.kt | 52 +++++++ .../ShouldInstallGTestDialog.kt | 20 +++ .../TestsStreamHandler.kt | 99 +++++++++++-- .../requests/SyncProjectStubsAndWrappers.kt | 42 ++++++ .../client/requests/test/BaseTestsRequest.kt | 2 +- .../settings/UTBotProjectStoredSettings.kt | 23 +-- .../ui/statusBar/StatusBarConnectionStatus.kt | 1 + .../clion/plugin/utils/NotificationUtils.kt | 1 + .../utbot/cpp/clion/plugin/utils/PathUtils.kt | 2 + .../src/main/resources/META-INF/plugin.xml | 3 + .../main/resources/messages/UTBot.properties | 2 + server/src/KleeRunner.cpp | 7 +- server/src/KleeRunner.h | 4 +- server/src/Paths.h | 4 + server/src/Server.cpp | 5 +- server/src/Synchronizer.cpp | 22 +-- server/src/building/BaseCommand.cpp | 6 - server/src/building/BuildDatabase.cpp | 2 +- server/src/building/BuildDatabase.h | 2 +- server/src/building/CMakeGenerator.cpp | 133 ++++++++++++++++++ server/src/building/CMakeGenerator.h | 54 +++++++ server/src/building/Linker.cpp | 21 ++- server/src/building/Linker.h | 7 +- .../src/building/UserProjectConfiguration.cpp | 18 +++ .../src/building/UserProjectConfiguration.h | 2 + server/src/printers/CMakeListsPrinter.cpp | 77 ++++++++++ server/src/printers/CMakeListsPrinter.h | 109 ++++++++++++++ server/src/printers/NativeMakefilePrinter.cpp | 71 +++++----- server/src/printers/NativeMakefilePrinter.h | 2 +- server/src/printers/SourceWrapperPrinter.cpp | 15 +- server/src/printers/SourceWrapperPrinter.h | 4 + server/src/streams/stubs/CLIStubsWriter.cpp | 3 +- server/src/streams/stubs/CLIStubsWriter.h | 2 +- .../src/streams/stubs/ServerStubsWriter.cpp | 19 +-- server/src/streams/stubs/ServerStubsWriter.h | 3 +- server/src/streams/stubs/StubsWriter.h | 2 +- .../src/streams/tests/ServerTestsWriter.cpp | 13 ++ server/src/streams/tests/ServerTestsWriter.h | 2 + server/src/streams/tests/TestsWriter.cpp | 4 + server/src/streams/tests/TestsWriter.h | 2 + server/src/testgens/BaseTestGen.h | 2 + server/src/utils/Copyright.h | 7 + server/src/utils/FileInfoForTransfer.h | 15 ++ server/src/utils/FileSystemUtils.cpp | 5 + server/src/utils/FileSystemUtils.h | 2 + server/src/utils/LinkerUtils.h | 2 - server/test/framework/CLI_Tests.cpp | 2 +- .../test/framework/CMakeGeneration_Tests.cpp | 69 +++++++++ server/test/framework/Server_Tests.cpp | 15 +- server/test/framework/Stub_Tests.cpp | 18 +-- server/test/framework/TestUtils.cpp | 8 ++ server/test/framework/TestUtils.h | 2 + server/test/framework/main.cpp | 5 + .../test/suites/cmake/shared/CMakeLists.txt | 9 ++ server/test/suites/cmake/shared/bar.c | 4 + server/test/suites/cmake/shared/bar.h | 5 + .../suites/cmake/shared/expected_cmake.txt | 24 ++++ server/test/suites/cmake/shared/foo.c | 5 + .../test/suites/cmake/simple/CMakeLists.txt | 9 ++ server/test/suites/cmake/simple/a.c | 5 + server/test/suites/cmake/simple/b.c | 8 ++ server/test/suites/cmake/simple/bar.h | 4 + .../suites/cmake/simple/expected_cmake.txt | 24 ++++ server/test/suites/cmake/stubs/CMakeLists.txt | 13 ++ .../cmake/stubs/expected_cmake_exe_target.txt | 55 ++++++++ .../stubs/expected_cmake_unitA_target.txt | 29 ++++ server/test/suites/cmake/stubs/main.c | 6 + .../suites/cmake/stubs/unitA/CMakeLists.txt | 4 + server/test/suites/cmake/stubs/unitA/caller.c | 10 ++ server/test/suites/cmake/stubs/unitA/caller.h | 8 ++ .../suites/cmake/stubs/unitB/CMakeLists.txt | 3 + server/test/suites/cmake/stubs/unitB/callee.c | 6 + server/test/suites/cmake/stubs/unitB/callee.h | 8 ++ .../suites/small-project/expected_cmake.txt | 44 ++++++ vscode-plugin/src/test/helper.ts | 3 +- 78 files changed, 1257 insertions(+), 162 deletions(-) create mode 100644 clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/actions/SyncWrappersAndStubsAction.kt create mode 100644 clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/testsStreamHandler/CMakePrinter.kt create mode 100644 clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/testsStreamHandler/ShouldInstallGTestDialog.kt rename clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/{ => testsStreamHandler}/TestsStreamHandler.kt (52%) create mode 100644 clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/requests/SyncProjectStubsAndWrappers.kt create mode 100644 server/src/building/CMakeGenerator.cpp create mode 100644 server/src/building/CMakeGenerator.h create mode 100644 server/src/printers/CMakeListsPrinter.cpp create mode 100644 server/src/printers/CMakeListsPrinter.h create mode 100644 server/src/utils/FileInfoForTransfer.h create mode 100644 server/test/framework/CMakeGeneration_Tests.cpp create mode 100644 server/test/suites/cmake/shared/CMakeLists.txt create mode 100644 server/test/suites/cmake/shared/bar.c create mode 100644 server/test/suites/cmake/shared/bar.h create mode 100644 server/test/suites/cmake/shared/expected_cmake.txt create mode 100644 server/test/suites/cmake/shared/foo.c create mode 100644 server/test/suites/cmake/simple/CMakeLists.txt create mode 100644 server/test/suites/cmake/simple/a.c create mode 100644 server/test/suites/cmake/simple/b.c create mode 100644 server/test/suites/cmake/simple/bar.h create mode 100644 server/test/suites/cmake/simple/expected_cmake.txt create mode 100644 server/test/suites/cmake/stubs/CMakeLists.txt create mode 100644 server/test/suites/cmake/stubs/expected_cmake_exe_target.txt create mode 100644 server/test/suites/cmake/stubs/expected_cmake_unitA_target.txt create mode 100644 server/test/suites/cmake/stubs/main.c create mode 100644 server/test/suites/cmake/stubs/unitA/CMakeLists.txt create mode 100644 server/test/suites/cmake/stubs/unitA/caller.c create mode 100644 server/test/suites/cmake/stubs/unitA/caller.h create mode 100644 server/test/suites/cmake/stubs/unitB/CMakeLists.txt create mode 100644 server/test/suites/cmake/stubs/unitB/callee.c create mode 100644 server/test/suites/cmake/stubs/unitB/callee.h create mode 100644 server/test/suites/small-project/expected_cmake.txt diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/actions/SyncWrappersAndStubsAction.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/actions/SyncWrappersAndStubsAction.kt new file mode 100644 index 000000000..cdaa33385 --- /dev/null +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/actions/SyncWrappersAndStubsAction.kt @@ -0,0 +1,14 @@ +package org.utbot.cpp.clion.plugin.actions + +import com.intellij.openapi.actionSystem.AnActionEvent +import org.utbot.cpp.clion.plugin.utils.client + +class SyncWrappersAndStubsAction: UTBotBaseAction() { + override fun actionPerformed(e: AnActionEvent) { + e.client.syncWrappersAnsStubs() + } + + override fun updateIfEnabled(e: AnActionEvent) { + e.presentation.isEnabledAndVisible = e.project != null + } +} diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/Client.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/Client.kt index d8539da2d..b5b458373 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/Client.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/Client.kt @@ -22,6 +22,8 @@ import org.utbot.cpp.clion.plugin.actions.ShowSettingsAction import org.utbot.cpp.clion.plugin.client.channels.LogChannel import org.utbot.cpp.clion.plugin.grpc.IllegalPathException import org.utbot.cpp.clion.plugin.client.logger.ClientLogger +import org.utbot.cpp.clion.plugin.client.requests.SyncProjectStubsAndWrappers +import org.utbot.cpp.clion.plugin.grpc.GrpcRequestBuilderFactory import org.utbot.cpp.clion.plugin.listeners.ConnectionStatus import org.utbot.cpp.clion.plugin.listeners.UTBotEventsListener import org.utbot.cpp.clion.plugin.settings.projectIndependentSettings @@ -83,46 +85,46 @@ class Client( ) return } - executeRequestImpl(request) + requestsCS.launch(CoroutineName(request.toString())) { + executeRequestImpl(request, coroutineContext[Job]) + } } - private fun executeRequestImpl(request: Request) { - requestsCS.launch(CoroutineName(request.toString())) { - try { - request.execute(stub, coroutineContext[Job]) - } catch (e: io.grpc.StatusException) { - val id = request.id - when (e.status.code) { - Status.UNAVAILABLE.code -> notifyNotConnected(project, port, serverName) - Status.UNKNOWN.code -> notifyError( - UTBot.message("notify.title.unknown.server.error"), // unknown server error - UTBot.message("notify.unknown.server.error"), - project - ) - Status.CANCELLED.code -> notifyError( - UTBot.message("notify.title.cancelled"), - UTBot.message("notify.cancelled", id, e.message ?: ""), - project - ) - Status.FAILED_PRECONDITION.code, Status.INTERNAL.code, Status.UNIMPLEMENTED.code, Status.INVALID_ARGUMENT.code -> notifyError( - UTBot.message("notify.title.error"), - UTBot.message("notify.request.failed", e.message ?: "", id), - project - ) - else -> notifyError( - UTBot.message("notify.title.error"), - e.message ?: "Corresponding exception's message is missing", - project - ) - } - } catch (e: IllegalPathException) { - notifyError( - UTBot.message("notify.bad.settings.title"), - UTBot.message("notify.bad.path", e.message ?: ""), - project, - ShowSettingsAction() + private suspend fun executeRequestImpl(request: Request, job: Job?) { + try { + request.execute(stub, job) + } catch (e: io.grpc.StatusException) { + val id = request.id + when (e.status.code) { + Status.UNAVAILABLE.code -> notifyNotConnected(project, port, serverName) + Status.UNKNOWN.code -> notifyError( + UTBot.message("notify.title.unknown.server.error"), // unknown server error + UTBot.message("notify.unknown.server.error"), + project + ) + Status.CANCELLED.code -> notifyError( + UTBot.message("notify.title.cancelled"), + UTBot.message("notify.cancelled", id, e.message ?: ""), + project + ) + Status.FAILED_PRECONDITION.code, Status.INTERNAL.code, Status.UNIMPLEMENTED.code, Status.INVALID_ARGUMENT.code -> notifyError( + UTBot.message("notify.title.error"), + UTBot.message("notify.request.failed", e.message ?: "", id), + project + ) + else -> notifyError( + UTBot.message("notify.title.error"), + e.message ?: "Corresponding exception's message is missing", + project ) } + } catch (e: IllegalPathException) { + notifyError( + UTBot.message("notify.bad.settings.title"), + UTBot.message("notify.bad.path", e.message ?: ""), + project, + ShowSettingsAction() + ) } } @@ -134,17 +136,27 @@ class Client( } } - private fun registerClient() { - requestsCS.launch { - try { - logger.info { "Sending REGISTER CLIENT request, clientID == $clientId" } - stub.registerClient(Testgen.RegisterClientRequest.newBuilder().setClientId(clientId).build()) - } catch (e: io.grpc.StatusException) { - logger.error { "${e.status}: ${e.message}" } - } + private suspend fun registerClient() { + try { + logger.info { "Sending REGISTER CLIENT request, clientID == $clientId" } + stub.registerClient(Testgen.RegisterClientRequest.newBuilder().setClientId(clientId).build()) + } catch (e: io.grpc.StatusException) { + logger.error { "Exception on registering client: ${e.status}: ${e.message}" } } } + fun syncWrappersAndStubs() { + createRequestForSync().also { + executeRequestIfNotDisposed(it) + } + } + + private fun createRequestForSync(): SyncProjectStubsAndWrappers = + SyncProjectStubsAndWrappers( + GrpcRequestBuilderFactory(project).createProjectRequestBuilder(), + project + ) + private fun startPeriodicHeartBeat() { logger.info { "The heartbeat started with interval: $HEARTBEAT_INTERVAL ms" } servicesCS.launch(CoroutineName("periodicHeartBeat")) { @@ -167,6 +179,7 @@ class Client( notifyInfo(UTBot.message("notify.connected.title"), UTBot.message("notify.connected", port, serverName)) logger.info { "Successfully connected to server!" } registerClient() + executeRequestIfNotDisposed(createRequestForSync()) } if (newClient || !response.linked) { diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/ManagedClient.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/ManagedClient.kt index 45c722505..dfab4b40e 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/ManagedClient.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/ManagedClient.kt @@ -103,6 +103,10 @@ class ManagedClient(val project: Project) : Disposable { return "${(System.getenv("USER") ?: "user")}-${createRandomSequence()}" } + fun syncWrappersAnsStubs() { + client?.syncWrappersAndStubs() + } + @TestOnly fun waitForServerRequestsToFinish( timeout: Long = SERVER_TIMEOUT, diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/testsStreamHandler/CMakePrinter.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/testsStreamHandler/CMakePrinter.kt new file mode 100644 index 000000000..70435f218 --- /dev/null +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/testsStreamHandler/CMakePrinter.kt @@ -0,0 +1,52 @@ +package org.utbot.cpp.clion.plugin.client.handlers.testsStreamHandler + +class CMakePrinter(private val currentCMakeListsContent: String) { + private val ss = StringBuilder() + var isEmpty = true + private set + + init { + ss.append("\n") + startUTBotSection() + } + + private fun add(string: String) { + ss.append(string) + ss.append("\n") + } + + fun startUTBotSection() { + add("#utbot_section_start") + } + + fun addDownloadGTestSection() { + isEmpty = false + add( + """ + include(FetchContent) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.12.1 + ) + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + enable_testing() + include(GoogleTest) + """.trimIndent() + ) + } + + fun addSubdirectory(dirRelativePath: String) { + isEmpty = false + val addDirectoryInstruction = "add_subdirectory($dirRelativePath)" + if (!currentCMakeListsContent.contains(addDirectoryInstruction)) + add(addDirectoryInstruction) + } + + fun get(): String { + return ss.toString() + "#utbot_section_end\n" + } +} + diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/testsStreamHandler/ShouldInstallGTestDialog.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/testsStreamHandler/ShouldInstallGTestDialog.kt new file mode 100644 index 000000000..4ad345b15 --- /dev/null +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/testsStreamHandler/ShouldInstallGTestDialog.kt @@ -0,0 +1,20 @@ +package org.utbot.cpp.clion.plugin.client.handlers.testsStreamHandler + +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.ui.dsl.builder.panel +import javax.swing.JComponent +import org.utbot.cpp.clion.plugin.UTBot + +class ShouldInstallGTestDialog(project: Project) : DialogWrapper(project) { + init { + init() + title = "UTBot: GTest Install" + } + + override fun createCenterPanel(): JComponent { + return panel { + row(UTBot.message("dialog.should.install.gtest")) {} + } + } +} diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/TestsStreamHandler.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/testsStreamHandler/TestsStreamHandler.kt similarity index 52% rename from clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/TestsStreamHandler.kt rename to clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/testsStreamHandler/TestsStreamHandler.kt index 6129448f3..f2b367923 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/TestsStreamHandler.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/testsStreamHandler/TestsStreamHandler.kt @@ -1,22 +1,32 @@ -package org.utbot.cpp.clion.plugin.client.handlers +package org.utbot.cpp.clion.plugin.client.handlers.testsStreamHandler import com.intellij.openapi.components.service +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project import com.intellij.util.io.exists +import com.intellij.util.io.readText +import kotlin.io.path.appendText import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow +import org.utbot.cpp.clion.plugin.UTBot +import org.utbot.cpp.clion.plugin.client.handlers.SourceCode +import org.utbot.cpp.clion.plugin.client.handlers.StreamHandlerWithProgress import org.utbot.cpp.clion.plugin.settings.settings import org.utbot.cpp.clion.plugin.ui.services.TestsResultsStorage -import org.utbot.cpp.clion.plugin.utils.convertFromRemotePathIfNeeded import org.utbot.cpp.clion.plugin.utils.createFileWithText import org.utbot.cpp.clion.plugin.utils.invokeOnEdt +import org.utbot.cpp.clion.plugin.utils.isCMakeListsFile import org.utbot.cpp.clion.plugin.utils.isSarifReport import org.utbot.cpp.clion.plugin.utils.logger import org.utbot.cpp.clion.plugin.utils.markDirtyAndRefresh import org.utbot.cpp.clion.plugin.utils.nioPath +import org.utbot.cpp.clion.plugin.utils.notifyError import testsgen.Testgen import testsgen.Util +import java.io.IOException import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -31,23 +41,31 @@ class TestsStreamHandler( ) : StreamHandlerWithProgress(project, grpcStream, progressName, cancellationJob) { private val myGeneratedTestFilesLocalFS: MutableList = mutableListOf() + private var isCMakePresent = false + private var isSarifPresent = false override fun onData(data: Testgen.TestsResponse) { super.onData(data) - val testSourceCodes = data.testSourcesList - .map { SourceCode(it, project) } - .filter { !it.localPath.isSarifReport() } + // currently testSourcesList contains not only test sourse codes but + // also some extra files like sarif report, cmake generated file + // this was done for compatibility + val sourceCodes = data.testSourcesList.mapNotNull { it.toSourceCodeOrNull() } + val testSourceCodes = sourceCodes + .filter { !it.localPath.isSarifReport() && !it.localPath.isCMakeListsFile() } handleTestSources(testSourceCodes) - val stubSourceCodes = data.stubs.stubSourcesList.map { SourceCode(it, project) } + // for stubs we know that stubSourcesList contains only stub sources + val stubSourceCodes = data.stubs.stubSourcesList.mapNotNull { it.toSourceCodeOrNull() } handleStubSources(stubSourceCodes) - val sarifReport = - data.testSourcesList.find { it.filePath.convertFromRemotePathIfNeeded(project).isSarifReport() }?.let { - SourceCode(it, project) - } - sarifReport?.let { handleSarifReport(it) } + val sarifReport = sourceCodes.find { it.localPath.isSarifReport() } + if (sarifReport != null) + handleSarifReport(sarifReport) + + val cmakeFile = sourceCodes.find { it.localPath.endsWith("CMakeLists.txt") } + if (cmakeFile != null) + handleCMakeFile(cmakeFile) // for new generated tests remove previous testResults project.service().clearTestResults(testSourceCodes) @@ -55,10 +73,59 @@ class TestsStreamHandler( override fun onFinish() { super.onFinish() + if (!isCMakePresent) + project.logger.warn("CMake file is missing in the tests response") + if (!isSarifPresent) + project.logger.warn("Sarif report is missing in the tests response") // tell ide to refresh vfs and refresh project tree markDirtyAndRefresh(project.nioPath) } + private fun handleCMakeFile(cmakeSourceCode: SourceCode) { + isCMakePresent = true + createFileWithText(cmakeSourceCode.localPath, cmakeSourceCode.content) + val rootCMakeFile = project.nioPath.resolve("CMakeLists.txt") + if (!rootCMakeFile.exists()) { + project.logger.warn("Root CMakeLists.txt file does not exist. Skipping CMake patches.") + return + } + + val currentCMakeFileContent = rootCMakeFile.readText() + val cMakePrinter = CMakePrinter(currentCMakeFileContent) + invokeOnEdt { // we can show dialog only from edt + + if (!project.settings.storedSettings.isGTestInstalled) { + val shouldInstallGTestDialog = ShouldInstallGTestDialog(project) + + if (shouldInstallGTestDialog.showAndGet()) { + cMakePrinter.addDownloadGTestSection() + } + + // whether user confirmed that gtest is installed or we added the gtest section, from now on + // we will assume that gtest is installed + project.settings.storedSettings.isGTestInstalled = true + } + + cMakePrinter.addSubdirectory(project.settings.storedSettings.testDirRelativePath) + + // currently we are on EDT, but writing to file better to be done on background thread + ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Modifying CMakeLists.txt file", false) { + override fun run(progressIndicator: ProgressIndicator) { + try { + if (!cMakePrinter.isEmpty) + project.nioPath.resolve("CMakeLists.txt").appendText(cMakePrinter.get()) + } catch (e: IOException) { + notifyError( + UTBot.message("notify.title.error"), + UTBot.message("notify.error.write.to.file", e.message ?: "unknown reason"), + project + ) + } + } + }) + } + } + override fun onCompletion(exception: Throwable?) { invokeOnEdt { indicator.stopShowingProgressInUI() @@ -71,6 +138,7 @@ class TestsStreamHandler( } private fun handleSarifReport(sarif: SourceCode) { + isSarifPresent = true backupPreviousClientSarifReport(sarif.localPath) createSourceCodeFiles(listOf(sarif), "sarif report") project.logger.info { "Generated SARIF report file ${sarif.localPath}" } @@ -97,6 +165,15 @@ class TestsStreamHandler( } } + private fun Util.SourceCode.toSourceCodeOrNull(): SourceCode? { + return try { + SourceCode(this, project) + } catch (e: IllegalArgumentException) { + project.logger.error("Could not convert remote path to local version: bad path: ${this.filePath}") + null + } + } + private fun handleStubSources(sources: List) { if (project.settings.isRemoteScenario) { createSourceCodeFiles(sources, "stub") diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/requests/SyncProjectStubsAndWrappers.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/requests/SyncProjectStubsAndWrappers.kt new file mode 100644 index 000000000..0a143f17e --- /dev/null +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/requests/SyncProjectStubsAndWrappers.kt @@ -0,0 +1,42 @@ +package org.utbot.cpp.clion.plugin.client.requests + +import com.intellij.openapi.project.Project +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.onEach +import org.utbot.cpp.clion.plugin.client.handlers.SourceCode +import org.utbot.cpp.clion.plugin.grpc.GrpcRequestBuilder +import org.utbot.cpp.clion.plugin.utils.createFileWithText +import org.utbot.cpp.clion.plugin.utils.logger +import org.utbot.cpp.clion.plugin.utils.markDirtyAndRefresh +import org.utbot.cpp.clion.plugin.utils.nioPath +import testsgen.Testgen +import testsgen.TestsGenServiceGrpcKt + +class SyncProjectStubsAndWrappers(builder: GrpcRequestBuilder, project: Project) : + BaseRequest>(builder, project) { + override val logMessage: String + get() = "Requesting project stubs and wrappers from server" + override val id: String + get() = "Sync project stubs and wrappers" + + override suspend fun Flow.handle(cancellationJob: Job?) { + this.onEach { + if (it.stubSourcesCount > 0) { + it.stubSourcesList.map { grpcSC -> + SourceCode(grpcSC, project).apply { + project.logger.trace { "Creating file $localPath" } + createFileWithText(localPath, content) + } + } + } + }.collect() + + markDirtyAndRefresh(project.nioPath) + } + + override suspend fun TestsGenServiceGrpcKt.TestsGenServiceCoroutineStub.send(cancellationJob: Job?): Flow { + return this.generateProjectStubs(request) + } +} \ No newline at end of file diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/requests/test/BaseTestsRequest.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/requests/test/BaseTestsRequest.kt index 329c38b46..48b42ec9e 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/requests/test/BaseTestsRequest.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/requests/test/BaseTestsRequest.kt @@ -5,7 +5,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import org.utbot.cpp.clion.plugin.UTBot import org.utbot.cpp.clion.plugin.actions.FocusAction -import org.utbot.cpp.clion.plugin.client.handlers.TestsStreamHandler +import org.utbot.cpp.clion.plugin.client.handlers.testsStreamHandler.TestsStreamHandler import org.utbot.cpp.clion.plugin.client.requests.BaseRequest import org.utbot.cpp.clion.plugin.grpc.GrpcRequestBuilder import org.utbot.cpp.clion.plugin.utils.getLongestCommonPathFromRoot diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotProjectStoredSettings.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotProjectStoredSettings.kt index ca2c9e596..ae83b714e 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotProjectStoredSettings.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotProjectStoredSettings.kt @@ -41,7 +41,8 @@ class UTBotProjectStoredSettings(val project: Project) : PersistentStateComponen var verbose: Boolean = false, var timeoutPerFunction: Int = 30, var timeoutPerTest: Int = 0, - var isPluginEnabled: Boolean = false + var isPluginEnabled: Boolean = false, + var isGTestInstalled: Boolean = false ) { fun fromSettingsModel(model: UTBotSettingsModel) { buildDirRelativePath = model.projectSettings.buildDirRelativePath @@ -144,19 +145,25 @@ class UTBotProjectStoredSettings(val project: Project) : PersistentStateComponen } } - var sourceDirs: Set get() { - return state.sourceDirs - } - set(value) { - state.sourceDirs = value - } + var isGTestInstalled: Boolean + get() = myState.isGTestInstalled + set(value) { + myState.isGTestInstalled = value + } + + var sourceDirs: Set + get() { + return state.sourceDirs + } + set(value) { + state.sourceDirs = value + } private fun isTargetUpToDate(): Boolean { return project.service().isTargetUpToDate(myState.targetPath) } - override fun getState() = myState override fun loadState(state: State) { myState = state diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/statusBar/StatusBarConnectionStatus.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/statusBar/StatusBarConnectionStatus.kt index ade0ee89f..e1c16ca17 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/statusBar/StatusBarConnectionStatus.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/statusBar/StatusBarConnectionStatus.kt @@ -22,6 +22,7 @@ import org.utbot.cpp.clion.plugin.actions.ReconnectAction import org.utbot.cpp.clion.plugin.actions.configure.ConfigureProjectAction import org.utbot.cpp.clion.plugin.actions.configure.ReconfigureProjectAction import org.utbot.cpp.clion.plugin.actions.ShowWizardAction +import org.utbot.cpp.clion.plugin.actions.SyncWrappersAndStubsAction import org.utbot.cpp.clion.plugin.actions.TogglePluginAction import org.utbot.cpp.clion.plugin.client.ManagedClient import org.utbot.cpp.clion.plugin.listeners.ConnectionStatus diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/utils/NotificationUtils.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/utils/NotificationUtils.kt index d8081d465..31d2acd50 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/utils/NotificationUtils.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/utils/NotificationUtils.kt @@ -21,6 +21,7 @@ fun notifyWarning(title: String, warningText: String, project: Project? = null, fun notifyUnknownResponse(response: Testgen.ProjectConfigResponse, project: Project) = notifyError(UTBot.message("notify.title.error"), "Unknown server response: ${response.message}", project) +// can be called from background thread private fun notify( type: NotificationType, title: String, diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/utils/PathUtils.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/utils/PathUtils.kt index e9ad53d94..92219456a 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/utils/PathUtils.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/utils/PathUtils.kt @@ -56,6 +56,8 @@ fun Path.visitAllDirectories(action: (Path) -> Unit) { fun Path.isSarifReport() = this.fileName.toString().endsWith(".sarif") +fun Path.isCMakeListsFile() = this.fileName.toString() == "CMakeLists.txt" + fun String.fileNameOrNull(): String? { return try { Paths.get(this).fileName.toString() diff --git a/clion-plugin/src/main/resources/META-INF/plugin.xml b/clion-plugin/src/main/resources/META-INF/plugin.xml index 2aa444fab..0503f8503 100644 --- a/clion-plugin/src/main/resources/META-INF/plugin.xml +++ b/clion-plugin/src/main/resources/META-INF/plugin.xml @@ -203,5 +203,8 @@ description="Turns on/off verbose formatting for tests"> + diff --git a/clion-plugin/src/main/resources/messages/UTBot.properties b/clion-plugin/src/main/resources/messages/UTBot.properties index 8e53c23a1..f3762d9db 100644 --- a/clion-plugin/src/main/resources/messages/UTBot.properties +++ b/clion-plugin/src/main/resources/messages/UTBot.properties @@ -95,6 +95,7 @@ notify.cancelled.request.title=UTBot: Request cancelled notify.cancelled.request=Successfully cancelled: "{0}" notify.connected.title=UTBot: Connection Established notify.connected=Successfully pinged UTBot server on port: {0, number, #}, host: {1} +notify.error.write.to.file=Could not write to file: {0} notify.disconnected.title=UTBot: No Connection notify.disconnected=Please check your connection settings #0 - name of a json file missing in the build dir (link_commands.json or compile_commands.json) @@ -111,3 +112,4 @@ actions.verbose.menu.disabled=Verbose Mode: Off actions.verbose.enabled=UTBot: Verbose Mode: Enabled actions.verbose.disabled=UTBot: Verbose Mode: Enable actions.settings.text=Settings... +dialog.should.install.gtest=Do you want UTBot plugin to install GTest? If you already have GTest installed, skip this dialog. \ No newline at end of file diff --git a/server/src/KleeRunner.cpp b/server/src/KleeRunner.cpp index eadd56153..a1d91c852 100644 --- a/server/src/KleeRunner.cpp +++ b/server/src/KleeRunner.cpp @@ -54,7 +54,8 @@ void KleeRunner::runKlee(const std::vector &testMethods, TestsWriter *testsWriter, bool isBatched, bool interactiveMode, - StatsUtils::TestsGenerationStatsFileMap &generationStats) { + StatsUtils::TestsGenerationStatsFileMap &generationStats, + const FileInfoForTransfer &generatedCMake) { LOG_SCOPE_FUNCTION(DEBUG); fs::path kleeOutDir = Paths::getKleeOutDir(projectContext); @@ -115,6 +116,10 @@ void KleeRunner::runKlee(const std::vector &testMethods, testsWriter->writeReport(sarif::sarifPackResults(sarifResults), "Sarif Report was created", Paths::getUTBotReportDir(projectContext) / sarif::SARIF_FILE_NAME); + testsWriter->writeFile(generatedCMake.code, + "CMake file was created", + generatedCMake.filePath + ); }; testsWriter->writeTestsWithProgress( diff --git a/server/src/KleeRunner.h b/server/src/KleeRunner.h index 37b4115e1..e9c9f45fd 100644 --- a/server/src/KleeRunner.h +++ b/server/src/KleeRunner.h @@ -12,6 +12,7 @@ #include #include +#include class KleeRunner { public: @@ -33,7 +34,8 @@ class KleeRunner { const std::unordered_map &methodNameToReturnTypeMap, const std::shared_ptr &lineInfo, TestsWriter *testsWriter, bool isBatched, bool interactiveMode, - StatsUtils::TestsGenerationStatsFileMap &generationStats); + StatsUtils::TestsGenerationStatsFileMap &generationStats, + const FileInfoForTransfer &generatedCMake); private: const utbot::ProjectContext projectContext; diff --git a/server/src/Paths.h b/server/src/Paths.h index 09a3a95a7..e0f662f40 100644 --- a/server/src/Paths.h +++ b/server/src/Paths.h @@ -268,6 +268,10 @@ namespace Paths { return CollectionUtils::contains(CFileSourceExtensions, path.extension()); } + static inline bool isCMakeListsFile(const fs::path &path) { + return path.filename() == "CMakeLists.txt"; + } + extern const std::vector CXXFileExtensions; static inline bool isCXXFile(const fs::path &path) { diff --git a/server/src/Server.cpp b/server/src/Server.cpp index fd5721d18..689ffdcee 100644 --- a/server/src/Server.cpp +++ b/server/src/Server.cpp @@ -273,7 +273,7 @@ Status Server::TestsGenServiceImpl::ProcessBaseTestRequest(BaseTestGen &testGen, generationStartTime - preprocessingStartTime)); kleeRunner.runKlee(testMethods, testGen.tests, generator, testGen.methodNameToReturnTypeMap, - lineInfo, testsWriter, testGen.isBatched(), interactiveMode, generationStatsMap); + lineInfo, testsWriter, testGen.isBatched(), interactiveMode, generationStatsMap, linker.getGeneratedCMakeFile()); LOG_S(INFO) << "KLEE time: " << std::chrono::duration_cast (generationStatsMap.getTotal().kleeStats.getKleeTime()).count() << " ms\n"; printer::CSVPrinter printer = generationStatsMap.toCSV(); @@ -557,7 +557,8 @@ Status Server::TestsGenServiceImpl::ProcessProjectStubsRequest(BaseTestGen *test fetcher.fetchWithProgress(testGen->progressWriter, logMessage); Synchronizer synchronizer(testGen, &sizeContext); synchronizer.synchronize(typesHandler); - stubsWriter->writeResponse(testGen->synchronizedStubs, testGen->projectContext.testDirPath); + stubsWriter->writeResponse(testGen->synchronizedStubs, testGen->projectContext.testDirPath, "Sending stubs"); + stubsWriter->writeResponse(testGen->synchronizedWrappers, testGen->projectContext.testDirPath, "Sending wrappers"); return Status::OK; } diff --git a/server/src/Synchronizer.cpp b/server/src/Synchronizer.cpp index 16da36497..ddb49c2a4 100644 --- a/server/src/Synchronizer.cpp +++ b/server/src/Synchronizer.cpp @@ -207,15 +207,19 @@ void Synchronizer::synchronizeWrappers(const CollectionUtils::FileSet &outdatedS } } } - ExecUtils::doWorkWithProgress( - sourceFilesNeedToRegenerateWrappers, testGen->progressWriter, - "Generating wrappers", [this](fs::path const &sourceFilePath) { - SourceToHeaderRewriter sourceToHeaderRewriter(testGen->projectContext, - testGen->getProjectBuildDatabase()->compilationDatabase, nullptr, - testGen->serverBuildDir); - std::string wrapper = sourceToHeaderRewriter.generateWrapper(sourceFilePath); - printer::SourceWrapperPrinter(Paths::getSourceLanguage(sourceFilePath)).print(testGen->projectContext, sourceFilePath, wrapper); - }); + + for (auto &sourceFileForWrapper : sourceFilesNeedToRegenerateWrappers) { + SourceToHeaderRewriter sourceToHeaderRewriter(testGen->projectContext, + testGen->getProjectBuildDatabase()->compilationDatabase, nullptr, + testGen->serverBuildDir); + std::string wrapper = sourceToHeaderRewriter.generateWrapper(sourceFileForWrapper); + if (!Paths::isCXXFile(sourceFileForWrapper)) { + auto content = printer::SourceWrapperPrinter(Paths::getSourceLanguage(sourceFileForWrapper)) + .getFinalContent(testGen->projectContext, sourceFileForWrapper, wrapper); + FileSystemUtils::writeToFile(Paths::getWrapperFilePath(testGen->projectContext, sourceFileForWrapper), content); + testGen->synchronizedWrappers.emplace_back(Paths::getWrapperFilePath(testGen->projectContext, sourceFileForWrapper), content); + } + } } const CollectionUtils::FileSet &Synchronizer::getTargetSourceFiles() const { diff --git a/server/src/building/BaseCommand.cpp b/server/src/building/BaseCommand.cpp index 0e9a70574..1eeafea72 100644 --- a/server/src/building/BaseCommand.cpp +++ b/server/src/building/BaseCommand.cpp @@ -1,18 +1,12 @@ #include "BaseCommand.h" -#include "CompileCommand.h" -#include "LinkCommand.h" #include "Paths.h" #include "printers/CCJsonPrinter.h" -#include "tasks/ShellExecTask.h" #include "utils/CollectionUtils.h" #include "utils/StringUtils.h" #include "utils/path/FileSystemPath.h" -#include "loguru.h" - #include -#include #include #include diff --git a/server/src/building/BuildDatabase.cpp b/server/src/building/BuildDatabase.cpp index f8b4e56cc..fd8bdb699 100644 --- a/server/src/building/BuildDatabase.cpp +++ b/server/src/building/BuildDatabase.cpp @@ -535,7 +535,7 @@ fs::path BuildDatabase::newDirForFile(const fs::path &file) const { return Paths::createNewDirForFile(file, base, this->serverBuildDir); } -CollectionUtils::FileSet BuildDatabase::getSourceFilesForTarget(const fs::path &_target) { +CollectionUtils::FileSet BuildDatabase::getSourceFilesForTarget(const fs::path &_target) const { return CollectionUtils::transformTo( getArchiveObjectFiles(_target), [this](fs::path const &objectPath) { diff --git a/server/src/building/BuildDatabase.h b/server/src/building/BuildDatabase.h index 49902297f..8861c21dd 100644 --- a/server/src/building/BuildDatabase.h +++ b/server/src/building/BuildDatabase.h @@ -226,7 +226,7 @@ class BuildDatabase { std::shared_ptr getPriorityTarget() const; - CollectionUtils::FileSet getSourceFilesForTarget(const fs::path &_target); + CollectionUtils::FileSet getSourceFilesForTarget(const fs::path &_target) const; std::shared_ptr getTargetInfo(const fs::path &_target); diff --git a/server/src/building/CMakeGenerator.cpp b/server/src/building/CMakeGenerator.cpp new file mode 100644 index 000000000..9d5365bf1 --- /dev/null +++ b/server/src/building/CMakeGenerator.cpp @@ -0,0 +1,133 @@ +#include "Synchronizer.h" +#include "building/Linker.h" +#include "utils/FileSystemUtils.h" +#include "utils/Copyright.h" +#include +#include +#include +#include "CMakeGenerator.h" + +FileInfoForTransfer CMakeGenerator::getResult() { + return {testGen->projectContext.testDirPath / "CMakeLists.txt", printer.ss.str()}; +} + +void CMakeGenerator::generate(const fs::path &target, const CollectionUtils::FileSet &stubsSet, + const CollectionUtils::FileSet &presentedFiles, const CollectionUtils::FileSet &stubSources) { + generateCMakeForTargetRecursively(target, stubsSet, stubSources); + addTests(presentedFiles, target, stubsSet); + printer.write(testGen->projectContext.testDirPath / "CMakeLists.txt"); +} + +void CMakeGenerator::generateCMakeForTargetRecursively(const fs::path &target, + const CollectionUtils::FileSet &stubsSet, + const CollectionUtils::FileSet &stubSources) { + addLinkTargetRecursively(target, true, stubsSet, stubSources); +} + +std::shared_ptr CMakeGenerator::getTargetUnitInfo(const fs::path &targetPath) { + auto targetBuildDb = testGen->getTargetBuildDatabase(); + return targetBuildDb->getClientLinkUnitInfo(targetPath); +} + +void CMakeGenerator::addLinkTargetRecursively(const fs::path &path, bool isRoot, + const CollectionUtils::FileSet &stubsSet, + const CollectionUtils::FileSet &stubSources) { + if (CollectionUtils::contains(alreadyBuildFiles, path)) { + return; + } + auto targetInfo = getTargetUnitInfo(path); + std::vector dependentLibs; + std::vector dependentSourceFiles; + for (auto &file: targetInfo->files) { + if (Paths::isObjectFile(file)) { + auto objectInfo = testGen->getClientCompilationUnitInfo(file); + auto fileToAdd = objectInfo->getSourcePath(); + if (CollectionUtils::contains(stubSources, fileToAdd)) { + fileToAdd = Paths::sourcePathToStubPath(testGen->projectContext, fileToAdd); + } else if (Paths::isCFile(fileToAdd)) { + // todo: wrappers help us to replace main to main_, what to do for c++ files? + fileToAdd = Paths::getWrapperFilePath(testGen->projectContext, fileToAdd); + } + dependentSourceFiles.push_back(fileToAdd); + alreadyBuildFiles.insert(file); + } else { + dependentLibs.push_back(file); + } + } + auto isExecutable = !Paths::isLibraryFile(path); + auto linkWithStubs = isRoot || isExecutable || Paths::isSharedLibraryFile(path); + if (linkWithStubs) { + for (auto &stubFile: stubsSet) { + dependentSourceFiles.push_back(stubFile); + } + } + auto libName = getLibraryName(path, isRoot); + printer.addLibrary(libName, /*isShared=*/linkWithStubs, + CollectionUtils::transform(dependentSourceFiles, [&](const fs::path &file) { + return fs::relative(file, testGen->projectContext.testDirPath); + })); + printer.addIncludeDirectoriesForTarget(libName, getIncludeDirectoriesFor(path)); + alreadyBuildFiles.insert(path); + if (!dependentLibs.empty()) { + for (auto &lib: dependentLibs) { + addLinkTargetRecursively(lib, false, stubsSet, stubSources); + } + printer.addTargetLinkLibraries(libName, CollectionUtils::transformTo>(dependentLibs, [&](const fs::path &lib){ + return getLibraryName(lib, false); + })); + } +} + +std::set CMakeGenerator::getIncludeDirectoriesFor(const fs::path &target) { + auto targetInfo = getTargetUnitInfo(target); + std::set res; + for (auto &file: targetInfo->files) { + if (!Paths::isObjectFile(file)) + continue; + auto objectInfo = testGen->getClientCompilationUnitInfo(file); + for (auto &arg: objectInfo->command.getCommandLine()) { + if (StringUtils::startsWith(arg, "-I")) { + res.insert(getAbsolutePath(arg.substr(2)).string()); + } + } + } + return res; +} + +std::filesystem::path CMakeGenerator::getAbsolutePath(const std::filesystem::path &path) { + // std::path is used here because fs::path is normalized in constructor, and it leads to the following behaviour + // ${cmake_path}/../a/b -> a/b, which is not what we want + auto relativeFromProjectToPath = path.lexically_relative(testGen->projectContext.projectPath.string()); + auto relativeFromTestsToProject = std::filesystem::path( + testGen->projectContext.projectPath.string()).lexically_relative( + testGen->projectContext.testDirPath.string()); + auto res = std::filesystem::path("${CMAKE_CURRENT_SOURCE_DIR}") / relativeFromTestsToProject / + relativeFromProjectToPath; + return res; +} + +std::string CMakeGenerator::getLibraryName(const fs::path &lib, bool isRoot) { + return lib.stem().string() + + (isRoot && Paths::isStaticLibraryFile(lib) ? "_shared" : "") + "_utbot"; +} + +void CMakeGenerator::addTests(const CollectionUtils::FileSet &filesUnderTest, const fs::path &target, + const CollectionUtils::FileSet &stubSet) { + std::vector testFiles; + for (auto &file: filesUnderTest) { + testFiles.push_back(Paths::sourcePathToTestPath(testGen->projectContext, file)); + } + auto testsTargetName = "utbot_tests"; + printer.addExecutable(testsTargetName, + CollectionUtils::transformTo>(filesUnderTest, + [&](const fs::path &elem) { + return fs::relative(Paths::sourcePathToTestPath(testGen->projectContext, elem), testGen->projectContext.testDirPath); })); + printer.addTargetLinkLibraries(testsTargetName, {GTEST_TARGET_NAME, getRootLibraryName(target)}); + printer.addDiscoverTestDirective(testsTargetName); +} + +std::string CMakeGenerator::getRootLibraryName(const fs::path &path) { + return getLibraryName(path, true); +} + +CMakeGenerator::CMakeGenerator(const BaseTestGen *testGen) : testGen(testGen), printer(printer::CMakeListsPrinter()) {} diff --git a/server/src/building/CMakeGenerator.h b/server/src/building/CMakeGenerator.h new file mode 100644 index 000000000..6cee320cd --- /dev/null +++ b/server/src/building/CMakeGenerator.h @@ -0,0 +1,54 @@ +// +// Created by Арсений Волынец on 13.09.2022. +// + +#ifndef UTBOTCPP_CMAKEGENERATOR_H +#define UTBOTCPP_CMAKEGENERATOR_H + +#include "testgens/BaseTestGen.h" +#include "BuildResult.h" +#include "printers/CMakeListsPrinter.h" + +#include +#include +#include + +class CMakeGenerator { +public: + CMakeGenerator() = delete; + CMakeGenerator(const CMakeGenerator &other) = delete; + explicit CMakeGenerator(const BaseTestGen* testGen); + + static inline std::string GTEST_TARGET_NAME = "GTest::gtest_main"; + + CollectionUtils::FileSet alreadyBuildFiles; + printer::CMakeListsPrinter printer; + + void generate(const fs::path &target, const CollectionUtils::FileSet &stubsSet, + const CollectionUtils::FileSet &presentedFiles, + const CollectionUtils::FileSet &stubSources); + + FileInfoForTransfer getResult(); + + void addLinkTargetRecursively(const fs::path &path, bool isRoot, const CollectionUtils::FileSet &stubsSet, const CollectionUtils::FileSet &stubSourcesFromProject); + + void addTests(const CollectionUtils::FileSet &filesUnderTest, const fs::path &target, + const CollectionUtils::FileSet &stubSet); + + void generateCMakeForTargetRecursively(const fs::path &target, const CollectionUtils::FileSet& stubsSet, const CollectionUtils::FileSet &stubSources); + + std::set getIncludeDirectoriesFor(const fs::path &target); + + std::string getLibraryName(const fs::path &lib, bool isRoot); + + std::string getRootLibraryName(const fs::path &path); + + std::shared_ptr getTargetUnitInfo(const fs::path &targetPath); + + const BaseTestGen *testGen; + +private: + std::filesystem::path getAbsolutePath(const std::filesystem::path &path); +}; + +#endif //UTBOTCPP_CMAKEGENERATOR_H diff --git a/server/src/building/Linker.cpp b/server/src/building/Linker.cpp index 3496a860d..2c94b0022 100644 --- a/server/src/building/Linker.cpp +++ b/server/src/building/Linker.cpp @@ -5,12 +5,10 @@ #include "Synchronizer.h" #include "RunCommand.h" #include "environment/EnvironmentPaths.h" -#include "exceptions/ExecutionProcessException.h" #include "exceptions/FileNotPresentedInCommandsException.h" #include "exceptions/FileNotPresentedInArtifactException.h" #include "exceptions/NoTestGeneratedException.h" #include "printers/DefaultMakefilePrinter.h" -#include "printers/NativeMakefilePrinter.h" #include "stubs/StubGen.h" #include "testgens/FileTestGen.h" #include "testgens/FolderTestGen.h" @@ -25,6 +23,7 @@ #include "utils/path/FileSystemPath.h" #include "loguru.h" +#include "CMakeGenerator.h" #include #include @@ -103,9 +102,15 @@ void Linker::linkForOneFile(const fs::path &sourceFilePath) { LOG_S(DEBUG) << "Trying target: " << target.filename(); auto result = linkForTarget(target, sourceFilePath, compilationUnitInfo, objectFile); if (result.isSuccess()) { - auto [targetBitcode, stubsSet, _] = result.getOpt().value(); + auto [targetBitcode, stubsSet, _, stubSources] = result.getOpt().value(); addToGenerated({ objectFile }, targetBitcode); + auto cmakeGen = CMakeGenerator(&testGen); + CollectionUtils::FileSet presented; + presented.insert(sourceFilePath); + cmakeGen.generate(target, stubsSet, presented, stubSources); + generatedCMakeFile = cmakeGen.getResult(); auto&& targetUnitInfo = testGen.getTargetBuildDatabase()->getClientLinkUnitInfo(target); + addToGenerated({ objectFile }, targetBitcode); return; } else { LOG_S(DEBUG) << "Linkage for target " << target.filename() << " failed: " << result.getError()->c_str(); @@ -193,6 +198,9 @@ void Linker::linkForProject() { return compilationUnitInfo->getOutputFile(); }); addToGenerated(objectFiles, linkres.bitcodeOutput); + auto cmakeGen = CMakeGenerator(&testGen); + cmakeGen.generate(target, linkres.stubsSet, linkres.presentedFiles, linkres.stubSources); + generatedCMakeFile = cmakeGen.getResult(); break; } else { std::stringstream ss; @@ -407,7 +415,7 @@ Result Linker::link(const CollectionUtils::MapFileTo &dependencies, fs::pa Paths::longestCommonPrefixPath); return std::max(prefixPath, defaultPath); } + +FileInfoForTransfer Linker::getGeneratedCMakeFile() { + return generatedCMakeFile; +} diff --git a/server/src/building/Linker.h b/server/src/building/Linker.h index 66aa01d13..bf21da0a1 100644 --- a/server/src/building/Linker.h +++ b/server/src/building/Linker.h @@ -19,6 +19,7 @@ #include #include #include +#include class Linker { public: @@ -41,10 +42,13 @@ class Linker { const std::optional &testedFilePath, bool shouldChangeDirectory = false); + FileInfoForTransfer getGeneratedCMakeFile(); + struct LinkResult { fs::path bitcodeOutput; CollectionUtils::FileSet stubsSet; CollectionUtils::FileSet presentedFiles; + CollectionUtils::FileSet stubSources; }; private: BaseTestGen &testGen; @@ -58,6 +62,8 @@ class Linker { IRParser irParser; + FileInfoForTransfer generatedCMakeFile; + fs::path getSourceFilePath(); bool isForOneFile(); @@ -76,7 +82,6 @@ class Linker { const CollectionUtils::FileSet &stubSources, bool errorOnMissingBitcode = true); - void checkSiblingsExist(const CollectionUtils::FileSet &archivedFiles) const; void addToGenerated(const CollectionUtils::FileSet &objectFiles, const fs::path &output); fs::path getPrefixPath(const std::vector &dependencies, fs::path defaultPath) const; diff --git a/server/src/building/UserProjectConfiguration.cpp b/server/src/building/UserProjectConfiguration.cpp index 30888594b..3129d7a9a 100644 --- a/server/src/building/UserProjectConfiguration.cpp +++ b/server/src/building/UserProjectConfiguration.cpp @@ -99,6 +99,8 @@ UserProjectConfiguration::RunProjectConfigurationCommands(const fs::path &buildD fs::path cmakeListsPath = getCmakeListsPath(buildDirPath); if (fs::exists(cmakeListsPath)) { LOG_S(INFO) << "Configure cmake project"; + // remove sections that are not needed to be analyzed for project building by utbot + prepareCMakeListsFile(cmakeListsPath); RunProjectConfigurationCommand(buildDirPath, cmakeParams, projectContext, writer); } else { LOG_S(INFO) << "CMakeLists.txt not found in root project directory: " @@ -116,6 +118,22 @@ UserProjectConfiguration::RunProjectConfigurationCommands(const fs::path &buildD return Status::OK; } +void UserProjectConfiguration::prepareCMakeListsFile(const fs::path &path) { + std::ifstream ifs(path); + std::stringstream ss; + auto ignoreText = false; + for(std::string line; std::getline(ifs, line); ) { + if (StringUtils::contains(line, "utbot_section_start")) + ignoreText = true; + else if (ignoreText && StringUtils::contains(line, "utbot_section_end")) + ignoreText = false; + if (ignoreText) + continue; + ss << line << "\n"; + } + FileSystemUtils::writeToFile(path, ss.str()); +} + void UserProjectConfiguration::RunProjectConfigurationCommand(const fs::path &buildDirPath, const ShellExecTask::ExecutionParameters ¶ms, const utbot::ProjectContext &projectContext, diff --git a/server/src/building/UserProjectConfiguration.h b/server/src/building/UserProjectConfiguration.h index 1fe6435c6..ffed46e7b 100644 --- a/server/src/building/UserProjectConfiguration.h +++ b/server/src/building/UserProjectConfiguration.h @@ -49,6 +49,8 @@ class UserProjectConfiguration { static fs::path createBearShScript(const fs::path &buildDirPath); static bool createBuildDirectory(const fs::path &buildDirPath, ProjectConfigWriter const &writer); + + static void prepareCMakeListsFile(const fs::path &path); }; diff --git a/server/src/printers/CMakeListsPrinter.cpp b/server/src/printers/CMakeListsPrinter.cpp new file mode 100644 index 000000000..341a5e32f --- /dev/null +++ b/server/src/printers/CMakeListsPrinter.cpp @@ -0,0 +1,77 @@ +#include +#include "CMakeListsPrinter.h" +#include "utils/Copyright.h" +#include "utils/FileSystemUtils.h" +#include "Synchronizer.h" +#include "loguru.h" + +namespace printer { + void CMakeListsPrinter::addDiscoverTestDirective(const std::string &testTargetName) { + ss << "gtest_discover_tests(" << testTargetName << ")" << NL; + } + + void CMakeListsPrinter::addLibrary(const std::string &libraryName, bool isShared, const std::vector &sourceFiles) { + if (sourceFiles.empty()) + return; + ss << "add_library" << LB(); + ss << libraryName << NL; + if (isShared) + ss << LINE_INDENT() << "SHARED" << NL; + for (auto &file : sourceFiles) { + ss << LINE_INDENT() << file.string() << NL; + } + ss << RB() << NL; + } + + void CMakeListsPrinter::write(const fs::path &path) { + FileSystemUtils::writeToFile(path, ss.str()); + } + + void CMakeListsPrinter::addCopyrightHeader() { + ss << Copyright::GENERATED_CMAKELISTS_FILE_HEADER << NL; + } + + std::string CMakeListsPrinter::RB() { + tabsDepth--; + return LINE_INDENT() + ")\n"; + } + + std::string CMakeListsPrinter::LB(bool startsWithSpace) { + tabsDepth++; + return std::string(startsWithSpace ? " " : "") + "(\n" + LINE_INDENT(); + } + + void CMakeListsPrinter::addIncludeDirectoriesForTarget(const std::string &targetName, + const std::set includePaths) { + if (includePaths.empty()) + return; + ss << "target_include_directories" << LB() << targetName << " PUBLIC" << NL; + for (auto &includePath : includePaths) { + ss << LINE_INDENT() << includePath << NL; + } + ss << RB() << NL; + } + + void CMakeListsPrinter::addTargetLinkLibraries(const std::string &targetName, + const std::vector &librariesNamesToLink) { + if (librariesNamesToLink.empty()) + return; + ss << "target_link_libraries" << LB() << targetName << NL; + for (auto &lib : librariesNamesToLink) { + ss << LINE_INDENT() << lib << NL; + } + ss << RB() << NL; + } + + CMakeListsPrinter::CMakeListsPrinter() { addCopyrightHeader(); } + + void CMakeListsPrinter::addExecutable(const std::string &executableName, const std::vector &sourceFiles) { + if (sourceFiles.empty()) + return; + ss << "add_executable" << LB() << executableName << NL; + for (auto &subFile : sourceFiles) { + ss << LINE_INDENT() << subFile.string() << NL; + } + ss << RB() << NL; + } +} diff --git a/server/src/printers/CMakeListsPrinter.h b/server/src/printers/CMakeListsPrinter.h new file mode 100644 index 000000000..602f84af1 --- /dev/null +++ b/server/src/printers/CMakeListsPrinter.h @@ -0,0 +1,109 @@ +#ifndef UTBOTCPP_CMAKELISTSPRINTER_H +#define UTBOTCPP_CMAKELISTSPRINTER_H + +#include "Printer.h" +#include "testgens/BaseTestGen.h" +#include "BuildResult.h" + +#include +#include +#include + + +namespace printer { + class CMakeListsPrinter { + public: + CMakeListsPrinter(); + + std::string SANITIZER_FLAGS_VAR_NAME="SANITIZER_FLAGS"; + std::string SANITIZER_LINK_FLAGS_VAR_NAME="SANITIZER_LINK_FLAGS"; + std::string COVERAGE_LINK_FLAGS_VAR_NAME="COVERAGE_LINK_FLAGS"; + using std_path = std::filesystem::path; + + + std::string LB(bool startsWithSpace = false); + + std::string RB(); + + static inline std::string GTEST_TARGET_NAME = "GTest::gtest_main"; + + std::stringstream ss; + int tabsDepth = 0; + + using FileToObjectInfo = CollectionUtils::MapFileTo>; + + inline std::string LINE_INDENT() const { + return StringUtils::repeat(TAB, tabsDepth); + } + + void addTargetLinkLibraries(const std::string &targetName, const std::vector& librariesNamesToLink); + + void addDiscoverTestDirective(const std::string &testTargetName); + + void addExecutable(const std::string &executableName, const std::vector &sourceFiles); + + void addIncludeDirectoriesForTarget(const std::string &targetName, const std::set includePaths); + + void addLibrary(const std::string &libraryName, bool isShared, const std::vector &sourceFiles); + + void addLinkTargetRecursively(const fs::path &path, bool isRoot, const CollectionUtils::FileSet &stubsSet); + + void + addTests(const CollectionUtils::FileSet &filesUnderTest, const fs::path &target, + const CollectionUtils::FileSet &stubSet); + + void generateCMakeForTargetRecursively(const fs::path &target, const CollectionUtils::FileSet& stubsSet); + + void generateCMakeLists(const CollectionUtils::FileSet &testsSourcePaths); + + std_path getAbsolutePath(const fs::path &path); + + std::set getIncludeDirectoriesFor(const fs::path &target); + + std::string getLibraryName(const fs::path &lib, bool isRoot); + + fs::path getRelativePath(const fs::path &path); + + std::string getRootLibraryName(const fs::path &path); + + std::shared_ptr getTargetUnitInfo(const fs::path &targetPath); + + void setLinkOptionsForTarget(const std::string &targetName, const std::string &options); + + const BaseTestGen *testGen; + + std::string wrapCMakeVariable(const std::string &variableName); + + void write(const fs::path &path); + + protected: + void addCopyrightHeader(); + + private: + fs::path getTargetCmakePath(const fs::path &lib); + void addInclude(const fs::path &cmakeFileToInclude); + + void addOptionsForSourceFiles(const FileToObjectInfo &sourceFiles); + + void addLinkFlagsForLibrary(const std::string &targetName, const fs::path &targetPath, bool transformExeToLib = false); + fs::path getCMakeFileForTestFile(const fs::path &testFile); + + std::string getTestName(const fs::path &test); + + void addTestExecutable(const fs::path &path, const CollectionUtils::FileSet &stubs, const fs::path &target); + + void addLinkOptionsForTestTarget(const std::string &testName, const fs::path &target); + + void addVariable(const std::string &varName, const std::string &value); + + void tryChangeToAbsolute(std::string &argument); + + + void setCompileOptionsForSource(const fs::path &sourceFile, const std::string &options); + + std::list prepareCompileFlagsForTestFile(const fs::path &sourcePath); + + }; +} + +#endif //UTBOTCPP_CMAKELISTSPRINTER_H diff --git a/server/src/printers/NativeMakefilePrinter.cpp b/server/src/printers/NativeMakefilePrinter.cpp index 17dede56a..4957b18c7 100644 --- a/server/src/printers/NativeMakefilePrinter.cpp +++ b/server/src/printers/NativeMakefilePrinter.cpp @@ -22,42 +22,42 @@ namespace printer { static const std::string STUB_OBJECT_FILES_NAME = "STUB_OBJECT_FILES"; static const std::string STUB_OBJECT_FILES = "$(STUB_OBJECT_FILES)"; - static const std::string FPIC_FLAG = "-fPIC"; - static const std::vector SANITIZER_NEEDED_FLAGS = { - "-g", "-fno-omit-frame-pointer", "-fno-optimize-sibling-calls" + const std::string FPIC_FLAG = "-fPIC"; + const std::vector SANITIZER_NEEDED_FLAGS = { + "-g", "-fno-omit-frame-pointer", "-fno-optimize-sibling-calls" }; - static const std::string STATIC_FLAG = "-static"; - static const std::string SHARED_FLAG = "-shared"; - static const std::string RELOCATE_FLAG = "-r"; - static const std::string OPTIMIZATION_FLAG = "-O0"; - static const std::unordered_set UNSUPPORTED_FLAGS_AND_OPTIONS_TEST_MAKE = { - // See https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html - "-ansi", - "-fallow-parameterless-variadic-functions", - "-fallow-single-precision", - "-fcond-mismatch", - "-ffreestanding", - "-fgnu89-inline", - "-fhosted", - "-flax-vector-conversions", - "-fms-extensions", - "-fno-asm", - "-fno-builtin", - "-fno-builtin-function", - "-fgimple", - "-fopenacc", - "-fopenacc-dim", - "-fopenacc-kernels", - "-fopenmp", - "-fopenmp-simd", - "-fpermitted-flt-eval-methods", - "-fplan9-extensions", - "-fsigned-bitfields", - "-fsigned-char", - "-fsso-struct", - "-funsigned-bitfields", - "-funsigned-char", - "-std", + const std::string STATIC_FLAG = "-static"; + const std::string SHARED_FLAG = "-shared"; + const std::string RELOCATE_FLAG = "-r"; + const std::string OPTIMIZATION_FLAG = "-O0"; + const std::unordered_set UNSUPPORTED_FLAGS_AND_OPTIONS_TEST_MAKE = { + // See https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html + "-ansi", + "-fallow-parameterless-variadic-functions", + "-fallow-single-precision", + "-fcond-mismatch", + "-ffreestanding", + "-fgnu89-inline", + "-fhosted", + "-flax-vector-conversions", + "-fms-extensions", + "-fno-asm", + "-fno-builtin", + "-fno-builtin-function", + "-fgimple", + "-fopenacc", + "-fopenacc-dim", + "-fopenacc-kernels", + "-fopenmp", + "-fopenmp-simd", + "-fpermitted-flt-eval-methods", + "-fplan9-extensions", + "-fsigned-bitfields", + "-fsigned-char", + "-fsso-struct", + "-funsigned-bitfields", + "-funsigned-char", + "-std", }; static void eraseIfWlOnly(std::string &argument) { @@ -577,6 +577,7 @@ namespace printer { const fs::path relativeDir = getRelativePath(linkCommand.getDirectory()); if (isExecutable && !transformExeToLib) { + // todo: how to translate objcopy to cmake? return stringFormat("%s && objcopy --redefine-sym main=main__ %s", linkCommand.toStringWithChangingDirectoryToNew(relativeDir), linkCommand.getOutput().string()); diff --git a/server/src/printers/NativeMakefilePrinter.h b/server/src/printers/NativeMakefilePrinter.h index fa40cc015..1e215c772 100644 --- a/server/src/printers/NativeMakefilePrinter.h +++ b/server/src/printers/NativeMakefilePrinter.h @@ -10,8 +10,8 @@ #include namespace printer { - static const std::string FORCE = ".FORCE"; + static const std::string FORCE = ".FORCE"; class NativeMakefilePrinter : public RelativeMakefilePrinter { friend class TestMakefilesPrinter; private: diff --git a/server/src/printers/SourceWrapperPrinter.cpp b/server/src/printers/SourceWrapperPrinter.cpp index c8e205d51..e1f616217 100644 --- a/server/src/printers/SourceWrapperPrinter.cpp +++ b/server/src/printers/SourceWrapperPrinter.cpp @@ -12,12 +12,21 @@ namespace printer { const std::string &wrapperDefinitions) { if (Paths::isCXXFile(sourceFilePath)) return; - writeCopyrightHeader(); + fs::path wrapperFilePath = Paths::getWrapperFilePath(projectContext, sourceFilePath); + auto content = getFinalContent(projectContext, sourceFilePath, wrapperDefinitions); + FileSystemUtils::writeToFile(wrapperFilePath, content); + } + std::string SourceWrapperPrinter::getFinalContent(const utbot::ProjectContext &projectContext, + const fs::path &sourceFilePath, + const std::string &wrapperDefinitions) { + if (Paths::isCXXFile(sourceFilePath)) + throw std::invalid_argument(StringUtils::stringFormat("Tried to get wrapper for cpp file: %s", sourceFilePath)); + + writeCopyrightHeader(); strDefine("main", "main__"); fs::path wrapperFilePath = Paths::getWrapperFilePath(projectContext, sourceFilePath); - fs::path sourcePathRelativeToProjectDir = fs::relative(sourceFilePath, projectContext.projectPath); fs::path projectDirRelativeToWrapperFile = fs::relative(projectContext.projectPath, wrapperFilePath.parent_path()); @@ -26,6 +35,6 @@ namespace printer { ss << wrapperDefinitions; - FileSystemUtils::writeToFile(wrapperFilePath, ss.str()); + return ss.str(); } } diff --git a/server/src/printers/SourceWrapperPrinter.h b/server/src/printers/SourceWrapperPrinter.h index abea1d020..1dae583f3 100644 --- a/server/src/printers/SourceWrapperPrinter.h +++ b/server/src/printers/SourceWrapperPrinter.h @@ -14,6 +14,10 @@ namespace printer { void print(const utbot::ProjectContext &projectContext, const fs::path &sourceFilePath, const std::string &wrapperDefinitions); + + std::string getFinalContent(const utbot::ProjectContext &projectContext, + const fs::path &sourceFilePath, + const std::string &wrapperDefinitions); }; } diff --git a/server/src/streams/stubs/CLIStubsWriter.cpp b/server/src/streams/stubs/CLIStubsWriter.cpp index b98cf49e8..86974842d 100644 --- a/server/src/streams/stubs/CLIStubsWriter.cpp +++ b/server/src/streams/stubs/CLIStubsWriter.cpp @@ -4,7 +4,8 @@ #include "loguru.h" void CLIStubsWriter::writeResponse(const std::vector &synchronizedStubs, - const fs::path &testDirPath) { + const fs::path &testDirPath, + const std::string &message) { LOG_S(INFO) << "Writing stubs..."; writeStubsFilesOnServer(synchronizedStubs, testDirPath); LOG_S(INFO) << "Stubs generated"; diff --git a/server/src/streams/stubs/CLIStubsWriter.h b/server/src/streams/stubs/CLIStubsWriter.h index b7f981b4e..8f6109e87 100644 --- a/server/src/streams/stubs/CLIStubsWriter.h +++ b/server/src/streams/stubs/CLIStubsWriter.h @@ -8,7 +8,7 @@ class CLIStubsWriter : public StubsWriter { public: explicit CLIStubsWriter(): StubsWriter(nullptr) {}; - void writeResponse(const std::vector &synchronizedStubs, const fs::path &testDirPath) override; + void writeResponse(const std::vector &synchronizedStubs, const fs::path &testDirPath, const std::string &message) override; }; diff --git a/server/src/streams/stubs/ServerStubsWriter.cpp b/server/src/streams/stubs/ServerStubsWriter.cpp index 2eab66ed0..44e53bfb2 100644 --- a/server/src/streams/stubs/ServerStubsWriter.cpp +++ b/server/src/streams/stubs/ServerStubsWriter.cpp @@ -3,21 +3,22 @@ #include "loguru.h" void ServerStubsWriter::writeResponse(const std::vector &synchronizedStubs, - const fs::path &testDirPath) { + const fs::path &testDirPath, + const std::string &message) { writeStubsFilesOnServer(synchronizedStubs, testDirPath); if (!hasStream()) { return; } - testsgen::StubsResponse response; - LOG_S(DEBUG) << "Creating final response."; - for (const auto &synchronizedStub : synchronizedStubs) { + LOG_S(DEBUG) << "Writing stubs with progress."; + for (size_t i = 0; i < synchronizedStubs.size(); i++) { + testsgen::StubsResponse response; auto sData = response.add_stubsources(); - sData->set_filepath(synchronizedStub.filePath); + sData->set_filepath(synchronizedStubs[i].filePath); if (synchronizeCode) { - sData->set_code(synchronizedStub.code); + sData->set_code(synchronizedStubs[i].code); } + auto progress = GrpcUtils::createProgress(message, (100.0 * i) / synchronizedStubs.size(), true); + response.set_allocated_progress(progress.release()); + writeMessage(response); } - auto progress = GrpcUtils::createProgress(std::nullopt, 0, true); - response.set_allocated_progress(progress.release()); - writeMessage(response); } diff --git a/server/src/streams/stubs/ServerStubsWriter.h b/server/src/streams/stubs/ServerStubsWriter.h index 9fca3a252..0d477fb1c 100644 --- a/server/src/streams/stubs/ServerStubsWriter.h +++ b/server/src/streams/stubs/ServerStubsWriter.h @@ -10,7 +10,8 @@ class ServerStubsWriter : public StubsWriter { } void writeResponse(const std::vector &synchronizedStubs, - const fs::path &testDirPath) override; + const fs::path &testDirPath, + const std::string &message) override; private: bool synchronizeCode; }; diff --git a/server/src/streams/stubs/StubsWriter.h b/server/src/streams/stubs/StubsWriter.h index 10efc40e4..9513ff7a7 100644 --- a/server/src/streams/stubs/StubsWriter.h +++ b/server/src/streams/stubs/StubsWriter.h @@ -12,7 +12,7 @@ class StubsWriter : public utbot::ServerWriter { public: explicit StubsWriter(grpc::ServerWriter *writer); - virtual void writeResponse(const std::vector &synchronizedStubs, const fs::path &testDirPath) = 0; + virtual void writeResponse(const std::vector &synchronizedStubs, const fs::path &testDirPath, const std::string &message) = 0; static void writeStubsFilesOnServer(const std::vector &stubs, const fs::path &testDirPath); diff --git a/server/src/streams/tests/ServerTestsWriter.cpp b/server/src/streams/tests/ServerTestsWriter.cpp index 8c4b12f06..6a8855901 100644 --- a/server/src/streams/tests/ServerTestsWriter.cpp +++ b/server/src/streams/tests/ServerTestsWriter.cpp @@ -84,3 +84,16 @@ void ServerTestsWriter::writeReport(const std::string &content, response.set_allocated_progress(progress.release()); writeMessage(response); } + +void ServerTestsWriter::writeFile(const std::string& content, const std::string& message, const std::string& filePath) const { + TestsWriter::writeFile(content, message, filePath); + + testsgen::TestsResponse response; + auto testSource = response.add_testsources(); + testSource->set_code(content); + testSource->set_filepath(filePath); + LOG_S(INFO) << message; + auto progress = GrpcUtils::createProgress(message, 100, false); + response.set_allocated_progress(progress.release()); + writeMessage(response); +} diff --git a/server/src/streams/tests/ServerTestsWriter.h b/server/src/streams/tests/ServerTestsWriter.h index b6504bf29..9a6cf1144 100644 --- a/server/src/streams/tests/ServerTestsWriter.h +++ b/server/src/streams/tests/ServerTestsWriter.h @@ -23,6 +23,8 @@ class ServerTestsWriter : public TestsWriter { const std::string &message, const fs::path &pathToStore) const override; + void writeFile(const std::string& content, const std::string& message, const std::string& filePath) const override; + private: [[nodiscard]] virtual bool writeFileAndSendResponse(const tests::Tests &tests, const fs::path &testDirPath, diff --git a/server/src/streams/tests/TestsWriter.cpp b/server/src/streams/tests/TestsWriter.cpp index 900e4e70d..9d1ddda0c 100644 --- a/server/src/streams/tests/TestsWriter.cpp +++ b/server/src/streams/tests/TestsWriter.cpp @@ -54,3 +54,7 @@ void TestsWriter::backupIfExists(const fs::path &filePath) { fs::rename(filePath, filePath.parent_path() / nfn.str()); } } + +void TestsWriter::writeFile(const std::string& content, const std::string& message, const std::string& filePath) const { + FileSystemUtils::writeToFile(filePath, content); +} diff --git a/server/src/streams/tests/TestsWriter.h b/server/src/streams/tests/TestsWriter.h index 2e080f7f8..4fbb2bad5 100644 --- a/server/src/streams/tests/TestsWriter.h +++ b/server/src/streams/tests/TestsWriter.h @@ -20,6 +20,8 @@ class TestsWriter : public utbot::ServerWriter { std::function &&prepareTests, std::function &&prepareTotal) = 0; + virtual void writeFile(const std::string& content, const std::string& message, const std::string& filePath) const; + virtual void writeReport(const std::string &content, const std::string &message, const fs::path &pathToStore) const; diff --git a/server/src/testgens/BaseTestGen.h b/server/src/testgens/BaseTestGen.h index 5e87e0219..de3868458 100644 --- a/server/src/testgens/BaseTestGen.h +++ b/server/src/testgens/BaseTestGen.h @@ -10,6 +10,7 @@ #include "streams/tests/TestsWriter.h" #include "stubs/Stubs.h" #include "types/Types.h" +#include "utils/FileInfoForTransfer.h" #include #include @@ -29,6 +30,7 @@ class BaseTestGen { tests::TestsMap tests; std::unordered_map methodNameToReturnTypeMap; std::vector synchronizedStubs; + std::vector synchronizedWrappers; types::TypeMaps types; CollectionUtils::FileSet targetSources; diff --git a/server/src/utils/Copyright.h b/server/src/utils/Copyright.h index a7379bebf..35ce234ac 100644 --- a/server/src/utils/Copyright.h +++ b/server/src/utils/Copyright.h @@ -18,6 +18,13 @@ namespace Copyright { PROJECT_HOMEPAGE_URL "\n"; + static inline const std::string GENERATED_CMAKELISTS_FILE_HEADER = + "#[[\n" + "This file is automatically generated by UnitTestBot. " + "For further information see " + PROJECT_HOMEPAGE_URL + "\n ]]\n"; + static inline const std::string GENERATED_SH_HEADER = GENERATED_MAKEFILE_HEADER; }; diff --git a/server/src/utils/FileInfoForTransfer.h b/server/src/utils/FileInfoForTransfer.h new file mode 100644 index 000000000..e81e6939f --- /dev/null +++ b/server/src/utils/FileInfoForTransfer.h @@ -0,0 +1,15 @@ +// + +#ifndef UTBOTCPP_FILEINFOFORTRANSFER_H +#define UTBOTCPP_FILEINFOFORTRANSFER_H + +#include +#include "utils/path/FileSystemPath.h" + +struct FileInfoForTransfer { + fs::path filePath; + std::string code; + FileInfoForTransfer() = default; + FileInfoForTransfer(fs::path filePath, std::string code) : filePath(std::move(filePath)), code(std::move(code)) {} +}; +#endif //UTBOTCPP_FILEINFOFORTRANSFER_H diff --git a/server/src/utils/FileSystemUtils.cpp b/server/src/utils/FileSystemUtils.cpp index bae2d4902..9f519efa3 100644 --- a/server/src/utils/FileSystemUtils.cpp +++ b/server/src/utils/FileSystemUtils.cpp @@ -69,6 +69,11 @@ namespace FileSystemUtils { this->directory = directory; } + std::string read(const fs::path &path) { + std::ifstream inputStream(path); + return std::string { std::istreambuf_iterator(inputStream), std::istreambuf_iterator() }; + } + size_t RecursiveDirectoryIterator::size() const { return std::distance(fs::recursive_directory_iterator(directory), fs::recursive_directory_iterator()); } diff --git a/server/src/utils/FileSystemUtils.h b/server/src/utils/FileSystemUtils.h index a72e41328..a9fae6a73 100644 --- a/server/src/utils/FileSystemUtils.h +++ b/server/src/utils/FileSystemUtils.h @@ -14,6 +14,8 @@ namespace FileSystemUtils { std::vector recursiveDirectories(const fs::path &root); + std::string read(const fs::path &path); + class DirectoryIterator : public fs::directory_iterator { fs::path directory; diff --git a/server/src/utils/LinkerUtils.h b/server/src/utils/LinkerUtils.h index 638345637..d341539ce 100644 --- a/server/src/utils/LinkerUtils.h +++ b/server/src/utils/LinkerUtils.h @@ -10,8 +10,6 @@ namespace LinkerUtils { fs::path applySuffix(const fs::path &output, BuildResult::Type unitType, const std::string &suffixForParentOfStubs); - }; - #endif // UNITTESTBOT_LINKERUTILS_H diff --git a/server/test/framework/CLI_Tests.cpp b/server/test/framework/CLI_Tests.cpp index 2dab580fd..4a78171d2 100644 --- a/server/test/framework/CLI_Tests.cpp +++ b/server/test/framework/CLI_Tests.cpp @@ -91,7 +91,7 @@ namespace { continue; } EXPECT_TRUE(Paths::isSubPathOf(getStubsDirectory(), it.path()) || - fileSet.count(it.path())) + fileSet.count(it.path()) || Paths::isCMakeListsFile(it.path())) << testUtils::unexpectedFileMessage(it.path()); } } diff --git a/server/test/framework/CMakeGeneration_Tests.cpp b/server/test/framework/CMakeGeneration_Tests.cpp new file mode 100644 index 000000000..523fa5905 --- /dev/null +++ b/server/test/framework/CMakeGeneration_Tests.cpp @@ -0,0 +1,69 @@ +#include "gtest/gtest.h" + +#include "BaseTest.h" + +namespace { + using namespace testUtils; + + class BaseCMakeTest : public BaseTest { + protected: + BaseCMakeTest() : BaseTest("cmake") {} + fs::path expectedCMakePath = suitePath / "expected_cmake.txt"; + + public: + void prepare(const std::string &subprojectName) { + baseSuitePath /= "cmake"; + suiteName = subprojectName; + clearEnv(CompilationUtils::CompilerName::CLANG); // will call setSuite(suiteName), which in turn will set suitePath and other paths + expectedCMakePath = suitePath / "expected_cmake.txt"; + } + + void setExpectedCMakePath(const fs::path &path) { + expectedCMakePath = path; + } + + void checkCMakeGenerationForProjectTestgen(bool useStubs = false) { + auto request = createProjectRequest(projectName, suitePath, buildDirRelativePath, srcPaths, + GrpcUtils::UTBOT_AUTO_TARGET_PATH, useStubs); + auto testGen = ProjectTestGen(*request, writer.get(), TESTMODE); + + Status status = Server::TestsGenServiceImpl::ProcessBaseTestRequest(testGen, writer.get()); + ASSERT_TRUE(status.ok()) << status.error_message(); + auto generatedCMake = FileSystemUtils::read(getTestDirectory() / "CMakeLists.txt"); + checkCMakeGenerated(suitePath, getTestDirectory(), expectedCMakePath); + } + + void checkCMakeGenerationForFile(const fs::path &file, bool useStubs = false, + const std::string &targetOrSource = GrpcUtils::UTBOT_AUTO_TARGET_PATH) { + auto request = createFileRequest(projectName, suitePath, buildDirRelativePath, srcPaths, file, + targetOrSource, useStubs); + auto testGen = FileTestGen(*request, writer.get(), TESTMODE); + Status status = Server::TestsGenServiceImpl::ProcessBaseTestRequest(testGen, writer.get()); + ASSERT_TRUE(status.ok()) << status.error_message(); + auto generatedCMake = FileSystemUtils::read(getTestDirectory() / "CMakeLists.txt"); + checkCMakeGenerated(suitePath, getTestDirectory(), expectedCMakePath); + } + }; + + TEST_F(BaseCMakeTest, simple_project + ) { + prepare("simple"); + checkCMakeGenerationForProjectTestgen(); + } + + TEST_F(BaseCMakeTest, project_with_shared_lib + ) { + prepare("shared"); + checkCMakeGenerationForProjectTestgen(); + } + + TEST_F(BaseCMakeTest, with_stubs) { + prepare("stubs"); + + setExpectedCMakePath(suitePath / "expected_cmake_exe_target.txt"); + checkCMakeGenerationForFile(getTestFilePath("unitA/caller.c"), true, "exe"); + + setExpectedCMakePath(suitePath / "expected_cmake_unitA_target.txt"); + checkCMakeGenerationForFile(getTestFilePath("unitA/caller.c"), true, "unitA"); + } +} diff --git a/server/test/framework/Server_Tests.cpp b/server/test/framework/Server_Tests.cpp index 46a165ddf..c7c7b303d 100644 --- a/server/test/framework/Server_Tests.cpp +++ b/server/test/framework/Server_Tests.cpp @@ -11,10 +11,13 @@ #include "printers/SourceWrapperPrinter.h" #include "utils/FileSystemUtils.h" #include "utils/ServerUtils.h" +#include "building/CMakeGenerator.h" #include "utils/path/FileSystemPath.h" #include #include +#include +#include namespace { using CompilationUtils::CompilerName; @@ -465,6 +468,7 @@ namespace { } } } + void checkFloatingPoint_C(BaseTestGen &testGen) { for (const auto &[methodName, methodDescription] : testGen.tests.at(floating_point_c).methods) { @@ -1233,11 +1237,11 @@ namespace { CoverageAndResultsGenerator generate(std::unique_ptr testFilter, bool withCoverage) { auto request = createCoverageAndResultsRequest( - projectName, suitePath, testDirPath, buildDirRelativePath, std::move(testFilter)); + projectName, suitePath, testDirPath, buildDirRelativePath, std::move(testFilter)); static auto coverageAndResultsWriter = - std::make_unique(nullptr); - CoverageAndResultsGenerator coverageGenerator{ request.get(), coverageAndResultsWriter.get() }; - utbot::SettingsContext settingsContext{ true, true, 15, 0, true, false }; + std::make_unique(nullptr); + CoverageAndResultsGenerator coverageGenerator{request.get(), coverageAndResultsWriter.get()}; + utbot::SettingsContext settingsContext{true, true, 15, 0, true, false}; coverageGenerator.generate(withCoverage, settingsContext); EXPECT_FALSE(coverageGenerator.hasExceptions()); return coverageGenerator; @@ -1605,7 +1609,6 @@ namespace { Status status = Server::TestsGenServiceImpl::ProcessBaseTestRequest(testGen, writer.get()); ASSERT_TRUE(status.ok()) << status.error_message(); - - + checkCMakeGenerated(suitePath, getTestDirectory()); } } diff --git a/server/test/framework/Stub_Tests.cpp b/server/test/framework/Stub_Tests.cpp index 143080505..f6b478494 100644 --- a/server/test/framework/Stub_Tests.cpp +++ b/server/test/framework/Stub_Tests.cpp @@ -125,7 +125,7 @@ namespace { TEST_F(Stub_Test, Implicit_Stubs_Test) { auto request = createFileRequest(projectName, suitePath, buildDirRelativePath, srcPaths, - literals_foo_c, GrpcUtils::UTBOT_AUTO_TARGET_PATH, true); + literals_foo_c, "libliterals", true); auto testGen = FileTestGen(*request, writer.get(), TESTMODE); testGen.setTargetForSource(literals_foo_c); @@ -138,9 +138,9 @@ namespace { TEST_F(Stub_Test, Pregenerated_Stubs_Test) { { auto request = createFileRequest(projectName, suitePath, buildDirRelativePath, srcPaths, - literals_foo_c, GrpcUtils::UTBOT_AUTO_TARGET_PATH, true); + literals_foo_c, "libliterals" /*GrpcUtils::UTBOT_AUTO_TARGET_PATH */, true); auto testGen = FileTestGen(*request, writer.get(), TESTMODE); - testGen.setTargetForSource(literals_foo_c); +// testGen.setTargetForSource(literals_foo_c); Status status = Server::TestsGenServiceImpl::ProcessBaseTestRequest(testGen, writer.get()); ASSERT_TRUE(status.ok()) << status.error_message(); @@ -149,9 +149,9 @@ namespace { { auto request = createFileRequest(projectName, suitePath, buildDirRelativePath, - srcPaths, literals_foo_c, GrpcUtils::UTBOT_AUTO_TARGET_PATH, true); + srcPaths, literals_foo_c, "libliterals"/*GrpcUtils::UTBOT_AUTO_TARGET_PATH*/, true); auto testGen = FileTestGen(*request, writer.get(), TESTMODE); - testGen.setTargetForSource(literals_foo_c); +// testGen.setTargetForSource(literals_foo_c); Status status = Server::TestsGenServiceImpl::ProcessBaseTestRequest(testGen, writer.get()); ASSERT_TRUE(status.ok()) << status.error_message(); @@ -216,9 +216,9 @@ namespace { TEST_F(Stub_Test, Run_Tests_For_Unused_Function) { auto request = testUtils::createFileRequest(projectName, suitePath, buildDirRelativePath, - srcPaths, calc_sum_c, GrpcUtils::UTBOT_AUTO_TARGET_PATH, true); + srcPaths, calc_sum_c, "libcalc"/*GrpcUtils::UTBOT_AUTO_TARGET_PATH*/, true); auto testGen = FileTestGen(*request, writer.get(), TESTMODE); - testGen.setTargetForSource(calc_sum_c); +// testGen.setTargetForSource(calc_sum_c); Status status = Server::TestsGenServiceImpl::ProcessBaseTestRequest(testGen, writer.get()); ASSERT_TRUE(status.ok()) << status.error_message(); EXPECT_GE(testUtils::getNumberOfTests(testGen.tests), 2); @@ -237,9 +237,9 @@ namespace { TEST_F(Stub_Test, File_Tests_Without_Stubs) { auto request = testUtils::createFileRequest(projectName, suitePath, buildDirRelativePath, - srcPaths, literals_foo_c, GrpcUtils::UTBOT_AUTO_TARGET_PATH, false); + srcPaths, literals_foo_c, "exe"/*GrpcUtils::UTBOT_AUTO_TARGET_PATH*/, false); auto testGen = FileTestGen(*request, writer.get(), TESTMODE); - testGen.setTargetForSource(literals_foo_c); + // testGen.setTargetForSource(literals_foo_c); Status status = Server::TestsGenServiceImpl::ProcessBaseTestRequest(testGen, writer.get()); ASSERT_TRUE(status.ok()) << status.error_message(); diff --git a/server/test/framework/TestUtils.cpp b/server/test/framework/TestUtils.cpp index c9965e7a8..4635ad23d 100644 --- a/server/test/framework/TestUtils.cpp +++ b/server/test/framework/TestUtils.cpp @@ -430,4 +430,12 @@ namespace testUtils { "Total Lines Number", "Covered Lines Number", "Line Coverage Ratio (%)"}; checkStatsCSV(statsPath, header, containedFiles); } + + void checkCMakeGenerated(const fs::path &suitePath, const fs::path &testsDirPath, const std::optional& expectedCMakePath) { + auto expectedPath = (expectedCMakePath.has_value() ? expectedCMakePath.value() : suitePath / "expected_cmake.txt"); + auto generatedCMakePath = testsDirPath / "CMakeLists.txt"; + EXPECT_TRUE(fs::exists(generatedCMakePath)) << "CMakeLists.txt file not generated!"; + auto expected = FileSystemUtils::read(expectedPath), actual = FileSystemUtils::read(generatedCMakePath); + EXPECT_EQ(actual, expected); + } } diff --git a/server/test/framework/TestUtils.h b/server/test/framework/TestUtils.h index e037a1ffd..d116e9946 100644 --- a/server/test/framework/TestUtils.h +++ b/server/test/framework/TestUtils.h @@ -137,6 +137,8 @@ namespace testUtils { void checkGenerationStatsCSV(const fs::path &statsPath, const std::vector &containedFiles); void checkExecutionStatsCSV(const fs::path &statsPath, const std::vector &containedFiles); + + void checkCMakeGenerated(const fs::path &suitePath, const fs::path &testsDirPath, const std::optional &expectedCMakePath = {}); } #endif // UNITTESTBOT_TESTUTILS_H diff --git a/server/test/framework/main.cpp b/server/test/framework/main.cpp index 6e900c9e0..1ce8961d2 100644 --- a/server/test/framework/main.cpp +++ b/server/test/framework/main.cpp @@ -50,6 +50,11 @@ int main(int argc, char **argv) { testUtils::tryExecGetBuildCommands(testUtils::getRelativeTestSuitePath("library-project")); + for (auto const &subproject : { "simple", "shared", "stubs" }) { + testUtils::tryExecGetBuildCommands( + testUtils::getRelativeTestSuitePath("cmake") / subproject); + } + for (auto const &subproject : { "executable", "static_library", "shared_library", "timeout" }) { for (auto const &compiler : { clang, gcc }) { testUtils::tryExecGetBuildCommands( diff --git a/server/test/suites/cmake/shared/CMakeLists.txt b/server/test/suites/cmake/shared/CMakeLists.txt new file mode 100644 index 000000000..16e7cbeea --- /dev/null +++ b/server/test/suites/cmake/shared/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.10) + +project(cmake) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_C_STANDARD_REQUIRED ON) + +add_library(shared_lib foo.c bar.c) diff --git a/server/test/suites/cmake/shared/bar.c b/server/test/suites/cmake/shared/bar.c new file mode 100644 index 000000000..0adb59754 --- /dev/null +++ b/server/test/suites/cmake/shared/bar.c @@ -0,0 +1,4 @@ + +int bar(int x, int y) { + return x*x + y; +} diff --git a/server/test/suites/cmake/shared/bar.h b/server/test/suites/cmake/shared/bar.h new file mode 100644 index 000000000..d56c52eb4 --- /dev/null +++ b/server/test/suites/cmake/shared/bar.h @@ -0,0 +1,5 @@ + +#ifndef UTBOTCPP_BAR_H +#define UTBOTCPP_BAR_H +int bar(int x, int y); +#endif //UTBOTCPP_BAR_H diff --git a/server/test/suites/cmake/shared/expected_cmake.txt b/server/test/suites/cmake/shared/expected_cmake.txt new file mode 100644 index 000000000..7858473dc --- /dev/null +++ b/server/test/suites/cmake/shared/expected_cmake.txt @@ -0,0 +1,24 @@ +#[[ +This file is automatically generated by UnitTestBot. For further information see https://www.utbot.org + ]] + +add_library( + libshared_lib_shared_utbot + SHARED + wrapper/foo_wrapper.c + wrapper/bar_wrapper.c +) + +add_executable( + utbot_tests + foo_dot_c_test.cpp + bar_dot_c_test.cpp +) + +target_link_libraries( + utbot_tests + GTest::gtest_main + libshared_lib_shared_utbot +) + +gtest_discover_tests(utbot_tests) diff --git a/server/test/suites/cmake/shared/foo.c b/server/test/suites/cmake/shared/foo.c new file mode 100644 index 000000000..582fcc9d3 --- /dev/null +++ b/server/test/suites/cmake/shared/foo.c @@ -0,0 +1,5 @@ +#include "bar.h" + +int foo(int x) { + return x + bar(x, x); +} diff --git a/server/test/suites/cmake/simple/CMakeLists.txt b/server/test/suites/cmake/simple/CMakeLists.txt new file mode 100644 index 000000000..4e973b844 --- /dev/null +++ b/server/test/suites/cmake/simple/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.10) + +project(cmake) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_C_STANDARD_REQUIRED ON) + +add_executable(executable a.c b.c) diff --git a/server/test/suites/cmake/simple/a.c b/server/test/suites/cmake/simple/a.c new file mode 100644 index 000000000..0e793eeb6 --- /dev/null +++ b/server/test/suites/cmake/simple/a.c @@ -0,0 +1,5 @@ +#include "bar.h" + +int foo(int x) { + return bar(x) + 4; +} diff --git a/server/test/suites/cmake/simple/b.c b/server/test/suites/cmake/simple/b.c new file mode 100644 index 000000000..03383a41c --- /dev/null +++ b/server/test/suites/cmake/simple/b.c @@ -0,0 +1,8 @@ + +int bar(int y) { + return y + 20; +} + +int main() { + return 0; +} \ No newline at end of file diff --git a/server/test/suites/cmake/simple/bar.h b/server/test/suites/cmake/simple/bar.h new file mode 100644 index 000000000..e3fb5f7af --- /dev/null +++ b/server/test/suites/cmake/simple/bar.h @@ -0,0 +1,4 @@ +#ifndef UTBOTCPP_BAR_H +#define UTBOTCPP_BAR_H +int bar(int); +#endif //UTBOTCPP_BAR_H diff --git a/server/test/suites/cmake/simple/expected_cmake.txt b/server/test/suites/cmake/simple/expected_cmake.txt new file mode 100644 index 000000000..f401fb583 --- /dev/null +++ b/server/test/suites/cmake/simple/expected_cmake.txt @@ -0,0 +1,24 @@ +#[[ +This file is automatically generated by UnitTestBot. For further information see https://www.utbot.org + ]] + +add_library( + executable_utbot + SHARED + wrapper/a_wrapper.c + wrapper/b_wrapper.c +) + +add_executable( + utbot_tests + a_dot_c_test.cpp + b_dot_c_test.cpp +) + +target_link_libraries( + utbot_tests + GTest::gtest_main + executable_utbot +) + +gtest_discover_tests(utbot_tests) diff --git a/server/test/suites/cmake/stubs/CMakeLists.txt b/server/test/suites/cmake/stubs/CMakeLists.txt new file mode 100644 index 000000000..a36b5914c --- /dev/null +++ b/server/test/suites/cmake/stubs/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.13) + +project(cmake) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_C_STANDARD_REQUIRED ON) + +add_executable(exe main.c) +target_include_directories(exe PUBLIC unitB) +add_subdirectory(unitB) +add_subdirectory(unitA) +target_link_libraries(exe unitA unitB) diff --git a/server/test/suites/cmake/stubs/expected_cmake_exe_target.txt b/server/test/suites/cmake/stubs/expected_cmake_exe_target.txt new file mode 100644 index 000000000..f5a342181 --- /dev/null +++ b/server/test/suites/cmake/stubs/expected_cmake_exe_target.txt @@ -0,0 +1,55 @@ +#[[ +This file is automatically generated by UnitTestBot. For further information see https://www.utbot.org + ]] + +add_library( + exe_utbot + SHARED + stubs/main_stub.c +) + +target_include_directories( + exe_utbot PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../unitA + ${CMAKE_CURRENT_SOURCE_DIR}/../unitB +) + +add_library( + libunitA_utbot + wrapper/unitA/caller_wrapper.c +) + +target_include_directories( + libunitA_utbot PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../unitA + ${CMAKE_CURRENT_SOURCE_DIR}/../unitB +) + +add_library( + libunitB_utbot + stubs/unitB/callee_stub.c +) + +target_include_directories( + libunitB_utbot PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../unitB +) + +target_link_libraries( + exe_utbot + libunitA_utbot + libunitB_utbot +) + +add_executable( + utbot_tests + unitA/caller_dot_c_test.cpp +) + +target_link_libraries( + utbot_tests + GTest::gtest_main + exe_utbot +) + +gtest_discover_tests(utbot_tests) diff --git a/server/test/suites/cmake/stubs/expected_cmake_unitA_target.txt b/server/test/suites/cmake/stubs/expected_cmake_unitA_target.txt new file mode 100644 index 000000000..538fbc773 --- /dev/null +++ b/server/test/suites/cmake/stubs/expected_cmake_unitA_target.txt @@ -0,0 +1,29 @@ +#[[ +This file is automatically generated by UnitTestBot. For further information see https://www.utbot.org + ]] + +add_library( + libunitA_shared_utbot + SHARED + wrapper/unitA/caller_wrapper.c + stubs/unitB/callee_stub.c +) + +target_include_directories( + libunitA_shared_utbot PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../unitA + ${CMAKE_CURRENT_SOURCE_DIR}/../unitB +) + +add_executable( + utbot_tests + unitA/caller_dot_c_test.cpp +) + +target_link_libraries( + utbot_tests + GTest::gtest_main + libunitA_shared_utbot +) + +gtest_discover_tests(utbot_tests) diff --git a/server/test/suites/cmake/stubs/main.c b/server/test/suites/cmake/stubs/main.c new file mode 100644 index 000000000..e53895ee6 --- /dev/null +++ b/server/test/suites/cmake/stubs/main.c @@ -0,0 +1,6 @@ +#include "caller.c" + +int main() { + int f = foo(0, 0); + return 0; +} \ No newline at end of file diff --git a/server/test/suites/cmake/stubs/unitA/CMakeLists.txt b/server/test/suites/cmake/stubs/unitA/CMakeLists.txt new file mode 100644 index 000000000..ca193db73 --- /dev/null +++ b/server/test/suites/cmake/stubs/unitA/CMakeLists.txt @@ -0,0 +1,4 @@ + +add_library(unitA caller.c) +target_include_directories(unitA PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(unitA unitB) \ No newline at end of file diff --git a/server/test/suites/cmake/stubs/unitA/caller.c b/server/test/suites/cmake/stubs/unitA/caller.c new file mode 100644 index 000000000..8ef5a6c97 --- /dev/null +++ b/server/test/suites/cmake/stubs/unitA/caller.c @@ -0,0 +1,10 @@ +#include "callee.h" + +int foo(int x, int y) { + return do_stuff(x, y-x); +} + +//int main() { +// int res = do_stuff(0, 0); +// return 0; +//} \ No newline at end of file diff --git a/server/test/suites/cmake/stubs/unitA/caller.h b/server/test/suites/cmake/stubs/unitA/caller.h new file mode 100644 index 000000000..7443cc156 --- /dev/null +++ b/server/test/suites/cmake/stubs/unitA/caller.h @@ -0,0 +1,8 @@ +// + +#ifndef UTBOTCPP_CALLER_H +#define UTBOTCPP_CALLER_H + +int foo(int x, int y); + +#endif //UTBOTCPP_CALLER_H diff --git a/server/test/suites/cmake/stubs/unitB/CMakeLists.txt b/server/test/suites/cmake/stubs/unitB/CMakeLists.txt new file mode 100644 index 000000000..c98d1b97e --- /dev/null +++ b/server/test/suites/cmake/stubs/unitB/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(unitB) +target_include_directories(unitB PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_sources(unitB PRIVATE callee.c PUBLIC callee.h) diff --git a/server/test/suites/cmake/stubs/unitB/callee.c b/server/test/suites/cmake/stubs/unitB/callee.c new file mode 100644 index 000000000..be7661cd5 --- /dev/null +++ b/server/test/suites/cmake/stubs/unitB/callee.c @@ -0,0 +1,6 @@ +// +#include "callee.h" + +int do_stuff(int x, int y) { + return x*x + y*y; +} diff --git a/server/test/suites/cmake/stubs/unitB/callee.h b/server/test/suites/cmake/stubs/unitB/callee.h new file mode 100644 index 000000000..4d8d6be97 --- /dev/null +++ b/server/test/suites/cmake/stubs/unitB/callee.h @@ -0,0 +1,8 @@ +// + +#ifndef UTBOTCPP_CALLEE_CPP_H +#define UTBOTCPP_CALLEE_CPP_H + +int do_stuff(int x, int y); + +#endif //UTBOTCPP_CALLEE_CPP_H diff --git a/server/test/suites/small-project/expected_cmake.txt b/server/test/suites/small-project/expected_cmake.txt new file mode 100644 index 000000000..572e3a570 --- /dev/null +++ b/server/test/suites/small-project/expected_cmake.txt @@ -0,0 +1,44 @@ +#[[ +This file is automatically generated by UnitTestBot. For further information see https://www.utbot.org + ]] + +add_library( + src_utbot + SHARED + wrapper/src/srcfile_wrapper.c +) + +target_include_directories( + src_utbot PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../src +) + +add_library( + liblib_utbot + wrapper/lib/libfile_wrapper.c +) + +target_include_directories( + liblib_utbot PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../lib +) + +target_link_libraries( + src_utbot + liblib_utbot +) + +add_executable( + utbot_tests + lib/libfile_dot_c_test.cpp + src/srcfile_dot_c_test.cpp +) + +target_link_libraries( + utbot_tests + GTest::gtest_main + src_utbot +) + +gtest_discover_tests(utbot_tests) diff --git a/vscode-plugin/src/test/helper.ts b/vscode-plugin/src/test/helper.ts index c5278b10e..76b7cd540 100644 --- a/vscode-plugin/src/test/helper.ts +++ b/vscode-plugin/src/test/helper.ts @@ -132,7 +132,8 @@ export function checkTestFilesGenerated(dirPath: string, srcFiles: string[]): bo walk(testDirPath).forEach(file => { const fileExt = path.parse(file).ext; const testFileName = path.parse(file).name; - if (testFileName.search(/_stub$/) < 0 && !(fileExt === ".mk") && !(testFileName.endsWith("_wrapper"))) { + if (testFileName.search(/_stub$/) < 0 && !(fileExt === ".mk") && !(testFileName.endsWith("_wrapper")) + && !(testFileName === "CMakeLists.txt")) { const fileName = checkForFirstMatch(testFileName, [/_dot_c_test_error$/, /_dot_c_test$/]); if (!srcFilesUsedMap.has(fileName)) { TestLogger.getLogger().error('Unable to find a corresponding source file for test: [%s]', testFileName);