1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | #include <QThread> |
29 | |
30 | struct TestIterator |
31 | { |
32 | TestIterator(int i) |
33 | :i(i) { } |
34 | |
35 | int operator-(const TestIterator &other) |
36 | { |
37 | return i - other.i; |
38 | } |
39 | |
40 | TestIterator& operator++() |
41 | { |
42 | ++i; |
43 | return *this; |
44 | } |
45 | |
46 | bool operator!=(const TestIterator &other) const |
47 | { |
48 | return i != other.i; |
49 | } |
50 | |
51 | int i; |
52 | }; |
53 | |
54 | #include <qiterator.h> |
55 | namespace std { |
56 | template <> |
57 | struct iterator_traits<TestIterator> |
58 | { |
59 | typedef random_access_iterator_tag iterator_category; |
60 | }; |
61 | |
62 | int distance(TestIterator &a, TestIterator &b) |
63 | { |
64 | return b - a; |
65 | } |
66 | |
67 | } |
68 | |
69 | #include <qtconcurrentiteratekernel.h> |
70 | #include <QtTest/QtTest> |
71 | |
72 | using namespace QtConcurrent; |
73 | |
74 | class tst_QtConcurrentIterateKernel: public QObject |
75 | { |
76 | Q_OBJECT |
77 | private slots: |
78 | // "for" iteration tests: |
79 | void instantiate(); |
80 | void cancel(); |
81 | void stresstest(); |
82 | void noIterations(); |
83 | void throttling(); |
84 | void multipleResults(); |
85 | }; |
86 | |
87 | QAtomicInt iterations; |
88 | class PrintFor : public IterateKernel<TestIterator, void> |
89 | { |
90 | public: |
91 | PrintFor(TestIterator begin, TestIterator end) : IterateKernel<TestIterator, void>(begin, end) { iterations.storeRelaxed(newValue: 0); } |
92 | bool runIterations(TestIterator/*beginIterator*/, int begin, int end, void *) |
93 | { |
94 | iterations.fetchAndAddRelaxed(valueToAdd: end - begin); |
95 | #ifdef PRINT |
96 | qDebug() << QThread::currentThread() << "iteration" << begin << "to" << end << "(exclusive)" ; |
97 | #endif |
98 | return false; |
99 | } |
100 | bool runIteration(TestIterator it, int index , void *result) |
101 | { |
102 | return runIterations(it, begin: index, end: index + 1, result); |
103 | } |
104 | |
105 | }; |
106 | |
107 | class SleepPrintFor : public IterateKernel<TestIterator, void> |
108 | { |
109 | public: |
110 | SleepPrintFor(TestIterator begin, TestIterator end) : IterateKernel<TestIterator, void>(begin, end) { iterations.storeRelaxed(newValue: 0); } |
111 | inline bool runIterations(TestIterator/*beginIterator*/, int begin, int end, void *) |
112 | { |
113 | QTest::qSleep(ms: 200); |
114 | iterations.fetchAndAddRelaxed(valueToAdd: end - begin); |
115 | #ifdef PRINT |
116 | qDebug() << QThread::currentThread() << "iteration" << begin << "to" << end << "(exclusive)" ; |
117 | #endif |
118 | return false; |
119 | } |
120 | bool runIteration(TestIterator it, int index , void *result) |
121 | { |
122 | return runIterations(it, begin: index, end: index + 1, result); |
123 | } |
124 | }; |
125 | |
126 | |
127 | void tst_QtConcurrentIterateKernel::instantiate() |
128 | { |
129 | auto future = startThreadEngine(threadEngine: new PrintFor(0, 40)).startAsynchronously(); |
130 | future.waitForFinished(); |
131 | QCOMPARE(iterations.loadRelaxed(), 40); |
132 | } |
133 | |
134 | void tst_QtConcurrentIterateKernel::cancel() |
135 | { |
136 | { |
137 | QFuture<void> f = startThreadEngine(threadEngine: new SleepPrintFor(0, 40)).startAsynchronously(); |
138 | f.cancel(); |
139 | f.waitForFinished(); |
140 | QVERIFY(f.isCanceled()); |
141 | // the threads might run one iteration each before they are canceled. |
142 | QVERIFY2(iterations.loadRelaxed() <= QThread::idealThreadCount(), |
143 | (QByteArray::number(iterations.loadRelaxed()) + ' ' + QByteArray::number(QThread::idealThreadCount()))); |
144 | } |
145 | } |
146 | |
147 | QAtomicInt counter; |
148 | class CountFor : public IterateKernel<TestIterator, void> |
149 | { |
150 | public: |
151 | CountFor(TestIterator begin, TestIterator end) : IterateKernel<TestIterator, void>(begin, end) { iterations.storeRelaxed(newValue: 0); } |
152 | inline bool runIterations(TestIterator/*beginIterator*/, int begin, int end, void *) |
153 | { |
154 | counter.fetchAndAddRelaxed(valueToAdd: end - begin); |
155 | return false; |
156 | } |
157 | bool runIteration(TestIterator it, int index , void *result) |
158 | { |
159 | return runIterations(it, begin: index, end: index + 1, result); |
160 | } |
161 | }; |
162 | |
163 | void tst_QtConcurrentIterateKernel::stresstest() |
164 | { |
165 | const int iterations = 1000; |
166 | const int times = 50; |
167 | for (int i = 0; i < times; ++i) { |
168 | counter.storeRelaxed(newValue: 0); |
169 | // ThreadEngine will delete f when it finishes |
170 | auto f = new CountFor(0, iterations); |
171 | auto future = f->startAsynchronously(); |
172 | future.waitForFinished(); |
173 | QCOMPARE(counter.loadRelaxed(), iterations); |
174 | } |
175 | } |
176 | |
177 | void tst_QtConcurrentIterateKernel::noIterations() |
178 | { |
179 | const int times = 20000; |
180 | for (int i = 0; i < times; ++i) { |
181 | auto future = startThreadEngine(threadEngine: new IterateKernel<TestIterator, void>(0, 0)) |
182 | .startAsynchronously(); |
183 | future.waitForFinished(); |
184 | } |
185 | } |
186 | |
187 | QMutex threadsMutex; |
188 | QSet<QThread *> threads; |
189 | class ThrottleFor : public IterateKernel<TestIterator, void> |
190 | { |
191 | public: |
192 | // this class throttles between iterations 100 and 200, |
193 | // and then records how many threads that run between |
194 | // iterations 140 and 160. |
195 | ThrottleFor(TestIterator begin, TestIterator end) : IterateKernel<TestIterator, void>(begin, end) { iterations.storeRelaxed(newValue: 0); throttling = false; } |
196 | inline bool runIterations(TestIterator/*beginIterator*/, int begin, int end, void *) |
197 | { |
198 | if (200 >= begin && 200 < end) { |
199 | throttling = false; |
200 | } |
201 | |
202 | iterations.fetchAndAddRelaxed(valueToAdd: end - begin); |
203 | |
204 | QThread *thread = QThread::currentThread(); |
205 | |
206 | if (begin > 140 && end < 160) { |
207 | QMutexLocker locker(&threadsMutex); |
208 | threads.insert(value: thread); |
209 | } |
210 | |
211 | if (100 >= begin && 100 < end) { |
212 | throttling = true; |
213 | } |
214 | |
215 | QTest::qWait(ms: 1); |
216 | |
217 | return false; |
218 | } |
219 | bool runIteration(TestIterator it, int index , void *result) |
220 | { |
221 | return runIterations(it, begin: index, end: index + 1, result); |
222 | } |
223 | |
224 | bool shouldThrottleThread() |
225 | { |
226 | const int load = iterations.loadRelaxed(); |
227 | return (load > 100 && load < 200); |
228 | } |
229 | bool throttling; |
230 | }; |
231 | |
232 | void tst_QtConcurrentIterateKernel::throttling() |
233 | { |
234 | const int totalIterations = 400; |
235 | iterations.storeRelaxed(newValue: 0); |
236 | |
237 | threads.clear(); |
238 | |
239 | // ThreadEngine will delete f when it finishes |
240 | auto f = new ThrottleFor(0, totalIterations); |
241 | auto future = f->startAsynchronously(); |
242 | future.waitForFinished(); |
243 | |
244 | QCOMPARE(iterations.loadRelaxed(), totalIterations); |
245 | |
246 | |
247 | QCOMPARE(threads.count(), 1); |
248 | } |
249 | |
250 | class MultipleResultsFor : public IterateKernel<TestIterator, int> |
251 | { |
252 | public: |
253 | MultipleResultsFor(TestIterator begin, TestIterator end) : IterateKernel<TestIterator, int>(begin, end) { } |
254 | inline bool runIterations(TestIterator, int begin, int end, int *results) |
255 | { |
256 | for (int i = begin; i < end; ++i) |
257 | results[i - begin] = i; |
258 | return true; |
259 | } |
260 | }; |
261 | |
262 | void tst_QtConcurrentIterateKernel::multipleResults() |
263 | { |
264 | QFuture<int> f = startThreadEngine(threadEngine: new MultipleResultsFor(0, 10)).startAsynchronously(); |
265 | QCOMPARE(f.results().count() , 10); |
266 | QCOMPARE(f.resultAt(0), 0); |
267 | QCOMPARE(f.resultAt(5), 5); |
268 | QCOMPARE(f.resultAt(9), 9); |
269 | f.waitForFinished(); |
270 | } |
271 | |
272 | QTEST_MAIN(tst_QtConcurrentIterateKernel) |
273 | |
274 | #include "tst_qtconcurrentiteratekernel.moc" |
275 | |