Skip to content

Commit

Permalink
[WIP] Add spec/support/fake_tmpdir_helper.rb
Browse files Browse the repository at this point in the history
This helper is in charge of creating a fake tmpdir that ruby will treat
(when Dir.tmpdir) is a normal file system, and will have actually file
system access everywhere else when called on directly (through other
ruby IO classes).

This works by creating a loopback device with a small file size (default
is 1MB), creates the file into a regular file system (EXT4 on linux, and
EXFAT on OSX), and mounts its.  `Dir.tmpdir` is then patched to point to
this directory instead of the default (note:  this ends up being mounted
in a subdirectory of tmp).

From there, ruby is able to add files, and check disk size just as you
would a normal file system.
  • Loading branch information
NickLaMuro committed May 14, 2019
1 parent 7f73880 commit 1155e07
Showing 1 changed file with 148 additions and 0 deletions.
148 changes: 148 additions & 0 deletions spec/support/fake_tmpdir_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
require 'tmpdir'
require 'tempfile'
require 'sys-uname'
require 'active_support/all'

module Spec
module Support
module FakeTmpdirHelper
PROJECT_TEMP_DIR = Pathname.new(File.expand_path("../../tmp", __dir__))

module_function

def with_fake_tmp(size = fake_fs_size)
build_file(size)
attach_tmp_fs
patch_tmpdir

yield
ensure
detach_fake_fs
unpatch_tmpdir
end

# rubocop:disable Lint/DuplicateMethods
case Sys::Platform::IMPL
when :linux
# TODO: Steps necessary for linux below
#
# $ losetup /dev/loop0 /tmp/fake_tmp_fs
# $ mkfs.ext4 /dev/loop0
# $ mkdir /fake_tmp
# $ echo "#{fake_tmp_fstab_entry}" >> /etc/fstab
# $ mount /dev/loop0
# $ chmod -R 777 /fake_tmp
# $ chmod +t /fake_tmp
#
def attach_tmp_fs
end

def detach_fake_fs
end
when :macosx
#
# $ hdiutil attach -nomount tmp/fake_fs.img
# #=> /dev/disk2
# $ newfs_exfat /dev/disk2
# $ mount_exfat /dev/disk2 tmp/fake_tmp/
# ... # do stuff
# $ hdiutil detach /dev/disk2
#
def attach_tmp_fs
# TODO: Error handling on these commands
puts "hdiutil attach -nomount #{tmp_fs_file}"
@dev_disk = `hdiutil attach -nomount #{tmp_fs_file}`.chomp.strip
puts "newfs_exfat #{@dev_disk} && mount_exfat #{@dev_disk} #{fake_tmp_dir}"
`newfs_exfat #{@dev_disk} && mount_exfat #{@dev_disk} #{fake_tmp_dir}`
end

def detach_fake_fs
`hdiutil detach #{@dev_disk}`
@dev_disk = nil
end
end
# rubocop:enable Lint/DuplicateMethods

def build_file(size)
if update_size?(size)
@tmp_fs_file = nil
tmp_fs_file
end
end

# Create a new class for `Dir`, but first removing the normal Dir
# constant from the global namespace, creating a new class that inherits
# from the original constant, re-writes Dir.tmpdir, and attaches itself
# inplace of Dir in the global namespace.
#
# Choosing **NOT** to use RSpec's `stub_const` so this can be run as a
# regular ruby script (for easier testing)
def patch_tmpdir
@_old_dir ||= nil
unless @_old_dir
@_old_dir = Dir
Object.send(:remove_const, :Dir)

_new_dir = Class.new(@_old_dir)
def _new_dir.tmpdir
Spec::Support::FakeTmpdirHelper.fake_tmp_dir
end
Object.const_set(:Dir, _new_dir)
end
end

# Removes the existing (fake) `Dir` constant, and re-attaches the
# original
def unpatch_tmpdir
@_old_dir ||= nil
if @_old_dir
Object.send(:remove_const, :Dir)
Object.const_set(:Dir, Class.new(@_old_dir))
@_old_dir = nil
end
end

# Write a null byte filled file, and will truncate any existing contents
# (a property of using "w" as the file mode).
#
# Unix Equivalent:
#
# dd if=/dev/zero of=filename bs=1K count=1024
#
# Note: To work with OSX machines, this has to be a .img file, or some
# extension that OSX recognizes (super dumb... I know...)
#
def tmp_fs_file
return @tmp_fs_file if defined? @tmp_fs_file

tmpfs = PROJECT_TEMP_DIR.join("fake_fs.img").to_s
puts "creating #{tmpfs}..."
base_chunk = "\x00" * 1.kilobyte

File.open(tmpfs, "w") do |file|
# Add data in 1KB chunks
(fake_fs_size / 1.kilobyte).times do
file << base_chunk
end

# Add remainder data to tmp file (if any)
file << "\x00" * (fake_fs_size % 1.kilobyte)
end

@tmp_fs_file = tmpfs
end

def fake_tmp_dir
@fake_tmp_dir ||= Dir.mktmpdir("fake_tmp")
end

def fake_fs_size
@size ||= 1.megabyte
end

def update_size?(size)
@size = size if @size != size
end
end
end
end

0 comments on commit 1155e07

Please sign in to comment.