Skip to content

Commit b9eb026

Browse files
authored
Add run by function name in CLI (#682)
1 parent e768496 commit b9eb026

9 files changed

+200
-120
lines changed

server/proto/testgen.proto

+1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ message TestFilter {
183183
string testFilePath = 1;
184184
string testName = 2;
185185
string testSuite = 3;
186+
string functionName = 4;
186187
}
187188

188189
message CoverageAndResultsRequest {

server/src/commands/Commands.cpp

+25-2
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ Commands::RunTestsCommands::RunTestsCommands(Commands::MainCommands &commands) {
443443
runCommand = commands.getRunTestsCommand();
444444

445445
runTestCommand = runCommand->add_subcommand("test", "Run specified test");
446+
runFunctionCommand = runCommand->add_subcommand("function", "Run specified function tests");
446447
runFileCommand = runCommand->add_subcommand("file", "Run all tests in specified file");
447448
runProjectCommand = runCommand->add_subcommand("project", "Run all tests for this project");
448449
}
@@ -459,10 +460,18 @@ CLI::App *Commands::RunTestsCommands::getRunTestCommand() {
459460
return runTestCommand;
460461
}
461462

463+
CLI::App *Commands::RunTestsCommands::getRunFunctionCommand() {
464+
return runFunctionCommand;
465+
}
466+
462467
bool Commands::RunTestsCommands::gotRunTestCommand() {
463468
return runCommand->got_subcommand(runTestCommand);
464469
}
465470

471+
bool Commands::RunTestsCommands::gotRunFunctionCommand() {
472+
return runCommand->got_subcommand(runFunctionCommand);
473+
}
474+
466475
bool Commands::RunTestsCommands::gotRunFileCommand() {
467476
return runCommand->got_subcommand(runFileCommand);
468477
}
@@ -477,13 +486,23 @@ Commands::RunTestsCommandOptions::RunTestsCommandOptions(Commands::RunTestsComma
477486
commands.getRunTestCommand()
478487
->add_option("--file-path", filePath, "Path to test file")
479488
->required();
489+
commands.getRunTestCommand()->add_flag("--no-coverage", noCoverage,
490+
"Flag that controls coverage generation.");
491+
492+
493+
commands.getRunFunctionCommand()
494+
->add_option("--file-path", filePath, "Path to test file")
495+
->required();
496+
commands.getRunFunctionCommand()->add_option("--function-name", functionName, "Test name")->required();
497+
commands.getRunFunctionCommand()->add_flag("--no-coverage", noCoverage,
498+
"Flag that controls coverage generation.");
499+
480500
commands.getRunFileCommand()
481501
->add_option("--file-path", filePath, "Path to test file")
482502
->required();
483-
commands.getRunTestCommand()->add_flag("--no-coverage", noCoverage,
484-
"Flag that controls coverage generation.");
485503
commands.getRunFileCommand()->add_flag("--no-coverage", noCoverage,
486504
"Flag that controls coverage generation.");
505+
487506
commands.getRunProjectCommand()->add_flag("--no-coverage", noCoverage,
488507
"Flag that controls coverage generation.");
489508
}
@@ -500,6 +519,10 @@ std::string Commands::RunTestsCommandOptions::getTestName() {
500519
return testName;
501520
}
502521

522+
std::string Commands::RunTestsCommandOptions::getFunctionName() {
523+
return functionName;
524+
}
525+
503526
bool Commands::RunTestsCommandOptions::withCoverage() const {
504527
return !noCoverage;
505528
}

server/src/commands/Commands.h

+8
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,16 @@ namespace Commands {
177177

178178
CLI::App *getRunTestCommand();
179179

180+
CLI::App *getRunFunctionCommand();
181+
180182
CLI::App *getRunFileCommand();
181183

182184
CLI::App *getRunProjectCommand();
183185

184186
bool gotRunTestCommand();
185187

188+
bool gotRunFunctionCommand();
189+
186190
bool gotRunFileCommand();
187191

188192
bool gotRunProjectCommand();
@@ -191,6 +195,7 @@ namespace Commands {
191195
CLI::App *runCommand;
192196

193197
CLI::App *runTestCommand;
198+
CLI::App *runFunctionCommand;
194199
CLI::App *runFileCommand;
195200
CLI::App *runProjectCommand;
196201
};
@@ -204,12 +209,15 @@ namespace Commands {
204209

205210
std::string getTestName();
206211

212+
std::string getFunctionName();
213+
207214
[[nodiscard]] bool withCoverage() const;
208215

209216
private:
210217
fs::path filePath;
211218
std::string testSuite;
212219
std::string testName;
220+
std::string functionName;
213221

214222
bool noCoverage = false;
215223
};

server/src/coverage/CoverageAndResultsGenerator.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ CoverageAndResultsGenerator::CoverageAndResultsGenerator(
1818
coverageAndResultsRequest->testfilter().testfilepath(),
1919
coverageAndResultsRequest->testfilter().testsuite(),
2020
coverageAndResultsRequest->testfilter().testname(),
21+
coverageAndResultsRequest->testfilter().functionname(),
2122
coverageAndResultsWriter),
2223
coverageAndResultsWriter(coverageAndResultsWriter) {
2324
}

server/src/coverage/TestRunner.cpp

+93-71
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <utils/stats/TestsExecutionStats.h>
2+
#include <utils/KleeUtils.h>
23
#include "TestRunner.h"
34

45
#include "printers/DefaultMakefilePrinter.h"
@@ -18,33 +19,37 @@ TestRunner::TestRunner(utbot::ProjectContext projectContext,
1819
std::string testFilePath,
1920
std::string testSuite,
2021
std::string testName,
22+
std::string functionName,
2123
ProgressWriter const *progressWriter)
22-
: projectContext(std::move(projectContext)),
23-
testFilePath(testFilePath.empty() ? std::nullopt : std::make_optional(testFilePath)),
24-
testSuite(std::move(testSuite)), testName(std::move(testName)),
25-
progressWriter(progressWriter) {
24+
: projectContext(std::move(projectContext)),
25+
testFilePath(testFilePath.empty() ? std::nullopt : std::make_optional(testFilePath)),
26+
testSuite(std::move(testSuite)), testName(std::move(testName)), functionName(std::move(functionName)),
27+
progressWriter(progressWriter) {
2628
}
2729

2830
TestRunner::TestRunner(
29-
const testsgen::CoverageAndResultsRequest *coverageAndResultsRequest,
30-
grpc::ServerWriter<testsgen::CoverageAndResultsResponse> *coverageAndResultsWriter,
31-
std::string testFilename,
32-
std::string testSuite,
33-
std::string testName)
34-
: TestRunner(utbot::ProjectContext(coverageAndResultsRequest->projectcontext()),
35-
std::move(testFilename),
36-
std::move(testSuite),
37-
std::move(testName),
38-
&writer) {
31+
const testsgen::CoverageAndResultsRequest *coverageAndResultsRequest,
32+
grpc::ServerWriter<testsgen::CoverageAndResultsResponse> *coverageAndResultsWriter,
33+
std::string testFilename,
34+
std::string testSuite,
35+
std::string testName,
36+
std::string functionName)
37+
: TestRunner(utbot::ProjectContext(coverageAndResultsRequest->projectcontext()),
38+
std::move(testFilename),
39+
std::move(testSuite),
40+
std::move(testName),
41+
std::move(functionName),
42+
&writer) {
3943
writer = ServerCoverageAndResultsWriter(coverageAndResultsWriter);
4044
}
4145

4246
std::vector<UnitTest> TestRunner::getTestsFromMakefile(const fs::path &makefile,
43-
const fs::path &testFilePath) {
47+
const fs::path &testFilePath,
48+
const std::string &filter) {
4449
auto cmdGetAllTests = MakefileUtils::MakefileCommand(projectContext, makefile,
4550
printer::DefaultMakefilePrinter::TARGET_RUN,
46-
"--gtest_list_tests", {"GTEST_FILTER=*"});
47-
auto[out, status, _] = cmdGetAllTests.run(projectContext.getBuildDirAbsPath(), false);
51+
"--gtest_list_tests", {"GTEST_FILTER=" + filter});
52+
auto [out, status, _] = cmdGetAllTests.run(projectContext.getBuildDirAbsPath(), false);
4853
if (status != 0) {
4954
auto [err, _, logFilePath] = cmdGetAllTests.run(projectContext.getBuildDirAbsPath(), true);
5055
progressWriter->writeProgress(StringUtils::stringFormat("command %s failed.\n"
@@ -55,12 +60,14 @@ std::vector<UnitTest> TestRunner::getTestsFromMakefile(const fs::path &makefile,
5560
throw ExecutionProcessException(err, logFilePath.value());
5661
}
5762
if (out.empty()) {
58-
LOG_S(WARNING) << "Running gtest with flag --gtest_list_tests returns empty output. Does file contain main function?";
63+
LOG_S(WARNING)
64+
<< "Running gtest with flag --gtest_list_tests returns empty output. Does file contain main function?";
5965
return {};
6066
}
6167
std::vector<std::string> gtestListTestsOutput = StringUtils::split(out, '\n');
62-
gtestListTestsOutput.erase(gtestListTestsOutput.begin()); //GTEST prints "Running main() from /opt/gtest/googletest/src/gtest_main.cc"
63-
for (std::string &s : gtestListTestsOutput) {
68+
gtestListTestsOutput.erase(
69+
gtestListTestsOutput.begin()); //GTEST prints "Running main() from /opt/gtest/googletest/src/gtest_main.cc"
70+
for (std::string &s: gtestListTestsOutput) {
6471
StringUtils::trim(s);
6572
}
6673
std::string testSuite;
@@ -84,50 +91,65 @@ std::vector<UnitTest> TestRunner::getTestsToLaunch() {
8491
if (fs::exists(projectContext.getTestDirAbsPath())) {
8592
FileSystemUtils::RecursiveDirectoryIterator directoryIterator(projectContext.getTestDirAbsPath());
8693
ExecUtils::doWorkWithProgress(
87-
directoryIterator, progressWriter, "Building tests",
88-
[this, &result](fs::directory_entry const &directoryEntry) {
89-
if (!directoryEntry.is_regular_file()) {
90-
return;
91-
}
92-
const auto &testFilePath = directoryEntry.path();
93-
if (testFilePath.extension() == Paths::CXX_EXTENSION &&
94-
StringUtils::endsWith(testFilePath.stem().c_str(), Paths::TEST_SUFFIX)) {
95-
fs::path sourcePath = Paths::testPathToSourcePath(projectContext, testFilePath);
96-
fs::path makefile =
97-
Paths::getMakefilePathFromSourceFilePath(projectContext, sourcePath);
98-
if (fs::exists(makefile)) {
99-
try {
100-
auto tests = getTestsFromMakefile(makefile, testFilePath);
101-
CollectionUtils::extend(result, tests);
102-
} catch (ExecutionProcessException const &e) {
103-
exceptions.push_back(e);
94+
directoryIterator, progressWriter, "Building tests",
95+
[this, &result](fs::directory_entry const &directoryEntry) {
96+
if (!directoryEntry.is_regular_file()) {
97+
return;
98+
}
99+
const auto &testFilePath = directoryEntry.path();
100+
if (testFilePath.extension() == Paths::CXX_EXTENSION &&
101+
StringUtils::endsWith(testFilePath.stem().c_str(), Paths::TEST_SUFFIX)) {
102+
fs::path sourcePath = Paths::testPathToSourcePath(projectContext, testFilePath);
103+
fs::path makefile =
104+
Paths::getMakefilePathFromSourceFilePath(projectContext, sourcePath);
105+
if (fs::exists(makefile)) {
106+
try {
107+
auto tests = getTestsFromMakefile(makefile, testFilePath);
108+
CollectionUtils::extend(result, tests);
109+
} catch (ExecutionProcessException const &e) {
110+
exceptions.push_back(e);
111+
}
112+
} else {
113+
LOG_S(WARNING) << StringUtils::stringFormat(
114+
"Makefile for %s not found, candidate: %s", testFilePath, makefile);
104115
}
105116
} else {
106-
LOG_S(WARNING) << StringUtils::stringFormat(
107-
"Makefile for %s not found, candidate: %s", testFilePath, makefile);
108-
}
109-
} else {
110-
if (!StringUtils::endsWith(testFilePath.stem().c_str(), Paths::TEST_SUFFIX) &&
111-
!StringUtils::endsWith(testFilePath.stem().c_str(), Paths::STUB_SUFFIX) &&
112-
!StringUtils::endsWith(testFilePath.stem().c_str(), Paths::MAKE_WRAPPER_SUFFIX) &&
113-
!StringUtils::endsWith(testFilePath.c_str(), Paths::MAKEFILE_EXTENSION)) {
114-
LOG_S(WARNING) << "Found extra file in test directory: " << testFilePath;
117+
if (!StringUtils::endsWith(testFilePath.stem().c_str(), Paths::TEST_SUFFIX) &&
118+
!StringUtils::endsWith(testFilePath.stem().c_str(), Paths::STUB_SUFFIX) &&
119+
!StringUtils::endsWith(testFilePath.stem().c_str(), Paths::MAKE_WRAPPER_SUFFIX) &&
120+
!StringUtils::endsWith(testFilePath.c_str(), Paths::MAKEFILE_EXTENSION)) {
121+
LOG_S(WARNING) << "Found extra file in test directory: " << testFilePath;
122+
}
115123
}
116-
}
117-
});
124+
});
118125
} else {
119126
LOG_S(WARNING) << "Test folder doesn't exist: " << projectContext.getTestDirAbsPath();
120127
}
121128
return result;
122129
}
123-
if (testName.empty()) {
130+
131+
if (testName.empty() && functionName.empty()) {
124132
//for file
125133
fs::path sourcePath = Paths::testPathToSourcePath(projectContext, testFilePath.value());
126134
fs::path makefile = Paths::getMakefilePathFromSourceFilePath(projectContext, sourcePath);
127135
return getTestsFromMakefile(makefile, testFilePath.value());
128136
}
137+
138+
if (testName.empty()) {
139+
//for function
140+
fs::path sourcePath = Paths::testPathToSourcePath(projectContext, testFilePath.value());
141+
fs::path makefile = Paths::getMakefilePathFromSourceFilePath(projectContext, sourcePath);
142+
143+
144+
std::string renamedMethodDescription = KleeUtils::getRenamedOperator(functionName);
145+
StringUtils::replaceColon(renamedMethodDescription);
146+
147+
std::string filter = "*." + renamedMethodDescription + Paths::TEST_SUFFIX + "*";
148+
149+
return getTestsFromMakefile(makefile, testFilePath.value(), filter);
150+
}
129151
//for single test
130-
return { UnitTest{ testFilePath.value(), testSuite, testName } };
152+
return {UnitTest{testFilePath.value(), testSuite, testName}};
131153
}
132154

133155
grpc::Status TestRunner::runTests(bool withCoverage, const std::optional<std::chrono::seconds> &testTimeout) {
@@ -136,22 +158,22 @@ grpc::Status TestRunner::runTests(bool withCoverage, const std::optional<std::ch
136158

137159
const auto buildRunCommands = coverageTool->getBuildRunCommands(testsToLaunch, withCoverage);
138160
ExecUtils::doWorkWithProgress(buildRunCommands, progressWriter, "Running tests",
139-
[this, testTimeout] (BuildRunCommand const &buildRunCommand) {
140-
auto const &[unitTest, buildCommand, runCommand] =
141-
buildRunCommand;
142-
try {
143-
auto status = runTest(buildRunCommand, testTimeout);
144-
testResultMap[unitTest.testFilePath][unitTest.testname] = status;
145-
ExecUtils::throwIfCancelled();
146-
} catch (ExecutionProcessException const &e) {
147-
testsgen::TestResultObject testRes;
148-
testRes.set_testfilepath(unitTest.testFilePath);
149-
testRes.set_testname(unitTest.testname);
150-
testRes.set_status(testsgen::TEST_FAILED);
151-
testResultMap[unitTest.testFilePath][unitTest.testname] = testRes;
152-
exceptions.emplace_back(e);
153-
}
154-
});
161+
[this, testTimeout](BuildRunCommand const &buildRunCommand) {
162+
auto const &[unitTest, buildCommand, runCommand] =
163+
buildRunCommand;
164+
try {
165+
auto status = runTest(buildRunCommand, testTimeout);
166+
testResultMap[unitTest.testFilePath][unitTest.testname] = status;
167+
ExecUtils::throwIfCancelled();
168+
} catch (ExecutionProcessException const &e) {
169+
testsgen::TestResultObject testRes;
170+
testRes.set_testfilepath(unitTest.testFilePath);
171+
testRes.set_testname(unitTest.testname);
172+
testRes.set_status(testsgen::TEST_FAILED);
173+
testResultMap[unitTest.testFilePath][unitTest.testname] = testRes;
174+
exceptions.emplace_back(e);
175+
}
176+
});
155177
LOG_S(DEBUG) << "All run commands were executed";
156178
return Status::OK;
157179
}
@@ -169,14 +191,14 @@ void TestRunner::init(bool withCoverage) {
169191
}
170192
}
171193

172-
bool TestRunner::buildTest(const utbot::ProjectContext& projectContext, const fs::path& sourcePath) {
194+
bool TestRunner::buildTest(const utbot::ProjectContext &projectContext, const fs::path &sourcePath) {
173195
ExecUtils::throwIfCancelled();
174196
fs::path makefile = Paths::getMakefilePathFromSourceFilePath(projectContext, sourcePath);
175197
if (fs::exists(makefile)) {
176198
auto command = MakefileUtils::MakefileCommand(projectContext, makefile,
177199
printer::DefaultMakefilePrinter::TARGET_BUILD, "", {});
178200
LOG_S(DEBUG) << "Try compile tests for: " << sourcePath.string();
179-
auto[out, status, logFilePath] = command.run(projectContext.getBuildDirAbsPath(), true);
201+
auto [out, status, logFilePath] = command.run(projectContext.getBuildDirAbsPath(), true);
180202
if (status != 0) {
181203
return false;
182204
}
@@ -185,18 +207,18 @@ bool TestRunner::buildTest(const utbot::ProjectContext& projectContext, const fs
185207
return false;
186208
}
187209

188-
size_t TestRunner::buildTests(const utbot::ProjectContext& projectContext, const tests::TestsMap& tests) {
210+
size_t TestRunner::buildTests(const utbot::ProjectContext &projectContext, const tests::TestsMap &tests) {
189211
size_t fail_count = 0;
190212
for (const auto &[file, _]: tests) {
191-
if(!TestRunner::buildTest(projectContext, file)) {
213+
if (!TestRunner::buildTest(projectContext, file)) {
192214
fail_count++;
193215
}
194216
}
195217
return fail_count;
196218
}
197219

198220
testsgen::TestResultObject TestRunner::runTest(const BuildRunCommand &command,
199-
const std::optional <std::chrono::seconds> &testTimeout) {
221+
const std::optional<std::chrono::seconds> &testTimeout) {
200222
fs::remove(Paths::getGTestResultsJsonPath(projectContext));
201223
auto res = command.runCommand.run(projectContext.getBuildDirAbsPath(), true, true, testTimeout);
202224
GTestLogger::log(res.output);

0 commit comments

Comments
 (0)