issue #293: Check external diff error
* lib/vclib/__init__.py
(): Don't import unused module 'io'
(ExternalDiffError): New exception.
(_diff_fp.__init__):
- Specify str I/O explicitly in to communicate to subprocess and remove
extra code for Python 2
- Prepare to read from stderr
(_diff_fp.read):
Rename parameter, so as not to override global class name 'bytes'
(_diff_fp.read, _diff_fp.readline):
Check stderr and exit code at EOF on stdout to detect error and
raise ExternalDifferror if it occurs.
diff --git a/lib/vclib/__init__.py b/lib/vclib/__init__.py
index fd81005..040f463 100644
--- a/lib/vclib/__init__.py
+++ b/lib/vclib/__init__.py
@@ -15,7 +15,6 @@
"""
import sys
-import io
import subprocess
import os
import time
@@ -376,6 +375,12 @@
pass
+class ExternalDiffError(Error):
+ def __init__(self, returncode, mess):
+ self.returncode = returncode
+ Error.__init__(self, "Diff terminated with exit code {0:d}: {1}".format(returncode, mess))
+
+
# ======================================================================
# Implementation code used by multiple vclib modules
@@ -427,26 +432,41 @@
if info1 and info2:
args.extend(["-L", self._label(info1), "-L", self._label(info2)])
args.extend([temp1, temp2])
+ # We assume pipe buffer for stderr is enough for diff utility,
+ # otherwise, it may cause deadlock.
self.proc = subprocess.Popen(
- args, stdout=subprocess.PIPE, bufsize=-1, close_fds=(sys.platform != "win32")
+ args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ encoding="utf-8", errors="surrogateescape",
+ bufsize=-1, close_fds=(sys.platform != "win32")
)
- if not isinstance(self.proc.stdout, io.TextIOBase) and isinstance(
- self.proc.stdout, io.BufferedIOBase
- ):
- self.fp = io.TextIOWrapper(self.proc.stdout, encoding="utf-8", errors="surrogateescape")
- else:
- self.fp = self.proc.stdout
- def read(self, bytes):
- return self.fp.read(bytes)
+ def read(self, buf_size):
+ buf = self.proc.stdout.read(buf_size)
+ if buf == "":
+ errs = self.proc.stderr.read()
+ ret = self.proc.poll()
+ if ret not in (None, 0, 1) or errs:
+ if ret is None:
+ ret = -1
+ raise ExternalDiffError(ret, errs)
+ return buf
def readline(self):
- return self.fp.readline()
+ buf = self.proc.stdout.readline()
+ if buf == "":
+ errs = self.proc.stderr.read()
+ ret = self.proc.poll()
+ if ret not in (None, 0, 1) or errs:
+ if ret is None:
+ ret = -1
+ raise ExternalDiffError(ret, errs)
+ return buf
def close(self):
try:
if self.proc:
- self.fp.close()
+ self.proc.stdout.close()
+ self.proc.stderr.close()
ret = self.proc.poll()
if ret is None:
# child process seems to be still running...