瀏覽代碼

Merge pull request #3789 from shin-/3788-json-splitter-fix

json_splitter: Don't break when buffer contains leading whitespace.
Aanand Prasad 9 年之前
父節點
當前提交
d29f8e1022
共有 4 個文件被更改,包括 37 次插入2 次删除
  1. 2 1
      compose/cli/main.py
  2. 5 0
      compose/errors.py
  3. 13 1
      compose/utils.py
  4. 17 0
      tests/unit/utils_test.py

+ 2 - 1
compose/cli/main.py

@@ -23,6 +23,7 @@ from ..config.environment import Environment
 from ..config.serialize import serialize_config
 from ..config.serialize import serialize_config
 from ..const import DEFAULT_TIMEOUT
 from ..const import DEFAULT_TIMEOUT
 from ..const import IS_WINDOWS_PLATFORM
 from ..const import IS_WINDOWS_PLATFORM
+from ..errors import StreamParseError
 from ..progress_stream import StreamOutputError
 from ..progress_stream import StreamOutputError
 from ..project import NoSuchService
 from ..project import NoSuchService
 from ..project import OneOffFilter
 from ..project import OneOffFilter
@@ -75,7 +76,7 @@ def main():
     except NeedsBuildError as e:
     except NeedsBuildError as e:
         log.error("Service '%s' needs to be built, but --no-build was passed." % e.service.name)
         log.error("Service '%s' needs to be built, but --no-build was passed." % e.service.name)
         sys.exit(1)
         sys.exit(1)
-    except errors.ConnectionError:
+    except (errors.ConnectionError, StreamParseError):
         sys.exit(1)
         sys.exit(1)
 
 
 
 

+ 5 - 0
compose/errors.py

@@ -5,3 +5,8 @@ from __future__ import unicode_literals
 class OperationFailedError(Exception):
 class OperationFailedError(Exception):
     def __init__(self, reason):
     def __init__(self, reason):
         self.msg = reason
         self.msg = reason
+
+
+class StreamParseError(RuntimeError):
+    def __init__(self, reason):
+        self.msg = reason

+ 13 - 1
compose/utils.py

@@ -5,11 +5,15 @@ import codecs
 import hashlib
 import hashlib
 import json
 import json
 import json.decoder
 import json.decoder
+import logging
 
 
 import six
 import six
 
 
+from .errors import StreamParseError
+
 
 
 json_decoder = json.JSONDecoder()
 json_decoder = json.JSONDecoder()
+log = logging.getLogger(__name__)
 
 
 
 
 def get_output_stream(stream):
 def get_output_stream(stream):
@@ -60,13 +64,21 @@ def split_buffer(stream, splitter=None, decoder=lambda a: a):
             yield item
             yield item
 
 
     if buffered:
     if buffered:
-        yield decoder(buffered)
+        try:
+            yield decoder(buffered)
+        except Exception as e:
+            log.error(
+                'Compose tried decoding the following data chunk, but failed:'
+                '\n%s' % repr(buffered)
+            )
+            raise StreamParseError(e)
 
 
 
 
 def json_splitter(buffer):
 def json_splitter(buffer):
     """Attempt to parse a json object from a buffer. If there is at least one
     """Attempt to parse a json object from a buffer. If there is at least one
     object, return it and the rest of the buffer, otherwise return None.
     object, return it and the rest of the buffer, otherwise return None.
     """
     """
+    buffer = buffer.strip()
     try:
     try:
         obj, index = json_decoder.raw_decode(buffer)
         obj, index = json_decoder.raw_decode(buffer)
         rest = buffer[json.decoder.WHITESPACE.match(buffer, index).end():]
         rest = buffer[json.decoder.WHITESPACE.match(buffer, index).end():]

+ 17 - 0
tests/unit/utils_test.py

@@ -15,6 +15,10 @@ class TestJsonSplitter(object):
         data = '{"foo": "bar"}\n  \n{"next": "obj"}'
         data = '{"foo": "bar"}\n  \n{"next": "obj"}'
         assert utils.json_splitter(data) == ({'foo': 'bar'}, '{"next": "obj"}')
         assert utils.json_splitter(data) == ({'foo': 'bar'}, '{"next": "obj"}')
 
 
+    def test_json_splitter_leading_whitespace(self):
+        data = '\n   \r{"foo": "bar"}\n\n   {"next": "obj"}'
+        assert utils.json_splitter(data) == ({'foo': 'bar'}, '{"next": "obj"}')
+
 
 
 class TestStreamAsText(object):
 class TestStreamAsText(object):
 
 
@@ -43,3 +47,16 @@ class TestJsonStream(object):
             [1, 2, 3],
             [1, 2, 3],
             [],
             [],
         ]
         ]
+
+    def test_with_leading_whitespace(self):
+        stream = [
+            '\n  \r\n  {"one": "two"}{"x": 1}',
+            '  {"three": "four"}\t\t{"x": 2}'
+        ]
+        output = list(utils.json_stream(stream))
+        assert output == [
+            {'one': 'two'},
+            {'x': 1},
+            {'three': 'four'},
+            {'x': 2}
+        ]