Skip to content

Commit 2211c64

Browse files
committed
(maint) Add a PuppetStack that adds stack entries to the backtrace
This adds a mechanism which can be used to make an entry on the ruby stack that indiciates that it is a puppet stack frame. The frame is visible in the ruby backtrace and PuppetStack provides a stacktrace that is filtered to only contain .pp entries. This adds the mechanism and a test. The mechanism is not yet in use.
1 parent 2620830 commit 2211c64

File tree

3 files changed

+123
-0
lines changed

3 files changed

+123
-0
lines changed

lib/puppet/pops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module Pops
1414

1515
require 'puppet/pops/patterns'
1616
require 'puppet/pops/utils'
17+
require 'puppet/pops/puppet_stack'
1718

1819
require 'puppet/pops/adaptable'
1920
require 'puppet/pops/adapters'

lib/puppet/pops/puppet_stack.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
module Puppet::Pops
2+
# Module for making a call such that there is an identifiable entry on
3+
# the ruby call stack enabling getting a puppet call stack
4+
# To use this make a call with:
5+
# ```
6+
# Puppet::Pops::PuppetStack.stack(file, line, receiver, message, args)
7+
# ```
8+
# To get the stack call:
9+
# ```
10+
# Puppet::Pops::PuppetStack.stacktrace
11+
#
12+
# When getting a backtrace in Ruby, the puppet stack frames are
13+
# identified as coming from "in 'stack'" and having a ".pp" file
14+
# name.
15+
# To support testing, a given file that is an empty string, or nil
16+
# as well as a nil line number are supported. Such stack frames
17+
# will be represented with the text `unknown` and `0´ respectively.
18+
#
19+
module PuppetStack
20+
# Sends a message to an obj such that it appears to come from
21+
# file, line when calling stacktrace.
22+
#
23+
def self.stack(file, line, obj, message, args, &block)
24+
file = '' if file.nil?
25+
line = 0 if line.nil?
26+
27+
if block_given?
28+
Kernel.eval("obj.send(message, *args, &block)", Kernel.binding(), file, line)
29+
else
30+
Kernel.eval("obj.send(message, *args)", Kernel.binding(), file, line)
31+
end
32+
end
33+
34+
def self.stacktrace
35+
result = caller().reduce([]) do |memo, loc|
36+
if loc =~ /^(.*\.pp)?:([0-9]+):in `stack'/
37+
memo << [$1.nil? ? 'unknown' : $1, $2.to_i]
38+
end
39+
memo
40+
end.reverse
41+
end
42+
end
43+
end

spec/unit/pops/puppet_stack_spec.rb

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
require 'spec_helper'
2+
3+
require 'puppet/pops'
4+
5+
describe 'Puppet::Pops::PuppetStack' do
6+
class StackTraceTest
7+
def get_stacktrace
8+
Puppet::Pops::PuppetStack.stacktrace
9+
end
10+
11+
def one_level
12+
Puppet::Pops::PuppetStack.stack("one_level.pp", 1234, self, :get_stacktrace, [])
13+
end
14+
15+
def one_level
16+
Puppet::Pops::PuppetStack.stack("one_level.pp", 1234, self, :get_stacktrace, [])
17+
end
18+
19+
def two_levels
20+
Puppet::Pops::PuppetStack.stack("two_levels.pp", 1237, self, :level2, [])
21+
end
22+
23+
def level2
24+
Puppet::Pops::PuppetStack.stack("level2.pp", 1240, self, :get_stacktrace, [])
25+
end
26+
27+
def gets_block(a, &block)
28+
block.call(a)
29+
end
30+
31+
def gets_args_and_block(a, b, &block)
32+
block.call(a, b)
33+
end
34+
35+
def with_nil_file
36+
Puppet::Pops::PuppetStack.stack(nil, 1250, self, :get_stacktrace, [])
37+
end
38+
39+
def with_empty_string_file
40+
Puppet::Pops::PuppetStack.stack('', 1251, self, :get_stacktrace, [])
41+
end
42+
end
43+
44+
it 'returns an empty array from stacktrace when there is nothing on the stack' do
45+
expect(Puppet::Pops::PuppetStack.stacktrace).to eql([])
46+
end
47+
48+
it 'returns a one element array with file, line from stacktrace when there is one entry on the stack' do
49+
expect(StackTraceTest.new.one_level).to eql([['one_level.pp', 1234]])
50+
end
51+
52+
it 'returns an array from stacktrace with information about each level with oldest frame last' do
53+
expect(StackTraceTest.new.two_levels).to eql([['two_levels.pp', 1237], ['level2.pp', 1240]])
54+
end
55+
56+
it 'accepts file to be nil' do
57+
expect(StackTraceTest.new.with_nil_file).to eql([['unknown', 1250]])
58+
end
59+
60+
it 'accepts file to be empty_string' do
61+
expect(StackTraceTest.new.with_empty_string_file).to eql([['unknown', 1251]])
62+
end
63+
64+
it 'stacktrace is empty when call has returned' do
65+
StackTraceTest.new.two_levels
66+
expect(Puppet::Pops::PuppetStack.stacktrace).to eql([])
67+
end
68+
69+
it 'allows passing a block to the stack call' do
70+
expect(Puppet::Pops::PuppetStack.stack("test.pp", 1, StackTraceTest.new, :gets_block, ['got_it']) {|x| x }).to eql('got_it')
71+
end
72+
73+
it 'allows passing multiple variables and a block' do
74+
expect(
75+
Puppet::Pops::PuppetStack.stack("test.pp", 1, StackTraceTest.new, :gets_args_and_block, ['got_it', 'again']) {|x, y| [x,y].join(' ')}
76+
).to eql('got_it again')
77+
end
78+
79+
end

0 commit comments

Comments
 (0)