OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "crazy_linker_system_mock.h" |
| 6 |
| 7 #include <stdarg.h> |
| 8 #include <stdio.h> |
| 9 #include <stdlib.h> |
| 10 |
| 11 #include "crazy_linker_util.h" |
| 12 #include "crazy_linker_system.h" |
| 13 |
| 14 // Unit-testing support code. This should never be compiled into |
| 15 // the production code. |
| 16 |
| 17 namespace { |
| 18 |
| 19 using crazy::String; |
| 20 using crazy::Vector; |
| 21 |
| 22 void Panic(const char* msg, ...) { |
| 23 va_list args; |
| 24 fprintf(stderr, "PANIC: "); |
| 25 va_start(args, msg); |
| 26 vfprintf(stderr, msg, args); |
| 27 va_end(args); |
| 28 fprintf(stderr, "\n"); |
| 29 exit(1); |
| 30 } |
| 31 |
| 32 // Models a simple list of pointers to objects, which are owned by the |
| 33 // list itself. |
| 34 template <class T> |
| 35 class List { |
| 36 public: |
| 37 List() : entries_() {} |
| 38 |
| 39 ~List() { |
| 40 Reset(); |
| 41 } |
| 42 |
| 43 void Reset() { |
| 44 for (size_t n = 0; n < entries_.GetCount(); ++n) { |
| 45 T* entry = entries_[n]; |
| 46 delete entry; |
| 47 entries_[n] = NULL; |
| 48 } |
| 49 entries_.Resize(0); |
| 50 } |
| 51 |
| 52 // Add an item to the list, transfer ownership to it. |
| 53 void PushBack(T* item) { |
| 54 entries_.PushBack(item); |
| 55 } |
| 56 |
| 57 size_t GetCount() const { return entries_.GetCount(); } |
| 58 |
| 59 T* operator[](size_t index) { return entries_[index]; } |
| 60 |
| 61 private: |
| 62 crazy::Vector<T*> entries_; |
| 63 }; |
| 64 |
| 65 // Models a single file entry in a mock file system. |
| 66 class MockFileEntry { |
| 67 public: |
| 68 MockFileEntry() : path_(), data_() {} |
| 69 |
| 70 ~MockFileEntry() {} |
| 71 |
| 72 const char* GetPath() const { return path_.c_str(); } |
| 73 const char* GetData() const { return data_.c_str(); } |
| 74 size_t GetDataSize() const { return data_.size(); } |
| 75 |
| 76 void SetPath(const char* path) { |
| 77 path_.Assign(path); |
| 78 } |
| 79 |
| 80 void SetData(const char* data, size_t data_size) { |
| 81 data_.Assign(data, data_size); |
| 82 } |
| 83 |
| 84 private: |
| 85 crazy::String path_; |
| 86 crazy::String data_; |
| 87 }; |
| 88 |
| 89 // Models a single mock environment variable value. |
| 90 class MockEnvEntry { |
| 91 public: |
| 92 MockEnvEntry(const char* var_name, const char* var_value) |
| 93 : var_name_(var_name), var_value_(var_value) {} |
| 94 |
| 95 const String& GetName() const { return var_name_; } |
| 96 const String& GetValue() const { return var_value_; } |
| 97 |
| 98 private: |
| 99 crazy::String var_name_; |
| 100 crazy::String var_value_; |
| 101 }; |
| 102 |
| 103 class MockSystem { |
| 104 public: |
| 105 MockSystem() : files_(), environment_() {} |
| 106 |
| 107 ~MockSystem() { |
| 108 Reset(); |
| 109 } |
| 110 |
| 111 void SetCurrentDir(const char* path) { |
| 112 current_dir_ = path; |
| 113 } |
| 114 |
| 115 String GetCurrentDir() const { |
| 116 return current_dir_; |
| 117 } |
| 118 |
| 119 void AddFileEntry(MockFileEntry* entry) { |
| 120 files_.PushBack(entry); |
| 121 } |
| 122 |
| 123 void AddEnvEntry(MockEnvEntry* entry) { |
| 124 environment_.PushBack(entry); |
| 125 } |
| 126 |
| 127 MockFileEntry* FindFileEntry(const char* path) { |
| 128 for (size_t n = 0; n < files_.GetCount(); ++n) { |
| 129 MockFileEntry* entry = files_[n]; |
| 130 if (entry->GetPath() && !strcmp(path, entry->GetPath())) |
| 131 return entry; |
| 132 } |
| 133 return NULL; |
| 134 } |
| 135 |
| 136 MockEnvEntry* FindEnvEntry(const char* var_name) { |
| 137 for (size_t n = 0; n < environment_.GetCount(); ++n) { |
| 138 MockEnvEntry* entry = environment_[n]; |
| 139 if (!strcmp(entry->GetName().c_str(), var_name)) |
| 140 return entry; |
| 141 } |
| 142 return NULL; |
| 143 } |
| 144 |
| 145 void Reset() { |
| 146 files_.Reset(); |
| 147 environment_.Reset(); |
| 148 current_dir_ = "/"; |
| 149 } |
| 150 |
| 151 void Check() { |
| 152 if (!active_) |
| 153 Panic("No mock file system setup!"); |
| 154 } |
| 155 |
| 156 void Activate() { |
| 157 if (active_) |
| 158 Panic("Double mock file system activation!"); |
| 159 |
| 160 active_ = true; |
| 161 } |
| 162 |
| 163 void Deactivate() { |
| 164 if (!active_) |
| 165 Panic("Double mock file system deactivation!"); |
| 166 |
| 167 active_ = false; |
| 168 } |
| 169 |
| 170 private: |
| 171 List<MockFileEntry> files_; |
| 172 List<MockEnvEntry> environment_; |
| 173 String current_dir_; |
| 174 bool active_; |
| 175 }; |
| 176 |
| 177 static MockSystem s_mock_fs; |
| 178 |
| 179 class MockFileHandle { |
| 180 public: |
| 181 MockFileHandle(MockFileEntry* entry) : entry_(entry), offset_(0) {} |
| 182 ~MockFileHandle() {} |
| 183 |
| 184 bool IsEof() const { |
| 185 return offset_ >= entry_->GetDataSize(); |
| 186 } |
| 187 |
| 188 bool GetString(char* buffer, size_t buffer_size) { |
| 189 const char* data = entry_->GetData(); |
| 190 size_t data_size = entry_->GetDataSize(); |
| 191 |
| 192 if (offset_ >= data_size || buffer_size == 0) |
| 193 return false; |
| 194 |
| 195 while (buffer_size > 1) { |
| 196 char ch = data[offset_ ++ ]; |
| 197 *buffer++ = ch; |
| 198 buffer_size--; |
| 199 if (ch == '\n') |
| 200 break; |
| 201 } |
| 202 *buffer = '\0'; |
| 203 return true; |
| 204 } |
| 205 |
| 206 int Read(void* buffer, size_t buffer_size) { |
| 207 if (buffer_size == 0) |
| 208 return 0; |
| 209 |
| 210 const char* data = entry_->GetData(); |
| 211 size_t data_size = entry_->GetDataSize(); |
| 212 |
| 213 size_t avail = data_size - offset_; |
| 214 if (avail == 0) |
| 215 return 0; |
| 216 |
| 217 if (buffer_size > avail) |
| 218 buffer_size = avail; |
| 219 |
| 220 ::memcpy(buffer, data + offset_, buffer_size); |
| 221 offset_ += buffer_size; |
| 222 |
| 223 return static_cast<int>(buffer_size); |
| 224 } |
| 225 |
| 226 int SeekTo(off_t offset) { |
| 227 if (offset < 0) { |
| 228 errno = EINVAL; |
| 229 return -1; |
| 230 } |
| 231 |
| 232 const char* data = entry_->GetData(); |
| 233 size_t data_size = entry_->GetDataSize(); |
| 234 |
| 235 if (offset > static_cast<off_t>(data_size)) { |
| 236 errno = EINVAL; |
| 237 return -1; |
| 238 } |
| 239 |
| 240 offset_ = static_cast<size_t>(offset); |
| 241 return 0; |
| 242 } |
| 243 |
| 244 void* Map(void* address, size_t length, int prot, int flags, |
| 245 off_t offset) { |
| 246 const char* data = entry_->GetData(); |
| 247 size_t data_size = entry_->GetDataSize(); |
| 248 if (offset_ >= data_size) { |
| 249 errno = EINVAL; |
| 250 return MAP_FAILED; |
| 251 } |
| 252 |
| 253 // Allocate an anonymous memory mapping, then copy the file contents |
| 254 // into it. |
| 255 void* map = mmap(address, length, PROT_WRITE, MAP_ANONYMOUS, -1, 0); |
| 256 if (map == MAP_FAILED) { |
| 257 return map; |
| 258 } |
| 259 |
| 260 size_t avail = data_size - offset_; |
| 261 if (avail > length) |
| 262 avail = length; |
| 263 |
| 264 ::memcpy(map, data + offset_, avail); |
| 265 |
| 266 // Restore desired protection after the write. |
| 267 mprotect(map, length, prot); |
| 268 |
| 269 // Done. |
| 270 return map; |
| 271 } |
| 272 |
| 273 private: |
| 274 MockFileEntry* entry_; |
| 275 size_t offset_; |
| 276 }; |
| 277 |
| 278 MockFileHandle* NewMockFileHandle(const char* path, |
| 279 crazy::FileOpenMode open_mode) { |
| 280 // Check that a mock file system instance is active. |
| 281 s_mock_fs.Check(); |
| 282 |
| 283 // TODO(digit): Add write support. |
| 284 if (open_mode != crazy::FILE_OPEN_READ_ONLY) |
| 285 Panic("Unsupported open mode (%d): %s", open_mode, path); |
| 286 |
| 287 MockFileEntry* entry = s_mock_fs.FindFileEntry(path); |
| 288 if (!entry) |
| 289 Panic("Missing mock file entry: %s", path); |
| 290 |
| 291 return new MockFileHandle(entry); |
| 292 } |
| 293 |
| 294 } // namespace |
| 295 |
| 296 namespace crazy { |
| 297 |
| 298 #ifdef UNIT_TESTS |
| 299 |
| 300 bool PathExists(const char* path) { |
| 301 s_mock_fs.Check(); |
| 302 return s_mock_fs.FindFileEntry(path) != NULL; |
| 303 } |
| 304 |
| 305 bool PathIsFile(const char* path) { |
| 306 // TODO(digit): Change this when support for mock directories is added. |
| 307 return PathExists(path); |
| 308 } |
| 309 |
| 310 String GetCurrentDirectory() { |
| 311 s_mock_fs.Check(); |
| 312 return s_mock_fs.GetCurrentDir(); |
| 313 } |
| 314 |
| 315 const char* GetEnv(const char* var_name) { |
| 316 s_mock_fs.Check(); |
| 317 MockEnvEntry* entry = s_mock_fs.FindEnvEntry(var_name); |
| 318 if (!entry) |
| 319 return NULL; |
| 320 else |
| 321 return entry->GetValue().c_str(); |
| 322 } |
| 323 |
| 324 bool FileDescriptor::OpenReadOnly(const char* path) { |
| 325 fd_ = NewMockFileHandle(path, FILE_OPEN_READ_ONLY); |
| 326 return fd_ != NULL; |
| 327 } |
| 328 |
| 329 bool FileDescriptor::OpenReadWrite(const char* path) { |
| 330 // NOT IMPLEMENTED ON PURPOSE. |
| 331 return false; |
| 332 } |
| 333 |
| 334 void FileDescriptor::Close() { |
| 335 if (fd_) { |
| 336 MockFileHandle* handle = reinterpret_cast<MockFileHandle*>(fd_); |
| 337 delete handle; |
| 338 fd_ = NULL; |
| 339 } |
| 340 } |
| 341 |
| 342 int FileDescriptor::Read(void* buffer, size_t buffer_size) { |
| 343 if (!fd_) { |
| 344 errno = EBADF; |
| 345 return -1; |
| 346 } |
| 347 MockFileHandle* handle = reinterpret_cast<MockFileHandle*>(fd_); |
| 348 return handle->Read(buffer, buffer_size); |
| 349 } |
| 350 |
| 351 int FileDescriptor::SeekTo(off_t offset) { |
| 352 if (!fd_) { |
| 353 errno = EBADF; |
| 354 return -1; |
| 355 } |
| 356 MockFileHandle* handle = reinterpret_cast<MockFileHandle*>(fd_); |
| 357 return handle->SeekTo(offset); |
| 358 } |
| 359 |
| 360 void* FileDescriptor::Map(void* address, size_t length, int prot, |
| 361 int flags, off_t offset) { |
| 362 if (!fd_ || (offset & 4095) != 0) { |
| 363 errno = EINVAL; |
| 364 return MAP_FAILED; |
| 365 } |
| 366 MockFileHandle* handle = reinterpret_cast<MockFileHandle*>(fd_); |
| 367 return handle->Map(address, length, prot, flags, offset); |
| 368 } |
| 369 |
| 370 SystemMock::SystemMock() { |
| 371 s_mock_fs.Activate(); |
| 372 } |
| 373 |
| 374 SystemMock::~SystemMock() { |
| 375 s_mock_fs.Deactivate(); |
| 376 s_mock_fs.Reset(); |
| 377 } |
| 378 |
| 379 void SystemMock::AddRegularFile(const char* path, |
| 380 const char* data, |
| 381 size_t data_size) { |
| 382 s_mock_fs.Check(); |
| 383 |
| 384 MockFileEntry* entry = new MockFileEntry(); |
| 385 entry->SetPath(path); |
| 386 entry->SetData(data, data_size); |
| 387 |
| 388 s_mock_fs.AddFileEntry(entry); |
| 389 } |
| 390 |
| 391 void SystemMock::AddEnvVariable(const char* var_name, |
| 392 const char* var_value) { |
| 393 s_mock_fs.Check(); |
| 394 |
| 395 MockEnvEntry* env = new MockEnvEntry(var_name, var_value); |
| 396 s_mock_fs.AddEnvEntry(env); |
| 397 } |
| 398 |
| 399 void SystemMock::SetCurrentDir(const char* path) { |
| 400 s_mock_fs.Check(); |
| 401 s_mock_fs.SetCurrentDir(path); |
| 402 } |
| 403 |
| 404 #endif // UNIT_TESTS |
| 405 |
| 406 } // namespace crazy |
OLD | NEW |