|
9 | 9 |
|
10 | 10 | import pytest
|
11 | 11 |
|
12 |
| -from logfmter.formatter import Logfmter |
| 12 | +from logfmter.formatter import Logfmter, parse_logfmt |
13 | 13 |
|
14 | 14 | STRING_ESCAPE_RULES = [
|
15 | 15 | # If the string contains a space, then it must be quoted.
|
|
26 | 26 | ("\t", '"\\t"'),
|
27 | 27 | # All other control chars must be escaped and quoted
|
28 | 28 | ("\x07", r'"\u0007"'),
|
| 29 | + ("\x7f", r'"\u007f"'), |
29 | 30 | (
|
30 | 31 | "".join(chr(c) for c in range(0x20) if chr(c) not in "\t\n\r"),
|
31 | 32 | r'"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u000b\u000c\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f"',
|
@@ -375,7 +376,15 @@ def test_format_nested_keys(record, expected):
|
375 | 376 | @pytest.mark.external
|
376 | 377 | @pytest.mark.parametrize(
|
377 | 378 | "value",
|
378 |
| - [x[0] for x in TYPE_CONVERSION_RULES + STRING_ESCAPE_RULES], |
| 379 | + [x[0] for x in TYPE_CONVERSION_RULES + STRING_ESCAPE_RULES if x[0] != "\x7f"] |
| 380 | + + [ |
| 381 | + pytest.param( |
| 382 | + "\x7f", |
| 383 | + marks=pytest.mark.xfail( |
| 384 | + reason="https://github.com/go-logfmt/logfmt/pull/17" |
| 385 | + ), |
| 386 | + ) |
| 387 | + ], |
379 | 388 | )
|
380 | 389 | def test_external_tools_compatibility(value):
|
381 | 390 | """
|
@@ -538,3 +547,131 @@ def test_ignored_keys_nested(record):
|
538 | 547 | Logfmter(keys=["at", "foo"], ignored_keys=["foo.key1"]).format(record)
|
539 | 548 | == "at=INFO msg=alpha foo.key2=val2"
|
540 | 549 | )
|
| 550 | + |
| 551 | + |
| 552 | +@pytest.mark.parametrize( |
| 553 | + "value", |
| 554 | + [ |
| 555 | + {"at": "INFO"}, |
| 556 | + {"levelname": "INFO", "a": "1"}, |
| 557 | + {"at": "INFO", "msg": "test", "a": "1"}, |
| 558 | + {"at": "INFO", "msg": "="}, |
| 559 | + {"at": "INFO", "msg": "alpha", "exc_info": 'exc\n"info"\ntb'}, |
| 560 | + {"a": " "}, |
| 561 | + {"_": " "}, |
| 562 | + {"a": '"'}, |
| 563 | + # {"a": "\\"}, |
| 564 | + # {"a": "\\", "b": "c"}, |
| 565 | + {"a": "\n"}, |
| 566 | + {"a": "'foo'"}, |
| 567 | + {"a": "", "b": "c"}, |
| 568 | + {"a": "1", "b": "2"}, |
| 569 | + {"a": "1.2", "b": "2"}, |
| 570 | + {"a": "1.2.3", "b": "0.2", "c": "1.0", "d": ".", "e": "1..2"}, |
| 571 | + {"foo.bar": "baz", "foo.blah": "blub"}, |
| 572 | + ], |
| 573 | +) |
| 574 | +def test_parse_logfmt_round_trip(value): |
| 575 | + assert parse_logfmt(Logfmter.format_params(value)) == value |
| 576 | + |
| 577 | + |
| 578 | +@pytest.mark.parametrize( |
| 579 | + "value", |
| 580 | + [ |
| 581 | + {"a": 1}, |
| 582 | + {"a": 1, "b": 2}, |
| 583 | + {"a": 1.2, "b": 2}, |
| 584 | + {"a": "1.2.3", "b": 0.2, "c": 1.0, "d": ".", "e": "1..2"}, |
| 585 | + ], |
| 586 | +) |
| 587 | +def test_parse_logfmt_numeric_round_trip(value): |
| 588 | + assert parse_logfmt(Logfmter.format_params(value), convert_numeric=True) == value |
| 589 | + |
| 590 | + |
| 591 | +@pytest.mark.parametrize( |
| 592 | + "value,expected,kwargs", |
| 593 | + [ |
| 594 | + ("at=INFO", {"at": "INFO"}, {}), |
| 595 | + ('at="INFO"', {"at": "INFO"}, {"reverse": False}), |
| 596 | + ( |
| 597 | + "at=INFO a=1", |
| 598 | + {"levelname": "INFO", "a": "1"}, |
| 599 | + {"aliases": {"at": "levelname"}, "reverse": False}, |
| 600 | + ), |
| 601 | + ("at=INFO msg=test a=1", {"at": "INFO", "msg": "test", "a": "1"}, {}), |
| 602 | + ('at=INFO msg="="', {"at": "INFO", "msg": "="}, {}), |
| 603 | + ( |
| 604 | + "at=INFO first_name=josh", |
| 605 | + {"at": "INFO", "first name": "josh"}, |
| 606 | + {"aliases": {"first_name": "first name"}, "reverse": False}, |
| 607 | + ), |
| 608 | + ( |
| 609 | + r'at=INFO msg=alpha exc_info="exc\n\"info\"\ntb"', |
| 610 | + {"at": "INFO", "msg": "alpha", "exc_info": 'exc\n"info"\ntb'}, |
| 611 | + {}, |
| 612 | + ), |
| 613 | + ('a=" "', {"a": " "}, {}), |
| 614 | + ('_=" "', {"_": " "}, {}), |
| 615 | + ('a="\\""', {"a": '"'}, {}), |
| 616 | + (r'a="\\"', {"a": "\\"}, {"reverse": False}), |
| 617 | + (r'a="\n"', {"a": "\n"}, {}), |
| 618 | + (r"a='foo'", {"a": "'foo'"}, {}), |
| 619 | + (r"a=\n", {"a": "n"}, {"reverse": False}), |
| 620 | + ("a= b=c", {"a": "", "b": "c"}, {}), |
| 621 | + ("a b=c", {"a": "", "b": "c"}, {"reverse": False}), |
| 622 | + ("a=1", {"a": 1}, {"convert_numeric": True}), |
| 623 | + ("a=1 b=2", {"a": "1", "b": "2"}, {}), |
| 624 | + ("a=1 b=2", {"a": 1, "b": 2}, {"convert_numeric": True}), |
| 625 | + ("a=1.2 b=2", {"a": "1.2", "b": "2"}, {}), |
| 626 | + ("a=1.2 b=2", {"a": 1.2, "b": 2}, {"convert_numeric": True}), |
| 627 | + ( |
| 628 | + "a=1.2.3 b=.2 c=1. d=. e=1..2", |
| 629 | + {"a": "1.2.3", "b": 0.2, "c": 1.0, "d": ".", "e": "1..2"}, |
| 630 | + {"convert_numeric": True, "reverse": False}, |
| 631 | + ), |
| 632 | + ("foo.bar=baz foo.blah=blub", {"foo.bar": "baz", "foo.blah": "blub"}, {}), |
| 633 | + ("\\n=foo", {"n": "foo"}, {"reverse": False}), |
| 634 | + ("a=' '", {"'": "", "a": "'"}, {"reverse": False}), |
| 635 | + pytest.param( |
| 636 | + "a=\n", {"a": "\n"}, {"reverse": False}, marks=pytest.mark.xfail() |
| 637 | + ), |
| 638 | + pytest.param( |
| 639 | + "a=\\", {"a": "\\"}, {"reverse": False}, marks=pytest.mark.xfail() |
| 640 | + ), |
| 641 | + pytest.param( |
| 642 | + "a=\\ b=c", |
| 643 | + {"a": "\\", "b": "c"}, |
| 644 | + {"reverse": False}, |
| 645 | + marks=pytest.mark.xfail(), |
| 646 | + ), |
| 647 | + ], |
| 648 | +) |
| 649 | +def test_parse_logfmt(value, kwargs, expected): |
| 650 | + reverse = kwargs.pop("reverse", True) |
| 651 | + assert parse_logfmt(value, **kwargs) == expected |
| 652 | + if reverse: |
| 653 | + assert Logfmter.format_params(expected) == value |
| 654 | + |
| 655 | + |
| 656 | +@pytest.mark.parametrize( |
| 657 | + "value", |
| 658 | + [ |
| 659 | + "a = b", |
| 660 | + ], |
| 661 | +) |
| 662 | +def test_parse_invalid_logfmt(value): |
| 663 | + with pytest.raises(ValueError): |
| 664 | + print(parse_logfmt(value)) |
| 665 | + exe = ( |
| 666 | + shutil.which("golang-logfmt-echo") |
| 667 | + or os.getcwd() + "/external/golang-logfmt-echo/golang-logfmt-echo" |
| 668 | + ) |
| 669 | + |
| 670 | + with pytest.raises(subprocess.CalledProcessError): |
| 671 | + subprocess.run( |
| 672 | + [exe], |
| 673 | + check=True, |
| 674 | + input=value, |
| 675 | + capture_output=True, |
| 676 | + text=True, |
| 677 | + ) |
0 commit comments