Skip to content

Commit 1f4e44c

Browse files
author
Daniel Pittman
committed
instance_variables changes return type between 1.8 and 1.9
The return type of `instance_variables` changes between Ruby 1.8 and 1.9 releases; it used to return an array of strings in the form "@foo", but now returns an array of symbols in the form :@foo. Nothing else in the stack cares which form you get - you can pass the string or symbol to things like `instance_variable_set` and they will work transparently. Having the same form in all releases of Puppet is a win, though, so we pick a unification and enforce than on all releases. That way developers who do set math on them (eg: for YAML rendering) don't have to handle the distinction themselves. In the sane tradition, we bring older releases into conformance with newer releases, so we return symbols rather than strings, to be more like the future versions of Ruby are. We also carefully support reloading, by only wrapping when we don't already have the original version of the method aliased away somewhere. This includes a second monkey-patch, on the `resolv` library that ships with Ruby 1.8. This is the only bit of code in the core stack that made assumptions about the return format of names from `instance_methods`. Specifically, it assumed they were strings, and could be evaluated using `instance_eval` to fetch out the value. The fix is to replace that with a call to `instance_variable_get` which, like it does in Ruby 1.9, avoids the cost of eval parsing as well as being more robust overall. I did audit the rest of the standard library on 1.8.5 and 1.8.7 to verify this. We only need worry if we start using the SOAP or Tk support. Signed-off-by: Daniel Pittman <daniel@puppetlabs.com>
1 parent 769eb94 commit 1f4e44c

File tree

13 files changed

+86
-18
lines changed

13 files changed

+86
-18
lines changed

lib/puppet/parser/yaml_trimmer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module Puppet::Parser::YamlTrimmer
2-
REMOVE = %w{@scope @source}
2+
REMOVE = [:@scope, :@source]
33

44
def to_yaml_properties
55
r = instance_variables - REMOVE

lib/puppet/resource/status.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ class Status
1212
attr_reader :source_description, :default_log_level, :time, :resource
1313
attr_reader :change_count, :out_of_sync_count, :resource_type, :title
1414

15-
YAML_ATTRIBUTES = %w{@resource @file @line @evaluation_time @change_count @out_of_sync_count @tags @time @events @out_of_sync @changed @resource_type @title @skipped @failed}
15+
YAML_ATTRIBUTES = %w{@resource @file @line @evaluation_time @change_count
16+
@out_of_sync_count @tags @time @events @out_of_sync
17+
@changed @resource_type @title @skipped @failed}.
18+
map(&:to_sym)
1619

1720
# Provide a boolean method for each of the states.
1821
STATES.each do |attr|

lib/puppet/simple_graph.rb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -526,11 +526,8 @@ def instance_variable_get(v)
526526
end
527527

528528
def to_yaml_properties
529-
other_vars = instance_variables.
530-
map {|v| v.to_s}.
531-
reject { |v| %w{@in_to @out_from @upstream_from @downstream_from}.include?(v) }
532-
533-
(other_vars + %w{@vertices @edges}).sort.uniq
529+
(instance_variables + [:@vertices, :@edges] -
530+
[:@in_to, :@out_from, :@upstream_from, :@downstream_from]).sort.uniq
534531
end
535532

536533
def yaml_initialize(tag, var)

lib/puppet/transaction/event.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Puppet::Transaction::Event
88
include Puppet::Util::Logging
99

1010
ATTRIBUTES = [:name, :resource, :property, :previous_value, :desired_value, :historical_value, :status, :message, :file, :line, :source_description, :audited]
11-
YAML_ATTRIBUTES = %w{@audited @property @previous_value @desired_value @historical_value @message @name @status @time}
11+
YAML_ATTRIBUTES = %w{@audited @property @previous_value @desired_value @historical_value @message @name @status @time}.map(&:to_sym)
1212
attr_accessor *ATTRIBUTES
1313
attr_writer :tags
1414
attr_accessor :time
@@ -55,7 +55,7 @@ def to_s
5555
end
5656

5757
def to_yaml_properties
58-
(YAML_ATTRIBUTES.map {|ya| ya.to_s} & instance_variables.map{|iv| iv.to_s}).sort
58+
(YAML_ATTRIBUTES & instance_variables).sort
5959
end
6060

6161
private

lib/puppet/transaction/report.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def exit_status
141141
end
142142

143143
def to_yaml_properties
144-
(instance_variables - ["@external_times"]).sort
144+
(instance_variables - [:@external_times]).sort
145145
end
146146

147147
private

lib/puppet/util/log.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ def message=(msg)
233233

234234
def level=(level)
235235
raise ArgumentError, "Puppet::Util::Log requires a log level" unless level
236+
raise ArgumentError, "Puppet::Util::Log requires a symbol or string" unless level.respond_to? "to_sym"
236237
@level = level.to_sym
237238
raise ArgumentError, "Invalid log level #{@level}" unless self.class.validlevel?(@level)
238239

lib/puppet/util/monkey_patches.rb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,56 @@ def tap
153153
self
154154
end unless method_defined?(:tap)
155155
end
156+
157+
158+
########################################################################
159+
# The return type of `instance_variables` changes between Ruby 1.8 and 1.9
160+
# releases; it used to return an array of strings in the form "@foo", but
161+
# now returns an array of symbols in the form :@foo.
162+
#
163+
# Nothing else in the stack cares which form you get - you can pass the
164+
# string or symbol to things like `instance_variable_set` and they will work
165+
# transparently.
166+
#
167+
# Having the same form in all releases of Puppet is a win, though, so we
168+
# pick a unification and enforce than on all releases. That way developers
169+
# who do set math on them (eg: for YAML rendering) don't have to handle the
170+
# distinction themselves.
171+
#
172+
# In the sane tradition, we bring older releases into conformance with newer
173+
# releases, so we return symbols rather than strings, to be more like the
174+
# future versions of Ruby are.
175+
#
176+
# We also carefully support reloading, by only wrapping when we don't
177+
# already have the original version of the method aliased away somewhere.
178+
if RUBY_VERSION[0,3] == '1.8'
179+
unless Object.respond_to?(:puppet_original_instance_variables)
180+
181+
# Add our wrapper to the method.
182+
class Object
183+
alias :puppet_original_instance_variables :instance_variables
184+
185+
def instance_variables
186+
puppet_original_instance_variables.map(&:to_sym)
187+
end
188+
end
189+
190+
# The one place that Ruby 1.8 assumes something about the return format of
191+
# the `instance_variables` method is actually kind of odd, because it uses
192+
# eval to get at instance variables of another object.
193+
#
194+
# This takes the original code and applies replaces instance_eval with
195+
# instance_variable_get through it. All other bugs in the original (such
196+
# as equality depending on the instance variables having the same order
197+
# without any promise from the runtime) are preserved. --daniel 2012-03-11
198+
require 'resolv'
199+
class Resolv::DNS::Resource
200+
def ==(other) # :nodoc:
201+
return self.class == other.class &&
202+
self.instance_variables == other.instance_variables &&
203+
self.instance_variables.collect {|name| self.instance_variable_get name} ==
204+
other.instance_variables.collect {|name| other.instance_variable_get name}
205+
end
206+
end
207+
end
208+
end

lib/puppet/util/zaml.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def to_zaml(z)
148148
else
149149
instance_variables.each { |v|
150150
z.nl
151-
v[1..-1].to_zaml(z) # Remove leading '@'
151+
v.to_s[1..-1].to_zaml(z) # Remove leading '@'
152152
z.emit(': ')
153153
yaml_property_munge(instance_variable_get(v)).to_zaml(z)
154154
}

spec/unit/application/inspect_spec.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ def retrieve
260260
event.property.should == "content"
261261
event.status.should == "failure"
262262
event.audited.should == true
263-
event.instance_variables.should_not include("@previous_value")
263+
event.instance_variables.should_not include "@previous_value"
264+
event.instance_variables.should_not include :@previous_value
264265
end
265266

266267
it "should continue to the next resource" do

spec/unit/resource/status_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@
148148
@status.line = 27
149149
@status.evaluation_time = 2.7
150150
@status.tags = %w{one two}
151-
@status.to_yaml_properties.should == Puppet::Resource::Status::YAML_ATTRIBUTES.sort
151+
@status.to_yaml_properties.should =~ Puppet::Resource::Status::YAML_ATTRIBUTES
152152
end
153153
end
154154
end

0 commit comments

Comments
 (0)