diff options
author | John Keiser <jkeiser@opscode.com> | 2013-12-02 23:43:58 -0800 |
---|---|---|
committer | John Keiser <jkeiser@opscode.com> | 2013-12-02 23:43:58 -0800 |
commit | 24eb243b25d75f8cbd1bf2955509bad6dfe3af5b (patch) | |
tree | 6da7f23302c8f33deec0540db8c714bfaad2f725 | |
parent | fa0ed189b89a279ea9ce0b709f90a5704835952a (diff) | |
download | mixlib-config-24eb243b25d75f8cbd1bf2955509bad6dfe3af5b.tar.gz |
Add save/restore for tests/serialization
-rw-r--r-- | lib/mixlib/config.rb | 94 | ||||
-rw-r--r-- | lib/mixlib/config/configurable.rb | 24 | ||||
-rw-r--r-- | spec/mixlib/config_spec.rb | 284 |
3 files changed, 393 insertions, 9 deletions
diff --git a/lib/mixlib/config.rb b/lib/mixlib/config.rb index 138eb5f..7b96fe0 100644 --- a/lib/mixlib/config.rb +++ b/lib/mixlib/config.rb @@ -31,7 +31,7 @@ module Mixlib class << base; attr_accessor :config_parent; end base.configuration = Hash.new base.configurables = Hash.new - base.config_contexts = Array.new + base.config_contexts = Hash.new end # Loads a given ruby file, and runs instance_eval against it in the context of the current @@ -105,21 +105,97 @@ module Mixlib # Resets all config options to their defaults. def reset self.configuration = Hash.new - self.config_contexts.each { |config_context| config_context.reset } + self.config_contexts.values.each { |config_context| config_context.reset } + end + + # Makes a copy of any non-default values. + # + # This returns a shallow copy of the hash; while the hash itself is + # duplicated a la dup, modifying data inside arrays and hashes may modify + # the original Config object. + # + # === Returns + # + # Hash of values the user has set. + # + # For example, this config class: + # + # class MyConfig << Mixlib::Config + # default :will_be_set, 1 + # default :will_be_set_to_default, 1 + # default :will_not_be_set, 1 + # configurable(:computed_value) { |x| x*2 } + # config_context :group do + # default :will_not_be_set, 1 + # end + # config_context :group_never_set + # end + # + # MyConfig.x = 2 + # MyConfig.will_be_set = 2 + # MyConfig.will_be_set_to_default = 1 + # MyConfig.computed_value = 2 + # MyConfig.group.x = 3 + # MyConfig.save + # + # produces this: + # + # { + # :x => 2, + # :will_be_set => 2, + # :will_be_set_to_default => 1, + # :computed_value => 4, + # :group => { + # :x => 3 + # } + # } + # + def save(include_defaults = false) + result = self.configuration.dup + (self.configurables.keys - result.keys).each do |missing_default| + # Ask any configurables to save themselves into the result array + self.configurables[missing_default].save(self.configuration, result, include_defaults) + end + result + end + + # Restore non-default values from the given hash. + # + # This method is the equivalent of +reset+ followed by +merge!(hash)+. + # + # === Parameters + # hash<Hash>: a hash in the same format as output by save. + # + # === Returns + # self + def restore(hash) + reset + merge!(hash) end # Merge an incoming hash with our config options # # === Parameters - # hash<Hash>:: The incoming hash + # hash<Hash>: a hash in the same format as output by save. # # === Returns - # result of Hash#merge! + # self def merge!(hash) - self.configuration.merge!(hash) + hash.each do |key, value| + if self.config_contexts.has_key?(key) + # Grab the config context and let internal_get cache it if so desired + internal_get(key).restore(value) + else + self.configuration[key] = value + end + end + self end - # Return the set of config hash keys + # Return the set of config hash keys. + # This *only* returns hash keys which have been set by the user. In future + # versions this will likely be removed in favor of something more explicit. + # For now though, we want this to match has_key? # # === Returns # result of Hash#keys @@ -128,11 +204,13 @@ module Mixlib end # Creates a shallow copy of the internal hash + # NOTE: remove this in 3.0 in favor of save. This is completely useless + # with default values and configuration_context. # # === Returns # result of Hash#dup def hash_dup - self.configuration.dup + save end # metaprogramming to ensure that the slot for method_symbol @@ -210,7 +288,7 @@ module Mixlib context = Class.new context.extend(::Mixlib::Config) context.config_parent = self - config_contexts << context + config_contexts[symbol] = context if block context.instance_eval(&block) end diff --git a/lib/mixlib/config/configurable.rb b/lib/mixlib/config/configurable.rb index 01de2bd..02081df 100644 --- a/lib/mixlib/config/configurable.rb +++ b/lib/mixlib/config/configurable.rb @@ -22,11 +22,13 @@ module Mixlib def initialize(symbol) @symbol = symbol @default = nil + @has_default = false @default_value = nil @writes_value = nil end def defaults_to(default_value = nil, &block) + @has_default = true @default = block @default_value = default_value self @@ -42,9 +44,13 @@ module Mixlib config[@symbol] elsif @default @default.call + elsif @default_value.is_a?(::Mixlib::Config) + # Don't dup config_contexts + @default_value else begin # Some things cannot be dup'd, and you won't know this till after the fact + # because all values implement dup config[@symbol] = @default_value.dup rescue TypeError @default_value @@ -55,6 +61,24 @@ module Mixlib def set(config, value) config[@symbol] = @writes_value ? @writes_value.call(value) : value end + + # Sets the saved value into the result hash. Don't bother stashing defaults away or duping them. + def save(config, result, include_defaults) + if config.has_key?(@symbol) + result[@symbol] = config[@symbol] + elsif @default_value.is_a?(::Mixlib::Config) + saved_context = @default_value.save(include_defaults) + if saved_context != {} || include_defaults + result[@symbol] = saved_context + end + elsif include_defaults && @has_default + if @default + result[@symbol] = @default.call + else + result[@symbol] = @default_value + end + end + end end end end diff --git a/spec/mixlib/config_spec.rb b/spec/mixlib/config_spec.rb index f955eae..b58dc97 100644 --- a/spec/mixlib/config_spec.rb +++ b/spec/mixlib/config_spec.rb @@ -275,6 +275,32 @@ describe Mixlib::Config do @klass.reset @klass.attr.should == 4 end + + it "save should not save anything for it" do + @klass.save.should == {} + end + + it "save with include_defaults should save all defaults" do + @klass.save(true).should == { :attr => 4 } + end + + it "save should save the new value if it gets set" do + @klass.attr 5 + (saved = @klass.save).should == { :attr => 5 } + @klass.reset + @klass.attr.should == 4 + @klass.restore(saved) + @klass.attr.should == 5 + end + + it "save should save the new value even if it is set to its default value" do + @klass.attr 4 + (saved = @klass.save).should == { :attr => 4 } + @klass.reset + @klass.save.should == {} + @klass.restore(saved) + @klass.save.should == { :attr => 4 } + end end describe "When config has a default value block" do @@ -283,7 +309,7 @@ describe Mixlib::Config do @klass.extend(::Mixlib::Config) @klass.class_eval do default :x, 4 - default(:attr) { x*2} + default(:attr) { x*2 } end end @@ -317,6 +343,32 @@ describe Mixlib::Config do @klass.reset @klass.attr.should == 8 end + + it "save should not save anything for it" do + @klass.save.should == {} + end + + it "save with include_defaults should save all defaults" do + @klass.save(true).should == { :attr => 8, :x => 4 } + end + + it "save should save the new value if it gets set" do + @klass.attr 5 + (saved = @klass.save).should == { :attr => 5 } + @klass.reset + @klass.attr.should == 8 + @klass.restore(saved) + @klass.attr.should == 5 + end + + it "save should save the new value even if it is set to its default value" do + @klass.attr 8 + (saved = @klass.save).should == { :attr => 8 } + @klass.reset + @klass.save.should == {} + @klass.restore(saved) + @klass.save.should == { :attr => 8 } + end end describe "When config has an array default value" do @@ -332,6 +384,32 @@ describe Mixlib::Config do @klass.reset @klass.attr.should == [] end + + it "save should not save anything for it" do + @klass.save.should == {} + end + + it "save with include_defaults should save all defaults" do + @klass.save(true).should == { :attr => [] } + end + + it "save should save the new value if it gets set" do + @klass.attr << 'x' + (saved = @klass.save).should == { :attr => [ 'x' ] } + @klass.reset + @klass.attr.should == [] + @klass.restore(saved) + @klass.attr.should == [ 'x' ] + end + + it "save should save the new value even if it is set to its default value" do + @klass.attr = [] + (saved = @klass.save).should == { :attr => [] } + @klass.reset + @klass.save.should == {} + @klass.restore(saved) + @klass.save.should == { :attr => [] } + end end describe "When config has a hash default value" do @@ -347,6 +425,32 @@ describe Mixlib::Config do @klass.reset @klass.attr[:x].should == nil end + + it "save should not save anything for it" do + @klass.save.should == {} + end + + it "save with include_defaults should save all defaults" do + @klass.save(true).should == { :attr => {} } + end + + it "save should save the new value if it gets set" do + @klass.attr[:hi] = 'lo' + (saved = @klass.save).should == { :attr => { :hi => 'lo' } } + @klass.reset + @klass.attr.should == {} + @klass.restore(saved) + @klass.save.should == { :attr => { :hi => 'lo' } } + end + + it "save should save the new value even if it is set to its default value" do + @klass.attr = {} + (saved = @klass.save).should == { :attr => {} } + @klass.reset + @klass.save.should == {} + @klass.restore(saved) + @klass.save.should == { :attr => {} } + end end describe "When config has a string default value" do @@ -362,6 +466,32 @@ describe Mixlib::Config do @klass.reset @klass.attr.should == 'hello' end + + it "save should not save anything for it" do + @klass.save.should == {} + end + + it "save with include_defaults should save all defaults" do + @klass.save(true).should == { :attr => 'hello' } + end + + it "save should save the new value if it gets set" do + @klass.attr << ' world' + (saved = @klass.save).should == { :attr => 'hello world' } + @klass.reset + @klass.attr.should == 'hello' + @klass.restore(saved) + @klass.attr.should == 'hello world' + end + + it "save should save the new value even if it is set to its default value" do + @klass.attr 'hello world' + (saved = @klass.save).should == { :attr => 'hello world' } + @klass.reset + @klass.save.should == {} + @klass.restore(saved) + @klass.save.should == { :attr => 'hello world' } + end end describe "When config has a a default value block" do @@ -398,6 +528,32 @@ describe Mixlib::Config do @klass.reset @klass.attr.should == 4 end + + it "save should not save anything for it" do + @klass.save.should == {} + end + + it "save with include_defaults should save all defaults" do + @klass.save(true).should == { :attr => 4 } + end + + it "save should save the new value if it gets set" do + @klass.attr 5 + (saved = @klass.save).should == { :attr => 5 } + @klass.reset + @klass.attr.should == 4 + @klass.restore(saved) + @klass.attr.should == 5 + end + + it "save should save the new value even if it is set to its default value" do + @klass.attr 4 + (saved = @klass.save).should == { :attr => 4 } + @klass.reset + @klass.save.should == {} + @klass.restore(saved) + @klass.save.should == { :attr => 4 } + end end describe "When a configurable exists with writer and default value" do @@ -449,6 +605,32 @@ describe Mixlib::Config do @klass.reset @klass.attr.should == 4 end + + it "save should not save anything for it" do + @klass.save.should == {} + end + + it "save with include_defaults should save all defaults" do + @klass.save(true).should == { :attr => 4 } + end + + it "save should save the new value if it gets set" do + @klass.attr 5 + (saved = @klass.save).should == { :attr => 10 } + @klass.reset + @klass.attr.should == 4 + @klass.restore(saved) + @klass.attr.should == 10 + end + + it "save should save the new value even if it is set to its default value" do + @klass.attr 4 + (saved = @klass.save).should == { :attr => 8 } + @klass.reset + @klass.save.should == {} + @klass.restore(saved) + @klass.save.should == { :attr => 8 } + end end describe "When a configurable exists with writer and default value set in chained form" do @@ -497,6 +679,32 @@ describe Mixlib::Config do @klass.reset @klass.attr.should == 4 end + + it "save should not save anything for it" do + @klass.save.should == {} + end + + it "save with include_defaults should save all defaults" do + @klass.save(true).should == { :attr => 4 } + end + + it "save should save the new value if it gets set" do + @klass.attr 5 + (saved = @klass.save).should == { :attr => 10 } + @klass.reset + @klass.attr.should == 4 + @klass.restore(saved) + @klass.attr.should == 10 + end + + it "save should save the new value even if it is set to its default value" do + @klass.attr 2 + (saved = @klass.save).should == { :attr => 4 } + @klass.reset + @klass.save.should == {} + @klass.restore(saved) + @klass.save.should == { :attr => 4 } + end end describe "When a configurable exists with a context" do @@ -532,6 +740,24 @@ describe Mixlib::Config do @klass.reset @klass.blah.x.should == 5 end + + it "save should not save anything for it by default" do + @klass.save.should == {} + end + + it "save with include_defaults should save all defaults" do + @klass.save(true).should == { :blah => { :x => 5 } } + end + + it "save should save any new values that are set in the context" do + @klass.blah.x = 10 + (saved = @klass.save).should == { :blah => { :x => 10 } } + @klass.reset + @klass.blah.x.should == 5 + @klass.restore(saved) + @klass.blah.x.should == 10 + @klass.save.should == { :blah => { :x => 10 } } + end end describe "When a configurable exists with a nested context" do @@ -569,6 +795,62 @@ describe Mixlib::Config do @klass.reset @klass.blah.yarr.x.should == 5 end + + it "save should not save anything for it by default" do + @klass.save.should == {} + end + + it "save with include_defaults should save all defaults" do + @klass.save(true).should == { :blah => { :yarr => { :x => 5 } } } + end + + it "save should save any new values that are set in the context" do + @klass.blah.yarr.x = 10 + (saved = @klass.save).should == { :blah => { :yarr => { :x => 10 } } } + @klass.reset + @klass.blah.yarr.x.should == 5 + @klass.restore(saved) + @klass.blah.yarr.x.should == 10 + @klass.save.should == { :blah => { :yarr => { :x => 10 } } } + end + end + + describe "When a config_context with no defaulted values exists" do + before :each do + @klass = Class.new + @klass.extend(::Mixlib::Config) + @klass.class_eval do + config_context(:blah) do + configurable(:x) + end + end + end + + it "save does not save the hash for the config_context" do + @klass.save.should == {} + end + + it "save with defaults saves the hash for the config_context" do + @klass.save(true).should == { :blah => {} } + end + end + + describe "When a config_context with no configurables exists" do + before :each do + @klass = Class.new + @klass.extend(::Mixlib::Config) + @klass.class_eval do + config_context(:blah) + end + end + + it "save does not save the hash for the config_context" do + @klass.save.should == {} + end + + it "save with defaults saves the hash for the config_context" do + @klass.save(true).should == { :blah => {} } + end end describe "When a nested context has strict mode on" do |