mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 13:36:13 +03:00
Multiple patches may be concatenated in the same patch file, such as git commits which are changing multiple files at the same time. To handle this, parse each patch in order in the patch file, and apply each patch sequentially. To determine whether we are at the end of a patch (and not just parsing another hunk) the parser will look for a leading '@@ ' after every hunk. If that is found, there is another hunk. Otherwise, we must be at the end of this patch.
190 lines
5.2 KiB
C++
190 lines
5.2 KiB
C++
/*
|
|
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/StringView.h>
|
|
#include <LibCore/Command.h>
|
|
#include <LibCore/File.h>
|
|
#include <LibCore/System.h>
|
|
#include <LibFileSystem/FileSystem.h>
|
|
#include <LibTest/Macros.h>
|
|
#include <LibTest/TestCase.h>
|
|
|
|
static constexpr char const* s_test_dir = "/tmp/patch-test";
|
|
|
|
#define EXPECT_FILE_EQ(file_path, expected_content) \
|
|
do { \
|
|
auto output = MUST(Core::File::open(file_path, Core::File::OpenMode::Read)); \
|
|
auto content = MUST(output->read_until_eof()); \
|
|
EXPECT_EQ(StringView { content }, expected_content); \
|
|
} while (false)
|
|
|
|
class PatchSetup {
|
|
public:
|
|
PatchSetup()
|
|
{
|
|
clean_up(); // Just in case something was left behind from beforehand.
|
|
MUST(Core::System::mkdir(StringView { s_test_dir, strlen(s_test_dir) }, 0755));
|
|
}
|
|
|
|
~PatchSetup()
|
|
{
|
|
clean_up();
|
|
}
|
|
|
|
private:
|
|
static void clean_up()
|
|
{
|
|
auto result = FileSystem::remove(StringView { s_test_dir, strlen(s_test_dir) }, FileSystem::RecursionMode::Allowed);
|
|
if (result.is_error())
|
|
VERIFY(result.error().is_errno() && result.error().code() == ENOENT);
|
|
}
|
|
};
|
|
|
|
static void run_patch(Vector<char const*>&& arguments, StringView standard_input, StringView expected_stdout)
|
|
{
|
|
// Ask patch to run the test in a temporary directory so we don't leave any files around.
|
|
Vector<char const*> args_with_chdir = { "patch", "-d", s_test_dir };
|
|
args_with_chdir.extend(arguments);
|
|
args_with_chdir.append(nullptr);
|
|
|
|
auto patch = MUST(Core::Command::create("patch"sv, args_with_chdir.data()));
|
|
|
|
MUST(patch->write(standard_input));
|
|
|
|
auto [stdout, stderr] = MUST(patch->read_all());
|
|
|
|
auto status = MUST(patch->status());
|
|
if (status != Core::Command::ProcessResult::DoneWithZeroExitCode) {
|
|
FAIL(MUST(String::formatted("patch didn't exit cleanly: status: {}, stdout:{}, stderr: {}", static_cast<int>(status), StringView { stdout.bytes() }, StringView { stderr.bytes() })));
|
|
}
|
|
EXPECT_EQ(StringView { expected_stdout.bytes() }, StringView { stdout.bytes() });
|
|
}
|
|
|
|
TEST_CASE(basic_change_patch)
|
|
{
|
|
PatchSetup setup;
|
|
|
|
auto patch = R"(
|
|
--- a
|
|
+++ b
|
|
@@ -1,3 +1,3 @@
|
|
1
|
|
-2
|
|
+b
|
|
3
|
|
)"sv;
|
|
|
|
auto file = "1\n2\n3\n"sv;
|
|
auto input = MUST(Core::File::open(MUST(String::formatted("{}/a", s_test_dir)), Core::File::OpenMode::Write));
|
|
MUST(input->write_until_depleted(file.bytes()));
|
|
|
|
run_patch({}, patch, "patching file a\n"sv);
|
|
|
|
EXPECT_FILE_EQ(MUST(String::formatted("{}/a", s_test_dir)), "1\nb\n3\n");
|
|
}
|
|
|
|
TEST_CASE(basic_addition_patch_from_empty_file)
|
|
{
|
|
PatchSetup setup;
|
|
|
|
auto patch = R"(
|
|
--- /dev/null
|
|
+++ a
|
|
@@ -0,0 +1,3 @@
|
|
+1
|
|
+2
|
|
+3
|
|
)"sv;
|
|
|
|
auto file = ""sv;
|
|
auto input = MUST(Core::File::open(MUST(String::formatted("{}/a", s_test_dir)), Core::File::OpenMode::Write));
|
|
MUST(input->write_until_depleted(file.bytes()));
|
|
|
|
run_patch({}, patch, "patching file a\n"sv);
|
|
|
|
EXPECT_FILE_EQ(MUST(String::formatted("{}/a", s_test_dir)), "1\n2\n3\n");
|
|
}
|
|
|
|
TEST_CASE(strip_path_to_basename)
|
|
{
|
|
PatchSetup setup;
|
|
|
|
auto patch = R"(
|
|
--- /dev/null
|
|
+++ a/bunch/of/../folders/stripped/to/basename
|
|
@@ -0,0 +1 @@
|
|
+Hello, friends!
|
|
)"sv;
|
|
|
|
auto file = ""sv;
|
|
auto input = MUST(Core::File::open(MUST(String::formatted("{}/basename", s_test_dir)), Core::File::OpenMode::Write));
|
|
MUST(input->write_until_depleted(file.bytes()));
|
|
|
|
run_patch({}, patch, "patching file basename\n"sv);
|
|
|
|
EXPECT_FILE_EQ(MUST(String::formatted("{}/basename", s_test_dir)), "Hello, friends!\n");
|
|
}
|
|
|
|
TEST_CASE(strip_path_partially)
|
|
{
|
|
PatchSetup setup;
|
|
|
|
auto patch = R"(
|
|
--- /dev/null
|
|
+++ a/bunch/of/../folders/stripped/to/basename
|
|
@@ -0,0 +1 @@
|
|
+Hello, friends!
|
|
)"sv;
|
|
|
|
MUST(Core::System::mkdir(MUST(String::formatted("{}/to", s_test_dir)), 0755));
|
|
|
|
auto file = ""sv;
|
|
auto input = MUST(Core::File::open(MUST(String::formatted("{}/to/basename", s_test_dir)), Core::File::OpenMode::Write));
|
|
MUST(input->write_until_depleted(file.bytes()));
|
|
|
|
run_patch({ "-p6" }, patch, "patching file to/basename\n"sv);
|
|
|
|
EXPECT_FILE_EQ(MUST(String::formatted("{}/to/basename", s_test_dir)), "Hello, friends!\n");
|
|
}
|
|
|
|
TEST_CASE(add_file_from_scratch)
|
|
{
|
|
PatchSetup setup;
|
|
|
|
auto patch = R"(
|
|
--- /dev/null
|
|
+++ a/file_to_add
|
|
@@ -0,0 +1 @@
|
|
+Hello, friends!
|
|
)"sv;
|
|
|
|
run_patch({}, patch, "patching file file_to_add\n"sv);
|
|
|
|
EXPECT_FILE_EQ(MUST(String::formatted("{}/file_to_add", s_test_dir)), "Hello, friends!\n");
|
|
}
|
|
|
|
TEST_CASE(two_patches_in_single_patch_file)
|
|
{
|
|
PatchSetup setup;
|
|
|
|
auto patch = R"(
|
|
--- /dev/null
|
|
+++ a/first_file_to_add
|
|
@@ -0,0 +1 @@
|
|
+Hello, friends!
|
|
--- /dev/null
|
|
+++ a/second_file_to_add
|
|
@@ -0,0 +1 @@
|
|
+Hello, friends!
|
|
)"sv;
|
|
|
|
run_patch({}, patch, "patching file first_file_to_add\n"
|
|
"patching file second_file_to_add\n"sv);
|
|
|
|
EXPECT_FILE_EQ(MUST(String::formatted("{}/first_file_to_add", s_test_dir)), "Hello, friends!\n");
|
|
EXPECT_FILE_EQ(MUST(String::formatted("{}/second_file_to_add", s_test_dir)), "Hello, friends!\n");
|
|
}
|