ios_parser 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/.rubocop.yml +19 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +14 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +2 -0
- data/Guardfile +10 -0
- data/LICENSE.txt +675 -0
- data/README.md +90 -0
- data/Rakefile +16 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/doc/state_machine.graphviz +41 -0
- data/doc/state_machine.png +0 -0
- data/ext/ios_parser/c_lexer/extconf.rb +4 -0
- data/ext/ios_parser/c_lexer/lexer.c +462 -0
- data/ios_parser.gemspec +24 -0
- data/lib/ios_parser/ios/command.rb +90 -0
- data/lib/ios_parser/ios/document.rb +53 -0
- data/lib/ios_parser/ios/queryable.rb +218 -0
- data/lib/ios_parser/ios.rb +70 -0
- data/lib/ios_parser/lexer.rb +278 -0
- data/lib/ios_parser/pure.rb +2 -0
- data/lib/ios_parser/version.rb +7 -0
- data/lib/ios_parser.rb +32 -0
- data/spec/lib/ios_parser/ios/queryable_spec.rb +153 -0
- data/spec/lib/ios_parser/ios_spec.rb +339 -0
- data/spec/lib/ios_parser/lexer_spec.rb +231 -0
- data/spec/lib/ios_parser_spec.rb +101 -0
- data/spec/spec_helper.rb +5 -0
- metadata +164 -0
data/lib/ios_parser.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module IOSParser
|
4
|
+
def self.lexer
|
5
|
+
if const_defined?(:PureLexer)
|
6
|
+
PureLexer
|
7
|
+
else
|
8
|
+
require_relative 'ios_parser/c_lexer'
|
9
|
+
CLexer
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Lexer = lexer
|
14
|
+
end
|
15
|
+
|
16
|
+
require_relative 'ios_parser/ios'
|
17
|
+
|
18
|
+
module IOSParser
|
19
|
+
class << self
|
20
|
+
def parse(input)
|
21
|
+
IOSParser::IOS.new.call(input)
|
22
|
+
end
|
23
|
+
|
24
|
+
def hash_to_ios(hash)
|
25
|
+
IOSParser::IOS::Document.from_hash(hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
def json_to_ios(text)
|
29
|
+
hash_to_ios JSON.load(text)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require_relative '../../../spec_helper'
|
2
|
+
require 'ios_parser'
|
3
|
+
|
4
|
+
module IOSParser
|
5
|
+
class IOS
|
6
|
+
describe Queryable do
|
7
|
+
let(:input) { <<-END }
|
8
|
+
policy-map mypolicy_in
|
9
|
+
class some_service
|
10
|
+
police 300000000 1000000 exceed-action policed-dscp-transmit
|
11
|
+
set dscp cs1
|
12
|
+
class my_service
|
13
|
+
police 600000000 1000000 exceed-action policed-dscp-transmit
|
14
|
+
set dscp cs2
|
15
|
+
command_with_no_args
|
16
|
+
END
|
17
|
+
|
18
|
+
let(:expectation) { 'set dscp cs1' }
|
19
|
+
let(:parsed) { IOSParser.parse(input) }
|
20
|
+
subject { parsed.find(matcher).line }
|
21
|
+
|
22
|
+
describe '#find' do
|
23
|
+
context 'shortcut matcher' do
|
24
|
+
describe String do
|
25
|
+
let(:matcher) { 'set dscp cs1' }
|
26
|
+
it { should == expectation }
|
27
|
+
end
|
28
|
+
|
29
|
+
describe Regexp do
|
30
|
+
let(:matcher) { /set .* cs1/ }
|
31
|
+
it { should == expectation }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe Proc do
|
35
|
+
let(:expectation) { 'command_with_no_args' }
|
36
|
+
let(:matcher) { ->(c) { c.args.count == 1 } }
|
37
|
+
it { should == expectation }
|
38
|
+
end
|
39
|
+
end # context 'shortcut matcher' do
|
40
|
+
|
41
|
+
context 'explicit matcher form of shortcut matcher' do
|
42
|
+
describe String do
|
43
|
+
let(:matcher) { { starts_with: 'set dscp cs1' } }
|
44
|
+
it { should == expectation }
|
45
|
+
end
|
46
|
+
|
47
|
+
describe Regexp do
|
48
|
+
let(:matcher) { { line: /set .* cs1/ } }
|
49
|
+
it { should == expectation }
|
50
|
+
end
|
51
|
+
|
52
|
+
describe Proc do
|
53
|
+
let(:expectation) { 'command_with_no_args' }
|
54
|
+
let(:matcher) { { procedure: ->(c) { c.args.count == 1 } } }
|
55
|
+
it { should == expectation }
|
56
|
+
end
|
57
|
+
end # context 'explicit matcher form of shortcut matcher' do
|
58
|
+
|
59
|
+
context 'matcher: contains' do
|
60
|
+
describe String do
|
61
|
+
let(:matcher) { { contains: 'dscp cs1' } }
|
62
|
+
it { should == expectation }
|
63
|
+
end
|
64
|
+
|
65
|
+
describe Array do
|
66
|
+
let(:matcher) { { contains: %w(dscp cs1) } }
|
67
|
+
it { should == expectation }
|
68
|
+
end
|
69
|
+
end # context 'matcher: contains' do
|
70
|
+
|
71
|
+
context 'matcher: ends_with' do
|
72
|
+
let(:expectation) { 'class my_service' }
|
73
|
+
|
74
|
+
describe String do
|
75
|
+
let(:matcher) { { ends_with: 'my_service' } }
|
76
|
+
it { should == expectation }
|
77
|
+
end
|
78
|
+
|
79
|
+
describe Array do
|
80
|
+
let(:matcher) { { ends_with: ['my_service'] } }
|
81
|
+
it { should == expectation }
|
82
|
+
end
|
83
|
+
end # context 'matcher: ends_with' do
|
84
|
+
|
85
|
+
context 'matcher: all' do
|
86
|
+
let(:matcher) { { all: ['set', /cs1/] } }
|
87
|
+
it { should == expectation }
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'matcher: parent' do
|
91
|
+
let(:matcher) { { parent: /police 3/ } }
|
92
|
+
it { should == expectation }
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'matcher: any' do
|
96
|
+
let(:matcher) { { any: [/asdf/, /cs1/, /qwerwqe/] } }
|
97
|
+
it { should == expectation }
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'matcher: any (with a hash)' do
|
101
|
+
let(:matcher) do
|
102
|
+
{ any: { depth: 0, procedure: ->(c) { c.args.count == 1 } } }
|
103
|
+
end
|
104
|
+
|
105
|
+
it do
|
106
|
+
expect(parsed.find_all(matcher).map(&:line))
|
107
|
+
.to eq ['policy-map mypolicy_in', 'command_with_no_args']
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'matcher: depth' do
|
112
|
+
let(:matcher) { { depth: 3 } }
|
113
|
+
it { should == expectation }
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'matcher: none' do
|
117
|
+
let(:matcher) do
|
118
|
+
{ none: [/policy/, /class/, /police/] }
|
119
|
+
end
|
120
|
+
it { should == expectation }
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'matcher: not_all' do
|
124
|
+
let(:matcher) do
|
125
|
+
{
|
126
|
+
all: {
|
127
|
+
none: /policy/,
|
128
|
+
not: /class/,
|
129
|
+
not_all: /police/
|
130
|
+
}
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
it do
|
135
|
+
expect(parsed.find(not_all: [/policy/, /class/]).line)
|
136
|
+
.to eq 'policy-map mypolicy_in'
|
137
|
+
end
|
138
|
+
it { should == expectation }
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'matcher: any_child' do
|
142
|
+
let(:matcher) { { not: { any_child: /dscp/ } } }
|
143
|
+
it { should == expectation }
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'matcher: no_child' do
|
147
|
+
let(:matcher) { { no_child: /dscp/ } }
|
148
|
+
it { should == expectation }
|
149
|
+
end
|
150
|
+
end # describe '#find' do
|
151
|
+
end # describe Queryable
|
152
|
+
end # class IOS
|
153
|
+
end # module IOSParser
|
@@ -0,0 +1,339 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
require 'ios_parser'
|
3
|
+
require 'ios_parser/lexer'
|
4
|
+
|
5
|
+
module IOSParser
|
6
|
+
describe IOS do
|
7
|
+
context 'indented region' do
|
8
|
+
let(:input) { <<-END }
|
9
|
+
policy-map mypolicy_in
|
10
|
+
class myservice_service
|
11
|
+
police 300000000 1000000 exceed-action policed-dscp-transmit
|
12
|
+
set dscp cs1
|
13
|
+
class other_service
|
14
|
+
police 600000000 1000000 exceed-action policed-dscp-transmit
|
15
|
+
set dscp cs2
|
16
|
+
command_with_no_args
|
17
|
+
END
|
18
|
+
|
19
|
+
let(:output) do
|
20
|
+
{
|
21
|
+
commands:
|
22
|
+
[{ args: ['policy-map', 'mypolicy_in'],
|
23
|
+
commands:
|
24
|
+
[{ args: %w(class myservice_service),
|
25
|
+
commands: [{ args: ['police', 300_000_000, 1_000_000,
|
26
|
+
'exceed-action',
|
27
|
+
'policed-dscp-transmit'],
|
28
|
+
commands: [{ args: %w(set dscp cs1),
|
29
|
+
commands: [], pos: 114 }],
|
30
|
+
pos: 50
|
31
|
+
}],
|
32
|
+
pos: 24
|
33
|
+
},
|
34
|
+
|
35
|
+
{ args: %w(class other_service),
|
36
|
+
commands: [{ args: ['police', 600_000_000, 1_000_000,
|
37
|
+
'exceed-action',
|
38
|
+
'policed-dscp-transmit'],
|
39
|
+
commands: [{ args: %w(set dscp cs2),
|
40
|
+
commands: [], pos: 214 },
|
41
|
+
{ args: ['command_with_no_args'],
|
42
|
+
commands: [], pos: 230 }],
|
43
|
+
pos: 150
|
44
|
+
}],
|
45
|
+
pos: 128
|
46
|
+
}],
|
47
|
+
pos: 0
|
48
|
+
}]
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#call' do
|
53
|
+
subject { klass.new.call(input) }
|
54
|
+
let(:subject_pure) do
|
55
|
+
klass.new(lexer: IOSParser::PureLexer.new).call(input)
|
56
|
+
end
|
57
|
+
|
58
|
+
it('constructs the right AST') { expect(subject.to_hash).to eq output }
|
59
|
+
|
60
|
+
it('constructs the right AST (using the pure-ruby lexer)') do
|
61
|
+
expect(subject_pure.to_hash[:commands]).to eq output[:commands]
|
62
|
+
end
|
63
|
+
|
64
|
+
it('can be searched by an exact command') do
|
65
|
+
expect(subject.find_all(name: 'set').map(&:to_hash))
|
66
|
+
.to eq [{ args: %w(set dscp cs1),
|
67
|
+
commands: [], pos: 114 },
|
68
|
+
{ args: %w(set dscp cs2),
|
69
|
+
commands: [], pos: 214 }]
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'can be searched by name and the first argument' do
|
73
|
+
let(:result) do
|
74
|
+
expect(subject.find_all(starts_with: starts_with).map(&:to_hash))
|
75
|
+
.to eq expectation
|
76
|
+
end
|
77
|
+
|
78
|
+
let(:expectation) { [output[:commands][0][:commands][1]] }
|
79
|
+
|
80
|
+
context 'with an array of strings' do
|
81
|
+
let(:starts_with) { %w(class other_service) }
|
82
|
+
it { result }
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with an array of regular expressions' do
|
86
|
+
let(:starts_with) { [/.lass/, /^other_[a-z]+$/] }
|
87
|
+
it { result }
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'with a string, space-separated' do
|
91
|
+
let(:starts_with) { 'class other_service' }
|
92
|
+
it { result }
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'integer argument' do
|
96
|
+
let(:expectation) do
|
97
|
+
[{ args: ['police', 300_000_000, 1_000_000, 'exceed-action',
|
98
|
+
'policed-dscp-transmit'],
|
99
|
+
commands: [{ args: %w(set dscp cs1),
|
100
|
+
commands: [], pos: 114 }],
|
101
|
+
pos: 50
|
102
|
+
}]
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'integer query' do
|
106
|
+
let(:starts_with) { ['police', 300_000_000] }
|
107
|
+
it { result }
|
108
|
+
end # context 'integer query'
|
109
|
+
|
110
|
+
context 'string query' do
|
111
|
+
let(:starts_with) { 'police 300000000' }
|
112
|
+
it { result }
|
113
|
+
end # context 'string query'
|
114
|
+
end
|
115
|
+
end # context 'integer argument'
|
116
|
+
|
117
|
+
context 'nested search' do
|
118
|
+
it 'queries can be chained' do
|
119
|
+
expect(subject
|
120
|
+
.find('policy-map').find('class').find('police')
|
121
|
+
.find('set')
|
122
|
+
.to_hash)
|
123
|
+
.to eq(args: %w(set dscp cs1),
|
124
|
+
commands: [], pos: 114)
|
125
|
+
end
|
126
|
+
end # context 'nested search'
|
127
|
+
|
128
|
+
context 'pass a block' do
|
129
|
+
it 'is evaluated for each matching command' do
|
130
|
+
ary = []
|
131
|
+
subject.find_all('class') { |cmd| ary << cmd.args[1] }
|
132
|
+
expect(ary).to eq %w(myservice_service other_service)
|
133
|
+
end
|
134
|
+
end # context 'pass a block'
|
135
|
+
end # end context 'indented region'
|
136
|
+
|
137
|
+
context '2950' do
|
138
|
+
let(:input) { <<END }
|
139
|
+
hostname myswitch1
|
140
|
+
vlan 3
|
141
|
+
name MyVlanName
|
142
|
+
interface FastEthernet0/1
|
143
|
+
speed 100
|
144
|
+
END
|
145
|
+
|
146
|
+
let(:output) { klass.new.call(input) }
|
147
|
+
|
148
|
+
it { expect(output.find('hostname').args[1]).to eq 'myswitch1' }
|
149
|
+
|
150
|
+
it('extracts vlan names') do
|
151
|
+
expect(output.find('vlan 3').find('name').args[1])
|
152
|
+
.to eq 'MyVlanName'
|
153
|
+
end
|
154
|
+
|
155
|
+
it('extracts interface speed') do
|
156
|
+
expect(output.find('interface FastEthernet0/1').find('speed').args[1])
|
157
|
+
.to eq 100
|
158
|
+
end
|
159
|
+
|
160
|
+
it('parses snmp commands') do
|
161
|
+
snmp_command = <<-END
|
162
|
+
snmp-server group my_group v3 auth read my_ro
|
163
|
+
END
|
164
|
+
result = klass.new.call(snmp_command)
|
165
|
+
expect(result[0].name).to eq 'snmp-server'
|
166
|
+
end
|
167
|
+
|
168
|
+
it('parses a simple alias') do
|
169
|
+
simple_alias = 'alias exec stat sh int | inc [1-9].*ignored|[1-9].*'\
|
170
|
+
"resets|Ethernet0|minute\n"
|
171
|
+
result = klass.new.call(simple_alias)
|
172
|
+
expect(result[0].name).to eq 'alias'
|
173
|
+
end
|
174
|
+
|
175
|
+
it('parses a complex alias') do
|
176
|
+
complex_alias = 'alias exec stats sh int | inc Ether.*'\
|
177
|
+
'(con|Loop.*is up|Vlan.*is up|Port-.*is up|'\
|
178
|
+
"input rate [^0]|output rate [^0]\n"
|
179
|
+
result = klass.new.call(complex_alias)
|
180
|
+
expect(result[0].name).to eq 'alias'
|
181
|
+
expect(result[0].args.last).to eq '[^0]'
|
182
|
+
end
|
183
|
+
|
184
|
+
it('parses a banner') do
|
185
|
+
banner_text = <<-END
|
186
|
+
|
187
|
+
|
188
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
189
|
+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
190
|
+
|
191
|
+
|
192
|
+
END
|
193
|
+
banner_command = "banner exec ^C#{banner_text}^C\n"
|
194
|
+
|
195
|
+
result = klass.new.call(banner_command)
|
196
|
+
expect(result[0].args[2]).to eq banner_text
|
197
|
+
end
|
198
|
+
|
199
|
+
it('parses a crypto trustpoint section') do
|
200
|
+
text = <<END
|
201
|
+
crypto pki trustpoint TP-self-signed-0123456789
|
202
|
+
enrollment selfsigned
|
203
|
+
subject-name cn=IOS-Self-Signed-Certificate-1234567890
|
204
|
+
revocation-check none
|
205
|
+
rsakeypair TP-self-signed-2345678901
|
206
|
+
END
|
207
|
+
result = klass.new.call(text)
|
208
|
+
expect(result).not_to be_nil
|
209
|
+
end
|
210
|
+
|
211
|
+
it('parses a crypto certificate section') do
|
212
|
+
sp = ' '
|
213
|
+
text = <<END
|
214
|
+
crypto pki certificate chain TP-self-signed-1234567890
|
215
|
+
certificate self-signed 01
|
216
|
+
FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF#{sp}
|
217
|
+
EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE#{sp}
|
218
|
+
DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD#{sp}
|
219
|
+
CCCCCCCC CCCCCCCC
|
220
|
+
quit
|
221
|
+
|
222
|
+
END
|
223
|
+
|
224
|
+
result = klass.new.call(text)
|
225
|
+
expect(result).not_to be_nil
|
226
|
+
end
|
227
|
+
|
228
|
+
it('parses an MST configuration section') do
|
229
|
+
text = <<END
|
230
|
+
spanning-tree mst configuration
|
231
|
+
name MyMSTConfig
|
232
|
+
revision 1
|
233
|
+
instance 1 vlan 1-59, 4000
|
234
|
+
instance 2 vlan 90-99
|
235
|
+
instance 3 vlan 100-1500
|
236
|
+
instance 4 vlan 2000-3500, 4000
|
237
|
+
END
|
238
|
+
|
239
|
+
result = klass.new.call(text)
|
240
|
+
expect(result).not_to be_nil
|
241
|
+
end
|
242
|
+
end # context '2950'
|
243
|
+
|
244
|
+
it('finds various ip route formats') do
|
245
|
+
text = <<END
|
246
|
+
ip route 10.0.0.1 255.255.255.255 Null0
|
247
|
+
ip route 9.9.9.199 255.255.255.255 42.42.42.142 name PONIES
|
248
|
+
ip route vrf Mgmt-intf 0.0.0.0 0.0.0.0 9.9.9.199
|
249
|
+
ip route 0.0.0.0/0 11.11.0.111 120
|
250
|
+
END
|
251
|
+
|
252
|
+
result = klass.new.call(text)
|
253
|
+
|
254
|
+
cmd_ary = [
|
255
|
+
{ args: ['ip', 'route', '10.0.0.1', '255.255.255.255',
|
256
|
+
'Null0'],
|
257
|
+
commands: [], pos: 0 },
|
258
|
+
{ args: ['ip', 'route', '9.9.9.199', '255.255.255.255',
|
259
|
+
'42.42.42.142', 'name', 'PONIES'],
|
260
|
+
commands: [], pos: 40 },
|
261
|
+
{ args: ['ip', 'route', 'vrf', 'Mgmt-intf', '0.0.0.0',
|
262
|
+
'0.0.0.0', '9.9.9.199'],
|
263
|
+
commands: [], pos: 100 },
|
264
|
+
{ args: ['ip', 'route', '0.0.0.0/0', '11.11.0.111', 120],
|
265
|
+
commands: [], pos: 149 }
|
266
|
+
]
|
267
|
+
|
268
|
+
expect(result.find_all('ip route').map(&:to_hash)).to eq(cmd_ary)
|
269
|
+
|
270
|
+
expect(result.find_all('ip route 9.9.9.199').length).to eq 1
|
271
|
+
|
272
|
+
cmd_hash = { args: ['ip', 'route', '9.9.9.199', '255.255.255.255',
|
273
|
+
'42.42.42.142', 'name', 'PONIES'],
|
274
|
+
commands: [], pos: 40 }
|
275
|
+
expect(result.find('ip route 9.9.9.199').to_hash).to eq(cmd_hash)
|
276
|
+
end # end context '#call'
|
277
|
+
|
278
|
+
describe '#to_s' do
|
279
|
+
subject { klass.new.call(input) }
|
280
|
+
let(:police2) { <<END }
|
281
|
+
police 600000000 1000000 exceed-action policed-dscp-transmit
|
282
|
+
set dscp cs2
|
283
|
+
command_with_no_args
|
284
|
+
END
|
285
|
+
|
286
|
+
it('returns the string form of the original command(s)') do
|
287
|
+
expect(subject.to_s).to eq input
|
288
|
+
expect(subject.find('policy-map').to_s).to eq input
|
289
|
+
expect(subject.find('command_with_no_args').to_s)
|
290
|
+
.to eq " command_with_no_args\n"
|
291
|
+
expect(subject.find('police 600000000').to_s).to eq police2
|
292
|
+
end
|
293
|
+
|
294
|
+
context 'with dedent: true' do
|
295
|
+
it('returns the original without extra indentation') do
|
296
|
+
expect(subject.find('police 600000000').to_s(dedent: true))
|
297
|
+
.to eq police2.lines.map { |l| l[2..-1] }.join
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end # describe '#to_s'
|
301
|
+
|
302
|
+
describe '#each' do
|
303
|
+
subject { klass.new.call(input) }
|
304
|
+
it 'traverses the AST' do
|
305
|
+
actual_paths = subject.map(&:path)
|
306
|
+
expected_paths = [
|
307
|
+
[],
|
308
|
+
['policy-map mypolicy_in'],
|
309
|
+
['policy-map mypolicy_in',
|
310
|
+
'class myservice_service'],
|
311
|
+
['policy-map mypolicy_in',
|
312
|
+
'class myservice_service',
|
313
|
+
'police 300000000 1000000 exceed-action policed-dscp-transmit'],
|
314
|
+
['policy-map mypolicy_in'],
|
315
|
+
['policy-map mypolicy_in',
|
316
|
+
'class other_service'],
|
317
|
+
['policy-map mypolicy_in',
|
318
|
+
'class other_service',
|
319
|
+
'police 600000000 1000000 exceed-action policed-dscp-transmit'],
|
320
|
+
['policy-map mypolicy_in',
|
321
|
+
'class other_service',
|
322
|
+
'police 600000000 1000000 exceed-action policed-dscp-transmit']
|
323
|
+
]
|
324
|
+
expect(actual_paths).to eq(expected_paths)
|
325
|
+
end
|
326
|
+
end # describe '#each'
|
327
|
+
end # context 'indented region'
|
328
|
+
|
329
|
+
context 'empty source' do
|
330
|
+
context 'when input is not a string' do
|
331
|
+
it 'raises ArgumentError' do
|
332
|
+
expect { klass.new.call [] }.to raise_error ArgumentError
|
333
|
+
expect { klass.new.call nil }.to raise_error ArgumentError
|
334
|
+
expect { klass.new.call 666 }.to raise_error ArgumentError
|
335
|
+
end
|
336
|
+
end # context 'when input is not a string' do
|
337
|
+
end # context 'empty source' do
|
338
|
+
end # end describe IOS
|
339
|
+
end # end module IOSParser
|