Skip to content
Open
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
6 changes: 4 additions & 2 deletions dropbox/content_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,10 @@ def read(self, *args):
return b

def write(self, b):
self._hasher.update(b)
return self._f.write(b)
written = self._f.write(b)
if written:
self._hasher.update(b[:written])
return written

def __iter__(self):
return self
Expand Down
26 changes: 26 additions & 0 deletions test/unit/test_content_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,32 @@ def test_write_hashes_passthrough(self):
assert out.getvalue() == data
assert hasher.hexdigest() == content_hash(data)

@pytest.mark.parametrize("written", [0, 3, None])
def test_write_hashes_only_accepted_bytes(self, written):
class ShortWriter(io.BytesIO):
def write(self, data):
if written is not None:
super().write(data[:written])
return written

hasher = DropboxContentHasher()
out = ShortWriter()
wrapped = StreamHasher(out, hasher)
assert wrapped.write(b"partial write") == written
assert hasher.hexdigest() == content_hash(out.getvalue())

def test_failed_write_does_not_change_hash(self):
class FailingWriter(io.BytesIO):
def write(self, data):
raise OSError("write failed")

hasher = DropboxContentHasher()
hasher.update(b"already written")
wrapped = StreamHasher(FailingWriter(), hasher)
with pytest.raises(OSError, match="write failed"):
wrapped.write(b"not written")
assert hasher.hexdigest() == content_hash(b"already written")

def test_readlines_hashes_and_returns_all_lines(self):
lines = [b"first\n", b"second\n"]
hasher = DropboxContentHasher()
Expand Down
Loading