llvm/unittests/Support/FileOutputBufferTest.cpp
Zachary Turner 822461008d Add a flag to FileOutputBuffer that allows modification.
FileOutputBuffer creates a temp file and on commit atomically
renames the temp file to the destination file.  Sometimes we
want to modify an existing file in place, but still have the
atomicity guarantee.  To do this we can initialize the contents
of the temp file from the destination file (if it exists), that
way the resulting FileOutputBuffer can have only selective
bytes modified.  Committing will then atomically replace the
destination file as desired.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@335902 91177308-0d34-0410-b5e6-96231b3b80d8
2018-06-28 18:49:09 +00:00

175 lines
6.5 KiB
C++

//===- llvm/unittest/Support/FileOutputBuffer.cpp - unit tests ------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
using namespace llvm;
using namespace llvm::sys;
#define ASSERT_NO_ERROR(x) \
if (std::error_code ASSERT_NO_ERROR_ec = x) { \
SmallString<128> MessageStorage; \
raw_svector_ostream Message(MessageStorage); \
Message << #x ": did not return errc::success.\n" \
<< "error number: " << ASSERT_NO_ERROR_ec.value() << "\n" \
<< "error message: " << ASSERT_NO_ERROR_ec.message() << "\n"; \
GTEST_FATAL_FAILURE_(MessageStorage.c_str()); \
} else { \
}
namespace {
TEST(FileOutputBuffer, Test) {
// Create unique temporary directory for these tests
SmallString<128> TestDirectory;
{
ASSERT_NO_ERROR(
fs::createUniqueDirectory("FileOutputBuffer-test", TestDirectory));
}
// TEST 1: Verify commit case.
SmallString<128> File1(TestDirectory);
File1.append("/file1");
{
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
FileOutputBuffer::create(File1, 8192);
ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError()));
std::unique_ptr<FileOutputBuffer> &Buffer = *BufferOrErr;
// Start buffer with special header.
memcpy(Buffer->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
// Write to end of buffer to verify it is writable.
memcpy(Buffer->getBufferEnd() - 20, "AABBCCDDEEFFGGHHIIJJ", 20);
// Commit buffer.
ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit()));
}
// Verify file is correct size.
uint64_t File1Size;
ASSERT_NO_ERROR(fs::file_size(Twine(File1), File1Size));
ASSERT_EQ(File1Size, 8192ULL);
ASSERT_NO_ERROR(fs::remove(File1.str()));
// TEST 2: Verify abort case.
SmallString<128> File2(TestDirectory);
File2.append("/file2");
{
Expected<std::unique_ptr<FileOutputBuffer>> Buffer2OrErr =
FileOutputBuffer::create(File2, 8192);
ASSERT_NO_ERROR(errorToErrorCode(Buffer2OrErr.takeError()));
std::unique_ptr<FileOutputBuffer> &Buffer2 = *Buffer2OrErr;
// Fill buffer with special header.
memcpy(Buffer2->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
// Do *not* commit buffer.
}
// Verify file does not exist (because buffer not committed).
ASSERT_EQ(fs::access(Twine(File2), fs::AccessMode::Exist),
errc::no_such_file_or_directory);
ASSERT_NO_ERROR(fs::remove(File2.str()));
// TEST 3: Verify sizing down case.
SmallString<128> File3(TestDirectory);
File3.append("/file3");
{
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
FileOutputBuffer::create(File3, 8192000);
ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError()));
std::unique_ptr<FileOutputBuffer> &Buffer = *BufferOrErr;
// Start buffer with special header.
memcpy(Buffer->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
// Write to end of buffer to verify it is writable.
memcpy(Buffer->getBufferEnd() - 20, "AABBCCDDEEFFGGHHIIJJ", 20);
ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit()));
}
// Verify file is correct size.
uint64_t File3Size;
ASSERT_NO_ERROR(fs::file_size(Twine(File3), File3Size));
ASSERT_EQ(File3Size, 8192000ULL);
ASSERT_NO_ERROR(fs::remove(File3.str()));
// TEST 4: Verify file can be made executable.
SmallString<128> File4(TestDirectory);
File4.append("/file4");
{
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
FileOutputBuffer::create(File4, 8192, FileOutputBuffer::F_executable);
ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError()));
std::unique_ptr<FileOutputBuffer> &Buffer = *BufferOrErr;
// Start buffer with special header.
memcpy(Buffer->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
// Commit buffer.
ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit()));
}
// Verify file exists and is executable.
fs::file_status Status;
ASSERT_NO_ERROR(fs::status(Twine(File4), Status));
bool IsExecutable = (Status.permissions() & fs::owner_exe);
EXPECT_TRUE(IsExecutable);
ASSERT_NO_ERROR(fs::remove(File4.str()));
// Clean up.
ASSERT_NO_ERROR(fs::remove(TestDirectory.str()));
}
TEST(FileOutputBuffer, TestModify) {
// Create unique temporary directory for these tests
SmallString<128> TestDirectory;
{
ASSERT_NO_ERROR(
fs::createUniqueDirectory("FileOutputBuffer-modify", TestDirectory));
}
SmallString<128> File1(TestDirectory);
File1.append("/file");
// First write some data.
{
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
FileOutputBuffer::create(File1, 10);
ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError()));
std::unique_ptr<FileOutputBuffer> &Buffer = *BufferOrErr;
memcpy(Buffer->getBufferStart(), "AAAAAAAAAA", 10);
ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit()));
}
// Then re-open the file for modify and change only some bytes.
{
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
FileOutputBuffer::create(File1, size_t(-1), FileOutputBuffer::F_modify);
ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError()));
std::unique_ptr<FileOutputBuffer> &Buffer = *BufferOrErr;
ASSERT_EQ(10, Buffer->getBufferSize());
uint8_t *Data = Buffer->getBufferStart();
Data[0] = 'X';
Data[9] = 'X';
ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit()));
}
// Finally, re-open the file for read and verify that it has the modified
// contents.
{
ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = MemoryBuffer::getFile(File1);
ASSERT_NO_ERROR(BufferOrErr.getError());
std::unique_ptr<MemoryBuffer> Buffer = std::move(*BufferOrErr);
ASSERT_EQ(10, Buffer->getBufferSize());
EXPECT_EQ(StringRef("XAAAAAAAAX"), Buffer->getBuffer());
}
// Clean up.
ASSERT_NO_ERROR(fs::remove(File1));
ASSERT_NO_ERROR(fs::remove(TestDirectory));
}
} // anonymous namespace