Add project files
This commit is contained in:
commit
f2fe6b8855
114 files changed
+20525
No files matched your search
@@ -0,0 +1,500 @@
|
||||
// A small harness and the tests of breeze_core's logic: text, versions, CMake editing, projects and
|
||||
// their settings, modules, argument handling and process running. A test is a function that checks;
|
||||
// the program exits with the number of failed checks (0 when all hold).
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "breeze/Arguments.hpp"
|
||||
#include "breeze/CMakeText.hpp"
|
||||
#include "breeze/Error.hpp"
|
||||
#include "breeze/Fs.hpp"
|
||||
#include "breeze/Modules.hpp"
|
||||
#include "breeze/Process.hpp"
|
||||
#include "breeze/Project.hpp"
|
||||
#include "breeze/Text.hpp"
|
||||
#include "breeze/Version.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int g_failures = 0;
|
||||
|
||||
void check(bool condition, std::source_location where = std::source_location::current())
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
++g_failures;
|
||||
std::cerr << where.file_name() << ":" << where.line() << ": check failed\n";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename A, typename E>
|
||||
void checkEq(const A& actual, const E& expected,
|
||||
std::source_location where = std::source_location::current())
|
||||
{
|
||||
if (!(actual == expected))
|
||||
{
|
||||
++g_failures;
|
||||
std::cerr << where.file_name() << ":" << where.line() << ": was '" << actual
|
||||
<< "', expected '" << expected << "'\n";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F> bool throwsError(F&& f)
|
||||
{
|
||||
try
|
||||
{
|
||||
f();
|
||||
}
|
||||
catch (const breeze::Error&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace sfs = std::filesystem;
|
||||
|
||||
struct TempDir
|
||||
{
|
||||
TempDir() = default;
|
||||
TempDir(const TempDir&) = delete;
|
||||
TempDir& operator=(const TempDir&) = delete;
|
||||
TempDir(TempDir&&) = delete;
|
||||
TempDir& operator=(TempDir&&) = delete;
|
||||
sfs::path path = breeze::fs::makeTempDir("breeze-unit");
|
||||
~TempDir()
|
||||
{
|
||||
std::error_code ec;
|
||||
sfs::remove_all(path, ec);
|
||||
}
|
||||
};
|
||||
|
||||
void write(const sfs::path& file, const std::string& text)
|
||||
{
|
||||
breeze::fs::writeText(file, text);
|
||||
}
|
||||
|
||||
// A project with the breeze/ layout and these settings and modules.
|
||||
sfs::path makeProject(const sfs::path& root)
|
||||
{
|
||||
write(root / "CMakeLists.txt", "cmake_minimum_required(VERSION 3.25)\n");
|
||||
write(root / "breeze" / "cmake" / "Breeze.cmake", "# sdk\n");
|
||||
write(root / "breeze" / "cmake" / "BuildHelpers.cmake",
|
||||
"macro(add_objects_module NAME)\nendmacro()\n");
|
||||
write(root / "breeze" / "VERSION", "1.2.3\n");
|
||||
write(root / "project" / "Configuration.cmake",
|
||||
"# settings\n"
|
||||
"set(PROJECT_TITLE \"Demo\")\n"
|
||||
"set(PROJECT_VERSION_NUMBER \"0.1.0\")\n"
|
||||
"set(PROJECT_CODE_NAME \"demo\" CACHE STRING\n"
|
||||
" \"A machine-friendly name.\")\n");
|
||||
return root;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// text
|
||||
|
||||
void textHelpers()
|
||||
{
|
||||
using namespace breeze::text;
|
||||
checkEq(snakeCase("CoreFarewell"), "core_farewell");
|
||||
checkEq(snakeCase("HelloApp"), "hello_app");
|
||||
checkEq(snakeCase("HTTPServer"), "http_server");
|
||||
checkEq(snakeCase("my-app"), "my_app");
|
||||
checkEq(snakeCase("already_snake"), "already_snake");
|
||||
checkEq(snakeCase("Util"), "util");
|
||||
checkEq(normalized("Clang-CL"), "clangcl");
|
||||
checkEq(normalized("MinGW64"), "mingw64");
|
||||
check(isName("Core"));
|
||||
check(isName("my_mod2"));
|
||||
check(!isName("9lives"));
|
||||
check(!isName("bad name"));
|
||||
check(!isName("with-dash"));
|
||||
check(isName("with-dash", true));
|
||||
check(isTargetId("fmt::fmt"));
|
||||
check(isTargetId("core_lib"));
|
||||
check(!isTargetId("bad id"));
|
||||
checkEq(trim(" x \n"), "x");
|
||||
checkEq(join({"a", "b", "c"}, ", "), "a, b, c");
|
||||
checkEq(replaceAll("a-b-c", "-", "+"), "a+b+c");
|
||||
checkEq(splitLines("one\r\ntwo\nthree\n").size(), std::size_t(3));
|
||||
check(startsWith("breeze", "bre"));
|
||||
check(endsWith("breeze", "eze"));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// versions
|
||||
|
||||
void versions()
|
||||
{
|
||||
using namespace breeze;
|
||||
checkEq(parseVersion("1.2.3")->toString(), "1.2.3");
|
||||
checkEq(parseVersion("v1.2.3")->toString(), "1.2.3");
|
||||
checkEq(parseVersion("1.2")->toString(), "1.2.0");
|
||||
checkEq(parseVersion("2")->toString(), "2.0.0");
|
||||
checkEq(parseVersion("1.0.0-rc1")->toString(), "1.0.0-rc1");
|
||||
checkEq(parseVersion("1.0.0+build5")->toString(), "1.0.0");
|
||||
check(!parseVersion("a.b.c"));
|
||||
check(!parseVersion("1.2.3.4"));
|
||||
check(!parseVersion(""));
|
||||
check(!parseVersion("notaversion"));
|
||||
check(*parseVersion("1.0.0-rc1") < *parseVersion("1.0.0"));
|
||||
check(*parseVersion("1.9.0") < *parseVersion("1.10.0"));
|
||||
check(*parseVersion("1.2.3") == *parseVersion("v1.2.3"));
|
||||
std::vector<Version> all{*parseVersion("1.0.0"), *parseVersion("1.2.0"),
|
||||
*parseVersion("2.0.0"), *parseVersion("1.1.5")};
|
||||
checkEq(greatest(all)->toString(), "2.0.0");
|
||||
checkEq(greatest(all, 1)->toString(), "1.2.0");
|
||||
check(!greatest(all, 3));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------- cmake
|
||||
// text
|
||||
|
||||
void cmakeText()
|
||||
{
|
||||
using namespace breeze::cmake;
|
||||
std::string text = "# add_exe_module(in_a_comment)\n"
|
||||
"message(\"add_exe_module(in_a_string)\")\n"
|
||||
"#[[ add_exe_module(in_a_bracket_comment) ]]\n"
|
||||
"add_exe_module(real_one OUTPUT_NAME \"real\"\n"
|
||||
" DEPENDS a # trailing comment\n"
|
||||
" b::c)\n"
|
||||
"set(NAME \"old value\" CACHE STRING\n"
|
||||
" \"doc\")\n";
|
||||
auto wanted = [](const std::string& n) { return n == "add_exe_module"; };
|
||||
std::vector<Call> calls = findCalls(text, wanted);
|
||||
checkEq(calls.size(), std::size_t(1));
|
||||
if (calls.size() == 1)
|
||||
{
|
||||
checkEq(calls[0].args.size(), std::size_t(6));
|
||||
checkEq(calls[0].args[0].text, "real_one");
|
||||
checkEq(calls[0].args[2].text, "real");
|
||||
check(calls[0].args[2].quoted);
|
||||
checkEq(calls[0].args[5].text, "b::c");
|
||||
}
|
||||
|
||||
std::optional<Call> set = findSet(text, "NAME");
|
||||
check(set.has_value());
|
||||
if (set)
|
||||
{
|
||||
checkEq(set->args[1].text, "old value");
|
||||
std::string edited = text;
|
||||
replaceToken(edited, set->args[1], quote("new \"value\""));
|
||||
check(edited.find("set(NAME \"new \\\"value\\\"\" CACHE STRING\n \"doc\")") !=
|
||||
std::string::npos);
|
||||
check(edited.find("# add_exe_module(in_a_comment)") !=
|
||||
std::string::npos); // everything else untouched
|
||||
}
|
||||
check(!findSet(text, "MISSING"));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------ projects
|
||||
|
||||
void projectDetection()
|
||||
{
|
||||
using namespace breeze;
|
||||
TempDir t;
|
||||
makeProject(t.path);
|
||||
std::optional<Sdk> sdk = detectSdk(t.path);
|
||||
check(sdk.has_value());
|
||||
if (sdk)
|
||||
{
|
||||
check(sdk->layout == SdkLayout::Breeze);
|
||||
checkEq(sdk->version->toString(), "1.2.3");
|
||||
check(sdk->supportsObjects);
|
||||
checkEq(sdk->describe(), "1.2.3");
|
||||
}
|
||||
write(t.path / "source" / "Mod" / "x.cpp", "");
|
||||
std::optional<Project> found = findProject(t.path / "source" / "Mod");
|
||||
check(found.has_value());
|
||||
if (found)
|
||||
check(found->root == sfs::weakly_canonical(t.path));
|
||||
TempDir empty;
|
||||
check(!detectSdk(empty.path));
|
||||
check(throwsError([&] { requireProject(empty.path); }));
|
||||
|
||||
// the layout before breeze/: scripts/, cmake/ and Configuration.cmake at the root
|
||||
TempDir legacy;
|
||||
write(legacy.path / "CMakeLists.txt", "project(Old VERSION 3.4.5)\n");
|
||||
write(legacy.path / "Configuration.cmake", "set(PROJECT_CODE_NAME \"old\")\n");
|
||||
write(legacy.path / "cmake" / "BuildHelpers.cmake", "# no objects\n");
|
||||
std::optional<Sdk> old = detectSdk(legacy.path);
|
||||
check(old.has_value());
|
||||
if (old)
|
||||
{
|
||||
check(old->layout == SdkLayout::Legacy);
|
||||
check(!old->supportsObjects);
|
||||
check(!old->version.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
void settings()
|
||||
{
|
||||
using namespace breeze;
|
||||
TempDir t;
|
||||
makeProject(t.path);
|
||||
Project p = *findProject(t.path);
|
||||
checkEq(readSetting(p, Setting::Name), "Demo");
|
||||
checkEq(readSetting(p, Setting::Version), "0.1.0");
|
||||
checkEq(readSetting(p, Setting::Id), "demo");
|
||||
writeSetting(p, Setting::Name, "Renamed");
|
||||
writeSetting(p, Setting::Version, "2.0.1");
|
||||
writeSetting(p, Setting::Id, "renamed_id");
|
||||
checkEq(readSetting(p, Setting::Name), "Renamed");
|
||||
checkEq(readSetting(p, Setting::Version), "2.0.1");
|
||||
checkEq(readSetting(p, Setting::Id), "renamed_id");
|
||||
std::string file = fs::readText(p.sdk.settingsFile);
|
||||
check(file.find("CACHE STRING\n \"A machine-friendly name.\")") !=
|
||||
std::string::npos); // the rest is kept
|
||||
check(throwsError([&] { writeSetting(p, Setting::Version, "1.x"); }));
|
||||
check(throwsError([&] { writeSetting(p, Setting::Id, "Upper"); }));
|
||||
check(throwsError([&] { writeSetting(p, Setting::Name, "bad name"); }));
|
||||
|
||||
TempDir legacy;
|
||||
write(legacy.path / "CMakeLists.txt",
|
||||
"cmake_minimum_required(VERSION 3.25)\nproject(Old VERSION 3.4.5 LANGUAGES CXX)\n");
|
||||
write(legacy.path / "Configuration.cmake",
|
||||
"set(PROJECT_CODE_NAME \"old\" CACHE STRING \"x\")\n");
|
||||
write(legacy.path / "cmake" / "BuildHelpers.cmake", "");
|
||||
Project lp = *findProject(legacy.path);
|
||||
checkEq(readSetting(lp, Setting::Name), "Old");
|
||||
checkEq(readSetting(lp, Setting::Version), "3.4.5");
|
||||
checkEq(readSetting(lp, Setting::Id), "old");
|
||||
writeSetting(lp, Setting::Version, "4.0");
|
||||
writeSetting(lp, Setting::Name, "Newer");
|
||||
check(fs::readText(legacy.path / "CMakeLists.txt")
|
||||
.find("project(Newer VERSION 4.0 LANGUAGES CXX)") != std::string::npos);
|
||||
}
|
||||
|
||||
void defaults()
|
||||
{
|
||||
using namespace breeze;
|
||||
TempDir t;
|
||||
makeProject(t.path);
|
||||
Project p = *findProject(t.path);
|
||||
Defaults d = Defaults::load(p);
|
||||
check(!d.build("preset"));
|
||||
d.setBuild("preset", "linux-x64-gcc");
|
||||
d.setRun("hello_exe");
|
||||
d.save(p);
|
||||
Defaults again = Defaults::load(p);
|
||||
checkEq(*again.build("preset"), "linux-x64-gcc");
|
||||
checkEq(*again.run(), "hello_exe");
|
||||
again.clearBuild("preset");
|
||||
again.clearRun();
|
||||
again.save(p);
|
||||
check(!sfs::exists(p.defaultsFile())); // nothing left to keep: the file goes
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------ modules
|
||||
|
||||
void modules()
|
||||
{
|
||||
using namespace breeze;
|
||||
TempDir t;
|
||||
makeProject(t.path);
|
||||
write(t.path / "source" / "Core" / "CMakeLists.txt",
|
||||
"add_lib_module(core_lib TYPE SHARED OUTPUT_NAME \"core\" DEPENDS fmt::fmt "
|
||||
"core_pieces)\n");
|
||||
write(t.path / "source" / "CorePieces" / "CMakeLists.txt",
|
||||
"add_objects_module(core_pieces)\n");
|
||||
write(t.path / "source" / "App" / "CMakeLists.txt",
|
||||
"add_exe_module(app_exe DEPENDS core_lib)\n");
|
||||
write(t.path / "source" / "tests" / "CoreTest" / "CMakeLists.txt",
|
||||
"add_test_module(core_test DEPENDS core_pieces)\n");
|
||||
Project p = *findProject(t.path);
|
||||
|
||||
std::vector<Module> mods = scanModules(p);
|
||||
checkEq(mods.size(), std::size_t(4));
|
||||
const Module* core = findModule(mods, "core_lib");
|
||||
check(core != nullptr);
|
||||
if (core)
|
||||
{
|
||||
check(core->kind == ModuleKind::Lib);
|
||||
check(core->libType == LibType::Shared);
|
||||
checkEq(core->outputName, "core");
|
||||
checkEq(core->deps.size(), std::size_t(2));
|
||||
}
|
||||
check(findModule(mods, "CorePieces") != nullptr); // by directory
|
||||
check(findModule(mods, "corepieces") != nullptr); // by directory, another case
|
||||
check(findModule(mods, "core_pieces")->kind == ModuleKind::Obj);
|
||||
check(findModule(mods, "core_test")->kind == ModuleKind::Test);
|
||||
|
||||
// dependency queries
|
||||
checkEq(dependenciesOf(mods, *findModule(mods, "app_exe"), false).size(), std::size_t(1));
|
||||
std::vector<std::string> all = dependenciesOf(mods, *findModule(mods, "app_exe"), true);
|
||||
checkEq(all.size(), std::size_t(3)); // core_lib, fmt::fmt, core_pieces
|
||||
checkEq(dependentsOf(mods, *findModule(mods, "core_pieces"), false).size(), std::size_t(2));
|
||||
checkEq(dependentsOf(mods, *findModule(mods, "core_pieces"), true).size(), std::size_t(3));
|
||||
|
||||
// adding
|
||||
NewModule lib;
|
||||
lib.kind = ModuleKind::Lib;
|
||||
lib.name = "Strings";
|
||||
lib.libType = LibType::Static;
|
||||
lib.deps = {"core_pieces", "fmt::fmt"};
|
||||
Module made = addModule(p, lib);
|
||||
checkEq(made.id, "strings_lib");
|
||||
check(sfs::exists(t.path / "source" / "Strings" / "include" / "strings" / "Strings.hpp"));
|
||||
check(sfs::exists(t.path / "source" / "Strings" / "src" / "Strings.cpp"));
|
||||
NewModule test;
|
||||
test.kind = ModuleKind::Test;
|
||||
test.name = "StringsTest";
|
||||
checkEq(addModule(p, test).id, "strings_test"); // not strings_test_test
|
||||
check(sfs::exists(t.path / "source" / "tests" / "StringsTest" / "main.cpp"));
|
||||
NewModule noType;
|
||||
noType.kind = ModuleKind::Lib;
|
||||
noType.name = "NoType";
|
||||
check(throwsError([&] { addModule(p, noType); }));
|
||||
NewModule duplicate;
|
||||
duplicate.kind = ModuleKind::Exec;
|
||||
duplicate.name = "Other";
|
||||
duplicate.id = "app_exe";
|
||||
check(throwsError([&] { addModule(p, duplicate); }));
|
||||
NewModule onExe;
|
||||
onExe.kind = ModuleKind::Exec;
|
||||
onExe.name = "OnExe";
|
||||
onExe.deps = {"app_exe"};
|
||||
check(throwsError([&] { addModule(p, onExe); }));
|
||||
NewModule badName;
|
||||
badName.kind = ModuleKind::Exec;
|
||||
badName.name = "9bad";
|
||||
check(throwsError([&] { addModule(p, badName); }));
|
||||
|
||||
// renaming: the id, the directory, and the DEPENDS of whoever names it
|
||||
RenameResult r =
|
||||
renameModule(p, "core_pieces", std::string("Pieces"), std::string("pieces_obj"));
|
||||
checkEq(r.newId, "pieces_obj");
|
||||
check(sfs::exists(t.path / "source" / "Pieces" / "CMakeLists.txt"));
|
||||
check(!sfs::exists(t.path / "source" / "CorePieces"));
|
||||
mods = scanModules(p);
|
||||
check(findModule(mods, "pieces_obj") != nullptr);
|
||||
check(findModule(mods, "core_pieces") == nullptr);
|
||||
checkEq(findModule(mods, "core_lib")->deps[1], "pieces_obj");
|
||||
checkEq(findModule(mods, "core_test")->deps[0], "pieces_obj");
|
||||
checkEq(findModule(mods, "strings_lib")->deps[0], "pieces_obj");
|
||||
check(throwsError([&] { renameModule(p, "app_exe", std::nullopt, std::nullopt); }));
|
||||
check(throwsError([&]
|
||||
{ renameModule(p, "app_exe", std::nullopt, std::string("core_lib")); }));
|
||||
|
||||
// deleting: refused while something depends on it
|
||||
check(throwsError([&] { deleteModule(p, "pieces_obj"); }));
|
||||
deleteModule(p, "app_exe");
|
||||
check(!sfs::exists(t.path / "source" / "App"));
|
||||
check(throwsError([&] { deleteModule(p, "app_exe"); }));
|
||||
}
|
||||
|
||||
void objectsNeedSdkSupport()
|
||||
{
|
||||
using namespace breeze;
|
||||
TempDir t;
|
||||
makeProject(t.path);
|
||||
write(t.path / "breeze" / "cmake" / "BuildHelpers.cmake",
|
||||
"# an SDK from before objects modules\n");
|
||||
Project p = *findProject(t.path);
|
||||
NewModule obj;
|
||||
obj.kind = ModuleKind::Obj;
|
||||
obj.name = "Pieces";
|
||||
check(throwsError([&] { addModule(p, obj); }));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// arguments
|
||||
|
||||
void arguments()
|
||||
{
|
||||
using breeze::cli::normalizeArguments;
|
||||
using V = std::vector<std::string>;
|
||||
check(normalizeArguments({"build", "--cargs", "-DA=1", "-DB=2", "-j", "4"}) ==
|
||||
V({"build", "--cargs=-DA=1", "--cargs=-DB=2", "-j", "4"}));
|
||||
check(normalizeArguments({"run", "hello_exe", "-x", "--verbose", "-q"}) ==
|
||||
V({"run", "hello_exe", "--args=--verbose", "--args=-q"}));
|
||||
check(normalizeArguments({"build", "--cargs=-DA=1"}) ==
|
||||
V({"build", "--cargs=-DA=1"})); // already in the form
|
||||
check(normalizeArguments({"build", "--cargs"}) ==
|
||||
V({"build", "--cargs"})); // nothing: CLIForge reports it
|
||||
check(normalizeArguments({"ls", "--cargs", "-D"}) ==
|
||||
V({"ls", "--cargs", "-D"})); // not a build command
|
||||
check(normalizeArguments({}).empty());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// processes
|
||||
|
||||
void processes()
|
||||
{
|
||||
#ifndef _WIN32
|
||||
using namespace breeze;
|
||||
proc::Options o;
|
||||
o.capture = true;
|
||||
proc::Result r = proc::run({"sh", "-c", "echo out; echo err >&2; exit 3"}, o);
|
||||
checkEq(r.exitCode, 3);
|
||||
checkEq(r.output, "out\n");
|
||||
o.mergeStderr = true;
|
||||
r = proc::run({"sh", "-c", "echo out; echo err >&2"}, o);
|
||||
check(r.output.find("err") != std::string::npos);
|
||||
proc::Options env;
|
||||
env.capture = true;
|
||||
env.env = {{"BREEZE_TEST_VARIABLE", "present"}};
|
||||
checkEq(proc::run({"sh", "-c", "printf %s \"$BREEZE_TEST_VARIABLE\""}, env).output,
|
||||
"present");
|
||||
TempDir t;
|
||||
proc::Options in;
|
||||
in.capture = true;
|
||||
in.cwd = t.path;
|
||||
checkEq(
|
||||
sfs::weakly_canonical(
|
||||
proc::run({"pwd"}, in).output.substr(0, proc::run({"pwd"}, in).output.size() - 1)),
|
||||
sfs::weakly_canonical(t.path));
|
||||
proc::Options quiet;
|
||||
quiet.silent = true;
|
||||
checkEq(proc::run({"sh", "-c", "echo hidden; exit 5"}, quiet).exitCode, 5);
|
||||
checkEq(proc::run({"/no/such/program"}).exitCode, 127);
|
||||
check(proc::findInPath("sh").has_value());
|
||||
check(!proc::findInPath("no-such-program-anywhere").has_value());
|
||||
#endif
|
||||
}
|
||||
|
||||
struct Case
|
||||
{
|
||||
const char* name;
|
||||
std::function<void()> run;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::vector<Case> cases = {
|
||||
{"text helpers", textHelpers}, {"versions", versions},
|
||||
{"cmake text", cmakeText}, {"project detection", projectDetection},
|
||||
{"settings", settings}, {"defaults", defaults},
|
||||
{"modules", modules}, {"objects modules need SDK support", objectsNeedSdkSupport},
|
||||
{"arguments", arguments}, {"processes", processes},
|
||||
};
|
||||
for (const Case& c : cases)
|
||||
{
|
||||
int before = g_failures;
|
||||
try
|
||||
{
|
||||
c.run();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
++g_failures;
|
||||
std::cerr << c.name << ": unexpected exception: " << e.what() << "\n";
|
||||
}
|
||||
std::cout << (g_failures == before ? "ok " : "FAIL ") << c.name << "\n";
|
||||
}
|
||||
std::cout << (g_failures == 0 ? "all tests passed"
|
||||
: std::to_string(g_failures) + " check(s) failed")
|
||||
<< "\n";
|
||||
return g_failures == 0 ? 0 : 1;
|
||||
}
|
||||
Reference in new issue
Block a user