Skip to content

Commit 1c1dffe

Browse files
committed
add generation statistics report
1 parent 6048615 commit 1c1dffe

26 files changed

+689
-294
lines changed

server/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ file(GLOB ALL_SOURCES
114114
"${PROJECT_SOURCE_DIR}/src/streams/tests/*"
115115
"${PROJECT_SOURCE_DIR}/src/testgens/*"
116116
"${PROJECT_SOURCE_DIR}/src/utils/*"
117+
"${PROJECT_SOURCE_DIR}/src/utils/stats/*"
117118
"${PROJECT_SOURCE_DIR}/src/stubs/*"
118119
"${PROJECT_SOURCE_DIR}/src/visitors/*"
119120
"${PROJECT_SOURCE_DIR}/src/commands/*"

server/src/KleeRunner.cpp

Lines changed: 124 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,54 @@
99
#include "utils/FileSystemUtils.h"
1010
#include "utils/KleeUtils.h"
1111
#include "utils/LogUtils.h"
12+
#include "utils/stats/CSVReader.h"
1213

1314
#include "loguru.h"
1415

1516
#include <fstream>
1617
#include <utility>
18+
#include <utils/stats/TestsGenerationStats.h>
1719

1820
using namespace tests;
1921

22+
namespace {
23+
void clearUnusedData(const fs::path &kleeDir) {
24+
fs::remove(kleeDir / "assembly.ll");
25+
fs::remove(kleeDir / "run.istats");
26+
}
27+
28+
StatsUtils::KleeStats parseKleeStatsReport(const std::string &kleeStatsReport) {
29+
std::stringstream ss(kleeStatsReport);
30+
StatsUtils::CSVTable parsedCSV = StatsUtils::readCSV(ss, ',');
31+
std::map<std::string, std::chrono::milliseconds> timeStats;
32+
std::vector<std::string> keys = {"Time(s)", "TSolver(s)", "TResolve(s)"};
33+
std::vector<std::chrono::milliseconds> timeValues;
34+
for (const auto &key: keys) {
35+
if (!CollectionUtils::containsKey(parsedCSV, key)) {
36+
LOG_S(WARNING) << StringUtils::stringFormat("Key %s not found in klee-stats report", key);
37+
}
38+
std::chrono::milliseconds totalTime((int)(1000 * std::stof(parsedCSV[key].back())));
39+
timeValues.emplace_back(totalTime);
40+
}
41+
return StatsUtils::KleeStats(timeValues[0], timeValues[1], timeValues[2]);
42+
}
43+
44+
StatsUtils::KleeStats writeKleeStats(const fs::path &kleeOut) {
45+
ShellExecTask::ExecutionParameters kleeStatsParams("klee-stats",
46+
{"--utbot-config", kleeOut.string(),
47+
"--table-format=readable-csv"});
48+
auto[out, status, _] = ShellExecTask::runShellCommandTask(kleeStatsParams);
49+
if (status != 0) {
50+
LOG_S(ERROR) << "klee-stats call failed:";
51+
LOG_S(ERROR) << out;
52+
} else {
53+
LOG_S(DEBUG) << "klee-stats report:";
54+
LOG_S(DEBUG) << '\n' << out;
55+
}
56+
return parseKleeStatsReport(out);
57+
}
58+
}
59+
2060
KleeRunner::KleeRunner(utbot::ProjectContext projectContext,
2161
utbot::SettingsContext settingsContext,
2262
fs::path serverBuildDir)
@@ -31,7 +71,8 @@ void KleeRunner::runKlee(const std::vector<tests::TestMethod> &testMethods,
3171
const std::shared_ptr<LineInfo> &lineInfo,
3272
TestsWriter *testsWriter,
3373
bool isBatched,
34-
bool interactiveMode) {
74+
bool interactiveMode,
75+
StatsUtils::TestsGenerationStatsFileMap &generationStats) {
3576
LOG_SCOPE_FUNCTION(DEBUG);
3677

3778
fs::path kleeOutDir = Paths::getKleeOutDir(projectTmpPath);
@@ -74,120 +115,97 @@ void KleeRunner::runKlee(const std::vector<tests::TestMethod> &testMethods,
74115
LOG_S(MAX) << logStream.str();
75116
}
76117
if (interactiveMode) {
77-
if (!batch.empty()) {
78-
processBatchWithInteractive(batch, tests, ktests);
79-
}
118+
processBatchWithInteractive(batch, tests, ktests);
80119
} else {
81-
for (auto const &testMethod : batch) {
82-
MethodKtests ktestChunk;
83-
processBatchWithoutInteractive(ktestChunk, testMethod, tests);
84-
ExecUtils::throwIfCancelled();
85-
ktests.push_back(ktestChunk);
86-
}
120+
processBatchWithoutInteractive(batch, tests, ktests);
87121
}
122+
auto kleeStats = writeKleeStats(Paths::kleeOutDirForFilePath(projectContext, projectTmpPath, filePath));
88123
generator->parseKTestsToFinalCode(tests, methodNameToReturnTypeMap, ktests, lineInfo,
89124
settingsContext.verbose);
125+
generationStats.addFileStats(kleeStats, tests);
90126
};
91127

92128
testsWriter->writeTestsWithProgress(testsMap, "Running klee", projectContext.testDirPath,
93129
std::move(writeFunctor));
94130
}
95131

96-
namespace {
97-
void clearUnusedData(const fs::path &kleeDir) {
98-
fs::remove(kleeDir / "assembly.ll");
99-
fs::remove(kleeDir / "run.istats");
100-
}
101-
102-
void writeKleeStats(const fs::path &kleeOut) {
103-
ShellExecTask::ExecutionParameters kleeStatsParams("klee-stats",
104-
{ "--utbot-config", kleeOut.string() });
105-
auto [out, status, _] = ShellExecTask::runShellCommandTask(kleeStatsParams);
106-
if (status != 0) {
107-
LOG_S(ERROR) << "klee-stats call failed:";
108-
LOG_S(ERROR) << out;
109-
} else {
110-
LOG_S(DEBUG) << "klee-stats report:";
111-
LOG_S(DEBUG) << '\n' << out;
112-
}
113-
}
114-
}
115-
116132
static void processMethod(MethodKtests &ktestChunk,
117133
tests::Tests &tests,
118134
const fs::path &kleeOut,
119135
const tests::TestMethod &method) {
120-
if (fs::exists(kleeOut)) {
121-
clearUnusedData(kleeOut);
122-
bool hasTimeout = false;
123-
bool hasError = false;
124-
for (auto const &entry : fs::directory_iterator(kleeOut)) {
125-
auto const &path = entry.path();
126-
if (Paths::isKtestJson(path)) {
127-
if (Paths::hasEarly(path)) {
128-
hasTimeout = true;
129-
} else if (Paths::hasInternalError(path)) {
130-
hasError = true;
131-
} else {
132-
std::unique_ptr<TestCase, decltype(&TestCase_free)> ktestData{
133-
TC_fromFile(path.c_str()), TestCase_free
134-
};
135-
if (ktestData == nullptr) {
136-
LOG_S(WARNING) << "Unable to open .ktestjson file";
137-
continue;
138-
}
139-
UTBotKTest::Status status = Paths::hasError(path) ? UTBotKTest::Status::FAILED
140-
: UTBotKTest::Status::SUCCESS;
141-
std::vector<ConcretizedObject> kTestObjects(
142-
ktestData->objects, ktestData->objects + ktestData->n_objects);
143-
144-
std::vector<UTBotKTestObject> objects = CollectionUtils::transform(
145-
kTestObjects, [](const ConcretizedObject &kTestObject) {
146-
return UTBotKTestObject{ kTestObject };
147-
});
148-
149-
ktestChunk[method].emplace_back(objects, status);
136+
if (!fs::exists(kleeOut)) {
137+
return;
138+
}
139+
140+
clearUnusedData(kleeOut);
141+
bool hasTimeout = false;
142+
bool hasError = false;
143+
for (auto const &entry : fs::directory_iterator(kleeOut)) {
144+
auto const &path = entry.path();
145+
if (Paths::isKtestJson(path)) {
146+
if (Paths::hasEarly(path)) {
147+
hasTimeout = true;
148+
} else if (Paths::hasInternalError(path)) {
149+
hasError = true;
150+
} else {
151+
std::unique_ptr<TestCase, decltype(&TestCase_free)> ktestData{
152+
TC_fromFile(path.c_str()), TestCase_free
153+
};
154+
if (ktestData == nullptr) {
155+
LOG_S(WARNING) << "Unable to open .ktestjson file";
156+
continue;
150157
}
151-
}
152-
}
153-
if (hasTimeout) {
154-
std::string message = StringUtils::stringFormat(
155-
"Some tests for function '%s' were skipped, as execution of function is "
156-
"out of timeout.",
157-
method.methodName);
158-
tests.commentBlocks.emplace_back(std::move(message));
159-
}
160-
if (hasError) {
161-
std::string message = StringUtils::stringFormat(
162-
"Some tests for function '%s' were skipped, as execution of function leads "
163-
"KLEE to the internal error. See console log for more details.",
164-
method.methodName);
165-
tests.commentBlocks.emplace_back(std::move(message));
166-
}
158+
UTBotKTest::Status status = Paths::hasError(path) ? UTBotKTest::Status::FAILED
159+
: UTBotKTest::Status::SUCCESS;
160+
std::vector<ConcretizedObject> kTestObjects(
161+
ktestData->objects, ktestData->objects + ktestData->n_objects);
167162

168-
writeKleeStats(kleeOut);
163+
std::vector<UTBotKTestObject> objects = CollectionUtils::transform(
164+
kTestObjects, [](const ConcretizedObject &kTestObject) {
165+
return UTBotKTestObject{ kTestObject };
166+
});
169167

170-
if (!CollectionUtils::containsKey(ktestChunk, method) || ktestChunk.at(method).empty()) {
171-
tests.commentBlocks.emplace_back(StringUtils::stringFormat(
172-
"Tests for %s were not generated. Maybe the function is too complex.",
173-
method.methodName));
168+
ktestChunk[method].emplace_back(objects, status);
169+
}
174170
}
175171
}
172+
if (hasTimeout) {
173+
std::string message = StringUtils::stringFormat(
174+
"Some tests for function '%s' were skipped, as execution of function is "
175+
"out of timeout.",
176+
method.methodName);
177+
tests.commentBlocks.emplace_back(std::move(message));
178+
}
179+
if (hasError) {
180+
std::string message = StringUtils::stringFormat(
181+
"Some tests for function '%s' were skipped, as execution of function leads "
182+
"KLEE to the internal error. See console log for more details.",
183+
method.methodName);
184+
tests.commentBlocks.emplace_back(std::move(message));
185+
}
186+
187+
if (!CollectionUtils::containsKey(ktestChunk, method) || ktestChunk.at(method).empty()) {
188+
tests.commentBlocks.emplace_back(StringUtils::stringFormat(
189+
"Tests for %s were not generated. Maybe the function is too complex.",
190+
method.methodName));
191+
}
176192
}
177193

178-
void KleeRunner::processBatchWithoutInteractive(MethodKtests &ktestChunk,
179-
const TestMethod &testMethod,
180-
Tests &tests) {
181-
if (!tests.isFilePresentedInArtifact) {
194+
void KleeRunner::processBatchWithoutInteractive(const std::vector<tests::TestMethod> &testMethods,
195+
tests::Tests &tests,
196+
std::vector<tests::MethodKtests> &ktests) {
197+
if (!tests.isFilePresentedInArtifact || testMethods.empty()) {
182198
return;
183199
}
184-
if (testMethod.sourceFilePath != tests.sourceFilePath) {
185-
std::string message = StringUtils::stringFormat(
186-
"While generating tests for source file: %s tried to generate tests for method %s "
187-
"from another source file: %s. This can cause invalid generation.\n",
188-
tests.sourceFilePath, testMethod.methodName, testMethod.sourceFilePath);
189-
LOG_S(WARNING) << message;
190-
}
200+
201+
for (const auto &testMethod : testMethods) {
202+
if (testMethod.sourceFilePath != tests.sourceFilePath) {
203+
std::string message = StringUtils::stringFormat(
204+
"While generating tests for source file: %s tried to generate tests for method %s "
205+
"from another source file: %s. This can cause invalid generation.\n",
206+
tests.sourceFilePath, testMethod.methodName, testMethod.sourceFilePath);
207+
LOG_S(WARNING) << message;
208+
}
191209

192210
std::string entryPoint = KleeUtils::entryPointFunction(tests, testMethod.methodName, true);
193211
std::string entryPointFlag = StringUtils::stringFormat("--entry-point=%s", entryPoint);
@@ -219,25 +237,28 @@ void KleeRunner::processBatchWithoutInteractive(MethodKtests &ktestChunk,
219237
argvData.emplace_back("--sym-stdin");
220238
argvData.emplace_back(std::to_string(types::Type::symStdinSize));
221239

222-
{
223-
std::vector<char *> cargv, cenvp;
224-
std::vector<std::string> tmp;
225-
ExecUtils::toCArgumentsPtr(argvData, tmp, cargv, cenvp, false);
226-
LOG_S(DEBUG) << "Klee command :: " + StringUtils::joinWith(argvData, " ");
227-
MEASURE_FUNCTION_EXECUTION_TIME
240+
{
241+
std::vector<char *> cargv, cenvp;
242+
std::vector<std::string> tmp;
243+
ExecUtils::toCArgumentsPtr(argvData, tmp, cargv, cenvp, false);
244+
LOG_S(DEBUG) << "Klee command :: " + StringUtils::joinWith(argvData, " ");
245+
MEASURE_FUNCTION_EXECUTION_TIME
228246

229-
RunKleeTask task(cargv.size(), cargv.data(), settingsContext.timeoutPerFunction);
230-
ExecUtils::ExecutionResult result __attribute__((unused)) = task.run();
231-
ExecUtils::throwIfCancelled();
247+
RunKleeTask task(cargv.size(), cargv.data(), settingsContext.timeoutPerFunction);
248+
ExecUtils::ExecutionResult result __attribute__((unused)) = task.run();
249+
ExecUtils::throwIfCancelled();
232250

233-
processMethod(ktestChunk, tests, kleeOut, testMethod);
251+
MethodKtests ktestChunk;
252+
processMethod(ktestChunk, tests, kleeOut, testMethod);
253+
ktests.push_back(ktestChunk);
254+
}
234255
}
235256
}
236257

237258
void KleeRunner::processBatchWithInteractive(const std::vector<tests::TestMethod> &testMethods,
238259
tests::Tests &tests,
239260
std::vector<tests::MethodKtests> &ktests) {
240-
if (!tests.isFilePresentedInArtifact) {
261+
if (!tests.isFilePresentedInArtifact || testMethods.empty()) {
241262
return;
242263
}
243264

server/src/KleeRunner.h

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
#include "SettingsContext.h"
77
#include "Tests.h"
88
#include "streams/tests/TestsWriter.h"
9+
#include "utils/stats/KleeStats.h"
910

1011
#include <grpcpp/grpcpp.h>
1112

1213
#include <vector>
14+
#include <utils/stats/TestsGenerationStats.h>
1315

1416
class KleeRunner {
1517
public:
@@ -27,24 +29,21 @@ class KleeRunner {
2729
* generated unit tests for each batch.
2830
* @throws ExecutionProcessException if a Clang call returns non-zero code.
2931
*/
30-
void runKlee(const std::vector<tests::TestMethod> &testMethods,
31-
tests::TestsMap &testsMap,
32-
const std::shared_ptr<KleeGenerator>& generator,
32+
void runKlee(const std::vector<tests::TestMethod> &testMethods, tests::TestsMap &testsMap,
33+
const std::shared_ptr<KleeGenerator> &generator,
3334
const std::unordered_map<std::string, types::Type> &methodNameToReturnTypeMap,
34-
const std::shared_ptr<LineInfo> &lineInfo,
35-
TestsWriter *testsWriter,
36-
bool isBatched,
37-
bool interactiveMode);
35+
const std::shared_ptr<LineInfo> &lineInfo, TestsWriter *testsWriter, bool isBatched,
36+
bool interactiveMode,
37+
StatsUtils::TestsGenerationStatsFileMap &generationStats);
3838

3939
private:
4040
const utbot::ProjectContext projectContext;
4141
const utbot::SettingsContext settingsContext;
4242
fs::path projectTmpPath;
4343

44-
45-
void processBatchWithoutInteractive(tests::MethodKtests &ktestChunk,
46-
const tests::TestMethod &testMethod,
47-
tests::Tests &tests);
44+
void processBatchWithoutInteractive(const std::vector<tests::TestMethod> &testMethods,
45+
tests::Tests &tests,
46+
std::vector<tests::MethodKtests> &ktests);
4847

4948
void processBatchWithInteractive(const std::vector<tests::TestMethod> &testMethods,
5049
tests::Tests &tests,

server/src/Paths.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,20 @@ namespace Paths {
153153
[&path](auto const &suffix) { return errorFileExists(path, suffix); });
154154
}
155155

156+
fs::path kleeOutDirForFilePath(const utbot::ProjectContext &projectContext, const fs::path &projectTmpPath,
157+
const fs::path &filePath) {
158+
fs::path kleeOutDir = getKleeOutDir(projectTmpPath);
159+
fs::path relative = (fs::relative(addOrigExtensionAsSuffixAndAddNew(filePath, ""), projectContext.projectPath));
160+
return kleeOutDir / relative;
161+
}
162+
156163
fs::path kleeOutDirForEntrypoints(const utbot::ProjectContext &projectContext, const fs::path &projectTmpPath,
157164
const fs::path &srcFilePath, const std::string &methodName) {
158-
fs::path kleeOutDir = getKleeOutDir(projectTmpPath);
159-
fs::path relative = (fs::relative(addOrigExtensionAsSuffixAndAddNew(srcFilePath, ""),
160-
projectContext.projectPath));
161165
if (!methodName.empty()) {
162-
return kleeOutDir / relative / ("klee_out_" + methodName);
166+
return kleeOutDirForFilePath(projectContext, projectTmpPath, srcFilePath) / ("klee_out_" + methodName);
163167
}
164-
return kleeOutDir / relative / ("klee_out_" + srcFilePath.filename().stem().string());
168+
return kleeOutDirForFilePath(projectContext, projectTmpPath, srcFilePath) /
169+
("klee_out_" + srcFilePath.filename().stem().string());
165170
}
166171

167172
//endregion

server/src/Paths.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ namespace Paths {
238238

239239
bool hasError(fs::path const &path);
240240

241+
fs::path kleeOutDirForFilePath(const utbot::ProjectContext &projectContext, const fs::path &projectTmpPath,
242+
const fs::path &filePath);
243+
241244
fs::path kleeOutDirForEntrypoints(const utbot::ProjectContext &projectContext, const fs::path &projectTmpPath,
242245
const fs::path &srcFilePath, const std::string &methodName = "");
243246

@@ -411,6 +414,15 @@ namespace Paths {
411414

412415
//endregion
413416

417+
//region stats
418+
inline fs::path getGenerationStatsCSVPath(const utbot::ProjectContext &projectContext) {
419+
return projectContext.resultsDirPath / "generation-stats.csv";
420+
}
421+
inline fs::path getExecutionStatsCSVPath(const utbot::ProjectContext &projectContext) {
422+
return projectContext.resultsDirPath / "execution-stats.csv";
423+
}
424+
//endregion
425+
414426
bool isHeadersEqual(const fs::path &srcPath, const fs::path &headerPath);
415427
}
416428

0 commit comments

Comments
 (0)