# HG changeset patch # User Marcin Kuzminski # Date 2018-12-10 13:16:37 # Node ID 0d6e5c13c18045075ca9802cb55a4f8a2390f1e8 # Parent bbf50eecb368df812f7806faa86a531897805dd3 # Parent 6b4cdb499b0a0dd715922098eb0883becad5685c release: Merge default into stable for release preparation diff --git a/.bumpversion.cfg b/.bumpversion.cfg --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 4.14.1 +current_version = 4.15.0 message = release: Bump version {current_version} to {new_version} [bumpversion:file:vcsserver/VERSION] diff --git a/.release.cfg b/.release.cfg --- a/.release.cfg +++ b/.release.cfg @@ -5,12 +5,10 @@ done = false done = true [task:fixes_on_stable] -done = true [task:pip2nix_generated] -done = true [release] -state = prepared -version = 4.14.1 +state = in_progress +version = 4.15.0 diff --git a/pkgs/overlays.nix b/pkgs/overlays.nix --- a/pkgs/overlays.nix +++ b/pkgs/overlays.nix @@ -1,17 +1,19 @@ self: super: { # bump GIT version git = super.lib.overrideDerivation super.git (oldAttrs: { - name = "git-2.17.2"; + name = "git-2.19.1"; src = self.fetchurl { - url = "https://www.kernel.org/pub/software/scm/git/git-2.17.2.tar.xz"; - sha256 = "1ghljlxmyqphx13qspy382cpl2pbkbwbhqm7w7z57r9mkhswx668"; + url = "https://www.kernel.org/pub/software/scm/git/git-2.19.1.tar.xz"; + sha256 = "1dfv43lmdnxz42504jc89sihbv1d4d6kgqcz3c5ji140kfm5cl1l"; }; + # patches come from: https://github.com/NixOS/nixpkgs/tree/master/pkgs/applications/version-management/git-and-tools/git patches = [ ./patches/git/docbook2texi.patch - ./patches/git/symlinks-in-bin.patch ./patches/git/git-sh-i18n.patch ./patches/git/ssh-path.patch + ./patches/git/git-send-email-honor-PATH.patch + ./patches/git/installCheck-path.patch ]; }); diff --git a/pkgs/patches/git/docbook2texi.patch b/pkgs/patches/git/docbook2texi.patch --- a/pkgs/patches/git/docbook2texi.patch +++ b/pkgs/patches/git/docbook2texi.patch @@ -2,29 +2,29 @@ This patch does two things: (1) use the and (2) make sure `gitman.info' isn't produced since it's broken (duplicate node names). -diff -ru git-1.8.4-orig/Documentation/Makefile git-1.8.4/Documentation/Makefile ---- git-1.8.4-orig/Documentation/Makefile 2013-08-23 21:38:43.000000000 +0200 -+++ git-1.8.4/Documentation/Makefile 2013-09-30 14:48:51.532890378 +0200 -@@ -101,7 +101,7 @@ - +diff --git a/Documentation/Makefile b/Documentation/Makefile +--- a/Documentation/Makefile ++++ b/Documentation/Makefile +@@ -122,7 +122,7 @@ + MAKEINFO = makeinfo INSTALL_INFO = install-info -DOCBOOK2X_TEXI = docbook2x-texi +DOCBOOK2X_TEXI = docbook2texi DBLATEX = dblatex - ifndef PERL_PATH - PERL_PATH = /usr/bin/perl -@@ -205,7 +205,7 @@ + ASCIIDOC_DBLATEX_DIR = /etc/asciidoc/dblatex + DBLATEX_COMMON = -p $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.xsl -s $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.sty +@@ -240,7 +240,7 @@ man5: $(DOC_MAN5) man7: $(DOC_MAN7) - + -info: git.info gitman.info +info: git.info - + pdf: user-manual.pdf - -@@ -221,10 +221,9 @@ - + +@@ -256,10 +256,9 @@ + install-info: info $(INSTALL) -d -m 755 $(DESTDIR)$(infodir) - $(INSTALL) -m 644 git.info gitman.info $(DESTDIR)$(infodir) diff --git a/pkgs/patches/git/git-send-email-honor-PATH.patch b/pkgs/patches/git/git-send-email-honor-PATH.patch new file mode 100644 --- /dev/null +++ b/pkgs/patches/git/git-send-email-honor-PATH.patch @@ -0,0 +1,26 @@ +diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt +--- a/Documentation/git-send-email.txt ++++ b/Documentation/git-send-email.txt +@@ -208,8 +208,7 @@ a password is obtained using 'git-credential'. + specify a full pathname of a sendmail-like program instead; + the program must support the `-i` option. Default value can + be specified by the `sendemail.smtpServer` configuration +- option; the built-in default is to search for `sendmail` in +- `/usr/sbin`, `/usr/lib` and $PATH if such program is ++ option; the built-in default is to search in $PATH if such program is + available, falling back to `localhost` otherwise. + + --smtp-server-port=:: +diff --git a/git-send-email.perl b/git-send-email.perl +--- a/git-send-email.perl ++++ b/git-send-email.perl +@@ -944,8 +944,7 @@ if (defined $reply_to) { + } + + if (!defined $smtp_server) { +- my @sendmail_paths = qw( /usr/sbin/sendmail /usr/lib/sendmail ); +- push @sendmail_paths, map {"$_/sendmail"} split /:/, $ENV{PATH}; ++ my @sendmail_paths = map {"$_/sendmail"} split /:/, $ENV{PATH}; + foreach (@sendmail_paths) { + if (-x $_) { + $smtp_server = $_; diff --git a/pkgs/patches/git/installCheck-path.patch b/pkgs/patches/git/installCheck-path.patch new file mode 100644 --- /dev/null +++ b/pkgs/patches/git/installCheck-path.patch @@ -0,0 +1,12 @@ +diff --git a/t/test-lib.sh b/t/test-lib.sh +--- a/t/test-lib.sh ++++ b/t/test-lib.sh +@@ -923,7 +923,7 @@ + then + GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) || + error "Cannot run git from $GIT_TEST_INSTALLED." +- PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR:$PATH ++ PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR/t/helper:$GIT_BUILD_DIR:$PATH + GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH} + else # normal case, use ../bin-wrappers only unless $with_dashes: + git_bin_dir="$GIT_BUILD_DIR/bin-wrappers" diff --git a/pkgs/patches/git/symlinks-in-bin.patch b/pkgs/patches/git/symlinks-in-bin.patch deleted file mode 100644 --- a/pkgs/patches/git/symlinks-in-bin.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff -ru -x '*~' git-1.8.2.1-orig/Makefile git-1.8.2.1/Makefile ---- git-1.8.2.1-orig/Makefile 2013-04-08 00:52:04.000000000 +0200 -+++ git-1.8.2.1/Makefile 2013-04-22 15:46:42.906026940 +0200 -@@ -2319,8 +2319,7 @@ - { test "$$bindir/" = "$$execdir/" || \ - for p in git$X $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \ - $(RM) "$$execdir/$$p" && \ -- test -z "$(NO_INSTALL_HARDLINKS)$(NO_CROSS_DIRECTORY_HARDLINKS)" && \ -- ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \ -+ ln -s "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \ - cp "$$bindir/$$p" "$$execdir/$$p" || exit; \ - done; \ - } && \ diff --git a/pkgs/python-packages.nix b/pkgs/python-packages.nix --- a/pkgs/python-packages.nix +++ b/pkgs/python-packages.nix @@ -154,14 +154,14 @@ self: super: { }; }; "gevent" = super.buildPythonPackage { - name = "gevent-1.3.6"; + name = "gevent-1.3.7"; doCheck = false; propagatedBuildInputs = [ self."greenlet" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/49/13/aa4bb3640b5167fe58875d3d7e65390cdb14f9682a41a741a566bb560842/gevent-1.3.6.tar.gz"; - sha256 = "1ih4k73dqz2zb561hda99vbanja3m6cdch3mgxxn1mla3qwkqhbv"; + url = "https://files.pythonhosted.org/packages/10/c1/9499b146bfa43aa4f1e0ed1bab1bd3209a4861d25650c11725036c731cf5/gevent-1.3.7.tar.gz"; + sha256 = "0b0fr04qdk1p4sniv87fh8z5psac60x01pv054kpgi94520g81iz"; }; meta = { license = [ pkgs.lib.licenses.mit ]; @@ -212,26 +212,26 @@ self: super: { }; }; "hgsubversion" = super.buildPythonPackage { - name = "hgsubversion-1.9.2"; + name = "hgsubversion-1.9.3"; doCheck = false; propagatedBuildInputs = [ self."mercurial" self."subvertpy" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/05/80/3a3cef10dd65e86528ef8d7ac57a41ebc782d0f3c6cfa4fed021aa9fbee0/hgsubversion-1.9.2.tar.gz"; - sha256 = "16490narhq14vskml3dam8g5y3w3hdqj3g8bgm2b0c0i85l1xvcz"; + url = "https://files.pythonhosted.org/packages/a3/53/6d205e641f3e09abcf1ddaed66e5e4b20da22d0145566d440a02c9e35f0d/hgsubversion-1.9.3.tar.gz"; + sha256 = "0nymcjlch8c4zjbncrs30p2nrbylsf25g3h6mr0zzzxr141h3sig"; }; meta = { license = [ pkgs.lib.licenses.gpl1 ]; }; }; "hupper" = super.buildPythonPackage { - name = "hupper-1.3.1"; + name = "hupper-1.4.2"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/cf/4b/467b826a84c8594b81f414b5ab6794e981951dac90ca40abaf9ea1cb36b0/hupper-1.3.1.tar.gz"; - sha256 = "03mf13n6i4dd60wlb9m99ddl4m3lmly70cjp7f82vdkibfl1v6l9"; + url = "https://files.pythonhosted.org/packages/f1/75/1915dc7650b4867fa3049256e24ca8eddb5989998fcec788cf52b9812dfc/hupper-1.4.2.tar.gz"; + sha256 = "16vb9fkiaakdpcp6pn56h3w0dwvm67bxq2k2dv4i382qhqwphdzb"; }; meta = { license = [ pkgs.lib.licenses.mit ]; @@ -502,11 +502,11 @@ self: super: { }; }; "pygments" = super.buildPythonPackage { - name = "pygments-2.2.0"; + name = "pygments-2.3.0"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/71/2a/2e4e77803a8bd6408a2903340ac498cb0a2181811af7c9ec92cb70b0308a/Pygments-2.2.0.tar.gz"; - sha256 = "1k78qdvir1yb1c634nkv6rbga8wv4289xarghmsbbvzhvr311bnv"; + url = "https://files.pythonhosted.org/packages/63/a2/91c31c4831853dedca2a08a0f94d788fc26a48f7281c99a303769ad2721b/Pygments-2.3.0.tar.gz"; + sha256 = "1z34ms51dh4jq4h3cizp7vd1dmsxcbvffkjsd2xxfav22nn6lrl2"; }; meta = { license = [ pkgs.lib.licenses.bsdOriginal ]; @@ -656,7 +656,7 @@ self: super: { }; }; "rhodecode-vcsserver" = super.buildPythonPackage { - name = "rhodecode-vcsserver-4.14.1"; + name = "rhodecode-vcsserver-4.15.0"; buildInputs = [ self."pytest" self."py" @@ -750,11 +750,11 @@ self: super: { }; }; "setuptools" = super.buildPythonPackage { - name = "setuptools-40.4.3"; + name = "setuptools-40.6.2"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/6e/9c/6a003320b00ef237f94aa74e4ad66c57a7618f6c79d67527136e2544b728/setuptools-40.4.3.zip"; - sha256 = "058v6zns4634n4al2nmmvp15j8nrgwn8wjrbdks47wk3vm05gg5c"; + url = "https://files.pythonhosted.org/packages/b0/d1/8acb42f391cba52e35b131e442e80deffbb8d0676b93261d761b1f0ef8fb/setuptools-40.6.2.zip"; + sha256 = "0r2c5hapirlzm34h7pl1lgkm6gk7bcrlrdj28qgsvaqg3f74vfw6"; }; meta = { license = [ pkgs.lib.licenses.mit ]; diff --git a/requirements.txt b/requirements.txt --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ dogpile.cache==0.6.7 dogpile.core==0.4.1 decorator==4.1.2 dulwich==0.13.0 -hgsubversion==1.9.2 +hgsubversion==1.9.3 hg-evolve==8.0.1 mako==1.0.7 markupsafe==1.0.0 @@ -20,7 +20,7 @@ psutil==5.4.7 pyramid==1.9.2 pyramid-mako==1.0.2 -pygments==2.2.0 +pygments==2.3.0 pathlib2==2.3.2 repoze.lru==0.7 simplejson==3.11.1 @@ -34,7 +34,7 @@ zope.deprecation==4.3.0 zope.interface==4.5.0 ## http servers -gevent==1.3.6 +gevent==1.3.7 greenlet==0.4.15 gunicorn==19.9.0 waitress==1.1.0 diff --git a/vcsserver/VERSION b/vcsserver/VERSION --- a/vcsserver/VERSION +++ b/vcsserver/VERSION @@ -1,1 +1,1 @@ -4.14.1 \ No newline at end of file +4.15.0 \ No newline at end of file diff --git a/vcsserver/git.py b/vcsserver/git.py --- a/vcsserver/git.py +++ b/vcsserver/git.py @@ -518,15 +518,6 @@ class GitRemote(object): return repo.get_description() @reraise_safe_exceptions - def get_file_history(self, wire, file_path, commit_id, limit): - repo = self._factory.repo(wire) - include = [commit_id] - paths = [file_path] - - walker = repo.get_walker(include, paths=paths, max_entries=limit) - return [x.commit.id for x in walker] - - @reraise_safe_exceptions def get_missing_revs(self, wire, rev1, rev2, path2): repo = self._factory.repo(wire) LocalGitClient(thin_packs=False).fetch(path2, repo) diff --git a/vcsserver/hg.py b/vcsserver/hg.py --- a/vcsserver/hg.py +++ b/vcsserver/hg.py @@ -417,7 +417,7 @@ class HgRemote(object): raise exceptions.LookupException(e)() @reraise_safe_exceptions - def file_history(self, wire, revision, path, limit): + def node_history(self, wire, revision, path, limit): repo = self._factory.repo(wire) ctx = repo[revision] @@ -439,7 +439,7 @@ class HgRemote(object): return [x for x in history] @reraise_safe_exceptions - def file_history_untill(self, wire, revision, path, limit): + def node_history_untill(self, wire, revision, path, limit): repo = self._factory.repo(wire) ctx = repo[revision] fctx = ctx.filectx(path) diff --git a/vcsserver/hook_utils/__init__.py b/vcsserver/hook_utils/__init__.py --- a/vcsserver/hook_utils/__init__.py +++ b/vcsserver/hook_utils/__init__.py @@ -42,7 +42,7 @@ def install_git_hooks(repo_path, bare, e if not bare: hooks_path = os.path.join(repo_path, '.git', 'hooks') if not os.path.isdir(hooks_path): - os.makedirs(hooks_path, mode=0777) + os.makedirs(hooks_path, mode=0o777) tmpl_post = pkg_resources.resource_string( 'vcsserver', '/'.join( @@ -69,7 +69,7 @@ def install_git_hooks(repo_path, bare, e template = template.replace('_ENV_', executable) template = template.replace('_PATH_', path) f.write(template) - os.chmod(_hook_file, 0755) + os.chmod(_hook_file, 0o755) except IOError: log.exception('error writing hook file %s', _hook_file) else: @@ -89,7 +89,7 @@ def install_svn_hooks(repo_path, executa executable = executable or sys.executable hooks_path = os.path.join(repo_path, 'hooks') if not os.path.isdir(hooks_path): - os.makedirs(hooks_path, mode=0777) + os.makedirs(hooks_path, mode=0o777) tmpl_post = pkg_resources.resource_string( 'vcsserver', '/'.join( @@ -118,7 +118,7 @@ def install_svn_hooks(repo_path, executa template = template.replace('_PATH_', path) f.write(template) - os.chmod(_hook_file, 0755) + os.chmod(_hook_file, 0o755) except IOError: log.exception('error writing hook file %s', _hook_file) else: diff --git a/vcsserver/hooks.py b/vcsserver/hooks.py --- a/vcsserver/hooks.py +++ b/vcsserver/hooks.py @@ -52,7 +52,16 @@ class HooksHttpClient(object): log.error('Connection failed on %s', connection) raise response = connection.getresponse() - return json.loads(response.read()) + + response_data = response.read() + + try: + return json.loads(response_data) + except Exception: + log.exception('Failed to decode hook response json data. ' + 'response_code:%s, raw_data:%s', + response.status, response_data) + raise def _serialize(self, hook_name, extras): data = { diff --git a/vcsserver/http_main.py b/vcsserver/http_main.py --- a/vcsserver/http_main.py +++ b/vcsserver/http_main.py @@ -197,6 +197,28 @@ class WsgiProxy(object): yield msgpack.packb(d) +def not_found(request): + return {'status': '404 NOT FOUND'} + + +class VCSViewPredicate(object): + def __init__(self, val, config): + self.remotes = val + + def text(self): + return 'vcs view method = %s' % (self.remotes.keys(),) + + phash = text + + def __call__(self, context, request): + """ + View predicate that returns true if given backend is supported by + defined remotes. + """ + backend = request.matchdict.get('backend') + return backend in self.remotes + + class HTTPApplication(object): ALLOWED_EXCEPTIONS = ('KeyError', 'URLError') @@ -256,7 +278,7 @@ class HTTPApplication(object): # ensure we have our dir created if not os.path.isdir(default_cache_dir): - os.makedirs(default_cache_dir, mode=0755) + os.makedirs(default_cache_dir, mode=0o755) # exception store cache _string_setting( @@ -279,9 +301,7 @@ class HTTPApplication(object): 1024) def _configure(self): - self.config.add_renderer( - name='msgpack', - factory=self._msgpack_renderer_factory) + self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory) self.config.add_route('service', '/_service') self.config.add_route('status', '/status') @@ -291,23 +311,20 @@ class HTTPApplication(object): self.config.add_route('stream_git', '/stream/git/*repo_name') self.config.add_route('stream_hg', '/stream/hg/*repo_name') - self.config.add_view( - self.status_view, route_name='status', renderer='json') - self.config.add_view( - self.service_view, route_name='service', renderer='msgpack') + self.config.add_view(self.status_view, route_name='status', renderer='json') + self.config.add_view(self.service_view, route_name='service', renderer='msgpack') self.config.add_view(self.hg_proxy(), route_name='hg_proxy') self.config.add_view(self.git_proxy(), route_name='git_proxy') - self.config.add_view( - self.vcs_view, route_name='vcs', renderer='msgpack', - custom_predicates=[self.is_vcs_view]) + self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack', + vcs_view=self._remotes) self.config.add_view(self.hg_stream(), route_name='stream_hg') self.config.add_view(self.git_stream(), route_name='stream_git') - def notfound(request): - return {'status': '404 NOT FOUND'} - self.config.add_notfound_view(notfound, renderer='json') + self.config.add_view_predicate('vcs_view', VCSViewPredicate) + + self.config.add_notfound_view(not_found, renderer='json') self.config.add_view(self.handle_vcs_exception, context=Exception) @@ -543,14 +560,6 @@ class HTTPApplication(object): return _git_stream - def is_vcs_view(self, context, request): - """ - View predicate that returns true if given backend is supported by - defined remotes. - """ - backend = request.matchdict.get('backend') - return backend in self._remotes - def handle_vcs_exception(self, exception, request): _vcs_kind = getattr(exception, '_vcs_kind', '') if _vcs_kind == 'repo_locked': diff --git a/vcsserver/tests/test_subprocessio.py b/vcsserver/tests/test_subprocessio.py --- a/vcsserver/tests/test_subprocessio.py +++ b/vcsserver/tests/test_subprocessio.py @@ -36,8 +36,7 @@ def environ(): def _get_python_args(script): - return [sys.executable, '-c', - 'import sys; import time; import shutil; ' + script] + return [sys.executable, '-c', 'import sys; import time; import shutil; ' + script] def test_raise_exception_on_non_zero_return_code(environ): @@ -48,8 +47,11 @@ def test_raise_exception_on_non_zero_ret def test_does_not_fail_on_non_zero_return_code(environ): args = _get_python_args('sys.exit(1)') - output = ''.join(subprocessio.SubprocessIOChunker( - args, shell=False, fail_on_return_code=False, env=environ)) + output = ''.join( + subprocessio.SubprocessIOChunker( + args, shell=False, fail_on_return_code=False, env=environ + ) + ) assert output == '' @@ -64,49 +66,56 @@ def test_raise_exception_on_stderr(envir def test_does_not_fail_on_stderr(environ): args = _get_python_args('sys.stderr.write("X"); time.sleep(1);') - output = ''.join(subprocessio.SubprocessIOChunker( - args, shell=False, fail_on_stderr=False, env=environ)) + output = ''.join( + subprocessio.SubprocessIOChunker( + args, shell=False, fail_on_stderr=False, env=environ + ) + ) assert output == '' -@pytest.mark.parametrize('size', [1, 10**5]) +@pytest.mark.parametrize('size', [1, 10 ** 5]) def test_output_with_no_input(size, environ): - print type(environ) + print(type(environ)) data = 'X' args = _get_python_args('sys.stdout.write("%s" * %d)' % (data, size)) - output = ''.join(subprocessio.SubprocessIOChunker( - args, shell=False, env=environ)) + output = ''.join(subprocessio.SubprocessIOChunker(args, shell=False, env=environ)) assert output == data * size -@pytest.mark.parametrize('size', [1, 10**5]) +@pytest.mark.parametrize('size', [1, 10 ** 5]) def test_output_with_no_input_does_not_fail(size, environ): data = 'X' - args = _get_python_args( - 'sys.stdout.write("%s" * %d); sys.exit(1)' % (data, size)) - output = ''.join(subprocessio.SubprocessIOChunker( - args, shell=False, fail_on_return_code=False, env=environ)) + args = _get_python_args('sys.stdout.write("%s" * %d); sys.exit(1)' % (data, size)) + output = ''.join( + subprocessio.SubprocessIOChunker( + args, shell=False, fail_on_return_code=False, env=environ + ) + ) - print len(data * size), len(output) + print("{} {}".format(len(data * size), len(output))) assert output == data * size -@pytest.mark.parametrize('size', [1, 10**5]) +@pytest.mark.parametrize('size', [1, 10 ** 5]) def test_output_with_input(size, environ): data = 'X' * size inputstream = io.BytesIO(data) # This acts like the cat command. args = _get_python_args('shutil.copyfileobj(sys.stdin, sys.stdout)') - output = ''.join(subprocessio.SubprocessIOChunker( - args, shell=False, inputstream=inputstream, env=environ)) + output = ''.join( + subprocessio.SubprocessIOChunker( + args, shell=False, inputstream=inputstream, env=environ + ) + ) - print len(data), len(output) + print("{} {}".format(len(data * size), len(output))) assert output == data -@pytest.mark.parametrize('size', [1, 10**5]) +@pytest.mark.parametrize('size', [1, 10 ** 5]) def test_output_with_input_skipping_iterator(size, environ): data = 'X' * size inputstream = io.BytesIO(data) @@ -115,8 +124,9 @@ def test_output_with_input_skipping_iter # Note: assigning the chunker makes sure that it is not deleted too early chunker = subprocessio.SubprocessIOChunker( - args, shell=False, inputstream=inputstream, env=environ) + args, shell=False, inputstream=inputstream, env=environ + ) output = ''.join(chunker.output) - print len(data), len(output) + print("{} {}".format(len(data * size), len(output))) assert output == data