diff --git a/python-stdlib/pprint/manifest.py b/python-stdlib/pprint/manifest.py index 9c0ebe9ed..7bd2c3749 100644 --- a/python-stdlib/pprint/manifest.py +++ b/python-stdlib/pprint/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.0.4") +metadata(version="0.1.0") module("pprint.py") diff --git a/python-stdlib/pprint/pprint.py b/python-stdlib/pprint/pprint.py index 3c140cadc..5cfd52260 100644 --- a/python-stdlib/pprint/pprint.py +++ b/python-stdlib/pprint/pprint.py @@ -1,6 +1,215 @@ -def pformat(obj): - return repr(obj) +import io +import sys +from micropython import const +_NONCONTAINER = const(0) +_SIMPLE = const(1) +_LIST = const(2) +_SET = const(4) +_TUPLE = const(8) +_DICT = const(16) -def pprint(obj): - print(repr(obj)) + +class PrettyPrinter: + """A convenience class for storing pretty printer settings. + + Wraps around pformat() and pprint() calls, saving the settings for each + call. For parameter descriptions, look at pprint() documentation. + """ + + def __init__(self, indent=1, depth=1, stream=None, sort_dicts=True): + self._indent = indent + self._depth = depth + self._stream = stream if stream else sys.stdout + self._sort_dicts = sort_dicts + + def pformat(self, obj): + """Call pformat() function with cached constructor values""" + return pformat(obj, indent=self._indent, depth=self._depth, sort_dicts=self._sort_dicts) + + def pprint(self, obj): + """Call pprint() function with cached constructor values""" + pprint( + obj, + stream=self._stream, + indent=self._indent, + depth=self._depth, + sort_dicts=self._sort_dicts, + ) + + +def pformat(obj, indent=1, depth=1, sort_dicts=True): + """Simply calls pprint() but capturing prints into a string and returning. + + Does not add a newline to the end like pprint() does. + """ + stream = io.StringIO() + _pprint_impl(obj, stream, 0, sort_dicts, depth, 0, indent) + return stream.getvalue() + + +def pp(obj, *args, sort_dicts=False, **kwargs): + """Same as pprint() except the default value of sort_dicts is False""" + pprint(obj, *args, sort_dicts=sort_dicts, **kwargs) + + +def pprint(obj, stream=None, indent=1, depth=1, sort_dicts=True): + """Simple implementation of a pretty-printer. + + It does not produce the same output as the CPython implementation, but it's + close. Instead, it is intended to simply expand lists and dictionaries to + multiple lines for easy viewing. When the recursion depth has been + reached, the elements will be printed with repl(item) instead of the + CPython implementation of eliding the results (e.g., "{...}"). This is + because this implementation is not as safe as the CPython implementation + for deep recursion, which is detailed two paragraphs lower. + + Only expands containers. Currently supported containers are set, list, + dict, and tuple. + + The depth parameter defaults to 1 which is different from the CPython + implementation which defaults to None, meaning infinite recursion. This + implementation is significantly simpler and does not have safety checks + against recursive structures (i.e., structures that contain a copy of + themselves somewhere) or costly overexpansion. This simplicity gains speed + and memory efficiency at the potential cost of expanding too far. Increase + the depth parameter cautiously. + + stream + The desired output stream. If omitted (or False), the standard output + stream will be used. + + indent + Number of space to indent for each level of nesting. + + depth + The maximum depth to print out nested structures. Set to None for + infinite depth. Warning: be careful of infinite depth as infinite + recursion and overexpansion checks are not performed. + + sort_dicts + If true, dict keys are sorted. You must ensure that the dicts + encountered can be sorted against each other (for example, int cannot + compare against a tuple, both of which can be dict keys). + + Note: raises TypeError if sort_dicts is on and sorting finds a type + mismatch + """ + if not stream: + stream = sys.stdout + _pprint_impl(obj, stream, 0, sort_dicts, depth, 0, indent) + stream.write("\n") # end in a newline + + +def ppdir(obj, *args, hidden=False, **kwargs): + """Like calling pprint(dir(obj)) + + This method is for listing the available attributes and methods of an + object. The main difference is that you can hide elements that start with + an underscore by specifying hidden=False, which is the default. + + Additional arguments are forwarded on to pprint(). + """ + pprint([x for x in dir(obj) if not x.startswith("_")], *args, **kwargs) + + +# Private implementation + + +def _pprint_impl(obj, stream, indent, sort_dicts, maxlevel, level, indent_per_level): + type_code = _container_type(obj) + if (maxlevel is not None and level >= maxlevel) or type_code == _NONCONTAINER or not obj: + # directly print noncontainers, empty containers, or if we have reached + # the max level. + stream.write(repr(obj)) + else: + # expand the container + if type_code & _SIMPLE: + _pprint_simple_container( + obj, + type_code, + stream, + indent + indent_per_level, + sort_dicts, + maxlevel, + level + 1, + indent_per_level, + ) + else: # type_code & _DICT: + _pprint_dict( + obj, + type_code, + stream, + indent + indent_per_level, + sort_dicts, + maxlevel, + level + 1, + indent_per_level, + ) + + +def _container_type(obj): + typ = type(obj) + if issubclass(typ, list): + return _LIST | _SIMPLE + if issubclass(typ, set): + return _SET | _SIMPLE + if issubclass(typ, dict): + return _DICT + if issubclass(typ, tuple): + return _TUPLE | _SIMPLE + return _NONCONTAINER + + +def _pprint_simple_container( + obj, type_code, stream, indent, sort_dicts, maxlevel, level, indent_per_level +): + if type_code & _LIST: + chars = "[]" + elif type_code & _TUPLE: + chars = "()" + else: # type_code & _SET: + chars = "{}" + + indent_string = " " * indent + + stream.write(chars[0]) + stream.write(" " * (indent_per_level - 1)) + first = True + for item in obj: + if not first: + stream.write(",\n") + stream.write(indent_string) + first = False + # recurse here + _pprint_impl(item, stream, indent, sort_dicts, maxlevel, level, indent_per_level) + if type_code & _TUPLE and len(obj) == 1: + stream.write(",") + stream.write(chars[1]) + + +def _pprint_dict(obj, type_code, stream, indent, sort_dicts, maxlevel, level, indent_per_level): + indent_string = " " * indent + stream.write("{") + stream.write(" " * (indent_per_level - 1)) + first = True + items = obj.items() + if sort_dicts: + items = sorted(items, key=lambda x: x[0]) + for key, val in items: + if not first: + stream.write(",\n") + stream.write(indent_string) + first = False + # python's implementation does not multiline keys, then offsets the + # value by the key's length. They still do safety checks such as + # recursiveness and checking that it can be printed within the + # specified width, which we skip in this implementation. + keystr = repr(key) + stream.write(keystr) + stream.write(": ") + # recurse on the value, lined up to wrap in front of the colon. + _pprint_impl( + val, stream, indent + len(keystr) + 2, sort_dicts, maxlevel, level, indent_per_level + ) + stream.write("}") diff --git a/python-stdlib/pprint/test_pprint.py b/python-stdlib/pprint/test_pprint.py new file mode 100644 index 000000000..2256cd4f5 --- /dev/null +++ b/python-stdlib/pprint/test_pprint.py @@ -0,0 +1,481 @@ +import pprint +from io import StringIO +import sys + +# setup + + +def pformat_as_pprint(*args, stream=None, **kwargs): + """Use pformat() to provide pprint() interface""" + val = pprint.pformat(*args, **kwargs) + if not stream: + stream = sys.stdout + stream.write(val) + stream.write("\n") + + +def pp_as_pprint(*args, sort_dicts=True, **kwargs): + """Use pp() to provide pprint() interface""" + pprint.pp(*args, sort_dicts=sort_dicts, **kwargs) + + +def PrettyPrinter_pprint_as_pprint(obj, *args, **kwargs): + """Use PrettyPrinter.pprint() to provide pprint() interface""" + p = pprint.PrettyPrinter(*args, **kwargs) + p.pprint(obj) + + +def PrettyPrinter_pformat_as_pprint(obj, *args, **kwargs): + """Use PrettyPrinter.pformat() to provide pprint() interface""" + p = pprint.PrettyPrinter(*args, **kwargs) + val = p.pformat(obj) + p._stream.write(val) + p._stream.write("\n") + + +pprint_func_map = { + "pprint": pprint.pprint, + "pformat": pformat_as_pprint, + "pp": pp_as_pprint, + "PrettyPrinter.pprint": PrettyPrinter_pprint_as_pprint, + "PrettyPrinter.pformat": PrettyPrinter_pformat_as_pprint, +} + + +def pprint_func_to_pformat_func(pprint_func): + """Convert pprint() interface into the pformat() interface""" + + def pformat_func(*args, **kwargs): + sio = StringIO() + pprint_func(*args, stream=sio, **kwargs) + return sio.getvalue()[:-1] # return all but the trailing newline + + return pformat_func + + +pformat_func_map = {key: pprint_func_to_pformat_func(val) for key, val in pprint_func_map.items()} +# reset the pformat function instead of two indirect wrappers +pformat_func_map["pformat"] = pprint.pformat + + +# test functions + + +def test_stream(name, pprint_func): + # TODO: somehow test that stream=None goes to stdout + # This method of mocking stdout works in CPython, but not Micropython + # try: + # # mock standard out + # oldstdout = sys.stdout + # sys.stdout = sio + # # run test + # #... + # finally: + # # restore standard out + # sys.stdout = oldstdout + + sio = StringIO() + sio2 = StringIO() + gen_msg = lambda s: f"{name}: {repr(s.getvalue())}" + pprint_func("abcdefg", stream=sio2) + assert sio.getvalue() == "", gen_msg(sio) + assert sio2.getvalue() == "'abcdefg'\n", gen_msg(sio2) + pprint_func("zxcvbnm", stream=sio) + assert sio.getvalue() == "'zxcvbnm'\n", gen_msg(sio) + assert sio2.getvalue() == "'abcdefg'\n", gen_msg(sio2) + + +def test_non_containers(name, pformat_func): + class A: + def __repr__(self): + return "\n\ncocoa\n\nnuts\n\n" + + assert pformat_func(1) == repr(1), f"{name}, {pformat_func(1)}" + assert pformat_func(1.423) == repr(1.423), name + assert pformat_func("hello") == repr("hello"), name + assert pformat_func(b"my grail diary") == repr(b"my grail diary"), name + assert pformat_func(A()) == repr(A()), name + + +def test_empty_containers(name, pformat_func): + assert pformat_func(list()) == "[]", name + assert pformat_func(tuple()) == "()", name + assert pformat_func(set()) == "set()", name + assert pformat_func(dict()) == "{}", name + + +def test_containers_with_one_item(name, pformat_func): + assert pformat_func(["abc"]) == repr(["abc"]), name + assert pformat_func({123}) == repr({123}), name + assert pformat_func({"abc": 123}) == repr({"abc": 123}), name + assert pformat_func(("my tuple",)) == repr(("my tuple",)), name + + +X = { + "widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500, + }, + "image": { + "src": "Images/Sun.png", + "name": "sun1", + "hOffset": 250, + "vOffset": 250, + "alignment": "center", + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "name": "text1", + "hOffset": 250, + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;", + }, + }, + "name": "King Arthur", +} + +expected_X_depth0_indent0 = repr(X) +expected_X_depth0_indent1 = expected_X_depth0_indent0 +expected_X_depth0_indent2 = expected_X_depth0_indent0 +expected_X_depth0_indent3 = expected_X_depth0_indent0 + +# note: these are sorted by key and will be tested that way +expected_X_depth1_indent0 = ( + "{'name': " + repr(X["name"]) + ",\n" + "'widget': " + repr(X["widget"]) + "}" +) +expected_X_depth1_indent1 = ( + "{'name': " + repr(X["name"]) + ",\n" + " 'widget': " + repr(X["widget"]) + "}" +) +expected_X_depth1_indent2 = ( + "{ 'name': " + repr(X["name"]) + ",\n" + " 'widget': " + repr(X["widget"]) + "}" +) +expected_X_depth1_indent3 = ( + "{ 'name': " + repr(X["name"]) + ",\n" + " 'widget': " + repr(X["widget"]) + "}" +) + +expected_X_depth2_indent0 = ( + "{'name': 'King Arthur',\n" + "'widget': {'debug': " + + repr(X["widget"]["debug"]) + + ",\n" + + " 'image': " + + repr(X["widget"]["image"]) + + ",\n" + + " 'text': " + + repr(X["widget"]["text"]) + + ",\n" + + " 'window': " + + repr(X["widget"]["window"]) + + "}}" +) +expected_X_depth2_indent1 = ( + "{'name': 'King Arthur',\n" + " 'widget': {'debug': " + + repr(X["widget"]["debug"]) + + ",\n" + + " 'image': " + + repr(X["widget"]["image"]) + + ",\n" + + " 'text': " + + repr(X["widget"]["text"]) + + ",\n" + + " 'window': " + + repr(X["widget"]["window"]) + + "}}" +) +expected_X_depth2_indent2 = ( + "{ 'name': 'King Arthur',\n" + " 'widget': { 'debug': " + + repr(X["widget"]["debug"]) + + ",\n" + + " 'image': " + + repr(X["widget"]["image"]) + + ",\n" + + " 'text': " + + repr(X["widget"]["text"]) + + ",\n" + + " 'window': " + + repr(X["widget"]["window"]) + + "}}" +) +expected_X_depth2_indent3 = ( + "{ 'name': 'King Arthur',\n" + " 'widget': { 'debug': " + + repr(X["widget"]["debug"]) + + ",\n" + + " 'image': " + + repr(X["widget"]["image"]) + + ",\n" + + " 'text': " + + repr(X["widget"]["text"]) + + ",\n" + + " 'window': " + + repr(X["widget"]["window"]) + + "}}" +) + +expected_X_depth3_indent0 = ( + "{'name': 'King Arthur',\n" + "'widget': {'debug': 'on',\n" + " 'image': {'alignment': " + + repr(X["widget"]["image"]["alignment"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["image"]["hOffset"]) + + ",\n" + " 'name': " + repr(X["widget"]["image"]["name"]) + ",\n" + " 'src': " + repr(X["widget"]["image"]["src"]) + ",\n" + " 'vOffset': " + repr(X["widget"]["image"]["vOffset"]) + "},\n" + " 'text': {'alignment': " + + repr(X["widget"]["text"]["alignment"]) + + ",\n" + + " 'data': " + + repr(X["widget"]["text"]["data"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["text"]["hOffset"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["text"]["name"]) + + ",\n" + + " 'onMouseUp': " + + repr(X["widget"]["text"]["onMouseUp"]) + + ",\n" + + " 'size': " + + repr(X["widget"]["text"]["size"]) + + ",\n" + + " 'style': " + + repr(X["widget"]["text"]["style"]) + + ",\n" + + " 'vOffset': " + + repr(X["widget"]["text"]["vOffset"]) + + "},\n" + + " 'window': {'height': " + + repr(X["widget"]["window"]["height"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["window"]["name"]) + + ",\n" + + " 'title': " + + repr(X["widget"]["window"]["title"]) + + ",\n" + + " 'width': " + + repr(X["widget"]["window"]["width"]) + + "}}}" +) +expected_X_depth3_indent1 = ( + "{'name': 'King Arthur',\n" + " 'widget': {'debug': 'on',\n" + " 'image': {'alignment': " + + repr(X["widget"]["image"]["alignment"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["image"]["hOffset"]) + + ",\n" + " 'name': " + repr(X["widget"]["image"]["name"]) + ",\n" + " 'src': " + repr(X["widget"]["image"]["src"]) + ",\n" + " 'vOffset': " + repr(X["widget"]["image"]["vOffset"]) + "},\n" + " 'text': {'alignment': " + + repr(X["widget"]["text"]["alignment"]) + + ",\n" + + " 'data': " + + repr(X["widget"]["text"]["data"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["text"]["hOffset"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["text"]["name"]) + + ",\n" + + " 'onMouseUp': " + + repr(X["widget"]["text"]["onMouseUp"]) + + ",\n" + + " 'size': " + + repr(X["widget"]["text"]["size"]) + + ",\n" + + " 'style': " + + repr(X["widget"]["text"]["style"]) + + ",\n" + + " 'vOffset': " + + repr(X["widget"]["text"]["vOffset"]) + + "},\n" + + " 'window': {'height': " + + repr(X["widget"]["window"]["height"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["window"]["name"]) + + ",\n" + + " 'title': " + + repr(X["widget"]["window"]["title"]) + + ",\n" + + " 'width': " + + repr(X["widget"]["window"]["width"]) + + "}}}" +) +expected_X_depth3_indent2 = ( + "{ 'name': 'King Arthur',\n" + " 'widget': { 'debug': 'on',\n" + " 'image': { 'alignment': " + + repr(X["widget"]["image"]["alignment"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["image"]["hOffset"]) + + ",\n" + " 'name': " + repr(X["widget"]["image"]["name"]) + ",\n" + " 'src': " + repr(X["widget"]["image"]["src"]) + ",\n" + " 'vOffset': " + repr(X["widget"]["image"]["vOffset"]) + "},\n" + " 'text': { 'alignment': " + + repr(X["widget"]["text"]["alignment"]) + + ",\n" + + " 'data': " + + repr(X["widget"]["text"]["data"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["text"]["hOffset"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["text"]["name"]) + + ",\n" + + " 'onMouseUp': " + + repr(X["widget"]["text"]["onMouseUp"]) + + ",\n" + + " 'size': " + + repr(X["widget"]["text"]["size"]) + + ",\n" + + " 'style': " + + repr(X["widget"]["text"]["style"]) + + ",\n" + + " 'vOffset': " + + repr(X["widget"]["text"]["vOffset"]) + + "},\n" + + " 'window': { 'height': " + + repr(X["widget"]["window"]["height"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["window"]["name"]) + + ",\n" + + " 'title': " + + repr(X["widget"]["window"]["title"]) + + ",\n" + + " 'width': " + + repr(X["widget"]["window"]["width"]) + + "}}}" +) +expected_X_depth3_indent3 = ( + "{ 'name': 'King Arthur',\n" + " 'widget': { 'debug': 'on',\n" + " 'image': { 'alignment': " + + repr(X["widget"]["image"]["alignment"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["image"]["hOffset"]) + + ",\n" + " 'name': " + repr(X["widget"]["image"]["name"]) + ",\n" + " 'src': " + repr(X["widget"]["image"]["src"]) + ",\n" + " 'vOffset': " + repr(X["widget"]["image"]["vOffset"]) + "},\n" + " 'text': { 'alignment': " + + repr(X["widget"]["text"]["alignment"]) + + ",\n" + + " 'data': " + + repr(X["widget"]["text"]["data"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["text"]["hOffset"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["text"]["name"]) + + ",\n" + + " 'onMouseUp': " + + repr(X["widget"]["text"]["onMouseUp"]) + + ",\n" + + " 'size': " + + repr(X["widget"]["text"]["size"]) + + ",\n" + + " 'style': " + + repr(X["widget"]["text"]["style"]) + + ",\n" + + " 'vOffset': " + + repr(X["widget"]["text"]["vOffset"]) + + "},\n" + + " 'window': { 'height': " + + repr(X["widget"]["window"]["height"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["window"]["name"]) + + ",\n" + + " 'title': " + + repr(X["widget"]["window"]["title"]) + + ",\n" + + " 'width': " + + repr(X["widget"]["window"]["width"]) + + "}}}" +) + +expected_X_depth4_indent0 = expected_X_depth3_indent0 +expected_X_depth4_indent1 = expected_X_depth3_indent1 +expected_X_depth4_indent2 = expected_X_depth3_indent2 +expected_X_depth4_indent3 = expected_X_depth3_indent3 + +expected_X_depth_None_indent0 = expected_X_depth3_indent0 +expected_X_depth_None_indent1 = expected_X_depth3_indent1 +expected_X_depth_None_indent2 = expected_X_depth3_indent2 +expected_X_depth_None_indent3 = expected_X_depth3_indent3 + + +def test_depth_and_indent(name, pformat_func): + gen_msg = lambda d, i: f"{name} depth={d} indent={i}:\n{pformat_func(X, depth=d, indent=i)}\n" + + assert pformat_func(X, depth=0, indent=0) == expected_X_depth0_indent0, gen_msg(0, 0) + assert pformat_func(X, depth=0, indent=1) == expected_X_depth0_indent1, gen_msg(0, 1) + assert pformat_func(X, depth=0, indent=2) == expected_X_depth0_indent2, gen_msg(0, 3) + assert pformat_func(X, depth=0, indent=3) == expected_X_depth0_indent3, gen_msg(0, 3) + + assert pformat_func(X, depth=1, indent=0) == expected_X_depth1_indent0, gen_msg(1, 0) + assert pformat_func(X, depth=1, indent=1) == expected_X_depth1_indent1, gen_msg(1, 1) + assert pformat_func(X, depth=1, indent=2) == expected_X_depth1_indent2, gen_msg(1, 3) + assert pformat_func(X, depth=1, indent=3) == expected_X_depth1_indent3, gen_msg(1, 3) + + assert pformat_func(X, depth=2, indent=0) == expected_X_depth2_indent0, gen_msg(2, 0) + assert pformat_func(X, depth=2, indent=1) == expected_X_depth2_indent1, gen_msg(2, 1) + assert pformat_func(X, depth=2, indent=2) == expected_X_depth2_indent2, gen_msg(2, 3) + assert pformat_func(X, depth=2, indent=3) == expected_X_depth2_indent3, gen_msg(2, 3) + + assert pformat_func(X, depth=3, indent=0) == expected_X_depth3_indent0, gen_msg(3, 0) + assert pformat_func(X, depth=3, indent=1) == expected_X_depth3_indent1, gen_msg(3, 1) + assert pformat_func(X, depth=3, indent=2) == expected_X_depth3_indent2, gen_msg(3, 3) + assert pformat_func(X, depth=3, indent=3) == expected_X_depth3_indent3, gen_msg(3, 3) + + assert pformat_func(X, depth=4, indent=0) == expected_X_depth4_indent0, gen_msg(4, 0) + assert pformat_func(X, depth=4, indent=1) == expected_X_depth4_indent1, gen_msg(4, 1) + assert pformat_func(X, depth=4, indent=2) == expected_X_depth4_indent2, gen_msg(4, 3) + assert pformat_func(X, depth=4, indent=3) == expected_X_depth4_indent3, gen_msg(4, 3) + + assert pformat_func(X, depth=None, indent=0) == expected_X_depth_None_indent0, gen_msg(None, 0) + assert pformat_func(X, depth=None, indent=1) == expected_X_depth_None_indent1, gen_msg(None, 1) + assert pformat_func(X, depth=None, indent=2) == expected_X_depth_None_indent2, gen_msg(None, 3) + assert pformat_func(X, depth=None, indent=3) == expected_X_depth_None_indent3, gen_msg(None, 3) + + +# TODO: it would be good to test with sort_dicts=False + + +# run tests + +for name, func in pprint_func_map.items(): + test_stream(name, func) + +for name, func in pformat_func_map.items(): + test_non_containers(name, func) + test_empty_containers(name, func) + test_depth_and_indent(name, func) + +# +# def test_depth(pformat_func): +# diff --git a/tools/ci.sh b/tools/ci.sh index e2b6919be..2040185c0 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -71,6 +71,7 @@ function ci_package_tests_run { python-stdlib/operator/test_operator.py \ python-stdlib/os-path/test_path.py \ python-stdlib/pickle/test_pickle.py \ + python-stdlib/pprint/test_pprint.py \ python-stdlib/string/test_translate.py \ python-stdlib/unittest/tests/exception.py \ unix-ffi/gettext/test_gettext.py \