// Matrix row C9 — confirm the patch writer emits a unified diff // that `git apply` can consume. // // Test pins: // 1. A FixIt that replaces a single line emits a hunk with one // `-` and one `+` line, valid `@@` header. // 2. FixIts on disjoint files produce one hunk per file in the // same patch. // 3. FixIts ordered out-of-line get sorted by start_line. // 4. Missing source file produces no hunk (no abort). #include #include #include #include #include #include "fold/report/finding.hpp" #include "fold/report/patch_writer.hpp" namespace { std::filesystem::path writeTempFile(const std::string& name, const std::string& contents) { auto p = std::filesystem::temp_directory_path() / name; std::ofstream out(p); out << contents; out.close(); return p; } fold::Finding makeFinding(std::string file, int line, int col, std::string replacement, std::string desc) { fold::Finding f; f.kind = fold::FindingKind::UninitLocalRead; fold::FixIt fix; fix.file = std::move(file); fix.start_line = line; fix.start_column = col; fix.end_line = line; fix.end_column = col; fix.replacement = std::move(replacement); fix.description = std::move(desc); f.fix_its.push_back(std::move(fix)); return f; } } // namespace TEST_CASE("patch_writer: single-line in-place insertion emits a valid hunk", "[c9][patch_writer][single]") { auto file = writeTempFile("c9_single.c", "int x;\n"); fold::Finding f = makeFinding(file.filename().string(), 1, 6, " = 0", "Initialise uninitialised local"); f.fix_its[0].end_column = 6; // pure insertion before `;` auto patch = fold::report::writeFixItPatch({f}, std::filesystem::temp_directory_path().string()); REQUIRE(patch.find("--- a/c9_single.c") != std::string::npos); REQUIRE(patch.find("+++ b/c9_single.c") != std::string::npos); REQUIRE(patch.find("@@") != std::string::npos); REQUIRE(patch.find("-int x;") != std::string::npos); REQUIRE(patch.find("+int x = 0;") != std::string::npos); std::filesystem::remove(file); } TEST_CASE("patch_writer: FixIts on two files produce two hunks", "[c9][patch_writer][multi-file]") { auto a = writeTempFile("c9_a.c", "int a;\n"); auto b = writeTempFile("c9_b.c", "int b;\n"); fold::Finding fa = makeFinding(a.filename().string(), 1, 6, " = 0", "init a"); fa.fix_its[0].end_column = 6; fold::Finding fb = makeFinding(b.filename().string(), 1, 6, " = 0", "init b"); fb.fix_its[0].end_column = 6; auto patch = fold::report::writeFixItPatch({fa, fb}, std::filesystem::temp_directory_path().string()); REQUIRE(patch.find("--- a/c9_a.c") != std::string::npos); REQUIRE(patch.find("--- a/c9_b.c") != std::string::npos); std::filesystem::remove(a); std::filesystem::remove(b); } TEST_CASE("patch_writer: FixIts within one file are sorted by line", "[c9][patch_writer][sort]") { auto file = writeTempFile("c9_sort.c", "int a;\nint b;\nint c;\n"); // Emit in reverse order — writer must sort. fold::Finding f3 = makeFinding(file.filename().string(), 3, 6, " = 0", "init c"); f3.fix_its[0].end_column = 6; fold::Finding f1 = makeFinding(file.filename().string(), 1, 6, " = 0", "init a"); f1.fix_its[0].end_column = 6; auto patch = fold::report::writeFixItPatch({f3, f1}, std::filesystem::temp_directory_path().string()); auto line1_pos = patch.find("-int a;"); auto line3_pos = patch.find("-int c;"); REQUIRE(line1_pos != std::string::npos); REQUIRE(line3_pos != std::string::npos); REQUIRE(line1_pos < line3_pos); std::filesystem::remove(file); } TEST_CASE("patch_writer: missing source file is skipped, not fatal", "[c9][patch_writer][missing]") { fold::Finding f = makeFinding("nonexistent_c9_file.c", 1, 1, "X", "noop"); auto patch = fold::report::writeFixItPatch({f}, std::filesystem::temp_directory_path().string()); // Either an empty string or one with no hunk for the missing path. REQUIRE(patch.find("--- a/nonexistent_c9_file.c") == std::string::npos); }