[DRE-commits] [ruby-hiera] 02/04: Updated to 1.3.0

Jonas Genannt jonas at brachium-system.net
Thu Nov 21 22:08:19 UTC 2013


This is an automated email from the git hooks/post-receive script.

hggh-guest pushed a commit to branch master
in repository ruby-hiera.

commit cb5caf835935c325a8194db2eefd7bab0c3e70c3
Author: Jonas Genannt <jonas at brachium-system.net>
Date:   Thu Nov 21 22:58:46 2013 +0100

    Updated to 1.3.0
---
 README.md                              |    6 +-
 bin/hiera                              |    3 +-
 lib/hiera.rb                           |    8 +-
 lib/hiera/backend.rb                   |   44 +++---
 lib/hiera/backend/json_backend.rb      |    2 +-
 lib/hiera/backend/yaml_backend.rb      |    2 +-
 lib/hiera/error.rb                     |    4 +
 lib/hiera/filecache.rb                 |   48 ++++---
 lib/hiera/interpolate.rb               |   48 +++++++
 lib/hiera/recursive_guard.rb           |   20 +++
 lib/hiera/version.rb                   |   89 ++++++++++++
 metadata.yml                           |   90 ++++++-------
 spec/unit/backend/json_backend_spec.rb |   10 +-
 spec/unit/backend/yaml_backend_spec.rb |   28 ++--
 spec/unit/backend_spec.rb              |  230 +++++++++++++++++++++++++++-----
 spec/unit/filecache_spec.rb            |  143 +++++++++++++++-----
 spec/unit/version_spec.rb              |   44 ++++++
 17 files changed, 633 insertions(+), 186 deletions(-)

diff --git a/README.md b/README.md
index 968290c..38f5db1 100644
--- a/README.md
+++ b/README.md
@@ -117,7 +117,7 @@ A sample configuration file can be seen here:
 :logger: console
 
 :hierarchy:
-  - "%{location}"
+  - "sites/%{location}"
   - common
 
 :yaml:
@@ -130,14 +130,14 @@ A sample configuration file can be seen here:
 This configuration will require YAML files in  _/etc/puppet/hieradata_ these need to contain
 Hash data, sample files matching the hierarchy described in the _Why?_ section are below:
 
-_/etc/puppet/hieradata/dc1.yaml_:
+_/etc/puppet/hieradata/sites/dc1.yaml_:
 <pre>
 ---
 ntpserver: ntp1.dc1.example.com
 sysadmin: dc1noc at example.com
 </pre>
 
-_/etc/puppet/hieradata/dc2.yaml_:
+_/etc/puppet/hieradata/sites/dc2.yaml_:
 <pre>
 ---
 ntpserver: ntp1.dc2.example.com
diff --git a/bin/hiera b/bin/hiera
index ddd4daa..ce2875e 100755
--- a/bin/hiera
+++ b/bin/hiera
@@ -24,6 +24,7 @@ end
 require 'hiera'
 require 'hiera/util'
 require 'optparse'
+require 'pp'
 
 options = {
   :default => nil,
@@ -222,5 +223,5 @@ ans = hiera.lookup(options[:key], options[:default], options[:scope], nil, optio
 if ans.is_a?(String)
   puts ans
 else
-  p ans
+  pp ans
 end
diff --git a/lib/hiera.rb b/lib/hiera.rb
index ec9e28b..d1a8979 100644
--- a/lib/hiera.rb
+++ b/lib/hiera.rb
@@ -1,8 +1,8 @@
 require 'yaml'
 
 class Hiera
-  VERSION = "1.2.1"
-
+  require "hiera/error"
+  require "hiera/version"
   require "hiera/config"
   require "hiera/util"
   require "hiera/backend"
@@ -15,10 +15,6 @@ class Hiera
   class << self
     attr_reader :logger
 
-    def version
-      VERSION
-    end
-
     # Loggers are pluggable, just provide a class called
     # Hiera::Foo_logger and respond to :warn and :debug
     #
diff --git a/lib/hiera/backend.rb b/lib/hiera/backend.rb
index 9b3af5e..c04bf24 100644
--- a/lib/hiera/backend.rb
+++ b/lib/hiera/backend.rb
@@ -1,5 +1,6 @@
 require 'hiera/util'
-require 'hiera/recursive_lookup'
+require 'hiera/recursive_guard'
+require 'hiera/interpolate'
 
 begin
   require 'deep_merge'
@@ -8,21 +9,25 @@ end
 
 class Hiera
   module Backend
-    INTERPOLATION = /%\{([^\}]*)\}/
-
     class << self
       # Data lives in /var/lib/hiera by default.  If a backend
       # supplies a datadir in the config it will be used and
       # subject to variable expansion based on scope
       def datadir(backend, scope)
         backend = backend.to_sym
-        default = Hiera::Util.var_dir
 
-        if Config.include?(backend)
-          parse_string(Config[backend][:datadir] || default, scope)
+        if Config[backend] && Config[backend][:datadir]
+          dir = Config[backend][:datadir]
         else
-          parse_string(default, scope)
+          dir = Hiera::Util.var_dir
+        end
+
+        if !dir.is_a?(String)
+          raise(Hiera::InvalidConfigurationError,
+                "datadir for #{backend} cannot be an array")
         end
+
+        parse_string(dir, scope)
       end
 
       # Finds the path to a datafile based on the Backend#datadir
@@ -85,22 +90,8 @@ class Hiera
       #
       # @api public
       def parse_string(data, scope, extra_data={})
-        interpolate(data, Hiera::RecursiveLookup.new(scope, extra_data))
-      end
-
-      def interpolate(data, values)
-        if data.is_a?(String)
-          data.gsub(INTERPOLATION) do
-            name = $1
-            values.lookup(name) do |value|
-              interpolate(value, values)
-            end
-          end
-        else
-          data
-        end
+        Hiera::Interpolate.interpolate(data, Hiera::RecursiveGuard.new, scope, extra_data)
       end
-      private :interpolate
 
       # Parses a answer received from data files
       #
@@ -114,7 +105,8 @@ class Hiera
         elsif data.is_a?(Hash)
           answer = {}
           data.each_pair do |key, val|
-            answer[key] = parse_answer(val, scope, extra_data)
+            interpolated_key = parse_string(key, scope, extra_data)
+            answer[interpolated_key] = parse_answer(val, scope, extra_data)
           end
 
           return answer
@@ -145,7 +137,7 @@ class Hiera
       # Deep merge options use the Hash utility function provided by [deep_merge](https://github.com/peritor/deep_merge)
       #
       #  :native => Native Hash.merge
-      #  :deep   => Use Hash.deep_merge  
+      #  :deep   => Use Hash.deep_merge
       #  :deeper => Use Hash.deep_merge!
       #
       def merge_answer(left,right)
@@ -205,6 +197,10 @@ class Hiera
         return default if answer.nil?
         return answer
       end
+
+      def clear!
+        @backends = {}
+      end
     end
   end
 end
diff --git a/lib/hiera/backend/json_backend.rb b/lib/hiera/backend/json_backend.rb
index 4ca9481..da78b99 100644
--- a/lib/hiera/backend/json_backend.rb
+++ b/lib/hiera/backend/json_backend.rb
@@ -21,7 +21,7 @@ class Hiera
 
           next unless File.exist?(jsonfile)
 
-          data = @cache.read(jsonfile, Hash, {}) do |data|
+          data = @cache.read_file(jsonfile, Hash) do |data|
             JSON.parse(data)
           end
 
diff --git a/lib/hiera/backend/yaml_backend.rb b/lib/hiera/backend/yaml_backend.rb
index 1ce6bd1..0e100c1 100644
--- a/lib/hiera/backend/yaml_backend.rb
+++ b/lib/hiera/backend/yaml_backend.rb
@@ -19,7 +19,7 @@ class Hiera
 
           next unless File.exist?(yamlfile)
 
-          data = @cache.read(yamlfile, Hash, {}) do |data|
+          data = @cache.read_file(yamlfile, Hash) do |data|
             YAML.load(data)
           end
 
diff --git a/lib/hiera/error.rb b/lib/hiera/error.rb
new file mode 100644
index 0000000..8ff4f3f
--- /dev/null
+++ b/lib/hiera/error.rb
@@ -0,0 +1,4 @@
+class Hiera
+  class Error < StandardError; end
+  class InvalidConfigurationError < Error; end
+end
diff --git a/lib/hiera/filecache.rb b/lib/hiera/filecache.rb
index ac46e02..f1a3d1f 100644
--- a/lib/hiera/filecache.rb
+++ b/lib/hiera/filecache.rb
@@ -24,32 +24,44 @@ class Hiera
     # reading/parsing fails it will return {} instead
     #
     # Prior to calling this method you should be sure the file exist
-    def read(path, expected_type=nil, default=nil)
-      @cache[path] ||= {:data => nil, :meta => path_metadata(path)}
-
-      if File.exist?(path) && !@cache[path][:data] || stale?(path)
-        if block_given?
-          begin
-            @cache[path][:data] = yield(File.read(path))
-          rescue => e
-            Hiera.debug("Reading data from %s failed: %s: %S" % [path, e.class, e.to_s])
-            @cache[path][:data] = default
-          end
-        else
-          @cache[path][:data] = File.read(path)
-        end
+    def read(path, expected_type = Object, default=nil, &block)
+      read_file(path, expected_type, &block)
+    rescue TypeError => detail
+      Hiera.debug("#{detail.message}, setting defaults")
+      @cache[path][:data] = default
+    rescue => detail
+      error = "Reading data from #{path} failed: #{detail.class}: #{detail}"
+      if default.nil?
+        raise detail
+      else
+        Hiera.debug(error)
+        @cache[path][:data] = default
       end
+    end
 
-      if block_given? && !expected_type.nil?
-        unless @cache[path][:data].is_a?(expected_type)
-          Hiera.debug("Data retrieved from %s is not a %s, setting defaults" % [path, expected_type])
-          @cache[path][:data] = default
+    # Read a file when it changes. If a file is re-read and has not changed since the last time
+    # then the last, processed, contents will be returned.
+    #
+    # The processed data can also be checked against an expected type. If the
+    # type does not match a TypeError is raised.
+    #
+    # No error handling is done inside this method. Any failed reads or errors
+    # in processing will be propagated to the caller
+    def read_file(path, expected_type = Object)
+      if stale?(path)
+        data = File.read(path)
+        @cache[path][:data] = block_given? ? yield(data) : data
+
+        if !@cache[path][:data].is_a?(expected_type)
+          raise TypeError, "Data retrieved from #{path} is #{data.class} not #{expected_type}"
         end
       end
 
       @cache[path][:data]
     end
 
+    private
+
     def stale?(path)
       meta = path_metadata(path)
 
diff --git a/lib/hiera/interpolate.rb b/lib/hiera/interpolate.rb
new file mode 100644
index 0000000..b5653f2
--- /dev/null
+++ b/lib/hiera/interpolate.rb
@@ -0,0 +1,48 @@
+require 'hiera/backend'
+
+class Hiera::Interpolate
+  class << self
+    INTERPOLATION = /%\{([^\}]*)\}/
+    METHOD_INTERPOLATION = /%\{(scope|hiera)\(['"]([^"']*)["']\)\}/
+
+    def interpolate(data, recurse_guard, scope, extra_data)
+      if data.is_a?(String) && (match = data.match(INTERPOLATION))
+        interpolation_variable = match[1]
+        recurse_guard.check(interpolation_variable) do
+          interpolate_method, key = get_interpolation_method_and_key(data)
+          interpolated_data = send(interpolate_method, data, key, scope, extra_data)
+          interpolate(interpolated_data, recurse_guard, scope, extra_data)
+        end
+      else
+        data
+      end
+    end
+
+    def get_interpolation_method_and_key(data)
+      if (match = data.match(METHOD_INTERPOLATION))
+        case match[1]
+        when 'hiera' then [:hiera_interpolate, match[2]]
+        when 'scope' then [:scope_interpolate, match[2]]
+        end
+      elsif (match = data.match(INTERPOLATION))
+        [:scope_interpolate, match[1]]
+      end
+    end
+    private :get_interpolation_method_and_key
+
+    def scope_interpolate(data, key, scope, extra_data)
+      value = scope[key]
+      if value.nil? || value == :undefined
+        value = extra_data[key]
+      end
+      data.sub(INTERPOLATION, value.to_s)
+    end
+    private :scope_interpolate
+
+    def hiera_interpolate(data, key, scope, extra_data)
+      value = Hiera::Backend.lookup(key, nil, scope, nil, :priority)
+      data.sub(METHOD_INTERPOLATION, value)
+    end
+    private :hiera_interpolate
+  end
+end
diff --git a/lib/hiera/recursive_guard.rb b/lib/hiera/recursive_guard.rb
new file mode 100644
index 0000000..c68d21c
--- /dev/null
+++ b/lib/hiera/recursive_guard.rb
@@ -0,0 +1,20 @@
+# Allow for safe recursive lookup of values during variable interpolation.
+#
+# @api private
+class Hiera::InterpolationLoop < StandardError; end
+
+class Hiera::RecursiveGuard
+  def initialize
+    @seen = []
+  end
+
+  def check(value, &block)
+    if @seen.include?(value)
+      raise Hiera::InterpolationLoop, "Detected in [#{@seen.join(', ')}]"
+    end
+    @seen.push(value)
+    ret = yield
+    @seen.pop
+    ret
+  end
+end
diff --git a/lib/hiera/version.rb b/lib/hiera/version.rb
new file mode 100644
index 0000000..a5a5445
--- /dev/null
+++ b/lib/hiera/version.rb
@@ -0,0 +1,89 @@
+# The version method and constant are isolated in hiera/version.rb so that a
+# simple `require 'hiera/version'` allows a rubygems gemspec or bundler
+# Gemfile to get the hiera version of the gem install.
+#
+# The version is programatically settable because we want to allow the
+# Raketasks and such to set the version based on the output of `git describe`
+
+
+class Hiera
+  VERSION = "1.3.0"
+
+  ##
+  # version is a public API method intended to always provide a fast and
+  # lightweight way to determine the version of hiera.
+  #
+  # The intent is that software external to hiera be able to determine the
+  # hiera version with no side-effects.  The expected use is:
+  #
+  #     require 'hiera/version'
+  #     version = Hiera.version
+  #
+  # This function has the following ordering precedence.  This precedence list
+  # is designed to facilitate automated packaging tasks by simply writing to
+  # the VERSION file in the same directory as this source file.
+  #
+  #  1. If a version has been explicitly assigned using the Hiera.version=
+  #     method, return that version.
+  #  2. If there is a VERSION file, read the contents, trim any
+  #     trailing whitespace, and return that version string.
+  #  3. Return the value of the Hiera::VERSION constant hard-coded into
+  #     the source code.
+  #
+  # If there is no VERSION file, the method must return the version string of
+  # the nearest parent version that is an officially released version.  That is
+  # to say, if a branch named 3.1.x contains 25 patches on top of the most
+  # recent official release of 3.1.1, then the version method must return the
+  # string "3.1.1" if no "VERSION" file is present.
+  #
+  # By design the version identifier is _not_ intended to vary during the life
+  # a process.  There is no guarantee provided that writing to the VERSION file
+  # while a Hiera process is running will cause the version string to be
+  # updated.  On the contrary, the contents of the VERSION are cached to reduce
+  # filesystem accesses.
+  #
+  # The VERSION file is intended to be used by package maintainers who may be
+  # applying patches or otherwise changing the software version in a manner
+  # that warrants a different software version identifier.  The VERSION file is
+  # intended to be managed and owned by the release process and packaging
+  # related tasks, and as such should not reside in version control.  The
+  # VERSION constant is intended to be version controlled in history.
+  #
+  # Ideally, this behavior will allow package maintainers to precisely specify
+  # the version of the software they're packaging as in the following example:
+  #
+  #     $ git describe --match "1.2.*" > lib/hiera/VERSION
+  #     $ ruby -r hiera/version -e 'puts Hiera.version'
+  #     1.2.1-9-g9fda440
+  #
+  # @api public
+  #
+  # @return [String] containing the hiera version, e.g. "1.2.1"
+  def self.version
+    version_file = File.join(File.dirname(__FILE__), 'VERSION')
+    return @hiera_version if @hiera_version
+    if version = read_version_file(version_file)
+      @hiera_version = version
+    end
+    @hiera_version ||= VERSION
+  end
+
+  def self.version=(version)
+    @hiera_version = version
+  end
+
+  ##
+  # read_version_file reads the content of the "VERSION" file that lives in the
+  # same directory as this source code file.
+  #
+  # @api private
+  #
+  # @return [String] for example: "1.6.14-6-gea42046" or nil if the VERSION
+  #   file does not exist.
+  def self.read_version_file(path)
+    if File.exists?(path)
+      File.read(path).chomp
+    end
+  end
+  private_class_method :read_version_file
+end
diff --git a/metadata.yml b/metadata.yml
index fb04e08..88aa39c 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,52 +1,49 @@
---- !ruby/object:Gem::Specification 
+--- !ruby/object:Gem::Specification
 name: hiera
-version: !ruby/object:Gem::Version 
-  hash: 29
+version: !ruby/object:Gem::Version
+  version: 1.3.0
   prerelease: 
-  segments: 
-  - 1
-  - 2
-  - 1
-  version: 1.2.1
 platform: ruby
-authors: 
+authors:
 - Puppet Labs
 autorequire: 
 bindir: bin
 cert_chain: []
-
-date: 2013-04-18 00:00:00 Z
-dependencies: 
-- !ruby/object:Gem::Dependency 
+date: 2013-11-21 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
   name: json_pure
-  prerelease: false
-  requirement: &id001 !ruby/object:Gem::Requirement 
+  requirement: !ruby/object:Gem::Requirement
     none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 3
-        segments: 
-        - 0
-        version: "0"
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
   type: :runtime
-  version_requirements: *id001
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
 description: A pluggable data store for hierarcical data
 email: info at puppetlabs.com
-executables: 
+executables:
 - hiera
 extensions: []
-
 extra_rdoc_files: []
-
-files: 
+files:
 - bin/hiera
 - lib/hiera/puppet_logger.rb
-- lib/hiera/recursive_lookup.rb
 - lib/hiera/console_logger.rb
 - lib/hiera/filecache.rb
+- lib/hiera/version.rb
 - lib/hiera/fallback_logger.rb
+- lib/hiera/error.rb
+- lib/hiera/interpolate.rb
 - lib/hiera/util.rb
+- lib/hiera/recursive_guard.rb
 - lib/hiera/backend.rb
 - lib/hiera/noop_logger.rb
 - lib/hiera/config.rb
@@ -58,6 +55,7 @@ files:
 - LICENSE
 - spec/unit/util_spec.rb
 - spec/unit/puppet_logger_spec.rb
+- spec/unit/version_spec.rb
 - spec/unit/config_spec.rb
 - spec/unit/backend_spec.rb
 - spec/unit/filecache_spec.rb
@@ -69,40 +67,32 @@ files:
 - spec/spec_helper.rb
 homepage: https://github.com/puppetlabs/hiera
 licenses: []
-
 post_install_message: 
 rdoc_options: []
-
-require_paths: 
+require_paths:
 - lib
-required_ruby_version: !ruby/object:Gem::Requirement 
+required_ruby_version: !ruby/object:Gem::Requirement
   none: false
-  requirements: 
-  - - ">="
-    - !ruby/object:Gem::Version 
-      hash: 3
-      segments: 
-      - 0
-      version: "0"
-required_rubygems_version: !ruby/object:Gem::Requirement 
+  requirements:
+  - - ! '>='
+    - !ruby/object:Gem::Version
+      version: '0'
+required_rubygems_version: !ruby/object:Gem::Requirement
   none: false
-  requirements: 
-  - - ">="
-    - !ruby/object:Gem::Version 
-      hash: 3
-      segments: 
-      - 0
-      version: "0"
+  requirements:
+  - - ! '>='
+    - !ruby/object:Gem::Version
+      version: '0'
 requirements: []
-
 rubyforge_project: 
-rubygems_version: 1.8.24
+rubygems_version: 1.8.23
 signing_key: 
 specification_version: 3
 summary: Light weight hierarchical data store
-test_files: 
+test_files:
 - spec/unit/util_spec.rb
 - spec/unit/puppet_logger_spec.rb
+- spec/unit/version_spec.rb
 - spec/unit/config_spec.rb
 - spec/unit/backend_spec.rb
 - spec/unit/filecache_spec.rb
diff --git a/spec/unit/backend/json_backend_spec.rb b/spec/unit/backend/json_backend_spec.rb
index 57497de..fecc723 100644
--- a/spec/unit/backend/json_backend_spec.rb
+++ b/spec/unit/backend/json_backend_spec.rb
@@ -33,7 +33,7 @@ class Hiera
           Backend.expects(:datafile).with(:json, {}, "one", "json").returns("/nonexisting/one.json").times(3)
           File.stubs(:exist?).with("/nonexisting/one.json").returns(true)
 
-          @cache.expects(:read).with("/nonexisting/one.json", Hash, {}).returns({"stringval" => "string", "boolval" => true, "numericval" => 1}).times(3)
+          @cache.expects(:read_file).with("/nonexisting/one.json", Hash).returns({"stringval" => "string", "boolval" => true, "numericval" => 1}).times(3)
 
           @backend.lookup("stringval", {}, nil, :priority).should == "string"
           @backend.lookup("boolval", {}, nil, :priority).should == true
@@ -47,7 +47,7 @@ class Hiera
           Backend.expects(:datafile).with(:json, scope, "two", "json").never
 
           File.stubs(:exist?).with("/nonexisting/one.json").returns(true)
-          @cache.expects(:read).with("/nonexisting/one.json", Hash, {}).returns({"key" => "test_%{rspec}"})
+          @cache.expects(:read_file).with("/nonexisting/one.json", Hash).returns({"key" => "test_%{rspec}"})
 
           @backend.lookup("key", scope, nil, :priority).should == "test_test"
         end
@@ -63,8 +63,8 @@ class Hiera
           File.expects(:exist?).with("/nonexisting/one.json").returns(true)
           File.expects(:exist?).with("/nonexisting/two.json").returns(true)
 
-          @cache.expects(:read).with("/nonexisting/one.json", Hash, {}).returns({"key" => "answer"})
-          @cache.expects(:read).with("/nonexisting/two.json", Hash, {}).returns({"key" => "answer"})
+          @cache.expects(:read_file).with("/nonexisting/one.json", Hash).returns({"key" => "answer"})
+          @cache.expects(:read_file).with("/nonexisting/two.json", Hash).returns({"key" => "answer"})
 
           @backend.lookup("key", {}, nil, :array).should == ["answer", "answer"]
         end
@@ -75,7 +75,7 @@ class Hiera
           Backend.expects(:datafile).with(:json, {"rspec" => "test"}, "one", "json").returns("/nonexisting/one.json")
 
           File.expects(:exist?).with("/nonexisting/one.json").returns(true)
-          @cache.expects(:read).with("/nonexisting/one.json", Hash, {}).returns({"key" => "test_%{rspec}"})
+          @cache.expects(:read_file).with("/nonexisting/one.json", Hash).returns({"key" => "test_%{rspec}"})
 
           @backend.lookup("key", {"rspec" => "test"}, nil, :priority).should == "test_test"
         end
diff --git a/spec/unit/backend/yaml_backend_spec.rb b/spec/unit/backend/yaml_backend_spec.rb
index 72048c2..2dee576 100644
--- a/spec/unit/backend/yaml_backend_spec.rb
+++ b/spec/unit/backend/yaml_backend_spec.rb
@@ -32,7 +32,7 @@ class Hiera
           Backend.expects(:datasources).multiple_yields(["one"], ["two"])
           Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns("/nonexisting/one.yaml")
           Backend.expects(:datafile).with(:yaml, {}, "two", "yaml").returns(nil).never
-          @cache.expects(:read).with("/nonexisting/one.yaml", Hash, {}).returns({"key"=>"answer"})
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>"answer"})
           File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
 
           @backend.lookup("key", {}, nil, :priority).should == "answer"
@@ -50,7 +50,7 @@ class Hiera
           Backend.expects(:datasources).multiple_yields(["one"])
           Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns("/nonexisting/one.yaml")
           File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
-          @cache.expects(:read).with("/nonexisting/one.yaml", Hash, {}).returns({})
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({})
 
           @backend.lookup("key", {}, nil, :priority).should be_nil
         end
@@ -62,8 +62,8 @@ class Hiera
           File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
           File.stubs(:exist?).with("/nonexisting/two.yaml").returns(true)
 
-          @cache.expects(:read).with("/nonexisting/one.yaml", Hash, {}).returns({"key"=>"answer"})
-          @cache.expects(:read).with("/nonexisting/two.yaml", Hash, {}).returns({"key"=>"answer"})
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>"answer"})
+          @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>"answer"})
 
           @backend.lookup("key", {}, nil, :array).should == ["answer", "answer"]
         end
@@ -75,8 +75,8 @@ class Hiera
           File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
           File.stubs(:exist?).with("/nonexisting/two.yaml").returns(true)
 
-          @cache.expects(:read).with("/nonexisting/one.yaml", Hash, {}).returns({})
-          @cache.expects(:read).with("/nonexisting/two.yaml", Hash, {}).returns({"key"=>{"a"=>"answer"}})
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({})
+          @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>{"a"=>"answer"}})
 
           @backend.lookup("key", {}, nil, :hash).should == {"a" => "answer"}
         end
@@ -88,8 +88,8 @@ class Hiera
           File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
           File.stubs(:exist?).with("/nonexisting/two.yaml").returns(true)
 
-          @cache.expects(:read).with("/nonexisting/one.yaml", Hash, {}).returns({"key"=>{"a"=>"answer"}})
-          @cache.expects(:read).with("/nonexisting/two.yaml", Hash, {}).returns({"key"=>{"b"=>"answer", "a"=>"wrong"}})
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>{"a"=>"answer"}})
+          @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>{"b"=>"answer", "a"=>"wrong"}})
 
           @backend.lookup("key", {}, nil, :hash).should == {"a" => "answer", "b" => "answer"}
         end
@@ -101,8 +101,8 @@ class Hiera
           File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
           File.stubs(:exist?).with("/nonexisting/two.yaml").returns(true)
 
-          @cache.expects(:read).with("/nonexisting/one.yaml", Hash, {}).returns({"key"=>["a", "answer"]})
-          @cache.expects(:read).with("/nonexisting/two.yaml", Hash, {}).returns({"key"=>{"a"=>"answer"}})
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>["a", "answer"]})
+          @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>{"a"=>"answer"}})
 
           expect {@backend.lookup("key", {}, nil, :array)}.to raise_error(Exception, "Hiera type mismatch: expected Array and got Hash")
         end
@@ -114,8 +114,8 @@ class Hiera
           File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
           File.stubs(:exist?).with("/nonexisting/two.yaml").returns(true)
 
-          @cache.expects(:read).with("/nonexisting/one.yaml", Hash, {}).returns({"key"=>{"a"=>"answer"}})
-          @cache.expects(:read).with("/nonexisting/two.yaml", Hash, {}).returns({"key"=>["a", "wrong"]})
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>{"a"=>"answer"}})
+          @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>["a", "wrong"]})
 
           expect { @backend.lookup("key", {}, nil, :hash) }.to raise_error(Exception, "Hiera type mismatch: expected Hash and got Array")
         end
@@ -125,7 +125,7 @@ class Hiera
           Backend.expects(:datafile).with(:yaml, {"rspec" => "test"}, "one", "yaml").returns("/nonexisting/one.yaml")
           File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
 
-          @cache.expects(:read).with("/nonexisting/one.yaml", Hash, {}).returns({"key"=>"test_%{rspec}"})
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>"test_%{rspec}"})
 
           @backend.lookup("key", {"rspec" => "test"}, nil, :priority).should == "test_test"
         end
@@ -137,7 +137,7 @@ class Hiera
 
           yaml = "---\nstringval: 'string'\nboolval: true\nnumericval: 1"
 
-          @cache.expects(:read).with("/nonexisting/one.yaml", Hash, {}).times(3).returns({"boolval"=>true, "numericval"=>1, "stringval"=>"string"})
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).times(3).returns({"boolval"=>true, "numericval"=>1, "stringval"=>"string"})
 
           @backend.lookup("stringval", {}, nil, :priority).should == "string"
           @backend.lookup("boolval", {}, nil, :priority).should == true
diff --git a/spec/unit/backend_spec.rb b/spec/unit/backend_spec.rb
index d5147c2..b70f87a 100644
--- a/spec/unit/backend_spec.rb
+++ b/spec/unit/backend_spec.rb
@@ -5,15 +5,30 @@ class Hiera
   describe Backend do
     describe "#datadir" do
       it "interpolates any values in the configured value" do
-        Config.load({:rspec => {:datadir => "/tmp"}})
-        Backend.expects(:parse_string).with("/tmp", {})
-        Backend.datadir(:rspec, {})
+        Config.load({:rspec => {:datadir => "/tmp/%{interpolate}"}})
+
+        dir = Backend.datadir(:rspec, { "interpolate" => "my_data" })
+
+        dir.should == "/tmp/my_data"
       end
 
       it "defaults to a directory in var" do
         Config.load({})
-        Backend.expects(:parse_string).with(Hiera::Util.var_dir, {})
-        Backend.datadir(:rspec, {})
+        Backend.datadir(:rspec, {}).should == Hiera::Util.var_dir
+
+        Config.load({:rspec => nil})
+        Backend.datadir(:rspec, {}).should == Hiera::Util.var_dir
+
+        Config.load({:rspec => {}})
+        Backend.datadir(:rspec, {}).should == Hiera::Util.var_dir
+      end
+
+      it "fails when the datadir is an array" do
+        Config.load({:rspec => {:datadir => []}})
+
+        expect do
+          Backend.datadir(:rspec, {})
+        end.to raise_error(Hiera::InvalidConfigurationError, /datadir for rspec cannot be an array/)
       end
     end
 
@@ -103,11 +118,19 @@ class Hiera
         Backend.parse_string(input, {}).should == input
       end
 
-      it "replaces interpolations with data looked up in the scope" do
-        input = "replace %{part1} and %{part2}"
-        scope = {"part1" => "value of part1", "part2" => "value of part2"}
+      @scope_interpolation_tests = {
+        "replace %{part1} and %{part2}" =>
+          "replace value of part1 and value of part2",
+        "replace %{scope('part1')} and %{scope('part2')}" =>
+          "replace value of part1 and value of part2"
+      }
+
+      @scope_interpolation_tests.each do |input, expected|
+        it "replaces interpolations with data looked up in the scope" do
+          scope = {"part1" => "value of part1", "part2" => "value of part2"}
 
-        Backend.parse_string(input, scope).should == "replace value of part1 and value of part2"
+          Backend.parse_string(input, scope).should == expected
+        end
       end
 
       it "replaces interpolations with data looked up in extra_data when scope does not contain the value" do
@@ -120,14 +143,27 @@ class Hiera
         Backend.parse_string(input, {"rspec" => "test"}, {"rspec" => "fail"}).should == "test_test_test"
       end
 
-      it "interprets nil in scope as a non-value" do
-        input = "test_%{rspec}_test"
-        Backend.parse_string(input, {"rspec" => nil}).should == "test__test"
+      @interprets_nil_in_scope_tests = {
+        "test_%{rspec}_test" => "test__test",
+        "test_%{scope('rspec')}_test" => "test__test"
+      }
+
+      @interprets_nil_in_scope_tests.each do |input, expected|
+        it "interprets nil in scope as a non-value" do
+          Backend.parse_string(input, {"rspec" => nil}).should == expected
+        end
       end
 
-      it "interprets false in scope as a real value" do
-        input = "test_%{rspec}_test"
-        Backend.parse_string(input, {"rspec" => false}).should == "test_false_test"
+      @interprets_false_in_scope_tests = {
+        "test_%{rspec}_test" => "test_false_test",
+        "test_%{scope('rspec')}_test" => "test_false_test"
+      }
+
+      @interprets_false_in_scope_tests.each do |input, expected|
+        it "interprets false in scope as a real value" do
+          input = "test_%{scope('rspec')}_test"
+          Backend.parse_string(input, {"rspec" => false}).should == expected
+        end
       end
 
       it "interprets false in extra_data as a real value" do
@@ -140,9 +176,15 @@ class Hiera
         Backend.parse_string(input, {}, {"rspec" => nil}).should == "test__test"
       end
 
-      it "interprets :undefined in scope as a non-value" do
-        input = "test_%{rspec}_test"
-        Backend.parse_string(input, {"rspec" => :undefined}).should == "test__test"
+      @interprets_undefined_in_scope_tests = {
+        "test_%{rspec}_test" => "test__test",
+        "test_%{scope('rspec')}_test" => "test__test"
+      }
+
+      @interprets_undefined_in_scope_tests.each do |input, expected|
+        it "interprets :undefined in scope as a non-value" do
+          Backend.parse_string(input, {"rspec" => :undefined}).should == expected
+        end
       end
 
       it "uses the value from extra_data when scope is :undefined" do
@@ -150,24 +192,51 @@ class Hiera
         Backend.parse_string(input, {"rspec" => :undefined}, { "rspec" => "extra" }).should == "test_extra_test"
       end
 
-      it "looks up the interpolated value exactly as it appears in the input" do
-        input = "test_%{::rspec::data}_test"
-        Backend.parse_string(input, {"::rspec::data" => "value"}).should == "test_value_test"
+      @exact_lookup_tests = {
+        "test_%{::rspec::data}_test" => "test_value_test",
+        "test_%{scope('::rspec::data')}_test" => "test_value_test"
+      }
+
+      @exact_lookup_tests.each do |input, expected|
+        it "looks up the interpolated value exactly as it appears in the input" do
+          Backend.parse_string(input, {"::rspec::data" => "value"}).should == expected
+        end
       end
 
-      it "does not remove any surrounding whitespace when parsing the key to lookup" do
-        input = "test_%{\trspec::data }_test"
-        Backend.parse_string(input, {"\trspec::data " => "value"}).should == "test_value_test"
+      @surrounding_whitespace_tests = {
+        "test_%{\trspec::data }_test" => "test_value_test",
+        "test_%{scope('\trspec::data ')}_test" => "test_value_test"
+      }
+      @surrounding_whitespace_tests.each do |input, expected|
+        it "does not remove any surrounding whitespace when parsing the key to lookup" do
+          Backend.parse_string(input, {"\trspec::data " => "value"}).should == expected
+        end
       end
 
-      it "does not try removing leading :: when a full lookup fails (#17434)" do
-        input = "test_%{::rspec::data}_test"
-        Backend.parse_string(input, {"rspec::data" => "value"}).should == "test__test"
+      @leading_double_colon_tests = {
+        "test_%{::rspec::data}_test" => "test__test",
+        "test_%{scope('::rspec::data')}_test" => "test__test"
+      }
+
+      @leading_double_colon_tests.each do |input, expected|
+        it "does not try removing leading :: when a full lookup fails (#17434)" do
+          Backend.parse_string(input, {"rspec::data" => "value"}).should == expected
+        end
+      end
+
+      @double_colon_key_tests = {
+        "test_%{::rspec::data}_test" => "test__test",
+        "test_%{scope('::rspec::data')}_test" => "test__test"
+      }
+      @double_colon_key_tests.each do |input, expected|
+        it "does not try removing leading sections separated by :: when a full lookup fails (#17434)" do
+          Backend.parse_string(input, {"data" => "value"}).should == expected
+        end
       end
 
-      it "does not try removing leading sections separated by :: when a full lookup fails (#17434)" do
-        input = "test_%{::rspec::data}_test"
-        Backend.parse_string(input, {"data" => "value"}).should == "test__test"
+      it "does not try removing unknown, preceeding characters when looking up values" do
+        input = "test_%{$var}_test"
+        Backend.parse_string(input, {"$var" => "value"}).should == "test_value_test"
       end
 
       it "looks up recursively" do
@@ -181,7 +250,17 @@ class Hiera
         input = "test_%{first}_test"
         expect do
           Backend.parse_string(input, scope)
-        end.to raise_error Exception, "Interpolation loop detected in [first, second]"
+        end.to raise_error Hiera::InterpolationLoop, "Detected in [first, second]"
+      end
+
+      it "replaces hiera interpolations with data looked up in hiera" do
+        input = "%{hiera('key1')}"
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("key1", scope, nil, :priority).returns("answer")
+
+        Backend.parse_string(input, scope).should == "answer"
       end
     end
 
@@ -201,11 +280,93 @@ class Hiera
         Backend.parse_answer(input, {"rspec" => "test"}).should == {"foo"=>"test_test_test", "bar"=>"test_test_test"}
       end
 
+      it "interpolates string in hash keys" do
+        input = {"%{rspec}" => "test"}
+        Backend.parse_answer(input, {"rspec" => "foo"}).should == {"foo"=>"test"}
+      end
+
+      it "interpolates strings in nested hash keys" do
+        input = {"topkey" => {"%{rspec}" => "test"}}
+        Backend.parse_answer(input, {"rspec" => "foo"}).should == {"topkey"=>{"foo" => "test"}}
+      end
+
       it "interpolates strings in a mixed structure of arrays and hashes" do
         input = {"foo" => "test_%{rspec}_test", "bar" => ["test_%{rspec}_test", "test_%{rspec}_test"]}
         Backend.parse_answer(input, {"rspec" => "test"}).should == {"foo"=>"test_test_test", "bar"=>["test_test_test", "test_test_test"]}
       end
 
+      it "interpolates hiera lookups values in strings" do
+        input = "test_%{hiera('rspec')}_test"
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("test")
+        Backend.parse_answer(input, scope).should == "test_test_test"
+      end
+
+      it "interpolates hiera lookups in each string in an array" do
+        input = ["test_%{hiera('rspec')}_test", "test_%{hiera('rspec')}_test", ["test_%{hiera('rspec')}_test"]]
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("test")
+        Backend.parse_answer(input, scope).should == ["test_test_test", "test_test_test", ["test_test_test"]]
+      end
+
+      it "interpolates hiera lookups in each string in a hash" do
+        input = {"foo" => "test_%{hiera('rspec')}_test", "bar" => "test_%{hiera('rspec')}_test"}
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("test")
+        Backend.parse_answer(input, scope).should == {"foo"=>"test_test_test", "bar"=>"test_test_test"}
+      end
+
+      it "interpolates hiera lookups in string in hash keys" do
+        input = {"%{hiera('rspec')}" => "test"}
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("foo")
+        Backend.parse_answer(input, scope).should == {"foo"=>"test"}
+      end
+
+      it "interpolates hiera lookups in strings in nested hash keys" do
+        input = {"topkey" => {"%{hiera('rspec')}" => "test"}}
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("foo")
+        Backend.parse_answer(input, scope).should == {"topkey"=>{"foo" => "test"}}
+      end
+
+      it "interpolates hiera lookups in strings in a mixed structure of arrays and hashes" do
+        input = {"foo" => "test_%{hiera('rspec')}_test", "bar" => ["test_%{hiera('rspec')}_test", "test_%{hiera('rspec')}_test"]}
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("test")
+        Backend.parse_answer(input, scope).should == {"foo"=>"test_test_test", "bar"=>["test_test_test", "test_test_test"]}
+      end
+
+      it "interpolates hiera lookups and scope lookups in the same string" do
+        input = {"foo" => "test_%{hiera('rspec')}_test", "bar" => "test_%{rspec2}_test"}
+        scope = {"rspec2" => "scope_rspec"}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("hiera_rspec")
+        Backend.parse_answer(input, scope).should == {"foo"=>"test_hiera_rspec_test", "bar"=>"test_scope_rspec_test"}
+      end
+
+      it "interpolates hiera and scope lookups with the same lookup query in a single string" do
+        input =  "test_%{hiera('rspec')}_test_%{rspec}"
+        scope = {"rspec" => "scope_rspec"}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("hiera_rspec")
+        Backend.parse_answer(input, scope).should == "test_hiera_rspec_test_scope_rspec"
+      end
+
       it "passes integers unchanged" do
         input = 1
         Backend.parse_answer(input, {"rspec" => "test"}).should == 1
@@ -225,6 +386,12 @@ class Hiera
         input = false
         Backend.parse_answer(input, {"rspec" => "test"}).should == false
       end
+
+      it "interpolates lookups using single or double quotes" do
+        input =  "test_%{scope(\"rspec\")}_test_%{scope('rspec')}"
+        scope = {"rspec" => "scope_rspec"}
+        Backend.parse_answer(input, scope).should == "test_scope_rspec_test_scope_rspec"
+      end
     end
 
     describe "#resolve_answer" do
@@ -244,6 +411,7 @@ class Hiera
       end
 
       it "caches loaded backends" do
+        Backend.clear!
         Hiera.expects(:debug).with(regexp_matches(/Hiera YAML backend starting/)).once
 
         Config.load({:yaml => {:datadir => "/tmp"}})
diff --git a/spec/unit/filecache_spec.rb b/spec/unit/filecache_spec.rb
index 2a7dd72..ae9a6a7 100644
--- a/spec/unit/filecache_spec.rb
+++ b/spec/unit/filecache_spec.rb
@@ -1,62 +1,141 @@
 require 'spec_helper'
+require 'tmpdir'
 
 class Hiera
   describe Filecache do
     before do
-      File.stubs(:exist?).returns(true)
       @cache = Filecache.new
     end
 
+    def write_file(file, contents)
+      File.open(file, 'w') do |f|
+        f.write(contents)
+      end
+    end
+
     describe "#read" do
-      it "should cache and read data" do
-        File.expects(:read).with("/nonexisting").returns("text")
-        @cache.expects(:path_metadata).returns(File.stat(__FILE__)).once
-        @cache.expects(:stale?).once.returns(false)
+      it "reads data from a file" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+
+          @cache.read(file).should == "my data"
+        end
+      end
+
+      it "rereads data when the file changes" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+          @cache.read(file).should == "my data"
 
-        @cache.read("/nonexisting").should == "text"
-        @cache.read("/nonexisting").should == "text"
+          write_file(file, "changed data")
+          @cache.read(file).should == "changed data"
+        end
       end
 
-      it "should support validating return types and setting defaults" do
-        File.expects(:read).with("/nonexisting").returns('{"rspec":1}')
+      it "uses the provided default when the type does not match the expected type" do
+        Hiera.expects(:debug).with(regexp_matches(/String.*not.*Hash, setting defaults/))
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+          data = @cache.read(file, Hash, { :testing => "hash" }) do |data|
+            "a string"
+          end
 
-        @cache.expects(:path_metadata).returns(File.stat(__FILE__))
+          data.should == { :testing => "hash" }
+        end
+      end
 
-        Hiera.expects(:debug).with(regexp_matches(/is not a Hash, setting defaults/))
+      it "traps any errors from the block and uses the default value" do
+        Hiera.expects(:debug).with(regexp_matches(/Reading data.*failed:.*testing error/))
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+          data = @cache.read(file, Hash, { :testing => "hash" }) do |data|
+            raise ArgumentError, "testing error"
+          end
 
-        # return bogus data on purpose, triggers setting defaults
-        data = @cache.read("/nonexisting", Hash, {"rspec" => 1}) do |data|
-          nil
+          data.should == { :testing => "hash" }
         end
+      end
 
-        data.should == {"rspec" => 1}
+      it "raises an error when there is no default given and there is a problem" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+
+          expect do
+            @cache.read(file, Hash) do |data|
+              raise ArgumentError, "testing error"
+            end
+          end.to raise_error(ArgumentError, "testing error")
+        end
       end
     end
 
-    describe "#stale?" do
-      it "should return false when the file has not changed" do
-        stat = File.stat(__FILE__)
+    describe "#read_file" do
+      it "reads data from a file" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
 
-        @cache.stubs(:path_metadata).returns(stat)
-        @cache.stale?("/nonexisting").should == true
-        @cache.stale?("/nonexisting").should == false
+          @cache.read_file(file).should == "my data"
+        end
       end
 
-      it "should update and return true when the file changed" do
-        @cache.expects(:path_metadata).returns({:inode => 1, :mtime => Time.now, :size => 1})
-        @cache.stale?("/nonexisting").should == true
-        @cache.expects(:path_metadata).returns({:inode => 2, :mtime => Time.now, :size => 1})
-        @cache.stale?("/nonexisting").should == true
+      it "rereads data when the file changes" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+          @cache.read_file(file).should == "my data"
+
+          write_file(file, "changed data")
+          @cache.read_file(file).should == "changed data"
+        end
       end
-    end
 
-    describe "#path_metadata" do
-      it "should return the right data" do
-        stat = File.stat(__FILE__)
+      it "errors when the type does not match the expected type" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
 
-        File.expects(:stat).with("/nonexisting").returns(stat)
+          expect do
+            @cache.read_file(file, Hash) do |data|
+              "a string"
+            end
+          end.to raise_error(TypeError)
+        end
+      end
+
+      it "converts the read data using the block" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
 
-        @cache.path_metadata("/nonexisting").should == {:inode => stat.ino, :mtime => stat.mtime, :size => stat.size}
+          @cache.read_file(file, Hash) do |data|
+            { :data => data }
+          end.should == { :data => "my data" }
+        end
+      end
+
+      it "errors when the file does not exist" do
+        expect do
+          @cache.read_file("/notexist")
+        end.to raise_error(Errno::ENOENT)
+      end
+
+      it "propogates any errors from the block" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+
+          expect do
+            @cache.read_file(file) do |data|
+              raise ArgumentError, "testing error"
+            end
+          end.to raise_error(ArgumentError, "testing error")
+        end
       end
     end
   end
diff --git a/spec/unit/version_spec.rb b/spec/unit/version_spec.rb
new file mode 100644
index 0000000..bc6e19a
--- /dev/null
+++ b/spec/unit/version_spec.rb
@@ -0,0 +1,44 @@
+require "spec_helper"
+require "hiera/version"
+require 'pathname'
+
+describe "Hiera.version Public API" do
+  subject() { Hiera }
+
+  before :each do
+    Hiera.instance_eval do
+      if @hiera_version
+        @hiera_version = nil
+      end
+    end
+  end
+
+  context "without a VERSION file" do
+    before :each do
+      subject.stubs(:read_version_file).returns(nil)
+    end
+
+    it "is Hiera::VERSION" do
+      subject.version.should == Hiera::VERSION
+    end
+    it "respects the version= setter" do
+      subject.version = '1.2.3'
+      subject.version.should == '1.2.3'
+    end
+  end
+
+  context "with a VERSION file" do
+    it "is the content of the file" do
+      subject.expects(:read_version_file).with() do |path|
+        pathname = Pathname.new(path)
+        pathname.basename.to_s == "VERSION"
+      end.returns('1.2.1-9-g9fda440')
+
+      subject.version.should == '1.2.1-9-g9fda440'
+    end
+    it "respects the version= setter" do
+      subject.version = '1.2.3'
+      subject.version.should == '1.2.3'
+    end
+  end
+end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-hiera.git



More information about the Pkg-ruby-extras-commits mailing list