/* Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License, version 2.0, for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */

/**
  This is a unit test written to demonstrate a race issue in debug_sync
  functionality which resulted in wait thread getting timed out. This
  happens because we use a single global variable to identify the thread
  that has signalled. Before the wait could process the signal from the
  signal thread, another signal thread can overwrite this global signal
  value thus making the wait thread to lose this signal and  timeout
  waiting for a signal that it has already lost.
*/

#include "my_global.h"
#include <gtest/gtest.h>
#include "test_utils.h"
#include "thread_utils.h"
#include "debug_sync.h"
#include "my_dbug.h"
#include <string>

#if defined(ENABLED_DEBUG_SYNC)
extern uchar* debug_sync_value_ptr(THD* thd);

namespace debug_sync_unittest {

using thread::Notification;
using thread::Thread;
using my_testing::Server_initializer;
using my_testing::Mock_error_handler;


class DebugSyncTest : public ::testing::Test
{
protected:
  DebugSyncTest() {}

  void SetUp()
  {
    // set debug sync timeout of 60 seconds.
    opt_debug_sync_timeout= 60;
    debug_sync_init();
  }

  void TearDown()
  {
     debug_sync_end();
     opt_debug_sync_timeout= 0;
  }
};


/*
  Set up a debug sync action thread. Depending on type of action, the  thread
  serves different purpose. In the case where it involves wait_for action, it
  serves the purpose of a waiting thread that wait at a 'sync_point' in it's
  thread of control for named signal event. The debug sync action could
  be a signal event, in which case it assumes the role of signalling thread
  that signals an event identified by signal name to wake a waiting thread.
*/
class DebugSyncThread : public Thread
{
public:
  DebugSyncThread(Notification *go,
                    bool wait_thread,
                    std::string action)
    : m_go(go),
      m_wait_thread(wait_thread),
      m_action(action)
  {}

  virtual void run()
  {
    m_initializer.SetUp();
    m_thd= m_initializer.thd();
    m_go->wait_for_notification();

    if (m_wait_thread)
    {
      std::string sync_point("sync_point ");
      std::string action_str= sync_point + m_action;
      Mock_error_handler error_handler(m_thd, 0);

      debug_sync_set_action(m_thd, action_str.c_str(), action_str.length());
      wait_point();

      // The above should not generate any warnings.
      EXPECT_EQ(0, error_handler.handle_called());
    }
    else
    {
      debug_sync_set_action(m_thd, m_action.c_str(), m_action.length());
    }

    m_initializer.TearDown();
  }

private:
  Notification *m_go;
  Server_initializer m_initializer;
  THD *m_thd; // THD context required to hold debug_sync context.
  bool m_wait_thread; // indicate if it is a wait thread.
  std::string m_action; // indicate type of action WAIT_FOR or SIGNAL.

  void wait_point()
  {
    DEBUG_SYNC(m_thd,"sync_point");
  }
};


/*
  Start a wait thread waiting for signal X1  and two signal threads
  one signalling event X1 and other signalling an event X2. Notify
  all of them to start at once.
*/
TEST_F(DebugSyncTest, DebugSyncSignalWaitTests)
{
  Notification go[3];

  /* wait thread */
  DebugSyncThread wait_thread(&go[0], true, std::string("WAIT_FOR X1"));
  /* signal thread */
  DebugSyncThread signal_thread1(&go[1], false,
                                   std::string("now signal X1"));
  DebugSyncThread signal_thread2(&go[2], false,
                                   std::string("now signal X2"));

  wait_thread.start();
  signal_thread1.start();
  signal_thread2.start();

  // Notify all threads
  go[0].notify();
  go[1].notify();
  go[2].notify();

  // Wait for all threads to finish.
  wait_thread.join();
  signal_thread1.join();
  signal_thread2.join();
}

// Test debug_sync_value_ptr
TEST_F(DebugSyncTest, DebugSyncValuesTest)
{
  std::string action;
  Server_initializer server_initializer;
  THD *thd;

  server_initializer.SetUp();
  thd= server_initializer.thd();

  // Ensure that we have a empty signal list at startup.
  EXPECT_STREQ("ON - signals: ''",
               reinterpret_cast<char*>(debug_sync_value_ptr(thd)));

  // Set up signalling actions and ensure the signals list reflect it.
  action= "now signal x1";
  debug_sync_set_action(thd, action.c_str(), action.length());
  EXPECT_STREQ("ON - signals: 'x1'",
               reinterpret_cast<char*>(debug_sync_value_ptr(thd)));

  action= "now signal x2";
  debug_sync_set_action(thd, action.c_str(), action.length());
  EXPECT_STREQ("ON - signals: 'x1,x2'",
               reinterpret_cast<char*>(debug_sync_value_ptr(thd)));

  action= "now signal x3";
  debug_sync_set_action(thd, action.c_str(), action.length());
  EXPECT_STREQ("ON - signals: 'x1,x2,x3'",
               reinterpret_cast<char*>(debug_sync_value_ptr(thd)));

  // Ensure the signal list is empty after reset.
  action= "reset";
  debug_sync_set_action(thd, action.c_str(), action.length());
  EXPECT_STREQ("ON - signals: ''",
               reinterpret_cast<char*>(debug_sync_value_ptr(thd)));

  server_initializer.TearDown();
}


}
#endif
