From 46cdbfe9644e6b32277dd60fd31b3c4144a0235f Mon Sep 17 00:00:00 2001 From: huangyuchen Date: Wed, 22 Mar 2023 14:07:04 +0800 Subject: [PATCH] Add new funtionality of file-mapping, including: 1. source code and header file 2. UT cases file 3. Revision on ErrCode 4. Revision on .gn file Issue: I6SNVV Test: Newly added UT suite - UtilsMappedFileTest Change-Id: I6f509d051abfff8a54df427dd3a6ae6670828056 Signed-off-by: huangyuchen --- base/BUILD.gn | 12 + base/include/common_errors.h | 3 +- base/include/common_mapped_file_errors.h | 61 + base/include/mapped_file.h | 218 +++ base/src/mapped_file.cpp | 595 ++++++++ base/test/unittest/common/BUILD.gn | 14 + .../common/utils_mapped_file_test.cpp | 1301 +++++++++++++++++ 7 files changed, 2203 insertions(+), 1 deletion(-) create mode 100644 base/include/common_mapped_file_errors.h create mode 100644 base/include/mapped_file.h create mode 100644 base/src/mapped_file.cpp create mode 100644 base/test/unittest/common/utils_mapped_file_test.cpp diff --git a/base/BUILD.gn b/base/BUILD.gn index 18a37fa..cd3ad8d 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn @@ -18,6 +18,7 @@ declare_args() { c_utils_debug_refbase = false c_utils_track_all = false c_utils_print_track_at_once = false + c_utils_debug_log_enabled = false } config("utils_config") { @@ -55,6 +56,10 @@ config("utils_all_dependent_configs") { include_dirs = [ "include" ] } +config("debug_log_enabled") { + defines = [ "DEBUG_UTILS" ] +} + config("debug_refbase") { defines = [ "DEBUG_REFBASE" ] } @@ -77,6 +82,7 @@ sources_utils = [ "src/semaphore_ex.cpp", "src/thread_pool.cpp", "src/file_ex.cpp", + "src/mapped_file.cpp", "src/observer.cpp", "src/thread_ex.cpp", "src/event_demultiplexer.cpp", @@ -114,6 +120,9 @@ ohos_static_library("utilsbase") { if (current_os != "android" && current_os != "ios") { defines = [ "CONFIG_HILOG" ] } + if (c_utils_debug_log_enabled) { + configs += [ ":debug_log_enabled" ] + } external_deps = [ "hilog_native:libhilog_base" ] public_deps = [ "//third_party/bounds_checking_function:libsec_static" ] @@ -162,6 +171,9 @@ ohos_shared_library("utils") { if (current_os != "android" && current_os != "ios") { defines = [ "CONFIG_HILOG" ] } + if (c_utils_debug_log_enabled) { + configs += [ ":debug_log_enabled" ] + } external_deps = [ "hilog_native:libhilog_base" ] public_deps = [ "//third_party/bounds_checking_function:libsec_shared" ] diff --git a/base/include/common_errors.h b/base/include/common_errors.h index 2f63ee4..2298202 100644 --- a/base/include/common_errors.h +++ b/base/include/common_errors.h @@ -47,7 +47,8 @@ namespace Utils { */ enum { MODULE_DEFAULT = 0, - MODULE_TIMER = 1 + MODULE_TIMER = 1, + MODULE_MAPPED_FILE = 2, // new module }; diff --git a/base/include/common_mapped_file_errors.h b/base/include/common_mapped_file_errors.h new file mode 100644 index 0000000..f672aec --- /dev/null +++ b/base/include/common_mapped_file_errors.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * 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 + * + * http://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. + */ + + /** + * @file common_mapped_file_errors.h + * + * @brief Provide value of 'Code' segment of ErrCode for 'MappedFile' module in + * commonlibrary subsystem. + */ + +#ifndef UTILS_COMMON_TIMER_ERRORS_H +#define UTILS_COMMON_TIMER_ERRORS_H + +#include +#include "errors.h" +#include "common_errors.h" + +namespace OHOS { +namespace Utils { + +/** + * ErrCode layout + * + * +-----+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | Bit |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00| + * +-----+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * |Field|Reserved| Subsystem | Module | Code | + * +-----+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * In this file, subsystem is "SUBSYS_COMMON" and module is "MODULE_MAPPED_FILE". + */ + +using ErrCode = int; + +// offset of mapped file module error, only be used in this file. +/** + * @brief Base ErrCode of module 'MappedFile' in commonlibrary subsystem. + */ +constexpr ErrCode COMMON_MAPPED_FILE_ERR_OFFSET = ErrCodeOffset(SUBSYS_COMMON, MODULE_MAPPED_FILE); + +enum { + MAPPED_FILE_ERR_OK = COMMON_MAPPED_FILE_ERR_OFFSET + 0, + MAPPED_FILE_ERR_FAILED = COMMON_MAPPED_FILE_ERR_OFFSET + 1, +}; + +} // Utils +} // OHOS + +#endif diff --git a/base/include/mapped_file.h b/base/include/mapped_file.h new file mode 100644 index 0000000..1ee09e1 --- /dev/null +++ b/base/include/mapped_file.h @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * 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 + * + * http://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. + */ + +/** + * @file mapped_file.h + * + * @brief Provide classes for memory-mapped file implemented in c_utils. + */ + +#ifndef UTILS_BASE_MAPPED_FILE_H +#define UTILS_BASE_MAPPED_FILE_H + +#include +#include +#include +#include +#include "errors.h" + +namespace OHOS { +namespace Utils { + + +enum class MapMode : uint8_t { + DEFAULT = 0, + PRIVATE = 2, + SHARED = DEFAULT, + READ_ONLY = 4, + READ_WRITE = DEFAULT, + CREATE_IF_ABSENT = 8 +}; + +class MappedFile { +public: + static constexpr off_t DEFAULT_LENGTH = -1LL; + + // constructors and assignment operators + explicit MappedFile(std::string& path, + MapMode mode = MapMode::DEFAULT, + off_t offset = 0, + off_t size = DEFAULT_LENGTH, + const char *hint = nullptr); + + MappedFile(const MappedFile& other) = delete; + MappedFile(MappedFile&& other) noexcept; + MappedFile& operator=(const MappedFile& other) = delete; + MappedFile& operator=(MappedFile&& other) noexcept; + virtual ~MappedFile(); + + // mapping + ErrCode Normalize(); + ErrCode Map(); + ErrCode Unmap(); + ErrCode TurnNext(); + ErrCode Resize(); + ErrCode Resize(off_t newSize, bool sync = false); + ErrCode Clear(bool force = false); + + + // info + inline off_t Size() const + { + return size_; + } + + inline off_t StartOffset() const + { + return offset_; + } + + inline off_t EndOffset() const + { + return size_ == DEFAULT_LENGTH ? -1LL : offset_ + size_ - 1LL; + } + + inline static off_t PageSize() + { + return pageSize_; + } + + inline char* Begin() const + { + return data_; + } + + inline char* End() const + { + return data_ == nullptr? nullptr : data_ + size_ - 1; + } + + inline char* RegionStart() const + { + return rStart_; + } + + + inline char* RegionEnd() const + { + return rEnd_; + } + + inline bool IsMapped() const + { + return isMapped_; + } + + inline bool IsNormed() const + { + return isNormed_; + } + + inline const std::string& GetPath() const + { + return path_; + } + + inline const char* GetHint() const + { + return hint_; + } + + inline MapMode GetMode() const + { + return mode_; + } + + inline int GetFd() const + { + return fd_; + } + + bool ChangeOffset(off_t offset); + bool ChangeSize(off_t size); + bool ChangePath(const std::string& path); + bool ChangeHint(const char* hint); + bool ChangeMode(MapMode mode); + +private: + inline static off_t RoundSize(off_t input) + { + return (input % PageSize() == 0) ? input : (input / PageSize() + 1) * PageSize(); + } + + bool ValidMappedSize(off_t &targetSize, const struct stat &stat); + bool NormalizeSize(); + bool NormalizeMode(); + bool OpenFile(); + bool SyncFileSize(off_t newSize); + void Reset(); + + char* data_ = nullptr; + char* rStart_ = nullptr; + char* rEnd_ = nullptr; + bool isMapped_ = false; + bool isNormed_ = false; + bool isNewFile_ = false; + + std::string path_; + off_t size_; + static off_t pageSize_; + off_t offset_; + MapMode mode_; + int fd_ = -1; + int mapProt_; + int mapFlag_; + int openFlag_; + const char *hint_; +}; + +inline MapMode operator&(MapMode a, MapMode b) +{ + return static_cast(static_cast(a) & static_cast(b)); +} + +inline MapMode operator|(MapMode a, MapMode b) +{ + return static_cast(static_cast(a) | static_cast(b)); +} + +inline MapMode operator^(MapMode a, MapMode b) +{ + return static_cast(static_cast(a) ^ static_cast(b)); +} + +inline MapMode operator~(MapMode a) +{ + return static_cast(~static_cast(a)); +} + +inline MapMode& operator|=(MapMode& a, MapMode b) +{ + return a = a | b; +} + +inline MapMode& operator&=(MapMode& a, MapMode b) +{ + return a = a & b; +} + +inline MapMode& operator^=(MapMode& a, MapMode b) +{ + return a = a ^ b; +} + +} // namespace Utils +} // namespace OHOS +#endif \ No newline at end of file diff --git a/base/src/mapped_file.cpp b/base/src/mapped_file.cpp new file mode 100644 index 0000000..594d9d4 --- /dev/null +++ b/base/src/mapped_file.cpp @@ -0,0 +1,595 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * 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 + * + * http://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 +#include +#include +#include +#include "errors.h" +#include "file_ex.h" +#include "utils_log.h" +#include "common_mapped_file_errors.h" +#include "mapped_file.h" + +namespace OHOS { +namespace Utils { +off_t MappedFile::pageSize_ = static_cast(sysconf(_SC_PAGESIZE)); + +MappedFile::MappedFile(std::string& path, MapMode mode, off_t offset, off_t size, const char *hint) + :path_(path), size_(size), offset_(offset), mode_(mode), hint_(hint) +{ + if (Map() != MAPPED_FILE_ERR_OK) { + UTILS_LOGW("%{public}s: Mapping Failed.", __FUNCTION__); + } +} + +bool MappedFile::ValidMappedSize(off_t &targetSize, const struct stat &stb) +{ + off_t max = RoundSize(stb.st_size) - offset_; // Avoid mapped size excessing + // that of the file more than a page, + if (max > 0) { // since write operation on it may raise signal 7. + targetSize = targetSize > max ? max : targetSize; + } else { + return false; + } + + return true; +} + +bool MappedFile::NormalizeSize() +{ + if (size_ == 0 || size_ < DEFAULT_LENGTH) { + UTILS_LOGD("%{public}s: Failed. Invalid mapping size: %{public}lld", + __FUNCTION__, static_cast(size_)); + return false; + } + + errno = 0; + if (!FileExists(path_)) { + if ((mode_ & MapMode::CREATE_IF_ABSENT) == MapMode::DEFAULT) { + UTILS_LOGD("%{public}s: Failed. %{public}s", __FUNCTION__, strerror(errno)); + return false; + } + + if (size_ == DEFAULT_LENGTH) { + size_ = PageSize(); + } + + isNewFile_ = true; + } else { + struct stat stb = {0}; + // Calculate specified mapping size + if (stat(path_.c_str(), &stb) != 0) { + UTILS_LOGW("%{public}s: Failed. Get file size failed! Mapped size will be that of a page.", __FUNCTION__); + size_ = PageSize(); + } + + if (size_ == DEFAULT_LENGTH) { + size_ = stb.st_size; + } + + // Get valid size + if (!ValidMappedSize(size_, stb)) { + UTILS_LOGD("%{public}s: Failed. Invalid params. Specified size: %{public}lld, File size: %{public}lld", \ + __FUNCTION__, static_cast(size_), static_cast(stb.st_size)); + return false; + } + } + + return true; +} + +bool MappedFile::NormalizeMode() +{ + mode_ &= (MapMode::PRIVATE | MapMode::READ_ONLY | MapMode::CREATE_IF_ABSENT); + + openFlag_ = O_CLOEXEC; + if (mode_ == MapMode::DEFAULT) { + mapFlag_ = MAP_SHARED; + mapProt_ = PROT_READ | PROT_WRITE; + openFlag_ |= O_RDWR; + } else { + if ((mode_ & MapMode::PRIVATE) != MapMode::DEFAULT) { + mapFlag_ = MAP_PRIVATE; + } else { + mapFlag_ = MAP_SHARED; + } + + if ((mode_ & MapMode::READ_ONLY) != MapMode::DEFAULT) { + mapProt_ = PROT_READ; + openFlag_ |= O_RDONLY; + } else { + mapProt_ = PROT_READ | PROT_WRITE; + openFlag_ |= O_RDWR; + } + + if ((mode_ & MapMode::CREATE_IF_ABSENT) != MapMode::DEFAULT) { + openFlag_ |= O_CREAT; + } + } + + return true; +} + +ErrCode MappedFile::Normalize() +{ + if (isNormed_) { + UTILS_LOGD("%{public}s: Failed. Already normalized.", __FUNCTION__); + return ERR_INVALID_OPERATION; + } + + // resolve params for mapping region + // offset + if (offset_ < 0 || (offset_ % PageSize() != 0)) { + UTILS_LOGD("%{public}s: Failed. Invalid offset: %{public}lld", __FUNCTION__, static_cast(offset_)); + return ERR_INVALID_VALUE; + } + + // size + if (!NormalizeSize()) { + UTILS_LOGD("%{public}s: Failed. Cannot normalize size.", __FUNCTION__); + return ERR_INVALID_VALUE; + } + + // Set open flags, mapping types and protections + if (!NormalizeMode()) { + UTILS_LOGD("%{public}s: Failed. Cannot normalize mode.", __FUNCTION__); + return ERR_INVALID_VALUE; + } + + isNormed_ = true; + return MAPPED_FILE_ERR_OK; +} + +bool MappedFile::OpenFile() +{ + int fd = open(path_.c_str(), openFlag_, S_IRWXU | S_IRGRP | S_IROTH); + if (fd == -1) { + UTILS_LOGD("%{public}s: Failed. Cannot open file - %{public}s.", __FUNCTION__, strerror(errno)); + return false; + } + + if (isNewFile_) { + if (ftruncate(fd, EndOffset() + 1) == -1) { + UTILS_LOGD("%{public}s: Failed. Cannot change file size: %{public}s.", __FUNCTION__, strerror(errno)); + if (close(fd) == -1) { + UTILS_LOGW("%{public}s: Failed. Cannot close the file: %{public}s.", \ + __FUNCTION__, strerror(errno)); + } + if (unlink(path_.c_str()) == -1) { + UTILS_LOGW("%{public}s: Failed. Cannot unlink the file: %{public}s.", \ + __FUNCTION__, strerror(errno)); + } + return false; + } + isNewFile_ = false; + } + + fd_ = fd; + return true; +} + +ErrCode MappedFile::Map() +{ + if (isMapped_) { + UTILS_LOGD("%{public}s: Failed. Already mapped.", __FUNCTION__); + return ERR_INVALID_OPERATION; + } + + // Normalize params + ErrCode res = Normalize(); + if (res != MAPPED_FILE_ERR_OK && res != ERR_INVALID_OPERATION) { + UTILS_LOGD("%{public}s: Normalize Failed.", __FUNCTION__); + return res; + } + + // Open file to get its fd + if (fd_ == -1) { + if (!OpenFile()) { + UTILS_LOGD("%{public}s: Open Failed.", __FUNCTION__); + return MAPPED_FILE_ERR_FAILED; + } + } + + // Try map + void* data = MAP_FAILED; + do { + data = mmap(reinterpret_cast(const_cast(hint_)), + static_cast(size_), + mapProt_, + mapFlag_, + fd_, + offset_); + if (data == MAP_FAILED && hint_ != nullptr) { + UTILS_LOGW("%{public}s: Mapping Failed. %{public}s, retry with a null hint.", \ + __FUNCTION__, strerror(errno)); + hint_ = nullptr; + } else { + break; + } + } while (true); + + if (data == MAP_FAILED) { + UTILS_LOGD("%{public}s: Mapping Failed. %{public}s", __FUNCTION__, strerror(errno)); + return MAPPED_FILE_ERR_FAILED; + } + + rStart_ = reinterpret_cast(data); + // set region boundary. + rEnd_ = rStart_ + (RoundSize(size_) - 1LL); + // set segment start + data_ = rStart_; + isMapped_ = true; + + return MAPPED_FILE_ERR_OK; +} + +ErrCode MappedFile::Unmap() +{ + if (!isMapped_) { + UTILS_LOGD("%{public}s: Failed. Already unmapped.", __FUNCTION__); + return ERR_INVALID_OPERATION; + } + + if (!isNormed_) { + UTILS_LOGW("%{public}s. Try unmapping with params changed.", __FUNCTION__); + } + + if (reinterpret_cast(rStart_) % PageSize() != 0) { + UTILS_LOGD("%{public}s: Failed. Invalid addr. Region start addr: %{public}p", + __FUNCTION__, reinterpret_cast(rStart_)); + return ERR_INVALID_VALUE; + } + + if (munmap(rStart_, static_cast(size_)) == -1) { + UTILS_LOGD("%{public}s: Failed. %{public}s.", __FUNCTION__, strerror(errno)); + return MAPPED_FILE_ERR_FAILED; + } + + rStart_ = nullptr; + rEnd_ = nullptr; + data_ = nullptr; + isMapped_ = false; + return MAPPED_FILE_ERR_OK; +} + +bool MappedFile::SyncFileSize(off_t newSize) +{ + if (newSize > size_) { + struct stat stb = {0}; + if (stat(path_.c_str(), &stb) != 0) { + UTILS_LOGD("%{public}s: Failed. Cannot get file size: %{public}s.", __FUNCTION__, strerror(errno)); + return false; + } else if (offset_ + newSize <= stb.st_size) { + UTILS_LOGW("%{public}s: Failed. Unextend file size, no need to sync.", __FUNCTION__); + } else { + if (fd_ == -1) { + UTILS_LOGD("%{public}s: Failed. Invalid fd.", __PRETTY_FUNCTION__); + return false; + } + if (ftruncate(fd_, offset_ + newSize) == -1) { + UTILS_LOGD("%{public}s: Failed. Cannot extend file size: %{public}s.", __FUNCTION__, strerror(errno)); + return false; + } + } + } + + return true; +} + + +ErrCode MappedFile::Resize(off_t newSize, bool sync) +{ + if (newSize == 0 || newSize < DEFAULT_LENGTH || newSize == size_) { + UTILS_LOGD("%{public}s: Failed. Cannot remap with the same /negative size.", __FUNCTION__); + return ERR_INVALID_OPERATION; + } + + if (!isMapped_) { + UTILS_LOGD("%{public}s: Failed. Cannot remap with no mapped file exists.", __FUNCTION__); + return ERR_INVALID_OPERATION; + } + + if (!isNormed_) { + UTILS_LOGD("%{public}s: Failed. Cannot remap with params unnormalized.", __FUNCTION__); + return ERR_INVALID_OPERATION; + } + + if (sync) { + if (!SyncFileSize(newSize)) { + UTILS_LOGD("%{public}s: Sync Failed.", __FUNCTION__); + return MAPPED_FILE_ERR_FAILED; + } + } else { + struct stat stb = {0}; + if (stat(path_.c_str(), &stb) != 0) { + UTILS_LOGW("%{public}s: Failed. Cannot get file size: %{public}s.", __FUNCTION__, strerror(errno)); + return ERR_INVALID_OPERATION; + } + + if (!ValidMappedSize(newSize, stb)) { + UTILS_LOGD("%{public}s: Failed. Invalid params.", __FUNCTION__); + return ERR_INVALID_VALUE; + } + } + + void* newData = mremap(rStart_, static_cast(size_), static_cast(newSize), MREMAP_MAYMOVE); + if (newData == MAP_FAILED) { + UTILS_LOGD("%{public}s: Failed. %{public}s", __FUNCTION__, strerror(errno)); + return MAPPED_FILE_ERR_FAILED; + } + + rStart_ = reinterpret_cast(newData); + size_ = newSize; + // set region boundary. + rEnd_ = rStart_ + RoundSize(size_) - 1; + // set segment start. + data_ = rStart_; + + return MAPPED_FILE_ERR_OK; +} + +ErrCode MappedFile::Resize() +{ + if (isMapped_) { + UTILS_LOGD("%{public}s: Failed. No param changes detected or unmapping required before resize.", __FUNCTION__); + return ERR_INVALID_OPERATION; + } + + int res = Map(); + if (res != MAPPED_FILE_ERR_OK) { + UTILS_LOGD("%{public}s: Failed. Remapping failed.", __FUNCTION__); + return res; + } + + return MAPPED_FILE_ERR_OK; +} + +ErrCode MappedFile::TurnNext() +{ + if (!isNormed_) { + UTILS_LOGD("%{public}s: Failed. Cannot turnNext with params changed.", __FUNCTION__); + return ERR_INVALID_OPERATION; + } + + struct stat stb = {0}; + if (stat(path_.c_str(), &stb) != 0) { + UTILS_LOGD("%{public}s: Failed. Get file size failed.", __FUNCTION__); + return MAPPED_FILE_ERR_FAILED; + } + + if (EndOffset() + 1 >= stb.st_size) { + UTILS_LOGD("%{public}s: Failed. No contents remained.", __FUNCTION__); + return ERR_INVALID_OPERATION; + } + + // save original params + off_t oldSize = size_; + off_t oldOff = offset_; + const char* oldHint = hint_; + + // if mapped, rStart_ and rEnd_ are viable + if (isMapped_) { + // case 1: remap needed + if (End() == rEnd_) { + // check if larger than exact file size. + if (EndOffset() + 1 + size_ > stb.st_size) { + size_ = stb.st_size - EndOffset() - 1; + } + isNormed_ = false; + hint_ = rStart_; + offset_ += oldSize; + + ErrCode res = Unmap(); + if (res != MAPPED_FILE_ERR_OK || (res = Resize()) != MAPPED_FILE_ERR_OK) { + UTILS_LOGD("%{public}s Failed. Fail to UnMap/Resize.", __FUNCTION__); + // restore + offset_ = oldOff; + size_ = oldSize; + hint_ = oldHint; + isNormed_ = true; + } + return res; + } + + // case 2: no need to remap, but to adjust boundary. + if (End() + oldSize > rEnd_) { // otherwise else keep original "size_" + size_ = rEnd_ - End(); + } + + data_ += oldSize; + offset_ += oldSize; + return MAPPED_FILE_ERR_OK; + } + + // if not mapped, turnNext() will set offset to next page of PageSize() + offset_ += PageSize(); + isNormed_ = false; + + return MAPPED_FILE_ERR_OK; +} + +void MappedFile::Reset() +{ + isNormed_ = false; + isMapped_ = false; + isNewFile_ = false; + + rStart_ = nullptr; + rEnd_ = nullptr; + data_ = nullptr; + path_ = ""; + size_ = DEFAULT_LENGTH; + offset_ = 0; + mode_ = MapMode::DEFAULT; + fd_ = -1; + mapProt_ = 0; + mapFlag_ = 0; + openFlag_ = 0; + hint_ = nullptr; +} + +ErrCode MappedFile::Clear(bool force) +{ + if (isMapped_) { + ErrCode res = Unmap(); + if (!force && res != MAPPED_FILE_ERR_OK && res != ERR_INVALID_OPERATION) { + UTILS_LOGD("%{public}s failed. UnMapping Failed.", __FUNCTION__); + return res; + } + } + + if (fd_ != -1 && close(fd_) == -1) { + UTILS_LOGD("%{public}s: Failed. Cannot close the file: %{public}s.", \ + __FUNCTION__, strerror(errno)); + return MAPPED_FILE_ERR_FAILED; + } + Reset(); + return MAPPED_FILE_ERR_OK; +} + +MappedFile::~MappedFile() +{ + if (isMapped_) { + ErrCode res = Unmap(); + if (res != MAPPED_FILE_ERR_OK && res != ERR_INVALID_OPERATION) { + UTILS_LOGW("%{public}s: File unmapping failed, it will be automatically \ + unmapped when the process is terminated.", __FUNCTION__); + } + } + + if (fd_ != -1 && close(fd_) == -1) { + UTILS_LOGW("%{public}s: Failed. Cannot close the file: %{public}s.", \ + __FUNCTION__, strerror(errno)); + } +} + +MappedFile::MappedFile(MappedFile&& other) noexcept + : data_(other.data_), rStart_(other.rStart_), rEnd_(other.rEnd_), isMapped_(other.isMapped_), + isNormed_(other.isNormed_), isNewFile_(other.isNewFile_), path_(std::move(other.path_)), size_(other.size_), + offset_(other.offset_), mode_(other.mode_), fd_(other.fd_), mapProt_(other.mapProt_), mapFlag_(other.mapFlag_), + openFlag_(other.openFlag_), hint_(other.hint_) +{ + other.Reset(); +} + +MappedFile& MappedFile::operator=(MappedFile&& other) noexcept +{ + Clear(true); + + data_ = other.data_; + rStart_ = other.rStart_; + rEnd_ = other.rEnd_; + isMapped_ = other.isMapped_; + isNormed_ = other.isNormed_; + isNewFile_ = other.isNewFile_; + path_ = other.path_; + size_ = other.size_; + offset_ = other.offset_; + mode_ = other.mode_; + fd_ = other.fd_; + mapProt_ = other.mapProt_; + mapFlag_ = other.mapFlag_; + openFlag_ = other.openFlag_; + hint_ = other.hint_; + + other.Reset(); + + return *this; +} + +bool MappedFile::ChangeOffset(off_t val) +{ + if (offset_ != val) { + if (!isMapped_ || Unmap() == MAPPED_FILE_ERR_OK) { + offset_ = val; + isNormed_ = false; + + return true; + } else { + UTILS_LOGW("%{public}s: Change params failed. Unmapping failed.", __FUNCTION__); + } + } + return false; +} + +bool MappedFile::ChangeSize(off_t val) +{ + if ((val > 0 || val == DEFAULT_LENGTH) && size_ != val) { + if (!isMapped_ || Unmap() == MAPPED_FILE_ERR_OK) { + size_ = val; + isNormed_ = false; + + return true; + } else { + UTILS_LOGW("%{public}s: Change params failed. Unmapping failed.", __FUNCTION__); + } + } + return false; +} + +bool MappedFile::ChangePath(const std::string& val) +{ + if (path_ != val) { + if (!isMapped_ || Unmap() == MAPPED_FILE_ERR_OK) { + if (fd_ != -1 && close(fd_) == -1) { + UTILS_LOGW("%{public}s: Failed. Cannot close the file: %{public}s.", \ + __FUNCTION__, strerror(errno)); + return false; + } + path_ = val; + isNormed_ = false; + fd_ = -1; + + return true; + } else { + UTILS_LOGW("%{public}s: Change params failed. Unmapping failed.", __FUNCTION__); + } + } + return false; +} + +bool MappedFile::ChangeHint(const char* val) +{ + if (hint_ != val) { + if (!isMapped_ || Unmap() == MAPPED_FILE_ERR_OK) { + hint_ = val; + isNormed_ = false; + + return true; + } else { + UTILS_LOGW("%{public}s: Change params failed. Unmapping failed.", __FUNCTION__); + } + } + return false; +} + +bool MappedFile::ChangeMode(MapMode val) +{ + if (mode_ != val) { + if (!isMapped_ || Unmap() == MAPPED_FILE_ERR_OK) { + mode_ = val; + isNormed_ = false; + + return true; + } else { + UTILS_LOGW("%{public}s: Change params failed. Unmapping failed.", __FUNCTION__); + } + } + return false; +} + +} // namespace Utils +} // namespace OHOS \ No newline at end of file diff --git a/base/test/unittest/common/BUILD.gn b/base/test/unittest/common/BUILD.gn index 2c88b39..852ed7e 100644 --- a/base/test/unittest/common/BUILD.gn +++ b/base/test/unittest/common/BUILD.gn @@ -132,6 +132,19 @@ ohos_unittest("UtilsFileTest") { ] } +##############################unittest########################################## +ohos_unittest("UtilsMappedFileTest") { + module_out_path = module_output_path + sources = [ "utils_mapped_file_test.cpp" ] + + configs = [ ":module_private_config" ] + + deps = [ + "//commonlibrary/c_utils/base:utils", + "//third_party/googletest:gtest_main", + ] +} + ##############################unittest########################################## ohos_unittest("UtilsObserverTest") { module_out_path = module_output_path @@ -304,6 +317,7 @@ group("unittest") { ":UtilsDateTimeTest", ":UtilsDirectoryTest", ":UtilsFileTest", + ":UtilsMappedFileTest", ":UtilsObserverTest", ":UtilsParcelTest", ":UtilsRWLockTest", diff --git a/base/test/unittest/common/utils_mapped_file_test.cpp b/base/test/unittest/common/utils_mapped_file_test.cpp new file mode 100644 index 0000000..8974133 --- /dev/null +++ b/base/test/unittest/common/utils_mapped_file_test.cpp @@ -0,0 +1,1301 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * 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 + * + * http://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 +#include +#include +#include +#include +#include "errors.h" +#include "file_ex.h" +#include "directory_ex.h" +#include "common_mapped_file_errors.h" +#include "mapped_file.h" + +using namespace testing::ext; +using namespace OHOS::Utils; + +namespace OHOS { +namespace { + +class UtilsMappedFileTest : public testing::Test { +public: + static constexpr char BASE_PATH[] = "/data/test/commonlibrary_c_utils/"; + static constexpr char SUITE_PATH[] = "mapped_file/"; + static void SetUpTestCase(void); + static void TearDownTestCase(void); +}; + +void UtilsMappedFileTest::SetUpTestCase() +{ + std::string dir = std::string(BASE_PATH).append(SUITE_PATH); + if (ForceCreateDirectory(dir)) { + std::cout << "Create test dir:" << dir.c_str() << std::endl; + } else { + std::cout << "Create test dir Failed:" << dir.c_str() << std::endl; + } + + std::cout << "Page size:" << MappedFile::PageSize() << std::endl; +} + +void UtilsMappedFileTest::TearDownTestCase() +{ + if (ForceRemoveDirectory(std::string(BASE_PATH))) { + std::cout << "Remove test dir:" << BASE_PATH << std::endl; + } +} + +void PrintStatus(MappedFile& mf) +{ + std::cout << "Mapped Region Start:" << reinterpret_cast(mf.RegionStart()) << std::endl << + "Mapped Region End:" << reinterpret_cast(mf.RegionEnd()) << std::endl << + "View start:" << reinterpret_cast(mf.Begin()) << std::endl << + "View End:" << reinterpret_cast(mf.End()) << std::endl << + "View Size:" << mf.Size() << std::endl << + "File Offset Start:" << mf.StartOffset() << std::endl << + "File Offset Start:" << mf.EndOffset() << std::endl; +} + +bool CreateTestFile(const std::string& path, const std::string& content) +{ + std::ofstream out(path, std::ios_base::out | std::ios_base::trunc); + if (out.is_open()) { + out << content.c_str(); + return true; + } + + std::cout << "open file failed!" << path.c_str() << std::endl; + return false; +} + +int RemoveTestFile(const std::string& path) +{ + return unlink(path.c_str()); +} + +bool SaveStringToFile(const std::string& filePath, const std::string& content, off_t offset, bool truncated /*= true*/) +{ + if (content.empty()) { + return true; + } + + std::ofstream file; + if (truncated) { + file.open(filePath.c_str(), std::ios::out | std::ios::trunc); + } else { + file.open(filePath.c_str(), std::ios::out | std::ios::app); + } + + if (!file.is_open()) { + return false; + } + + file.seekp(offset, std::ios::beg); + + file.write(content.c_str(), content.length()); + if (file.fail()) { + return false; + } + return true; +} + +/* + * @tc.name: testDefaultMapping001 + * @tc.desc: Test file mapping with default params. + */ +HWTEST_F(UtilsMappedFileTest, testDefaultMapping001, TestSize.Level0) +{ + // 1. Create a new file + std::string filename = "test_read_write_1.txt"; + std::string content = "Test for normal use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + MappedFile mf(filename); + + // check status + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + // check size + struct stat stb = {0}; + stat(filename.c_str(), &stb); + ASSERT_TRUE(stb.st_size == mf.Size() || mf.PageSize() == mf.Size()); + + // check map-mode + ASSERT_EQ(MapMode::DEFAULT, mf.GetMode()); + + // check offset + ASSERT_EQ(mf.StartOffset(), 0u); + + // 3. read from mapped file + std::string readout; + char* cur = mf.Begin(); + for (; cur <= mf.End(); cur++) { + readout.push_back(*cur); + } + EXPECT_EQ(readout, content); + + // 4. write to mapped file + std::string toWrite("Complete."); + char* newCur = mf.Begin(); + for (std::string::size_type i = 0; i < toWrite.length(); i++) { + (*newCur) = toWrite[i]; + newCur++; + } + std::string res; + LoadStringFromFile(filename, res); + EXPECT_EQ(res, "Complete.normal use."); + + // 5. test default mapping and write to addr which excess End() but not across this memory page. + EXPECT_LE(mf.Size(), mf.PageSize()); + char* trueEnd = mf.RegionEnd(); + ASSERT_GT(trueEnd, mf.Begin()); + // write to mapped file + (*trueEnd) = 'E'; // It is allowed to write to this address which excess the End() + + EXPECT_EQ((*trueEnd), 'E'); // and of course it is allowed to read from that same address. + + std::string res1; + LoadStringFromFile(filename, res1); + EXPECT_EQ(res1, "Complete.normal use."); // While no changes will be sync in the original file. + + RemoveTestFile(filename); +} + +/* + * @tc.name: testNewSharedMappingDefaultSize001 + * @tc.desc: Test mapping which will create a new file with default size. + */ +HWTEST_F(UtilsMappedFileTest, testNewSharedMappingDefaultSize001, TestSize.Level0) +{ + // 1. Create a new file + std::string filename = "test_read_write_2.txt"; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + // 2. map file + MappedFile mf(filename, MapMode::DEFAULT | MapMode::CREATE_IF_ABSENT); + + // check if file is created + ASSERT_TRUE(FileExists(filename)); + + // check status + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + // check map-mode + ASSERT_EQ(MapMode::DEFAULT | MapMode::CREATE_IF_ABSENT, mf.GetMode()); + + // check default size + struct stat stb = {0}; + if (stat(filename.c_str(), &stb) == 0) { + EXPECT_EQ(stb.st_size, mf.PageSize()); // contents will be zero-filled. + } + ASSERT_EQ(mf.Size(), mf.PageSize()); + + // 3. write to mapped file + std::string toWrite("Write to newly created file."); + char* newCur = mf.Begin(); + for (std::string::size_type i = 0; i < toWrite.length(); i++) { + (*newCur) = toWrite[i]; + newCur++; + } + std::string res; + LoadStringFromFile(filename, res); + EXPECT_STREQ(res.c_str(), toWrite.c_str()); // note that `res` contains filled '0', + // use c_str() to compare conveniently. + + // 4. read from mapped file + std::string toRead("Waiting to be read."); + SaveStringToFile(filename, toRead, 0, true); + std::string readout; + char* cur = mf.Begin(); + for (; *cur != '\0'; cur++) { + readout.push_back(*cur); + } + EXPECT_EQ(readout, toRead); + + RemoveTestFile(filename); +} + +/* + * @tc.name: testNewSharedMapping001 + * @tc.desc: Test mapping which will create a new file with specified params. + */ +HWTEST_F(UtilsMappedFileTest, testNewSharedMapping001, TestSize.Level0) +{ + std::string filename = "test_read_write_3.txt"; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + // set params + char* hint = reinterpret_cast(0x80000); // new mapping region will not guaranteed to be located at `hint` + off_t size = 1024; + off_t offset = 4 * 1024; + + // 1. map a non-existed file + MappedFile mf(filename, MapMode::DEFAULT | MapMode::CREATE_IF_ABSENT, offset, size, hint); + + // check if file is created + ASSERT_TRUE(FileExists(filename)); + + // check status + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + // check specified size + struct stat stb = {0}; + if (stat(filename.c_str(), &stb) == 0) { + EXPECT_EQ(stb.st_size, offset + size); // Exact file size should be offset + mapped size, contents will be + // zero-filled. + } + ASSERT_EQ(mf.Size(), size); + + // check specified offset + ASSERT_EQ(mf.StartOffset(), offset); + + // check hint + ASSERT_TRUE(mf.GetHint() == nullptr || mf.GetHint() == hint); + std::cout << "Exact addr:" << + reinterpret_cast(mf.Begin()) << std::endl << + "Input hint:" << reinterpret_cast(hint) << std::endl; + + // 2. write to mapped file + std::string toWrite("Write to newly created file."); + char* newCur = mf.Begin(); + for (std::string::size_type i = 0; i < toWrite.length(); i++) { + (*newCur) = toWrite[i]; + newCur++; + } + std::cout << "Write finished" << std::endl; + EXPECT_TRUE(StringExistsInFile(filename, toWrite)); + + // 3. read from mapped file + std::string toRead("Waiting to be read."); + SaveStringToFile(filename, toRead, offset, true); + std::string readout; + char* cur = mf.Begin(); + for (; cur <= mf.End() && *cur != '\0'; cur++) { + readout.push_back(*cur); + } + std::cout << "Read finished" << std::endl; + EXPECT_EQ(readout, toRead); + + RemoveTestFile(filename); +} + +/* + * @tc.name: testPrivateMapping001 + * @tc.desc: Test mapping which will create a new file with specified params. + */ +HWTEST_F(UtilsMappedFileTest, testPrivateMapping001, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_read_write_4.txt"; + std::string content = "Test for private use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + MappedFile mf(filename, MapMode::DEFAULT | MapMode::PRIVATE); + + // 3. check status + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + // 4. read from mapped file + std::string readout; + char* cur = mf.Begin(); + for (; cur <= mf.End(); cur++) { + readout.push_back(*cur); + } + EXPECT_EQ(readout, content); + + // 5. write to mapped file + std::string toWrite("Complete."); + char* newCur = mf.Begin(); + for (std::string::size_type i = 0; i < toWrite.length(); i++) { + (*newCur) = toWrite[i]; + newCur++; + } + std::string res; + LoadStringFromFile(filename, res); + EXPECT_EQ(res, content); // changes to private mapped file will not write back to the original file + + RemoveTestFile(filename); +} + +/* + * @tc.name: testSharedReadOnlyMapping001 + * @tc.desc: Test mapping which will create a new file with specified params. + */ +HWTEST_F(UtilsMappedFileTest, testSharedReadOnlyMapping001, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_read_write_5.txt"; + std::string content = "Test for readonly use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + MappedFile mf(filename, MapMode::DEFAULT | MapMode::READ_ONLY); + + // 3. check status + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + // 4. read from mapped file + std::string readout; + char* cur = mf.Begin(); + for (; cur <= mf.End(); cur++) { + readout.push_back(*cur); + } + EXPECT_EQ(readout, content); + // !Note: write operation is not permitted, which will raise a signal 11. + + RemoveTestFile(filename); +} + +/* + * @tc.name: testReMap001 + * @tc.desc: Test remapping using `Unmap()` and `Map()` + */ +HWTEST_F(UtilsMappedFileTest, testReMap001, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_remap_1.txt"; + std::string content = "Test for remapping use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + MappedFile mf(filename); + + // 3. check status after mapping + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + ASSERT_EQ(mf.Unmap(), MAPPED_FILE_ERR_OK); + + // 4. check status after unmapping + EXPECT_FALSE(mf.IsMapped()); + EXPECT_TRUE(mf.IsNormed()); + EXPECT_EQ(mf.Begin(), nullptr); + + ASSERT_EQ(mf.Map(), MAPPED_FILE_ERR_OK); + // 5. check status after remapping + EXPECT_TRUE(mf.IsMapped()); + EXPECT_TRUE(mf.IsNormed()); + + // 6. check default size + struct stat stb = {0}; + stat(filename.c_str(), &stb); + EXPECT_TRUE(stb.st_size == mf.Size() || mf.PageSize() == mf.Size()); + + + RemoveTestFile(filename); +} + +/* + * @tc.name: testReMap002 + * @tc.desc: Test remapping via changing params. + */ +HWTEST_F(UtilsMappedFileTest, testReMap002, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_remap.txt"; + std::string content = "Test for default use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + std::string filename1 = "test_remap_1.txt"; + std::string content1 = "Test for remapping use."; + filename1.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename1); + + ASSERT_TRUE(CreateTestFile(filename, content)); + ASSERT_TRUE(CreateTestFile(filename1, content1)); + + // 2. map file + MappedFile mf(filename); + + // 3. check status after mapping + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + // 4. check size + struct stat stb = {0}; + stat(filename.c_str(), &stb); + ASSERT_TRUE(stb.st_size == mf.Size() || mf.PageSize() == mf.Size()); + + // 5. read from Mapped File + std::string readout; + char* cur = mf.Begin(); + for (; cur <= mf.End(); cur++) { + readout.push_back(*cur); + } + EXPECT_EQ(readout, content); + + // 6. change params + mf.ChangePath(filename1); + mf.ChangeSize(MappedFile::DEFAULT_LENGTH); + + // 7. check status after changing + EXPECT_FALSE(mf.IsMapped()); + EXPECT_FALSE(mf.IsNormed()); + + // 8. remap file + ASSERT_EQ(mf.Map(), MAPPED_FILE_ERR_OK); + // 9. check status after remapping + EXPECT_TRUE(mf.IsMapped()); + EXPECT_TRUE(mf.IsNormed()); + + // 10. check size + stat(filename1.c_str(), &stb); + EXPECT_TRUE(stb.st_size == mf.Size()); + + // 11. read from Mapped File + std::string readout1; + char* cur1 = mf.Begin(); + for (; cur1 <= mf.End(); cur1++) { + readout1.push_back(*cur1); + } + EXPECT_EQ(readout1, content1); + + RemoveTestFile(filename); + RemoveTestFile(filename1); +} + +/* + * @tc.name: testReMap003 + * @tc.desc: Test remapping via Resize(). + */ +HWTEST_F(UtilsMappedFileTest, testReMap003, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_remap.txt"; + std::string content = "Test for default use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + std::string filename1 = "test_remap_1.txt"; + std::string content1 = "Test for remapping use."; + filename1.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename1); + + ASSERT_TRUE(CreateTestFile(filename, content)); + ASSERT_TRUE(CreateTestFile(filename1, content1)); + + // 2. map file + MappedFile mf(filename); + + // 3. check status after mapping + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + // 4. check size + struct stat stb = {0}; + stat(filename.c_str(), &stb); + ASSERT_TRUE(stb.st_size == mf.Size() || mf.PageSize() == mf.Size()); + + // 5. read from Mapped File + std::string readout; + char* cur = mf.Begin(); + for (; cur <= mf.End(); cur++) { + readout.push_back(*cur); + } + EXPECT_EQ(readout, content); + + // 6. change params + mf.ChangePath(filename1); + mf.ChangeSize(MappedFile::DEFAULT_LENGTH); + + // 7. check status after changing + EXPECT_FALSE(mf.IsMapped()); + EXPECT_FALSE(mf.IsNormed()); + + // 8. remap file + ASSERT_EQ(mf.Resize(), MAPPED_FILE_ERR_OK); + // 9. check status after remapping + EXPECT_TRUE(mf.IsMapped()); + EXPECT_TRUE(mf.IsNormed()); + + // 10. check size + stat(filename1.c_str(), &stb); + EXPECT_TRUE(stb.st_size == mf.Size()); + + // 11. read from Mapped File + std::string readout1; + char* cur1 = mf.Begin(); + for (; cur1 <= mf.End(); cur1++) { + readout1.push_back(*cur1); + } + EXPECT_EQ(readout1, content1); + + RemoveTestFile(filename); + RemoveTestFile(filename1); +} + +/* + * @tc.name: testReMap004 + * @tc.desc: Test remapping only to extend mapped region via Resize(off_t, bool). + */ +HWTEST_F(UtilsMappedFileTest, testReMap004, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_remap.txt"; + std::string content = "Test for remapping use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + MappedFile mf(filename); + + // 3. check status after mapping + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + // 4. check size + struct stat stb = {0}; + stat(filename.c_str(), &stb); + ASSERT_TRUE(stb.st_size == mf.Size() || mf.PageSize() == mf.Size()); + + // 5. read from Mapped File + std::string readout; + char* cur = mf.Begin(); + for (; cur <= mf.End(); cur++) { + readout.push_back(*cur); + } + EXPECT_EQ(readout, content); + + // 6. Remap to extend region + ASSERT_EQ(mf.Resize(mf.Size() + 10), MAPPED_FILE_ERR_OK); + // 7. check status after remapping + EXPECT_TRUE(mf.IsMapped()); + EXPECT_TRUE(mf.IsNormed()); + + // 8. check size after remapping + stat(filename.c_str(), &stb); + EXPECT_TRUE(stb.st_size < mf.Size()); + + // 9. write to the extended region + *(cur) = 'E'; + EXPECT_EQ((*cur), 'E'); + + std::string res; + LoadStringFromFile(filename, res); + EXPECT_EQ(res, content); // No changes will be sync in the original file, since mapped region + // is larger than substantial size of the file + + RemoveTestFile(filename); +} + +/* + * @tc.name: testReMap005 + * @tc.desc: Test remapping to extend mapped region as well as substantial file size via Resize(off_t, bool). + */ +HWTEST_F(UtilsMappedFileTest, testReMap005, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_remap.txt"; + std::string content = "Test for remapping use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + MappedFile mf(filename); + + // 3. check status after mapping + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + // 4. check size + struct stat stb = {0}; + stat(filename.c_str(), &stb); + ASSERT_TRUE(stb.st_size == mf.Size() || mf.PageSize() == mf.Size()); + + // 5. read from Mapped File + std::string readout; + char* cur = mf.Begin(); + for (; cur <= mf.End(); cur++) { + readout.push_back(*cur); + } + EXPECT_EQ(readout, content); + + // 6. remap to extend region + ASSERT_EQ(mf.Resize(mf.Size() + 10, true), MAPPED_FILE_ERR_OK); + // check status after remapping + EXPECT_TRUE(mf.IsMapped()); + EXPECT_TRUE(mf.IsNormed()); + + // 7. check size after remapping + stat(filename.c_str(), &stb); + EXPECT_TRUE(stb.st_size == mf.Size()); // File size will sync to that of the mapped region. + + // 8. write to the extended region + *(cur) = 'E'; + EXPECT_EQ((*cur), 'E'); + + std::string res; + LoadStringFromFile(filename, res); + EXPECT_STREQ(res.c_str(), content.append("E").c_str()); // Changes will be sync in the original file. +} + +/* + * @tc.name: testTurnNext001 + * @tc.desc: Test TurnNext() when `IsMapped()`. + */ +HWTEST_F(UtilsMappedFileTest, testTurnNext001, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_remap.txt"; + std::string content = "Test for remapping use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + struct stat stb = {0}; + ASSERT_EQ(stat(filename.c_str(), &stb), 0); + off_t orig = stb.st_size; // 25 bit + + // 2. extend its size + int fd = open(filename.c_str(), O_RDWR | O_CLOEXEC); + ASSERT_NE(fd, -1); + ASSERT_EQ(ftruncate(fd, MappedFile::PageSize() + MappedFile::PageSize() / 100LL), 0); + + // 3. map file + MappedFile mf(filename, MapMode::DEFAULT, 0, orig); + + // 4. check status after mapping + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + // 5. turn next mapped region with the same size as the file's initial size. + EXPECT_EQ(mf.TurnNext(), MAPPED_FILE_ERR_OK); + char* cur = mf.Begin(); + *cur = 'N'; + + std::string res; + LoadStringFromFile(filename, res); + EXPECT_STREQ(res.c_str(), content.append("N").c_str()); + + off_t endOff; + // 6. keep turnNext within a page + for (unsigned int cnt = 2; cnt < (MappedFile::PageSize() / orig); cnt++) { + endOff = mf.EndOffset(); + EXPECT_EQ(mf.TurnNext(), MAPPED_FILE_ERR_OK); + EXPECT_EQ(mf.StartOffset(), endOff + 1); + EXPECT_EQ(mf.Size(), orig); + } + std::cout << "==Last TurnNext() with The Same Size==" << std::endl; + PrintStatus(mf); + + // 7. this turn will reach the bottom of a page + endOff = mf.EndOffset(); + char* rEnd = mf.RegionEnd(); + char* end = mf.End(); + EXPECT_EQ(mf.TurnNext(), MAPPED_FILE_ERR_OK); + EXPECT_EQ(mf.StartOffset(), endOff + 1); + EXPECT_EQ(mf.Size(), static_cast(rEnd - end)); + std::cout << "==Reached Bottom of A Page==" << std::endl; + PrintStatus(mf); + + // 8. this turn will trigger a remapping + endOff = mf.EndOffset(); + off_t curSize = mf.Size(); + EXPECT_EQ(mf.TurnNext(), MAPPED_FILE_ERR_OK); + EXPECT_TRUE(mf.IsMapped()); + EXPECT_EQ(mf.StartOffset(), endOff + 1); + EXPECT_EQ(mf.Size(), curSize); + EXPECT_EQ(mf.RegionStart(), mf.Begin()); + EXPECT_EQ(static_cast(mf.RegionEnd() - mf.RegionStart()) + 1LL, mf.PageSize()); + std::cout << "==Remap A New Page==" << std::endl; + PrintStatus(mf); + + // 9. keep turnNext within a page + for (off_t cnt = 1; cnt < (MappedFile::PageSize() / 100LL / curSize); cnt++) { + endOff = mf.EndOffset(); + EXPECT_EQ(mf.TurnNext(), MAPPED_FILE_ERR_OK); + EXPECT_EQ(mf.StartOffset(), endOff + 1); + EXPECT_EQ(mf.Size(), curSize); + } + + // 10. this turn will fail since no place remained. + EXPECT_NE(mf.TurnNext(), MAPPED_FILE_ERR_OK); + + RemoveTestFile(filename); +} + +/* + * @tc.name: testTurnNext002 + * @tc.desc: Test TurnNext() when `!IsMapped()`. + */ +HWTEST_F(UtilsMappedFileTest, testTurnNext002, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_remap.txt"; + std::string content = "Test for remapping use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + MappedFile mf(filename); + off_t curSize = mf.Size(); + off_t curOff = mf.StartOffset(); + + // 3. check status after mapping + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + // 4. recommand to unmap first before other operations on the file. + ASSERT_EQ(mf.Unmap(), MAPPED_FILE_ERR_OK); + // 5. enlarge file size to make it possible to `turnNext()`. + ASSERT_EQ(ftruncate(mf.GetFd(), MappedFile::PageSize() + MappedFile::PageSize() / 100LL), 0); + // 6. turn next page of `PageSize()` and keep the same `size_` + EXPECT_EQ(mf.TurnNext(), MAPPED_FILE_ERR_OK); + EXPECT_EQ(mf.Size(), curSize); + EXPECT_EQ(static_cast(mf.StartOffset()), curOff + mf.PageSize()); + + RemoveTestFile(filename); +} + +/* + * @tc.name: testTurnNext003 + * @tc.desc: Test TurnNext() (using internal fd to `ftruncate()`). + */ +HWTEST_F(UtilsMappedFileTest, testTurnNext003, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_remap.txt"; + std::string content = "Test for remapping use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + MappedFile mf(filename); + + // 3. check status after mapping + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + // 4. recommand to unmap first before other operations on the file. + ASSERT_EQ(mf.Unmap(), MAPPED_FILE_ERR_OK); + // 5. enlarge file size to make it possible to `turnNext()`. + ASSERT_EQ(ftruncate(mf.GetFd(), MappedFile::PageSize() + MappedFile::PageSize() / 100LL), 0); + + // 6. remap + ASSERT_EQ(mf.Map(), MAPPED_FILE_ERR_OK); + + // 7. turn next mapped region with the same size as the file's initial size. + ASSERT_EQ(mf.TurnNext(), MAPPED_FILE_ERR_OK); + char* cur = mf.Begin(); + *cur = 'N'; + + std::string res; + LoadStringFromFile(filename, res); + EXPECT_STREQ(res.c_str(), content.append("N").c_str()); + + RemoveTestFile(filename); +} + +/* + * @tc.name: testTurnNext004 + * @tc.desc: Test TurnNext() failed. + */ +HWTEST_F(UtilsMappedFileTest, testTurnNext004, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_remap.txt"; + std::string content = "Test for remapping use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + MappedFile mf(filename); + + // 3. check status after mapping + ASSERT_TRUE(mf.IsMapped()); + ASSERT_TRUE(mf.IsNormed()); + + // 4. turn next mapped region with the same size as the file's initial size. + EXPECT_EQ(mf.TurnNext(), ERR_INVALID_OPERATION); + + RemoveTestFile(filename); +} + +/* + * @tc.name: testInvalidMap001 + * @tc.desc: Test file mapping with invalid offset. + */ +HWTEST_F(UtilsMappedFileTest, testInvalidMap001, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_invalid_1.txt"; + std::string content = "Test for invalid use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + off_t offset = 100; // Specify offset that is not multiple of page-size. + MappedFile mf(filename, MapMode::DEFAULT, offset); + + // 3. check status + EXPECT_FALSE(mf.IsMapped()); + EXPECT_FALSE(mf.IsNormed()); // mapping will fail in normalize stage. + + RemoveTestFile(filename); +} + +/* + * @tc.name: testInvalidMap002 + * @tc.desc: Test file mapping with invalid offset excessing the substantial size of the file. + */ +HWTEST_F(UtilsMappedFileTest, testInvalidMap002, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_invalid_2.txt"; + std::string content = "Test for invalid use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + off_t offset = 4 * 1024; // Specify offset excessing the substantial size of the file. + MappedFile mf(filename, MapMode::DEFAULT, offset); + + // 3. check status + EXPECT_FALSE(mf.IsMapped()); + EXPECT_FALSE(mf.IsNormed()); // mapping will fail in normalize stage. + + RemoveTestFile(filename); +} + +/* + * @tc.name: testInvalidMap003 + * @tc.desc: Test mapping non-existed file without setting CREAT_IF_ABSENT. + */ +HWTEST_F(UtilsMappedFileTest, testInvalidMap003, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_invalid_3.txt"; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + // 2. map file + MappedFile mf(filename); + + // 3. check status + EXPECT_FALSE(mf.IsMapped()); + EXPECT_FALSE(mf.IsNormed()); // mapping will fail in normalize stage. + + RemoveTestFile(filename); +} + +/* + * @tc.name: testAutoAdjustedMode001 + * @tc.desc: Test mapping file with invalid mapping mode, but can be auto adjusted. + */ +HWTEST_F(UtilsMappedFileTest, testAutoAdjustedMode001, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_adjmod_1.txt"; + std::string content = "Test for auto adj use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + MapMode mode = static_cast(1) | static_cast(16) | + MapMode::PRIVATE | MapMode::READ_ONLY; // bits out of the scope will be ignored. + MappedFile mf(filename, mode); + + // 3. check status + EXPECT_TRUE(mf.IsMapped()); + EXPECT_TRUE(mf.IsNormed()); + + // 4. check map-mode + ASSERT_EQ(MapMode::PRIVATE | MapMode::READ_ONLY, mf.GetMode()); + + RemoveTestFile(filename); +} + +/* + * @tc.name: testAutoAdjustedSize001 + * @tc.desc: Test file mapping with size excessing the last page of the file. + */ +HWTEST_F(UtilsMappedFileTest, testAutoAdjustedSize001, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_adjsize_1.txt"; + std::string content = "Test for auto adj use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + off_t size = 5 * 1024; // Specified size excessing the last page of the file. + MappedFile mf(filename, MapMode::DEFAULT, 0, size); + + // 3. check status + EXPECT_TRUE(mf.IsMapped()); + EXPECT_TRUE(mf.IsNormed()); + + // 4. check size + struct stat stb = {0}; + stat(filename.c_str(), &stb); + off_t max = (stb.st_size / mf.PageSize() + 1LL) * mf.PageSize() - 0LL; + EXPECT_EQ(mf.Size(), max); // Size will be automatically adjusted, due to safe-concern. + + RemoveTestFile(filename); +} + +/* + * @tc.name: testAutoAdjustedSize002 + * @tc.desc: Test file mapping with size excessing the last page of the file. + */ +HWTEST_F(UtilsMappedFileTest, testAutoAdjustedSize002, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_adjsize_2.txt"; + std::string content = "Test for auto adj use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. Extend size manually + int fd = open(filename.c_str(), O_RDWR | O_CLOEXEC); + if (fd != -1) { + std::cout << "open success." << std::endl; + ftruncate(fd, 7 * 1024); + + // 3. map file + off_t offset = 4 * 1024; + off_t size = 5 * 1024; // Specified size excessing the last page of the file. + MappedFile mf(filename, MapMode::DEFAULT, offset, size); + + // 4. check status + EXPECT_TRUE(mf.IsMapped()); + EXPECT_TRUE(mf.IsNormed()); + + // 5. check size + struct stat stb = {0}; + stat(filename.c_str(), &stb); + off_t max = (stb.st_size / mf.PageSize() + 1LL) * mf.PageSize() - offset; + EXPECT_EQ(mf.Size(), max); // Size will be automatically adjusted, due to safe-concern. + + close(fd); + } + + RemoveTestFile(filename); +} + +/* + * @tc.name: testMoveMappedFile001 + * @tc.desc: Test move constructor. + */ +HWTEST_F(UtilsMappedFileTest, testMoveMappedFile001, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_move_1.txt"; + std::string content = "Test for move use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + MappedFile mf(filename); + + off_t size = mf.Size(); + off_t offset = mf.StartOffset(); + char* data = mf.Begin(); + MapMode mode = mf.GetMode(); + const char* hint = mf.GetHint(); + + // 3. move to a new object + MappedFile mfNew(std::move(mf)); + + // 4. check status and params after move + EXPECT_FALSE(mf.IsMapped()); + EXPECT_FALSE(mf.IsNormed()); + EXPECT_EQ(mf.Begin(), nullptr); + EXPECT_EQ(mf.Size(), MappedFile::DEFAULT_LENGTH); + EXPECT_EQ(mf.StartOffset(), 0); + EXPECT_EQ(mf.GetMode(), MapMode::DEFAULT); + EXPECT_EQ(mf.GetHint(), nullptr); + EXPECT_EQ(mf.GetPath(), ""); + + EXPECT_TRUE(mfNew.IsMapped()); + EXPECT_TRUE(mfNew.IsNormed()); + EXPECT_EQ(mfNew.Begin(), data); + EXPECT_EQ(mfNew.Size(), size); + EXPECT_EQ(mfNew.StartOffset(), offset); + EXPECT_EQ(mfNew.GetMode(), mode); + EXPECT_EQ(mfNew.GetHint(), hint); + EXPECT_EQ(mfNew.GetPath(), filename); + + // 5. read from mapped file + std::string readout; + char* cur = mfNew.Begin(); + for (; cur <= mfNew.End(); cur++) { + readout.push_back(*cur); + } + EXPECT_EQ(readout, content); + + // 6. write to mapped file + std::string toWrite("Complete."); + char* newCur = mfNew.Begin(); + for (std::string::size_type i = 0; i < toWrite.length(); i++) { + (*newCur) = toWrite[i]; + newCur++; + } + std::string res; + LoadStringFromFile(filename, res); + EXPECT_EQ(res, "Complete.move use."); + + RemoveTestFile(filename); +} + +/* + * @tc.name: testMoveMappedFile002 + * @tc.desc: Test move constructor with ummapped region. + */ +HWTEST_F(UtilsMappedFileTest, testMoveMappedFile002, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_move_2.txt"; + std::string content = "Test for move use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + ASSERT_TRUE(CreateTestFile(filename, content)); + + // 2. map file + MappedFile mf(filename); + + off_t size = mf.Size(); + off_t offset = mf.StartOffset(); + MapMode mode = mf.GetMode(); + const char* hint = mf.GetHint(); + + ASSERT_EQ(mf.Unmap(), MAPPED_FILE_ERR_OK); + // 3. move to a new object + MappedFile mfNew(std::move(mf)); + + // 4. check status and params after move + EXPECT_FALSE(mf.IsMapped()); + EXPECT_FALSE(mf.IsNormed()); + EXPECT_EQ(mf.Begin(), nullptr); + EXPECT_EQ(mf.Size(), MappedFile::DEFAULT_LENGTH); + EXPECT_EQ(mf.StartOffset(), 0); + EXPECT_EQ(mf.GetMode(), MapMode::DEFAULT); + EXPECT_EQ(mf.GetHint(), nullptr); + EXPECT_EQ(mf.GetPath(), ""); + + EXPECT_FALSE(mfNew.IsMapped()); + EXPECT_TRUE(mfNew.IsNormed()); + EXPECT_EQ(mfNew.Begin(), nullptr); + EXPECT_EQ(mfNew.Size(), size); + EXPECT_EQ(mfNew.StartOffset(), offset); + EXPECT_EQ(mfNew.GetMode(), mode); + EXPECT_EQ(mfNew.GetHint(), hint); + EXPECT_EQ(mfNew.GetPath(), filename); + + // 5. Map again + ASSERT_EQ(mfNew.Map(), MAPPED_FILE_ERR_OK); + // 6. read from mapped file + std::string readout; + char* cur = mfNew.Begin(); + for (; cur <= mfNew.End(); cur++) { + readout.push_back(*cur); + } + EXPECT_EQ(readout, content); + + // 7. write to mapped file + std::string toWrite("Complete."); + char* newCur = mfNew.Begin(); + for (std::string::size_type i = 0; i < toWrite.length(); i++) { + (*newCur) = toWrite[i]; + newCur++; + } + std::string res; + LoadStringFromFile(filename, res); + EXPECT_EQ(res, "Complete.move use."); + + RemoveTestFile(filename); +} + +/* + * @tc.name: testMoveMappedFile003 + * @tc.desc: Test move assignment operator overload. + */ +HWTEST_F(UtilsMappedFileTest, testMoveMappedFile003, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_move_3.txt"; + std::string content = "Test for move use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + std::string filename1 = "test_move_4.txt"; + std::string content1 = "Test for move use."; + filename1.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename1); + + ASSERT_TRUE(CreateTestFile(filename, content)); + ASSERT_TRUE(CreateTestFile(filename1, content1)); + + // 2. map file + MappedFile mf(filename); + MappedFile mf1(filename1); + + off_t size = mf1.Size(); + off_t offset = mf1.StartOffset(); + MapMode mode = mf1.GetMode(); + char* data = mf1.Begin(); + const char* hint = mf1.GetHint(); + + // 3. move assignment + mf = std::move(mf1); + + // 4. check status and params after move + EXPECT_TRUE(mf.IsMapped()); + EXPECT_TRUE(mf.IsNormed()); + EXPECT_EQ(mf.Begin(), data); + EXPECT_EQ(mf.Size(), size); + EXPECT_EQ(mf.StartOffset(), offset); + EXPECT_EQ(mf.GetMode(), mode); + EXPECT_EQ(mf.GetHint(), hint); + EXPECT_EQ(mf.GetPath(), filename1); + + // 5. read from mapped file + std::string readout; + char* cur = mf.Begin(); + for (; cur <= mf.End(); cur++) { + readout.push_back(*cur); + } + EXPECT_EQ(readout, content1); + + // 6. write to mapped file + std::string toWrite("Complete."); + char* newCur = mf.Begin(); + for (std::string::size_type i = 0; i < toWrite.length(); i++) { + (*newCur) = toWrite[i]; + newCur++; + } + std::string res; + LoadStringFromFile(filename1, res); + EXPECT_EQ(res, "Complete.move use."); + + RemoveTestFile(filename); + RemoveTestFile(filename1); +} + +/* + * @tc.name: testMoveMappedFile004 + * @tc.desc: Test move assignment operator overload with ummapped region. + */ +HWTEST_F(UtilsMappedFileTest, testMoveMappedFile004, TestSize.Level0) +{ + // 1. create a new file + std::string filename = "test_move_4.txt"; + std::string content = "Test for move use."; + filename.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename); + + std::string filename1 = "test_move_5.txt"; + std::string content1 = "Test for move use."; + filename1.insert(0, SUITE_PATH).insert(0, BASE_PATH); + RemoveTestFile(filename1); + + ASSERT_TRUE(CreateTestFile(filename, content)); + ASSERT_TRUE(CreateTestFile(filename1, content1)); + + // 2. map file + MappedFile mf(filename); + MappedFile mf1(filename1); + + off_t size = mf1.Size(); + off_t offset = mf1.StartOffset(); + MapMode mode = mf1.GetMode(); + const char* hint = mf1.GetHint(); + + // 3. ummap mf1 + ASSERT_EQ(mf1.Unmap(), MAPPED_FILE_ERR_OK); + // 4. move assignment + mf = std::move(mf1); + // 5. check status and params after move + EXPECT_FALSE(mf.IsMapped()); + EXPECT_TRUE(mf.IsNormed()); + EXPECT_EQ(mf.Begin(), nullptr); // since mf1 is unmapped, its `data_` are set to `nullptr` + EXPECT_EQ(mf.Size(), size); + EXPECT_EQ(mf.StartOffset(), offset); + EXPECT_EQ(mf.GetMode(), mode); + EXPECT_EQ(mf.GetHint(), hint); + EXPECT_EQ(mf.GetPath(), filename1); + + ASSERT_EQ(mf.Map(), MAPPED_FILE_ERR_OK); + // 6. read from mapped file + std::string readout; + char* cur = mf.Begin(); + for (; cur <= mf.End(); cur++) { + readout.push_back(*cur); + } + EXPECT_EQ(readout, content1); + + // 7. write to mapped file + std::string toWrite("Complete."); + char* newCur = mf.Begin(); + for (std::string::size_type i = 0; i < toWrite.length(); i++) { + (*newCur) = toWrite[i]; + newCur++; + } + std::string res; + LoadStringFromFile(filename1, res); + EXPECT_EQ(res, "Complete.move use."); + + RemoveTestFile(filename); + RemoveTestFile(filename1); +} + + +} // namespace +} // namespace OHOS \ No newline at end of file -- Gitee