Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ file(GLOB ALL_SOURCES
"${PROJECT_SOURCE_DIR}/src/streams/tests/*"
"${PROJECT_SOURCE_DIR}/src/testgens/*"
"${PROJECT_SOURCE_DIR}/src/utils/*"
"${PROJECT_SOURCE_DIR}/src/utils/stats/*"
"${PROJECT_SOURCE_DIR}/src/stubs/*"
"${PROJECT_SOURCE_DIR}/src/visitors/*"
"${PROJECT_SOURCE_DIR}/src/commands/*"
Expand Down
1 change: 0 additions & 1 deletion server/proto/testgen.proto
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ message ProjectContext {
string projectPath = 2;
string testDirPath = 3;
string buildDirRelativePath = 4;
string resultsDirRelativePath = 5;
}

message SettingsContext {
Expand Down
228 changes: 115 additions & 113 deletions server/src/KleeRunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include "utils/FileSystemUtils.h"
#include "utils/KleeUtils.h"
#include "utils/LogUtils.h"
#include "utils/stats/CSVReader.h"
#include "utils/stats/TestsGenerationStats.h"

#include "loguru.h"

Expand All @@ -18,6 +20,27 @@

using namespace tests;

namespace {
void clearUnusedData(const fs::path &kleeDir) {
fs::remove(kleeDir / "assembly.ll");
fs::remove(kleeDir / "run.istats");
}

StatsUtils::KleeStats writeKleeStats(const fs::path &kleeOut) {
ShellExecTask::ExecutionParameters kleeStatsParams("klee-stats",
{"--utbot-config", kleeOut.string(),
"--table-format=readable-csv"});
auto[out, status, _] = ShellExecTask::runShellCommandTask(kleeStatsParams);
if (status != 0) {
LOG_S(ERROR) << "klee-stats call failed:" << "\n" << out;
return {};
}
LOG_S(DEBUG) << "klee-stats report:" << '\n' << out;
std::stringstream ss(out);
return StatsUtils::KleeStats(ss);
}
}

KleeRunner::KleeRunner(utbot::ProjectContext projectContext,
utbot::SettingsContext settingsContext,
fs::path serverBuildDir)
Expand All @@ -32,7 +55,8 @@ void KleeRunner::runKlee(const std::vector<tests::TestMethod> &testMethods,
const std::shared_ptr<LineInfo> &lineInfo,
TestsWriter *testsWriter,
bool isBatched,
bool interactiveMode) {
bool interactiveMode,
StatsUtils::TestsGenerationStatsFileMap &generationStats) {
LOG_SCOPE_FUNCTION(DEBUG);

fs::path kleeOutDir = Paths::getKleeOutDir(projectTmpPath);
Expand Down Expand Up @@ -77,19 +101,14 @@ void KleeRunner::runKlee(const std::vector<tests::TestMethod> &testMethods,
LOG_S(MAX) << logStream.str();
}
if (interactiveMode) {
if (!batch.empty()) {
processBatchWithInteractive(batch, tests, ktests);
}
processBatchWithInteractive(batch, tests, ktests);
} else {
for (auto const &testMethod : batch) {
MethodKtests ktestChunk;
processBatchWithoutInteractive(ktestChunk, testMethod, tests);
ExecUtils::throwIfCancelled();
ktests.push_back(ktestChunk);
}
processBatchWithoutInteractive(batch, tests, ktests);
}
auto kleeStats = writeKleeStats(Paths::kleeOutDirForFilePath(projectContext, projectTmpPath, filePath));
generator->parseKTestsToFinalCode(tests, methodNameToReturnTypeMap, ktests, lineInfo,
settingsContext.verbose);
generationStats.addFileStats(kleeStats, tests);

sarif::sarifAddTestsToResults(projectContext, tests, sarifResults);
};
Expand All @@ -108,121 +127,101 @@ void KleeRunner::runKlee(const std::vector<tests::TestMethod> &testMethods,
std::move(prepareTotal));
}

namespace {
void clearUnusedData(const fs::path &kleeDir) {
fs::remove(kleeDir / "assembly.ll");
fs::remove(kleeDir / "run.istats");
}

void writeKleeStats(const fs::path &kleeOut) {
ShellExecTask::ExecutionParameters kleeStatsParams("klee-stats",
{ "--utbot-config", kleeOut.string() });
auto [out, status, _] = ShellExecTask::runShellCommandTask(kleeStatsParams);
if (status != 0) {
LOG_S(ERROR) << "klee-stats call failed:";
LOG_S(ERROR) << out;
} else {
LOG_S(DEBUG) << "klee-stats report:";
LOG_S(DEBUG) << '\n' << out;
}
}
}

static void processMethod(MethodKtests &ktestChunk,
tests::Tests &tests,
const fs::path &kleeOut,
const tests::TestMethod &method) {
if (fs::exists(kleeOut)) {
clearUnusedData(kleeOut);
bool hasTimeout = false;
bool hasError = false;
for (auto const &entry : fs::directory_iterator(kleeOut)) {
auto const &path = entry.path();
if (Paths::isKtestJson(path)) {
if (Paths::hasEarly(path)) {
hasTimeout = true;
} else if (Paths::hasInternalError(path)) {
hasError = true;
} else {
std::unique_ptr<TestCase, decltype(&TestCase_free)> ktestData{
TC_fromFile(path.c_str()), TestCase_free
};
if (ktestData == nullptr) {
LOG_S(WARNING) << "Unable to open .ktestjson file";
continue;
}

const std::vector<fs::path> &errorDescriptorFiles =
Paths::getErrorDescriptors(path);
if (!fs::exists(kleeOut)) {
return;
}

UTBotKTest::Status status = errorDescriptorFiles.empty()
? UTBotKTest::Status::SUCCESS
: UTBotKTest::Status::FAILED;
std::vector<ConcretizedObject> kTestObjects(
ktestData->objects, ktestData->objects + ktestData->n_objects);

std::vector<UTBotKTestObject> objects = CollectionUtils::transform(
kTestObjects, [](const ConcretizedObject &kTestObject) {
return UTBotKTestObject{ kTestObject };
});

std::vector<std::string> errorDescriptors = CollectionUtils::transform(
errorDescriptorFiles, [](const fs::path &errorFile) {
std::ifstream fileWithError(errorFile.c_str(), std::ios_base::in);
std::string content((std::istreambuf_iterator<char>(fileWithError)),
std::istreambuf_iterator<char>());

const std::string &errorId = errorFile.stem().extension().string();
if (!errorId.empty()) {
// skip leading dot
content += "\n" + sarif::ERROR_ID_KEY + ":" + errorId.substr(1);
}
return content;
});


ktestChunk[method].emplace_back(objects, status, errorDescriptors);
clearUnusedData(kleeOut);
bool hasTimeout = false;
bool hasError = false;
for (auto const &entry : fs::directory_iterator(kleeOut)) {
auto const &path = entry.path();
if (Paths::isKtestJson(path)) {
if (Paths::hasEarly(path)) {
hasTimeout = true;
} else if (Paths::hasInternalError(path)) {
hasError = true;
} else {
std::unique_ptr<TestCase, decltype(&TestCase_free)> ktestData{
TC_fromFile(path.c_str()), TestCase_free
};
if (ktestData == nullptr) {
LOG_S(WARNING) << "Unable to open .ktestjson file";
continue;
}
const std::vector<fs::path> &errorDescriptorFiles =
Paths::getErrorDescriptors(path);

UTBotKTest::Status status = errorDescriptorFiles.empty()
? UTBotKTest::Status::SUCCESS
: UTBotKTest::Status::FAILED;
std::vector<ConcretizedObject> kTestObjects(
ktestData->objects, ktestData->objects + ktestData->n_objects);

std::vector<UTBotKTestObject> objects = CollectionUtils::transform(
kTestObjects, [](const ConcretizedObject &kTestObject) {
return UTBotKTestObject{ kTestObject };
});

std::vector<std::string> errorDescriptors = CollectionUtils::transform(
errorDescriptorFiles, [](const fs::path &errorFile) {
std::ifstream fileWithError(errorFile.c_str(), std::ios_base::in);
std::string content((std::istreambuf_iterator<char>(fileWithError)),
std::istreambuf_iterator<char>());

const std::string &errorId = errorFile.stem().extension().string();
if (!errorId.empty()) {
// skip leading dot
content += "\n" + sarif::ERROR_ID_KEY + ":" + errorId.substr(1);
}
return content;
});

ktestChunk[method].emplace_back(objects, status, errorDescriptors);
}
}
if (hasTimeout) {
std::string message = StringUtils::stringFormat(
"Some tests for function '%s' were skipped, as execution of function is "
"out of timeout.",
method.methodName);
tests.commentBlocks.emplace_back(std::move(message));
}
if (hasError) {
std::string message = StringUtils::stringFormat(
"Some tests for function '%s' were skipped, as execution of function leads "
"KLEE to the internal error. See console log for more details.",
method.methodName);
tests.commentBlocks.emplace_back(std::move(message));
}

writeKleeStats(kleeOut);
}
if (hasTimeout) {
std::string message = StringUtils::stringFormat(
"Some tests for function '%s' were skipped, as execution of function is "
"out of timeout.",
method.methodName);
tests.commentBlocks.emplace_back(std::move(message));
}
if (hasError) {
std::string message = StringUtils::stringFormat(
"Some tests for function '%s' were skipped, as execution of function leads "
"KLEE to the internal error. See console log for more details.",
method.methodName);
tests.commentBlocks.emplace_back(std::move(message));
}

if (!CollectionUtils::containsKey(ktestChunk, method) || ktestChunk.at(method).empty()) {
tests.commentBlocks.emplace_back(StringUtils::stringFormat(
"Tests for %s were not generated. Maybe the function is too complex.",
method.methodName));
}
if (!CollectionUtils::containsKey(ktestChunk, method) || ktestChunk.at(method).empty()) {
tests.commentBlocks.emplace_back(StringUtils::stringFormat(
"Tests for %s were not generated. Maybe the function is too complex.",
method.methodName));
}
}

void KleeRunner::processBatchWithoutInteractive(MethodKtests &ktestChunk,
const TestMethod &testMethod,
Tests &tests) {
if (!tests.isFilePresentedInArtifact) {
void KleeRunner::processBatchWithoutInteractive(const std::vector<tests::TestMethod> &testMethods,
tests::Tests &tests,
std::vector<tests::MethodKtests> &ktests) {
if (!tests.isFilePresentedInArtifact || testMethods.empty()) {
return;
}
if (testMethod.sourceFilePath != tests.sourceFilePath) {
std::string message = StringUtils::stringFormat(

for (const auto &testMethod : testMethods) {
if (testMethod.sourceFilePath != tests.sourceFilePath) {
std::string message = StringUtils::stringFormat(
"While generating tests for source file: %s tried to generate tests for method %s "
"from another source file: %s. This can cause invalid generation.\n",
tests.sourceFilePath, testMethod.methodName, testMethod.sourceFilePath);
LOG_S(WARNING) << message;
}
LOG_S(WARNING) << message;
}

std::string entryPoint = KleeUtils::entryPointFunction(tests, testMethod.methodName, true);
std::string entryPointFlag = StringUtils::stringFormat("--entry-point=%s", entryPoint);
Expand Down Expand Up @@ -261,18 +260,21 @@ void KleeRunner::processBatchWithoutInteractive(MethodKtests &ktestChunk,
LOG_S(DEBUG) << "Klee command :: " + StringUtils::joinWith(argvData, " ");
MEASURE_FUNCTION_EXECUTION_TIME

RunKleeTask task(cargv.size(), cargv.data(), settingsContext.timeoutPerFunction);
ExecUtils::ExecutionResult result __attribute__((unused)) = task.run();
ExecUtils::throwIfCancelled();
RunKleeTask task(cargv.size(), cargv.data(), settingsContext.timeoutPerFunction);
ExecUtils::ExecutionResult result __attribute__((unused)) = task.run();
ExecUtils::throwIfCancelled();

processMethod(ktestChunk, tests, kleeOut, testMethod);
MethodKtests ktestChunk;
processMethod(ktestChunk, tests, kleeOut, testMethod);
ktests.push_back(ktestChunk);
}
}
}

void KleeRunner::processBatchWithInteractive(const std::vector<tests::TestMethod> &testMethods,
tests::Tests &tests,
std::vector<tests::MethodKtests> &ktests) {
if (!tests.isFilePresentedInArtifact) {
if (!tests.isFilePresentedInArtifact || testMethods.empty()) {
return;
}

Expand Down
21 changes: 10 additions & 11 deletions server/src/KleeRunner.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include "SettingsContext.h"
#include "Tests.h"
#include "streams/tests/TestsWriter.h"
#include "utils/stats/KleeStats.h"
#include "utils/stats/TestsGenerationStats.h"

#include <grpcpp/grpcpp.h>

Expand All @@ -27,24 +29,21 @@ class KleeRunner {
* generated unit tests for each batch.
* @throws ExecutionProcessException if a Clang call returns non-zero code.
*/
void runKlee(const std::vector<tests::TestMethod> &testMethods,
tests::TestsMap &testsMap,
const std::shared_ptr<KleeGenerator>& generator,
void runKlee(const std::vector<tests::TestMethod> &testMethods, tests::TestsMap &testsMap,
const std::shared_ptr<KleeGenerator> &generator,
const std::unordered_map<std::string, types::Type> &methodNameToReturnTypeMap,
const std::shared_ptr<LineInfo> &lineInfo,
TestsWriter *testsWriter,
bool isBatched,
bool interactiveMode);
const std::shared_ptr<LineInfo> &lineInfo, TestsWriter *testsWriter, bool isBatched,
bool interactiveMode,
StatsUtils::TestsGenerationStatsFileMap &generationStats);

private:
const utbot::ProjectContext projectContext;
const utbot::SettingsContext settingsContext;
fs::path projectTmpPath;


void processBatchWithoutInteractive(tests::MethodKtests &ktestChunk,
const tests::TestMethod &testMethod,
tests::Tests &tests);
void processBatchWithoutInteractive(const std::vector<tests::TestMethod> &testMethods,
tests::Tests &tests,
std::vector<tests::MethodKtests> &ktests);

void processBatchWithInteractive(const std::vector<tests::TestMethod> &testMethods,
tests::Tests &tests,
Expand Down
Loading