text: /****************************************************************************
  **
  ** Copyright (C) 2016 Thiago Macieira <thiago@kde.org>
  ** Copyright (C) 2016 Intel Corporation.
  ** Contact: https://www.qt.io/licensing/
  **
  ** This file is part of the test suite of the Qt Toolkit.
  **
  ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
  ** Commercial License Usage
  ** Licensees holding valid commercial Qt licenses may use this file in
  ** accordance with the commercial license agreement provided with the
  ** Software or, alternatively, in accordance with the terms contained in
  ** a written agreement between you and The Qt Company. For licensing terms
  ** and conditions see https://www.qt.io/terms-conditions. For further
  ** information use the contact form at https://www.qt.io/contact-us.
  **
  ** GNU General Public License Usage
  ** Alternatively, this file may be used under the terms of the GNU
  ** General Public License version 3 as published by the Free Software
  ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
  ** included in the packaging of this file. Please review the following
  ** information to ensure the GNU General Public License requirements will
  ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
  **
  ** $QT_END_LICENSE$
  **
  ****************************************************************************/
  
  #include <QtCore/QThread>
  #include <QtTest/QtTest>
  
  #if defined(Q_OS_UNIX)
  #include <sys/resource.h>
  #endif
  
  class tst_QGlobalStatic : public QObject
  {
      Q_OBJECT
  
  public Q_SLOTS:
      void initTestCase();
  
  private Q_SLOTS:
      void beforeInitialization();
      void api();
      void constVolatile();
      void exception();
      void catchExceptionAndRetry();
      void threadStressTest();
      void afterDestruction();
  };
  
  void tst_QGlobalStatic::initTestCase()
  {
  #if defined(Q_OS_UNIX)
      // The tests create a lot of threads, which require file descriptors. On systems like
      // OS X low defaults such as 256 as the limit for the number of simultaneously
      // open files is not sufficient.
      struct rlimit numFiles;
      if (getrlimit(RLIMIT_NOFILE, &numFiles) == 0 && numFiles.rlim_cur < 1024) {
          numFiles.rlim_cur = qMin(rlim_t(1024), numFiles.rlim_max);
          setrlimit(RLIMIT_NOFILE, &numFiles);
      }
  #endif
  }
  
  Q_GLOBAL_STATIC_WITH_ARGS(const int, constInt, (42))
  Q_GLOBAL_STATIC_WITH_ARGS(volatile int, volatileInt, (-47))
  
  void otherFunction()
  {
      // never called
      constInt();
      volatileInt();
  }
  
  // do not initialize the following Q_GLOBAL_STATIC
  Q_GLOBAL_STATIC(int, checkedBeforeInitialization)
  void tst_QGlobalStatic::beforeInitialization()
  {
      QVERIFY(!checkedBeforeInitialization.exists());
      QVERIFY(!checkedBeforeInitialization.isDestroyed());
  }
  
  struct Type {
      int i;
  };
  
  Q_GLOBAL_STATIC(Type, checkedAfterInitialization)
  void tst_QGlobalStatic::api()
  {
      // check the API
      QVERIFY((Type *)checkedAfterInitialization);
      QVERIFY(checkedAfterInitialization());
      *checkedAfterInitialization = Type();
      *checkedAfterInitialization() = Type();
  
      checkedAfterInitialization()->i = 47;
      checkedAfterInitialization->i = 42;
      QCOMPARE(checkedAfterInitialization()->i, 42);
      checkedAfterInitialization()->i = 47;
      QCOMPARE(checkedAfterInitialization->i, 47);
  
      QVERIFY(checkedAfterInitialization.exists());
      QVERIFY(!checkedAfterInitialization.isDestroyed());
  }
  
  void tst_QGlobalStatic::constVolatile()
  {
      QCOMPARE(*constInt(), 42);
      QCOMPARE((int)*volatileInt(), -47);
      QCOMPARE(*constInt(), 42);
      QCOMPARE((int)*volatileInt(), -47);
  }
  
  struct ThrowingType
  {
      static QBasicAtomicInt constructedCount;
      static QBasicAtomicInt destructedCount;
      ThrowingType()
      {
          throw 0;
      }
  
      ThrowingType(QBasicAtomicInt &throwControl)
      {
          constructedCount.ref();
          if (throwControl.fetchAndAddRelaxed(-1) != 0)
              throw 0;
      }
      ~ThrowingType() { destructedCount.ref(); }
  };
  
  QBasicAtomicInt ThrowingType::constructedCount = Q_BASIC_ATOMIC_INITIALIZER(0);
  QBasicAtomicInt ThrowingType::destructedCount = Q_BASIC_ATOMIC_INITIALIZER(0);
  
  Q_GLOBAL_STATIC(ThrowingType, throwingGS)
  void tst_QGlobalStatic::exception()
  {
      bool exceptionCaught = false;
      try {
          throwingGS();
      } catch (int) {
          exceptionCaught = true;
      }
      QVERIFY(exceptionCaught);
      QCOMPARE(Q_QGS_throwingGS::guard.load(), 0);
      QVERIFY(!throwingGS.exists());
      QVERIFY(!throwingGS.isDestroyed());
  }
  
  QBasicAtomicInt exceptionControlVar = Q_BASIC_ATOMIC_INITIALIZER(1);
  Q_GLOBAL_STATIC_WITH_ARGS(ThrowingType, exceptionGS, (exceptionControlVar))
  void tst_QGlobalStatic::catchExceptionAndRetry()
  {
      if (exceptionControlVar.load() != 1)
          QSKIP("This test cannot be run more than once");
      ThrowingType::constructedCount.store(0);
      ThrowingType::destructedCount.store(0);
  
      bool exceptionCaught = false;
      try {
          exceptionGS();
      } catch (int) {
          exceptionCaught = true;
      }
      QCOMPARE(ThrowingType::constructedCount.load(), 1);
      QVERIFY(exceptionCaught);
  
      exceptionGS();
      QCOMPARE(ThrowingType::constructedCount.load(), 2);
  }
  
  QBasicAtomicInt threadStressTestControlVar = Q_BASIC_ATOMIC_INITIALIZER(5);
  Q_GLOBAL_STATIC_WITH_ARGS(ThrowingType, threadStressTestGS, (threadStressTestControlVar))
  
  
  void tst_QGlobalStatic::threadStressTest()
  {
      class ThreadStressTestThread: public QThread
      {
      public:
          QReadWriteLock *lock;
          void run()
          {
              QReadLocker l(lock);
              //usleep(qrand() * 200 / RAND_MAX);
              // thundering herd
              try {
                  threadStressTestGS();
              } catch (int) {
              }
          }
      };
  
      ThrowingType::constructedCount.store(0);
      ThrowingType::destructedCount.store(0);
      int expectedConstructionCount = threadStressTestControlVar.load() + 1;
      if (expectedConstructionCount <= 0)
          QSKIP("This test cannot be run more than once");
  
      const int numThreads = 200;
      ThreadStressTestThread threads[numThreads];
      QReadWriteLock lock;
      lock.lockForWrite();
      for (int i = 0; i < numThreads; ++i) {
          threads[i].lock = &lock;
          threads[i].start();
      }
  
      // wait for all threads
      // release the herd
      lock.unlock();
  
      for (int i = 0; i < numThreads; ++i)
          threads[i].wait();
  
      QCOMPARE(ThrowingType::constructedCount.loadAcquire(), expectedConstructionCount);
      QCOMPARE(ThrowingType::destructedCount.loadAcquire(), 0);
  }
  
  Q_GLOBAL_STATIC(int, checkedAfterDestruction)
  void tst_QGlobalStatic::afterDestruction()
  {
      // this test will not produce results now
      // it will simply run some code on destruction (after the global statics have been deleted)
      // if that fails, this will cause a crash
  
      // static destruction is LIFO: so we must add our exit-time code before the
      // global static is used for the first time
      static struct RunAtExit {
          ~RunAtExit() {
              int *ptr = checkedAfterDestruction();
              if (ptr)
                  qFatal("Global static is not null as was expected");
          }
      } runAtExit;
      (void) runAtExit;
  
      *checkedAfterDestruction = 42;
  }
  
  QTEST_APPLESS_MAIN(tst_QGlobalStatic);
  
  #include "tst_qglobalstatic.moc"
  