chromium/ui/file_manager/file_manager/common/js/async_util_unittest.ts

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {assertArrayEquals} from 'chrome://webui-test/chromeos/chai_assert.js';

import {AsyncQueue, RateLimiter, type Task} from './async_util.js';
import {waitUntil} from './test_error_reporting.js';

/** Checks that the order of the tasks is preserved. */
export async function testAsyncQueueOrder() {
  const queue = new AsyncQueue();
  const taskTrace: string[] = [];

  const secondTask = (callback: VoidCallback) => {
    taskTrace.push('2');
    callback();
  };

  const firstTask = (callback: VoidCallback) => {
    queue.run(secondTask);
    setTimeout(() => {
      taskTrace.push('1');
      callback();
    }, 100);
  };

  queue.run(firstTask);
  await waitUntil(() => taskTrace.length === 2);
  assertArrayEquals(['1', '2'], taskTrace);
}

/** Checks that tasks with errors do not interrupt the queue's operations. */
export async function testAsyncQueueFailingTask() {
  const queue = new AsyncQueue();
  const taskTrace: string[] = [];

  const followingTask = (callback: VoidCallback) => {
    taskTrace.push('following');
    callback();
  };

  const badTask = (_callback: VoidCallback) => {
    taskTrace.push('bad');
    queue.run(followingTask);
    throw new Error('Something went wrong');
  };

  queue.run(badTask);
  await waitUntil(() => taskTrace.length === 2);
  assertArrayEquals(['bad', 'following'], taskTrace);
}

/**
 * Checks the order of start and end of two mutually calling tasks. We expect
 * that task that is run first finishes before the task ran after it.
 */
export async function testAsyncQueueStartEndOrder() {
  const queue = new AsyncQueue();
  let runCount = 0;
  const task: Task[] = [];
  const taskTrace: number[] = [];
  const maxRunCount = 4;

  // Makes a task that enqueues the task specified by the |index|.
  // Each task also records its trace right at the start and at the end.
  const makeTask = (index: number) => {
    return (callback: VoidCallback) => {
      const myID = runCount++;
      taskTrace.push(myID);
      if (runCount < maxRunCount) {
        queue.run(task[index]!);
      }
      callback();
      taskTrace.push(myID);
    };
  };

  // Task 0 enqueues task 1.
  task.push(makeTask(1));
  // Task 1 enqueues task 0.
  task.push(makeTask(0));
  // Kick off the process by running task 0.
  queue.run(task[0]!);

  await waitUntil(() => runCount >= maxRunCount);
  // TODO(1350885): This records problematic behavior in the test. Fixing
  // concurrent queue should change this tests to expect the sequence to be
  // [0, 0, 1, 1, 2, 2, 3, 3].
  const expected = [0, 1, 2, 3, 3, 2, 1, 0];
  assertArrayEquals(expected, taskTrace);
}

/**
 * Checks that calling `run` inside a RateRimiter's clojure does not lead to
 * recursive calls.
 */
export function testRateLimiterDoesNotLeadToRescursion(done: VoidCallback) {
  const limiter = new RateLimiter(() => {
    // This should not lead to an infinite recursion.
    limiter.run();
    done();
  });

  limiter.run();
}