From 830ad1b055bb10be77a06d85519a9694b6e79c45 Mon Sep 17 00:00:00 2001 From: Ehud Halamish Date: Wed, 23 Nov 2022 16:37:32 +0200 Subject: [PATCH 1/7] We want to still print the given args even when the source is unavaiilable --- icecream/icecream.py | 70 +++++++++++++++++++----------------------- tests/test_icecream.py | 11 ++++--- 2 files changed, 39 insertions(+), 42 deletions(-) diff --git a/icecream/icecream.py b/icecream/icecream.py index 410921f..78ec401 100644 --- a/icecream/icecream.py +++ b/icecream/icecream.py @@ -90,25 +90,25 @@ def colorizedStderrPrint(s): DEFAULT_ARG_TO_STRING_FUNCTION = pprint.pformat -class NoSourceAvailableError(OSError): - """ - Raised when icecream fails to find or access source code that's - required to parse and analyze. This can happen, for example, when +""" +This info message is printed instead of the arguments when icecream +fails to find or access source code that's required to parse and analyze. +This can happen, for example, when - - ic() is invoked inside a REPL or interactive shell, e.g. from the - command line (CLI) or with python -i. + - ic() is invoked inside a REPL or interactive shell, e.g. from the + command line (CLI) or with python -i. - - The source code is mangled and/or packaged, e.g. with a project - freezer like PyInstaller. + - The source code is mangled and/or packaged, e.g. with a project + freezer like PyInstaller. - - The underlying source code changed during execution. See - https://stackoverflow.com/a/33175832. - """ - infoMessage = ( - 'Failed to access the underlying source code for analysis. Was ic() ' - 'invoked in a REPL (e.g. from the command line), a frozen application ' - '(e.g. packaged with PyInstaller), or did the underlying source code ' - 'change during execution?') + - The underlying source code changed during execution. See + https://stackoverflow.com/a/33175832. +""" +NO_SOURCE_AVAILABLE_INFO_MESSAGE = ( + 'Error: Failed to access the underlying source code for analysis. Was ic() ' + 'invoked in a REPL (e.g. from the command line), a frozen application ' + '(e.g. packaged with PyInstaller), or did the underlying source code ' + 'change during execution?') def callOrValue(obj): @@ -204,12 +204,7 @@ def __init__(self, prefix=DEFAULT_PREFIX, def __call__(self, *args): if self.enabled: callFrame = inspect.currentframe().f_back - try: - out = self._format(callFrame, *args) - except NoSourceAvailableError as err: - prefix = callOrValue(self.prefix) - out = prefix + 'Error: ' + err.infoMessage - self.outputFunction(out) + self.outputFunction(self._format(callFrame, *args)) if not args: # E.g. ic(). passthrough = None @@ -228,11 +223,7 @@ def format(self, *args): def _format(self, callFrame, *args): prefix = callOrValue(self.prefix) - callNode = Source.executing(callFrame).node - if callNode is None: - raise NoSourceAvailableError() - - context = self._formatContext(callFrame, callNode) + context = self._formatContext(callFrame) if not args: time = self._formatTime() out = prefix + context + time @@ -240,15 +231,19 @@ def _format(self, callFrame, *args): if not self.includeContext: context = '' out = self._formatArgs( - callFrame, callNode, prefix, context, args) + callFrame, prefix, context, args) return out - def _formatArgs(self, callFrame, callNode, prefix, context, args): - source = Source.for_frame(callFrame) - sanitizedArgStrs = [ - source.get_text_with_indentation(arg) - for arg in callNode.args] + def _formatArgs(self, callFrame, prefix, context, args): + callNode = Source.executing(callFrame).node + if callNode is not None: + source = Source.for_frame(callFrame) + sanitizedArgStrs = [ + source.get_text_with_indentation(arg) + for arg in callNode.args] + else: + sanitizedArgStrs = [NO_SOURCE_AVAILABLE_INFO_MESSAGE] * len(args) pairs = list(zip(sanitizedArgStrs, args)) @@ -313,9 +308,8 @@ def argPrefix(arg): return '\n'.join(lines) - def _formatContext(self, callFrame, callNode): - filename, lineNumber, parentFunction = self._getContext( - callFrame, callNode) + def _formatContext(self, callFrame): + filename, lineNumber, parentFunction = self._getContext(callFrame) if parentFunction != '': parentFunction = '%s()' % parentFunction @@ -328,9 +322,9 @@ def _formatTime(self): formatted = now.strftime('%H:%M:%S.%f')[:-3] return ' at %s' % formatted - def _getContext(self, callFrame, callNode): - lineNumber = callNode.lineno + def _getContext(self, callFrame): frameInfo = inspect.getframeinfo(callFrame) + lineNumber = frameInfo.lineno parentFunction = frameInfo.function filepath = (realpath if self.contextAbsPath else basename)(frameInfo.filename) diff --git a/tests/test_icecream.py b/tests/test_icecream.py index e0f718e..ee3a378 100644 --- a/tests/test_icecream.py +++ b/tests/test_icecream.py @@ -21,8 +21,7 @@ from os.path import basename, splitext, realpath import icecream -from icecream import ic, argumentToString, stderrPrint, NoSourceAvailableError - +from icecream import ic, argumentToString, stderrPrint, NO_SOURCE_AVAILABLE_INFO_MESSAGE TEST_PAIR_DELIMITER = '| ' MY_FILENAME = basename(__file__) @@ -529,8 +528,12 @@ def testMultilineInvocationWithComments(self): def testNoSourceAvailable(self): with disableColoring(), captureStandardStreams() as (out, err): - eval('ic()') - assert NoSourceAvailableError.infoMessage in err.getvalue() + eval('ic(a, b)') + + self.assertEqual(err.getvalue().strip(), """ +ic| {0}: 1 + {0}: 2 + """.format(NO_SOURCE_AVAILABLE_INFO_MESSAGE).strip()) def testSingleTupleArgument(self): with disableColoring(), captureStandardStreams() as (out, err): From b10045cf38f4701a1e9b29303b64c995ca759e19 Mon Sep 17 00:00:00 2001 From: Ehud Halamish Date: Wed, 23 Nov 2022 18:17:34 +0200 Subject: [PATCH 2/7] allow configuring the message when no source is available, useful for pro users who don't need the whole warning and just want to see their printed values --- icecream/icecream.py | 14 ++++++++++---- tests/test_icecream.py | 22 ++++++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/icecream/icecream.py b/icecream/icecream.py index 78ec401..018eb27 100644 --- a/icecream/icecream.py +++ b/icecream/icecream.py @@ -104,7 +104,7 @@ def colorizedStderrPrint(s): - The underlying source code changed during execution. See https://stackoverflow.com/a/33175832. """ -NO_SOURCE_AVAILABLE_INFO_MESSAGE = ( +DEFAULT_NO_SOURCE_AVAILABLE_MESSAGE = ( 'Error: Failed to access the underlying source code for analysis. Was ic() ' 'invoked in a REPL (e.g. from the command line), a frozen application ' '(e.g. packaged with PyInstaller), or did the underlying source code ' @@ -193,13 +193,15 @@ class IceCreamDebugger: def __init__(self, prefix=DEFAULT_PREFIX, outputFunction=DEFAULT_OUTPUT_FUNCTION, argToStringFunction=argumentToString, includeContext=False, - contextAbsPath=False): + contextAbsPath=False, + noSourceAvailableMessage=DEFAULT_NO_SOURCE_AVAILABLE_MESSAGE): self.enabled = True self.prefix = prefix self.includeContext = includeContext self.outputFunction = outputFunction self.argToStringFunction = argToStringFunction self.contextAbsPath = contextAbsPath + self.noSourceAvailableMessage = noSourceAvailableMessage def __call__(self, *args): if self.enabled: @@ -243,7 +245,7 @@ def _formatArgs(self, callFrame, prefix, context, args): source.get_text_with_indentation(arg) for arg in callNode.args] else: - sanitizedArgStrs = [NO_SOURCE_AVAILABLE_INFO_MESSAGE] * len(args) + sanitizedArgStrs = [self.noSourceAvailableMessage] * len(args) pairs = list(zip(sanitizedArgStrs, args)) @@ -338,7 +340,8 @@ def disable(self): def configureOutput(self, prefix=_absent, outputFunction=_absent, argToStringFunction=_absent, includeContext=_absent, - contextAbsPath=_absent): + contextAbsPath=_absent, + noSourceAvailableMessage=_absent): noParameterProvided = all( v is _absent for k,v in locals().items() if k != 'self') if noParameterProvided: @@ -359,5 +362,8 @@ def configureOutput(self, prefix=_absent, outputFunction=_absent, if contextAbsPath is not _absent: self.contextAbsPath = contextAbsPath + if noSourceAvailableMessage is not _absent: + self.noSourceAvailableMessage = noSourceAvailableMessage + ic = IceCreamDebugger() diff --git a/tests/test_icecream.py b/tests/test_icecream.py index ee3a378..0952e6e 100644 --- a/tests/test_icecream.py +++ b/tests/test_icecream.py @@ -21,7 +21,7 @@ from os.path import basename, splitext, realpath import icecream -from icecream import ic, argumentToString, stderrPrint, NO_SOURCE_AVAILABLE_INFO_MESSAGE +from icecream import ic, argumentToString, stderrPrint, DEFAULT_NO_SOURCE_AVAILABLE_MESSAGE TEST_PAIR_DELIMITER = '| ' MY_FILENAME = basename(__file__) @@ -63,12 +63,14 @@ def disableColoring(): @contextmanager def configureIcecreamOutput(prefix=None, outputFunction=None, argToStringFunction=None, includeContext=None, - contextAbsPath=None): + contextAbsPath=None, + noSourceAvailableMessage=None): oldPrefix = ic.prefix oldOutputFunction = ic.outputFunction oldArgToStringFunction = ic.argToStringFunction oldIncludeContext = ic.includeContext oldContextAbsPath = ic.contextAbsPath + oldNoSourceAvailableMessage = ic.noSourceAvailableMessage if prefix: ic.configureOutput(prefix=prefix) @@ -80,12 +82,14 @@ def configureIcecreamOutput(prefix=None, outputFunction=None, ic.configureOutput(includeContext=includeContext) if contextAbsPath: ic.configureOutput(contextAbsPath=contextAbsPath) + if noSourceAvailableMessage: + ic.configureOutput(noSourceAvailableMessage=noSourceAvailableMessage) yield ic.configureOutput( oldPrefix, oldOutputFunction, oldArgToStringFunction, - oldIncludeContext, oldContextAbsPath) + oldIncludeContext, oldContextAbsPath, oldNoSourceAvailableMessage) @contextmanager @@ -533,7 +537,17 @@ def testNoSourceAvailable(self): self.assertEqual(err.getvalue().strip(), """ ic| {0}: 1 {0}: 2 - """.format(NO_SOURCE_AVAILABLE_INFO_MESSAGE).strip()) + """.format(DEFAULT_NO_SOURCE_AVAILABLE_MESSAGE).strip()) + + def testNoSourceAvailableConfiguration(self): + noSourceAvailableMessage = 'no source' + with configureIcecreamOutput(noSourceAvailableMessage=noSourceAvailableMessage): + with disableColoring(), captureStandardStreams() as (out, err): + eval('ic(a)') + + self.assertEqual(err.getvalue().strip(), """ + ic| {0}: 1 + """.format(noSourceAvailableMessage).strip()) def testSingleTupleArgument(self): with disableColoring(), captureStandardStreams() as (out, err): From 6e0dd000c43f03bcf75ad9e46d838775e2257dd5 Mon Sep 17 00:00:00 2001 From: Ehud Halamish Date: Sat, 26 Nov 2022 13:56:53 +0200 Subject: [PATCH 3/7] Revert "allow configuring the message when no source is available, useful for pro users who don't need the whole warning and just want to see their printed values" This reverts commit b10045cf38f4701a1e9b29303b64c995ca759e19. --- icecream/icecream.py | 14 ++++---------- tests/test_icecream.py | 22 ++++------------------ 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/icecream/icecream.py b/icecream/icecream.py index 018eb27..78ec401 100644 --- a/icecream/icecream.py +++ b/icecream/icecream.py @@ -104,7 +104,7 @@ def colorizedStderrPrint(s): - The underlying source code changed during execution. See https://stackoverflow.com/a/33175832. """ -DEFAULT_NO_SOURCE_AVAILABLE_MESSAGE = ( +NO_SOURCE_AVAILABLE_INFO_MESSAGE = ( 'Error: Failed to access the underlying source code for analysis. Was ic() ' 'invoked in a REPL (e.g. from the command line), a frozen application ' '(e.g. packaged with PyInstaller), or did the underlying source code ' @@ -193,15 +193,13 @@ class IceCreamDebugger: def __init__(self, prefix=DEFAULT_PREFIX, outputFunction=DEFAULT_OUTPUT_FUNCTION, argToStringFunction=argumentToString, includeContext=False, - contextAbsPath=False, - noSourceAvailableMessage=DEFAULT_NO_SOURCE_AVAILABLE_MESSAGE): + contextAbsPath=False): self.enabled = True self.prefix = prefix self.includeContext = includeContext self.outputFunction = outputFunction self.argToStringFunction = argToStringFunction self.contextAbsPath = contextAbsPath - self.noSourceAvailableMessage = noSourceAvailableMessage def __call__(self, *args): if self.enabled: @@ -245,7 +243,7 @@ def _formatArgs(self, callFrame, prefix, context, args): source.get_text_with_indentation(arg) for arg in callNode.args] else: - sanitizedArgStrs = [self.noSourceAvailableMessage] * len(args) + sanitizedArgStrs = [NO_SOURCE_AVAILABLE_INFO_MESSAGE] * len(args) pairs = list(zip(sanitizedArgStrs, args)) @@ -340,8 +338,7 @@ def disable(self): def configureOutput(self, prefix=_absent, outputFunction=_absent, argToStringFunction=_absent, includeContext=_absent, - contextAbsPath=_absent, - noSourceAvailableMessage=_absent): + contextAbsPath=_absent): noParameterProvided = all( v is _absent for k,v in locals().items() if k != 'self') if noParameterProvided: @@ -362,8 +359,5 @@ def configureOutput(self, prefix=_absent, outputFunction=_absent, if contextAbsPath is not _absent: self.contextAbsPath = contextAbsPath - if noSourceAvailableMessage is not _absent: - self.noSourceAvailableMessage = noSourceAvailableMessage - ic = IceCreamDebugger() diff --git a/tests/test_icecream.py b/tests/test_icecream.py index 0952e6e..ee3a378 100644 --- a/tests/test_icecream.py +++ b/tests/test_icecream.py @@ -21,7 +21,7 @@ from os.path import basename, splitext, realpath import icecream -from icecream import ic, argumentToString, stderrPrint, DEFAULT_NO_SOURCE_AVAILABLE_MESSAGE +from icecream import ic, argumentToString, stderrPrint, NO_SOURCE_AVAILABLE_INFO_MESSAGE TEST_PAIR_DELIMITER = '| ' MY_FILENAME = basename(__file__) @@ -63,14 +63,12 @@ def disableColoring(): @contextmanager def configureIcecreamOutput(prefix=None, outputFunction=None, argToStringFunction=None, includeContext=None, - contextAbsPath=None, - noSourceAvailableMessage=None): + contextAbsPath=None): oldPrefix = ic.prefix oldOutputFunction = ic.outputFunction oldArgToStringFunction = ic.argToStringFunction oldIncludeContext = ic.includeContext oldContextAbsPath = ic.contextAbsPath - oldNoSourceAvailableMessage = ic.noSourceAvailableMessage if prefix: ic.configureOutput(prefix=prefix) @@ -82,14 +80,12 @@ def configureIcecreamOutput(prefix=None, outputFunction=None, ic.configureOutput(includeContext=includeContext) if contextAbsPath: ic.configureOutput(contextAbsPath=contextAbsPath) - if noSourceAvailableMessage: - ic.configureOutput(noSourceAvailableMessage=noSourceAvailableMessage) yield ic.configureOutput( oldPrefix, oldOutputFunction, oldArgToStringFunction, - oldIncludeContext, oldContextAbsPath, oldNoSourceAvailableMessage) + oldIncludeContext, oldContextAbsPath) @contextmanager @@ -537,17 +533,7 @@ def testNoSourceAvailable(self): self.assertEqual(err.getvalue().strip(), """ ic| {0}: 1 {0}: 2 - """.format(DEFAULT_NO_SOURCE_AVAILABLE_MESSAGE).strip()) - - def testNoSourceAvailableConfiguration(self): - noSourceAvailableMessage = 'no source' - with configureIcecreamOutput(noSourceAvailableMessage=noSourceAvailableMessage): - with disableColoring(), captureStandardStreams() as (out, err): - eval('ic(a)') - - self.assertEqual(err.getvalue().strip(), """ - ic| {0}: 1 - """.format(noSourceAvailableMessage).strip()) + """.format(NO_SOURCE_AVAILABLE_INFO_MESSAGE).strip()) def testSingleTupleArgument(self): with disableColoring(), captureStandardStreams() as (out, err): From 6908093e22825fc5143e76a61649101799dc64c9 Mon Sep 17 00:00:00 2001 From: Ehud Halamish Date: Sat, 26 Nov 2022 17:35:31 +0200 Subject: [PATCH 4/7] When no source is available we want to print a single warning once, and then continue to print only the values --- icecream/icecream.py | 17 ++++++++++++----- tests/test_icecream.py | 25 +++++++++++++++++-------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/icecream/icecream.py b/icecream/icecream.py index 78ec401..5406ab2 100644 --- a/icecream/icecream.py +++ b/icecream/icecream.py @@ -17,6 +17,7 @@ import inspect import pprint import sys +import warnings from datetime import datetime import functools from contextlib import contextmanager @@ -37,8 +38,8 @@ PYTHON2 = (sys.version_info[0] == 2) - _absent = object() +_arg_source_missing = object() def bindStaticVariable(name, value): @@ -104,8 +105,8 @@ def colorizedStderrPrint(s): - The underlying source code changed during execution. See https://stackoverflow.com/a/33175832. """ -NO_SOURCE_AVAILABLE_INFO_MESSAGE = ( - 'Error: Failed to access the underlying source code for analysis. Was ic() ' +NO_SOURCE_AVAILABLE_WARNING_MESSAGE = ( + 'Failed to access the underlying source code for analysis. Was ic() ' 'invoked in a REPL (e.g. from the command line), a frozen application ' '(e.g. packaged with PyInstaller), or did the underlying source code ' 'change during execution?') @@ -243,7 +244,9 @@ def _formatArgs(self, callFrame, prefix, context, args): source.get_text_with_indentation(arg) for arg in callNode.args] else: - sanitizedArgStrs = [NO_SOURCE_AVAILABLE_INFO_MESSAGE] * len(args) + warnings.warn(NO_SOURCE_AVAILABLE_WARNING_MESSAGE, + category=RuntimeWarning) + sanitizedArgStrs = [_arg_source_missing] * len(args) pairs = list(zip(sanitizedArgStrs, args)) @@ -266,8 +269,12 @@ def argPrefix(arg): # # ic| "hello": 'hello'. # + # When the source for an arg is missing we also only print the value, + # since we can't know anything about the argument itself. pairStrs = [ - val if isLiteral(arg) else (argPrefix(arg) + val) + val + if (isLiteral(arg) or arg is _arg_source_missing) + else (argPrefix(arg) + val) for arg, val in pairs] allArgsOnOneLine = self._pairDelimiter.join(pairStrs) diff --git a/tests/test_icecream.py b/tests/test_icecream.py index ee3a378..92409a6 100644 --- a/tests/test_icecream.py +++ b/tests/test_icecream.py @@ -13,6 +13,8 @@ import functools import sys import unittest +import warnings + try: # Python 2.x. from StringIO import StringIO except ImportError: # Python 3.x. @@ -21,7 +23,7 @@ from os.path import basename, splitext, realpath import icecream -from icecream import ic, argumentToString, stderrPrint, NO_SOURCE_AVAILABLE_INFO_MESSAGE +from icecream import ic, argumentToString, stderrPrint, NO_SOURCE_AVAILABLE_WARNING_MESSAGE TEST_PAIR_DELIMITER = '| ' MY_FILENAME = basename(__file__) @@ -526,14 +528,21 @@ def testMultilineInvocationWithComments(self): pairs = parseOutputIntoPairs(out, err, 1)[0] assert pairs == [('a', '1'), ('b', '2')] - def testNoSourceAvailable(self): - with disableColoring(), captureStandardStreams() as (out, err): + def testNoSourceAvailablePrintsValues(self): + with disableColoring(), captureStandardStreams() as (out, err), warnings.catch_warnings(): + # we ignore the warning so that it doesn't interfere with parsing ic's output + warnings.simplefilter("ignore") eval('ic(a, b)') - - self.assertEqual(err.getvalue().strip(), """ -ic| {0}: 1 - {0}: 2 - """.format(NO_SOURCE_AVAILABLE_INFO_MESSAGE).strip()) + pairs = parseOutputIntoPairs(out, err, 1) + self.assertEqual(pairs, [[('1', None), ("2", None)]]) + + def testNoSourceAvailableIssuesExactlyOneWarning(self): + with warnings.catch_warnings(record=True) as all_warnings: + eval('ic(a)') + eval('ic(b)') + assert len(all_warnings) == 1 + warning = all_warnings[-1] + assert NO_SOURCE_AVAILABLE_WARNING_MESSAGE in str(warning.message) def testSingleTupleArgument(self): with disableColoring(), captureStandardStreams() as (out, err): From ecc287ce970ee045a37a255d9cb22451a6cc1d69 Mon Sep 17 00:00:00 2001 From: Ehud Halamish Date: Sat, 26 Nov 2022 17:48:47 +0200 Subject: [PATCH 5/7] the warning should point to the user's code --- icecream/icecream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/icecream/icecream.py b/icecream/icecream.py index 5406ab2..4cb1f87 100644 --- a/icecream/icecream.py +++ b/icecream/icecream.py @@ -245,7 +245,7 @@ def _formatArgs(self, callFrame, prefix, context, args): for arg in callNode.args] else: warnings.warn(NO_SOURCE_AVAILABLE_WARNING_MESSAGE, - category=RuntimeWarning) + category=RuntimeWarning, stacklevel=4) sanitizedArgStrs = [_arg_source_missing] * len(args) pairs = list(zip(sanitizedArgStrs, args)) From f0fcf3d910f13ae9139d3b517be161be9ed0bb4f Mon Sep 17 00:00:00 2001 From: Ehud Halamish Date: Sat, 26 Nov 2022 18:27:21 +0200 Subject: [PATCH 6/7] there was an edge case with multiline values when the source was changed. This handles multiline values as well and added a test so that the bug doesn't come back in the future --- icecream/icecream.py | 8 ++++++-- tests/test_icecream.py | 23 ++++++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/icecream/icecream.py b/icecream/icecream.py index 4cb1f87..435518f 100644 --- a/icecream/icecream.py +++ b/icecream/icecream.py @@ -144,8 +144,12 @@ def indented_lines(prefix, string): def format_pair(prefix, arg, value): - arg_lines = indented_lines(prefix, arg) - value_prefix = arg_lines[-1] + ': ' + if arg is _arg_source_missing: + arg_lines = [] + value_prefix = prefix + else: + arg_lines = indented_lines(prefix, arg) + value_prefix = arg_lines[-1] + ': ' looksLikeAString = value[0] + value[-1] in ["''", '""'] if looksLikeAString: # Align the start of multiline strings. diff --git a/tests/test_icecream.py b/tests/test_icecream.py index 92409a6..40497ba 100644 --- a/tests/test_icecream.py +++ b/tests/test_icecream.py @@ -184,12 +184,13 @@ def parseOutputIntoPairs(out, err, assertNumLines, if len(pairs[0]) == 1 and line.startswith(' '): arg, value = linePairs[-1][-1] looksLikeAString = value[0] in ["'", '"'] - prefix = (arg + ': ') + (' ' if looksLikeAString else '') + prefix = ((arg + ': ' if arg is not None else '') # A multiline value + + (' ' if looksLikeAString else '')) dedented = line[len(ic.prefix) + len(prefix):] linePairs[-1][-1] = (arg, value + '\n' + dedented) else: items = [ - (p[0].strip(), None) if len(p) == 1 # A value, like ic(3). + (None, p[0].strip()) if len(p) == 1 # A value, like ic(3). else (p[0].strip(), p[1].strip()) # A variable, like ic(a). for p in pairs] linePairs.append(items) @@ -252,7 +253,7 @@ def testNestedMultiline(self): ic(a, 'foo') pairs = parseOutputIntoPairs(out, err, 1)[0] - assert pairs == [('a', '1'), ("'foo'", None)] + assert pairs == [('a', '1'), (None, "'foo'")] with disableColoring(), captureStandardStreams() as (out, err): noop(noop(noop({1: ic( @@ -479,7 +480,7 @@ def testValues(self): ic(3, 'asdf', "asdf") pairs = parseOutputIntoPairs(out, err, 1) - assert pairs == [[('3', None), ("'asdf'", None), ("'asdf'", None)]] + assert pairs == [[(None, '3'), (None, "'asdf'"), (None, "'asdf'")]] def testIncludeContextMultiLine(self): multilineStr = 'line1\nline2' @@ -534,7 +535,19 @@ def testNoSourceAvailablePrintsValues(self): warnings.simplefilter("ignore") eval('ic(a, b)') pairs = parseOutputIntoPairs(out, err, 1) - self.assertEqual(pairs, [[('1', None), ("2", None)]]) + self.assertEqual(pairs, [[(None, '1'), (None, "2")]]) + + def testNoSourceAvailablePrintsMultiline(self): + """ + This tests for a bug which caused only multiline prints to fail. + """ + multilineStr = 'line1\nline2' + with disableColoring(), captureStandardStreams() as (out, err), warnings.catch_warnings(): + # we ignore the warning so that it doesn't interfere with parsing ic's output + warnings.simplefilter("ignore") + eval('ic(multilineStr)') + pair = parseOutputIntoPairs(out, err, 2)[0][0] + self.assertEqual(pair, (None, ic.argToStringFunction(multilineStr))) def testNoSourceAvailableIssuesExactlyOneWarning(self): with warnings.catch_warnings(record=True) as all_warnings: From ecf1e0bd5a4ab7f5ed72530108be717312ea2b1c Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Sun, 4 Dec 2022 21:48:55 +0200 Subject: [PATCH 7/7] Set GHA Ubuntu version so it finds Python versions --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a56d255..9dd8db2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: include: