/*
  This file is part of Valgrind, a dynamic binary instrumentation
  framework.

  Copyright (C) 2008-2008 Google Inc
     opensource@google.com

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License as
  published by the Free Software Foundation; either version 2 of the
  License, or (at your option) any later version.

  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 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., 59 Temple Place, Suite 330, Boston, MA
  02111-1307, USA.

  The GNU General Public License is contained in the file COPYING.
*/

/* Author: Timur Iskhodzhanov <opensource@google.com>

 This file contains a set of Windows-specific unit tests for
 a data race detection tool.
*/

#include <gtest/gtest.h>
#include "test_utils.h"
#include "gtest_fixture_injection.h"

void DummyWorker() {
}

void LongWorker() {
  Sleep(1);
  volatile int i = 1 << 20;
  while(i--);
}

void WriteWorker(int *var) {
  LongWorker();
  *var = 42;
}

void VeryLongWriteWorker(int *var) {
  Sleep(1000);
  *var = 42;
}

TEST(NegativeTests, WindowsCreateThreadFailureTest) {  // {{{1
  HANDLE t = ::CreateThread(0, -1,
                           (LPTHREAD_START_ROUTINE)DummyWorker, 0, 0, 0);
  CHECK(t == 0);
}

TEST(NegativeTests, DISABLED_WindowsCreateThreadSuspendedTest) {  // {{{1
  // Hangs under TSan, see
  // http://code.google.com/p/data-race-test/issues/detail?id=61
  int *var = new int;
  HANDLE t = ::CreateThread(0, 0,
                           (LPTHREAD_START_ROUTINE)WriteWorker, var,
                           CREATE_SUSPENDED, 0);
  CHECK(t > 0);
  EXPECT_EQ(WAIT_TIMEOUT,  ::WaitForSingleObject(t, 200));
  *var = 1;
  EXPECT_EQ(1, ResumeThread(t));
  EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(t, INFINITE));
  EXPECT_EQ(42, *var);
  delete var;
}

TEST(NegativeTests, WindowsThreadStackSizeTest) {  // {{{1
// Just spawn few threads with different stack sizes.
  int sizes[3] = {1 << 19, 1 << 21, 1 << 22};
  for (int i = 0; i < 3; i++) {
    HANDLE t = ::CreateThread(0, sizes[i],
                             (LPTHREAD_START_ROUTINE)DummyWorker, 0, 0, 0);
    CHECK(t > 0);
    EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(t, INFINITE));
    CloseHandle(t);
  }
}

TEST(NegativeTests, WindowsJoinWithTimeout) {  // {{{1
  HANDLE t = ::CreateThread(0, 0,
                            (LPTHREAD_START_ROUTINE)LongWorker, 0, 0, 0);
  ASSERT_TRUE(t > 0);
  EXPECT_EQ(WAIT_TIMEOUT,  ::WaitForSingleObject(t, 1));
  EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(t, INFINITE));
  CloseHandle(t);
}

TEST(NegativeTests, HappensBeforeOnThreadJoin) {  // {{{1
  int *var = new int;
  HANDLE t = ::CreateThread(0, 0,
                            (LPTHREAD_START_ROUTINE)WriteWorker, var, 0, 0);
  ASSERT_TRUE(t > 0);
  // Calling WaitForSingleObject two times to make sure the H-B arc
  // is created on the second call. There was a bug that the thread handle
  // was deleted even when WaitForSingleObject timed out.
  EXPECT_EQ(WAIT_TIMEOUT,  ::WaitForSingleObject(t, 1));
  EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(t, INFINITE));
  EXPECT_EQ(*var, 42);
  CloseHandle(t);
  delete var;
}

TEST(NegativeTests, HappensBeforeOnThreadJoinTidReuse) {  // {{{1
  HANDLE t1 = ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)DummyWorker, 0, 0, 0);
  CloseHandle(t1);
  Sleep(1000);

  int *var = new int;
  HANDLE t2 = ::CreateThread(0, 0,
                             (LPTHREAD_START_ROUTINE)WriteWorker, var, 0, 0);
  printf("t1 = %d, t2 = %d\n");
  CHECK(t2 > 0);
  CHECK(WAIT_OBJECT_0 == ::WaitForSingleObject(t2, INFINITE));
  CHECK(*var == 42);
  delete var;
}

TEST(NegativeTests, WaitForMultipleObjectsWaitAllTest) {
  int var1 = 13,
      var2 = 13;
  HANDLE t1 = ::CreateThread(0, 0,
                            (LPTHREAD_START_ROUTINE)WriteWorker, &var1, 0, 0),
         t2 = ::CreateThread(0, 0,
                            (LPTHREAD_START_ROUTINE)WriteWorker, &var2, 0, 0);
  ASSERT_TRUE(t1 > 0);
  ASSERT_TRUE(t2 > 0);

  HANDLE handles[2] = {t1, t2};
  // Calling WaitForMultipleObjectsTest two times to make sure the H-B arc
  // are created on the second call.
  EXPECT_EQ(WAIT_TIMEOUT,  ::WaitForMultipleObjects(2, handles, TRUE, 1));
  EXPECT_EQ(WAIT_OBJECT_0, ::WaitForMultipleObjects(2, handles, TRUE, INFINITE));
  EXPECT_EQ(var1, 42);
  EXPECT_EQ(var2, 42);
  CloseHandle(t1);
  CloseHandle(t2);
}

TEST(NegativeTests, WaitForMultipleObjectsWaitOneTest) {
  int var1 = 13,
      var2 = 13;
  HANDLE t1 = ::CreateThread(0, 0,
                            (LPTHREAD_START_ROUTINE)VeryLongWriteWorker, &var1, 0, 0),
         t2 = ::CreateThread(0, 0,
                            (LPTHREAD_START_ROUTINE)WriteWorker, &var2, 0, 0);
  ASSERT_TRUE(t1 > 0);
  ASSERT_TRUE(t2 > 0);

  HANDLE handles[2] = {t1, t2};
  // Calling WaitForMultipleObjectsTest two times to make sure the H-B arc
  // are created on the second call.
  EXPECT_EQ(WAIT_TIMEOUT,  ::WaitForMultipleObjects(2, handles, FALSE, 1));
  EXPECT_EQ(WAIT_OBJECT_0 + 1, ::WaitForMultipleObjects(2, handles, FALSE, INFINITE));
  EXPECT_EQ(var2, 42);
  EXPECT_EQ(WAIT_OBJECT_0, ::WaitForMultipleObjects(1, handles, FALSE, INFINITE));
  EXPECT_EQ(var1, 42);
  CloseHandle(t1);
  CloseHandle(t2);
}

namespace RegisterWaitForSingleObjectTest {  // {{{1
StealthNotification *n = NULL;
HANDLE monitored_object = NULL;

void SignalStealthNotification() {
  n->wait();
  SetEvent(monitored_object);
}

void foo() { }

void CALLBACK DoneWaiting(void *param, BOOLEAN timed_out) {
  int *i = (int*)param;
  foo();  // make sure this function has a call. See issue 24.
  (*i)++;
}

TEST(NegativeTests, WindowsRegisterWaitForSingleObjectTest) {  // {{{1
  // These are very tricky false positive found while testing Chromium.
  //
  // Report #1:
  //   Everything after UnregisterWaitEx(*, INVALID_HANDLE_VALUE) happens-after
  //   execution of DoneWaiting callback. Currently, we don't catch this h-b.
  //
  // Report #2:
  //   The callback thread is re-used between Registet/Unregister/Register calls
  //   so we miss h-b between "int *obj = ..." and DoneWaiting on the second
  //   iteration.
  for (int i = 0; i < 2; i++) {
    n = new StealthNotification();
    int *obj = new int(0);
    HANDLE wait_object = NULL;

    monitored_object = ::CreateEvent(NULL, false, false, NULL);
    printf("monitored_object = %p\n", monitored_object);
    MyThread mt(SignalStealthNotification);
    mt.Start();
    ANNOTATE_TRACE_MEMORY(obj);
    CHECK(0 != ::RegisterWaitForSingleObject(&wait_object, monitored_object,
                                             DoneWaiting, obj, INFINITE,
                                             WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE));
    printf("wait_object      = %p\n", wait_object);
    n->signal();
    mt.Join();
    Sleep(1000);
    CHECK(0 != ::UnregisterWaitEx(wait_object, INVALID_HANDLE_VALUE));
    (*obj)++;
    CHECK(*obj == 2);
    CloseHandle(monitored_object);
    delete n;
    delete obj;
  }
}
}

namespace QueueUserWorkItemTests {
DWORD CALLBACK Callback(void *param) {
  int *ptr = (int*)param;
  (*ptr)++;
  delete ptr;
  return 0;
}

TEST(NegativeTests, WindowsQueueUserWorkItemTest) {
  // False positive:
  //   The callback thread is allocated from a thread pool and can be re-used.
  //   As a result, we may miss h-b between "int *obj = ..." and Callback execution.
  for (int i = 0; i < 5; i++) {
    int *obj = new int(0);
    ANNOTATE_TRACE_MEMORY(obj);
    CHECK(QueueUserWorkItem(Callback, obj, i % 2 ? WT_EXECUTELONGFUNCTION : 0));
    Sleep(500);
  }
}

int GLOB = 42;

DWORD CALLBACK Callback2(void *param) {
  StealthNotification *ptr = (StealthNotification*)param;
  GLOB++;
  Sleep(100);
  ptr->signal();
  return 0;
}

TEST(PositiveTests, WindowsQueueUserWorkItemTest) {
  ANNOTATE_EXPECT_RACE_FOR_TSAN(&GLOB, "PositiveTests.WindowsQueueUserWorkItemTest");

  const int N_THREAD = 5;
  StealthNotification n[N_THREAD];

  for (int i = 0; i < N_THREAD; i++)
    CHECK(QueueUserWorkItem(Callback2, &n[i], i % 2 ? WT_EXECUTELONGFUNCTION : 0));

  for (int i = 0; i < N_THREAD; i++)
    n[i].wait();
}
}

namespace WindowsCriticalSectionTest {  // {{{1
CRITICAL_SECTION cs;

TEST(NegativeTests, WindowsCriticalSectionTest) {
  InitializeCriticalSection(&cs);
  EnterCriticalSection(&cs);
  TryEnterCriticalSection(&cs);
  LeaveCriticalSection(&cs);
  DeleteCriticalSection(&cs);
}
}  // namespace


namespace WindowsSRWLockTest {  // {{{1
#if WINVER >= 0x0600 // Vista or Windows Server 2000
SRWLOCK SRWLock;
int *obj;

void Reader() {
  AcquireSRWLockShared(&SRWLock);
  CHECK(*obj <= 2 && *obj >= 0);
  ReleaseSRWLockShared(&SRWLock);
}

void Writer() {
  AcquireSRWLockExclusive(&SRWLock);
  (*obj)++;
  ReleaseSRWLockExclusive(&SRWLock);
}

#if 0  // This doesn't work in older versions of Windows.
void TryReader() {
  if (TryAcquireSRWLockShared(&SRWLock)) {
    CHECK(*obj <= 2 && *obj >= 0);
    ReleaseSRWLockShared(&SRWLock);
  }
}

void TryWriter() {
  if (TryAcquireSRWLockExclusive(&SRWLock)) {
    (*obj)++;
    ReleaseSRWLockExclusive(&SRWLock);
  }
}
#endif

TEST(NegativeTests, WindowsSRWLockTest) {
  InitializeSRWLock(&SRWLock);
  obj = new int(0);
  ANNOTATE_TRACE_MEMORY(obj);
  MyThreadArray t(Reader, Writer, Reader, Writer);
  t.Start();
  t.Join();
  AcquireSRWLockShared(&SRWLock);
  ReleaseSRWLockShared(&SRWLock);
  CHECK(*obj == 2);
  delete obj;
}

TEST(NegativeTests, WindowsSRWLockHackyInitializationTest) {
  // A similar pattern has been found on Chromium media_unittests
  InitializeSRWLock(&SRWLock);
  AcquireSRWLockExclusive(&SRWLock);
  // Leave the lock acquired

  // Reset the lock
  InitializeSRWLock(&SRWLock);
  obj = new int(0);
  MyThreadArray t(Reader, Writer, Reader, Writer);
  t.Start();
  t.Join();
  delete obj;
}
#endif // WINVER >= 0x0600
}  // namespace

namespace WindowsConditionVariableSRWTest {  // {{{1
#if WINVER >= 0x0600 // Vista or Windows Server 2000
SRWLOCK SRWLock;
CONDITION_VARIABLE cv;
bool cond;
int *obj;

StealthNotification n;

void WaiterSRW() {
  *obj = 1;
  n.wait();
  AcquireSRWLockExclusive(&SRWLock);
  cond = true;
  WakeConditionVariable(&cv);
  ReleaseSRWLockExclusive(&SRWLock);
}

void WakerSRW() {
  AcquireSRWLockExclusive(&SRWLock);
  n.signal();
  while (!cond) {
    SleepConditionVariableSRW(&cv, &SRWLock, 10, 0);
  }
  ReleaseSRWLockExclusive(&SRWLock);
  CHECK(*obj == 1);
  *obj = 2;
}

TEST(NegativeTests, WindowsConditionVariableSRWTest) {
  InitializeSRWLock(&SRWLock);
  InitializeConditionVariable(&cv);
  obj = new int(0);
  cond = false;
  ANNOTATE_TRACE_MEMORY(obj);
  MyThreadArray t(WaiterSRW, WakerSRW);
  t.Start();
  t.Join();
  CHECK(*obj == 2);
  delete obj;
}
#endif // WINVER >= 0x0600
}  // namespace


namespace WindowsInterlockedListTest {  // {{{1
SLIST_HEADER list;

struct Item {
  SLIST_ENTRY entry;
  int foo;
};

void Push() {
  Item *item = new Item;
  item->foo = 42;
  InterlockedPushEntrySList(&list, (PSINGLE_LIST_ENTRY)item);
}

void Pop() {
  Item *item;
  while (0 == (item = (Item*)InterlockedPopEntrySList(&list))) {
    Sleep(1);
  }
  CHECK(item->foo == 42);
  delete item;
}

TEST(NegativeTests, WindowsInterlockedListTest) {
  InitializeSListHead(&list);
  MyThreadArray t(Push, Pop);
  t.Start();
  t.Join();
}

}  // namespace

namespace FileSystemReports {  // {{{1

// This is a test for the flaky report found in
// Chromium net_unittests.
//
// Looks like the test is sensitive to memory allocations / scheduling order,
// so you shouldn't run other tests while investigating the issue.
// The report is ~50% flaky.

HANDLE hDone = NULL;

void CreateFileJob() {
  HANDLE hFile = CreateFileA("ZZZ\\tmpfile", GENERIC_READ | GENERIC_WRITE,
                      FILE_SHARE_READ, NULL, CREATE_ALWAYS,
                      FILE_ATTRIBUTE_NORMAL, NULL);
  CloseHandle(hFile);
  DWORD attr1 = GetFileAttributes("ZZZ");  // "Concurrent write" is here.
}

DWORD CALLBACK PrintDirectoryListingJob(void *param) {
  Sleep(500);
  WIN32_FIND_DATAA data;

  // "Current write" is here.
  HANDLE hFind = FindFirstFileA("ZZZ/*", &data);
  CHECK(hFind != INVALID_HANDLE_VALUE);

  CloseHandle(hFind);
  SetEvent(hDone);
  return 0;
}

// This test is not very friendly to bots environment, so you should only
// run it manually.
TEST(NegativeTests, DISABLED_CreateFileVsFindFirstFileTest) {
  hDone = ::CreateEvent(NULL, false, false, NULL);

  ::CreateDirectory("ZZZ", NULL);

  // Run PrintDirectoryListingJob in a concurrent thread.
  CHECK(::QueueUserWorkItem(PrintDirectoryListingJob, NULL,
                          WT_EXECUTELONGFUNCTION));
  CreateFileJob();

  ::WaitForSingleObject(hDone, INFINITE);
  ::CloseHandle(hDone);
  CHECK(::DeleteFile("ZZZ\\tmpfile"));
  CHECK(::RemoveDirectory("ZZZ"));
}

}  //namespace

namespace WindowsAtomicsTests { // {{{1
// This test should not give us any reports if compiled with proper MSVS flags.
// The Atomic_{Read,Write} functions are ignored in racecheck_unittest.ignore

int GLOB = 42;

inline int Atomic_Read(volatile const int* ptr) {
  // MSVS volatile gives us atomicity.
  int value = *ptr;
  return value;
}

inline void Atomic_Write(volatile int* ptr, int value) {
  // MSVS volatile gives us atomicity.
  *ptr = value;
}

void Worker() {
  int value = Atomic_Read(&GLOB);
  Atomic_Write(&GLOB, ~value);
}

TEST(NegativeTests, WindowsAtomicsTests) {
  MyThreadArray mta(Worker, Worker);
  mta.Start();
  mta.Join();
}

}  // namespace

namespace WindowsSemaphoreTests {
void Poster(int *var, HANDLE sem) {
  *var = 1;
  ReleaseSemaphore(sem, 1, NULL);
}

void Waiter(int *var, HANDLE sem) {
  DWORD ret = ::WaitForSingleObject(sem, INFINITE);
  ASSERT_EQ(ret, WAIT_OBJECT_0);
  EXPECT_EQ(*var, 1);
}

TEST(NegativeTests, SimpleSemaphoreTest) {
  HANDLE sem = CreateSemaphore(NULL,
                               0 /* initial count */,
                               20 /* max count */,
                               NULL);
  ASSERT_TRUE(sem != NULL);

  {
    int VAR = 0;
    ThreadPool tp(2);
    tp.StartWorkers();
    tp.Add(NewCallback(Waiter, &VAR, sem));
    tp.Add(NewCallback(Poster, &VAR, sem));
  }

  CloseHandle(sem);
}

TEST(NegativeTests, DISABLED_SemaphoreNameReuseTest) {
  // TODO(timurrrr): Semaphore reuse is not yet understood by TSan.
  const char NAME[] = "SemaphoreZZZ";
  HANDLE h1 = CreateSemaphore(NULL, 0, 10, NAME),
         h2 = CreateSemaphore(NULL, 0, 15, NAME);
  ASSERT_TRUE(h1 != NULL);
  ASSERT_TRUE(h2 != NULL);

  // h1 and h2 refer to the same semaphore but are not equal.
  EXPECT_NE(h1, h2);

  {
    int VAR = 0;
    ThreadPool tp(2);
    tp.StartWorkers();
    tp.Add(NewCallback(Waiter, &VAR, h1));
    tp.Add(NewCallback(Poster, &VAR, h2));
  }

  CloseHandle(h1);
  CloseHandle(h2);
}

}

namespace HandleReuseTests {

void Waker(int *var, HANDLE h) {
  *var = 1;
  SetEvent(h);
}

void Waiter(int *var, HANDLE h) {
  DWORD ret = ::WaitForSingleObject(h, INFINITE);
  ASSERT_EQ(ret, WAIT_OBJECT_0);
  EXPECT_EQ(*var, 1);
}

TEST(NegativeTests, DISABLED_EventHandleReuseTest) {
  // TODO(timurrrr): DuplicateHandle is not yet understood by TSan.
  HANDLE h1 = CreateEvent(NULL, false, false, NULL);
  ASSERT_TRUE(h1 != NULL);
  HANDLE h2 = NULL;
  DuplicateHandle(GetCurrentProcess(), h1,
                  GetCurrentProcess(), &h2,
                  0 /* access */, FALSE /* inherit*/, DUPLICATE_SAME_ACCESS);
  ASSERT_TRUE(h2 != NULL);

  // h1 and h2 refer to the same Event but are not equal.
  EXPECT_NE(h1, h2);

  {
    int VAR = 0;
    ThreadPool tp(2);
    tp.StartWorkers();
    tp.Add(NewCallback(Waiter, &VAR, h1));
    tp.Add(NewCallback(Waker,  &VAR, h2));
  }

  CloseHandle(h1);
  CloseHandle(h2);
}

}
// End {{{1
 // vim:shiftwidth=2:softtabstop=2:expandtab:foldmethod=marker