Home | History | Annotate | Download | only in tests
      1 /*
      2  * Copyright 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #define LOG_TAG "EGLCleanup"
     18 #include <android/log.h>
     19 #include <jni.h>
     20 
     21 #include <EGL/egl.h>
     22 #include <EGL/eglext.h>
     23 
     24 #include <gtest/gtest.h>
     25 
     26 #include <GLTestHelper.h>
     27 
     28 #include <pthread.h>
     29 
     30 #define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
     31 #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
     32 #define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
     33 #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
     34 
     35 namespace android {
     36 
     37 /**
     38  * Tests EGL cleanup edge cases.
     39  */
     40 class EGLCleanupTest : public ::testing::Test {
     41 protected:
     42     EGLCleanupTest() {}
     43 
     44     virtual void SetUp() {
     45         // Termination of a terminated display is defined to be a no-op.
     46         // Android uses a refcounted implementation, so terminate it a few
     47         // times to make sure it's really dead.  Without this, we might not
     48         // get all the way into the driver eglTerminate implementation
     49         // when we call eglTerminate.
     50         EGLDisplay disp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
     51         if (disp != EGL_NO_DISPLAY) {
     52             ALOGD("speculative terminate");
     53             eglTerminate(disp);
     54             eglTerminate(disp);
     55             eglTerminate(disp);
     56         }
     57     }
     58     virtual void TearDown() {}
     59 };
     60 
     61 /**
     62  * Perform an operation and then start a new thread.
     63  *
     64  * The trick here is that some code may be helpfully releasing storage in
     65  * pthread_key destructors.  Those run after the thread returns out of the
     66  * initial function, but before the thread fully exits.  We want them to
     67  * run concurrently with the next thread's initialization so we can confirm
     68  * that the specified behavior of eglTerminate vs. eglInitialize holds.
     69  */
     70 class ChainedThread {
     71 public:
     72     enum TestType {
     73         TEST_CORRECT,
     74         TEST_NO_RELEASE_CURRENT
     75     };
     76 
     77     ChainedThread(TestType testType) : mEglDisplay(EGL_NO_DISPLAY),
     78             mEglSurface(EGL_NO_SURFACE), mEglContext(EGL_NO_CONTEXT),
     79             mTestType(testType), mIteration(0), mResult(true) {
     80         pthread_mutex_init(&mLock, NULL);
     81         pthread_cond_init(&mCond, NULL);
     82     }
     83     ~ChainedThread() {
     84         // could get fancy and clean up the mutex
     85     }
     86 
     87     /* start here */
     88     bool start() {
     89         lock();
     90         bool result = startThread_l();
     91         unlock();
     92         return result;
     93     }
     94 
     95     /* waits until test is done; when finished, call getResult() */
     96     bool waitForEnd() {
     97         lock();
     98         int err = pthread_cond_wait(&mCond, &mLock);
     99         if (err != 0) {
    100             ALOGW("pthread_cond_wait failed: %d", err);
    101         }
    102         unlock();
    103         return err == 0;
    104     }
    105 
    106     /* returns the result; true means success */
    107     bool getResult() {
    108         return mResult;
    109     }
    110 
    111 private:
    112     enum { MAX_ITERATIONS = 1000 };
    113 
    114     EGLDisplay mEglDisplay;
    115     EGLSurface mEglSurface;
    116     EGLContext mEglContext;
    117 
    118     TestType mTestType;
    119     int mIteration;
    120     bool mResult;
    121     pthread_mutex_t mLock;
    122     pthread_cond_t mCond;
    123 
    124     // Assertions set a flag in Java and return from the current method (which
    125     // must be declared to return void).  They do not throw a C++ exception.
    126     //
    127     // Because we're running in a separate thread, which is not attached to
    128     // the VM, the assert macros don't help us much.  We could attach to the
    129     // VM (by linking to libdvm.so and calling a global function), but the
    130     // assertions won't cause the test to stop, which makes them less valuable.
    131     //
    132     // So instead we just return a boolean out of functions that can fail.
    133 
    134     /* call this to start the test */
    135     bool startThread_l() {
    136         pthread_attr_t attr;
    137         pthread_attr_init(&attr);
    138         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    139 
    140         pthread_t newThread;
    141         int err = pthread_create(&newThread, &attr, ChainedThread::func,
    142                 (void*) this);
    143         return (err == 0);
    144     }
    145 
    146     /* thread entry point */
    147     static void* func(void* arg) {
    148         ChainedThread* obj = static_cast<ChainedThread*>(arg);
    149         obj->doWork();
    150         return NULL;
    151     }
    152 
    153     bool lock() {
    154         int err = pthread_mutex_lock(&mLock);
    155         if (err != 0) {
    156             ALOGW("pthread_mutex_lock failed: %d", err);
    157         }
    158         return err == 0;
    159     }
    160 
    161     bool unlock() {
    162         int err = pthread_mutex_unlock(&mLock);
    163         if (err != 0) {
    164             ALOGW("pthread_mutex_unlock failed: %d", err);
    165         }
    166         return err == 0;
    167     }
    168 
    169     /* main worker */
    170     void doWork() {
    171         lock();
    172 
    173         if ((mIteration % 25) == 0) {
    174             ALOGD("iteration %d\n", mIteration);
    175         }
    176 
    177         mIteration++;
    178         bool result = runTest_l();
    179         if (!result) {
    180             ALOGW("failed at iteration %d, stopping test", mIteration);
    181             mResult = false;
    182             mIteration = MAX_ITERATIONS;
    183         }
    184 
    185         if (mIteration < MAX_ITERATIONS) {
    186             // still going, try to start the next one
    187             if (!startThread_l()) {
    188                 ALOGW("Unable to start thread at iter=%d", mIteration);
    189                 mResult = false;
    190                 mIteration = MAX_ITERATIONS;
    191             }
    192         }
    193 
    194         if (mIteration >= MAX_ITERATIONS) {
    195             ALOGD("Test ending, signaling main thread");
    196             pthread_cond_signal(&mCond);
    197         }
    198 
    199         unlock();
    200     }
    201 
    202     /* setup, use, release EGL */
    203     bool runTest_l() {
    204         if (!eglSetup()) {
    205             return false;
    206         }
    207         if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
    208                 mEglContext))
    209         {
    210             ALOGW("eglMakeCurrent failed: 0x%x", eglGetError());
    211             return false;
    212         }
    213         if (!eglRelease_l()) {
    214             return false;
    215         }
    216 
    217         return true;
    218     }
    219 
    220     /*
    221      * Sets up EGL.  Creates a 1280x720 pbuffer, which is large enough to
    222      * cause a rapid and highly visible memory leak if we fail to discard it.
    223      */
    224     bool eglSetup() {
    225         static const EGLint kConfigAttribs[] = {
    226                 EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
    227                 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
    228                 EGL_RED_SIZE, 8,
    229                 EGL_GREEN_SIZE, 8,
    230                 EGL_BLUE_SIZE, 8,
    231                 EGL_NONE
    232         };
    233         static const EGLint kContextAttribs[] = {
    234                 EGL_CONTEXT_CLIENT_VERSION, 2,
    235                 EGL_NONE
    236         };
    237         static const EGLint kPbufferAttribs[] = {
    238                 EGL_WIDTH, 1280,
    239                 EGL_HEIGHT, 720,
    240                 EGL_NONE
    241         };
    242 
    243         //usleep(25000);
    244 
    245         mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    246         if (mEglDisplay == EGL_NO_DISPLAY) {
    247             ALOGW("eglGetDisplay failed: 0x%x", eglGetError());
    248             return false;
    249         }
    250 
    251         EGLint majorVersion, minorVersion;
    252         if (!eglInitialize(mEglDisplay, &majorVersion, &minorVersion)) {
    253             ALOGW("eglInitialize failed: 0x%x", eglGetError());
    254             return false;
    255         }
    256 
    257         EGLConfig eglConfig;
    258         EGLint numConfigs = 0;
    259         if (!eglChooseConfig(mEglDisplay, kConfigAttribs, &eglConfig,
    260                 1, &numConfigs)) {
    261             ALOGW("eglChooseConfig failed: 0x%x", eglGetError());
    262             return false;
    263         }
    264 
    265         mEglSurface = eglCreatePbufferSurface(mEglDisplay, eglConfig,
    266                 kPbufferAttribs);
    267         if (mEglSurface == EGL_NO_SURFACE) {
    268             ALOGW("eglCreatePbufferSurface failed: 0x%x", eglGetError());
    269             return false;
    270         }
    271 
    272         mEglContext = eglCreateContext(mEglDisplay, eglConfig, EGL_NO_CONTEXT,
    273                 kContextAttribs);
    274         if (mEglContext == EGL_NO_CONTEXT) {
    275             ALOGW("eglCreateContext failed: 0x%x", eglGetError());
    276             return false;
    277         }
    278 
    279         return true;
    280     }
    281 
    282     /*
    283      * Releases EGL.  How we do that depends on the type of test we're
    284      * running.
    285      */
    286     bool eglRelease_l() {
    287         if (mEglDisplay == EGL_NO_DISPLAY) {
    288             ALOGW("No display to release");
    289             return false;
    290         }
    291 
    292         switch (mTestType) {
    293         case TEST_CORRECT:
    294             eglTerminate(mEglDisplay);
    295             eglReleaseThread();
    296             break;
    297         case TEST_NO_RELEASE_CURRENT:
    298             eglDestroyContext(mEglDisplay, mEglContext);
    299             eglDestroySurface(mEglDisplay, mEglSurface);
    300             eglTerminate(mEglDisplay);
    301             break;
    302         default:
    303             ALOGE("Unknown test type %d", mTestType);
    304             break;
    305         }
    306 
    307         int err = eglGetError();
    308         if (err != EGL_SUCCESS) {
    309             ALOGW("eglRelease failed: 0x%x", err);
    310             return false;
    311         }
    312         return true;
    313     }
    314 };
    315 
    316 
    317 /* do things correctly */
    318 TEST_F(EGLCleanupTest, TestCorrect) {
    319     ALOGI("Starting TEST_CORRECT");
    320     ChainedThread cht(ChainedThread::TEST_CORRECT);
    321 
    322     // start initial thread
    323     ASSERT_TRUE(cht.start());
    324 
    325     // wait for the end
    326     cht.waitForEnd();
    327     bool result = cht.getResult();
    328     ASSERT_TRUE(result);
    329 }
    330 
    331 /* try it without un-currenting the surfaces and context
    332 TEST _F(EGLCleanupTest, TestNoReleaseCurrent) {
    333     ALOGI("Starting TEST_NO_RELEASE_CURRENT");
    334     ChainedThread cht(ChainedThread::TEST_NO_RELEASE_CURRENT);
    335 
    336     // start initial thread
    337     ASSERT_TRUE(cht.start());
    338 
    339     // wait for the end
    340     cht.waitForEnd();
    341     bool result = cht.getResult();
    342     ASSERT_TRUE(result);
    343 }
    344 */
    345 
    346 } // namespace android
    347