Skip to content

Commit

Permalink
style tweak
Browse files Browse the repository at this point in the history
  • Loading branch information
ken-matsui committed Jan 29, 2025
1 parent ccfe66d commit 5cdd746
Showing 1 changed file with 157 additions and 156 deletions.
313 changes: 157 additions & 156 deletions src/Cmd/Test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,6 @@

namespace cabin {

static Result<void> testMain(CliArgsView cliArgs);

const Subcmd TEST_CMD = //
Subcmd{ "test" }
.setShort("t")
.setDesc("Run the tests of a local package")
.addOpt(OPT_DEBUG)
.addOpt(OPT_RELEASE)
.addOpt(OPT_JOBS)
.setMainFn(testMain);

class Test {
struct TestArgs {
bool isDebug = true;
Expand All @@ -47,177 +36,189 @@ class Test {
Test(TestArgs args, Manifest manifest)
: args(args), manifest(std::move(manifest)) {}

static Result<TestArgs> parseArgs(const CliArgsView cliArgs) {
TestArgs args;

for (auto itr = cliArgs.begin(); itr != cliArgs.end(); ++itr) {
const std::string_view arg = *itr;

const auto control =
Try(Cli::handleGlobalOpts(itr, cliArgs.end(), "test"));
if (control == Cli::Return) {
return Ok(args);
} else if (control == Cli::Continue) {
continue;
} else if (arg == "-d" || arg == "--debug") {
args.isDebug = true;
} else if (arg == "-r" || arg == "--release") {
args.isDebug = false;
} else if (arg == "-j" || arg == "--jobs") {
if (itr + 1 == cliArgs.end()) {
return Subcmd::missingOptArgumentFor(arg);
}
const std::string_view nextArg = *++itr;

uint64_t numThreads{};
auto [ptr, ec] = std::from_chars(
nextArg.data(), nextArg.data() + nextArg.size(), numThreads
);
if (ec == std::errc()) {
setParallelism(numThreads);
} else {
Bail("invalid number of threads: {}", nextArg);
}
} else {
return TEST_CMD.noSuchArg(arg);
}
}
static Result<TestArgs> parseArgs(CliArgsView cliArgs);
Result<void> compileTestTargets();
Result<void> runTestTargets();

return Ok(args);
}
public:
static Result<void> exec(CliArgsView cliArgs);
};

Result<void> compileTestTargets() {
const auto start = std::chrono::steady_clock::now();
const Subcmd TEST_CMD = //
Subcmd{ "test" }
.setShort("t")
.setDesc("Run the tests of a local package")
.addOpt(OPT_DEBUG)
.addOpt(OPT_RELEASE)
.addOpt(OPT_JOBS)
.setMainFn(Test::exec);

const BuildConfig config =
Try(emitMakefile(manifest, args.isDebug, /*includeDevDeps=*/true));
Result<Test::TestArgs>
Test::parseArgs(const CliArgsView cliArgs) {
TestArgs args;

// Collect test targets from the generated Makefile.
unittestTargetPrefix = (config.outBasePath / "unittests").string() + '/';
std::ifstream infile(config.outBasePath / "Makefile");
std::string line;
while (std::getline(infile, line)) {
if (!line.starts_with(unittestTargetPrefix)) {
continue;
for (auto itr = cliArgs.begin(); itr != cliArgs.end(); ++itr) {
const std::string_view arg = *itr;

const auto control = Try(Cli::handleGlobalOpts(itr, cliArgs.end(), "test"));
if (control == Cli::Return) {
return Ok(args);
} else if (control == Cli::Continue) {
continue;
} else if (arg == "-d" || arg == "--debug") {
args.isDebug = true;
} else if (arg == "-r" || arg == "--release") {
args.isDebug = false;
} else if (arg == "-j" || arg == "--jobs") {
if (itr + 1 == cliArgs.end()) {
return Subcmd::missingOptArgumentFor(arg);
}
line = line.substr(0, line.find(':'));
if (!line.ends_with(".test")) {
continue;
const std::string_view nextArg = *++itr;

uint64_t numThreads{};
auto [ptr, ec] = std::from_chars(
nextArg.data(), nextArg.data() + nextArg.size(), numThreads
);
if (ec == std::errc()) {
setParallelism(numThreads);
} else {
Bail("invalid number of threads: {}", nextArg);
}
unittestTargets.push_back(line);
} else {
return TEST_CMD.noSuchArg(arg);
}
}

if (unittestTargets.empty()) {
logger::warn("No test targets found");
return Ok();
}
return Ok(args);
}

const Command baseMakeCmd =
getMakeCommand().addArg("-C").addArg(config.outBasePath.string());

// Find not up-to-date test targets, emit compilation status once, and
// compile them.
ExitStatus exitStatus;
bool alreadyEmitted = false;
for (const std::string& target : unittestTargets) {
Command checkUpToDateCmd = baseMakeCmd;
checkUpToDateCmd.addArg("--question").addArg(target);
if (!Try(execCmd(checkUpToDateCmd)).success()) {
// This test target is not up-to-date.
if (!alreadyEmitted) {
logger::info(
"Compiling", "{} v{} ({})", manifest.package.name,
manifest.package.version.toString(),
manifest.path.parent_path().string()
);
alreadyEmitted = true;
}

Command testCmd = baseMakeCmd;
testCmd.addArg(target);
const ExitStatus curExitStatus = Try(execCmd(testCmd));
if (!curExitStatus.success()) {
exitStatus = curExitStatus;
}
}
}
// If the compilation failed, don't proceed to run tests.
Ensure(exitStatus.success(), "compilation failed");
Result<void>
Test::compileTestTargets() {
const auto start = std::chrono::steady_clock::now();

const auto end = std::chrono::steady_clock::now();
const std::chrono::duration<double> elapsed = end - start;
const BuildConfig config =
Try(emitMakefile(manifest, args.isDebug, /*includeDevDeps=*/true));

const Profile& profile = args.isDebug ? manifest.profiles.at("dev")
: manifest.profiles.at("release");
logger::info(
"Finished", "`{}` profile [{}] target(s) in {:.2f}s",
modeToProfile(args.isDebug), profile.toString(), elapsed.count()
);
// Collect test targets from the generated Makefile.
unittestTargetPrefix = (config.outBasePath / "unittests").string() + '/';
std::ifstream infile(config.outBasePath / "Makefile");
std::string line;
while (std::getline(infile, line)) {
if (!line.starts_with(unittestTargetPrefix)) {
continue;
}
line = line.substr(0, line.find(':'));
if (!line.ends_with(".test")) {
continue;
}
unittestTargets.push_back(line);
}

if (unittestTargets.empty()) {
logger::warn("No test targets found");
return Ok();
}

Result<void> runTestTargets() {
const auto start = std::chrono::steady_clock::now();

std::size_t numPassed = 0;
std::size_t numFailed = 0;
ExitStatus exitStatus;
for (const std::string& target : unittestTargets) {
// `target` always starts with "unittests/" and ends with ".test".
// We need to replace "unittests/" with "src/" and remove ".test" to get
// the source file path.
std::string sourcePath = target;
sourcePath.replace(0, unittestTargetPrefix.size(), "src/");
sourcePath.resize(sourcePath.size() - ".test"sv.size());

const std::string testBinPath =
fs::relative(target, manifest.path.parent_path()).string();
logger::info("Running", "unittests {} ({})", sourcePath, testBinPath);

const ExitStatus curExitStatus = Try(execCmd(Command(target)));
if (curExitStatus.success()) {
++numPassed;
} else {
++numFailed;
const Command baseMakeCmd =
getMakeCommand().addArg("-C").addArg(config.outBasePath.string());

// Find not up-to-date test targets, emit compilation status once, and
// compile them.
ExitStatus exitStatus;
bool alreadyEmitted = false;
for (const std::string& target : unittestTargets) {
Command checkUpToDateCmd = baseMakeCmd;
checkUpToDateCmd.addArg("--question").addArg(target);
if (!Try(execCmd(checkUpToDateCmd)).success()) {
// This test target is not up-to-date.
if (!alreadyEmitted) {
logger::info(
"Compiling", "{} v{} ({})", manifest.package.name,
manifest.package.version.toString(),
manifest.path.parent_path().string()
);
alreadyEmitted = true;
}

Command testCmd = baseMakeCmd;
testCmd.addArg(target);
const ExitStatus curExitStatus = Try(execCmd(testCmd));
if (!curExitStatus.success()) {
exitStatus = curExitStatus;
}
}
}
// If the compilation failed, don't proceed to run tests.
Ensure(exitStatus.success(), "compilation failed");

const auto end = std::chrono::steady_clock::now();
const std::chrono::duration<double> elapsed = end - start;
const auto end = std::chrono::steady_clock::now();
const std::chrono::duration<double> elapsed = end - start;

// TODO: collect stdout/err's of failed tests and print them here.
const std::string summary = fmt::format(
"{} passed; {} failed; finished in {:.2f}s", numPassed, numFailed,
elapsed.count()
);
if (!exitStatus.success()) {
return Err(anyhow::anyhow(summary));
const Profile& profile = args.isDebug ? manifest.profiles.at("dev")
: manifest.profiles.at("release");
logger::info(
"Finished", "`{}` profile [{}] target(s) in {:.2f}s",
modeToProfile(args.isDebug), profile.toString(), elapsed.count()
);

return Ok();
}

Result<void>
Test::runTestTargets() {
const auto start = std::chrono::steady_clock::now();

std::size_t numPassed = 0;
std::size_t numFailed = 0;
ExitStatus exitStatus;
for (const std::string& target : unittestTargets) {
// `target` always starts with "unittests/" and ends with ".test".
// We need to replace "unittests/" with "src/" and remove ".test" to get
// the source file path.
std::string sourcePath = target;
sourcePath.replace(0, unittestTargetPrefix.size(), "src/");
sourcePath.resize(sourcePath.size() - ".test"sv.size());

const std::string testBinPath =
fs::relative(target, manifest.path.parent_path()).string();
logger::info("Running", "unittests {} ({})", sourcePath, testBinPath);

const ExitStatus curExitStatus = Try(execCmd(Command(target)));
if (curExitStatus.success()) {
++numPassed;
} else {
++numFailed;
exitStatus = curExitStatus;
}
logger::info("Ok", "{}", summary);
return Ok();
}

public:
static Result<void> exec(const CliArgsView cliArgs) {
const TestArgs args = Try(parseArgs(cliArgs));
Manifest manifest = Try(Manifest::tryParse());
Test cmd(args, std::move(manifest));

Try(cmd.compileTestTargets());
if (cmd.unittestTargets.empty()) {
return Ok();
}
const auto end = std::chrono::steady_clock::now();
const std::chrono::duration<double> elapsed = end - start;

return cmd.runTestTargets();
// TODO: collect stdout/err's of failed tests and print them here.
const std::string summary = fmt::format(
"{} passed; {} failed; finished in {:.2f}s", numPassed, numFailed,
elapsed.count()
);
if (!exitStatus.success()) {
return Err(anyhow::anyhow(summary));
}
logger::info("Ok", "{}", summary);
return Ok();
}

Result<void>
Test::exec(const CliArgsView cliArgs) {
const TestArgs args = Try(parseArgs(cliArgs));
Manifest manifest = Try(Manifest::tryParse());
Test cmd(args, std::move(manifest));

Try(cmd.compileTestTargets());
if (cmd.unittestTargets.empty()) {
return Ok();
}
};

static Result<void>
testMain(const CliArgsView cliArgs) {
// FIXME: eventually, use command pattern intead of testMain.
return Test::exec(cliArgs);
return cmd.runTestTargets();
}

} // namespace cabin

0 comments on commit 5cdd746

Please sign in to comment.