iceberg-cpp
Loading...
Searching...
No Matches
mock_io.h
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20#pragma once
21
22#include <algorithm>
23#include <cstddef>
24#include <cstdint>
25#include <cstring>
26#include <limits>
27#include <memory>
28#include <optional>
29#include <span>
30#include <string>
31#include <string_view>
32#include <unordered_map>
33#include <utility>
34#include <vector>
35
36#include <gmock/gmock.h>
37#include <gtest/gtest.h>
38
39#include "iceberg/file_io.h"
40#include "iceberg/util/macros.h"
41
42namespace iceberg {
43
44class MockFileIO : public FileIO {
45 public:
46 MockFileIO() = default;
47 ~MockFileIO() override = default;
48
49 Result<std::unique_ptr<InputFile>> NewInputFile(std::string file_location) override {
50 auto file = FindFile(file_location);
51 if (!file) {
52 return NotFound("File does not exist: {}", file_location);
53 }
54 return std::make_unique<InMemoryInputFile>(std::move(file_location), std::move(file),
55 std::nullopt);
56 }
57
58 Result<std::unique_ptr<InputFile>> NewInputFile(std::string file_location,
59 size_t length) override {
60 if (length > static_cast<size_t>(std::numeric_limits<int64_t>::max())) {
61 return InvalidArgument("File length {} exceeds int64_t max", length);
62 }
63 auto file = FindFile(file_location);
64 if (!file) {
65 return NotFound("File does not exist: {}", file_location);
66 }
67 return std::make_unique<InMemoryInputFile>(std::move(file_location), std::move(file),
68 static_cast<int64_t>(length));
69 }
70
71 Result<std::unique_ptr<OutputFile>> NewOutputFile(std::string file_location) override {
72 return std::make_unique<InMemoryOutputFile>(files_, std::move(file_location));
73 }
74
75 void AddFile(std::string file_location, std::span<const std::byte> data) {
76 files_->insert_or_assign(
77 std::move(file_location),
78 std::make_shared<std::vector<std::byte>>(data.begin(), data.end()));
79 }
80
81 void AddFile(std::string file_location, std::string_view data) {
82 AddFile(std::move(file_location),
83 std::as_bytes(std::span<const char>(data.data(), data.size())));
84 }
85
86 std::vector<std::byte>& FileData(const std::string& file_location) {
87 return *GetOrCreateFile(file_location);
88 }
89
90 const std::vector<std::byte>& FileData(const std::string& file_location) const {
91 return *files_->at(file_location);
92 }
93
94 MOCK_METHOD((Result<std::string>), ReadFile,
95 (const std::string&, std::optional<size_t>), (override));
96
97 MOCK_METHOD(Status, WriteFile, (const std::string&, std::string_view), (override));
98
99 MOCK_METHOD(Status, DeleteFile, (const std::string&), (override));
100
101 private:
102 using FileMap =
103 std::unordered_map<std::string, std::shared_ptr<std::vector<std::byte>>>;
104
105 class InMemoryInputStream : public SeekableInputStream {
106 public:
107 explicit InMemoryInputStream(std::shared_ptr<std::vector<std::byte>> data)
108 : data_(std::move(data)) {}
109
110 Result<int64_t> Position() const override { return position_; }
111
112 Status Seek(int64_t position) override {
113 ICEBERG_PRECHECK(!closed_, "Input stream is closed");
114 ICEBERG_PRECHECK(position >= 0, "Position must not be negative: {}", position);
115 position_ = position;
116 return {};
117 }
118
119 Result<int64_t> Read(std::span<std::byte> out) override {
120 ICEBERG_PRECHECK(!closed_, "Input stream is closed");
121 auto file_size = static_cast<int64_t>(data_->size());
122 ICEBERG_PRECHECK(position_ <= file_size, "Position {} exceeds file size {}",
123 position_, file_size);
124 auto bytes_to_read =
125 std::min(static_cast<int64_t>(out.size()), file_size - position_);
126 if (bytes_to_read > 0) {
127 std::memcpy(out.data(), data_->data() + static_cast<size_t>(position_),
128 static_cast<size_t>(bytes_to_read));
129 position_ += bytes_to_read;
130 }
131 return bytes_to_read;
132 }
133
134 Status ReadFully(int64_t position, std::span<std::byte> out) override {
135 ICEBERG_PRECHECK(!closed_, "Input stream is closed");
136 ICEBERG_PRECHECK(position >= 0, "Position must not be negative: {}", position);
137 auto file_size = static_cast<int64_t>(data_->size());
138 ICEBERG_PRECHECK(static_cast<int64_t>(out.size()) <= file_size - position,
139 "Read out of bounds: offset {} + length {} exceeds file size {}",
140 position, out.size(), file_size);
141 if (!out.empty()) {
142 std::memcpy(out.data(), data_->data() + static_cast<size_t>(position),
143 out.size());
144 }
145 return {};
146 }
147
148 Status Close() override {
149 closed_ = true;
150 return {};
151 }
152
153 private:
154 std::shared_ptr<std::vector<std::byte>> data_;
155 int64_t position_ = 0;
156 bool closed_ = false;
157 };
158
159 class InMemoryOutputStream : public PositionOutputStream {
160 public:
161 explicit InMemoryOutputStream(std::shared_ptr<std::vector<std::byte>> data)
162 : data_(std::move(data)) {}
163
164 Result<int64_t> Position() const override {
165 return static_cast<int64_t>(data_->size());
166 }
167
168 Status Write(std::span<const std::byte> data) override {
169 ICEBERG_PRECHECK(!closed_, "Output stream is closed");
170 data_->insert(data_->end(), data.begin(), data.end());
171 return {};
172 }
173
174 Status Flush() override {
175 ICEBERG_PRECHECK(!closed_, "Output stream is closed");
176 return {};
177 }
178
179 Status Close() override {
180 closed_ = true;
181 return {};
182 }
183
184 private:
185 std::shared_ptr<std::vector<std::byte>> data_;
186 bool closed_ = false;
187 };
188
189 class InMemoryInputFile : public InputFile {
190 public:
191 InMemoryInputFile(std::string location, std::shared_ptr<std::vector<std::byte>> data,
192 std::optional<int64_t> length)
193 : location_(std::move(location)), data_(std::move(data)), length_(length) {}
194
195 std::string_view location() const override { return location_; }
196
197 Result<int64_t> Size() const override {
198 return length_.value_or(static_cast<int64_t>(data_->size()));
199 }
200
201 Result<std::unique_ptr<SeekableInputStream>> Open() override {
202 return std::make_unique<InMemoryInputStream>(data_);
203 }
204
205 private:
206 std::string location_;
207 std::shared_ptr<std::vector<std::byte>> data_;
208 std::optional<int64_t> length_;
209 };
210
211 class InMemoryOutputFile : public OutputFile {
212 public:
213 InMemoryOutputFile(std::shared_ptr<FileMap> files, std::string location)
214 : files_(std::move(files)), location_(std::move(location)) {}
215
216 std::string_view location() const override { return location_; }
217
218 Result<std::unique_ptr<PositionOutputStream>> Create() override {
219 if (files_->contains(location_)) {
220 return AlreadyExists("File already exists: {}", location_);
221 }
222 auto file = std::make_shared<std::vector<std::byte>>();
223 files_->emplace(location_, file);
224 return std::make_unique<InMemoryOutputStream>(std::move(file));
225 }
226
227 Result<std::unique_ptr<PositionOutputStream>> CreateOrOverwrite() override {
228 auto file = std::make_shared<std::vector<std::byte>>();
229 files_->insert_or_assign(location_, file);
230 return std::make_unique<InMemoryOutputStream>(std::move(file));
231 }
232
233 private:
234 std::shared_ptr<FileMap> files_;
235 std::string location_;
236 };
237
238 std::shared_ptr<std::vector<std::byte>> FindFile(
239 const std::string& file_location) const {
240 auto file = files_->find(file_location);
241 if (file == files_->end()) {
242 return nullptr;
243 }
244 return file->second;
245 }
246
247 std::shared_ptr<std::vector<std::byte>> GetOrCreateFile(
248 const std::string& file_location) {
249 auto& file = (*files_)[file_location];
250 if (!file) {
251 file = std::make_shared<std::vector<std::byte>>();
252 }
253 return file;
254 }
255
256 std::shared_ptr<FileMap> files_ = std::make_shared<FileMap>();
257};
258
259} // namespace iceberg
Pluggable module for reading, writing, and deleting files.
Definition file_io.h:125
virtual Result< std::string > ReadFile(const std::string &file_location, std::optional< size_t > length)
Read the content of the file at the given location.
Definition file_io.cc:58
virtual Status WriteFile(const std::string &file_location, std::string_view content)
Write the given content to the file at the given location.
Definition file_io.cc:89
virtual Status DeleteFile(const std::string &file_location)
Delete a file at the given location.
Definition file_io.h:164
Definition mock_io.h:44
Result< std::unique_ptr< InputFile > > NewInputFile(std::string file_location) override
Create an input file handle for the given location.
Definition mock_io.h:49
Result< std::unique_ptr< OutputFile > > NewOutputFile(std::string file_location) override
Create an output file handle for the given location.
Definition mock_io.h:71
Result< std::unique_ptr< InputFile > > NewInputFile(std::string file_location, size_t length) override
Create an input file handle for the given location with a known length.
Definition mock_io.h:58
STL namespace.