def test_clone(user_file): size = user_file.sector_size * 2 with io.open(user_file.path, "wb") as f: f.write(b"x" * size) with file.open(user_file.url, "r+", sparse=True) as a, \ a.clone() as b, \ util.aligned_buffer(user_file.sector_size) as buf: # Backends are identical when created. assert a.size() == b.size() assert a.tell() == b.tell() assert a.block_size == b.block_size # Operations on one backend do not affect the other. buf[:] = b"y" * len(buf) a.write(buf) assert a.tell() == len(buf) assert b.tell() == 0 # But both expose the same file contents. buf[:] = b"\0" * len(buf) b.readinto(buf) assert buf[:] == b"y" * len(buf)
def test_dirty(nbd_server): nbd_server.start() with nbd.open(nbd_server.url, "r+") as b: # backend created clean assert not b.dirty # write and zero dirty the backend b.write(b"01234") assert b.dirty b.flush() assert not b.dirty b.zero(5) assert b.dirty b.flush() assert not b.dirty # readinto, seek do not affect dirty. b.seek(0) assert not b.dirty with closing(util.aligned_buffer(10)) as buf: b.readinto(buf) assert not b.dirty
def test_read_seek(): src = memory.Backend("r", bytearray(b"0123456789")) src.seek(8) dst = io.BytesIO() with util.aligned_buffer(32) as buf: op = ops.Read(src, dst, buf, 5) op.run() assert dst.getvalue() == b"01234"
def test_write_flush(extra, dirty): size = 4096 dst = memory.Backend("r+", bytearray(b"a" * size)) src = io.BytesIO(b"b" * size) with util.aligned_buffer(4096) as buf: op = ops.Write(dst, src, buf, size, **extra) op.run() assert dst.dirty == dirty
def test_open_read_only(user_file): with io.open(user_file.path, "wb") as f: f.write(b"x" * user_file.sector_size) with file.open(user_file.url) as f, \ closing(util.aligned_buffer(user_file.sector_size)) as buf: assert f.readable() assert not f.writable() f.readinto(buf) assert buf[:] == b"x" * user_file.sector_size
def test_readinto(user_file): with io.open(user_file.path, "wb") as f: f.write(b"a" * 4096) with file.open(user_file.url) as f, \ closing(util.aligned_buffer(4096)) as buf: n = f.readinto(buf) assert n == len(buf) assert f.tell() == len(buf) assert buf[:] == b"a" * 4096
def test_write_unbuffered_stream_partial_content(user_file): chunks = [b"a" * 8192, b"b" * 42, b"c" * (8192 - 42)] src = util.UnbufferedStream(chunks) size = sum(len(c) for c in chunks) with file.open(user_file.url, "r+") as dst, \ util.aligned_buffer(1024**2) as buf: op = ops.Write(dst, src, buf, size + 1) with pytest.raises(errors.PartialContent): op.run()
def test_zeroout_end(loop_device): with util.open(loop_device, "r+") as f: ioutil.blkzeroout(f.fileno(), BLOCKSIZE * 2, BLOCKSIZE) with util.open(loop_device, "r") as f: buf = util.aligned_buffer(BLOCKSIZE * 3) with closing(buf): f.readinto(buf) assert buf[:-BLOCKSIZE] == b"x" * BLOCKSIZE * 2 assert buf[-BLOCKSIZE:] == b"\0" * BLOCKSIZE
def test_zero_middle(nbd_server, sparse): nbd_server.start() with nbd.open(nbd_server.url, "r+", sparse=sparse) as b: b.write(b"xxxxxxxxxxxx") b.seek(4) assert b.zero(4) == 4 with closing(util.aligned_buffer(12)) as buf: b.seek(0) assert b.readinto(buf) == 12 assert buf[:] == b"xxxx\x00\x00\x00\x00xxxx"
def test_write_seek(): dst = memory.Backend("r+", bytearray(b"a" * 10)) dst.seek(8) src = io.BytesIO(b"b" * 5) with util.aligned_buffer(32) as buf: op = ops.Write(dst, src, buf, 5) op.run() dst.seek(0) b = bytearray(11) n = dst.readinto(b) assert n == 10 assert b == b"bbbbbaaaaa\0"
def test_read_partial_content(user_file, offset, size): with io.open(user_file.path, "wb") as f: f.truncate(offset + size - 1) dst = io.BytesIO() with file.open(user_file.url, "r") as src, \ util.aligned_buffer(1024**2) as buf: op = ops.Read(src, dst, buf, size, offset=offset) with pytest.raises(errors.PartialContent) as e: op.run() assert e.value.requested == size assert e.value.available == size - 1
def test_write_aligned_at_end(user_file): with io.open(user_file.path, "wb") as f: f.write(b"a" * 8192) with file.open(user_file.url, "r+") as f, \ closing(util.aligned_buffer(8192)) as buf: buf.write(b"b" * 8192) f.seek(4096) n = f.write(buf) assert n == len(buf) assert f.tell() == 4096 + len(buf) with io.open(user_file.path, "rb") as f: assert f.read(4096) == b"a" * 4096 assert f.read() == b"b" * 8192
def test_open_read_write(user_file): with io.open(user_file.path, "wb") as f: f.write(b"a" * user_file.sector_size) with file.open(user_file.url, "r+") as f, \ closing(util.aligned_buffer(user_file.sector_size)) as buf: assert f.readable() assert f.writable() f.readinto(buf) buf[:] = b"b" * user_file.sector_size f.seek(0) f.write(buf) with io.open(user_file.path, "rb") as f: assert f.read() == b"b" * user_file.sector_size
def test_write_partial_content(user_file, offset, size): with io.open(user_file.path, "wb") as f: f.truncate(size + offset) src = io.BytesIO(b"x" * (size - 1)) with file.open(user_file.url, "r+") as dst, \ util.aligned_buffer(1024**2) as buf: op = ops.Write(dst, src, buf, size, offset=offset) with pytest.raises(errors.PartialContent) as e: op.run() assert e.value.requested == size assert e.value.available == size - 1
def test_fallocate_zero_end(tmpdir, mode): path = str(tmpdir.join("file")) with open(path, "wb") as f: f.write(b"x" * BLOCKSIZE * 3) buf = util.aligned_buffer(BLOCKSIZE * 3) with closing(buf), util.open(path, "r+") as f: try_fallocate(f.fileno(), mode, BLOCKSIZE * 2, BLOCKSIZE) n = f.readinto(buf) assert n == BLOCKSIZE * 3 assert buf[:-BLOCKSIZE] == b"x" * BLOCKSIZE * 2 assert buf[-BLOCKSIZE:] == b"\0" * BLOCKSIZE assert f.readinto(buf) == 0
def loop_device(tmpdir): backing_file = str(tmpdir.join("backing_file")) with util.open(backing_file, "w") as f: buf = util.aligned_buffer(BLOCKSIZE * 3) with closing(buf): buf[:] = b"x" * BLOCKSIZE * 3 f.write(buf) out = subprocess.check_output( ["losetup", "--find", backing_file, "--show"]) try: loop = out.strip().decode("ascii") yield loop finally: subprocess.check_call(["losetup", "--detach", loop])
def test_write_unbuffered_stream(user_file): chunks = [b"a" * 8192, b"b" * 42, b"c" * (8192 - 42)] src = util.UnbufferedStream(chunks) size = sum(len(c) for c in chunks) with file.open(user_file.url, "r+") as dst, \ util.aligned_buffer(1024**2) as buf: op = ops.Write(dst, src, buf, size) op.run() with io.open(user_file.path, "rb") as f: for c in chunks: assert f.read(len(c)) == c assert f.read() == b""
def test_readinto_short_unaligned(user_file): size = 42 buf_size = user_file.sector_size with io.open(user_file.path, "wb") as f: f.write(b"a" * size) with file.open(user_file.url) as f, \ closing(util.aligned_buffer(buf_size)) as buf: n = f.readinto(buf) assert n == size assert f.tell() == size assert buf[:size] == b"a" * size assert buf[size:] == b"\0" * (buf_size - size)
def test_close(nbd_server): nbd_server.start() with nbd.open(nbd_server.url, "r+") as b: pass # Closing twice does not do anything. b.close() # But other operations should fail now with: # socket.error: Bad file descriptor with pytest.raises(IOError): b.write("more") with pytest.raises(IOError): with closing(util.aligned_buffer(100)) as buf: b.readinto(buf)
def test_open_writeonly(nbd_server): nbd_server.start() with nbd.open(nbd_server.url, "w") as b: assert not b.readable() assert b.writable() data = b"data" b.write(data) assert b.tell() == len(data) with pytest.raises(IOError): with closing(util.aligned_buffer(100)) as buf: b.readinto(buf) b.flush()
def test_fallocate_punch_hole_after_end(tmpdir): path = str(tmpdir.join("file")) with open(path, "wb") as f: f.write(b"x" * BLOCKSIZE * 3) buf = util.aligned_buffer(BLOCKSIZE * 3) with closing(buf), util.open(path, "r+") as f: # This does not change file contents or size. mode = ioutil.FALLOC_FL_PUNCH_HOLE | ioutil.FALLOC_FL_KEEP_SIZE try_fallocate(f.fileno(), mode, BLOCKSIZE * 3, BLOCKSIZE) n = f.readinto(buf) assert n == BLOCKSIZE * 3 assert buf[:] == b"x" * BLOCKSIZE * 3 assert f.readinto(buf) == 0
def test_read_full(user_file, offset, size, trailer): data = b"b" * size with io.open(user_file.path, "wb") as f: f.write(b"a" * offset) f.write(data) f.write(b"c" * trailer) dst = io.BytesIO() with file.open(user_file.url, "r") as src, \ util.aligned_buffer(1024**2) as buf: op = ops.Read(src, dst, buf, size, offset=offset) op.run() assert dst.getvalue() == data
def test_fallocate_zero_after_end(tmpdir): path = str(tmpdir.join("file")) with open(path, "wb") as f: f.write(b"x" * BLOCKSIZE * 3) buf = util.aligned_buffer(BLOCKSIZE * 4) with closing(buf), util.open(path, "r+") as f: # Will allocate more space that will return zeros when read. mode = ioutil.FALLOC_FL_ZERO_RANGE try_fallocate(f.fileno(), mode, BLOCKSIZE * 3, BLOCKSIZE) n = f.readinto(buf) assert n == BLOCKSIZE * 4 assert buf[:-BLOCKSIZE] == b"x" * BLOCKSIZE * 3 assert buf[-BLOCKSIZE:] == b"\0" * BLOCKSIZE assert f.readinto(buf) == 0
def test_write_no_size(user_file, offset, size): with io.open(user_file.path, "wb") as f: f.truncate(offset + size) src = io.BytesIO(b"x" * size) with file.open(user_file.url, "r+") as dst, \ util.aligned_buffer(1024**2) as buf: op = ops.Write(dst, src, buf, offset=offset) op.run() with io.open(user_file.path, "rb") as f: assert f.read(offset) == b"\0" * offset assert f.read(size) == src.getvalue() file_size = os.path.getsize(user_file.path) trailer = file_size - offset - size assert file_size % user_file.sector_size == 0 assert f.read() == b"\0" * trailer
def test_close(nbd_server): nbd_server.start() with nbd.open(nbd_server.url, "r+") as b: pass # Closing twice does not do anything. b.close() # But other operations should fail. error = "Operation on closed backend" with pytest.raises(ValueError) as e: b.write("more") assert str(e.value) == error with pytest.raises(ValueError) as e: with closing(util.aligned_buffer(100)) as buf: b.readinto(buf) assert str(e.value) == error
def test_open_read_write(nbd_server): nbd_server.start() with nbd.open(nbd_server.url, "r+") as b: assert b.readable() assert b.writable() data = b"data" b.write(data) assert b.tell() == len(data) b.zero(4) size = len(data) + 4 assert b.tell() == size with closing(util.aligned_buffer(size)) as buf: b.seek(0) assert b.readinto(buf) == size assert buf[:] == data + b"\0" * 4 b.flush()
def test_write_inside(user_file, offset, size): trailer = 8192 with io.open(user_file.path, "wb") as f: f.truncate(offset + size + trailer) src = io.BytesIO(b"x" * size) with file.open(user_file.url, "r+") as dst, \ util.aligned_buffer(1024**2) as buf: op = ops.Write(dst, src, buf, size, offset=offset) op.run() with io.open(user_file.path, "rb") as f: # Nothing is written before offset. assert f.read(offset) == b"\0" * offset # All data was written. assert f.read(size) == src.getvalue() # Nothing was written after offset + size, and file size is not # modified. assert f.read() == b"\0" * trailer
def test_open_readonly(nbd_server): nbd_server.read_only = True nbd_server.start() with nbd.open(nbd_server.url) as b: assert b.readable() assert not b.writable() with pytest.raises(IOError): b.write(b"data") assert b.tell() == 0 with pytest.raises(IOError): b.zero(4) assert b.tell() == 0 with closing(util.aligned_buffer(100)) as buf: buf.write(b"x" * 100) assert b.readinto(buf) == len(buf) assert buf[:] == b"\0" * len(buf) b.flush()
def test_close(tmpurl): with file.open(tmpurl, "r+") as b: pass # Closing twice does nothing. b.close() # But other oprations should fail. error = "Operation on closed backend" with closing(util.aligned_buffer(4096)) as buf: with pytest.raises(ValueError) as e: b.write(buf) assert str(e.value) == error with pytest.raises(ValueError) as e: b.seek(0) assert str(e.value) == error with pytest.raises(ValueError): b.readinto(buf) assert str(e.value) == error
def test_dirty(user_file): # backend created clean with file.open(user_file.url, "r+", sparse=True) as f: assert not f.dirty buf = util.aligned_buffer(4096) with closing(buf): # write ans zero dirty the backend buf.write(b"x" * 4096) f.write(buf) assert f.dirty f.flush() assert not f.dirty f.zero(4096) assert f.dirty f.flush() assert not f.dirty # readinto, seek do not affect dirty. f.seek(0) assert not f.dirty f.readinto(buf) assert not f.dirty