Repository: radiasoft/pykern

Issue:
pkeq, pkne, etc. *args should augment message, not replace it
```py
        if args or kwargs:
            pkfail(*args, **kwargs)
        else:
            pkfail("expect={} != actual={}", expect, actual)
```

Should be something like:

```py
_fail(("expect={} != actual={}", expect, actual), args, kwargs)
```

`_fail` would merge the messages appropriately and pass to pkfail.

Discussion:
gurhar1133: @e-carlin automerge is still blocked. Not sure if intentional

Patch:
diff --git a/pykern/pkunit.py b/pykern/pkunit.py
index 962af6d..57542aa 100644
--- a/pykern/pkunit.py
+++ b/pykern/pkunit.py
@@ -324,19 +324,13 @@ def pkeq(expect, actual, *args, **kwargs):
         kwargs (dict): passed to pkfail()
     """
     if expect != actual:
-        if args or kwargs:
-            pkfail(*args, **kwargs)
-        else:
-            pkfail("expect={} != actual={}", expect, actual)
+        _fail(("expect={} != actual={}", expect, actual), *args, **kwargs)
 
 
 @contextlib.contextmanager
-def pkexcept(exc_or_re, *fmt_and_args, **kwargs):
+def pkexcept(exc_or_re, *args, **kwargs):
     """Expect an exception to be thrown and match or output msg
 
-    If `fmt_and_args` is falsey, will generate a message saying
-    what was expected and what was received.
-
     Examples::
 
         # Expect an exception (or its subclass)
@@ -353,7 +347,7 @@ def pkexcept(exc_or_re, *fmt_and_args, **kwargs):
 
     Args:
         exc_or_re (object): BaseException, re, or str; if str, compiled with `re.IGNORECASE`
-        fmt_and_args (tuple): passed to format
+        args (tuple): passed to format
         kwargs (dict): passed to format
 
     Yields:
@@ -368,29 +362,26 @@ def pkexcept(exc_or_re, *fmt_and_args, **kwargs):
         if isinstance(exc_or_re, type) and issubclass(exc_or_re, BaseException):
             if isinstance(e, exc_or_re):
                 return
-            if not fmt_and_args:
-                fmt_and_args = (
-                    "{}: an exception was raised, but expected it to be {}; stack={}",
-                    e_str,
-                    exc_or_re,
-                    pkdexc(),
-                )
+            m = (
+                "{}: an exception was raised, but expected it to be {}; stack={}",
+                e_str,
+                exc_or_re,
+                pkdexc(),
+            )
         else:
             if not isinstance(exc_or_re, _RE_TYPE):
                 exc_or_re = re.compile(exc_or_re, flags=re.IGNORECASE)
             if exc_or_re.search(e_str):
                 return
-            if not fmt_and_args:
-                fmt_and_args = (
-                    '{}: an exception was raised, but did not match "{}"; stack={}',
-                    e_str,
-                    exc_or_re.pattern,
-                    pkdexc(),
-                )
+            m = (
+                '{}: an exception was raised, but did not match "{}"; stack={}',
+                e_str,
+                exc_or_re.pattern,
+                pkdexc(),
+            )
     else:
-        if not fmt_and_args:
-            fmt_and_args = ("Exception was not raised: expecting={}", exc_or_re)
-    pkfail(*fmt_and_args, **kwargs)
+        m = ("Exception was not raised: expecting={}", exc_or_re)
+    _fail(m, *args, **kwargs)
 
 
 def pkfail(fmt, *args, **kwargs):
@@ -418,10 +409,7 @@ def pkne(expect, actual, *args, **kwargs):
         kwargs (dict): passed to pkfail()
     """
     if expect == actual:
-        if args or kwargs:
-            pkfail(*args, **kwargs)
-        else:
-            pkfail("expect={} == actual={}", expect, actual)
+        _fail(("expect={} == actual={}", expect, actual), *args, **kwargs)
 
 
 def pkok(cond, fmt, *args, **kwargs):
@@ -735,6 +723,22 @@ def _base_dir(postfix):
     return f.new(basename=b + postfix).realpath()
 
 
+def _fail(std_message_args, *args, **kwargs):
+    """Augment standard failure messages with args and kwargs
+
+    Args:
+        std_message_args (tuple): fmt string and args. eg. ("expect={} != actual={}", expect, actual)
+    """
+    if args:
+        pkfail(
+            f"{std_message_args[0]} {args[0]}",
+            *std_message_args[1:],
+            *args[1:],
+            **kwargs,
+        )
+    pkfail(*std_message_args)
+
+
 def _pkdlog(*args, **kwargs):
     from pykern.pkdebug import pkdlog
 


Test patch:
diff --git a/tests/pkunit_test.py b/tests/pkunit_test.py
index cc01187..9111b96 100644
--- a/tests/pkunit_test.py
+++ b/tests/pkunit_test.py
@@ -17,6 +17,25 @@ def test_assert_object_with_json():
         pkunit.assert_object_with_json("assert1", {"b": 1})
 
 
+def test_pkfail_output():
+    from pykern.pkunit import pkexcept, pkne, pkeq
+    import re
+
+    k = "example KeyError message"
+    with pkexcept(
+        re.compile(k + r".*?" + "expecting ValueError but will get KeyError", re.DOTALL)
+    ):
+        with pkexcept(ValueError, "expecting ValueError but will get {}", "KeyError"):
+            raise KeyError(k)
+
+    with pkexcept("expect=x != actual=y"):
+        pkeq("x", "y")
+    with pkexcept("expect=x != actual=y args: arg1 arg2 kwarg=z"):
+        pkeq("x", "y", "args: {} {} kwarg={kwarg}", "arg1", "arg2", kwarg="z")
+    with pkexcept("expect=x == actual=x args: arg1 arg2 kwarg=z"):
+        pkne("x", "x", "args: {} {} kwarg={kwarg}", "arg1", "arg2", kwarg="z")
+
+
 def test_data_dir():
     from pykern import pkio
     from pykern import pkunit