Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Provide an access object for the file system (#50)
- Loading branch information
Showing
4 changed files
with
206 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
# frozen_string_literal: true | ||
|
||
# Copyright 2023 Google LLC | ||
# | ||
# 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 | ||
# | ||
# https://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. | ||
|
||
require "google/cloud/env/lazy_value" | ||
|
||
module Google | ||
module Cloud | ||
class Env | ||
## | ||
# Access to file system contents. | ||
# | ||
# This is a simple class that reads the contents of objects in the file | ||
# system, caching data so that subsequent accesses do not need to reread | ||
# the file system. | ||
# | ||
# You can also "mock" the file system by providing a hash of overrides. | ||
# If overrides are present, actual file system access is disabled; that | ||
# is, overrides are "all or nothing". | ||
# | ||
# This class does not provide any controls for data size. If you read a | ||
# large file, its contents will stay in memory for the lifetime of the | ||
# Ruby process. | ||
# | ||
class FileSystem | ||
## | ||
# Create a file system access object with no overrides. | ||
# | ||
def initialize | ||
@overrides = nil | ||
@cache = LazyDict.new do |path, binary| | ||
if binary | ||
File.binread path | ||
else | ||
File.read path | ||
end | ||
rescue IOError, SystemCallError | ||
nil | ||
end | ||
# This mutex protects the overrides variable. Its setting (i.e. | ||
# whether nil or an overrides hash) will not change within a | ||
# synchronize block. | ||
@mutex = Thread::Mutex.new | ||
end | ||
|
||
## | ||
# Read the given file from the file system and return its contents. | ||
# | ||
# @param path [String] The path to the file. | ||
# @param binary [boolean] Whether to read in binary mode. Defaults to | ||
# false. This must be consistent across multiple requests for the | ||
# same path; if it is not, an error will be raised. | ||
# @return [String] if the file exists. | ||
# @return [nil] if the file does not exist. | ||
# | ||
def read path, binary: false | ||
result = false | ||
@mutex.synchronize do | ||
result = @overrides[path] if @overrides | ||
end | ||
result = @cache.get(path, binary) if result == false | ||
if result && binary != (result.encoding == Encoding::ASCII_8BIT) | ||
raise IOError, "binary encoding flag mismatch" | ||
end | ||
result | ||
end | ||
|
||
## | ||
# The overrides hash, or nil if overrides are not present. | ||
# The hash maps paths to contents of the file at that path. | ||
# | ||
# @return [Hash{String => String},nil] | ||
# | ||
attr_reader :overrides | ||
|
||
## | ||
# Set the overrides hash. You can either provide a hash of file paths | ||
# to content, or nil to disable overrides. If overrides are present, | ||
# actual filesystem access is disabled; overrides are "all or nothing". | ||
# | ||
# @param new_overrides [Hash{String => String},nil] | ||
# | ||
def overrides= new_overrides | ||
@mutex.synchronize do | ||
@overrides = new_overrides | ||
end | ||
end | ||
|
||
## | ||
# Run the given block with the overrides replaced with the given hash | ||
# (or nil to disable overrides in the block). The original overrides | ||
# setting is restored at the end of the block. This is used for | ||
# debugging/testing/mocking. | ||
# | ||
# @param temp_overrides [nil,Hash{String => String}] | ||
# | ||
def with_overrides temp_overrides | ||
old_overrides = @overrides | ||
begin | ||
@mutex.synchronize do | ||
@overrides = temp_overrides | ||
end | ||
yield | ||
ensure | ||
@mutex.synchronize do | ||
@overrides = old_overrides | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Hello, Ruby! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# frozen_string_literal: true | ||
|
||
# Copyright 2023 Google LLC | ||
# | ||
# 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 | ||
# | ||
# https://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. | ||
|
||
require "helper" | ||
require "google/cloud/env/file_system" | ||
require "English" | ||
|
||
describe Google::Cloud::Env::FileSystem do | ||
let(:file_system) { Google::Cloud::Env::FileSystem.new } | ||
let(:data_dir_path) { File.expand_path "../../../data", __dir__ } | ||
let(:text_file_path) { File.join data_dir_path, "text-file.txt" } | ||
let(:binary_file_path) { File.join data_dir_path, "binary-file" } | ||
let(:missing_file_path) { File.join data_dir_path, "i-dont-exist" } | ||
let(:text_file_contents) { "Hello, Ruby!" } | ||
let(:text_file_contents_as_binary) { text_file_contents.encode Encoding::ASCII_8BIT } | ||
let(:binary_file_contents) { [0, 1, 2, 3].pack("c*") } | ||
let(:fake_text_file_contents) { "Ruby rocks!" } | ||
let(:fake_binary_file_contents) { [4, 5, 6, 7].pack("c*") } | ||
let(:fake_data) { | ||
{ | ||
text_file_path => fake_text_file_contents, | ||
binary_file_path => fake_binary_file_contents | ||
} | ||
} | ||
|
||
it "returns text content from the file system" do | ||
assert_equal text_file_contents, file_system.read(text_file_path) | ||
end | ||
|
||
it "returns binary content from the file system" do | ||
assert_equal binary_file_contents, file_system.read(binary_file_path, binary: true) | ||
end | ||
|
||
it "returns nil for a file that doesn't exist" do | ||
assert_nil file_system.read(missing_file_path) | ||
end | ||
|
||
it "returns nil for a directory" do | ||
assert_nil file_system.read(data_dir_path) | ||
end | ||
|
||
it "reports binary mismatch" do | ||
assert_equal text_file_contents_as_binary, file_system.read(text_file_path, binary: true) | ||
assert_raises IOError do | ||
file_system.read text_file_path, binary: false | ||
end | ||
assert_equal text_file_contents_as_binary, file_system.read(text_file_path, binary: true) | ||
end | ||
|
||
it "supports overrides" do | ||
file_system.overrides = fake_data | ||
assert_equal fake_text_file_contents, file_system.read(text_file_path) | ||
assert_equal fake_binary_file_contents, file_system.read(binary_file_path, binary: true) | ||
file_system.overrides = nil | ||
assert_equal text_file_contents, file_system.read(text_file_path) | ||
assert_equal binary_file_contents, file_system.read(binary_file_path, binary: true) | ||
end | ||
|
||
it "supports with_overrides" do | ||
file_system.with_overrides fake_data do | ||
assert_equal fake_text_file_contents, file_system.read(text_file_path) | ||
assert_equal fake_binary_file_contents, file_system.read(binary_file_path, binary: true) | ||
end | ||
assert_equal text_file_contents, file_system.read(text_file_path) | ||
assert_equal binary_file_contents, file_system.read(binary_file_path, binary: true) | ||
end | ||
end |