Add stack introspection

Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
Clinton Wolfe 2019-02-07 13:05:46 -05:00
parent 9faf4b1034
commit 9156a782fe
5 changed files with 86 additions and 61 deletions

View file

@ -26,9 +26,9 @@ module Inspec
:provider, # Name of the plugin
:priority, # Priority of this plugin for resolving conflicts. 1-100, higher numbers win.
:value, # New value, if provided.
:file, # File containing the attribute-changing action, if known
:line, # Line in file containing the attribute-changing action, if known
:hit, # if action is :fetch, true if the remote source had the attribute
:file, # File containing the input-changing action, if known
:line, # Line in file containing the input-changing action, if known
:hit, # if action is :fetch, true if the remote source had the input
].freeze
# Value has a special handler
@ -178,7 +178,7 @@ module Inspec
end
def diagnostic_string
"Attribute #{name}, with history:\n" +
"Input #{name}, with history:\n" +
events.map(&:diagnostic_string).map { |line| " #{line}" }.join("\n")
end
@ -218,7 +218,7 @@ module Inspec
def make_creation_event(options)
loc = options[:location] || probe_stack
Attribute::Event.new(
Input::Event.new(
action: :create,
provider: options[:provider],
file: loc.path,
@ -227,9 +227,9 @@ module Inspec
end
def probe_stack
locs = caller_locations(2, 40)
# TODO: - refine huristics
locs[0]
frames = caller_locations(2, 40)
frames.reject! { |f| f.path && f.path.include?('/lib/inspec/') }
frames.first
end
# We can determine a value:
@ -240,7 +240,7 @@ module Inspec
# Don't rely on this working; you really should be passing a proper Input::Event
# with the context information you have.
location = probe_stack
event = Attribute::Event.new(
event = Input::Event.new(
action: :set,
provider: :unknown,
priority: options[:priority] || Inspec::Input::DEFAULT_PRIORITY_FOR_UNKNOWN_CALLER,
@ -302,7 +302,7 @@ module Inspec
end
def has_value?
!current_value.is_a? DEFAULT_ATTRIBUTE
!current_value.is_a? NO_VALUE_SET
end
#--------------------------------------------------------------------------#
@ -341,7 +341,7 @@ module Inspec
#--------------------------------------------------------------------------#
def to_s
"Attribute #{name} with #{current_value}"
"Input #{name} with #{current_value}"
end
#--------------------------------------------------------------------------#
@ -385,11 +385,11 @@ module Inspec
end
if invalid_type == true
error = Inspec::Attribute::ValidationError.new
error.attribute_name = @name
error.attribute_value = proposed_value
error.attribute_type = type_req
raise error, "Attribute '#{error.attribute_name}' with value '#{error.attribute_value}' does not validate to type '#{error.attribute_type}'."
error = Inspec::Input::ValidationError.new
error.input_name = @name
error.input_value = proposed_value
error.input_type = type_req
raise error, "Input '#{error.input_name}' with value '#{error.input_value}' does not validate to type '#{error.input_type}'."
end
end
@ -403,9 +403,9 @@ module Inspec
}
type_req = abbreviations[type_req] if abbreviations.key?(type_req)
if !VALID_TYPES.include?(type_req)
error = Inspec::Attribute::TypeError.new
error.attribute_type = type_req
raise error, "Type '#{error.attribute_type}' is not a valid attribute type."
error = Inspec::Input::TypeError.new
error.input_type = type_req
raise error, "Type '#{error.input_type}' is not a valid input type."
end
@type = type_req
end

View file

@ -2,88 +2,115 @@ require 'helper'
require 'inspec/objects/input'
describe 'Inspec::Input and Events' do
let(:ipt { Inspec::Input.new('input') }
let(:ipt) { Inspec::Input.new('input') }
#==============================================================#
# Create Event
#==============================================================#
describe 'when creating an input' do
it 'should have a creation event' do
creation_events = att.events.select { |e| e.action == :create }
creation_events = ipt.events.select { |e| e.action == :create }
creation_events.wont_be_empty
end
it 'should only have a creation event if no value was provided' do
creation_events = att.events.select { |e| e.action == :create }
creation_events = ipt.events.select { |e| e.action == :create }
creation_events.count.must_equal 1
end
it 'should have a create and a set event if a value was provided' do
att = Inspec::Input.new('input', value: 42)
creation_events = att.events.select { |e| e.action == :create }
ipt = Inspec::Input.new('input', value: 42)
creation_events = ipt.events.select { |e| e.action == :create }
creation_events.count.must_equal 1
set_events = att.set_events
set_events = ipt.set_events
set_events.count.must_equal 1
set_events.first.value.must_equal 42
end
end
#==============================================================#
# Set Events
#==============================================================#
describe 'when setting an input using value=' do
it 'should add a set event' do
att.set_events.count.must_equal 0
att.value = 42
att.set_events.count.must_equal 1
ipt.set_events.count.must_equal 0
ipt.value = 42
ipt.set_events.count.must_equal 1
end
it 'should add one event for each value= operation' do
att.set_events.count.must_equal 0
att.value = 1
att.value = 2
att.value = 3
att.set_events.count.must_equal 3
ipt.set_events.count.must_equal 0
ipt.value = 1
ipt.value = 2
ipt.value = 3
ipt.set_events.count.must_equal 3
end
end
#==============================================================#
# Picking a Winner
#==============================================================#
# For more real-world testing of metadata vs --attrs vs inline, see
# test/functional/inputs_test.rb
describe 'priority voting' do
it 'value() should return the correct value when there is just one set operation' do
evt = Inspec::Input::Event.new(value: 42, priority: 25, action: :set)
att.update(event: evt)
att.value.must_equal 42
ipt.update(event: evt)
ipt.value.must_equal 42
end
it 'should return the highest priority regardless of order' do
evt1 = Inspec::Input::Event.new(value: 1, priority: 25, action: :set)
att.update(event: evt1)
ipt.update(event: evt1)
evt2 = Inspec::Input::Event.new(value: 2, priority: 35, action: :set)
att.update(event: evt2)
ipt.update(event: evt2)
evt3 = Inspec::Input::Event.new(value: 3, priority: 15, action: :set)
att.update(event: evt3)
ipt.update(event: evt3)
att.value.must_equal 2
ipt.value.must_equal 2
end
it 'breaks ties using the last event of the highest priority' do
evt1 = Inspec::Input::Event.new(value: 1, priority: 15, action: :set)
att.update(event: evt1)
ipt.update(event: evt1)
evt2 = Inspec::Input::Event.new(value: 2, priority: 25, action: :set)
att.update(event: evt2)
ipt.update(event: evt2)
evt3 = Inspec::Input::Event.new(value: 3, priority: 25, action: :set)
att.update(event: evt3)
ipt.update(event: evt3)
att.value.must_equal 3
ipt.value.must_equal 3
end
end
# TODO: test stack hueristics?
#==============================================================#
# Stack Hueristics
#==============================================================#
describe 'when determining where the call came from' do
it 'should get the line and file correct in the constructor' do
expected_file = __FILE__
expected_line = __LINE__; ipt = Inspec::Input.new('some_input') # Important to keep theses on one line
event = ipt.events.first
event.file.must_equal expected_file
event.line.must_equal expected_line
end
end
#==============================================================#
# Diagnostics
#==============================================================#
describe 'input diagnostics' do
it 'should dump the events' do
evt1 = Inspec::Input::Event.new(value: {a:1, b:2}, priority: 15, action: :set, provider: :unit_test)
att.update(event: evt1)
ipt.update(event: evt1)
evt2 = Inspec::Input::Event.new(action: :fetch, provider: :alcubierre, hit: false)
att.update(event: evt2)
ipt.update(event: evt2)
evt3 = Inspec::Input::Event.new(value: 12, action: :set, provider: :control_dsl, file: '/tmp/some/file.rb', line: 2)
att.update(event: evt3)
ipt.update(event: evt3)
text = att.diagnostic_string
text = ipt.diagnostic_string
lines = text.split("\n")
lines.count.must_equal 5 # 3 events above + 1 create + 1 input name line
lines.shift # Not testing the inputs top line here

View file

@ -77,13 +77,8 @@ describe Inspec::InputRegistry do
before do
Inspec::InputRegistry.any_instance.stubs(:validate_inputs_file_readability!)
end
let(:profile) do
p = mock()
p.expects(:profile_name).returns('test_fixture_profile').at_least_once
p
end
let(:seen_inputs) do
registry.bind_profile_inputs(profile, sources)
registry.bind_profile_inputs('test_fixture_profile', sources)
inputs = registry.list_inputs_for_profile('test_fixture_profile')
# Flatten Input objects down to their values
inputs.keys.each { |input_name| inputs[input_name] = inputs[input_name].value }

View file

@ -4,7 +4,8 @@ require 'helper'
require 'inspec/objects/input'
describe Inspec::Input do
let(:input) { Inspec::Input.new('test_input') }
let(:opts) { { } }
let(:input) { Inspec::Input.new('test_input', opts) }
#==============================================================#
# Metadata
@ -73,7 +74,9 @@ describe Inspec::Input do
}
ipt.to_hash.must_equal expected
end
end #==============================================================#
end
#==============================================================#
# Setting Value - One Shot
# (see events_test.rb for overwrite support)
#==============================================================#
@ -90,7 +93,7 @@ describe Inspec::Input do
end
it 'the dummy value responds true to the legacy class checks' do
input.value.is_a?(Inspec::ATTRIBUTE::DEFAULT_ATTRIBUTE).must_equal true
input.value.is_a?(Inspec::Attribute::DEFAULT_ATTRIBUTE).must_equal true
input.value.must_be_kind_of Inspec::Attribute::DEFAULT_ATTRIBUTE
end

View file

@ -39,7 +39,7 @@ describe 'type validation' do
{
'string' => { good: 'a_string', bad: 123.3, norm: 'String' },
'numeric' => { good: 123, bad: 'not a number', norm: 'Numeric' },
'regex' => { good: /\d+.+/, bad: '/(.+/', norm: 'Regexp' }
'regex' => { good: /\d+.+/, bad: '/(.+/', norm: 'Regexp' },
'array' => { good: [1, 2, 3], bad: { a: 1, b: 2, c: 3 }, norm: 'Array' },
'hash' => { good: { a: 1, b: 2, c: 3 }, bad: 'i am not a hash', norm: 'Hash' },
'boolean' => { good: true, bad: 'i am not a boolean', norm: 'Boolean' },
@ -84,17 +84,17 @@ describe 'type validation' do
describe 'validate type option' do
it 'converts regex to Regexp' do
opts[:type] = 'regex'
input.type_restriction.must_equal 'Regexp'
input.type.must_equal 'Regexp'
end
it 'returns the same value if there is nothing to clean' do
opts[:type] = 'String'
input.type_restriction.must_equal 'String'
input.type.must_equal 'String'
end
it 'returns an error if a invalid type is sent' do
opts[:type] = 'dressing'
ex = assert_raises(Inspec::Attribute::TypeError) { input }
ex = assert_raises(Inspec::Input::TypeError) { input }
ex.message.must_match /Type 'Dressing' is not a valid input type./
end
end