I have created an open source project that uses Rspec to validate the formatting of AWS::CloudFormation::Init YAML blocks in CloudFormation templates.1
My project is here.
Some general concerns:
Is this a valid use-case for using Rspec?
Is the documentation in the code okay?
I have some specific concerns about the code. Concerning the recursive compare() method:
# Method: compare
#
# Recursively compare real blocks in a CloudFormation template
# with the spec defined in $types.
#
# @param data [Hash] The real data in the template.
# @param spec [Hash] The reference data corresponding to this
# key in $types.
#
def compare(data, spec)
if spec.is_a?(Hash)
check_keys(data.keys, spec.keys)
end
data.each do |k,v|
context k do
if spec == Array
it "#{k} should match Array" do
expect(v.class).to eq Array
end
elsif spec[k] == Hash
it "#{k} should match Hash" do
expect(v.class).to eq Hash
end
elsif spec == {String => Array} or
spec == {String => String}
it "#{k}=>#{v} should match #{spec}" do
expect(k.class).to eq spec.keys.first
expect(v.class).to eq spec[spec.keys.first]
end
elsif spec.has_key?(k)
if v.is_a?(Hash)
compare(v, spec[k]) # recurse.
elsif spec[k].is_a?(Regexp)
it "#{k} should match #{spec[k]}" do
expect(v).to match spec[k]
end
elsif spec[k] == String
# Actually, an Array of Strings joined by CloudFormation
# is also okay.
#
it "#{k} should match #{spec[k]}" do
expect([String, Array].include?(v.class)).to be true
end
elsif spec[k] == Fixnum
it "#{k} should match (or be cast to) #{spec[k]}" do
expect { v.to_i }.to_not raise_error
end
elsif [Array, TrueClass, FalseClass]
.include?(v.class)
it "#{v} should be a #{spec[k]}" do
expect(v).to be_a spec[k]
end
end
elsif v.is_a?(Hash)
spec_key = spec.keys.first
compare(v, spec[spec_key]) # recurse.
else
raise "Something went wrong"
end
end
end
end
- Can this conditional logic be simplified in any way?
The compare() method compares input data against this reference spec:
module Boolean; end
class TrueClass; include Boolean; end
class FalseClass; include Boolean; end
# Supporting Ruby < 2.4.
#
if not defined?(Fixnum)
class Fixnum < Integer; end
end
# This hash defines the expected format of
# config sets in AWS::CloudFormation::Init blocks.
#
# Data types and regular expressions used as
# placeholders for real values.
#
# Based on documentation here:
#
# https://docs.aws.amazon.com/AWSCloudFormation/
# latest/UserGuide/aws-resource-init.html
#
$types = {
'packages' => {
'apt' => {String => Array},
'msi' => {String => String},
'python' => {String => Array},
'rpm' => {String => String},
'rubygems' => {String => Array},
'yum' => {String => Array},
},
'groups' => {
String => {'gid' => Fixnum},
},
'users' => {
String => {
'groups' => Array,
'uid' => Fixnum,
'homeDir' => String,
},
},
'sources' => {String => String},
'files' => {
String => {
'content' => String,
'source' => /^http/,
'encoding' => /plain|base64/,
'group' => String,
'owner' => String,
'mode' => Fixnum,
'authentication' => String,
'context' => String,
},
},
'commands' => {
String => {
'command' => String, # TODO. Apparently the only mandatory
'env' => Hash, # attribute.
'cwd' => String,
'test' => String,
'ignoreErrors' => Boolean,
'waitAfterCompletion' => Boolean,
},
},
'services' => {
/sysvinit|windows/ => {
String => {
'ensureRunning' => Boolean,
'enabled' => Boolean,
'files' => Array,
'sources' => Array,
'packages' => Array,
'commands' => Array,
},
},
},
}
- Is the use of a global variable okay there? I chose that just to make it stand out.
And any general feedback most welcome.
1 There is some confusion in the comments about what I actually "implemented" - i.e. where the code-under-test is. I haven't implemented anything. I am using RSpec purely to make assertions about the formatting of YAML files that could come from anywhere. I hope that clarifies this.
test_runner.shrepresents the tests for the implementation, which is in the folder for testing. Well, when I come across a project and want to know its behaviour I like to take a look at the tests. They are usually in thetestsor in therspecfolder. But in our case we find the implementation there instead of the real tests. \$\endgroup\$