Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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) {
Original file line number Diff line number Diff line change
@@ -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,
Original file line number Diff line number Diff line change
@@ -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"
}
}

Original file line number Diff line number Diff line change
@@ -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")) {}
}
}
}
Original file line number Diff line number Diff line change
@@ -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,34 +41,91 @@ class TestsStreamHandler(
) : StreamHandlerWithProgress<Testgen.TestsResponse>(project, grpcStream, progressName, cancellationJob) {

private val myGeneratedTestFilesLocalFS: MutableList<Path> = 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<TestsResultsStorage>().clearTestResults(testSourceCodes)
}

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<SourceCode>) {
if (project.settings.isRemoteScenario) {
createSourceCodeFiles(sources, "stub")
Original file line number Diff line number Diff line change
@@ -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<Testgen.ProjectRequest>, project: Project) :
BaseRequest<Testgen.ProjectRequest, Flow<Testgen.StubsResponse>>(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<Testgen.StubsResponse>.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<Testgen.StubsResponse> {
return this.generateProjectStubs(request)
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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<String> get() {
return state.sourceDirs
}
set(value) {
state.sourceDirs = value
}
var isGTestInstalled: Boolean
get() = myState.isGTestInstalled
set(value) {
myState.isGTestInstalled = value
}

var sourceDirs: Set<String>
get() {
return state.sourceDirs
}
set(value) {
state.sourceDirs = value
}

private fun isTargetUpToDate(): Boolean {
return project.service<UTBotTargetsController>().isTargetUpToDate(myState.targetPath)
}



override fun getState() = myState
override fun loadState(state: State) {
myState = state
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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,
Original file line number Diff line number Diff line change
@@ -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()
3 changes: 3 additions & 0 deletions clion-plugin/src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
@@ -203,5 +203,8 @@
description="Turns on/off verbose formatting for tests">
<override-text place="GoToAction" text="UTBot: Enable Verbose Mode"/>
</action>
<action id="org.utbot.cpp.clion.plugin.actions.SyncWrappersAndStubsAction"
class="org.utbot.cpp.clion.plugin.actions.SyncWrappersAndStubsAction"
text="Synchronize wrappers and stubs" description="Requests stubs and wrappers from server"/>
</actions>
</idea-plugin>
2 changes: 2 additions & 0 deletions clion-plugin/src/main/resources/messages/UTBot.properties
Original file line number Diff line number Diff line change
@@ -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: <em>{0, number, #}</em>, host: <em>{1}</em>
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.
7 changes: 6 additions & 1 deletion server/src/KleeRunner.cpp
Original file line number Diff line number Diff line change
@@ -54,7 +54,8 @@ void KleeRunner::runKlee(const std::vector<tests::TestMethod> &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<tests::TestMethod> &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(
4 changes: 3 additions & 1 deletion server/src/KleeRunner.h
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
#include <grpcpp/grpcpp.h>

#include <vector>
#include <utils/FileInfoForTransfer.h>

class KleeRunner {
public:
@@ -33,7 +34,8 @@ class KleeRunner {
const std::unordered_map<std::string, types::Type> &methodNameToReturnTypeMap,
const std::shared_ptr<LineInfo> &lineInfo, TestsWriter *testsWriter, bool isBatched,
bool interactiveMode,
StatsUtils::TestsGenerationStatsFileMap &generationStats);
StatsUtils::TestsGenerationStatsFileMap &generationStats,
const FileInfoForTransfer &generatedCMake);

private:
const utbot::ProjectContext projectContext;
4 changes: 4 additions & 0 deletions server/src/Paths.h
Original file line number Diff line number Diff line change
@@ -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<std::string> CXXFileExtensions;

static inline bool isCXXFile(const fs::path &path) {
5 changes: 3 additions & 2 deletions server/src/Server.cpp
Original file line number Diff line number Diff line change
@@ -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<std::chrono::milliseconds>
(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;
}

22 changes: 13 additions & 9 deletions server/src/Synchronizer.cpp
Original file line number Diff line number Diff line change
@@ -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 {
6 changes: 0 additions & 6 deletions server/src/building/BaseCommand.cpp
Original file line number Diff line number Diff line change
@@ -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 <algorithm>
#include <iterator>
#include <set>
#include <utility>

2 changes: 1 addition & 1 deletion server/src/building/BuildDatabase.cpp
Original file line number Diff line number Diff line change
@@ -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<CollectionUtils::FileSet>(
getArchiveObjectFiles(_target),
[this](fs::path const &objectPath) {
2 changes: 1 addition & 1 deletion server/src/building/BuildDatabase.h
Original file line number Diff line number Diff line change
@@ -226,7 +226,7 @@ class BuildDatabase {

std::shared_ptr<TargetInfo> getPriorityTarget() const;

CollectionUtils::FileSet getSourceFilesForTarget(const fs::path &_target);
CollectionUtils::FileSet getSourceFilesForTarget(const fs::path &_target) const;

std::shared_ptr<TargetInfo> getTargetInfo(const fs::path &_target);

133 changes: 133 additions & 0 deletions server/src/building/CMakeGenerator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#include "Synchronizer.h"
#include "building/Linker.h"
#include "utils/FileSystemUtils.h"
#include "utils/Copyright.h"
#include <utils/ArgumentsUtils.h>
#include <utils/SanitizerUtils.h>
#include <filesystem>
#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<const BuildDatabase::TargetInfo> 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<std::string> dependentLibs;
std::vector<fs::path> 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<std::vector<std::string>>(dependentLibs, [&](const fs::path &lib){
return getLibraryName(lib, false);
}));
}
}

std::set<std::string> CMakeGenerator::getIncludeDirectoriesFor(const fs::path &target) {
auto targetInfo = getTargetUnitInfo(target);
std::set<std::string> 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<fs::path> testFiles;
for (auto &file: filesUnderTest) {
testFiles.push_back(Paths::sourcePathToTestPath(testGen->projectContext, file));
}
auto testsTargetName = "utbot_tests";
printer.addExecutable(testsTargetName,
CollectionUtils::transformTo<std::vector<fs::path>>(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()) {}
54 changes: 54 additions & 0 deletions server/src/building/CMakeGenerator.h
Original file line number Diff line number Diff line change
@@ -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 <string>
#include <vector>
#include <building/Linker.h>

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<std::string> getIncludeDirectoriesFor(const fs::path &target);

std::string getLibraryName(const fs::path &lib, bool isRoot);

std::string getRootLibraryName(const fs::path &path);

std::shared_ptr<const BuildDatabase::TargetInfo> getTargetUnitInfo(const fs::path &targetPath);

const BaseTestGen *testGen;

private:
std::filesystem::path getAbsolutePath(const std::filesystem::path &path);
};

#endif //UTBOTCPP_CMAKEGENERATOR_H
21 changes: 16 additions & 5 deletions server/src/building/Linker.cpp
Original file line number Diff line number Diff line change
@@ -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 <unordered_set>
#include <utility>
@@ -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::LinkResult> Linker::link(const CollectionUtils::MapFileTo<fs::pat
testMakefilesPrinter.GetMakefiles(sourcePath).write();
}
}
return LinkResult{ targetBitcode, stubsSet, presentedFiles };
return LinkResult{ targetBitcode, stubsSet, presentedFiles, stubSources };
};

static const std::string STUB_BITCODE_FILES_NAME = "STUB_BITCODE_FILES";
@@ -705,7 +713,6 @@ Linker::declareRootLibraryTarget(printer::DefaultMakefilePrinter &bitcodeLinkMak
return rootOutput;
}


BuildResult
Linker::addLinkTargetRecursively(const fs::path &fileToBuild,
printer::DefaultMakefilePrinter &bitcodeLinkMakefilePrinter,
@@ -786,3 +793,7 @@ fs::path Linker::getPrefixPath(const std::vector<fs::path> &dependencies, fs::pa
Paths::longestCommonPrefixPath);
return std::max(prefixPath, defaultPath);
}

FileInfoForTransfer Linker::getGeneratedCMakeFile() {
return generatedCMakeFile;
}
7 changes: 6 additions & 1 deletion server/src/building/Linker.h
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <utils/FileInfoForTransfer.h>

class Linker {
public:
@@ -41,10 +42,13 @@ class Linker {
const std::optional<fs::path> &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<fs::path> &dependencies, fs::path defaultPath) const;

18 changes: 18 additions & 0 deletions server/src/building/UserProjectConfiguration.cpp
Original file line number Diff line number Diff line change
@@ -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 &params,
const utbot::ProjectContext &projectContext,
2 changes: 2 additions & 0 deletions server/src/building/UserProjectConfiguration.h
Original file line number Diff line number Diff line change
@@ -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);
};


77 changes: 77 additions & 0 deletions server/src/printers/CMakeListsPrinter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#include <utils/ArgumentsUtils.h>
#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<fs::path> &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<std::string> 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<std::string> &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<fs::path> &sourceFiles) {
if (sourceFiles.empty())
return;
ss << "add_executable" << LB() << executableName << NL;
for (auto &subFile : sourceFiles) {
ss << LINE_INDENT() << subFile.string() << NL;
}
ss << RB() << NL;
}
}
109 changes: 109 additions & 0 deletions server/src/printers/CMakeListsPrinter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#ifndef UTBOTCPP_CMAKELISTSPRINTER_H
#define UTBOTCPP_CMAKELISTSPRINTER_H

#include "Printer.h"
#include "testgens/BaseTestGen.h"
#include "BuildResult.h"

#include <string>
#include <vector>
#include <building/Linker.h>


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<std::shared_ptr<const BuildDatabase::ObjectFileInfo>>;

inline std::string LINE_INDENT() const {
return StringUtils::repeat(TAB, tabsDepth);
}

void addTargetLinkLibraries(const std::string &targetName, const std::vector<std::string>& librariesNamesToLink);

void addDiscoverTestDirective(const std::string &testTargetName);

void addExecutable(const std::string &executableName, const std::vector<fs::path> &sourceFiles);

void addIncludeDirectoriesForTarget(const std::string &targetName, const std::set<std::string> includePaths);

void addLibrary(const std::string &libraryName, bool isShared, const std::vector<fs::path> &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<std::string> 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<const BuildDatabase::TargetInfo> 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<std::string> prepareCompileFlagsForTestFile(const fs::path &sourcePath);

};
}

#endif //UTBOTCPP_CMAKELISTSPRINTER_H
71 changes: 36 additions & 35 deletions server/src/printers/NativeMakefilePrinter.cpp
Original file line number Diff line number Diff line change
@@ -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<std::string> SANITIZER_NEEDED_FLAGS = {
"-g", "-fno-omit-frame-pointer", "-fno-optimize-sibling-calls"
const std::string FPIC_FLAG = "-fPIC";
const std::vector<std::string> 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<std::string> 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<std::string> 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());
2 changes: 1 addition & 1 deletion server/src/printers/NativeMakefilePrinter.h
Original file line number Diff line number Diff line change
@@ -10,8 +10,8 @@
#include <vector>

namespace printer {
static const std::string FORCE = ".FORCE";

static const std::string FORCE = ".FORCE";
class NativeMakefilePrinter : public RelativeMakefilePrinter {
friend class TestMakefilesPrinter;
private:
15 changes: 12 additions & 3 deletions server/src/printers/SourceWrapperPrinter.cpp
Original file line number Diff line number Diff line change
@@ -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();
}
}
4 changes: 4 additions & 0 deletions server/src/printers/SourceWrapperPrinter.h
Original file line number Diff line number Diff line change
@@ -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);
};
}

3 changes: 2 additions & 1 deletion server/src/streams/stubs/CLIStubsWriter.cpp
Original file line number Diff line number Diff line change
@@ -4,7 +4,8 @@
#include "loguru.h"

void CLIStubsWriter::writeResponse(const std::vector<Stubs> &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";
2 changes: 1 addition & 1 deletion server/src/streams/stubs/CLIStubsWriter.h
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ class CLIStubsWriter : public StubsWriter {
public:
explicit CLIStubsWriter(): StubsWriter(nullptr) {};

void writeResponse(const std::vector<Stubs> &synchronizedStubs, const fs::path &testDirPath) override;
void writeResponse(const std::vector<Stubs> &synchronizedStubs, const fs::path &testDirPath, const std::string &message) override;

};

19 changes: 10 additions & 9 deletions server/src/streams/stubs/ServerStubsWriter.cpp
Original file line number Diff line number Diff line change
@@ -3,21 +3,22 @@
#include "loguru.h"

void ServerStubsWriter::writeResponse(const std::vector<Stubs> &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);
}
3 changes: 2 additions & 1 deletion server/src/streams/stubs/ServerStubsWriter.h
Original file line number Diff line number Diff line change
@@ -10,7 +10,8 @@ class ServerStubsWriter : public StubsWriter {
}

void writeResponse(const std::vector<Stubs> &synchronizedStubs,
const fs::path &testDirPath) override;
const fs::path &testDirPath,
const std::string &message) override;
private:
bool synchronizeCode;
};
2 changes: 1 addition & 1 deletion server/src/streams/stubs/StubsWriter.h
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ class StubsWriter : public utbot::ServerWriter<testsgen::StubsResponse> {
public:
explicit StubsWriter(grpc::ServerWriter<testsgen::StubsResponse> *writer);

virtual void writeResponse(const std::vector<Stubs> &synchronizedStubs, const fs::path &testDirPath) = 0;
virtual void writeResponse(const std::vector<Stubs> &synchronizedStubs, const fs::path &testDirPath, const std::string &message) = 0;

static void writeStubsFilesOnServer(const std::vector<Stubs> &stubs, const fs::path &testDirPath);

13 changes: 13 additions & 0 deletions server/src/streams/tests/ServerTestsWriter.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
2 changes: 2 additions & 0 deletions server/src/streams/tests/ServerTestsWriter.h
Original file line number Diff line number Diff line change
@@ -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,
4 changes: 4 additions & 0 deletions server/src/streams/tests/TestsWriter.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
2 changes: 2 additions & 0 deletions server/src/streams/tests/TestsWriter.h
Original file line number Diff line number Diff line change
@@ -20,6 +20,8 @@ class TestsWriter : public utbot::ServerWriter<testsgen::TestsResponse> {
std::function<void(tests::Tests &)> &&prepareTests,
std::function<void()> &&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;
2 changes: 2 additions & 0 deletions server/src/testgens/BaseTestGen.h
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
#include "streams/tests/TestsWriter.h"
#include "stubs/Stubs.h"
#include "types/Types.h"
#include "utils/FileInfoForTransfer.h"

#include <grpcpp/grpcpp.h>
#include <protobuf/testgen.grpc.pb.h>
@@ -29,6 +30,7 @@ class BaseTestGen {
tests::TestsMap tests;
std::unordered_map<std::string, types::Type> methodNameToReturnTypeMap;
std::vector<Stubs> synchronizedStubs;
std::vector<Stubs> synchronizedWrappers;
types::TypeMaps types;

CollectionUtils::FileSet targetSources;
7 changes: 7 additions & 0 deletions server/src/utils/Copyright.h
Original file line number Diff line number Diff line change
@@ -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;
};

15 changes: 15 additions & 0 deletions server/src/utils/FileInfoForTransfer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//

#ifndef UTBOTCPP_FILEINFOFORTRANSFER_H
#define UTBOTCPP_FILEINFOFORTRANSFER_H

#include <string>
#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
5 changes: 5 additions & 0 deletions server/src/utils/FileSystemUtils.cpp
Original file line number Diff line number Diff line change
@@ -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<char>(inputStream), std::istreambuf_iterator<char>() };
}

size_t RecursiveDirectoryIterator::size() const {
return std::distance(fs::recursive_directory_iterator(directory), fs::recursive_directory_iterator());
}
2 changes: 2 additions & 0 deletions server/src/utils/FileSystemUtils.h
Original file line number Diff line number Diff line change
@@ -14,6 +14,8 @@ namespace FileSystemUtils {

std::vector<fs::path> recursiveDirectories(const fs::path &root);

std::string read(const fs::path &path);

class DirectoryIterator : public fs::directory_iterator {
fs::path directory;

2 changes: 0 additions & 2 deletions server/src/utils/LinkerUtils.h
Original file line number Diff line number Diff line change
@@ -10,8 +10,6 @@ namespace LinkerUtils {
fs::path applySuffix(const fs::path &output,
BuildResult::Type unitType,
const std::string &suffixForParentOfStubs);

};


#endif // UNITTESTBOT_LINKERUTILS_H
2 changes: 1 addition & 1 deletion server/test/framework/CLI_Tests.cpp
Original file line number Diff line number Diff line change
@@ -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());
}
}
69 changes: 69 additions & 0 deletions server/test/framework/CMakeGeneration_Tests.cpp
Original file line number Diff line number Diff line change
@@ -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");
}
}
15 changes: 9 additions & 6 deletions server/test/framework/Server_Tests.cpp
Original file line number Diff line number Diff line change
@@ -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 <functional>
#include <tuple>
#include <fstream>
#include <iostream>

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<testsgen::TestFilter> 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<ServerCoverageAndResultsWriter>(nullptr);
CoverageAndResultsGenerator coverageGenerator{ request.get(), coverageAndResultsWriter.get() };
utbot::SettingsContext settingsContext{ true, true, 15, 0, true, false };
std::make_unique<ServerCoverageAndResultsWriter>(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());
}
}
18 changes: 9 additions & 9 deletions server/test/framework/Stub_Tests.cpp
Original file line number Diff line number Diff line change
@@ -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();
8 changes: 8 additions & 0 deletions server/test/framework/TestUtils.cpp
Original file line number Diff line number Diff line change
@@ -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<fs::path>& 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);
}
}
2 changes: 2 additions & 0 deletions server/test/framework/TestUtils.h
Original file line number Diff line number Diff line change
@@ -137,6 +137,8 @@ namespace testUtils {
void checkGenerationStatsCSV(const fs::path &statsPath, const std::vector<fs::path> &containedFiles);

void checkExecutionStatsCSV(const fs::path &statsPath, const std::vector<fs::path> &containedFiles);

void checkCMakeGenerated(const fs::path &suitePath, const fs::path &testsDirPath, const std::optional<fs::path> &expectedCMakePath = {});
}

#endif // UNITTESTBOT_TESTUTILS_H
5 changes: 5 additions & 0 deletions server/test/framework/main.cpp
Original file line number Diff line number Diff line change
@@ -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(
9 changes: 9 additions & 0 deletions server/test/suites/cmake/shared/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 4 additions & 0 deletions server/test/suites/cmake/shared/bar.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

int bar(int x, int y) {
return x*x + y;
}
5 changes: 5 additions & 0 deletions server/test/suites/cmake/shared/bar.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

#ifndef UTBOTCPP_BAR_H
#define UTBOTCPP_BAR_H
int bar(int x, int y);
#endif //UTBOTCPP_BAR_H
24 changes: 24 additions & 0 deletions server/test/suites/cmake/shared/expected_cmake.txt
Original file line number Diff line number Diff line change
@@ -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)
5 changes: 5 additions & 0 deletions server/test/suites/cmake/shared/foo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include "bar.h"

int foo(int x) {
return x + bar(x, x);
}
9 changes: 9 additions & 0 deletions server/test/suites/cmake/simple/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
5 changes: 5 additions & 0 deletions server/test/suites/cmake/simple/a.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include "bar.h"

int foo(int x) {
return bar(x) + 4;
}
8 changes: 8 additions & 0 deletions server/test/suites/cmake/simple/b.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

int bar(int y) {
return y + 20;
}

int main() {
return 0;
}
4 changes: 4 additions & 0 deletions server/test/suites/cmake/simple/bar.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#ifndef UTBOTCPP_BAR_H
#define UTBOTCPP_BAR_H
int bar(int);
#endif //UTBOTCPP_BAR_H
24 changes: 24 additions & 0 deletions server/test/suites/cmake/simple/expected_cmake.txt
Original file line number Diff line number Diff line change
@@ -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)
13 changes: 13 additions & 0 deletions server/test/suites/cmake/stubs/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
55 changes: 55 additions & 0 deletions server/test/suites/cmake/stubs/expected_cmake_exe_target.txt
Original file line number Diff line number Diff line change
@@ -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)
29 changes: 29 additions & 0 deletions server/test/suites/cmake/stubs/expected_cmake_unitA_target.txt
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 6 additions & 0 deletions server/test/suites/cmake/stubs/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "caller.c"

int main() {
int f = foo(0, 0);
return 0;
}
4 changes: 4 additions & 0 deletions server/test/suites/cmake/stubs/unitA/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

add_library(unitA caller.c)
target_include_directories(unitA PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(unitA unitB)
10 changes: 10 additions & 0 deletions server/test/suites/cmake/stubs/unitA/caller.c
Original file line number Diff line number Diff line change
@@ -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;
//}
8 changes: 8 additions & 0 deletions server/test/suites/cmake/stubs/unitA/caller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//

#ifndef UTBOTCPP_CALLER_H
#define UTBOTCPP_CALLER_H

int foo(int x, int y);

#endif //UTBOTCPP_CALLER_H
3 changes: 3 additions & 0 deletions server/test/suites/cmake/stubs/unitB/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 6 additions & 0 deletions server/test/suites/cmake/stubs/unitB/callee.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//
#include "callee.h"

int do_stuff(int x, int y) {
return x*x + y*y;
}
8 changes: 8 additions & 0 deletions server/test/suites/cmake/stubs/unitB/callee.h
Original file line number Diff line number Diff line change
@@ -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
44 changes: 44 additions & 0 deletions server/test/suites/small-project/expected_cmake.txt
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 2 additions & 1 deletion vscode-plugin/src/test/helper.ts
Original file line number Diff line number Diff line change
@@ -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);