File indexing completed on 2024-04-21 14:54:42

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2016 Michael Pyne <mpyne@kde.org>
0004     SPDX-FileCopyrightText: 2016 Arne Spiegelhauer <gm2.asp@gmail.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #include <krandom.h>
0010 #include <krandomsequence.h>
0011 #include <stdlib.h>
0012 
0013 #include <QTest>
0014 #include <QThread>
0015 
0016 #include <QObject>
0017 #include <QProcess>
0018 #include <QRegularExpression>
0019 #include <QString>
0020 #include <QTextStream>
0021 #include <QVarLengthArray>
0022 
0023 #include <algorithm>
0024 #include <iostream>
0025 
0026 typedef QVarLengthArray<int> intSequenceType;
0027 
0028 static const char *binpath;
0029 
0030 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75)
0031 static bool seqsAreEqual(const intSequenceType &l, const intSequenceType &r)
0032 {
0033     if (l.size() != r.size()) {
0034         return false;
0035     }
0036     const intSequenceType::const_iterator last(l.end());
0037 
0038     intSequenceType::const_iterator l_first(l.begin());
0039     intSequenceType::const_iterator r_first(r.begin());
0040 
0041     while (l_first != last && *l_first == *r_first) {
0042         l_first++;
0043         r_first++;
0044     }
0045 
0046     return l_first == last;
0047 }
0048 #endif
0049 
0050 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72)
0051 // Fills seq with random bytes produced by a new process. Seq should already
0052 // be sized to the needed amount of random numbers.
0053 static bool getChildRandSeq(intSequenceType &seq)
0054 {
0055     QProcess subtestProcess;
0056 
0057     // Launch a separate process to generate random numbers to test first-time
0058     // seeding.
0059     subtestProcess.start(QLatin1String(binpath), QStringList() << QString::number(seq.count()));
0060     subtestProcess.waitForFinished();
0061 
0062     QTextStream childStream(subtestProcess.readAllStandardOutput());
0063 
0064     std::generate(seq.begin(), seq.end(), [&]() {
0065         int temp;
0066         childStream >> temp;
0067         return temp;
0068     });
0069 
0070     char c;
0071     childStream >> c;
0072     return c == '@' && childStream.status() == QTextStream::Ok;
0073 }
0074 #endif
0075 
0076 class KRandomTest : public QObject
0077 {
0078     Q_OBJECT
0079 
0080 private Q_SLOTS:
0081 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72)
0082     void test_random();
0083 #endif
0084     void test_randomString();
0085     void test_randomStringThreaded();
0086 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75)
0087     void test_KRS();
0088 #endif
0089     void test_shuffle();
0090 };
0091 
0092 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72)
0093 void KRandomTest::test_random()
0094 {
0095     int testValue = KRandom::random();
0096 
0097     QVERIFY(testValue >= 0);
0098     QVERIFY(testValue < RAND_MAX);
0099 
0100     // Verify seeding results in different numbers across different procs
0101     // See bug 362161
0102     intSequenceType out1(10);
0103     intSequenceType out2(10);
0104 
0105     QVERIFY(getChildRandSeq(out1));
0106     QVERIFY(getChildRandSeq(out2));
0107 
0108     QVERIFY(!seqsAreEqual(out1, out2));
0109 }
0110 #endif
0111 
0112 void KRandomTest::test_randomString()
0113 {
0114     const int desiredLength = 12;
0115     const QString testString = KRandom::randomString(desiredLength);
0116     const QRegularExpression outputFormat(QRegularExpression::anchoredPattern(QStringLiteral("[A-Za-z0-9]+")));
0117     const QRegularExpressionMatch match = outputFormat.match(testString);
0118 
0119     QCOMPARE(testString.length(), desiredLength);
0120     QVERIFY(match.hasMatch());
0121 }
0122 
0123 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75)
0124 void KRandomTest::test_KRS()
0125 {
0126     using std::all_of;
0127     using std::generate;
0128 
0129     const int maxInt = 50000;
0130     KRandomSequence krs1;
0131     KRandomSequence krs2;
0132     intSequenceType out1(10);
0133     intSequenceType out2(10);
0134 
0135     generate(out1.begin(), out1.end(), [&]() {
0136         return krs1.getInt(maxInt);
0137     });
0138     generate(out2.begin(), out2.end(), [&]() {
0139         return krs2.getInt(maxInt);
0140     });
0141     QVERIFY(!seqsAreEqual(out1, out2));
0142     QVERIFY(all_of(out1.begin(), out1.end(), [&](int x) {
0143         return x < maxInt;
0144     }));
0145     QVERIFY(all_of(out2.begin(), out2.end(), [&](int x) {
0146         return x < maxInt;
0147     }));
0148 
0149     // Compare same-seed
0150     krs1.setSeed(5000);
0151     krs2.setSeed(5000);
0152 
0153     generate(out1.begin(), out1.end(), [&]() {
0154         return krs1.getInt(maxInt);
0155     });
0156     generate(out2.begin(), out2.end(), [&]() {
0157         return krs2.getInt(maxInt);
0158     });
0159     QVERIFY(seqsAreEqual(out1, out2));
0160     QVERIFY(all_of(out1.begin(), out1.end(), [&](int x) {
0161         return x < maxInt;
0162     }));
0163     QVERIFY(all_of(out2.begin(), out2.end(), [&](int x) {
0164         return x < maxInt;
0165     }));
0166 
0167     // Compare same-seed and assignment ctor
0168 
0169     krs1 = KRandomSequence(8000);
0170     krs2 = KRandomSequence(8000);
0171 
0172     generate(out1.begin(), out1.end(), [&]() {
0173         return krs1.getInt(maxInt);
0174     });
0175     generate(out2.begin(), out2.end(), [&]() {
0176         return krs2.getInt(maxInt);
0177     });
0178     QVERIFY(seqsAreEqual(out1, out2));
0179     QVERIFY(all_of(out1.begin(), out1.end(), [&](int x) {
0180         return x < maxInt;
0181     }));
0182     QVERIFY(all_of(out2.begin(), out2.end(), [&](int x) {
0183         return x < maxInt;
0184     }));
0185 }
0186 #endif
0187 
0188 void KRandomTest::test_shuffle()
0189 {
0190     {
0191         QRandomGenerator rg(1);
0192         QList<int> list = {1, 2, 3, 4, 5};
0193         const QList<int> shuffled = {5, 2, 4, 3, 1};
0194         KRandom::shuffle(list, &rg);
0195         QCOMPARE(list, shuffled);
0196     }
0197 
0198     {
0199         QRandomGenerator rg(1);
0200         QVector<int> vector = {1, 2, 3, 4, 5};
0201         const QVector<int> shuffled = {5, 2, 4, 3, 1};
0202         KRandom::shuffle(vector, &rg);
0203         QCOMPARE(vector, shuffled);
0204     }
0205 
0206     {
0207         QRandomGenerator rg(1);
0208         std::vector<int> std_vector = {1, 2, 3, 4, 5};
0209         const std::vector<int> shuffled = {5, 2, 4, 3, 1};
0210         KRandom::shuffle(std_vector, &rg);
0211         QCOMPARE(std_vector, shuffled);
0212     }
0213 }
0214 
0215 class KRandomTestThread : public QThread
0216 {
0217 protected:
0218     void run() override
0219     {
0220         result = KRandom::randomString(32);
0221     };
0222 
0223 public:
0224     QString result;
0225 };
0226 
0227 void KRandomTest::test_randomStringThreaded()
0228 {
0229     static const int size = 5;
0230     KRandomTestThread *threads[size];
0231     for (int i = 0; i < size; ++i) {
0232         threads[i] = new KRandomTestThread();
0233         threads[i]->start();
0234     }
0235     QSet<QString> results;
0236     for (int i = 0; i < size; ++i) {
0237         threads[i]->wait(2000);
0238         results.insert(threads[i]->result);
0239     }
0240     // each thread should have returned a unique result
0241     QCOMPARE(results.size(), size);
0242     for (int i = 0; i < size; ++i) {
0243         delete threads[i];
0244     }
0245 }
0246 
0247 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72)
0248 // Used by getChildRandSeq... outputs random numbers to stdout and then
0249 // exits the process.
0250 static void childGenRandom(int count)
0251 {
0252     // No logic to 300, just wanted to avoid it accidentally being 2.4B...
0253     if (count <= 0 || count > 300) {
0254         exit(-1);
0255     }
0256 
0257     while (--count > 0) {
0258         std::cout << KRandom::random() << ' ';
0259     }
0260 
0261     std::cout << KRandom::random() << '@';
0262     exit(0);
0263 }
0264 #endif
0265 
0266 // Manually implemented to dispatch to child process if needed to support
0267 // subtests
0268 int main([[maybe_unused]] int argc, char *argv[])
0269 {
0270 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72)
0271     if (argc > 1) {
0272         childGenRandom(std::atoi(argv[1]));
0273         Q_UNREACHABLE();
0274     }
0275 #endif
0276 
0277     binpath = argv[0];
0278     KRandomTest randomTest;
0279     return QTest::qExec(&randomTest);
0280 }
0281 
0282 #include "krandomtest.moc"