Allow negative timeouts in delay, withTimeout and onTimeout on JVM

Fixes #310
diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt
index 25775d1..bd9ec4a 100644
--- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt
+++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt
@@ -35,7 +35,6 @@
      * immediately resumes with [CancellationException].
      */
     suspend fun delay(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) {
-        require(time >= 0) { "Delay time $time cannot be negative" }
         if (time <= 0) return // don't delay
         return suspendCancellableCoroutine { scheduleResumeAfterDelay(time, unit, it) }
     }
@@ -99,7 +98,6 @@
  * @param unit time unit.
  */
 public suspend fun delay(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) {
-    require(time >= 0) { "Delay time $time cannot be negative" }
     if (time <= 0) return // don't delay
     return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
         cont.context.delay.scheduleResumeAfterDelay(time, unit, cont)
diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
index 93a522e..e06323d 100644
--- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
+++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
@@ -61,7 +61,6 @@
  * @param unit timeout unit (milliseconds by default)
  */
 public suspend fun <T> withTimeout(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend CoroutineScope.() -> T): T {
-    require(time >= 0) { "Timeout time $time cannot be negative" }
     if (time <= 0L) throw CancellationException("Timed out immediately")
     return suspendCoroutineOrReturn { cont: Continuation<T> ->
         setupTimeout(TimeoutCoroutine(time, unit, cont), block)
@@ -151,7 +150,6 @@
  * @param unit timeout unit (milliseconds by default)
  */
 public suspend fun <T> withTimeoutOrNull(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend CoroutineScope.() -> T): T? {
-    require(time >= 0) { "Timeout time $time cannot be negative" }
     if (time <= 0L) return null
     return suspendCoroutineOrReturn { cont: Continuation<T?> ->
         setupTimeout(TimeoutOrNullCoroutine(time, unit, cont), block)
diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
index 85ba53c..b7f61ce 100644
--- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
+++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
@@ -52,6 +52,7 @@
 
     /**
      * Clause that selects the given [block] after a specified timeout passes.
+     * If timeout is negative or zero, [block] is selected immediately.
      *
      * @param time timeout time
      * @param unit timeout unit (milliseconds by default)
@@ -416,8 +417,7 @@
     }
 
     override fun onTimeout(time: Long, unit: TimeUnit, block: suspend () -> R) {
-        require(time >= 0) { "Timeout time $time cannot be negative" }
-        if (time == 0L) {
+        if (time <= 0L) {
             if (trySelect(null))
                 block.startCoroutineUndispatched(completion)
             return
diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
index 69b9bd4..5116d12 100644
--- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
+++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
@@ -192,4 +192,18 @@
     }
 
     private class TestException : Exception()
+
+    @Test
+    fun testNegativeTimeout() = runTest {
+        expect(1)
+        var result = withTimeoutOrNull(-1) {
+            expectUnreached()
+        }
+        assertNull(result)
+        result = withTimeoutOrNull(0) {
+            expectUnreached()
+        }
+        assertNull(result)
+        finish(2)
+    }
 }
\ No newline at end of file
diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt
index 1aa2368..220a12b 100644
--- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt
+++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt
@@ -179,5 +179,19 @@
     }
 
     private class TestException : Exception()
+
+    @Test
+    fun testNegativeTimeout() = runTest {
+        expect(1)
+        try {
+            withTimeout(-1) {
+                expectUnreached()
+                "OK"
+            }
+        } catch (e: CancellationException) {
+            assertEquals("Timed out immediately", e.message)
+            finish(2)
+        }
+    }
 }
 
diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt
index 26bb748..10ad327 100644
--- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt
+++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt
@@ -20,6 +20,7 @@
 import kotlin.test.*
 
 class SelectTimeoutTest : TestBase() {
+
     @Test
     fun testBasic() = runTest {
         expect(1)
@@ -40,4 +41,62 @@
         assertEquals("OK", result)
         finish(3)
     }
-}
\ No newline at end of file
+
+    @Test
+    fun testZeroTimeout() = runTest {
+        expect(1)
+        val result = select<String> {
+            onTimeout(1000) {
+                expectUnreached()
+                "FAIL"
+            }
+            onTimeout(0) {
+                expect(2)
+                "OK"
+            }
+        }
+        assertEquals("OK", result)
+        finish(3)
+    }
+
+    @Test
+    fun testNegativeTimeout() = runTest {
+        expect(1)
+        val result = select<String> {
+            onTimeout(1000) {
+                expectUnreached()
+                "FAIL"
+            }
+            onTimeout(-10) {
+                expect(2)
+                "OK"
+            }
+        }
+        assertEquals("OK", result)
+        finish(3)
+    }
+
+    @Test
+    fun testUnbiasedNegativeTimeout() = runTest {
+        val counters = intArrayOf(0, 0, 0)
+        val iterations =10_000
+        for (i in 0..iterations) {
+            val result = selectUnbiased<Int> {
+                onTimeout(-10) {
+                    0
+                }
+                onTimeout(0) {
+                    1
+                }
+                onTimeout(10) {
+                    expectUnreached()
+                    2
+                }
+            }
+            ++counters[result]
+        }
+        assertEquals(0, counters[2])
+        assertTrue { counters[0] >  iterations / 4 }
+        assertTrue { counters[1] >  iterations / 4 }
+    }
+}