1import warnings 2from io import BytesIO 3 4import pytest 5 6from translate.convert import dtd2po, po2dtd, test_convert 7from translate.storage import dtd, po 8 9 10class TestPO2DTD: 11 def setup_method(self, method): 12 warnings.resetwarnings() 13 14 def teardown_method(self, method): 15 warnings.resetwarnings() 16 17 def po2dtd(self, posource, remove_untranslated=False): 18 """helper that converts po source to dtd source without requiring files""" 19 inputfile = BytesIO(posource.encode()) 20 inputpo = po.pofile(inputfile) 21 convertor = po2dtd.po2dtd(remove_untranslated=remove_untranslated) 22 return convertor.convertstore(inputpo) 23 24 def merge2dtd(self, dtdsource, posource): 25 """helper that merges po translations to dtd source without requiring files""" 26 inputfile = BytesIO(posource.encode()) 27 inputpo = po.pofile(inputfile) 28 templatefile = BytesIO(dtdsource.encode()) 29 templatedtd = dtd.dtdfile(templatefile) 30 convertor = po2dtd.redtd(templatedtd) 31 return convertor.convertstore(inputpo) 32 33 def convertdtd(self, posource, dtdtemplate, remove_untranslated=False): 34 """helper to exercise the command line function""" 35 inputfile = BytesIO(posource.encode()) 36 outputfile = BytesIO() 37 templatefile = BytesIO(dtdtemplate.encode()) 38 assert po2dtd.convertdtd( 39 inputfile, outputfile, templatefile, remove_untranslated=remove_untranslated 40 ) 41 return outputfile.getvalue().decode("utf-8") 42 43 def roundtripsource(self, dtdsource): 44 """converts dtd source to po and back again, returning the resulting source""" 45 dtdinputfile = BytesIO(dtdsource.encode()) 46 dtdinputfile2 = BytesIO(dtdsource.encode()) 47 pooutputfile = BytesIO() 48 dtd2po.convertdtd(dtdinputfile, pooutputfile, dtdinputfile2) 49 posource = pooutputfile.getvalue() 50 poinputfile = BytesIO(posource) 51 dtdtemplatefile = BytesIO(dtdsource.encode()) 52 dtdoutputfile = BytesIO() 53 po2dtd.convertdtd(poinputfile, dtdoutputfile, dtdtemplatefile) 54 dtdresult = dtdoutputfile.getvalue().decode("utf-8") 55 print_string = "Original DTD:\n%s\n\nPO version:\n%s\n\n" 56 print_string = print_string + "Output DTD:\n%s\n################" 57 print(print_string % (dtdsource, posource, dtdresult)) 58 return dtdresult 59 60 def roundtripstring(self, entitystring): 61 """Just takes the contents of a ENTITY definition (with quotes) and does a roundtrip on that""" 62 dtdintro, dtdoutro = "<!ENTITY Test.RoundTrip ", ">\n" 63 dtdsource = dtdintro + entitystring + dtdoutro 64 dtdresult = self.roundtripsource(dtdsource) 65 assert dtdresult.startswith(dtdintro) and dtdresult.endswith(dtdoutro) 66 return dtdresult[len(dtdintro) : -len(dtdoutro)] 67 68 def check_roundtrip(self, dtdsource, dtdcompare=None): 69 """Checks that the round-tripped string is the same as dtdcompare. 70 71 If no dtdcompare string is provided then the round-tripped string is 72 compared with the original string. 73 74 The reason why sometimes another string is provided to compare with the 75 resulting string from the roundtrip is that if the original string 76 contains some characters, like " character, or escapes like ", 77 then when the roundtrip is performed those characters or escapes are 78 escaped, rendering a round-tripped string which differs from the 79 original one. 80 """ 81 if not dtdcompare: 82 dtdcompare = dtdsource 83 assert self.roundtripstring(dtdsource) == dtdcompare 84 85 def test_joinlines(self): 86 """tests that po lines are joined seamlessly (bug 16)""" 87 multilinepo = """#: pref.menuPath\nmsgid ""\n"<span>Tools > Options</"\n"span>"\nmsgstr ""\n""" 88 dtdfile = self.po2dtd(multilinepo) 89 dtdsource = bytes(dtdfile) 90 assert b"</span>" in dtdsource 91 92 def test_escapedstr(self): 93 r"""tests that \n in msgstr is escaped correctly in dtd""" 94 multilinepo = ( 95 """#: pref.menuPath\nmsgid "Hello\\nEveryone"\nmsgstr "Good day\\nAll"\n""" 96 ) 97 dtdfile = self.po2dtd(multilinepo) 98 dtdsource = bytes(dtdfile) 99 assert b"Good day\nAll" in dtdsource 100 101 def test_missingaccesskey(self): 102 """tests that proper warnings are given if access key is missing""" 103 simplepo = """#: simple.label 104#: simple.accesskey 105msgid "Simple &String" 106msgstr "Dimpled Ring" 107""" 108 simpledtd = """<!ENTITY simple.label "Simple String"> 109<!ENTITY simple.accesskey "S">""" 110 warnings.simplefilter("error") 111 with pytest.raises(Warning): 112 self.merge2dtd(simpledtd, simplepo) 113 114 def test_accesskeycase(self): 115 """tests that access keys come out with the same case as the original, regardless""" 116 simplepo_template = ( 117 """#: simple.label\n#: simple.accesskey\nmsgid "%s"\nmsgstr "%s"\n""" 118 ) 119 simpledtd_template = ( 120 """<!ENTITY simple.label "Simple %s">\n<!ENTITY simple.accesskey "%s">""" 121 ) 122 possibilities = [ 123 # (en label, en akey, en po, af po, af label, expected af akey) 124 ("Sis", "S", "&Sis", "&Sies", "Sies", "S"), 125 ("Sis", "s", "Si&s", "&Sies", "Sies", "S"), 126 ("Sis", "S", "&Sis", "Sie&s", "Sies", "s"), 127 ("Sis", "s", "Si&s", "Sie&s", "Sies", "s"), 128 # untranslated strings should have the casing of the source 129 ("Sis", "S", "&Sis", "", "Sis", "S"), 130 ("Sis", "s", "Si&s", "", "Sis", "s"), 131 ("Suck", "S", "&Suck", "", "Suck", "S"), 132 ("Suck", "s", "&Suck", "", "Suck", "s"), 133 ] 134 for ( 135 en_label, 136 en_akey, 137 po_source, 138 po_target, 139 target_label, 140 target_akey, 141 ) in possibilities: 142 simplepo = simplepo_template % (po_source, po_target) 143 simpledtd = simpledtd_template % (en_label, en_akey) 144 dtdfile = self.merge2dtd(simpledtd, simplepo) 145 dtdfile.makeindex() 146 accel = dtd.unquotefromdtd(dtdfile.id_index["simple.accesskey"].definition) 147 assert accel == target_akey 148 149 def test_accesskey_types(self): 150 """tests that we can detect the various styles of accesskey""" 151 simplepo_template = ( 152 """#: simple.%s\n#: simple.%s\nmsgid "&File"\nmsgstr "F&aele"\n""" 153 ) 154 simpledtd_template = """<!ENTITY simple.%s "File">\n<!ENTITY simple.%s "a">""" 155 for label in ("label", "title"): 156 for accesskey in ("accesskey", "accessKey", "akey"): 157 simplepo = simplepo_template % (label, accesskey) 158 simpledtd = simpledtd_template % (label, accesskey) 159 dtdfile = self.merge2dtd(simpledtd, simplepo) 160 dtdfile.makeindex() 161 assert ( 162 dtd.unquotefromdtd( 163 dtdfile.id_index["simple.%s" % accesskey].definition 164 ) 165 == "a" 166 ) 167 168 def test_accesskey_missing(self): 169 """tests that missing ampersands use the source accesskey""" 170 po_snippet = r"""#: key.label 171#: key.accesskey 172msgid "&Search" 173msgstr "Ileti" 174""" 175 dtd_snippet = r"""<!ENTITY key.accesskey "S"> 176<!ENTITY key.label "Ileti">""" 177 dtdfile = self.merge2dtd(dtd_snippet, po_snippet) 178 dtdsource = bytes(dtdfile).decode("utf-8") 179 print(dtdsource) 180 assert '"Ileti"' in dtdsource 181 assert '""' not in dtdsource 182 assert '"S"' in dtdsource 183 184 def test_accesskey_and_amp_case_no_accesskey(self): 185 """ 186 tests that accesskey and & can work together 187 188 If missing we use the source accesskey 189 """ 190 po_snippet = r"""#: key.label 191#: key.accesskey 192msgid "Colour & &Light" 193msgstr "Lig en Kleur" 194""" 195 dtd_snippet = r"""<!ENTITY key.accesskey "L"> 196<!ENTITY key.label "Colour & Light">""" 197 dtdfile = self.merge2dtd(dtd_snippet, po_snippet) 198 dtdsource = bytes(dtdfile).decode("utf-8") 199 print(dtdsource) 200 assert '"Lig en Kleur"' in dtdsource 201 assert '"L"' in dtdsource 202 203 def test_accesskey_and_amp_source_no_amp_in_target(self): 204 """ 205 tests that accesskey and & can work together 206 207 If present we use the target accesskey 208 """ 209 po_snippet = r"""#: key.label 210#: key.accesskey 211msgid "Colour & &Light" 212msgstr "Lig en &Kleur" 213""" 214 dtd_snippet = r"""<!ENTITY key.accesskey "L"> 215<!ENTITY key.label "Colour & Light">""" 216 dtdfile = self.merge2dtd(dtd_snippet, po_snippet) 217 dtdsource = bytes(dtdfile).decode("utf-8") 218 print(dtdsource) 219 assert '"Lig en Kleur"' in dtdsource 220 assert '"K"' in dtdsource 221 222 def test_accesskey_and_amp_case_both_amp_and_accesskey(self): 223 """ 224 tests that accesskey and & can work together 225 226 If present both & (and) and a marker then we use the correct source 227 accesskey 228 """ 229 po_snippet = r"""#: key.label 230#: key.accesskey 231msgid "Colour & &Light" 232msgstr "Lig & &Kleur" 233""" 234 dtd_snippet = r"""<!ENTITY key.accesskey "L"> 235<!ENTITY key.label "Colour & Light">""" 236 dtdfile = self.merge2dtd(dtd_snippet, po_snippet) 237 dtdsource = bytes(dtdfile).decode("utf-8") 238 print(dtdsource) 239 assert '"Lig & Kleur"' in dtdsource 240 assert '"K"' in dtdsource 241 242 def test_accesskey_and_amp_case_amp_no_accesskey(self): 243 """ 244 tests that accesskey and & can work together 245 246 If present both & (and) and a no marker then we use the correct source 247 accesskey 248 """ 249 po_snippet = r"""#: key.label 250#: key.accesskey 251msgid "Colour & &Light" 252msgstr "Lig & Kleur" 253""" 254 dtd_snippet = r"""<!ENTITY key.accesskey "L"> 255<!ENTITY key.label "Colour & Light">""" 256 dtdfile = self.merge2dtd(dtd_snippet, po_snippet) 257 dtdsource = bytes(dtdfile).decode("utf-8") 258 print(dtdsource) 259 assert '"Lig & Kleur"' in dtdsource 260 assert '"L"' in dtdsource 261 262 def test_entities_two(self): 263 """test the error ouput when we find two entities""" 264 simplestring = """#: simple.string second.string\nmsgid "Simple String"\nmsgstr "Dimpled Ring"\n""" 265 dtdfile = self.po2dtd(simplestring) 266 dtdsource = bytes(dtdfile) 267 assert b"CONVERSION NOTE - multiple entities" in dtdsource 268 269 def test_entities(self): 270 """tests that entities are correctly idnetified in the dtd""" 271 simplestring = ( 272 """#: simple.string\nmsgid "Simple String"\nmsgstr "Dimpled Ring"\n""" 273 ) 274 dtdfile = self.po2dtd(simplestring) 275 dtdsource = bytes(dtdfile) 276 assert dtdsource.startswith(b"<!ENTITY simple.string") 277 278 def test_comments_translator(self): 279 """tests for translator comments""" 280 simplestring = """# Comment1\n# Comment2\n#: simple.string\nmsgid "Simple String"\nmsgstr "Dimpled Ring"\n""" 281 dtdfile = self.po2dtd(simplestring) 282 dtdsource = bytes(dtdfile) 283 assert dtdsource.startswith(b"<!-- Comment1 -->") 284 285 def test_retains_hashprefix(self): 286 """tests that hash prefixes in the dtd are retained""" 287 hashpo = """#: lang.version\nmsgid "__MOZILLA_LOCALE_VERSION__"\nmsgstr "__MOZILLA_LOCALE_VERSION__"\n""" 288 hashdtd = '#expand <!ENTITY lang.version "__MOZILLA_LOCALE_VERSION__">\n' 289 dtdfile = self.merge2dtd(hashdtd, hashpo) 290 regendtd = bytes(dtdfile).decode("utf-8") 291 assert regendtd == hashdtd 292 293 def test_convertdtd(self): 294 """checks that the convertdtd function is working""" 295 posource = """#: simple.label\n#: simple.accesskey\nmsgid "Simple &String"\nmsgstr "Dimpled &Ring"\n""" 296 dtdtemplate = """<!ENTITY simple.label "Simple String">\n<!ENTITY simple.accesskey "S">\n""" 297 dtdexpected = """<!ENTITY simple.label "Dimpled Ring">\n<!ENTITY simple.accesskey "R">\n""" 298 newdtd = self.convertdtd(posource, dtdtemplate) 299 print(newdtd) 300 assert newdtd == dtdexpected 301 302 def test_untranslated_with_template(self): 303 """test removing of untranslated entries in redtd""" 304 posource = """#: simple.label 305msgid "Simple string" 306msgstr "Dimpled ring" 307 308#: simple.label2 309msgid "Simple string 2" 310msgstr "" 311 312#: simple.label3 313msgid "Simple string 3" 314msgstr "Simple string 3" 315 316#: simple.label4 317#, fuzzy 318msgid "Simple string 4" 319msgstr "simple string four" 320""" 321 dtdtemplate = """<!ENTITY simple.label "Simple string"> 322<!ENTITY simple.label2 "Simple string 2"> 323<!ENTITY simple.label3 "Simple string 3"> 324<!ENTITY simple.label4 "Simple string 4"> 325""" 326 dtdexpected = """<!ENTITY simple.label "Dimpled ring"> 327 328<!ENTITY simple.label3 "Simple string 3"> 329 330""" 331 newdtd = self.convertdtd(posource, dtdtemplate, remove_untranslated=True) 332 print(newdtd) 333 assert newdtd == dtdexpected 334 335 def test_untranslated_without_template(self): 336 """test removing of untranslated entries in po2dtd""" 337 posource = """#: simple.label 338msgid "Simple string" 339msgstr "Dimpled ring" 340 341#: simple.label2 342msgid "Simple string 2" 343msgstr "" 344 345#: simple.label3 346msgid "Simple string 3" 347msgstr "Simple string 3" 348 349#: simple.label4 350#, fuzzy 351msgid "Simple string 4" 352msgstr "simple string four" 353""" 354 dtdexpected = """<!ENTITY simple.label "Dimpled ring"> 355<!ENTITY simple.label3 "Simple string 3"> 356""" 357 newdtd = self.po2dtd(posource, remove_untranslated=True) 358 print(bytes(newdtd)) 359 assert bytes(newdtd).decode("utf-8") == dtdexpected 360 361 def test_blank_source(self): 362 """test removing of untranslated entries where source is blank""" 363 posource = """#: simple.label 364msgid "Simple string" 365msgstr "Dimpled ring" 366 367#: simple.label2 368msgid "" 369msgstr "" 370 371#: simple.label3 372msgid "Simple string 3" 373msgstr "Simple string 3" 374""" 375 dtdtemplate = """<!ENTITY simple.label "Simple string"> 376<!ENTITY simple.label2 ""> 377<!ENTITY simple.label3 "Simple string 3"> 378""" 379 dtdexpected_with_template = """<!ENTITY simple.label "Dimpled ring"> 380<!ENTITY simple.label2 ""> 381<!ENTITY simple.label3 "Simple string 3"> 382""" 383 384 dtdexpected_no_template = """<!ENTITY simple.label "Dimpled ring"> 385<!ENTITY simple.label3 "Simple string 3"> 386""" 387 newdtd_with_template = self.convertdtd( 388 posource, dtdtemplate, remove_untranslated=True 389 ) 390 print(newdtd_with_template) 391 assert newdtd_with_template == dtdexpected_with_template 392 newdtd_no_template = self.po2dtd(posource, remove_untranslated=True) 393 print(bytes(newdtd_no_template)) 394 assert bytes(newdtd_no_template).decode("utf-8") == dtdexpected_no_template 395 396 def test_newlines_escapes(self): 397 r"""check that we can handle a \n in the PO file""" 398 posource = """#: simple.label\n#: simple.accesskey\nmsgid "A hard coded newline.\\n"\nmsgstr "Hart gekoeerde nuwe lyne\\n"\n""" 399 dtdtemplate = '<!ENTITY simple.label "A hard coded newline.\n">\n' 400 dtdexpected = """<!ENTITY simple.label "Hart gekoeerde nuwe lyne\n">\n""" 401 dtdfile = self.merge2dtd(dtdtemplate, posource) 402 print(bytes(dtdfile)) 403 assert bytes(dtdfile).decode("utf-8") == dtdexpected 404 405 def test_roundtrip_simple(self): 406 """checks that simple strings make it through a dtd->po->dtd roundtrip""" 407 self.check_roundtrip('"Hello"') 408 self.check_roundtrip('"Hello Everybody"') 409 410 def test_roundtrip_escape(self): 411 """checks that escapes in strings make it through a dtd->po->dtd roundtrip""" 412 self.check_roundtrip(r'"Simple Escape \ \n \\ \: \t \r "') 413 self.check_roundtrip(r'"End Line Escape \"') 414 415 def test_roundtrip_quotes(self): 416 """Checks that quotes make it through a DTD->PO->DTD roundtrip. 417 418 Quotes may be escaped or not. 419 """ 420 # NOTE: during the roundtrip, if " quote mark is present, then it is 421 # converted to " and the resulting string is always enclosed 422 # between " characters independently of which quotation marks the 423 # original string is enclosed between. Thus the string cannot be 424 # compared with itself and therefore other string should be provided to 425 # compare with the result. 426 # 427 # Thus the string cannot be compared with itself and therefore another 428 # string should be provided to compare with the roundtrip result. 429 self.check_roundtrip( 430 r"""'Quote Escape "" '""", r'''"Quote Escape "" "''' 431 ) 432 self.check_roundtrip(r'''"Double-Quote Escape "" "''') 433 self.check_roundtrip(r'''"Single-Quote ' "''') 434 self.check_roundtrip(r'''"Single-Quote Escape \' "''') 435 # NOTE: during the roundtrip, if " quote mark is present, then ' is 436 # converted to ' and " is converted to " Also the resulting 437 # string is always enclosed between " characters independently of which 438 # quotation marks the original string is enclosed between. Thus the 439 # string cannot be compared with itself and therefore another string 440 # should be provided to compare with the result. 441 # 442 # Thus the string cannot be compared with itself and therefore another 443 # string should be provided to compare with the roundtrip result. 444 self.check_roundtrip( 445 r"""'Both Quotes "" '' '""", 446 r'''"Both Quotes "" '' "''', 447 ) 448 self.check_roundtrip(r'''"Both Quotes "" '' "''') 449 # NOTE: during the roundtrip, if " is present, then ' is converted 450 # to ' Also the resulting string is always enclosed between " 451 # characters independently of which quotation marks the original string 452 # is enclosed between. 453 # 454 # Thus the string cannot be compared with itself and therefore another 455 # string should be provided to compare with the roundtrip result. 456 self.check_roundtrip( 457 r'''"Both Quotes "" '' "''', 458 r'''"Both Quotes "" '' "''', 459 ) 460 461 def test_roundtrip_amp(self): 462 """Checks that quotes make it through a DTD->PO->DTD roundtrip. 463 464 Quotes may be escaped or not. 465 """ 466 self.check_roundtrip('"Colour & Light"') 467 468 def test_merging_entries_with_spaces_removed(self): 469 """dtd2po removes pretty printed spaces, this tests that we can merge this back into the pretty printed dtd""" 470 posource = """#: simple.label\nmsgid "First line then "\n"next lines."\nmsgstr "Eerste lyne en dan volgende lyne."\n""" 471 dtdtemplate = ( 472 '<!ENTITY simple.label "First line then\n' 473 ' next lines.">\n' 474 ) 475 dtdexpected = '<!ENTITY simple.label "Eerste lyne en dan volgende lyne.">\n' 476 dtdfile = self.merge2dtd(dtdtemplate, posource) 477 print(bytes(dtdfile)) 478 assert bytes(dtdfile).decode("utf-8") == dtdexpected 479 480 def test_preserving_spaces(self): 481 """ensure that we preserve spaces between entity and value. Bug 1662""" 482 posource = """#: simple.label\nmsgid "One"\nmsgstr "Een"\n""" 483 dtdtemplate = '<!ENTITY simple.label "One">\n' 484 dtdexpected = '<!ENTITY simple.label "Een">\n' 485 dtdfile = self.merge2dtd(dtdtemplate, posource) 486 print(bytes(dtdfile)) 487 assert bytes(dtdfile).decode("utf-8") == dtdexpected 488 489 def test_preserving_spaces_after_value(self): 490 """Preserve spaces after value. Bug 1662""" 491 # Space between value and > 492 posource = """#: simple.label\nmsgid "One"\nmsgstr "Een"\n""" 493 dtdtemplate = '<!ENTITY simple.label "One" >\n' 494 dtdexpected = '<!ENTITY simple.label "Een" >\n' 495 dtdfile = self.merge2dtd(dtdtemplate, posource) 496 print(bytes(dtdfile)) 497 assert bytes(dtdfile).decode("utf-8") == dtdexpected 498 # Space after > 499 dtdtemplate = '<!ENTITY simple.label "One"> \n' 500 dtdexpected = '<!ENTITY simple.label "Een"> \n' 501 dtdfile = self.merge2dtd(dtdtemplate, posource) 502 print(dtdfile) 503 assert bytes(dtdfile).decode("utf-8") == dtdexpected 504 505 def test_comments(self): 506 """test that we preserve comments, bug 351""" 507 posource = '''#: name\nmsgid "Text"\nmsgstr "Teks"''' 508 dtdtemplate = """<!ENTITY name "%s">\n<!-- \n\nexample -->\n""" 509 dtdfile = self.merge2dtd(dtdtemplate % "Text", posource) 510 print(bytes(dtdfile)) 511 assert bytes(dtdfile).decode("utf-8") == dtdtemplate % "Teks" 512 513 def test_duplicates(self): 514 """test that we convert duplicates back correctly to their respective entries.""" 515 posource = r"""#: bookmarksMenu.label bookmarksMenu.accesskey 516msgctxt "bookmarksMenu.label bookmarksMenu.accesskey" 517msgid "&Bookmarks" 518msgstr "Dipu&kutshwayo1" 519 520#: bookmarksItem.title 521msgctxt "bookmarksItem.title" 522msgid "Bookmarks" 523msgstr "Dipukutshwayo2" 524 525#: bookmarksButton.label 526msgctxt "bookmarksButton.label" 527msgid "Bookmarks" 528msgstr "Dipukutshwayo3" 529""" 530 dtdtemplate = r"""<!ENTITY bookmarksMenu.label "Bookmarks"> 531<!ENTITY bookmarksMenu.accesskey "B"> 532<!ENTITY bookmarksItem.title "Bookmarks"> 533<!ENTITY bookmarksButton.label "Bookmarks"> 534""" 535 dtdexpected = r"""<!ENTITY bookmarksMenu.label "Dipukutshwayo1"> 536<!ENTITY bookmarksMenu.accesskey "k"> 537<!ENTITY bookmarksItem.title "Dipukutshwayo2"> 538<!ENTITY bookmarksButton.label "Dipukutshwayo3"> 539""" 540 dtdfile = self.merge2dtd(dtdtemplate, posource) 541 print(bytes(dtdfile)) 542 assert bytes(dtdfile).decode("utf-8") == dtdexpected 543 544 545class TestPO2DTDCommand(test_convert.TestConvertCommand, TestPO2DTD): 546 """Tests running actual po2dtd commands on files""" 547 548 convertmodule = po2dtd 549 defaultoptions = {"progress": "none"} 550 # TODO: because of having 2 base classes, we need to call all their setup and teardown methods 551 # (otherwise we won't reset the warnings etc) 552 553 def setup_method(self, method): 554 """call both base classes setup_methods""" 555 super().setup_method(method) 556 TestPO2DTD.setup_method(self, method) 557 558 def teardown_method(self, method): 559 """call both base classes teardown_methods""" 560 super().teardown_method(method) 561 TestPO2DTD.teardown_method(self, method) 562 563 def test_help(self, capsys): 564 """tests getting help""" 565 options = super().test_help(capsys) 566 options = self.help_check(options, "-t TEMPLATE, --template=TEMPLATE") 567 options = self.help_check(options, "--fuzzy") 568 options = self.help_check(options, "--threshold=PERCENT") 569 options = self.help_check(options, "--removeuntranslated") 570 options = self.help_check(options, "--nofuzzy", last=True) 571