def test_get_absolute_piece_indexes(piece_size, files, file, relative_piece_indexes, exp_absolute_indexes, tmp_path): torrent = Torrent(piece_size=piece_size, files=files) tfs = TorrentFileStream(torrent, location=tmp_path) file = [f for f in files if f == file][0] assert tfs.get_absolute_piece_indexes( file, relative_piece_indexes) == exp_absolute_indexes
def test_get_open_file_gets_nonexisting_file(mocker): exists_mock = mocker.patch('os.path.exists', return_value=False) open_mock = mocker.patch('__main__.open') torrent = Torrent(piece_size=123, files=(File('a', 1), File('b', 2), File('c', 3))) tfs = TorrentFileStream(torrent, location='foo/path') assert tfs._get_open_file('b') is None assert exists_mock.call_args_list == [call('foo/path/b')] assert open_mock.call_args_list == []
def test_get_file_size_from_fs_gets_nonexisting_file(mocker): torrent = Torrent(piece_size=123, files=(File('a', 1), File('b', 2), File('c', 3))) tfs = TorrentFileStream(torrent, location='path/to') exists_mock = mocker.patch('os.path.exists', return_value=False) getsize_mock = mocker.patch('os.path.getsize', return_value=123456) assert tfs._get_file_size_from_fs('b') is None assert exists_mock.call_args_list == [call('path/to/b')] assert getsize_mock.call_args_list == []
def test_get_file_size_from_fs_gets_private_file(mocker): torrent = Torrent(piece_size=123, files=(File('a', 1), File('b', 2), File('c', 3))) tfs = TorrentFileStream(torrent, location='path/to') exists_mock = mocker.patch('os.path.exists', return_value=True) getsize_mock = mocker.patch('os.path.getsize', side_effect=PermissionError('Size is secret')) assert tfs._get_file_size_from_fs('b') is None assert exists_mock.call_args_list == [call('path/to/b')] assert getsize_mock.call_args_list == [call('path/to/b')]
def test_get_open_file_opens_file_only_once(mocker): exists_mock = mocker.patch('os.path.exists', return_value=True) fh1, fh2 = (Mock(), Mock()) open_mock = mocker.patch('builtins.open', side_effect=(fh1, fh2)) torrent = Torrent(piece_size=123, files=(File('a', 1), File('b', 2), File('c', 3))) tfs = TorrentFileStream(torrent, location='foo/path') for _ in range(5): assert tfs._get_open_file('b') == fh1 assert exists_mock.call_args_list == [call('foo/path/b')] assert open_mock.call_args_list == [call('foo/path/b', 'rb')]
def test_get_piece_raises_if_piece_index_is_out_of_bounds( piece_size, files, piece_index, exp_min_piece_index, exp_max_piece_index, tmp_path): torrent = Torrent(piece_size=piece_size, files=files) tfs = TorrentFileStream(torrent, location=tmp_path) with pytest.raises( ValueError, match= rf'^piece_index must be in range 0 - {exp_max_piece_index}: {piece_index}$' ): tfs.get_piece(piece_index)
def test_get_piece_hash_from_unreadable_piece(mocker): torrent = Torrent(piece_size=123, files=(File('a', 1), File('b', 2), File('c', 3))) tfs = TorrentFileStream(torrent, location='foo/path') get_piece_mock = mocker.patch.object(tfs, 'get_piece', return_value=None) sha1_mock = mocker.patch( 'hashlib.sha1', return_value=Mock(digest=Mock(return_value=b'mock hash'))) assert tfs.get_piece_hash(123) is None assert get_piece_mock.call_args_list == [call(123)] assert sha1_mock.call_args_list == []
def test_get_file_at_position(files, position, exp_filename, exp_error, tmp_path): torrent = Torrent(piece_size=4, files=files) tfs = TorrentFileStream(torrent, location=tmp_path) if exp_error: with pytest.raises(ValueError, match=rf'^{re.escape(exp_error)}$'): tfs.get_file_at_position(position) else: file = [f for f in files if f == exp_filename][0] assert tfs.get_file_at_position(position) == file
def test_get_open_file_fails_to_open_file(mocker): exists_mock = mocker.patch('os.path.exists', return_value=True) open_mock = mocker.patch('builtins.open', side_effect=PermissionError('nope')) torrent = Torrent(piece_size=123, files=(File('a', 1), File('b', 2), File('c', 3))) tfs = TorrentFileStream(torrent, location='foo/path') with pytest.raises(errors.ContentError, match=r'^Failed to open foo/path/b: nope$'): tfs._get_open_file('b') assert exists_mock.call_args_list == [call('foo/path/b')] assert open_mock.call_args_list == [call('foo/path/b', 'rb')]
def test_close(mocker): torrent = Torrent(piece_size=123, files=(File('a', 1), File('b', 2), File('c', 3))) tfs = TorrentFileStream(torrent, location='foo/path') mocked_open_files = [Mock(), Mock(), Mock()] tfs._open_files = { f'path/to/{i}': mof for i, mof in enumerate(mocked_open_files) } tfs.close() for mof in mocked_open_files: assert mof.close.call_args_list == [call()] assert tfs._open_files == {}
def test_get_files_for_byte_range(first_byte_indexes, last_byte_indexes, files, exp_files, tmp_path): torrent = Torrent(piece_size=12, files=files) tfs = TorrentFileStream(torrent, location=tmp_path) first_byte_indexes = tuple(first_byte_indexes) last_byte_indexes = tuple(last_byte_indexes) assert first_byte_indexes, first_byte_indexes assert last_byte_indexes, last_byte_indexes for first_byte_index in first_byte_indexes: for last_byte_index in last_byte_indexes: if first_byte_index <= last_byte_index: assert tfs.get_files_for_byte_range( first_byte_index, last_byte_index) == exp_files
def test_get_file_position(tmp_path): torrent = Torrent( piece_size=12, files=( File('foo', size=12), File('bar', size=15), File('baz', size=24), File('bam', size=5), ), ) tfs = TorrentFileStream(torrent, location=tmp_path) assert tfs.get_file_position(torrent.files[0]) == 0 assert tfs.get_file_position(torrent.files[1]) == 12 assert tfs.get_file_position(torrent.files[2]) == 27 assert tfs.get_file_position(torrent.files[3]) == 51
def test_get_piece_returns_None_if_file_is_missing(files, missing_files, piece_index, exp_piece, tmp_path): for f in files: if f not in missing_files: print(f'writing {f}: {f.size} bytes: {f.content}') f.write_at(tmp_path) else: print(f'not writing {f}: {f.size} bytes: {f.content}') torrent = Torrent(piece_size=12, files=files) tfs = TorrentFileStream(torrent, location=tmp_path) if exp_piece is None: assert tfs.get_piece(piece_index) is None else: assert isinstance(tfs.get_piece(piece_index), bytes)
def test_get_piece_resets_seek_position_when_reusing_file_handle( piece_size, tmp_path): files = ( File('a', size=12), File('b', size=13), File('c', size=7), File('d', size=16), ) for f in files: print(f'{f}: {f.size} bytes: {f.content}') f.write_at(tmp_path) stream = b''.join(f.content for f in files) print('concatenated stream:', stream) total_size = sum(f.size for f in files) max_piece_index = math.floor((total_size - 1) // piece_size) for piece_index in range(max_piece_index + 1): print('testing piece:', piece_index) start = piece_index * piece_size stop = min(start + piece_size, len(stream)) exp_piece = stream[start:stop] print('exp_piece:', f'[{start}:{stop}]:', exp_piece) exp_piece_length = stop - start assert len(exp_piece) == exp_piece_length torrent = Torrent(piece_size=piece_size, files=files) with TorrentFileStream(torrent, location=tmp_path) as tfs: for i in range(10): piece = tfs.get_piece(piece_index) assert piece == exp_piece
def test_behaviour_as_context_manager(mocker): torrent = Torrent(piece_size=123, files=(File('a', 1), File('b', 2), File('c', 3))) tfs = TorrentFileStream(torrent, location='foo/path') mocker.patch.object(tfs, 'close') assert tfs.close.call_args_list == [] with tfs as x: assert x is tfs assert tfs.close.call_args_list == [] assert tfs.close.call_args_list == [call()]
def test_verify_piece_returns_None_for_nonexisting_files(mocker): torrent = Torrent(piece_size=123, files=(File('a', 1), File('b', 2), File('c', 3))) tfs = TorrentFileStream(torrent, location='foo/path') mocker.patch.object(type(tfs), 'torrent_piece_hashes', PropertyMock(return_value=(b'foo', b'bar', b'baz'), )) mocker.patch.object(tfs, 'get_piece_hash', return_value=None) assert tfs.verify_piece(0) is None assert tfs.verify_piece(1) is None assert tfs.verify_piece(2) is None with pytest.raises(ValueError, match=r'^piece_index must be in range 0 - 2: 3$'): tfs.verify_piece(3)
def test_get_piece_returns_piece_from_file(piece_size, files, piece_index, tmp_path): for f in files: print(f'{f}: {f.size} bytes: {f.content}') f.write_at(tmp_path) stream = b''.join(f.content for f in files) print('concatenated stream:', stream) start = piece_index * piece_size stop = min(start + piece_size, len(stream)) exp_piece = stream[start:stop] print('exp_piece:', f'[{start}:{stop}]:', exp_piece) exp_piece_length = stop - start assert len(exp_piece) == exp_piece_length torrent = Torrent(piece_size=piece_size, files=files) with TorrentFileStream(torrent, location=tmp_path) as tfs: piece = tfs.get_piece(piece_index) assert piece == exp_piece
def test_torrent_piece_hashes(mocker): torrent = Torrent(piece_size=123, files=(File('a', 1), File('b', 2), File('c', 3))) tfs = TorrentFileStream(torrent, location='foo/path') mocker.patch.object( tfs, '_torrent', Mock( metainfo={ 'info': { 'pieces': (b'01234567890123456789' b'abcdefghijklmnopqrst' b'ABCDEFGHIJKLMNOPQRST') } })) assert tfs.torrent_piece_hashes == ( b'01234567890123456789', b'abcdefghijklmnopqrst', b'ABCDEFGHIJKLMNOPQRST', )
def test_get_piece_returns_None_if_file_size_is_wrong(piece_size, files, contents, piece_index, exp_piece, tmp_path): for f in files: content = contents.get(f, f.content) print( f'{f}: {f.size} bytes: {content}, real size: {len(content)} bytes') f.write_at(tmp_path, content) if exp_piece is not None: stream = b''.join(f.content for f in files) print('concatenated stream:', stream) start = piece_index * piece_size stop = min(start + piece_size, len(stream)) exp_piece = stream[start:stop] print('exp_piece:', f'[{start}:{stop}]:', exp_piece) exp_piece_length = stop - start assert len(exp_piece) == exp_piece_length torrent = Torrent(piece_size=piece_size, files=files) with TorrentFileStream(torrent, location=tmp_path) as tfs: assert tfs.get_piece(piece_index) == exp_piece
def test_get_piece_indexes_for_file(files, exp_piece_indexes, tmp_path): torrent = Torrent(piece_size=12, files=files) tfs = TorrentFileStream(torrent, location=tmp_path) for filename, exp_indexes in exp_piece_indexes.items(): file = [f for f in torrent.files if f == filename][0] assert tfs.get_piece_indexes_for_file(file) == exp_indexes