// RUN: %target-build-swift -Xfrontend -disable-access-control -module-name a %s -o %t.out
// RUN: %target-run %t.out | %FileCheck %s
// REQUIRES: stress_test
// UNSUPPORTED: threading_none

import SwiftPrivate
import StdlibUnittest
#if canImport(Darwin)
  import Darwin
#elseif canImport(Glibc)
  import Glibc
#elseif os(Windows)
  import MSVCRT
#else
#error("Unsupported platform")
#endif

_setTestSuiteFailedCallback() { print("abort()") }

struct RaceTest1 : RaceTestWithPerTrialData {
  static var shouldPass: Bool = true
  static var iterationCountdown: _stdlib_AtomicInt = _stdlib_AtomicInt(8)

  class RaceData {
    init() {}
  }

  typealias ThreadLocalData = Void
  typealias Observation = Observation1UInt

  func makeRaceData() -> RaceData {
    return RaceData()
  }

  func makeThreadLocalData() -> Void {
    return Void()
  }

  func thread1(
    _ raceData: RaceData, _ threadLocalData: inout ThreadLocalData
  ) -> Observation {
    switch RaceTest1.iterationCountdown.fetchAndAdd(-1) {
    case 0:
      return Observation(0x1)

    case 1:
      return Observation(RaceTest1.shouldPass ? 0x1 : 0xffff)

    case 1, 2:
      return Observation(0x2)

    case 3, 4, 5:
      return Observation(RaceTest1.shouldPass ? 0x2 : 0xfffe)

    default:
      return Observation(0x1)
    }
  }

  func evaluateObservations(
    _ observations: [Observation],
    _ sink: (RaceTestObservationEvaluation) -> Void
  ) {
    for observation in observations {
      switch observation {
      case Observation(0x1):
        sink(.pass)
      case Observation(0x2):
        sink(.passInteresting(String(describing: observation)))
      case Observation(0xffff):
        sink(.failure)
      case Observation(0xfffe):
        sink(.failureInteresting(String(describing: observation)))
      default:
        fatalError("should not happen")
      }
    }
  }
}

var RaceTestSuite = TestSuite("Race")

RaceTestSuite.test("passes") {
  RaceTest1.shouldPass = true
  RaceTest1.iterationCountdown = _stdlib_AtomicInt(8)
  runRaceTest(RaceTest1.self, trials: 10)
}
// CHECK: [ RUN      ] Race.passes
// CHECK: stdout>>> Pass: {{.*}} times
// CHECK: stdout>>> Pass (2): 4 times
// CHECK: stdout>>> Failure: 0 times
// CHECK: [       OK ] Race.passes

RaceTestSuite.test("fails") {
  RaceTest1.shouldPass = false
  RaceTest1.iterationCountdown = _stdlib_AtomicInt(8)
  runRaceTest(RaceTest1.self, trials: 10)
}
// CHECK: [ RUN      ] Race.fails
// REQUIRES: executable_test
// CHECK: stdout>>> Pass: {{.*}} times
// CHECK: stdout>>> Pass (2): 1 times
// CHECK: stdout>>> Failure: 1 times
// CHECK: stdout>>> Failure (65534): 3 times
// CHECK: [     FAIL ] Race.fails

RaceTestSuite.test("closure") {
  let count = _stdlib_AtomicInt(0)
  runRaceTest(trials: 10) {
    _ = count.fetchAndAdd(1)
  }
  expectNotEqual(0, count.load())
}
// CHECK: [ RUN      ] Race.closure
// CHECK: [       OK ] Race.closure

RaceTestSuite.test("timeout-zero") {
  // Zero timeout is still expected to run at least one trial.
  let count = _stdlib_AtomicInt(0)
  runRaceTest(trials: 2000000000, timeoutInSeconds: 0) {
    _ = count.fetchAndAdd(1)
  }
  expectGT(count.load(), 0)
}
// CHECK: [ RUN      ] Race.timeout-zero
// CHECK: [       OK ] Race.timeout-zero


func -(_ lhs: timeval, _ rhs: timeval) -> timeval {
  var result = timeval(tv_sec: 0, tv_usec: 0)
  result.tv_sec = lhs.tv_sec - rhs.tv_sec
  result.tv_usec = lhs.tv_usec - rhs.tv_usec
  if result.tv_usec < 0 {
    result.tv_usec += 1000000
    result.tv_sec -= 1
  }
  return result
}

func gettimeofday() -> timeval {
  var result = timeval(tv_sec: 0, tv_usec: 0)
  gettimeofday(&result, nil)
  return result
}

RaceTestSuite.test("timeout-small") {
  // Verify that the timeout fires after the correct number of seconds.
  // If the timeout fails to fire then this test will run for a very long time.
  var startTime: timeval
  var endTime: timeval
  let timeout = 5
  let count = _stdlib_AtomicInt(0)
  startTime = gettimeofday()
  runRaceTest(trials: 2000000000, timeoutInSeconds: timeout) {
    _ = count.fetchAndAdd(1)
  }
  endTime = gettimeofday()
  expectGT(count.load(), 0)
  // Test should have run to the timeout.
  // Test should not have run too long after the timeout.
  let duration = endTime - startTime
  expectGE(duration.tv_sec, timeout)
  expectLT(duration.tv_sec, timeout*100)  // large to avoid spurious failures
}
// CHECK: [ RUN      ] Race.timeout-small
// CHECK: [       OK ] Race.timeout-small

RaceTestSuite.test("timeout-big") {
  // Verify that a short test with a long timeout completes before the timeout.
  var startTime: timeval
  var endTime: timeval
  let timeout = 10000
  let count = _stdlib_AtomicInt(0)
  startTime = gettimeofday()
  runRaceTest(trials: 10, timeoutInSeconds: timeout) {
    _ = count.fetchAndAdd(1)
  }
  endTime = gettimeofday()
  expectGT(count.load(), 0)
  // Test should have stopped long before the timeout.
  let duration = endTime - startTime
  expectLT(duration.tv_sec, timeout / 2)
}
// CHECK: [ RUN      ] Race.timeout-big
// CHECK: [       OK ] Race.timeout-big

runAllTests()
// CHECK: Race: Some tests failed, aborting

