I want to have a periodic timer loop (e.g. 1 second intervals). There are many ways to do that, but I haven't found a solution that would be suitable for unit testing.
Timer should be precise
Unit test should be able to skip the waiting
The closest that I came to a solution was to use coroutines: A simple loop with delay, runBlockingTest and advanceTimeBy.
coScope.launch {
while (isActive) {
// do stuff
delay(1000L)
}
}
and
#Test
fun timer_test() = coScope.runBlockingTest {
... // start job
advanceTimeBy(9_000L)
... // cancel job
}
It works to some degree, but the timer is not precise as it does not account for the execution time.
I haven't found a way to query internal timer used in a coroutine scope or a remaining timeout value inside withTimeoutOrNull:
coScope.launch {
withTimeoutOrNull(999_000_000L) { // max allowed looping time
while (isActive) {
// do stuff
val timeoutLeft // How to get that value ???
delay(timeoutLeft.mod(1000L))
}
}
}
Next idea was to use ticker:
coScope.launch {
val tickerChannel = ticker(1000L, 0L, coroutineContext)
var referenceTimer = 0L
for (event in tickerChannel) {
// do stuff
println(referenceTimer)
referenceTimer += 1000L
}
}
However, the connection between TestCoroutineDispatcher() and ticker does not produce right results:
private val coDispatcher = TestCoroutineDispatcher()
#Test timerTest() = runBlockingTest(coDispatcher) {
myTimer.lauchPeriodicJob()
advanceTimeBy(20_000L) // or delay(20_000L)
myTimer.cancelPeriodicJob()
println("end_of_test")
}
rather consistently results in:
0
1000
2000
3000
4000
5000
6000
end_of_test
I am also open for any alternative approaches that satisfy the two points above.
Related
I want flow output (return type Flow<T>) from a non-flow function (return typeT).
fun getTotalFiles(): Int
// Say, This is a library function it'll return the number of files (Int) in that folder at that specific moment.
//And,
fun getAllFiles(): List<File>
// Say, This is a library function it'll return all the files (List<File>) in that folder.
The files in that folder can and will change in the future.
Now, I want to constantly observe the output, so how do I implement it?
fun getFlowOfTotalFiles(): Flow<Int> =
// A wrapper function that converts the library function return type to an observable flow, Flow<Int>
//And,
fun getFlowOfAllFiles(): Flow<List<File>> =
// A wrapper function that converts the library function return type to an observable flow, Flow<List<File>>
For specifically monitoring a directory for files, you can use WatchService and convert it to a flow with the flow builder. Something like this:
fun getDirectoryMonitorFlow(directory: String) = flow {
FileSystems.getDefault().newWatchService().use { watchService ->
while (true) {
val watchKey = Path.of(directory).register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY)
if (watchKey.pollEvents().isNotEmpty()) {
emit(Unit)
}
yield() // give flow opportunity to be cancelled.
if (!watchKey.reset()) {
println("Directory became unreadable. Finishing flow.")
break
}
}
}
}
.catch { println("Exception while monitoring directory.") }
.flowOn(Dispatchers.IO)
And then your class might look like:
fun getFlowOfTotalFiles(): Flow<Int> = getFlowOfAllFiles()
.map { it.size }
.distinctUntilChanged()
fun getFlowOfAllFiles(): Flow<List<File>> = flow {
emit(Unit) // so current state is always emitted
emitAll(getDirectoryMonitorFlow(directory))
}
.map {
File(directory).listFiles()?.toList().orEmpty()
}
.flowOn(Dispatchers.IO)
.distinctUntilChanged()
Although you might consider making the first flow a private SharedFlow so you aren't running multiple WatchServices to monitor the same directory concurrently.
I believe you need an infinite loop inside a flow builder, something like the following:
fun getFlowOfTotalFiles(): Flow<Int> = flow {
while (true) {
emit(getTotalFiles())
// delays for 5 sec before next request and
// terminates the infinite cycle when a coroutine,
// that collects this Flow, is canceled
delay(5000)
}
}
fun getAllFilesFlow(): Flow<List<File>> = flow {
while (true) {
emit(getAllFiles())
delay(5000)
}
}
I am trying to validate that a suspend function does not return anything at all in a certain test.
Consider the following situation:
val completionSignal = Channel<Unit>(capacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
suspend fun waitForCompletionSignal(): String {
completionSignal.receive()
return "Completion signal received"
}
I want to test this code with 2 unit tests, one that validates it returns the string when I provide the CompletionSignal with a value (thats the easy one).
And one that validates that it does not return anything when i don't give it anything. This is the hard one, since how long should I wait? And can i be sure the test fails if my code changes and suddenly the string is returned?
I got the following approach but I am missing some pieces:
#Test
fun `waitForCompletionSignal when completionSignal is provided assert result`() = runTest {
// Start waiting for result
val result = async { waitForCompletionSignal() }
// Provide completion signal
completionSignal.trySend(Unit)
// Await result and verify its correct
assertThat(result.await() == "Completion signal received")
}
#Test
fun `waitForCompletionSignal when completionSignal is not provided assert no result`() = runTest {
// Start waiting for result
val result = async { waitForCompletionSignal() }
// TODO?? some validation that succeeds if the test is just like this, but fails when i do the following:
completionSignal.trySend(Unit)
// A regular await would wait indefinately, and checking if the deferred result is completed does not work very well as well.
}
I hope the question is clear, thanks in advance.
I made an extension function on the deferred type to be able to wait for a max amount of time and after that it will return null. In my particular situation a delay time of 0 (so no delay whatsoever) is enough, but I can imagine that in some situations its useful to delay for a minimum amount of time.
#ExperimentalCoroutinesApi
suspend inline fun <reified T> Deferred<T>.awaitOrNull(
time: Long = 0,
crossinline actBlock: () -> Unit = { }
): T? = coroutineScope {
actBlock()
val timeoutJob = async<T?> {
delay(time)
null
}
return#coroutineScope select<T?> {
this#awaitOrNull.onAwait {
timeoutJob.cancel()
it
}
timeoutJob.onAwait {
this#awaitOrNull.cancel()
it
}
}
}
Using this method i can write the following tests that fail / succeed as expected:
// Succeeds
#Test
fun `waitForCompletionSignal when completionSignal is provided assert result`() = runTest {
val result = async {
waitForCompletionSignal()
}.awaitOrNull {
completionSignal.trySend(Unit)
}
assertThat(result == "Completion signal received")
}
// Succeeds
#Test
fun `waitForCompletionSignal when completionSignal is not provided assert no result`() = runTest {
val result = async {
waitForCompletionSignal()
}.awaitOrNull()
assertThat(result == null)
}
// Fails (to prove that it works)
#Test
fun `waitForCompletionSignal when completionSignal is not provided assert no result`() = runTest {
val result = async {
waitForCompletionSignal()
}.awaitOrNull {
completionSignal.trySend(Unit) // This (obviously) causes the test to fail, as I wanted.
}
assertThat(result == null)
}
How do I make an action occur with a delay, but after a timeout?
The setTimeout() function doesn’t work in Decentraland scenes, so is there an alternative?
For example, I want an entity to wait 300 milliseconds after it’s clicked before I remove it from the engine.
To implement this you’ll have to create:
A custom component to keep track of time
A component group to keep track of all the entities with a delay in the scene
A system that updates the timers con all these
components on each frame.
It sounds rather complicated, but once you created one delay, implementing another delay only takes one line.
The component:
#Component("timerDelay")
export class Delay implements ITimerComponent{
elapsedTime: number;
targetTime: number;
onTargetTimeReached: (ownerEntity: IEntity) => void;
private onTimeReachedCallback?: ()=> void
/**
* #param millisecs amount of time in milliseconds
* #param onTimeReachedCallback callback for when time is reached
*/
constructor(millisecs: number, onTimeReachedCallback?: ()=> void){
this.elapsedTime = 0
this.targetTime = millisecs / 1000
this.onTimeReachedCallback = onTimeReachedCallback
this.onTargetTimeReached = (entity)=>{
if (this.onTimeReachedCallback) this.onTimeReachedCallback()
entity.removeComponent(this)
}
}
}
The component group:
export const delayedEntities = engine.getComponentGroup(Delay)
The system:
// define system
class TimerSystem implements ISystem {
update(dt: number){
for (let entity of delayedEntities.entities) {
let timerComponent = entity.getComponent(component)
timerComponent.elapsedTime += dt
if (timerComponent.elapsedTime >= timerComponent.targetTime){
timerComponent.onTargetTimeReached(entity)
}
})
}
}
// instance system
engine.addSystem(new TimerSystem())
Once all these parts are in place, you can simply do the following to delay an execution in your scene:
const myEntity = new Entity()
myEntity.addComponent(new Delay(1000, () => {
log("time ran out")
}))
engine.addEntity(myEntity)
A few years late, but the OP's selected answer is kind of deprecated because you can accomplish a delay doing:
import { Delay } from "node_modules/decentraland-ecs-utils/timer/component/delay"
const ent = new Entity
ent.addComponent(new Delay(3 * 1000, () => {
// this code will run when time is up
}))
Read the docs.
Use the utils.Delay() function in the utils library.
This function just takes the delay time in milliseconds, and the function you want to execute.
Here's the full documentation, explaining how to add the library + how to use this function, including example code:
https://www.npmjs.com/package/decentraland-ecs-utils
I've hit a bit of an interesting road block in my attempt at writing unit tests for some middleware as I can't seem to come up with a feasible means to fake two concurrent connections for a generator function which is a piece of koa middleware.
I have a constructor function that takes some setup options and returns a generator. This generator has access to some variables via closure which increment per request and decrement when the complete. Here is a subset of the code to give you an idea of what i'm trying to accomplish.
module.exports = function (options = {}) {
let connections = 0;
let {
max = 100
...
} = options;
return function *() {
connections++
...
if (connections > max) {
connections--;
// callback here
}
...
}
}
In simple terms I want to be able to keep track of multiple simultaneous "connections" in which I fire a callback when a max number of requests have been met. However, in my test i get back a single instance of this generator and can only call it once mimicking a single request, thus i can never meet the connections > max conditional
it("Should trigger callback when max connections reached", () => {
const gen = middleware({
max: 1,
onMax: function (current, max) {
this.maxReached = true;
}
}).call(context);
gen.next();
expect(context.maxReached).to.be.true;
});
Sometimes you just need a good night sleep to dream your answer. This was simply a matter of calling the same generator with two different contexts that represented two different requests and store a value to tests against on the latter. The counter would still increment because I never returned up the middleware chain (response) in order to decrement. It's more of a fake concurrency.
const middleware = limiter({
max: 1,
onMax: function (current, max) {
this.maxReached = true;
}
});
middleware.call(reqContext).next();
middleware.call(secondReqContext).next();
expect(secondReqContext.maxReached).to.be.true;
There is a Broadcaster, that accepts strings and append them to a StringBuilder.
I want to test it.
I have to use Thread#sleep to wait, while the broadcaster finish processing of strings. I want to remove sleep.
I tried to use Control#debug() unsuccessfully.
public class BroadcasterUnitTest {
#Test
public void test() {
//prepare
Environment.initialize();
Broadcaster<String> sink = Broadcaster.create(Environment.newDispatcher()); //run broadcaster in separate thread (dispatcher)
StringBuilder sb = new StringBuilder();
sink
.observe(s -> sleep(100)) //long-time operation
.consume(sb::append);
//do
sink.onNext("a");
sink.onNext("b");
//assert
sleep(500);//wait while broadcaster finished (if comment this line then the test will fail)
assertEquals("ab", sb.toString());
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
I'm not familiar with Broadcaster (and it's probably deprecated since the question is old), but these 3 ways could be helpful in general:
When testing Project-Reactor's Fluxes and stuff, you're probably better of using their testing library made specially for this. Their reference and the Javadoc on that part are pretty good, and I'll just copy an example that speaks for itself here:
#Test
public void testAppendBoomError() {
Flux<String> source = Flux.just("foo", "bar");
StepVerifier.create(
appendBoomError(source))
.expectNext("foo")
.expectNext("bar")
.expectErrorMessage("boom")
.verify();
}
You could just block() by yourself on the Fluxes and Monos and then run checks. And note that if an error is emitted, this will result in an exception. But have a feeling you'll find yourself needing to write more code for some cases (e.g., checking the Flux has emitted 2 items X & Y then terminated with error) and you'd be then re-implementing StepVerifier.
#Test
public void testFluxOrMono() {
Flux<String> source = Flux.just(2, 3);
List<Integer> result = source
.flatMap(i -> multiplyBy2Async(i))
.collectList()
.block();
// run your asserts on the list. Reminder: the order may not be what you expect because of the `flatMap`
// Or with a Mono:
Integer resultOfMono = Mono.just(5)
.flatMap(i -> multiplyBy2Async(i))
.map(i -> i * 4)
.block();
// run your asserts on the integer
}
You could use the general solutions to async testing like CountDownLatch, but, again, wouldn't recommend and would give you trouble in some cases. For example, if you don't know the number of receivers in advance you'll need to use something else.
Per answer above, I found blockLast() helped.
#Test
public void MyTest()
{
Logs.Info("Start test");
/* 1 */
// Make a request
WebRequest wr1 = new WebRequest("1", "2", "3", "4");
String json1 = wr1.toJson(wr1);
Logs.Info("Flux");
Flux<String> responses = controller.getResponses(json1);
/* 2 */
Logs.Info("Responses in");
responses.subscribe(s -> mySub.myMethod(s)); // Test for strings is in myMethod
Logs.Info("Test thread sleeping");
Thread.sleep(2000);
/* 3 */
Logs.Info("Test thread blocking");
responses.blockLast();
Logs.Info("Finish test");
}