Skip to content

Initial Python 3 compatibility fixes #23256

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions benchmark/scripts/Benchmark_Driver
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class `BenchmarkDoctor` analyzes performance tests, implements `check` COMMAND.
"""

import argparse
import functools
import glob
import logging
import math
Expand All @@ -37,6 +38,9 @@ import time

from compare_perf_tests import LogParser

if not hasattr(sys.modules['__builtin__'], 'reduce'):
reduce = functools.reduce

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is easier to use functools.reduce in both Python 2 and 3.

from functools import reduce

(This happens in a couple more places in other files, I will use the same solution there)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was learning as I went along, so didn't always pick up the best solutions, especially in some of my earlier changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you adopt that throughout? It seems cleaner.

DRIVER_DIR = os.path.dirname(os.path.realpath(__file__))


Expand Down Expand Up @@ -178,14 +182,10 @@ class BenchmarkDriver(object):

Returns the aggregated result of independent benchmark invocations.
"""
def merge_results(a, b):
a.merge(b)
return a

return reduce(merge_results,
[self.run(test, measure_memory=True,
result_sets = [self.run(test, measure_memory=True,
num_iters=1, quantile=20)
for _ in range(self.args.independent_samples)])
for _ in range(self.args.independent_samples)]
return reduce(lambda m, res: (m.merge(res), m)[1], result_sets)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original version of this seems less magical and easier for non-python folks to grok.


def log_results(self, output, log_file=None):
"""Log output to `log_file`.
Expand Down Expand Up @@ -303,8 +303,12 @@ class MarkdownReportHandler(logging.StreamHandler):
msg = self.format(record)
stream = self.stream
try:
if (isinstance(msg, unicode) and
getattr(stream, 'encoding', None)):
is_text = isinstance(msg, unicode)
except NameError:
is_text = isinstance(msg, bytes)

try:
if is_text and getattr(stream, 'encoding', None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bytes in Python 3 do not have encoding. Maybe you wanted to compare with isinstance(msg, str)?

Python 3.6.3rc1+ (default, Feb  5 2019, 15:51:57)
[GCC 5.x 20180625 5.5.0+] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> b''.encode()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'bytes' object has no attribute 'encode'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember correctly it was specifically a check to find out whether the attribute was present or not - keep in mind I was maintaining compatibility with both 2 and 3, not just updating to 3.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I mistyped. Corrected: bytes in Python 3 do not have encode(…). str does.

stream.write(msg.encode(stream.encoding))
else:
stream.write(msg)
Expand Down Expand Up @@ -414,6 +418,7 @@ class BenchmarkDoctor(object):
name = measurements['name']
setup, ratio = BenchmarkDoctor._setup_overhead(measurements)
setup = 0 if ratio < 0.05 else setup

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove whitespace-only changes.

runtime = min(
[(result.samples.min - correction) for i_series in
[BenchmarkDoctor._select(measurements, num_iters=i)
Expand Down
6 changes: 5 additions & 1 deletion benchmark/scripts/compare_perf_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ class `ReportFormatter` creates the test comparison report in specified format.
from __future__ import print_function

import argparse
import functools
import re
import sys
from bisect import bisect, bisect_left, bisect_right
from collections import namedtuple
from math import ceil, sqrt

if not hasattr(sys.modules['__builtin__'], 'reduce'):
reduce = functools.reduce

class Sample(namedtuple('Sample', 'i num_iters runtime')):
u"""Single benchmark measurement.
Expand Down Expand Up @@ -185,13 +188,14 @@ def sd(self):
sqrt(self.S_runtime / (self.count - 1)))

@staticmethod
def running_mean_variance((k, M_, S_), x):
def running_mean_variance(kMS, x):
"""Compute running variance, B. P. Welford's method.

See Knuth TAOCP vol 2, 3rd edition, page 232, or
https://www.johndcook.com/blog/standard_deviation/
M is mean, Standard Deviation is defined as sqrt(S/k-1)
"""
k, M_, S_ = kMS
k = float(k + 1)
M = M_ + (x - M_) / k
S = S_ + (x - M_) * (x - M)
Expand Down
6 changes: 5 additions & 1 deletion benchmark/scripts/test_Benchmark_Driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
import time
import unittest

from StringIO import StringIO
try:
from StringIO import StringIO # py2
except ModuleNotFoundError:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: ModuleNotFoundError was introduced in Python 3.6. Python 2 should find StringIO just fine, but maybe you can change this with ImportError (the supertype), which should work in any Python 2 and 3.

(This also happens in more places below)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was getting no such module errors from Python 3 because StringIO is io.StringIO in 3.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I wanted to say is that ModuleNotFoundError was introduced in Python 3.6. ModuleNotFoundError is a subtype of ImportError. It will be more portable to use ImportError, so anything from Python 3.0 to Python 3.6 can import io.StringIO.

from io import StringIO # py3

from imp import load_source

from compare_perf_tests import PerformanceTestResult
Expand Down
6 changes: 5 additions & 1 deletion benchmark/scripts/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
import logging
import sys

from StringIO import StringIO
try:
from StringIO import StringIO # py2
except ModuleNotFoundError:
from io import StringIO # py3

from contextlib import contextmanager


Expand Down
4 changes: 2 additions & 2 deletions stdlib/public/core/IntegerTypes.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
# Utility code for later in this template
#

from __future__ import division
from SwiftIntTypes import all_integer_types, int_max_bits, should_define_truncating_bit_pattern_init
from SwiftFloatingPointTypes import getFtoIBounds

from string import maketrans, capitalize
from itertools import chain

# Number of bits in the Builtin.Word type
Expand Down Expand Up @@ -1651,7 +1651,7 @@ extension ${Self} : Hashable {
return Hasher._hash(
seed: seed,
bytes: UInt64(truncatingIfNeeded: ${U}${Self}(_value)),
count: ${bits / 8})
count: ${bits // 8})
% end
}
}
Expand Down
24 changes: 15 additions & 9 deletions stdlib/public/core/SIMDVectorTypes.swift.gyb
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
%{
from __future__ import division
try:
xrange
except NameError:
xrange = range
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my version I simply change the xrange to range. In Py3, range works as Py2's xrange, and for the usages here, range in Py2 is not generating very long list, and the list is completely iterated. It makes the code easier to read, IMO. The only change was in L156.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to agree; there was some reason I went with this approach that I've forgotten now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, please just use range for these cases; I think there's only like one instance of xrange in this file anyway, it should just be converted to keep the diffs minimal.


from SwiftIntTypes import all_integer_types
word_bits = int(CMAKE_SIZEOF_VOID_P) * 8
storagescalarCounts = [2,4,8,16,32,64]
Expand Down Expand Up @@ -46,9 +52,9 @@ public struct SIMD${n}<Scalar> : SIMD where Scalar: SIMDScalar {

/// Creates a new vector from the given elements.
@_transparent
public init(${', '.join(['_ v' + str(i) + ': Scalar' for i in range(n)])}) {
public init(${', '.join(['_ v' + str(i) + ': Scalar' for i in list(range(n))])}) {
self.init()
% for i in range(n):
% for i in list(range(n)):
self[${i}] = v${i}
% end
}
Expand All @@ -65,7 +71,7 @@ public struct SIMD${n}<Scalar> : SIMD where Scalar: SIMDScalar {
self.init(${', '.join('xyzw'[:n])})
}

% for i in range(n):
% for i in list(range(n)):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the three cases above, list(range(n)) can be just range(n). range returns a list in Python 2, and an iterator in Python 3. Both can be used directly by for … in ….

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, these shouldn't need any changes.

/// The ${ordinalPositions[i]} element of the vector.
@_transparent
public var ${'xyzw'[i]}: Scalar {
Expand All @@ -78,17 +84,17 @@ public struct SIMD${n}<Scalar> : SIMD where Scalar: SIMDScalar {
% if n >= 4:
/// Creates a new vector from two half-length vectors.
@_transparent
public init(lowHalf: SIMD${n/2}<Scalar>, highHalf: SIMD${n/2}<Scalar>) {
public init(lowHalf: SIMD${n//2}<Scalar>, highHalf: SIMD${n//2}<Scalar>) {
self.init()
self.lowHalf = lowHalf
self.highHalf = highHalf
}

% for (half,indx) in [('low','i'), ('high',str(n/2)+'+i'), ('even','2*i'), ('odd','2*i+1')]:
% for (half,indx) in [('low','i'), ('high',str(n//2)+'+i'), ('even','2*i'), ('odd','2*i+1')]:
/// A half-length vector made up of the ${half} elements of the vector.
public var ${half}Half: SIMD${n/2}<Scalar> {
public var ${half}Half: SIMD${n//2}<Scalar> {
@inlinable get {
var result = SIMD${n/2}<Scalar>()
var result = SIMD${n//2}<Scalar>()
for i in result.indices { result[i] = self[${indx}] }
return result
}
Expand Down Expand Up @@ -184,7 +190,7 @@ extension ${Self} : SIMDScalar {
public typealias SIMDMaskScalar = ${Mask}

% for n in storagescalarCounts:
% bytes = n * self_type.bits / 8
% bytes = n * self_type.bits // 8
/// Storage for a vector of ${spelledNumbers[n]} integers.
@_fixed_layout
@_alignment(${bytes if bytes <= 16 else 16})
Expand Down Expand Up @@ -229,7 +235,7 @@ extension ${Self} : SIMDScalar {
public typealias SIMDMaskScalar = Int${bits}

% for n in storagescalarCounts:
% bytes = n * bits / 8
% bytes = n * bits // 8
/// Storage for a vector of ${spelledNumbers[n]} floating-point values.
@_fixed_layout
@_alignment(${bytes if bytes <= 16 else 16})
Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/core/Tuple.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public func >=(lhs: (), rhs: ()) -> Bool {
% equatableTypeParams = ", ".join(["{} : Equatable".format(c) for c in typeParams])

% originalTuple = "(\"a\", {})".format(", ".join(map(str, range(1, arity))))
% greaterTuple = "(\"a\", {})".format(", ".join(map(str, range(1, arity - 1) + [arity])))
% greaterTuple = "(\"a\", {})".format(", ".join(map(str, list(range(1, arity - 1)) + [arity])))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need this change, either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python 3 definitely went bananas without this change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this change is needed. In Python 3, range will return an iterable, which cannot be added a list. The list(…) transforms the iterable into a list, which will work.

Copy link

@tyoc213 tyoc213 Apr 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because if you enter arity=3; "(\"a\", {})".format(", ".join(map(str, range(1, arity - 1) + [arity]))) to an interpreter it will cause TypeError: unsupported operand type(s) for +: 'range' and 'list' also https://stackoverflow.com/a/47472763 which leads to xrange() was renamed to range() in Python 3 and range(...) replaced with list(range(...))


/// Returns a Boolean value indicating whether the corresponding components of
/// two tuples are equal.
Expand Down
2 changes: 1 addition & 1 deletion test/Driver/response-file-merge-modules.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %{python} -c 'for i in range(500001): print "-DTEST_" + str(i)' > %t.resp
// RUN: %{python} -c 'for i in range(500001): print("-DTEST_{}".format(i))' > %t.resp
// RUN: %swiftc_driver -driver-print-jobs -module-name merge -emit-module %S/Inputs/main.swift %S/Inputs/lib.swift @%t.resp 2>&1 > %t.jobs.txt
// RUN: %FileCheck %s < %t.jobs.txt -check-prefix=MERGE

Expand Down
4 changes: 2 additions & 2 deletions test/Driver/response-file.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// RUN: %target-build-swift -typecheck @%t.0.resp %s 2>&1 | %FileCheck %s -check-prefix=SHORT
// SHORT: warning: result of call to 'abs' is unused

// RUN: %{python} -c 'for a in ["A", "B", "C", "D"]: print "-DTEST1" + a' > %t.1.resp
// RUN: %{python} -c 'for a in ["A", "B", "C", "D"]: print("-DTEST1{}".format(a))' > %t.1.resp
// RUN: %target-build-swift -typecheck @%t.1.resp %s 2>&1 | %FileCheck %s -check-prefix=MULTIPLE
// MULTIPLE: warning: result of call to 'abs' is unused

Expand All @@ -24,7 +24,7 @@
// RUN: %target-build-swift -typecheck @%t.4B.resp 2>&1 | %FileCheck %s -check-prefix=RECURSIVE
// RECURSIVE: warning: result of call to 'abs' is unused

// RUN: %{python} -c 'for i in range(500001): print "-DTEST5_" + str(i)' > %t.5.resp
// RUN: %{python} -c 'for i in range(500001): print("-DTEST5_{}".format(i))' > %t.5.resp
// RUN: %target-build-swift -typecheck @%t.5.resp %s 2>&1 | %FileCheck %s -check-prefix=LONG
// LONG: warning: result of call to 'abs' is unused
// RUN: %empty-directory(%t/tmp)
Expand Down
2 changes: 1 addition & 1 deletion test/lit.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -1120,7 +1120,7 @@ source_compiler_rt_libs(compiler_rt_dir)

def check_runtime_libs(features_to_check):
for lib in config.compiler_rt_libs:
for (libname, feature) in features_to_check.iteritems():
for (libname, feature) in features_to_check.items():
if lib.startswith("libclang_rt." + libname + "_"):
config.available_features.add(feature)

Expand Down
12 changes: 6 additions & 6 deletions test/swift_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __init__(self, coverage_mode=None, execute_external=True):
self.coverage_mode = coverage_mode
self.skipped_tests = set()

def before_test(self, test, litConfig):
def before_test(self, test, litconfig):
if self.coverage_mode:
# FIXME: The compiler crashers run so fast they fill up the
# merger's queue (and therefore the build bot's disk)
Expand All @@ -53,12 +53,12 @@ def before_test(self, test, litConfig):
os.path.join(test.config.swift_test_results_dir,
"swift-%4m.profraw")

def after_test(self, test, litConfig, result):
def after_test(self, test, litconfig, result):
if test.getSourcePath() in self.skipped_tests:
self.skipped_tests.remove(test.getSourcePath())
return result

def execute(self, test, litConfig):
self.before_test(test, litConfig)
result = super(SwiftTest, self).execute(test, litConfig)
return self.after_test(test, litConfig, result)
def execute(self, test, litconfig):
self.before_test(test, litconfig)
result = super(SwiftTest, self).execute(test, litconfig)
return self.after_test(test, litconfig, result)
8 changes: 4 additions & 4 deletions unittests/Reflection/RemoteMirrorInterop/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@
for i in range(len(swiftcs) + 1):
for localMirrorlibs in itertools.combinations(mirrorlibs, i):
for i, arg in enumerate(absoluteArgs):
print 'Testing', arg, 'with mirror libs:'
print('Testing {} with mirror libs:'.format(arg))
for l in localMirrorlibs:
print '\t', l
callArgs = ['/tmp/test']
dylibPath = os.path.join('/tmp', 'libtest' + str(i) + '.dylib')
callArgs.append(dylibPath)
callArgs += list(localMirrorlibs)
print ' '.join(callArgs)
print(' '.join(callArgs))
subprocess.call(callArgs)
print 'DONE'
print ''
print('DONE')
print('')
print localMirrorlibs
2 changes: 1 addition & 1 deletion utils/PathSanitizingFileCheck
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ constants.""")

p = subprocess.Popen(
[args.file_check_path] + unknown_args, stdin=subprocess.PIPE)
stdout, stderr = p.communicate(stdin)
stdout, stderr = p.communicate(stdin.encode('utf-8'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My changes in PathSanitizingFileCheck are a little bit different.

I found out that when invoked from lit.py, the environment is reset. In Linux this means that my LANG variable gets lost, and the encoding seems to be us-ascii (with another more explicit name, but us-ascii). To avoid hardcoding utf-8 when communicating with the terminal, I arrived to another solution (basically use binary to communicate with the console). I think at some point I did have this, but it broke in some other case.

if stdout is not None:
print(stdout)
if stderr is not None:
Expand Down
5 changes: 4 additions & 1 deletion utils/bug_reducer/bug_reducer/swift_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import os
import subprocess

import subprocess_utils
try:
from . import subprocess_utils
except ValueError:
import subprocess_utils
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a relative import, Python 2 and 3 will work with this change:

from __future__ import absolute_import
from . import subprocess_utils

In #23038 I use this trick to change all this relative imports that look absolute to Python 3 everywhere.


DRY_RUN = False
SQUELCH_STDERR = True
Expand Down
5 changes: 3 additions & 2 deletions utils/bug_reducer/tests/test_funcbugreducer.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ def setUp(self):
self.module_cache = os.path.join(self.tmp_dir, 'module_cache')
self.sdk = subprocess.check_output(['xcrun', '--sdk', 'macosx',
'--toolchain', 'Default',
'--show-sdk-path']).strip("\n")
'--show-sdk-path']
).decode('utf-8').strip("\n")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the subprocess module is returning byte strings, if you can suppose Python 2.7 (which I hope we can), you can do simply .strip(b"\n") and it will work in Python 3. That avoids hardcoding the encoding of the terminal (I noted above that it might not be compatible with UTF-8 in some cases).

(This happens a couple more times below).

self.tools = swift_tools.SwiftTools(self.build_dir)
self.passes = ['--pass=-bug-reducer-tester']

Expand Down Expand Up @@ -111,7 +112,7 @@ def test_basic(self):
'--extra-silopt-arg=-bug-reducer-tester-failure-kind=opt-crasher'
]
args.extend(self.passes)
output = self.run_check_output(args).split("\n")
output = self.run_check_output(args).decode('utf-8').split("\n")
self.assertTrue("*** Successfully Reduced file!" in output)
self.assertTrue("*** Final Functions: " +
"$s9testbasic6foo413yyF" in output)
Expand Down
11 changes: 6 additions & 5 deletions utils/bug_reducer/tests/test_optbugreducer.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ def setUp(self):
self.module_cache = os.path.join(self.tmp_dir, 'module_cache')
self.sdk = subprocess.check_output(['xcrun', '--sdk', 'macosx',
'--toolchain', 'Default',
'--show-sdk-path']).strip("\n")
'--show-sdk-path']
).decode('utf-8').strip("\n")
self.tools = swift_tools.SwiftTools(self.build_dir)
json_data = json.loads(subprocess.check_output(
[self.tools.sil_passpipeline_dumper, '-Performance']))
Expand All @@ -54,7 +55,7 @@ def setUp(self):
for z in y:
self.passes.append('--pass=-' + z[1])
random.seed(0xf487c07f)
random.shuffle(self.passes)
random.shuffle(self.passes, random=random.random)
self.passes.insert(random.randint(0, len(self.passes)),
'--pass=-bug-reducer-tester')

Expand Down Expand Up @@ -101,7 +102,7 @@ def test_basic(self):
'--extra-arg=-bug-reducer-tester-failure-kind=opt-crasher'
]
args.extend(self.passes)
output = subprocess.check_output(args).split("\n")
output = subprocess.check_output(args).decode('utf-8').split("\n")
self.assertTrue('*** Found miscompiling passes!' in output)
self.assertTrue('*** Final Passes: --bug-reducer-tester' in output)
re_end = 'testoptbugreducer_testbasic_initial'
Expand Down Expand Up @@ -130,7 +131,7 @@ def test_suffix_in_need_of_prefix(self):
'--extra-arg=-bug-reducer-tester-failure-kind=opt-crasher'
]
args.extend(self.passes)
output = subprocess.check_output(args).split("\n")
output = subprocess.check_output(args).decode('utf-8').split("\n")
self.assertTrue('*** Found miscompiling passes!' in output)
self.assertTrue('*** Final Passes: --bug-reducer-tester' in output)
re_end = 'testoptbugreducer_testsuffixinneedofprefix_initial'
Expand Down Expand Up @@ -160,7 +161,7 @@ def test_reduce_function(self):
'--reduce-sil'
]
args.extend(self.passes)
output = subprocess.check_output(args).split("\n")
output = subprocess.check_output(args).decode('utf-8').split("\n")
self.assertTrue('*** Found miscompiling passes!' in output)
self.assertTrue(
'*** Final Functions: $s18testreducefunction6foo413yyF' in output)
Expand Down
Loading