1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3# 4# Copyright 2010 Google Inc. All Rights Reserved. 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 15# implied. See the License for the specific language governing 16# permissions and limitations under the License. 17 18"""Unittest for textfsm module.""" 19from __future__ import absolute_import 20from __future__ import division 21from __future__ import print_function 22from __future__ import unicode_literals 23 24from builtins import str 25import unittest 26from io import StringIO 27 28 29 30import textfsm 31 32 33class UnitTestFSM(unittest.TestCase): 34 """Tests the FSM engine.""" 35 36 def testFSMValue(self): 37 # Check basic line is parsed. 38 line = r'Value beer (\S+)' 39 v = textfsm.TextFSMValue() 40 v.Parse(line) 41 self.assertEqual(v.name, 'beer') 42 self.assertEqual(v.regex, r'(\S+)') 43 self.assertEqual(v.template, r'(?P<beer>\S+)') 44 self.assertFalse(v.options) 45 46 # Test options 47 line = r'Value Filldown,Required beer (\S+)' 48 v = textfsm.TextFSMValue(options_class=textfsm.TextFSMOptions) 49 v.Parse(line) 50 self.assertEqual(v.name, 'beer') 51 self.assertEqual(v.regex, r'(\S+)') 52 self.assertEqual(v.OptionNames(), ['Filldown', 'Required']) 53 54 # Multiple parenthesis. 55 v = textfsm.TextFSMValue(options_class=textfsm.TextFSMOptions) 56 v.Parse('Value Required beer (boo(hoo))') 57 self.assertEqual(v.name, 'beer') 58 self.assertEqual(v.regex, '(boo(hoo))') 59 self.assertEqual(v.template, '(?P<beer>boo(hoo))') 60 self.assertEqual(v.OptionNames(), ['Required']) 61 62 # regex must be bounded by parenthesis. 63 self.assertRaises(textfsm.TextFSMTemplateError, 64 v.Parse, 65 'Value beer (boo(hoo)))boo') 66 self.assertRaises(textfsm.TextFSMTemplateError, 67 v.Parse, 68 'Value beer boo(boo(hoo)))') 69 self.assertRaises(textfsm.TextFSMTemplateError, 70 v.Parse, 71 'Value beer (boo)hoo)') 72 73 # Unbalanced parenthesis can exist if within square "[]" braces. 74 v = textfsm.TextFSMValue(options_class=textfsm.TextFSMOptions) 75 v.Parse('Value beer (boo[(]hoo)') 76 self.assertEqual(v.name, 'beer') 77 self.assertEqual(v.regex, '(boo[(]hoo)') 78 79 # Escaped braces don't count. 80 self.assertRaises(textfsm.TextFSMTemplateError, 81 v.Parse, 82 'Value beer (boo\[)\]hoo)') 83 84 # String function. 85 v = textfsm.TextFSMValue(options_class=textfsm.TextFSMOptions) 86 v.Parse('Value Required beer (boo(hoo))') 87 self.assertEqual(str(v), 'Value Required beer (boo(hoo))') 88 v = textfsm.TextFSMValue(options_class=textfsm.TextFSMOptions) 89 v.Parse( 90 r'Value Required,Filldown beer (bo\S+(hoo))') 91 self.assertEqual(str(v), r'Value Required,Filldown beer (bo\S+(hoo))') 92 93 def testFSMRule(self): 94 95 # Basic line, no action 96 line = ' ^A beer called ${beer}' 97 r = textfsm.TextFSMRule(line) 98 self.assertEqual(r.match, '^A beer called ${beer}') 99 self.assertEqual(r.line_op, '') 100 self.assertEqual(r.new_state, '') 101 self.assertEqual(r.record_op, '') 102 # Multiple matches 103 line = ' ^A $hi called ${beer}' 104 r = textfsm.TextFSMRule(line) 105 self.assertEqual(r.match, '^A $hi called ${beer}') 106 self.assertEqual(r.line_op, '') 107 self.assertEqual(r.new_state, '') 108 self.assertEqual(r.record_op, '') 109 110 # Line with action. 111 line = ' ^A beer called ${beer} -> Next' 112 r = textfsm.TextFSMRule(line) 113 self.assertEqual(r.match, '^A beer called ${beer}') 114 self.assertEqual(r.line_op, 'Next') 115 self.assertEqual(r.new_state, '') 116 self.assertEqual(r.record_op, '') 117 118 # Line with record. 119 line = ' ^A beer called ${beer} -> Continue.Record' 120 r = textfsm.TextFSMRule(line) 121 self.assertEqual(r.match, '^A beer called ${beer}') 122 self.assertEqual(r.line_op, 'Continue') 123 self.assertEqual(r.new_state, '') 124 self.assertEqual(r.record_op, 'Record') 125 126 # Line with new state. 127 line = ' ^A beer called ${beer} -> Next.NoRecord End' 128 r = textfsm.TextFSMRule(line) 129 self.assertEqual(r.match, '^A beer called ${beer}') 130 self.assertEqual(r.line_op, 'Next') 131 self.assertEqual(r.new_state, 'End') 132 self.assertEqual(r.record_op, 'NoRecord') 133 134 # Bad syntax tests. 135 self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSMRule, 136 ' ^A beer called ${beer} -> Next Next Next') 137 self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSMRule, 138 ' ^A beer called ${beer} -> Boo.hoo') 139 self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSMRule, 140 ' ^A beer called ${beer} -> Continue.Record $Hi') 141 142 def testRulePrefixes(self): 143 """Test valid and invalid rule prefixes.""" 144 145 # Bad syntax tests. 146 for prefix in (' ', '.^', ' \t', ''): 147 f = StringIO('Value unused (.)\n\nStart\n' + prefix + 'A simple string.') 148 self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSM, f) 149 150 # Good syntax tests. 151 for prefix in (' ^', ' ^', '\t^'): 152 f = StringIO('Value unused (.)\n\nStart\n' + prefix + 'A simple string.') 153 self.assertIsNotNone(textfsm.TextFSM(f)) 154 155 def testImplicitDefaultRules(self): 156 157 for line in (' ^A beer called ${beer} -> Record End', 158 ' ^A beer called ${beer} -> End', 159 ' ^A beer called ${beer} -> Next.NoRecord End', 160 ' ^A beer called ${beer} -> Clear End', 161 ' ^A beer called ${beer} -> Error "Hello World"'): 162 r = textfsm.TextFSMRule(line) 163 self.assertEqual(str(r), line) 164 165 for line in (' ^A beer called ${beer} -> Next "Hello World"', 166 ' ^A beer called ${beer} -> Record.Next', 167 ' ^A beer called ${beer} -> Continue End', 168 ' ^A beer called ${beer} -> Beer End'): 169 self.assertRaises(textfsm.TextFSMTemplateError, 170 textfsm.TextFSMRule, line) 171 172 def testSpacesAroundAction(self): 173 for line in (' ^Hello World -> Boo', 174 ' ^Hello World -> Boo', 175 ' ^Hello World -> Boo'): 176 self.assertEqual( 177 str(textfsm.TextFSMRule(line)), ' ^Hello World -> Boo') 178 179 # A '->' without a leading space is considered part of the matching line. 180 self.assertEqual(' A simple line-> Boo -> Next', 181 str(textfsm.TextFSMRule(' A simple line-> Boo -> Next'))) 182 183 def testParseFSMVariables(self): 184 # Trivial template to initiate object. 185 f = StringIO('Value unused (.)\n\nStart\n') 186 t = textfsm.TextFSM(f) 187 188 # Trivial entry 189 buf = 'Value Filldown Beer (beer)\n\n' 190 f = StringIO(buf) 191 t._ParseFSMVariables(f) 192 193 # Single variable with commented header. 194 buf = '# Headline\nValue Filldown Beer (beer)\n\n' 195 f = StringIO(buf) 196 t._ParseFSMVariables(f) 197 self.assertEqual(str(t._GetValue('Beer')), 'Value Filldown Beer (beer)') 198 199 # Multiple variables. 200 buf = ('# Headline\n' 201 'Value Filldown Beer (beer)\n' 202 'Value Required Spirits (whiskey)\n' 203 'Value Filldown Wine (claret)\n' 204 '\n') 205 t._line_num = 0 206 f = StringIO(buf) 207 t._ParseFSMVariables(f) 208 self.assertEqual(str(t._GetValue('Beer')), 'Value Filldown Beer (beer)') 209 self.assertEqual( 210 str(t._GetValue('Spirits')), 'Value Required Spirits (whiskey)') 211 self.assertEqual(str(t._GetValue('Wine')), 'Value Filldown Wine (claret)') 212 213 # Multiple variables. 214 buf = ('# Headline\n' 215 'Value Filldown Beer (beer)\n' 216 ' # A comment\n' 217 'Value Spirits ()\n' 218 'Value Filldown,Required Wine ((c|C)laret)\n' 219 '\n') 220 221 f = StringIO(buf) 222 t._ParseFSMVariables(f) 223 self.assertEqual(str(t._GetValue('Beer')), 'Value Filldown Beer (beer)') 224 self.assertEqual( 225 str(t._GetValue('Spirits')), 'Value Spirits ()') 226 self.assertEqual(str(t._GetValue('Wine')), 227 'Value Filldown,Required Wine ((c|C)laret)') 228 229 # Malformed variables. 230 buf = 'Value Beer (beer) beer' 231 f = StringIO(buf) 232 self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMVariables, f) 233 234 buf = 'Value Filldown, Required Spirits ()' 235 f = StringIO(buf) 236 self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMVariables, f) 237 buf = 'Value filldown,Required Wine ((c|C)laret)' 238 f = StringIO(buf) 239 self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMVariables, f) 240 241 # Values that look bad but are OK. 242 buf = ('# Headline\n' 243 'Value Filldown Beer (bee(r), (and) (M)ead$)\n' 244 '# A comment\n' 245 'Value Spirits,and,some ()\n' 246 'Value Filldown,Required Wine ((c|C)laret)\n' 247 '\n') 248 f = StringIO(buf) 249 t._ParseFSMVariables(f) 250 self.assertEqual(str(t._GetValue('Beer')), 251 'Value Filldown Beer (bee(r), (and) (M)ead$)') 252 self.assertEqual( 253 str(t._GetValue('Spirits,and,some')), 'Value Spirits,and,some ()') 254 self.assertEqual(str(t._GetValue('Wine')), 255 'Value Filldown,Required Wine ((c|C)laret)') 256 257 # Variable name too long. 258 buf = ('Value Filldown ' 259 'nametoolong_nametoolong_nametoolo_nametoolong_nametoolong ' 260 '(beer)\n\n') 261 f = StringIO(buf) 262 self.assertRaises(textfsm.TextFSMTemplateError, 263 t._ParseFSMVariables, f) 264 265 def testParseFSMState(self): 266 267 f = StringIO('Value Beer (.)\nValue Wine (\\w)\n\nStart\n') 268 t = textfsm.TextFSM(f) 269 270 # Fails as we already have 'Start' state. 271 buf = 'Start\n ^.\n' 272 f = StringIO(buf) 273 self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMState, f) 274 275 # Remove start so we can test new Start state. 276 t.states = {} 277 278 # Single state. 279 buf = '# Headline\nStart\n ^.\n\n' 280 f = StringIO(buf) 281 t._ParseFSMState(f) 282 self.assertEqual(str(t.states['Start'][0]), ' ^.') 283 try: 284 _ = t.states['Start'][1] 285 except IndexError: 286 pass 287 288 # Multiple states. 289 buf = '# Headline\nStart\n ^.\n ^Hello World\n ^Last-[Cc]ha$$nge\n' 290 f = StringIO(buf) 291 t._line_num = 0 292 t.states = {} 293 t._ParseFSMState(f) 294 self.assertEqual(str(t.states['Start'][0]), ' ^.') 295 self.assertEqual(str(t.states['Start'][1]), ' ^Hello World') 296 self.assertEqual(t.states['Start'][1].line_num, 4) 297 self.assertEqual(str(t.states['Start'][2]), ' ^Last-[Cc]ha$$nge') 298 try: 299 _ = t.states['Start'][3] 300 except IndexError: 301 pass 302 303 t.states = {} 304 # Malformed states. 305 buf = 'St%art\n ^.\n ^Hello World\n' 306 f = StringIO(buf) 307 self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMState, f) 308 309 buf = 'Start\n^.\n ^Hello World\n' 310 f = StringIO(buf) 311 self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMState, f) 312 313 buf = ' Start\n ^.\n ^Hello World\n' 314 f = StringIO(buf) 315 self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMState, f) 316 317 # Multiple variables and substitution (depends on _ParseFSMVariables). 318 buf = ('# Headline\nStart\n ^.${Beer}${Wine}.\n' 319 ' ^Hello $Beer\n ^Last-[Cc]ha$$nge\n') 320 f = StringIO(buf) 321 t.states = {} 322 t._ParseFSMState(f) 323 self.assertEqual(str(t.states['Start'][0]), ' ^.${Beer}${Wine}.') 324 self.assertEqual(str(t.states['Start'][1]), ' ^Hello $Beer') 325 self.assertEqual(str(t.states['Start'][2]), ' ^Last-[Cc]ha$$nge') 326 try: 327 _ = t.states['Start'][3] 328 except IndexError: 329 pass 330 331 t.states['bogus'] = [] 332 333 # State name too long (>32 char). 334 buf = 'rnametoolong_nametoolong_nametoolong_nametoolong_nametoolo\n ^.\n\n' 335 f = StringIO(buf) 336 self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMState, f) 337 338 def testInvalidStates(self): 339 340 # 'Continue' should not accept a destination. 341 self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSMRule, 342 '^.* -> Continue Start') 343 344 # 'Error' accepts a text string but "next' state does not. 345 self.assertEqual(str(textfsm.TextFSMRule(' ^ -> Error "hi there"')), 346 ' ^ -> Error "hi there"') 347 self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSMRule, 348 '^.* -> Next "Hello World"') 349 350 def testRuleStartsWithCarrot(self): 351 352 f = StringIO( 353 'Value Beer (.)\nValue Wine (\\w)\n\nStart\n A Simple line') 354 self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSM, f) 355 356 def testValidateFSM(self): 357 358 # No Values. 359 f = StringIO('\nNotStart\n') 360 self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSM, f) 361 362 # No states. 363 f = StringIO('Value unused (.)\n\n') 364 self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSM, f) 365 366 # No 'Start' state. 367 f = StringIO('Value unused (.)\n\nNotStart\n') 368 self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSM, f) 369 370 # Has 'Start' state with valid destination 371 f = StringIO('Value unused (.)\n\nStart\n') 372 t = textfsm.TextFSM(f) 373 t.states['Start'] = [] 374 t.states['Start'].append(textfsm.TextFSMRule('^.* -> Start')) 375 t._ValidateFSM() 376 377 # Invalid destination. 378 t.states['Start'].append(textfsm.TextFSMRule('^.* -> bogus')) 379 self.assertRaises(textfsm.TextFSMTemplateError, t._ValidateFSM) 380 381 # Now valid again. 382 t.states['bogus'] = [] 383 t.states['bogus'].append(textfsm.TextFSMRule('^.* -> Start')) 384 t._ValidateFSM() 385 386 # Valid destination with options. 387 t.states['bogus'] = [] 388 t.states['bogus'].append(textfsm.TextFSMRule('^.* -> Next.Record Start')) 389 t._ValidateFSM() 390 391 # Error with and without messages string. 392 t.states['bogus'] = [] 393 t.states['bogus'].append(textfsm.TextFSMRule('^.* -> Error')) 394 t._ValidateFSM() 395 t.states['bogus'].append(textfsm.TextFSMRule('^.* -> Error "Boo hoo"')) 396 t._ValidateFSM() 397 398 def testTextFSM(self): 399 400 # Trivial template 401 buf = 'Value Beer (.*)\n\nStart\n ^\\w\n' 402 buf_result = buf 403 f = StringIO(buf) 404 t = textfsm.TextFSM(f) 405 self.assertEqual(str(t), buf_result) 406 407 # Slightly more complex, multple vars. 408 buf = 'Value A (.*)\nValue B (.*)\n\nStart\n ^\\w\n\nState1\n ^.\n' 409 buf_result = buf 410 f = StringIO(buf) 411 t = textfsm.TextFSM(f) 412 self.assertEqual(str(t), buf_result) 413 414 def testParseText(self): 415 416 # Trivial FSM, no records produced. 417 tplt = 'Value unused (.)\n\nStart\n ^Trivial SFM\n' 418 t = textfsm.TextFSM(StringIO(tplt)) 419 420 data = 'Non-matching text\nline1\nline 2\n' 421 self.assertFalse(t.ParseText(data)) 422 # Matching. 423 data = 'Matching text\nTrivial SFM\nline 2\n' 424 self.assertFalse(t.ParseText(data)) 425 426 # Simple FSM, One Variable no options. 427 tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next.Record\n\nEOF\n' 428 t = textfsm.TextFSM(StringIO(tplt)) 429 430 # Matching one line. 431 # Tests 'Next' & 'Record' actions. 432 data = 'Matching text' 433 result = t.ParseText(data) 434 self.assertListEqual(result, [['Matching text']]) 435 436 # Matching two lines. Reseting FSM before Parsing. 437 t.Reset() 438 data = 'Matching text\nAnd again' 439 result = t.ParseText(data) 440 self.assertListEqual(result, [['Matching text'], ['And again']]) 441 442 # Two Variables and singular options. 443 tplt = ('Value Required boo (one)\nValue Filldown hoo (two)\n\n' 444 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Record\n\n' 445 'EOF\n') 446 t = textfsm.TextFSM(StringIO(tplt)) 447 448 # Matching two lines. Only one records returned due to 'Required' flag. 449 # Tests 'Filldown' and 'Required' options. 450 data = 'two\none' 451 result = t.ParseText(data) 452 self.assertListEqual(result, [['one', 'two']]) 453 454 t = textfsm.TextFSM(StringIO(tplt)) 455 # Matching two lines. Two records returned due to 'Filldown' flag. 456 data = 'two\none\none' 457 t.Reset() 458 result = t.ParseText(data) 459 self.assertListEqual(result, [['one', 'two'], ['one', 'two']]) 460 461 # Multiple Variables and options. 462 tplt = ('Value Required,Filldown boo (one)\n' 463 'Value Filldown,Required hoo (two)\n\n' 464 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Record\n\n' 465 'EOF\n') 466 t = textfsm.TextFSM(StringIO(tplt)) 467 data = 'two\none\none' 468 result = t.ParseText(data) 469 self.assertListEqual(result, [['one', 'two'], ['one', 'two']]) 470 471 def testParseTextToDicts(self): 472 473 # Trivial FSM, no records produced. 474 tplt = 'Value unused (.)\n\nStart\n ^Trivial SFM\n' 475 t = textfsm.TextFSM(StringIO(tplt)) 476 477 data = 'Non-matching text\nline1\nline 2\n' 478 self.assertFalse(t.ParseText(data)) 479 # Matching. 480 data = 'Matching text\nTrivial SFM\nline 2\n' 481 self.assertFalse(t.ParseText(data)) 482 483 # Simple FSM, One Variable no options. 484 tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next.Record\n\nEOF\n' 485 t = textfsm.TextFSM(StringIO(tplt)) 486 487 # Matching one line. 488 # Tests 'Next' & 'Record' actions. 489 data = 'Matching text' 490 result = t.ParseTextToDicts(data) 491 self.assertListEqual(result, [{'boo': 'Matching text'}]) 492 493 # Matching two lines. Reseting FSM before Parsing. 494 t.Reset() 495 data = 'Matching text\nAnd again' 496 result = t.ParseTextToDicts(data) 497 self.assertListEqual(result, 498 [{'boo': 'Matching text'}, {'boo': 'And again'}]) 499 500 # Two Variables and singular options. 501 tplt = ('Value Required boo (one)\nValue Filldown hoo (two)\n\n' 502 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Record\n\n' 503 'EOF\n') 504 t = textfsm.TextFSM(StringIO(tplt)) 505 506 # Matching two lines. Only one records returned due to 'Required' flag. 507 # Tests 'Filldown' and 'Required' options. 508 data = 'two\none' 509 result = t.ParseTextToDicts(data) 510 self.assertListEqual(result, [{'hoo': 'two', 'boo': 'one'}]) 511 512 t = textfsm.TextFSM(StringIO(tplt)) 513 # Matching two lines. Two records returned due to 'Filldown' flag. 514 data = 'two\none\none' 515 t.Reset() 516 result = t.ParseTextToDicts(data) 517 self.assertListEqual( 518 result, [{'hoo': 'two', 'boo': 'one'}, {'hoo': 'two', 'boo': 'one'}]) 519 520 # Multiple Variables and options. 521 tplt = ('Value Required,Filldown boo (one)\n' 522 'Value Filldown,Required hoo (two)\n\n' 523 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Record\n\n' 524 'EOF\n') 525 t = textfsm.TextFSM(StringIO(tplt)) 526 data = 'two\none\none' 527 result = t.ParseTextToDicts(data) 528 self.assertListEqual( 529 result, [{'hoo': 'two', 'boo': 'one'}, {'hoo': 'two', 'boo': 'one'}]) 530 531 def testParseNullText(self): 532 533 # Simple FSM, One Variable no options. 534 tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next.Record\n\n' 535 t = textfsm.TextFSM(StringIO(tplt)) 536 537 # Null string 538 data = '' 539 result = t.ParseText(data) 540 self.assertListEqual(result, []) 541 542 def testReset(self): 543 544 tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next.Record\n\nEOF\n' 545 t = textfsm.TextFSM(StringIO(tplt)) 546 data = 'Matching text' 547 result1 = t.ParseText(data) 548 t.Reset() 549 result2 = t.ParseText(data) 550 self.assertListEqual(result1, result2) 551 552 tplt = ('Value boo (one)\nValue hoo (two)\n\n' 553 'Start\n ^$boo -> State1\n\n' 554 'State1\n ^$hoo -> Start\n\n' 555 'EOF') 556 t = textfsm.TextFSM(StringIO(tplt)) 557 558 data = 'one' 559 t.ParseText(data) 560 t.Reset() 561 self.assertEqual(t._cur_state[0].match, '^$boo') 562 self.assertEqual(t._GetValue('boo').value, None) 563 self.assertEqual(t._GetValue('hoo').value, None) 564 self.assertEqual(t._result, []) 565 566 def testClear(self): 567 568 # Clear Filldown variable. 569 # Tests 'Clear'. 570 tplt = ('Value Required boo (on.)\n' 571 'Value Filldown,Required hoo (tw.)\n\n' 572 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Clear') 573 574 t = textfsm.TextFSM(StringIO(tplt)) 575 data = 'one\ntwo\nonE\ntwO' 576 result = t.ParseText(data) 577 self.assertListEqual(result, [['onE', 'two']]) 578 579 # Clearall, with Filldown variable. 580 # Tests 'Clearall'. 581 tplt = ('Value Filldown boo (on.)\n' 582 'Value Filldown hoo (tw.)\n\n' 583 'Start\n ^$boo -> Next.Clearall\n' 584 ' ^$hoo') 585 586 t = textfsm.TextFSM(StringIO(tplt)) 587 data = 'one\ntwo' 588 result = t.ParseText(data) 589 self.assertListEqual(result, [['', 'two']]) 590 591 def testContinue(self): 592 593 tplt = ('Value Required boo (on.)\n' 594 'Value Filldown,Required hoo (on.)\n\n' 595 'Start\n ^$boo -> Continue\n ^$hoo -> Continue.Record') 596 597 t = textfsm.TextFSM(StringIO(tplt)) 598 data = 'one\non0' 599 result = t.ParseText(data) 600 self.assertListEqual(result, [['one', 'one'], ['on0', 'on0']]) 601 602 def testError(self): 603 604 tplt = ('Value Required boo (on.)\n' 605 'Value Filldown,Required hoo (on.)\n\n' 606 'Start\n ^$boo -> Continue\n ^$hoo -> Error') 607 608 t = textfsm.TextFSM(StringIO(tplt)) 609 data = 'one' 610 self.assertRaises(textfsm.TextFSMError, t.ParseText, data) 611 612 tplt = ('Value Required boo (on.)\n' 613 'Value Filldown,Required hoo (on.)\n\n' 614 'Start\n ^$boo -> Continue\n ^$hoo -> Error "Hello World"') 615 616 t = textfsm.TextFSM(StringIO(tplt)) 617 self.assertRaises(textfsm.TextFSMError, t.ParseText, data) 618 619 def testKey(self): 620 tplt = ('Value Required boo (on.)\n' 621 'Value Required,Key hoo (on.)\n\n' 622 'Start\n ^$boo -> Continue\n ^$hoo -> Record') 623 624 t = textfsm.TextFSM(StringIO(tplt)) 625 self.assertTrue('Key' in t._GetValue('hoo').OptionNames()) 626 self.assertTrue('Key' not in t._GetValue('boo').OptionNames()) 627 628 def testList(self): 629 630 tplt = ('Value List boo (on.)\n' 631 'Value hoo (tw.)\n\n' 632 'Start\n ^$boo\n ^$hoo -> Next.Record\n\n' 633 'EOF') 634 635 t = textfsm.TextFSM(StringIO(tplt)) 636 data = 'one\ntwo\non0\ntw0' 637 result = t.ParseText(data) 638 self.assertListEqual(result, [[['one'], 'two'], [['on0'], 'tw0']]) 639 640 tplt = ('Value List,Filldown boo (on.)\n' 641 'Value hoo (on.)\n\n' 642 'Start\n ^$boo -> Continue\n ^$hoo -> Next.Record\n\n' 643 'EOF') 644 645 t = textfsm.TextFSM(StringIO(tplt)) 646 data = 'one\non0\non1' 647 result = t.ParseText(data) 648 self.assertEqual(result, ([[['one'], 'one'], 649 [['one', 'on0'], 'on0'], 650 [['one', 'on0', 'on1'], 'on1']])) 651 652 tplt = ('Value List,Required boo (on.)\n' 653 'Value hoo (tw.)\n\n' 654 'Start\n ^$boo -> Continue\n ^$hoo -> Next.Record\n\n' 655 'EOF') 656 657 t = textfsm.TextFSM(StringIO(tplt)) 658 data = 'one\ntwo\ntw2' 659 result = t.ParseText(data) 660 self.assertListEqual(result, [[['one'], 'two']]) 661 662 663 def testNestedMatching(self): 664 """ 665 Ensures that List-type values with nested regex capture groups are parsed 666 correctly as a list of dictionaries. 667 668 Additionaly, another value is used with the same group-name as one of the 669 nested groups to ensure that there are no conflicts when the same name is 670 used. 671 """ 672 tplt = ( 673 # A nested group is called "name" 674 r"Value List foo ((?P<name>\w+):\s+(?P<age>\d+)\s+(?P<state>\w{2})\s*)" 675 "\n" 676 # A regular value is called "name" 677 r"Value name (\w+)" 678 # "${name}" here refers to the Value called "name" 679 "\n\nStart\n" 680 r" ^\s*${foo}" 681 "\n" 682 r" ^\s*${name}" 683 "\n" 684 r" ^\s*$$ -> Record" 685 ) 686 t = textfsm.TextFSM(StringIO(tplt)) 687 # Julia should be parsed as "name" separately 688 data = " Bob: 32 NC\n Alice: 27 NY\n Jeff: 45 CA\nJulia\n\n" 689 result = t.ParseText(data) 690 self.assertListEqual( 691 result, ( 692 [[[ 693 {'name': 'Bob', 'age': '32', 'state': 'NC'}, 694 {'name': 'Alice', 'age': '27', 'state': 'NY'}, 695 {'name': 'Jeff', 'age': '45', 'state': 'CA'} 696 ], 'Julia']] 697 ) 698 ) 699 700 def testNestedNameConflict(self): 701 tplt = ( 702 # Two nested groups are called "name" 703 r"Value List foo ((?P<name>\w+)\s+(?P<name>\w+):\s+(?P<age>\d+)\s+(?P<state>\w{2})\s*)" 704 "\nStart\n" 705 r"^\s*${foo}" 706 "\n ^" 707 r"\s*$$ -> Record" 708 ) 709 self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSM, StringIO(tplt)) 710 711 712 def testGetValuesByAttrib(self): 713 714 tplt = ('Value Required boo (on.)\n' 715 'Value Required,List hoo (on.)\n\n' 716 'Start\n ^$boo -> Continue\n ^$hoo -> Record') 717 718 # Explicit default. 719 t = textfsm.TextFSM(StringIO(tplt)) 720 self.assertEqual(t.GetValuesByAttrib('List'), ['hoo']) 721 self.assertEqual(t.GetValuesByAttrib('Filldown'), []) 722 result = t.GetValuesByAttrib('Required') 723 result.sort() 724 self.assertListEqual(result, ['boo', 'hoo']) 725 726 def testStateChange(self): 727 728 # Sinple state change, no actions 729 tplt = ('Value boo (one)\nValue hoo (two)\n\n' 730 'Start\n ^$boo -> State1\n\nState1\n ^$hoo -> Start\n\n' 731 'EOF') 732 t = textfsm.TextFSM(StringIO(tplt)) 733 734 data = 'one' 735 t.ParseText(data) 736 self.assertEqual(t._cur_state[0].match, '^$hoo') 737 self.assertEqual('one', t._GetValue('boo').value) 738 self.assertEqual(None, t._GetValue('hoo').value) 739 self.assertEqual(t._result, []) 740 741 # State change with actions. 742 tplt = ('Value boo (one)\nValue hoo (two)\n\n' 743 'Start\n ^$boo -> Next.Record State1\n\n' 744 'State1\n ^$hoo -> Start\n\n' 745 'EOF') 746 t = textfsm.TextFSM(StringIO(tplt)) 747 748 data = 'one' 749 t.ParseText(data) 750 self.assertEqual(t._cur_state[0].match, '^$hoo') 751 self.assertEqual(None, t._GetValue('boo').value) 752 self.assertEqual(None, t._GetValue('hoo').value) 753 self.assertEqual(t._result, [['one', '']]) 754 755 def testEOF(self): 756 757 # Implicit EOF. 758 tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next\n' 759 t = textfsm.TextFSM(StringIO(tplt)) 760 761 data = 'Matching text' 762 result = t.ParseText(data) 763 self.assertListEqual(result, [['Matching text']]) 764 765 # EOF explicitly suppressed in template. 766 tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next\n\nEOF\n' 767 t = textfsm.TextFSM(StringIO(tplt)) 768 769 result = t.ParseText(data) 770 self.assertListEqual(result, []) 771 772 # Implicit EOF suppressed by argument. 773 tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next\n' 774 t = textfsm.TextFSM(StringIO(tplt)) 775 776 result = t.ParseText(data, eof=False) 777 self.assertListEqual(result, []) 778 779 def testEnd(self): 780 781 # End State, EOF is skipped. 782 tplt = 'Value boo (.*)\n\nStart\n ^$boo -> End\n ^$boo -> Record\n' 783 t = textfsm.TextFSM(StringIO(tplt)) 784 data = 'Matching text A\nMatching text B' 785 786 result = t.ParseText(data) 787 self.assertListEqual(result, []) 788 789 # End State, with explicit Record. 790 tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Record End\n' 791 t = textfsm.TextFSM(StringIO(tplt)) 792 793 result = t.ParseText(data) 794 self.assertListEqual(result, [['Matching text A']]) 795 796 # EOF state transition is followed by implicit End State. 797 tplt = 'Value boo (.*)\n\nStart\n ^$boo -> EOF\n ^$boo -> Record\n' 798 t = textfsm.TextFSM(StringIO(tplt)) 799 800 result = t.ParseText(data) 801 self.assertListEqual(result, [['Matching text A']]) 802 803 def testInvalidRegexp(self): 804 805 tplt = 'Value boo (.$*)\n\nStart\n ^$boo -> Next\n' 806 self.assertRaises(textfsm.TextFSMTemplateError, 807 textfsm.TextFSM, StringIO(tplt)) 808 809 def testValidRegexp(self): 810 """RegexObjects uncopyable in Python 2.6.""" 811 812 tplt = 'Value boo (fo*)\n\nStart\n ^$boo -> Record\n' 813 t = textfsm.TextFSM(StringIO(tplt)) 814 data = 'f\nfo\nfoo\n' 815 result = t.ParseText(data) 816 self.assertListEqual(result, [['f'], ['fo'], ['foo']]) 817 818 def testReEnteringState(self): 819 """Issue 2. TextFSM should leave file pointer at top of template file.""" 820 821 tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next Stop\n\nStop\n ^abc\n' 822 output_text = 'one\ntwo' 823 tmpl_file = StringIO(tplt) 824 825 t = textfsm.TextFSM(tmpl_file) 826 t.ParseText(output_text) 827 t = textfsm.TextFSM(tmpl_file) 828 t.ParseText(output_text) 829 830 def testFillup(self): 831 """Fillup should work ok.""" 832 tplt = """Value Required Col1 ([^-]+) 833Value Fillup Col2 ([^-]+) 834Value Fillup Col3 ([^-]+) 835 836Start 837 ^$Col1 -- -- -> Record 838 ^$Col1 $Col2 -- -> Record 839 ^$Col1 -- $Col3 -> Record 840 ^$Col1 $Col2 $Col3 -> Record 841""" 842 data = """ 8431 -- B1 8442 A2 -- 8453 -- B3 846""" 847 t = textfsm.TextFSM(StringIO(tplt)) 848 result = t.ParseText(data) 849 self.assertListEqual( 850 result, [['1', 'A2', 'B1'], ['2', 'A2', 'B3'], ['3', '', 'B3']]) 851 852 853class UnitTestUnicode(unittest.TestCase): 854 """Tests the FSM engine.""" 855 856 def testFSMValue(self): 857 # Check basic line is parsed. 858 line = 'Value beer (\\S+Δ)' 859 v = textfsm.TextFSMValue() 860 v.Parse(line) 861 self.assertEqual(v.name, 'beer') 862 self.assertEqual(v.regex, '(\\S+Δ)') 863 self.assertEqual(v.template, '(?P<beer>\\S+Δ)') 864 self.assertFalse(v.options) 865 866 def testFSMRule(self): 867 # Basic line, no action 868 line = ' ^A beer called ${beer}Δ' 869 r = textfsm.TextFSMRule(line) 870 self.assertEqual(r.match, '^A beer called ${beer}Δ') 871 self.assertEqual(r.line_op, '') 872 self.assertEqual(r.new_state, '') 873 self.assertEqual(r.record_op, '') 874 875 def testTemplateValue(self): 876 # Complex template, multiple vars and states with comments (no var options). 877 buf = """# Header 878# Header 2 879Value Beer (.*) 880Value Wine (\\w+) 881 882# An explanation with a unicode character Δ 883Start 884 ^hi there ${Wine}. -> Next.Record State1 885 886State1 887 ^\\wΔ 888 ^$Beer .. -> Start 889 # Some comments 890 ^$$ -> Next 891 ^$$ -> End 892 893End 894# Tail comment. 895""" 896 897 buf_result = """Value Beer (.*) 898Value Wine (\\w+) 899 900Start 901 ^hi there ${Wine}. -> Next.Record State1 902 903State1 904 ^\\wΔ 905 ^$Beer .. -> Start 906 ^$$ -> Next 907 ^$$ -> End 908""" 909 f = StringIO(buf) 910 t = textfsm.TextFSM(f) 911 self.assertEqual(str(t), buf_result) 912 913 914if __name__ == '__main__': 915 unittest.main() 916