Skip to content

Commit

Permalink
Replace timeout + timeUnit with Duration (#30)
Browse files Browse the repository at this point in the history
* replace timeout and timeoutUnit with duration

* add changelog
  • Loading branch information
helmbold authored and sksamuel committed Jul 3, 2016
1 parent 3833c72 commit 6ce07bd
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 27 deletions.
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Changelog
=========

This project follows [semantic versioning](http://semver.org/).

Version 2.0.0, released YYYY-MM-DD TODO
----------------------------------

[Closed Issues](https://github.com/kotlintest/kotlintest/issues?utf8=%E2%9C%93&q=is%3Aclosed+milestone%3A2.0)

### Added

nothing

### Changed

#### Replaced `timeout` + `timeUnit` with `Duration` ([#29](https://github.com/kotlintest/kotlintest/issues/29))

You can now write `config(timeout = 2.seconds)` instead of
`config(timeout = 2, timeoutUnit = TimeUnit.SECONDS)`.

### Deprecated

nothing

### Removed

nothing

### Fixed

nothing
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ Each test can be configured with various parameters. After the test block, invok
* `invocations` - the number of times to run this test. Useful if you have a non-deterministic test and you want to run that particular test a set number of times. Defaults to 1.
* `threads` - Allows the invocation of this test to be parallelized by setting the number of threads to use in a thread pool executor for this test. If invocations is 1 (the default) then this parameter will have no effect. Similarly, if you set invocations to a value less than or equal to the number threads, then each invocation will have its own thread.
* `ignored` - If set to true then this test is ignored. Can be useful if a test needs to be temporarily disabled.
* `timeout` / `timeoutUnit` - sets a timeout for this test. If the test has not finished in that time then the test fails. Useful for code that is non-deterministic and might not finish.
* `timeout` - sets a timeout for this test. If the test has not finished in that time then the test fails. Useful for code that is non-deterministic and might not finish. Timeout is of type `Duration` which can be instantiated like `2.seconds`, `3.minutes` and so on.
* `tag` / `tags` - a list of String tags that can be set on a test. Then by invoking the test runner with a system property of testTags, you can control which tests are run. For example, tests that require a linux based O/S might be tagged with "linux" then gradle could be invoked with gradle test -DtestTags=linux. Another example might be tagging database tags that you only want to run on a server that has a database installed. Any test that has no tags is always run.

Examples of setting config:
Expand All @@ -251,7 +251,7 @@ class MyTests : WordSpec() {
"return the length of the string" {
"sammy".length shouldBe 5
"".length shouldBe 0
}.config(timeout=2, timeoutUnit=TimeUnit.SECONDS)
}.config(timeout = 2.seconds)
}
}
}
Expand Down
31 changes: 31 additions & 0 deletions src/main/kotlin/io/kotlintest/Duration.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.kotlintest

import java.util.concurrent.TimeUnit

data class Duration(val amount: Long, val timeUnit: TimeUnit) {

companion object {
// Actually limited to 9223372036854775807 days, so unless you are very patient, it is unlimited ;-)
val unlimited = Duration(amount = Long.MAX_VALUE, timeUnit = TimeUnit.DAYS)
}

val Long.days: Duration
get() = Duration(this, TimeUnit.DAYS)

val Long.hours: Duration
get() = Duration(this, TimeUnit.HOURS)

val Long.microseconds: Duration
get() = Duration(this, TimeUnit.MICROSECONDS)

val Long.milliseconds: Duration
get() = Duration(this, TimeUnit.MILLISECONDS)

val Long.nanoseconds: Duration
get() = Duration(this, TimeUnit.NANOSECONDS)

val Long.seconds: Duration
get() = Duration(this, TimeUnit.SECONDS)

val nanoseconds: Long = timeUnit.toNanos(amount)
}
9 changes: 4 additions & 5 deletions src/main/kotlin/io/kotlintest/Eventually.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
package io.kotlintest

import java.util.concurrent.TimeUnit

interface Eventually {

fun eventually(duration: Long, unit: TimeUnit, f: () -> Unit): Unit {
val end = System.nanoTime() + unit.toNanos(duration)
fun eventually(duration: Duration, f: () -> Unit): Unit {
val end = System.nanoTime() + duration.nanoseconds
var times = 0
while (System.nanoTime() < end) {
try {
f()
return
} catch (e: Exception) {
// ignore and proceed
}
times++
}
throw TestFailedException("Test failed after $duration $unit; attempted $times times")
throw TestFailedException("Test failed after ${duration.amount} ${duration.timeUnit}; attempted $times times")
}
}
18 changes: 8 additions & 10 deletions src/main/kotlin/io/kotlintest/KTestJUnitRunner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,31 +52,29 @@ class KTestJUnitRunner(val testClass: Class<TestBase>) : Runner() {
return systemTags.isEmpty() || testcase.config.tags.isEmpty() || systemTags.intersect(testcase.config.tags).isNotEmpty()
}

private fun runTest(testcase: TestCase, notifier: RunNotifier, desc: Description): Unit {
private fun runTest(testcase: TestCase, notifier: RunNotifier, description: Description): Unit {

fun executorForTests(): ExecutorService =
if (testcase.config.threads < 2) Executors.newSingleThreadExecutor()
else Executors.newFixedThreadPool(testcase.config.threads)

val executor = executorForTests()
notifier.fireTestStarted(desc)
notifier.fireTestStarted(description)
for (j in 1..testcase.config.invocations) {
executor.submit {
try {
testcase.test()
} catch(e: Throwable) {
notifier.fireTestFailure(Failure(desc, e))
notifier.fireTestFailure(Failure(description, e))
}
}
}
notifier.fireTestFinished(desc)
notifier.fireTestFinished(description)
executor.shutdown()
if (testcase.config.timeout > 0) {
if (!executor.awaitTermination(testcase.config.timeout, testcase.config.timeoutUnit)) {
notifier.fireTestFailure(Failure(desc, TestTimedOutException(testcase.config.timeout, testcase.config.timeoutUnit)))
}
} else {
executor.awaitTermination(1, TimeUnit.DAYS)
val timeout = testcase.config.timeout
val terminated = executor.awaitTermination(timeout.amount, timeout.timeUnit)
if (!terminated) {
notifier.fireTestFailure(Failure(description, TestTimedOutException(timeout.amount, timeout.timeUnit)))
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/io/kotlintest/specs/FlatSpec.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.kotlintest.specs

import io.kotlintest.Duration
import io.kotlintest.TestCase
import io.kotlintest.TestConfig
import io.kotlintest.TestSuite
Expand All @@ -14,12 +15,11 @@ abstract class FlatSpec : PropertyTesting() {

fun String.config(invocations: Int = 1,
ignored: Boolean = false,
timeout: Long = 0,
timeoutUnit: TimeUnit = TimeUnit.MILLISECONDS,
timeout: Duration = Duration.unlimited,
threads: Int = 1,
tag: String? = null,
tags: List<String> = listOf()): Pair<String, TestConfig> =
Pair(this, TestConfig(ignored, invocations, timeout, timeoutUnit, threads, tags))
Pair(this, TestConfig(ignored, invocations, timeout, threads, tags))

// allows us to write "name of test" { test here }
operator fun String.invoke(test: () -> Unit): Pair<String, () -> Unit> = Pair(this, test)
Expand Down
9 changes: 2 additions & 7 deletions src/main/kotlin/io/kotlintest/testcase.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.kotlintest

import java.util.concurrent.TimeUnit

data class TestSuite(val name: String, val nestedSuites: MutableList<TestSuite>, val cases: MutableList<io.kotlintest.TestCase>) {
companion object {
fun empty(name: String) = TestSuite(name, mutableListOf<TestSuite>(), mutableListOf<io.kotlintest.TestCase>())
Expand All @@ -10,8 +8,7 @@ data class TestSuite(val name: String, val nestedSuites: MutableList<TestSuite>,

data class TestConfig(var ignored: Boolean = false,
var invocations: Int = 1,
var timeout: Long = 0,
var timeoutUnit: TimeUnit = TimeUnit.MILLISECONDS,
var timeout: Duration = Duration.unlimited,
var threads: Int = 1,
var tags: List<String> = listOf())

Expand All @@ -22,15 +19,13 @@ data class TestCase(val suite: TestSuite,

fun config(invocations: Int = 1,
ignored: Boolean = false,
timeout: Long = 0,
timeoutUnit: TimeUnit = TimeUnit.MILLISECONDS,
timeout: Duration = Duration.unlimited,
threads: Int = 1,
tag: String? = null,
tags: List<String> = listOf()): Unit {
config.invocations = invocations
config.ignored = ignored
config.timeout = timeout
config.timeoutUnit = timeoutUnit
config.threads = threads
config.tags = tags
if (tag != null)
Expand Down

0 comments on commit 6ce07bd

Please sign in to comment.