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