1# 2# test_simple_unit.py 3# 4# While these unit tests *do* perform low-level unit testing of the classes in pyparsing, 5# this testing module should also serve an instructional purpose, to clearly show simple passing 6# and failing parse cases of some basic pyparsing expressions. 7# 8# Copyright (c) 2018 Paul T. McGuire 9# 10import unittest 11import pyparsing as pp 12from collections import namedtuple 13from datetime import datetime 14 15ppt = pp.pyparsing_test 16TestParseResultsAsserts = ppt.TestParseResultsAsserts 17 18# Test spec data class for specifying simple pyparsing test cases 19PpTestSpec = namedtuple( 20 "PpTestSpec", 21 "desc expr text parse_fn " "expected_list expected_dict expected_fail_locn", 22) 23PpTestSpec.__new__.__defaults__ = ("", pp.Empty(), "", "parseString", None, None, None) 24 25 26class PyparsingExpressionTestCase(ppt.TestParseResultsAsserts, unittest.TestCase): 27 """ 28 Base pyparsing testing class to parse various pyparsing expressions against 29 given text strings. Subclasses must define a class attribute 'tests' which 30 is a list of PpTestSpec instances. 31 """ 32 33 tests = [] 34 35 def runTest(self): 36 if self.__class__ is PyparsingExpressionTestCase: 37 return 38 39 for test_spec in self.tests: 40 # for each spec in the class's tests list, create a subtest 41 # that will either: 42 # - parse the string with expected success, display the 43 # results, and validate the returned ParseResults 44 # - or parse the string with expected failure, display the 45 # error message and mark the error location, and validate 46 # the location against an expected value 47 with self.subTest(test_spec=test_spec): 48 test_spec.expr.streamline() 49 print( 50 "\n{} - {}({})".format( 51 test_spec.desc, type(test_spec.expr).__name__, test_spec.expr 52 ) 53 ) 54 55 parsefn = getattr(test_spec.expr, test_spec.parse_fn) 56 if test_spec.expected_fail_locn is None: 57 # expect success 58 result = parsefn(test_spec.text) 59 if test_spec.parse_fn == "parseString": 60 print(result.dump()) 61 # compare results against given list and/or dict 62 self.assertParseResultsEquals( 63 result, 64 expected_list=test_spec.expected_list, 65 expected_dict=test_spec.expected_dict, 66 ) 67 elif test_spec.parse_fn == "transformString": 68 print(result) 69 # compare results against given list and/or dict 70 if test_spec.expected_list is not None: 71 self.assertEqual([result], test_spec.expected_list) 72 elif test_spec.parse_fn == "searchString": 73 print(result) 74 # compare results against given list and/or dict 75 if test_spec.expected_list is not None: 76 self.assertEqual([result], test_spec.expected_list) 77 else: 78 # expect fail 79 with self.assertRaisesParseException(): 80 try: 81 parsefn(test_spec.text) 82 except Exception as exc: 83 print(pp.ParseException.explain(exc)) 84 self.assertEqual(exc.loc, test_spec.expected_fail_locn) 85 raise 86 87 88# =========== TEST DEFINITIONS START HERE ============== 89 90 91class TestLiteral(PyparsingExpressionTestCase): 92 tests = [ 93 PpTestSpec( 94 desc="Simple match", 95 expr=pp.Literal("xyz"), 96 text="xyz", 97 expected_list=["xyz"], 98 ), 99 PpTestSpec( 100 desc="Simple match after skipping whitespace", 101 expr=pp.Literal("xyz"), 102 text=" xyz", 103 expected_list=["xyz"], 104 ), 105 PpTestSpec( 106 desc="Simple fail - parse an empty string", 107 expr=pp.Literal("xyz"), 108 text="", 109 expected_fail_locn=0, 110 ), 111 PpTestSpec( 112 desc="Simple fail - parse a mismatching string", 113 expr=pp.Literal("xyz"), 114 text="xyu", 115 expected_fail_locn=0, 116 ), 117 PpTestSpec( 118 desc="Simple fail - parse a partially matching string", 119 expr=pp.Literal("xyz"), 120 text="xy", 121 expected_fail_locn=0, 122 ), 123 PpTestSpec( 124 desc="Fail - parse a partially matching string by matching individual letters", 125 expr=pp.Literal("x") + pp.Literal("y") + pp.Literal("z"), 126 text="xy", 127 expected_fail_locn=2, 128 ), 129 ] 130 131 132class TestCaselessLiteral(PyparsingExpressionTestCase): 133 tests = [ 134 PpTestSpec( 135 desc="Match colors, converting to consistent case", 136 expr=( 137 pp.CaselessLiteral("RED") 138 | pp.CaselessLiteral("GREEN") 139 | pp.CaselessLiteral("BLUE") 140 )[...], 141 text="red Green BluE blue GREEN green rEd", 142 expected_list=["RED", "GREEN", "BLUE", "BLUE", "GREEN", "GREEN", "RED"], 143 ), 144 ] 145 146 147class TestWord(PyparsingExpressionTestCase): 148 tests = [ 149 PpTestSpec( 150 desc="Simple Word match", 151 expr=pp.Word("xy"), 152 text="xxyxxyy", 153 expected_list=["xxyxxyy"], 154 ), 155 PpTestSpec( 156 desc="Simple Word match of two separate Words", 157 expr=pp.Word("x") + pp.Word("y"), 158 text="xxxxxyy", 159 expected_list=["xxxxx", "yy"], 160 ), 161 PpTestSpec( 162 desc="Simple Word match of two separate Words - implicitly skips whitespace", 163 expr=pp.Word("x") + pp.Word("y"), 164 text="xxxxx yy", 165 expected_list=["xxxxx", "yy"], 166 ), 167 ] 168 169 170class TestCombine(PyparsingExpressionTestCase): 171 tests = [ 172 PpTestSpec( 173 desc="Parsing real numbers - fail, parsed numbers are in pieces", 174 expr=(pp.Word(pp.nums) + "." + pp.Word(pp.nums))[...], 175 text="1.2 2.3 3.1416 98.6", 176 expected_list=[ 177 "1", 178 ".", 179 "2", 180 "2", 181 ".", 182 "3", 183 "3", 184 ".", 185 "1416", 186 "98", 187 ".", 188 "6", 189 ], 190 ), 191 PpTestSpec( 192 desc="Parsing real numbers - better, use Combine to combine multiple tokens into one", 193 expr=pp.Combine(pp.Word(pp.nums) + "." + pp.Word(pp.nums))[...], 194 text="1.2 2.3 3.1416 98.6", 195 expected_list=["1.2", "2.3", "3.1416", "98.6"], 196 ), 197 ] 198 199 200class TestRepetition(PyparsingExpressionTestCase): 201 tests = [ 202 PpTestSpec( 203 desc="Match several words", 204 expr=(pp.Word("x") | pp.Word("y"))[...], 205 text="xxyxxyyxxyxyxxxy", 206 expected_list=["xx", "y", "xx", "yy", "xx", "y", "x", "y", "xxx", "y"], 207 ), 208 PpTestSpec( 209 desc="Match several words, skipping whitespace", 210 expr=(pp.Word("x") | pp.Word("y"))[...], 211 text="x x y xxy yxx y xyx xxy", 212 expected_list=[ 213 "x", 214 "x", 215 "y", 216 "xx", 217 "y", 218 "y", 219 "xx", 220 "y", 221 "x", 222 "y", 223 "x", 224 "xx", 225 "y", 226 ], 227 ), 228 PpTestSpec( 229 desc="Match several words, skipping whitespace (old style)", 230 expr=pp.OneOrMore(pp.Word("x") | pp.Word("y")), 231 text="x x y xxy yxx y xyx xxy", 232 expected_list=[ 233 "x", 234 "x", 235 "y", 236 "xx", 237 "y", 238 "y", 239 "xx", 240 "y", 241 "x", 242 "y", 243 "x", 244 "xx", 245 "y", 246 ], 247 ), 248 PpTestSpec( 249 desc="Match words and numbers - show use of results names to collect types of tokens", 250 expr=(pp.Word(pp.alphas)("alpha*") | pp.pyparsing_common.integer("int*"))[ 251 ... 252 ], 253 text="sdlfj23084ksdfs08234kjsdlfkjd0934", 254 expected_list=["sdlfj", 23084, "ksdfs", 8234, "kjsdlfkjd", 934], 255 expected_dict={ 256 "alpha": ["sdlfj", "ksdfs", "kjsdlfkjd"], 257 "int": [23084, 8234, 934], 258 }, 259 ), 260 PpTestSpec( 261 desc="Using delimited_list (comma is the default delimiter)", 262 expr=pp.delimited_list(pp.Word(pp.alphas)), 263 text="xxyx,xy,y,xxyx,yxx, xy", 264 expected_list=["xxyx", "xy", "y", "xxyx", "yxx", "xy"], 265 ), 266 PpTestSpec( 267 desc="Using delimited_list (comma is the default delimiter) with trailing delimiter", 268 expr=pp.delimited_list(pp.Word(pp.alphas), allow_trailing_delim=True), 269 text="xxyx,xy,y,xxyx,yxx, xy,", 270 expected_list=["xxyx", "xy", "y", "xxyx", "yxx", "xy"], 271 ), 272 PpTestSpec( 273 desc="Using delimited_list, with ':' delimiter", 274 expr=pp.delimited_list( 275 pp.Word(pp.hexnums, exact=2), delim=":", combine=True 276 ), 277 text="0A:4B:73:21:FE:76", 278 expected_list=["0A:4B:73:21:FE:76"], 279 ), 280 PpTestSpec( 281 desc="Using delimited_list, with ':' delimiter", 282 expr=pp.delimited_list( 283 pp.Word(pp.hexnums, exact=2), 284 delim=":", 285 combine=True, 286 allow_trailing_delim=True, 287 ), 288 text="0A:4B:73:21:FE:76:", 289 expected_list=["0A:4B:73:21:FE:76:"], 290 ), 291 ] 292 293 294class TestResultsName(PyparsingExpressionTestCase): 295 tests = [ 296 PpTestSpec( 297 desc="Match with results name", 298 expr=pp.Literal("xyz").set_results_name("value"), 299 text="xyz", 300 expected_dict={"value": "xyz"}, 301 expected_list=["xyz"], 302 ), 303 PpTestSpec( 304 desc="Match with results name - using naming short-cut", 305 expr=pp.Literal("xyz")("value"), 306 text="xyz", 307 expected_dict={"value": "xyz"}, 308 expected_list=["xyz"], 309 ), 310 PpTestSpec( 311 desc="Define multiple results names", 312 expr=pp.Word(pp.alphas, pp.alphanums)("key") 313 + "=" 314 + pp.pyparsing_common.integer("value"), 315 text="range=5280", 316 expected_dict={"key": "range", "value": 5280}, 317 expected_list=["range", "=", 5280], 318 ), 319 ] 320 321 322class TestGroups(PyparsingExpressionTestCase): 323 EQ = pp.Suppress("=") 324 tests = [ 325 PpTestSpec( 326 desc="Define multiple results names in groups", 327 expr=pp.Group( 328 pp.Word(pp.alphas)("key") + EQ + pp.pyparsing_common.number("value") 329 )[...], 330 text="range=5280 long=-138.52 lat=46.91", 331 expected_list=[["range", 5280], ["long", -138.52], ["lat", 46.91]], 332 ), 333 PpTestSpec( 334 desc="Define multiple results names in groups - use Dict to define results names using parsed keys", 335 expr=pp.Dict( 336 pp.Group(pp.Word(pp.alphas) + EQ + pp.pyparsing_common.number)[...] 337 ), 338 text="range=5280 long=-138.52 lat=46.91", 339 expected_list=[["range", 5280], ["long", -138.52], ["lat", 46.91]], 340 expected_dict={"lat": 46.91, "long": -138.52, "range": 5280}, 341 ), 342 PpTestSpec( 343 desc="Define multiple value types", 344 expr=pp.Dict( 345 pp.Group( 346 pp.Word(pp.alphas) 347 + EQ 348 + ( 349 pp.pyparsing_common.number 350 | pp.oneOf("True False") 351 | pp.QuotedString("'") 352 ) 353 )[...] 354 ), 355 text="long=-122.47 lat=37.82 public=True name='Golden Gate Bridge'", 356 expected_list=[ 357 ["long", -122.47], 358 ["lat", 37.82], 359 ["public", "True"], 360 ["name", "Golden Gate Bridge"], 361 ], 362 expected_dict={ 363 "long": -122.47, 364 "lat": 37.82, 365 "public": "True", 366 "name": "Golden Gate Bridge", 367 }, 368 ), 369 ] 370 371 372class TestParseAction(PyparsingExpressionTestCase): 373 tests = [ 374 PpTestSpec( 375 desc="Parsing real numbers - use parse action to convert to float at parse time", 376 expr=pp.Combine(pp.Word(pp.nums) + "." + pp.Word(pp.nums)).add_parse_action( 377 lambda t: float(t[0]) 378 )[...], 379 text="1.2 2.3 3.1416 98.6", 380 expected_list=[ 381 1.2, 382 2.3, 383 3.1416, 384 98.6, 385 ], # note, these are now floats, not strs 386 ), 387 PpTestSpec( 388 desc="Match with numeric string converted to int", 389 expr=pp.Word("0123456789").addParseAction(lambda t: int(t[0])), 390 text="12345", 391 expected_list=[12345], # note - result is type int, not str 392 ), 393 PpTestSpec( 394 desc="Use two parse actions to convert numeric string, then convert to datetime", 395 expr=pp.Word(pp.nums).add_parse_action( 396 lambda t: int(t[0]), lambda t: datetime.utcfromtimestamp(t[0]) 397 ), 398 text="1537415628", 399 expected_list=[datetime(2018, 9, 20, 3, 53, 48)], 400 ), 401 PpTestSpec( 402 desc="Use tokenMap for parse actions that operate on a single-length token", 403 expr=pp.Word(pp.nums).add_parse_action( 404 pp.token_map(int), pp.token_map(datetime.utcfromtimestamp) 405 ), 406 text="1537415628", 407 expected_list=[datetime(2018, 9, 20, 3, 53, 48)], 408 ), 409 PpTestSpec( 410 desc="Using a built-in function that takes a sequence of strs as a parse action", 411 expr=pp.Word(pp.hexnums, exact=2)[...].add_parse_action(":".join), 412 text="0A4B7321FE76", 413 expected_list=["0A:4B:73:21:FE:76"], 414 ), 415 PpTestSpec( 416 desc="Using a built-in function that takes a sequence of strs as a parse action", 417 expr=pp.Word(pp.hexnums, exact=2)[...].add_parse_action(sorted), 418 text="0A4B7321FE76", 419 expected_list=["0A", "21", "4B", "73", "76", "FE"], 420 ), 421 ] 422 423 424class TestResultsModifyingParseAction(PyparsingExpressionTestCase): 425 # do not make staticmethod 426 # @staticmethod 427 def compute_stats_parse_action(t): 428 # by the time this parse action is called, parsed numeric words 429 # have been converted to ints by a previous parse action, so 430 # they can be treated as ints 431 t["sum"] = sum(t) 432 t["ave"] = sum(t) / len(t) 433 t["min"] = min(t) 434 t["max"] = max(t) 435 436 tests = [ 437 PpTestSpec( 438 desc="A parse action that adds new key-values", 439 expr=pp.pyparsing_common.integer[...].addParseAction( 440 compute_stats_parse_action 441 ), 442 text="27 1 14 22 89", 443 expected_list=[27, 1, 14, 22, 89], 444 expected_dict={"ave": 30.6, "max": 89, "min": 1, "sum": 153}, 445 ), 446 ] 447 448 449class TestRegex(PyparsingExpressionTestCase): 450 tests = [ 451 PpTestSpec( 452 desc="Parsing real numbers - using Regex instead of Combine", 453 expr=pp.Regex(r"\d+\.\d+").add_parse_action(lambda t: float(t[0]))[...], 454 text="1.2 2.3 3.1416 98.6", 455 expected_list=[ 456 1.2, 457 2.3, 458 3.1416, 459 98.6, 460 ], # note, these are now floats, not strs 461 ), 462 ] 463 464 465class TestParseCondition(PyparsingExpressionTestCase): 466 tests = [ 467 PpTestSpec( 468 desc="Define a condition to only match numeric values that are multiples of 7", 469 expr=pp.Word(pp.nums).addCondition(lambda t: int(t[0]) % 7 == 0)[...], 470 text="14 35 77 12 28", 471 expected_list=["14", "35", "77"], 472 ), 473 PpTestSpec( 474 desc="Separate conversion to int and condition into separate parse action/conditions", 475 expr=pp.Word(pp.nums) 476 .add_parse_action(lambda t: int(t[0])) 477 .add_condition(lambda t: t[0] % 7 == 0)[...], 478 text="14 35 77 12 28", 479 expected_list=[14, 35, 77], 480 ), 481 ] 482 483 484class TestTransformStringUsingParseActions(PyparsingExpressionTestCase): 485 markup_convert_map = { 486 "*": "B", 487 "_": "U", 488 "/": "I", 489 } 490 491 # do not make staticmethod 492 # @staticmethod 493 def markup_convert(t): 494 htmltag = TestTransformStringUsingParseActions.markup_convert_map[ 495 t.markup_symbol 496 ] 497 return "<{}>{}</{}>".format(htmltag, t.body, htmltag) 498 499 tests = [ 500 PpTestSpec( 501 desc="Use transformString to convert simple markup to HTML", 502 expr=( 503 pp.one_of(markup_convert_map)("markup_symbol") 504 + "(" 505 + pp.CharsNotIn(")")("body") 506 + ")" 507 ).add_parse_action(markup_convert), 508 text="Show in *(bold), _(underscore), or /(italic) type", 509 expected_list=[ 510 "Show in <B>bold</B>, <U>underscore</U>, or <I>italic</I> type" 511 ], 512 parse_fn="transformString", 513 ), 514 ] 515 516 517class TestCommonHelperExpressions(PyparsingExpressionTestCase): 518 tests = [ 519 PpTestSpec( 520 desc="A comma-delimited list of words", 521 expr=pp.delimited_list(pp.Word(pp.alphas)), 522 text="this, that, blah,foo, bar", 523 expected_list=["this", "that", "blah", "foo", "bar"], 524 ), 525 PpTestSpec( 526 desc="A counted array of words", 527 expr=pp.Group(pp.counted_array(pp.Word("ab")))[...], 528 text="2 aaa bbb 0 3 abab bbaa abbab", 529 expected_list=[["aaa", "bbb"], [], ["abab", "bbaa", "abbab"]], 530 ), 531 PpTestSpec( 532 desc="skipping comments with ignore", 533 expr=( 534 pp.pyparsing_common.identifier("lhs") 535 + "=" 536 + pp.pyparsing_common.fnumber("rhs") 537 ).ignore(pp.cpp_style_comment), 538 text="abc_100 = /* value to be tested */ 3.1416", 539 expected_list=["abc_100", "=", 3.1416], 540 expected_dict={"lhs": "abc_100", "rhs": 3.1416}, 541 ), 542 PpTestSpec( 543 desc="some pre-defined expressions in pyparsing_common, and building a dotted identifier with delimted_list", 544 expr=( 545 pp.pyparsing_common.number("id_num") 546 + pp.delimitedList(pp.pyparsing_common.identifier, ".", combine=True)( 547 "name" 548 ) 549 + pp.pyparsing_common.ipv4_address("ip_address") 550 ), 551 text="1001 www.google.com 192.168.10.199", 552 expected_list=[1001, "www.google.com", "192.168.10.199"], 553 expected_dict={ 554 "id_num": 1001, 555 "name": "www.google.com", 556 "ip_address": "192.168.10.199", 557 }, 558 ), 559 PpTestSpec( 560 desc="using one_of (shortcut for Literal('a') | Literal('b') | Literal('c'))", 561 expr=pp.one_of("a b c")[...], 562 text="a b a b b a c c a b b", 563 expected_list=["a", "b", "a", "b", "b", "a", "c", "c", "a", "b", "b"], 564 ), 565 PpTestSpec( 566 desc="parsing nested parentheses", 567 expr=pp.nested_expr(), 568 text="(a b (c) d (e f g ()))", 569 expected_list=[["a", "b", ["c"], "d", ["e", "f", "g", []]]], 570 ), 571 PpTestSpec( 572 desc="parsing nested braces", 573 expr=( 574 pp.Keyword("if") 575 + pp.nested_expr()("condition") 576 + pp.nested_expr("{", "}")("body") 577 ), 578 text='if ((x == y) || !z) {printf("{}");}', 579 expected_list=[ 580 "if", 581 [["x", "==", "y"], "||", "!z"], 582 ["printf(", '"{}"', ");"], 583 ], 584 expected_dict={ 585 "condition": [[["x", "==", "y"], "||", "!z"]], 586 "body": [["printf(", '"{}"', ");"]], 587 }, 588 ), 589 ] 590 591 592class TestWhitespaceMethods(PyparsingExpressionTestCase): 593 tests = [ 594 # These test the single-element versions 595 PpTestSpec( 596 desc="The word foo", 597 expr=pp.Literal("foo").ignore_whitespace(), 598 text=" foo ", 599 expected_list=["foo"], 600 ), 601 PpTestSpec( 602 desc="The word foo", 603 expr=pp.Literal("foo").leave_whitespace(), 604 text=" foo ", 605 expected_fail_locn=0, 606 ), 607 PpTestSpec( 608 desc="The word foo", 609 expr=pp.Literal("foo").ignore_whitespace(), 610 text="foo", 611 expected_list=["foo"], 612 ), 613 PpTestSpec( 614 desc="The word foo", 615 expr=pp.Literal("foo").leave_whitespace(), 616 text="foo", 617 expected_list=["foo"], 618 ), 619 # These test the composite elements 620 PpTestSpec( 621 desc="If we recursively leave whitespace on the parent, this whitespace-dependent grammar will succeed, even if the children themselves skip whitespace", 622 expr=pp.And( 623 [ 624 pp.Literal(" foo").ignore_whitespace(), 625 pp.Literal(" bar").ignore_whitespace(), 626 ] 627 ).leave_whitespace(recursive=True), 628 text=" foo bar", 629 expected_list=[" foo", " bar"], 630 ), 631 # 632 PpTestSpec( 633 desc="If we recursively ignore whitespace in our parsing, this whitespace-dependent grammar will fail, even if the children themselves keep whitespace", 634 expr=pp.And( 635 [ 636 pp.Literal(" foo").leave_whitespace(), 637 pp.Literal(" bar").leave_whitespace(), 638 ] 639 ).ignore_whitespace(recursive=True), 640 text=" foo bar", 641 expected_fail_locn=1, 642 ), 643 PpTestSpec( 644 desc="If we leave whitespace on the parent, but it isn't recursive, this whitespace-dependent grammar will fail", 645 expr=pp.And( 646 [ 647 pp.Literal(" foo").ignore_whitespace(), 648 pp.Literal(" bar").ignore_whitespace(), 649 ] 650 ).leave_whitespace(recursive=False), 651 text=" foo bar", 652 expected_fail_locn=5, 653 ), 654 # These test the Enhance classes 655 PpTestSpec( 656 desc="If we recursively leave whitespace on the parent, this whitespace-dependent grammar will succeed, even if the children themselves skip whitespace", 657 expr=pp.Optional(pp.Literal(" foo").ignore_whitespace()).leave_whitespace( 658 recursive=True 659 ), 660 text=" foo", 661 expected_list=[" foo"], 662 ), 663 # 664 PpTestSpec( 665 desc="If we ignore whitespace on the parent, but it isn't recursive, parsing will fail because we skip to the first character 'f' before the internal expr can see it", 666 expr=pp.Optional(pp.Literal(" foo").leave_whitespace()).ignore_whitespace( 667 recursive=True 668 ), 669 text=" foo", 670 expected_list=[], 671 ), 672 # PpTestSpec( 673 # desc="If we leave whitespace on the parent, this whitespace-dependent grammar will succeed, even if the children themselves skip whitespace", 674 # expr=pp.Optional(pp.Literal(" foo").ignoreWhitespace()).leaveWhitespace( 675 # recursive=False 676 # ), 677 # text=" foo", 678 # expected_list=[] 679 # ), 680 ] 681 682 683def _get_decl_line_no(cls): 684 import inspect 685 686 return inspect.getsourcelines(cls)[1] 687 688 689# get all test case classes defined in this module and sort them by decl line no 690test_case_classes = list(PyparsingExpressionTestCase.__subclasses__()) 691test_case_classes.sort(key=_get_decl_line_no) 692 693# make into a suite and run it - this will run the tests in the same order 694# they are declared in this module 695# 696# runnable from setup.py using "python setup.py test -s simple_unit_tests.suite" 697# 698suite = unittest.TestSuite(cls() for cls in test_case_classes) 699 700 701# ============ MAIN ================ 702 703if __name__ == "__main__": 704 705 result = unittest.TextTestRunner().run(suite) 706 707 exit(0 if result.wasSuccessful() else 1) 708