| // Copyright 2026 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // | |
| #include "utils/byte_instruction.h" | |
| #include <cstdint> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/types/span.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "riegeli/bytes/string_reader.h" | |
| #include "riegeli/bytes/string_writer.h" | |
| namespace credentio { | |
| namespace { | |
| using ::absl_testing::IsOk; | |
| using ::absl_testing::StatusIs; | |
| using ::testing::HasSubstr; | |
| struct SuccessfulApplicationTestCase { | |
| std::string name; | |
| std::string input; | |
| std::vector<ByteInstruction> instructions; | |
| std::string expected; | |
| }; | |
| using SuccessfulApplicationTest = | |
| testing::TestWithParam<SuccessfulApplicationTestCase>; | |
| INSTANTIATE_TEST_SUITE_P( | |
| ParameterizedTests, SuccessfulApplicationTest, | |
| testing::ValuesIn<SuccessfulApplicationTestCase>({ | |
| { | |
| .name = "NoInstructions", | |
| .input = "hello world", | |
| .instructions = {}, | |
| .expected = "hello world", | |
| }, | |
| { | |
| .name = "EmptyInsert", | |
| .input = "", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 0, | |
| .bytes = "foo"}, | |
| }, | |
| .expected = "foo", | |
| }, | |
| { | |
| .name = "EmptyReplace", | |
| .input = "", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 0, | |
| .bytes = "foo"}, | |
| }, | |
| .expected = "foo", | |
| }, | |
| { | |
| .name = "EmptyInstruction", | |
| .input = "bar", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 0, | |
| .bytes = ""}, | |
| }, | |
| .expected = "bar", | |
| }, | |
| { | |
| .name = "InsertAtStart", | |
| .input = "bar", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 0, | |
| .bytes = "foo"}, | |
| }, | |
| .expected = "foobar", | |
| }, | |
| { | |
| .name = "InsertAtEnd", | |
| .input = "bar", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 3, | |
| .bytes = "foo"}, | |
| }, | |
| .expected = "barfoo", | |
| }, | |
| { | |
| .name = "ReplaceAtStart", | |
| .input = "bar", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 0, | |
| .bytes = "foo"}, | |
| }, | |
| .expected = "foo", | |
| }, | |
| { | |
| .name = "ReplaceAtEnd", | |
| .input = "bar", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 3, | |
| .bytes = "foo"}, | |
| }, | |
| .expected = "barfoo", | |
| }, | |
| { | |
| .name = "InsertAtMiddle", | |
| .input = "bar", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 1, | |
| .bytes = "foo"}, | |
| }, | |
| .expected = "bfooar", | |
| }, | |
| { | |
| .name = "ReplaceAtMiddle", | |
| .input = "bar", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 1, | |
| .bytes = "foo"}, | |
| }, | |
| .expected = "bfoo", | |
| }, | |
| { | |
| .name = "ReplaceInsertReplace", | |
| .input = "0_________0_________0_________0_________", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 10, | |
| .bytes = "replace"}, | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 17, | |
| .bytes = "insert"}, | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 30, | |
| .bytes = "second"}, | |
| }, | |
| .expected = "0_________replaceinsert___0_________second____", | |
| }, | |
| { | |
| .name = "InsertPastEndOfInput", | |
| .input = "hello world", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 30, | |
| .bytes = " goodnight"}, | |
| }, | |
| .expected = "hello world goodnight", | |
| }, | |
| { | |
| .name = "ReplacePastEndOfInput", | |
| .input = "hello world", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 30, | |
| .bytes = " goodnight"}, | |
| }, | |
| .expected = "hello world goodnight", | |
| }, | |
| { | |
| .name = "ReplacePastEndOfInputWithTrailingInstructions", | |
| .input = "hello world", | |
| .instructions = | |
| { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 6, | |
| .bytes = "world goodnight"}, | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 30, | |
| .bytes = " and goodbye"}, | |
| }, | |
| .expected = "hello world goodnight and goodbye", | |
| }, | |
| }), | |
| [](const testing::TestParamInfo<SuccessfulApplicationTest::ParamType>& | |
| info) { return info.param.name; }); | |
| TEST_P(SuccessfulApplicationTest, AppliesByteInstructions) { | |
| riegeli::StringReader<std::string> reader(GetParam().input); | |
| ABSL_ASSERT_OK(reader.status()); | |
| riegeli::StringWriter<std::string> writer; | |
| ABSL_ASSERT_OK(writer.status()); | |
| EXPECT_THAT(ApplyByteInstructions(&reader, GetParam().instructions, &writer), | |
| IsOk()); | |
| ASSERT_TRUE(reader.VerifyEndAndClose()); | |
| ASSERT_TRUE(writer.Close()); | |
| EXPECT_EQ(writer.dest(), GetParam().expected); | |
| } | |
| TEST_P(SuccessfulApplicationTest, AppliesByteInstructionsInPlace) { | |
| riegeli::StringWriter<std::string> writer( | |
| GetParam().input, riegeli::StringWriterBase::Options().set_append(true)); | |
| ABSL_ASSERT_OK(writer.status()); | |
| EXPECT_THAT(ApplyByteInstructions(GetParam().instructions, &writer), IsOk()); | |
| ASSERT_TRUE(writer.Close()); | |
| EXPECT_EQ(writer.dest(), GetParam().expected); | |
| } | |
| TEST(SuccessfulApplicationTest, ReaderNotAtStart) { | |
| riegeli::StringReader<> reader("hello world"); | |
| ABSL_ASSERT_OK(reader.status()); | |
| ASSERT_TRUE(reader.Seek(8)); | |
| riegeli::StringWriter<std::string> writer; | |
| ABSL_ASSERT_OK(writer.status()); | |
| EXPECT_THAT( | |
| ApplyByteInstructions(&reader, | |
| {{.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 6, | |
| .bytes = "W"}}, | |
| &writer), | |
| IsOk()); | |
| ASSERT_TRUE(reader.VerifyEndAndClose()); | |
| ASSERT_TRUE(writer.Close()); | |
| EXPECT_EQ(writer.dest(), "hello World"); | |
| } | |
| TEST(SuccessfulApplicationTest, WriterNotAtStart) { | |
| riegeli::StringWriter<std::string> writer( | |
| "hello world", riegeli::StringWriterBase::Options().set_append(true)); | |
| ABSL_ASSERT_OK(writer.status()); | |
| ASSERT_TRUE(writer.Seek(8)); | |
| EXPECT_THAT( | |
| ApplyByteInstructions({{.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 6, | |
| .bytes = "W"}}, | |
| &writer), | |
| IsOk()); | |
| ASSERT_TRUE(writer.Close()); | |
| EXPECT_EQ(writer.dest(), "hello World"); | |
| } | |
| TEST(FailedApplicationTest, ReaderClosed) { | |
| riegeli::StringReader<> reader("hello world"); | |
| ABSL_ASSERT_OK(reader.status()); | |
| reader.Close(); | |
| riegeli::StringWriter<std::string> writer; | |
| ABSL_ASSERT_OK(writer.status()); | |
| EXPECT_THAT(ApplyByteInstructions(&reader, {}, &writer), | |
| StatusIs(absl::StatusCode::kFailedPrecondition, | |
| HasSubstr("Object closed"))); | |
| } | |
| TEST(FailedApplicationTest, WriterClosed) { | |
| riegeli::StringReader<> reader("hello world"); | |
| ABSL_ASSERT_OK(reader.status()); | |
| riegeli::StringWriter<std::string> writer; | |
| ABSL_ASSERT_OK(writer.status()); | |
| writer.Close(); | |
| EXPECT_THAT(ApplyByteInstructions(&reader, {}, &writer), | |
| StatusIs(absl::StatusCode::kFailedPrecondition, | |
| HasSubstr("Object closed"))); | |
| } | |
| TEST(FailedApplicationTest, InstructionOutOfOrder) { | |
| riegeli::StringReader<> reader("hello world"); | |
| ABSL_ASSERT_OK(reader.status()); | |
| riegeli::StringWriter<std::string> writer; | |
| ABSL_ASSERT_OK(writer.status()); | |
| EXPECT_THAT( | |
| ApplyByteInstructions(&reader, | |
| {{.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 5, | |
| .bytes = "da"}, | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 2, | |
| .bytes = "da"}}, | |
| &writer), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("must be in ascending order"))); | |
| } | |
| class PrematureEofReader : public riegeli::StringReader<> { | |
| public: | |
| PrematureEofReader(absl::string_view data, uint64_t fake_size) | |
| : riegeli::StringReader<>(data), fake_size_(fake_size) {} | |
| std::optional<riegeli::Position> SizeImpl() override { return fake_size_; } | |
| private: | |
| uint64_t fake_size_; | |
| }; | |
| TEST(FailedApplicationTest, UnexpectedEofDuringCopy) { | |
| PrematureEofReader reader("hello", 100); | |
| ABSL_ASSERT_OK(reader.status()); | |
| riegeli::StringWriter<std::string> writer; | |
| ABSL_ASSERT_OK(writer.status()); | |
| EXPECT_THAT( | |
| ApplyByteInstructions(&reader, | |
| {{.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 10, | |
| .bytes = "x"}}, | |
| &writer), | |
| StatusIs(absl::StatusCode::kDataLoss, | |
| HasSubstr("Unexpected EOF while copying source data"))); | |
| } | |
| TEST(FailedApplicationTest, UnexpectedEofDuringSkip) { | |
| PrematureEofReader reader("hello", 100); | |
| ABSL_ASSERT_OK(reader.status()); | |
| riegeli::StringWriter<std::string> writer; | |
| ABSL_ASSERT_OK(writer.status()); | |
| EXPECT_THAT( | |
| ApplyByteInstructions(&reader, | |
| {{.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 0, | |
| .bytes = "xxxxxxxxxx"}}, | |
| &writer), | |
| StatusIs(absl::StatusCode::kDataLoss, | |
| HasSubstr("Unexpected EOF while skipping source data"))); | |
| } | |
| TEST(FailedApplicationInPlaceTest, WriterClosed) { | |
| riegeli::StringWriter<std::string> writer( | |
| "hello world", riegeli::StringWriterBase::Options().set_append(true)); | |
| ABSL_ASSERT_OK(writer.status()); | |
| writer.Close(); | |
| EXPECT_THAT(ApplyByteInstructions({}, &writer), | |
| StatusIs(absl::StatusCode::kDataLoss, | |
| HasSubstr("failed to seek to start of file"))); | |
| } | |
| TEST(FailedApplicationInPlaceTest, InstructionOutOfOrder) { | |
| riegeli::StringWriter<std::string> writer( | |
| "hello world", riegeli::StringWriterBase::Options().set_append(true)); | |
| ABSL_ASSERT_OK(writer.status()); | |
| EXPECT_THAT( | |
| ApplyByteInstructions({{.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 5, | |
| .bytes = "da"}, | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 2, | |
| .bytes = "da"}}, | |
| &writer), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("must be in ascending order"))); | |
| } | |
| TEST(CalculateBytesWrittenTest, IsCopyEmptyFile) { | |
| EXPECT_EQ(CalculateBytesWritten({}, /*file_size=*/0, /*is_copy=*/true), 0); | |
| EXPECT_EQ( | |
| CalculateBytesWritten({{.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 0, | |
| .bytes = "hello"}}, | |
| /*file_size=*/0, /*is_copy=*/true), | |
| 5); | |
| } | |
| TEST(CalculateBytesWrittenTest, IsCopyNoInstructions) { | |
| EXPECT_EQ(CalculateBytesWritten({}, /*file_size=*/100, /*is_copy=*/true), | |
| 100); | |
| } | |
| TEST(CalculateBytesWrittenTest, IsCopyOnlyInserts) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 10, | |
| .bytes = "hello"}, | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 50, | |
| .bytes = "world!"}, | |
| }; | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/true), | |
| 100 + 5 + 6); | |
| } | |
| TEST(CalculateBytesWrittenTest, IsCopyOnlyReplaces) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 10, | |
| .bytes = "hello"}, | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 50, | |
| .bytes = "world!"}, | |
| }; | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/true), | |
| 100); | |
| } | |
| TEST(CalculateBytesWrittenTest, IsCopyMixed) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 10, | |
| .bytes = "replace"}, | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 40, | |
| .bytes = "insert_data"}, | |
| }; | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/true), | |
| 100 + 11); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceEmptyFile) { | |
| EXPECT_EQ(CalculateBytesWritten({}, /*file_size=*/0, /*is_copy=*/false), 0); | |
| EXPECT_EQ( | |
| CalculateBytesWritten({{.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 0, | |
| .bytes = "hello"}}, | |
| /*file_size=*/0, /*is_copy=*/false), | |
| 5); | |
| EXPECT_EQ( | |
| CalculateBytesWritten({{.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 0, | |
| .bytes = "hello"}}, | |
| /*file_size=*/0, /*is_copy=*/false), | |
| 5); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceNoInstructions) { | |
| EXPECT_EQ(CalculateBytesWritten({}, /*file_size=*/100, /*is_copy=*/false), 0); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceOnlyReplaces) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 5, | |
| .bytes = "abc"}, | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 50, | |
| .bytes = "12345"}, | |
| }; | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/false), | |
| 3 + 5); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceOnlyInsertsSingle) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 40, | |
| .bytes = "12345"}, | |
| }; | |
| // Shifted bytes: (100 - 40) = 60, inserted bytes: 5 -> total 65 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/false), | |
| 65); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceOnlyInsertsMultiple) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 20, | |
| .bytes = "12345"}, | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 50, | |
| .bytes = "abc"}, | |
| }; | |
| // First insert offset: 20 -> Shifted bytes: 80, inserts: 5 + 3 = 8 -> total | |
| // 88 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/false), | |
| 88); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceReplaceBeforeInsert) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 5, | |
| .bytes = "1234567890"}, | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 40, | |
| .bytes = "abcdefghij"}, | |
| }; | |
| // Replace before insert: 10, first insert offset: 40 -> shifted: 60, insert: | |
| // 10 -> total 10 + 60 + 10 = 80 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/false), | |
| 80); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceReplaceAfterInsert) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 10, | |
| .bytes = "12345"}, | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 50, | |
| .bytes = "abcdefghij"}, | |
| }; | |
| // First insert offset: 10 -> shifted: 90, insert: 5, replace before insert: 0 | |
| // -> total 95 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/false), | |
| 95); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceReplaceBeforeAndAfterInsert) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 2, | |
| .bytes = "12345678"}, | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 20, | |
| .bytes = "123456789012345"}, | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 60, | |
| .bytes = "123456789012"}, | |
| }; | |
| // Replace before insert: 8, first insert offset: 20 -> shifted: 80, insert: | |
| // 15 -> total 8 + 80 + 15 = 103 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/false), | |
| 103); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceInsertPastEndOfFile) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 120, | |
| .bytes = "1234567890"}, // 10 bytes past EOF (file_size = 100) | |
| }; | |
| // Offset 120 >= 100: no shifted original bytes -> bytes_shifted = 0, insert = | |
| // 10 -> total 10 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/false), | |
| 10); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceInsertPastEndOfFileWithPriorReplace) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 10, | |
| .bytes = "12345"}, // 5 bytes replaced before insert | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 120, | |
| .bytes = "1234567890"}, // 10 bytes past EOF | |
| }; | |
| // Replace before insert = 5, offset 120 >= 100: bytes_shifted = 0, insert = | |
| // 10 -> total 15 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/false), | |
| 15); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceInsertAtEndOfFileBoundary) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 100, | |
| .bytes = "1234567890"}, // 10 bytes at exact EOF (file_size = 100) | |
| }; | |
| // Offset 100 >= 100: bytes_shifted = 0, insert = 10 -> total 10 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/false), | |
| 10); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceInsertJustBeforeEndOfFile) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 99, | |
| .bytes = "1234567890"}, // 10 bytes at offset 99 (file_size = 100) | |
| }; | |
| // Offset 99 < 100: bytes_shifted = 100 - 99 = 1, insert = 10 -> total 11 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/false), | |
| 11); | |
| } | |
| TEST(CalculateBytesWrittenTest, IsCopyInsertPastEndOfFile) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 120, | |
| .bytes = "1234567890"}, // 10 bytes past EOF (file_size = 100) | |
| }; | |
| // In copy mode, entire original file is written -> bytes_shifted = 100, | |
| // insert = 10 -> total 110 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/true), | |
| 110); | |
| } | |
| TEST(CalculateBytesWrittenTest, IsCopyEmptyFileWithReplace) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 0, | |
| .bytes = "12345"}, // Replace 5 bytes on file_size = 0 | |
| }; | |
| // file_size = 0, replace 5 bytes: instruction is written as append -> total 5 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/0, /*is_copy=*/true), | |
| 5); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceReplacePastEndOfFileNoInsert) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kReplace, | |
| .offset = 120, | |
| .bytes = "12345"}, // 5 bytes replace past EOF (file_size = 100) | |
| }; | |
| // No insert: bytes_shifted = 0, sum_replace_before_insert = 5 -> total 5 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/false), | |
| 5); | |
| } | |
| TEST(CalculateBytesWrittenTest, InPlaceMultipleInsertsFirstPastEOF) { | |
| std::vector<ByteInstruction> instructions = { | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 120, | |
| .bytes = "12345"}, // 5 bytes at offset 120 | |
| {.operation = ByteInstruction::Operation::kInsert, | |
| .offset = 150, | |
| .bytes = "1234567890"}, // 10 bytes at offset 150 | |
| }; | |
| // First insert offset 120 >= 100: bytes_shifted = 0, sum_insert = 15 -> total | |
| // 15 | |
| EXPECT_EQ( | |
| CalculateBytesWritten(instructions, /*file_size=*/100, /*is_copy=*/false), | |
| 15); | |
| } | |
| } // namespace | |
| } // namespace credentio |