diff --git a/google/resumable_media/_upload.py b/google/resumable_media/_upload.py index e3906aa3..e54cff66 100644 --- a/google/resumable_media/_upload.py +++ b/google/resumable_media/_upload.py @@ -526,7 +526,7 @@ def _prepare_request(self): u'This upload has not been initiated. Please call ' u'initiate() before beginning to transmit chunks.') - start_byte, end_byte, payload, content_range = get_next_chunk( + start_byte, payload, content_range = get_next_chunk( self._stream, self._chunk_size, self._total_bytes) if start_byte != self.bytes_uploaded: msg = _STREAM_ERROR_TEMPLATE.format( @@ -784,14 +784,15 @@ def get_next_chunk(stream, chunk_size, total_bytes): in the ``stream``. Returns: - Tuple[int, int, bytes, str]: Quadruple of: + Tuple[int, bytes, str]: Triple of: * the start byte index - * the end byte index * the content in between the start and end bytes (inclusive) * content range header for the chunk (slice) that has been read Raises: + ValueError: If ``total_bytes == 0`` but ``stream.read()`` yields + non-empty content. ValueError: If there is no data left to consume. This corresponds exactly to the case ``end_byte < start_byte``, which can only occur if ``end_byte == start_byte - 1``. @@ -807,6 +808,13 @@ def get_next_chunk(stream, chunk_size, total_bytes): if num_bytes_read < chunk_size: # We now **KNOW** the total number of bytes. total_bytes = end_byte + 1 + elif total_bytes == 0: + # NOTE: We also expect ``start_byte == 0`` here but don't check + # because ``_prepare_initiate_request()`` requires the + # stream to be at the beginning. + if num_bytes_read != 0: + raise ValueError( + u'Stream specified as empty, but produced non-empty content.') else: if num_bytes_read == 0: raise ValueError( @@ -817,7 +825,7 @@ def get_next_chunk(stream, chunk_size, total_bytes): raise ValueError(msg) content_range = get_content_range(start_byte, end_byte, total_bytes) - return start_byte, end_byte, payload, content_range + return start_byte, payload, content_range def get_content_range(start_byte, end_byte, total_bytes): diff --git a/tests/unit/test__upload.py b/tests/unit/test__upload.py index 9baab335..657d605e 100644 --- a/tests/unit/test__upload.py +++ b/tests/unit/test__upload.py @@ -860,6 +860,19 @@ def test_exhausted_known_size(self): exc_info.match( u'Stream is already exhausted. There is no content remaining.') + def test_exhausted_known_size_zero(self): + stream = io.BytesIO(b'') + answer = _upload.get_next_chunk(stream, 1, 0) + assert answer == (0, b'', 'bytes */0') + + def test_exhausted_known_size_zero_nonempty(self): + stream = io.BytesIO(b'not empty WAT!') + with pytest.raises(ValueError) as exc_info: + _upload.get_next_chunk(stream, 1, 0) + + exc_info.match( + u'Stream specified as empty, but produced non-empty content.') + def test_read_past_known_size(self): data = b'more content than we expected' stream = io.BytesIO(data) @@ -882,10 +895,10 @@ def test_success_known_size(self): result1 = _upload.get_next_chunk(stream, chunk_size, total_bytes) result2 = _upload.get_next_chunk(stream, chunk_size, total_bytes) result3 = _upload.get_next_chunk(stream, chunk_size, total_bytes) - assert result0 == (0, 2, b'012', u'bytes 0-2/10') - assert result1 == (3, 5, b'345', u'bytes 3-5/10') - assert result2 == (6, 8, b'678', u'bytes 6-8/10') - assert result3 == (9, 9, b'9', u'bytes 9-9/10') + assert result0 == (0, b'012', u'bytes 0-2/10') + assert result1 == (3, b'345', u'bytes 3-5/10') + assert result2 == (6, b'678', u'bytes 6-8/10') + assert result3 == (9, b'9', u'bytes 9-9/10') assert stream.tell() == total_bytes def test_success_unknown_size(self): @@ -895,8 +908,8 @@ def test_success_unknown_size(self): # Splits into 4 chunks: abcdef, ghij result0 = _upload.get_next_chunk(stream, chunk_size, None) result1 = _upload.get_next_chunk(stream, chunk_size, None) - assert result0 == (0, chunk_size - 1, b'abcdef', u'bytes 0-5/*') - assert result1 == (chunk_size, len(data) - 1, b'ghij', u'bytes 6-9/10') + assert result0 == (0, b'abcdef', u'bytes 0-5/*') + assert result1 == (chunk_size, b'ghij', u'bytes 6-9/10') assert stream.tell() == len(data) # Do the same when the chunk size evenly divides len(data) @@ -905,8 +918,8 @@ def test_success_unknown_size(self): # Splits into 2 chunks: `data` and empty string result0 = _upload.get_next_chunk(stream, chunk_size, None) result1 = _upload.get_next_chunk(stream, chunk_size, None) - assert result0 == (0, len(data) - 1, data, u'bytes 0-9/*') - assert result1 == (len(data), len(data) - 1, b'', u'bytes */10') + assert result0 == (0, data, u'bytes 0-9/*') + assert result1 == (len(data), b'', u'bytes */10') assert stream.tell() == len(data)