Drives Run() with `format clean` on a staged project whose project file cannot compile. Both trees must survive, and the run must fail: getting as far as loading the project is what shows it went on to format rather than cleaning and exiting 0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
101 lines
4.4 KiB
C++
101 lines
4.4 KiB
C++
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
import std;
|
|
import Crafter.Build;
|
|
namespace fs = std::filesystem;
|
|
using namespace Crafter;
|
|
|
|
// `crafter-build clean` exists so the fix for a stale build is discoverable
|
|
// rather than folklore. It must not need the project to load: the state you most
|
|
// want to clear is one where the build is already broken.
|
|
namespace {
|
|
std::int32_t Failures = 0;
|
|
|
|
void Check(bool cond, std::string_view msg) {
|
|
if (!cond) {
|
|
std::println(std::cerr, "FAIL: {}", msg);
|
|
++Failures;
|
|
}
|
|
}
|
|
|
|
fs::path StageProject(std::string_view leaf) {
|
|
fs::path root = fs::temp_directory_path() / std::format("crafter-build-clean-{}", leaf);
|
|
fs::remove_all(root);
|
|
fs::create_directories(root / "bin" / "app-x86_64-pc-linux-gnu-native-native-0badf00d");
|
|
fs::create_directories(root / "build" / "app-x86_64-pc-linux-gnu-native-native-0badf00d");
|
|
std::ofstream(root / "bin" / "app-x86_64-pc-linux-gnu-native-native-0badf00d" / "app") << "binary";
|
|
std::ofstream(root / "build" / "app-x86_64-pc-linux-gnu-native-native-0badf00d" / "main_impl.o") << "object";
|
|
std::ofstream(root / "keep-me.txt") << "untouched";
|
|
return root;
|
|
}
|
|
|
|
std::int32_t RunWith(std::vector<std::string> args) {
|
|
std::vector<char*> argv;
|
|
for (std::string& arg : args) argv.push_back(arg.data());
|
|
return Run(static_cast<std::int32_t>(argv.size()), argv.data());
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
{
|
|
// A project file that could never compile: clean has to work anyway.
|
|
fs::path root = StageProject("broken");
|
|
fs::path projectFile = root / "project.cpp";
|
|
std::ofstream(projectFile) << "this is not valid C++ at all\n";
|
|
|
|
std::vector<fs::path> removed = CleanProject(projectFile);
|
|
Check(removed.size() == 2, std::format("both trees reported removed, got {}", removed.size()));
|
|
Check(!fs::exists(root / "bin"), "bin/ is gone");
|
|
Check(!fs::exists(root / "build"), "build/ is gone");
|
|
Check(fs::exists(root / "keep-me.txt"), "unrelated files in the project root are left alone");
|
|
Check(fs::exists(projectFile), "the project file itself is left alone");
|
|
}
|
|
|
|
{
|
|
// Idempotent, and quiet about it — nothing to remove is not an error.
|
|
fs::path root = StageProject("twice");
|
|
fs::path projectFile = root / "project.cpp";
|
|
std::ofstream(projectFile) << "\n";
|
|
|
|
CleanProject(projectFile);
|
|
std::vector<fs::path> second = CleanProject(projectFile);
|
|
Check(second.empty(), "a second clean reports nothing removed");
|
|
}
|
|
|
|
{
|
|
// Resolved relative to the project file, not the cwd, so --project=
|
|
// cleans the project it names.
|
|
fs::path root = StageProject("elsewhere");
|
|
fs::path projectFile = root / "project.cpp";
|
|
std::ofstream(projectFile) << "\n";
|
|
fs::path cwdBin = fs::current_path() / "bin";
|
|
const bool cwdBinExisted = fs::exists(cwdBin);
|
|
|
|
CleanProject(projectFile);
|
|
Check(!fs::exists(root / "bin"), "the named project's bin/ is gone");
|
|
Check(fs::exists(cwdBin) == cwdBinExisted, "the cwd's bin/ is untouched");
|
|
}
|
|
|
|
{
|
|
// `clean` is a verb only where a verb can stand. After `format` it
|
|
// is a rule-name glob like any other word, and a glob must never
|
|
// delete the build trees. The project file cannot compile, so the
|
|
// run fails once it gets as far as loading it — which is the proof
|
|
// that it went on to format instead of cleaning and exiting 0.
|
|
fs::path root = StageProject("format-glob");
|
|
fs::path projectFile = root / "project.cpp";
|
|
std::ofstream(projectFile) << "this is not valid C++ at all\n";
|
|
|
|
const std::int32_t exitCode = RunWith({"crafter-build", "format", "clean", std::format("--project={}", projectFile.string())});
|
|
Check(fs::exists(root / "bin" / "app-x86_64-pc-linux-gnu-native-native-0badf00d" / "app"), "`format clean` leaves bin/ alone");
|
|
Check(fs::exists(root / "build" / "app-x86_64-pc-linux-gnu-native-native-0badf00d" / "main_impl.o"), "`format clean` leaves build/ alone");
|
|
Check(exitCode != 0, "`format clean` goes on to load the project instead of cleaning");
|
|
}
|
|
|
|
if (Failures > 0) {
|
|
std::println(std::cerr, "{} assertions failed", Failures);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|