1 #ifndef QT_GUI_LIB 2 #define QT_GUI_LIB 3 #endif 4 5 #include <QLocale> 6 #include <QObject> 7 #include <QRandomGenerator> 8 #include <QtTest> 9 10 #include <ctime> 11 #include <interfaces/queuemanagerinterface.h> 12 #include <testlib/dummytorrentcreator.h> 13 #include <torrent/torrentcontrol.h> 14 #include <torrent/torrentfilestream.h> 15 #include <unistd.h> 16 #include <util/error.h> 17 #include <util/fileops.h> 18 #include <util/log.h> 19 #include <util/sha1hashgen.h> 20 21 using namespace bt; 22 23 const bt::Uint32 TEST_FILE_SIZE = 5 * 1024 * 1024; 24 25 class TorrentFileStreamTest : public QEventLoop, public bt::QueueManagerInterface 26 { 27 Q_OBJECT 28 public: 29 TorrentFileStreamTest(QObject *parent = nullptr) 30 : QEventLoop(parent) 31 { 32 } 33 34 bool alreadyLoaded(const bt::SHA1Hash &ih) const override 35 { 36 Q_UNUSED(ih); 37 return false; 38 } 39 40 void mergeAnnounceList(const bt::SHA1Hash &ih, const bt::TrackerTier *trk) override 41 { 42 Q_UNUSED(ih); 43 Q_UNUSED(trk); 44 } 45 46 private Q_SLOTS: 47 void initTestCase() 48 { 49 QLocale::setDefault(QLocale("main")); 50 bt::InitLog("torrentfilestreamtest.log", false, false); 51 QVERIFY(creator.createSingleFileTorrent(TEST_FILE_SIZE, "test.avi")); 52 QVERIFY(creator2.createSingleFileTorrent(TEST_FILE_SIZE, "test2.avi")); 53 54 Out(SYS_GEN | LOG_DEBUG) << "Created " << creator.torrentPath() << endl; 55 Out(SYS_GEN | LOG_DEBUG) << "Created " << creator2.torrentPath() << endl; 56 try { 57 tc.init(this, bt::LoadFile(creator.torrentPath()), creator.tempPath() + "tor0", creator.tempPath() + "data/"); 58 tc.createFiles(); 59 QVERIFY(tc.hasExistingFiles()); 60 tc.startDataCheck(false, 0, tc.getStats().total_chunks); 61 do { 62 processEvents(AllEvents, 1000); 63 } while (tc.getStats().status == bt::CHECKING_DATA); 64 QVERIFY(tc.getStats().completed); 65 66 incomplete_tc.init(this, bt::LoadFile(creator2.torrentPath()), creator2.tempPath() + "tor0", creator2.tempPath() + "data/"); 67 incomplete_tc.createFiles(); 68 } catch (bt::Error &err) { 69 Out(SYS_GEN | LOG_DEBUG) << "Failed to load torrent: " << creator.torrentPath() << endl; 70 QFAIL("Torrent load failure"); 71 } 72 } 73 74 void cleanupTestCase() 75 { 76 } 77 78 void testSimple() 79 { 80 Out(SYS_GEN | LOG_DEBUG) << "Begin: testSimple() " << endl; 81 bt::TorrentFileStream::Ptr stream = tc.createTorrentFileStream(0, false, this); 82 QVERIFY(stream); 83 QVERIFY(!stream->open(QIODevice::ReadWrite)); 84 QVERIFY(stream->open(QIODevice::ReadOnly)); 85 // Simple test read per chunk and verify if it works 86 QByteArray tmp(tc.getStats().chunk_size, 0); 87 bt::Uint64 written = 0; 88 bt::Uint32 idx = 0; 89 while (written < TEST_FILE_SIZE) { 90 qint64 ret = stream->read(tmp.data(), tc.getStats().chunk_size); 91 QVERIFY(ret == tc.getStats().chunk_size); 92 written += tc.getStats().chunk_size; 93 94 // Verify the hash 95 bt::SHA1Hash hash = bt::SHA1Hash::generate((const bt::Uint8 *)tmp.data(), tmp.size()); 96 QVERIFY(hash == tc.getTorrent().getHash(idx)); 97 idx++; 98 } 99 100 stream->close(); 101 Out(SYS_GEN | LOG_DEBUG) << "End: testSimple() " << endl; 102 } 103 104 void testSeek() 105 { 106 Out(SYS_GEN | LOG_DEBUG) << "Begin: testSeek() " << endl; 107 bt::TorrentFileStream::Ptr stream = tc.createTorrentFileStream(0, false, this); 108 QVERIFY(stream); 109 QVERIFY(!stream->open(QIODevice::ReadWrite)); 110 QVERIFY(stream->open(QIODevice::ReadOnly)); 111 // Simple test read per chunk and seek somewhat 112 bt::Uint32 chunk_size = tc.getStats().chunk_size; 113 QByteArray tmp(chunk_size, 0); 114 bt::Uint64 written = 0; 115 bt::Uint32 idx = 0; 116 while (written < TEST_FILE_SIZE) { 117 // Chunk size of last chunk can be smaller 118 if (idx == tc.getStats().total_chunks - 1) { 119 chunk_size = tc.getStats().chunk_size % tc.getStats().total_bytes; 120 if (chunk_size == 0) 121 chunk_size = tc.getStats().chunk_size; 122 } 123 124 // Lets read in two times, first at the back and then at the front 125 qint64 split = QRandomGenerator::global()->bounded(chunk_size); 126 while (split == 0) 127 split = QRandomGenerator::global()->bounded(chunk_size); 128 129 QVERIFY(stream->seek(idx * tc.getStats().chunk_size + split)); 130 qint64 ret = stream->read(tmp.data() + split, chunk_size - split); 131 QVERIFY(ret == (chunk_size - split)); 132 written += ret; 133 134 QVERIFY(stream->seek(idx * tc.getStats().chunk_size)); 135 ret = stream->read(tmp.data(), split); 136 QVERIFY(ret == split); 137 written += ret; 138 139 // Verify the hash 140 bt::SHA1Hash hash = bt::SHA1Hash::generate((const bt::Uint8 *)tmp.data(), tmp.size()); 141 QVERIFY(hash == tc.getTorrent().getHash(idx)); 142 idx++; 143 } 144 145 stream->close(); 146 Out(SYS_GEN | LOG_DEBUG) << "End: testSeek() " << endl; 147 } 148 149 void testRandomAccess() 150 { 151 Out(SYS_GEN | LOG_DEBUG) << "Begin: testRandomAccess() " << endl; 152 bt::TorrentFileStream::Ptr stream = tc.createTorrentFileStream(0, false, this); 153 QVERIFY(stream); 154 QVERIFY(!stream->open(QIODevice::ReadWrite)); 155 QVERIFY(stream->open(QIODevice::ReadOnly)); 156 157 // Read an area of around 5 chunks in at a random location 158 // And verify the 3 middle chunks 159 qint64 range_size = tc.getStats().chunk_size * 5; 160 qint64 off = QRandomGenerator::global()->bounded(100) / 100.0 * (tc.getStats().total_bytes - range_size); 161 QVERIFY(stream->seek(off)); 162 163 Out(SYS_GEN | LOG_DEBUG) << "Reading random range" << endl; 164 Out(SYS_GEN | LOG_DEBUG) << "range_size = " << range_size << endl; 165 Out(SYS_GEN | LOG_DEBUG) << "off = " << off << endl; 166 167 QByteArray range(range_size, 0); 168 qint64 bytes_read = 0; 169 while (bytes_read < range_size) { 170 qint64 ret = stream->read(range.data() + bytes_read, range_size - bytes_read); 171 Out(SYS_GEN | LOG_DEBUG) << "ret = " << ret << endl; 172 Out(SYS_GEN | LOG_DEBUG) << "read = " << bytes_read << endl; 173 QVERIFY(ret > 0); 174 bytes_read += ret; 175 } 176 177 { 178 QFile fptr(stream->path()); 179 QVERIFY(fptr.open(QIODevice::ReadOnly)); 180 QByteArray tmp(range_size, 0); 181 fptr.seek(off); 182 QVERIFY(fptr.read(tmp.data(), range_size) == range_size); 183 QVERIFY(tmp == range); 184 } 185 186 QVERIFY(bytes_read == range_size); 187 188 // Calculate the offset of the first chunk in range 189 qint64 chunk_idx = off / tc.getStats().chunk_size; 190 if (off % tc.getStats().chunk_size != 0) 191 chunk_idx++; 192 qint64 chunk_off = chunk_idx * tc.getStats().chunk_size - off; 193 194 Out(SYS_GEN | LOG_DEBUG) << "chunk_idx = " << chunk_idx << endl; 195 Out(SYS_GEN | LOG_DEBUG) << "chunk_off = " << chunk_off << endl; 196 197 // Verify the hashes 198 for (int i = 0; i < 3; i++) { 199 bt::SHA1Hash hash = bt::SHA1Hash::generate((const bt::Uint8 *)range.data() + chunk_off, tc.getStats().chunk_size); 200 201 Out(SYS_GEN | LOG_DEBUG) << "chash = " << hash.toString() << endl; 202 Out(SYS_GEN | LOG_DEBUG) << "whash = " << tc.getTorrent().getHash(chunk_idx).toString() << endl; 203 QVERIFY(hash == tc.getTorrent().getHash(chunk_idx)); 204 chunk_idx++; 205 chunk_off += tc.getStats().chunk_size; 206 } 207 208 stream->close(); 209 Out(SYS_GEN | LOG_DEBUG) << "End: testRandomAccess() " << endl; 210 } 211 212 void testMultiSeek() 213 { 214 Out(SYS_GEN | LOG_DEBUG) << "Begin: testMultiSeek() " << endl; 215 bt::TorrentFileStream::Ptr stream = tc.createTorrentFileStream(0, false, this); 216 QVERIFY(stream); 217 QVERIFY(!stream->open(QIODevice::ReadWrite)); 218 QVERIFY(stream->open(QIODevice::ReadOnly)); 219 220 QFile fptr(stream->path()); 221 QVERIFY(fptr.open(QIODevice::ReadOnly)); 222 for (int i = 0; i < 20; i++) { 223 qint64 off = QRandomGenerator::global()->bounded(TEST_FILE_SIZE - 100); 224 // Seek to a random location 225 QVERIFY(stream->seek(off)); 226 QByteArray tmp(100, 0); 227 QVERIFY(stream->read(tmp.data(), 100) == 100); 228 229 // Verify those 230 QByteArray tmp2(100, 0); 231 fptr.seek(off); 232 QVERIFY(fptr.read(tmp2.data(), 100) == 100); 233 QVERIFY(tmp == tmp2); 234 } 235 236 stream->close(); 237 Out(SYS_GEN | LOG_DEBUG) << "End: testMultiSeek() " << endl; 238 } 239 240 void testStreamingCreate() 241 { 242 bt::TorrentFileStream::Ptr a = tc.createTorrentFileStream(0, true, this); 243 QVERIFY(a); 244 bt::TorrentFileStream::Ptr b = tc.createTorrentFileStream(0, true, this); 245 QVERIFY(!b); 246 a.clear(); 247 b = tc.createTorrentFileStream(0, true, this); 248 QVERIFY(b); 249 } 250 251 void testSeekToUndownloadedSection() 252 { 253 bt::TorrentFileStream::Ptr a = incomplete_tc.createTorrentFileStream(0, true, this); 254 QVERIFY(incomplete_tc.getStats().completed == false); 255 QVERIFY(a->seek(TEST_FILE_SIZE / 2)); 256 QVERIFY(a->bytesAvailable() == 0); 257 } 258 259 private: 260 DummyTorrentCreator creator; 261 DummyTorrentCreator creator2; 262 bt::TorrentControl tc; 263 bt::TorrentControl incomplete_tc; 264 }; 265 266 QTEST_MAIN(TorrentFileStreamTest) 267 268 #include "torrentfilestreamtest.moc" 269