mirror of
https://github.com/arvidn/libtorrent.git
synced 2026-07-24 13:01:13 -04:00
596 lines
21 KiB
Python
596 lines
21 KiB
Python
import os
|
|
import tempfile
|
|
from typing import AnyStr
|
|
from typing import cast
|
|
from typing import List
|
|
from typing import TYPE_CHECKING
|
|
import unittest
|
|
|
|
import libtorrent as lt
|
|
|
|
from . import lib
|
|
from . import tdummy
|
|
|
|
if TYPE_CHECKING:
|
|
from libtorrent import _Entry
|
|
from libtorrent import load_torrent_limits
|
|
from libtorrent import TorrentFileDict
|
|
from libtorrent import web_seed_entry
|
|
else:
|
|
_Entry, TorrentFileDict, load_torrent_limits = dict, dict, dict
|
|
|
|
|
|
class FileEntryTest(unittest.TestCase):
|
|
def test_attributes(self) -> None:
|
|
if lt.api_version < 2:
|
|
with self.assertWarns(DeprecationWarning):
|
|
fe = lt.file_entry()
|
|
|
|
fe.path = os.path.join("path", "file.txt")
|
|
path = fe.path
|
|
self.assertEqual(path, os.path.join("path", "file.txt"))
|
|
|
|
fe.symlink_path = os.path.join("path", "other.txt")
|
|
self.assertEqual(fe.symlink_path, os.path.join("path", "other.txt"))
|
|
|
|
fe.filehash = lt.sha1_hash(b"a" * 20)
|
|
self.assertEqual(fe.filehash, lt.sha1_hash(b"a" * 20))
|
|
|
|
fe.mtime = 123456
|
|
self.assertEqual(fe.mtime, 123456)
|
|
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertFalse(fe.pad_file)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertFalse(fe.executable_attribute)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertFalse(fe.hidden_attribute)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertFalse(fe.symlink_attribute)
|
|
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(fe.offset, 0)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(fe.size, 0)
|
|
|
|
|
|
class InfoHashTest(unittest.TestCase):
|
|
def test_sha1(self) -> None:
|
|
sha1 = lt.sha1_hash(lib.get_random_bytes(20))
|
|
|
|
ih = lt.info_hash_t(sha1)
|
|
self.assertEqual(ih.get(lt.protocol_version.V1), sha1)
|
|
self.assertEqual(ih.get(lt.protocol_version.V2), lt.sha1_hash())
|
|
self.assertEqual(ih.get_best(), sha1)
|
|
self.assertTrue(ih.has(lt.protocol_version.V1))
|
|
self.assertFalse(ih.has(lt.protocol_version.V2))
|
|
self.assertTrue(ih.has_v1())
|
|
self.assertFalse(ih.has_v2())
|
|
self.assertEqual(ih.v1, sha1)
|
|
self.assertEqual(ih.v2, lt.sha256_hash())
|
|
|
|
self.assertEqual(ih, lt.info_hash_t(sha1))
|
|
self.assertEqual(hash(ih), hash(lt.info_hash_t(sha1)))
|
|
self.assertNotEqual(ih, lt.info_hash_t())
|
|
|
|
def test_sha256(self) -> None:
|
|
sha256 = lt.sha256_hash(lib.get_random_bytes(32))
|
|
sha256_trunc = lt.sha1_hash(sha256.to_bytes()[:20])
|
|
|
|
ih = lt.info_hash_t(sha256)
|
|
self.assertEqual(ih.get(lt.protocol_version.V1), lt.sha1_hash())
|
|
self.assertEqual(ih.get(lt.protocol_version.V2), sha256_trunc)
|
|
self.assertEqual(ih.get_best(), sha256_trunc)
|
|
self.assertFalse(ih.has(lt.protocol_version.V1))
|
|
self.assertTrue(ih.has(lt.protocol_version.V2))
|
|
self.assertFalse(ih.has_v1())
|
|
self.assertTrue(ih.has_v2())
|
|
self.assertEqual(ih.v1, lt.sha1_hash())
|
|
self.assertEqual(ih.v2, sha256)
|
|
|
|
self.assertEqual(ih, lt.info_hash_t(sha256))
|
|
self.assertEqual(hash(ih), hash(lt.info_hash_t(sha256)))
|
|
self.assertNotEqual(ih, lt.info_hash_t())
|
|
|
|
def test_dual(self) -> None:
|
|
sha1 = lt.sha1_hash(lib.get_random_bytes(20))
|
|
sha256 = lt.sha256_hash(lib.get_random_bytes(32))
|
|
sha256_trunc = lt.sha1_hash(sha256.to_bytes()[:20])
|
|
|
|
ih = lt.info_hash_t(sha1, sha256)
|
|
self.assertEqual(ih.get(lt.protocol_version.V1), sha1)
|
|
self.assertEqual(ih.get(lt.protocol_version.V2), sha256_trunc)
|
|
self.assertEqual(ih.get_best(), sha256_trunc)
|
|
self.assertTrue(ih.has(lt.protocol_version.V1))
|
|
self.assertTrue(ih.has(lt.protocol_version.V2))
|
|
self.assertTrue(ih.has_v1())
|
|
self.assertTrue(ih.has_v2())
|
|
self.assertEqual(ih.v1, sha1)
|
|
self.assertEqual(ih.v2, sha256)
|
|
|
|
self.assertEqual(ih, lt.info_hash_t(sha1, sha256))
|
|
self.assertEqual(hash(ih), hash(lt.info_hash_t(sha1, sha256)))
|
|
self.assertNotEqual(ih, lt.info_hash_t())
|
|
|
|
|
|
class ConstructorTest(unittest.TestCase):
|
|
def do_test_filename(self, name: AnyStr) -> None:
|
|
entry = cast(
|
|
"_Entry",
|
|
{
|
|
b"info": {
|
|
b"name": b"test.txt",
|
|
b"piece length": 16384,
|
|
b"pieces": lib.get_random_bytes(20),
|
|
b"length": 1024,
|
|
}
|
|
},
|
|
)
|
|
data = lt.bencode(entry)
|
|
with tempfile.TemporaryDirectory() as tempdir_str:
|
|
tempdir: AnyStr
|
|
if isinstance(name, str):
|
|
tempdir = tempdir_str
|
|
else:
|
|
tempdir = os.fsencode(tempdir_str)
|
|
path = os.path.join(tempdir, name)
|
|
with open(path, mode="wb") as fp:
|
|
fp.write(data)
|
|
ti = lt.torrent_info(path)
|
|
self.assertEqual(ti.name(), "test.txt")
|
|
|
|
def test_filename(self) -> None:
|
|
# ascii str
|
|
self.do_test_filename("test.torrent")
|
|
|
|
# non-ascii str
|
|
self.do_test_filename("test-\u1234.torrent")
|
|
|
|
@unittest.skip("https://github.com/arvidn/libtorrent/issues/5984")
|
|
@lib.uses_non_unicode_paths()
|
|
def test_filename_non_unicode_str(self) -> None:
|
|
# non-unicode str
|
|
self.do_test_filename(os.fsdecode(b"test-\xff.torrent"))
|
|
|
|
def test_dict(self) -> None:
|
|
entry = TorrentFileDict(
|
|
{
|
|
b"info": {
|
|
b"name": b"test.txt",
|
|
b"piece length": 16384,
|
|
b"pieces": lib.get_random_bytes(20),
|
|
b"length": 1024,
|
|
}
|
|
}
|
|
)
|
|
ti = lt.torrent_info(entry)
|
|
self.assertEqual(ti.name(), "test.txt")
|
|
|
|
def test_dict_with_str_keys(self) -> None:
|
|
with self.assertWarns(DeprecationWarning):
|
|
ti = lt.torrent_info(
|
|
TorrentFileDict(
|
|
{
|
|
"info": {
|
|
"name": "test.txt",
|
|
"length": 1024,
|
|
"piece length": 16384,
|
|
"pieces": "aaaaaaaaaaaaaaaaaaaa",
|
|
}
|
|
}
|
|
)
|
|
)
|
|
self.assertEqual(ti.name(), "test.txt")
|
|
|
|
def test_copy_constructor(self) -> None:
|
|
ti = tdummy.get_default().torrent_info()
|
|
other = lt.torrent_info(ti)
|
|
self.assertEqual(ti.info_hashes(), other.info_hashes())
|
|
|
|
def test_info_hash(self) -> None:
|
|
sha1 = lt.sha1_hash(lib.get_random_bytes(20))
|
|
ti = lt.torrent_info(lt.info_hash_t(sha1))
|
|
self.assertEqual(ti.info_hashes(), lt.info_hash_t(sha1))
|
|
|
|
def test_sha1_hash(self) -> None:
|
|
sha1 = lt.sha1_hash(lib.get_random_bytes(20))
|
|
ti = lt.torrent_info(sha1)
|
|
self.assertEqual(ti.info_hash(), sha1)
|
|
|
|
|
|
class ConstructorWithLimitsTest(unittest.TestCase):
|
|
def test_load_decode_depth_limit(self) -> None:
|
|
with self.assertRaises(RuntimeError):
|
|
lt.torrent_info(
|
|
TorrentFileDict(
|
|
{
|
|
b"test": {b"test": {b"test": {b"test": {b"test": {}}}}},
|
|
b"info": {
|
|
b"name": b"test_torrent",
|
|
b"length": 1234,
|
|
b"piece length": 16 * 1024,
|
|
b"pieces": b"aaaaaaaaaaaaaaaaaaaa",
|
|
},
|
|
}
|
|
),
|
|
load_torrent_limits({"max_decode_depth": 1}),
|
|
)
|
|
|
|
def test_load_max_pieces_limit(self) -> None:
|
|
with self.assertRaises(RuntimeError):
|
|
lt.torrent_info(
|
|
TorrentFileDict(
|
|
{
|
|
b"info": {
|
|
b"name": b"test_torrent",
|
|
b"length": 1234000,
|
|
b"piece length": 16 * 1024,
|
|
b"pieces": b"aaaaaaaaaaaaaaaaaaaa",
|
|
}
|
|
}
|
|
),
|
|
load_torrent_limits({"max_pieces": 1}),
|
|
)
|
|
|
|
def test_load_max_buffer_size_limit(self) -> None:
|
|
with self.assertRaises(RuntimeError):
|
|
lt.torrent_info(
|
|
TorrentFileDict(
|
|
{
|
|
b"info": {
|
|
b"name": b"test_torrent",
|
|
b"length": 1234000,
|
|
b"piece length": 16 * 1024,
|
|
b"pieces": b"aaaaaaaaaaaaaaaaaaaa",
|
|
}
|
|
}
|
|
),
|
|
load_torrent_limits({"max_buffer_size": 1}),
|
|
)
|
|
|
|
|
|
class FieldTest(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.ti = tdummy.get_default().torrent_info()
|
|
|
|
def test_trackers(self) -> None:
|
|
self.ti.add_tracker("http://example0.com", 0, lt.tracker_source.source_client)
|
|
with self.assertRaises(TypeError):
|
|
self.ti.add_tracker(
|
|
b"http://example1.com", # type: ignore
|
|
1,
|
|
int(lt.tracker_source.source_torrent),
|
|
)
|
|
|
|
self.assertEqual([t.url for t in self.ti.trackers()], ["http://example0.com"])
|
|
self.assertEqual([t.tier for t in self.ti.trackers()], [0])
|
|
self.assertEqual(
|
|
[t.source for t in self.ti.trackers()], [lt.tracker_source.source_client]
|
|
)
|
|
|
|
def test_add_tracker_defaults(self) -> None:
|
|
self.ti.add_tracker("http://example.com")
|
|
|
|
self.assertEqual([t.url for t in self.ti.trackers()], ["http://example.com"])
|
|
self.assertEqual([t.tier for t in self.ti.trackers()], [0])
|
|
self.assertEqual(
|
|
[t.source for t in self.ti.trackers()], [lt.tracker_source.source_client]
|
|
)
|
|
|
|
def test_web_seeds(self) -> None:
|
|
# empty, and ascii str
|
|
web_seeds: list[web_seed_entry] = [
|
|
{"url": "http://example1.com", "auth": ""},
|
|
{"url": "http://example2.com", "auth": "password"},
|
|
]
|
|
self.ti.set_web_seeds(web_seeds)
|
|
self.assertEqual(self.ti.web_seeds(), web_seeds)
|
|
|
|
def do_test(auth: AnyStr, auth_check: str) -> None:
|
|
new_web_seeds: list[web_seed_entry] = [
|
|
{"url": "http://example.com", "auth": auth}
|
|
]
|
|
self.ti.set_web_seeds(new_web_seeds)
|
|
self.assertEqual(
|
|
self.ti.web_seeds(), [{"url": "http://example.com", "auth": auth_check}]
|
|
)
|
|
|
|
# ascii bytes
|
|
do_test(b"password", "password")
|
|
|
|
# non-ascii str
|
|
do_test("\u1234", "\u1234")
|
|
|
|
# non-ascii bytes
|
|
do_test("\u1234".encode(), "\u1234")
|
|
|
|
# non-unicode
|
|
with self.assertRaises(ValueError):
|
|
do_test(b"\xff", "dummy")
|
|
|
|
@unittest.skip("need to implement default empty auth")
|
|
def test_web_seeds_default_auth(self) -> None:
|
|
web_seeds: list[web_seed_entry] = [{"url": "http://example.com"}]
|
|
self.ti.set_web_seeds(web_seeds)
|
|
self.assertEqual(self.ti.web_seeds(), web_seeds)
|
|
|
|
def test_add_url_seed(self) -> None:
|
|
self.ti.add_url_seed("http://example1.com")
|
|
self.ti.add_url_seed("http://example2.com", "password", [("header", "value")])
|
|
|
|
def test_add_http_seed(self) -> None:
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.ti.add_http_seed("http://example1.com")
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.ti.add_http_seed(
|
|
"http://example2.com", "password", [("header", "value")]
|
|
)
|
|
|
|
def test_name(self) -> None:
|
|
def generate(name: bytes) -> lt.torrent_info:
|
|
return lt.torrent_info(
|
|
TorrentFileDict(
|
|
{
|
|
b"info": {
|
|
b"name": name,
|
|
b"pieces": lib.get_random_bytes(20),
|
|
b"piece length": 16384,
|
|
b"length": 1024,
|
|
}
|
|
}
|
|
)
|
|
)
|
|
|
|
# ascii
|
|
self.assertEqual(generate(b"test.txt").name(), "test.txt")
|
|
|
|
# non-ascii
|
|
self.assertEqual(generate("\u1234.txt".encode()).name(), "\u1234.txt")
|
|
|
|
# non-unicode
|
|
self.assertEqual(generate(b"\xff.txt").name(), "_.txt")
|
|
|
|
def test_creator(self) -> None:
|
|
def generate(creator: bytes) -> lt.torrent_info:
|
|
return lt.torrent_info(
|
|
TorrentFileDict(
|
|
{
|
|
b"created by": creator,
|
|
b"info": {
|
|
b"name": b"test.txt",
|
|
b"pieces": lib.get_random_bytes(20),
|
|
b"piece length": 16384,
|
|
b"length": 1024,
|
|
},
|
|
}
|
|
)
|
|
)
|
|
|
|
# ascii
|
|
self.assertEqual(generate(b"test").creator(), "test")
|
|
|
|
# non-ascii
|
|
self.assertEqual(generate("test-\u1234".encode()).creator(), "test-\u1234")
|
|
|
|
# non-unicode
|
|
self.assertEqual(generate(b"test-\xff").creator(), "test-_")
|
|
|
|
def test_comment(self) -> None:
|
|
def generate(comment: bytes) -> lt.torrent_info:
|
|
return lt.torrent_info(
|
|
TorrentFileDict(
|
|
{
|
|
b"comment": comment,
|
|
b"info": {
|
|
b"name": b"test.txt",
|
|
b"pieces": lib.get_random_bytes(20),
|
|
b"piece length": 16384,
|
|
b"length": 1024,
|
|
},
|
|
}
|
|
)
|
|
)
|
|
|
|
# ascii
|
|
self.assertEqual(generate(b"test").comment(), "test")
|
|
|
|
# non-ascii
|
|
self.assertEqual(generate("\u1234".encode()).comment(), "\u1234")
|
|
|
|
# non-unicode
|
|
self.assertEqual(generate(b"test-\xff").comment(), "test-_")
|
|
|
|
def test_collections(self) -> None:
|
|
def generate(collection: bytes) -> lt.torrent_info:
|
|
return lt.torrent_info(
|
|
TorrentFileDict(
|
|
{
|
|
b"collections": [collection],
|
|
b"info": {
|
|
b"name": b"test.txt",
|
|
b"pieces": lib.get_random_bytes(20),
|
|
b"piece length": 16384,
|
|
b"length": 1024,
|
|
},
|
|
}
|
|
)
|
|
)
|
|
|
|
# ascii
|
|
self.assertEqual(generate(b"test").collections(), ["test"])
|
|
|
|
# non-ascii
|
|
self.assertEqual(generate("\u1234".encode()).collections(), ["\u1234"])
|
|
|
|
# non-unicode
|
|
with self.assertRaises(ValueError):
|
|
generate(b"\xff").collections()
|
|
|
|
def test_hash_for_piece_invalid(self) -> None:
|
|
ti = lt.torrent_info(
|
|
TorrentFileDict(
|
|
{
|
|
b"info": {
|
|
b"name": b"test.txt",
|
|
b"pieces": lib.get_random_bytes(20),
|
|
b"piece length": 16384,
|
|
b"length": 1024,
|
|
}
|
|
}
|
|
)
|
|
)
|
|
with self.assertRaises(IndexError):
|
|
ti.hash_for_piece(1)
|
|
with self.assertRaises(IndexError):
|
|
ti.hash_for_piece(-1)
|
|
|
|
def test_info_hash(self) -> None:
|
|
self.assertFalse(self.ti.info_hash().is_all_zeros())
|
|
self.assertEqual(self.ti.info_hashes(), lt.info_hash_t(self.ti.info_hash()))
|
|
|
|
def test_is_i2p(self) -> None:
|
|
ti = lt.torrent_info(
|
|
TorrentFileDict(
|
|
{
|
|
b"announce": b"http://example.i2p",
|
|
b"info": {
|
|
b"name": b"test.txt",
|
|
b"pieces": lib.get_random_bytes(20),
|
|
b"piece length": 16384,
|
|
b"length": 1024,
|
|
},
|
|
}
|
|
)
|
|
)
|
|
self.assertTrue(ti.is_i2p())
|
|
|
|
def test_normal_fixed_fields(self) -> None:
|
|
piece = lib.get_random_bytes(20)
|
|
similar = lib.get_random_bytes(20)
|
|
info = cast(
|
|
"_Entry",
|
|
{
|
|
b"name": b"test.txt",
|
|
b"pieces": piece,
|
|
b"piece length": 16384,
|
|
b"length": 1024,
|
|
b"similar": [similar],
|
|
},
|
|
)
|
|
ti = lt.torrent_info(TorrentFileDict({b"creation date": 12345, b"info": info}))
|
|
|
|
self.assertEqual(ti.total_size(), 1024)
|
|
self.assertEqual(ti.piece_length(), 16384)
|
|
self.assertEqual(ti.num_pieces(), 1)
|
|
with self.assertRaises(IndexError):
|
|
ti.piece_size(-1)
|
|
self.assertEqual(ti.piece_size(0), 1024)
|
|
with self.assertRaises(IndexError):
|
|
ti.piece_size(1)
|
|
self.assertEqual(ti.hash_for_piece(0), piece)
|
|
self.assertEqual(ti.num_files(), 1)
|
|
self.assertTrue(ti.is_valid())
|
|
self.assertFalse(ti.priv())
|
|
self.assertEqual(ti.info_section(), lt.bencode(info))
|
|
self.assertEqual(ti.creation_date(), 12345)
|
|
self.assertEqual(ti.similar_torrents(), [lt.sha1_hash(similar)])
|
|
|
|
def test_metadata(self) -> None:
|
|
with self.assertWarns(DeprecationWarning):
|
|
metadata = self.ti.metadata()
|
|
self.assertEqual(metadata, self.ti.info_section())
|
|
with self.assertWarns(DeprecationWarning):
|
|
size = self.ti.metadata_size()
|
|
self.assertEqual(size, len(metadata))
|
|
|
|
def test_merkle_tree(self) -> None:
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertFalse(self.ti.is_merkle_torrent())
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(self.ti.merkle_tree(), [])
|
|
|
|
tree: List[bytes] = []
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.ti.set_merkle_tree(tree)
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertFalse(self.ti.is_merkle_torrent())
|
|
with self.assertWarns(DeprecationWarning):
|
|
self.assertEqual(self.ti.merkle_tree(), tree)
|
|
|
|
def test_nodes(self) -> None:
|
|
self.ti.add_node("node1", 1234)
|
|
self.assertEqual(self.ti.nodes(), [("node1", 1234)])
|
|
|
|
def test_rename_file(self) -> None:
|
|
# ascii str
|
|
self.ti.rename_file(0, "ascii-str.txt")
|
|
self.assertEqual(self.ti.files().file_path(0), "ascii-str.txt")
|
|
|
|
# ascii bytes
|
|
self.ti.rename_file(0, b"ascii-bytes.txt") # type: ignore
|
|
self.assertEqual(self.ti.files().file_path(0), "ascii-bytes.txt")
|
|
|
|
# non-ascii str
|
|
self.ti.rename_file(0, "\u1234-str.txt")
|
|
self.assertEqual(self.ti.files().file_path(0), "\u1234-str.txt")
|
|
|
|
# non-ascii bytes
|
|
self.ti.rename_file(0, "\u1234-bytes.txt".encode()) # type: ignore
|
|
self.assertEqual(self.ti.files().file_path(0), "\u1234-bytes.txt")
|
|
|
|
# non-unicode
|
|
self.ti.rename_file(0, b"\xff.txt") # type: ignore
|
|
with self.assertRaises(ValueError):
|
|
self.ti.files().file_path(0)
|
|
|
|
def test_files_and_remap(self) -> None:
|
|
ti = lt.torrent_info(
|
|
TorrentFileDict(
|
|
{
|
|
b"info": {
|
|
b"name": b"test.txt",
|
|
b"pieces": lib.get_random_bytes(20),
|
|
b"piece length": 16384,
|
|
b"length": 1024,
|
|
},
|
|
}
|
|
)
|
|
)
|
|
|
|
self.assertEqual(ti.name(), "test.txt")
|
|
self.assertEqual(ti.layout().file_path(0), "test.txt")
|
|
self.assertEqual(ti.orig_files().file_path(0), "test.txt")
|
|
|
|
fs = lt.file_storage()
|
|
fs.add_file(os.path.join("path", "remapped1.txt"), 512)
|
|
fs.add_file(os.path.join("path", "remapped2.txt"), 512)
|
|
|
|
ti.remap_files(fs)
|
|
|
|
self.assertEqual(ti.name(), "path")
|
|
self.assertEqual(ti.files().num_files(), 2)
|
|
self.assertEqual(ti.files().file_path(0), os.path.join("path", "remapped1.txt"))
|
|
self.assertEqual(ti.files().file_path(1), os.path.join("path", "remapped2.txt"))
|
|
|
|
self.assertEqual(ti.orig_files().num_files(), 1)
|
|
self.assertEqual(ti.orig_files().file_path(0), "test.txt")
|
|
|
|
def test_map(self) -> None:
|
|
pr = self.ti.map_file(0, 100, 200)
|
|
self.assertEqual(pr.piece, 0)
|
|
self.assertEqual(pr.start, 100)
|
|
self.assertEqual(pr.length, 200)
|
|
|
|
slices = self.ti.map_block(0, 100, 200)
|
|
self.assertEqual(len(slices), 1)
|
|
file_slice = slices[0]
|
|
self.assertEqual(file_slice.file_index, 0)
|
|
self.assertEqual(file_slice.offset, 100)
|
|
self.assertEqual(file_slice.size, 200)
|
|
|
|
def test_file_at(self) -> None:
|
|
if lt.api_version < 2:
|
|
with self.assertWarns(DeprecationWarning):
|
|
fe = self.ti.file_at(0)
|
|
self.assertEqual(fe.path, self.ti.layout().file_path(0))
|