content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.apache.bookkeeper.common.testing.util;
  
  import java.io.PrintWriter;
  import java.io.StringWriter;
  import java.util.concurrent.CyclicBarrier;
  import java.util.concurrent.locks.Lock;
  import java.util.concurrent.locks.ReentrantLock;
  import org.junit.Assert;
  import org.junit.Test;
  import org.junit.runner.notification.Failure;
  
  /**
   * Test Case for {@link TimedOutTestsListener}.
   */
  public class TestTimedOutTestsListener {
  
      private static class Deadlock {
          private CyclicBarrier barrier = new CyclicBarrier(6);
  
          public Deadlock() {
              DeadlockThread[] dThreads = new DeadlockThread[6];
  
              Monitor a = new Monitor("a");
              Monitor b = new Monitor("b");
              Monitor c = new Monitor("c");
              dThreads[0] = new DeadlockThread("MThread-1", a, b);
              dThreads[1] = new DeadlockThread("MThread-2", b, c);
              dThreads[2] = new DeadlockThread("MThread-3", c, a);
  
              Lock d = new ReentrantLock();
              Lock e = new ReentrantLock();
              Lock f = new ReentrantLock();
  
              dThreads[3] = new DeadlockThread("SThread-4", d, e);
              dThreads[4] = new DeadlockThread("SThread-5", e, f);
              dThreads[5] = new DeadlockThread("SThread-6", f, d);
  
              // make them daemon threads so that the test will exit
              for (int i = 0; i < 6; i++) {
                  dThreads[i].setDaemon(true);
                  dThreads[i].start();
              }
          }
  
          class DeadlockThread extends Thread {
              private Lock lock1 = null;
  
              private Lock lock2 = null;
  
              private Monitor mon1 = null;
  
              private Monitor mon2 = null;
  
              private boolean useSync;
  
              DeadlockThread(String name, Lock lock1, Lock lock2) {
                  super(name);
                  this.lock1 = lock1;
                  this.lock2 = lock2;
                  this.useSync = true;
              }
  
              DeadlockThread(String name, Monitor mon1, Monitor mon2) {
                  super(name);
                  this.mon1 = mon1;
                  this.mon2 = mon2;
                  this.useSync = false;
              }
  
              public void run() {
                  if (useSync) {
                      syncLock();
                  } else {
                      monitorLock();
                  }
              }
  
              private void syncLock() {
                  lock1.lock();
                  try {
                      try {
                          barrier.await();
                      } catch (Exception e) {
                      }
                      goSyncDeadlock();
                  } finally {
                      lock1.unlock();
                  }
              }
  
              private void goSyncDeadlock() {
                  try {
                      barrier.await();
                  } catch (Exception e) {
                  }
                  lock2.lock();
                  throw new RuntimeException("should not reach here.");
              }
  
              private void monitorLock() {
                  synchronized (mon1) {
                      try {
                          barrier.await();
                      } catch (Exception e) {
                      }
                      goMonitorDeadlock();
                  }
              }
  
              private void goMonitorDeadlock() {
                  try {
                      barrier.await();
                  } catch (Exception e) {
                  }
                  synchronized (mon2) {
                      throw new RuntimeException(getName() + " should not reach here.");
                  }
              }
          }
  
          class Monitor {
              String name;
  
              Monitor(String name) {
                  this.name = name;
              }
          }
  
      }
  
      @Test(timeout = 500)
      public void testThreadDumpAndDeadlocks() throws Exception {
          new Deadlock();
          String s = null;
          while (true) {
              s = TimedOutTestsListener.buildDeadlockInfo();
              if (s != null) {
                  break;
              }
              Thread.sleep(100);
          }
  
          Assert.assertEquals(3, countStringOccurrences(s, "BLOCKED"));
  
          Failure failure = new Failure(null, new Exception(TimedOutTestsListener.TEST_TIMED_OUT_PREFIX));
          StringWriter writer = new StringWriter();
          new TimedOutTestsListener(new PrintWriter(writer)).testFailure(failure);
          String out = writer.toString();
  
          Assert.assertTrue(out.contains("THREAD DUMP"));
          Assert.assertTrue(out.contains("DEADLOCKS DETECTED"));
  
          System.out.println(out);
      }
  
      private int countStringOccurrences(String s, String substr) {
          int n = 0;
          int index = 0;
          while ((index = s.indexOf(substr, index) + 1) != 0) {
              n++;
          }
          return n;
      }
  
  }
  