chromium/chrome/test/data/extensions/api_test/file_system_provider/service_worker/read_file/test.js

// 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 {catchError, mountTestFileSystem, openFile, readTextFromBlob, remoteProvider, startReadTextFromBlob} from '/_test_resources/api_test/file_system_provider/service_worker/helpers.js';
// For shared constants.
import {TestFileSystemProvider} from '/_test_resources/api_test/file_system_provider/service_worker/provider.js';

async function main() {
  await navigator.serviceWorker.ready;
  const fileSystem = await mountTestFileSystem();

  chrome.test.runTests([
    // Read contents of a file. This file exists, so it should succeed.
    async function readFileSuccess() {
      const fileEntry = await fileSystem.getFileEntry(
          TestFileSystemProvider.FILE_READ_SUCCESS,
          {create: false},
      );
      const file = await openFile(fileEntry);
      const text = await readTextFromBlob(file);

      chrome.test.assertEq(TestFileSystemProvider.INITIAL_TEXT, text);
      chrome.test.succeed();
    },

    // Read contents of a file multiple times at once. Verify that there is at
    // most as many opened files at once as permitted per limit.
    async function readFileWithOpenedFilesLimitSuccess() {
      await fileSystem.remount(/*openedFilesLimit=*/ 2);

      // Start N read requests in parallel.
      const N = 20;
      const reads = [];
      for (let i = 0; i < N; i++) {
        const fileEntry = await fileSystem.getFileEntry(
            TestFileSystemProvider.FILE_BLOCK_IO, {create: false});
        const file = await openFile(fileEntry);
        reads.push(readTextFromBlob(file));
      }
      // All reads will be stalled, unblock them one by one.
      // In theory there is a race: it is possible (although unlikely) for
      // requests to come through two at a time by chance, but it's not
      // possible to check without some sort of timeout (waiting for a read to
      // not happen in a period of time).
      for (let i = 0; i < N; i++) {
        const {requestId} =
            await remoteProvider.waitForEvent('onReadFileRequestedStalled');
        chrome.test.assertTrue(await remoteProvider.getOpenedFiles() <= 2);
        await remoteProvider.continueRequest(requestId);
      }

      // All reads should complete successfully.
      for (const promise of reads) {
        chrome.test.assertEq(
            TestFileSystemProvider.INITIAL_TEXT, await promise);
      }
      chrome.test.succeed();
    },

    // Read contents of a file, but with an error on the way. This should
    // result in an error.
    async function readFileError() {
      await fileSystem.remount(/*openedFilesLimit=*/ 0);

      const fileEntry = await fileSystem.getFileEntry(
          TestFileSystemProvider.FILE_FAIL,
          {create: false},
      );
      const file = await openFile(fileEntry);
      const error = await catchError(readTextFromBlob(file));

      chrome.test.assertTrue(
          !!error, 'Unexpectedly succeeded to read a broken file.');
      chrome.test.assertEq('NotReadableError', error.name);
      chrome.test.succeed();
    },

    // Abort reading a file with a registered abort handler. Should result in a
    // gracefully terminated reading operation.
    async function abortReadingSuccess() {
      await remoteProvider.resetState();

      const fileEntry = await fileSystem.getFileEntry(
          TestFileSystemProvider.FILE_BLOCK_IO,
          {create: false},
      );
      const file = await openFile(fileEntry);
      const {promise, reader} = startReadTextFromBlob(file);
      // Wait until the read request made it to the FSP.
      await remoteProvider.waitForEvent('onReadFileRequested');
      chrome.test.assertEq(1, await remoteProvider.getOpenedFiles());
      reader.abort();
      await remoteProvider.waitForEvent('onAbortRequested');
      await remoteProvider.waitForEvent('onCloseFileRequested');
      const error = await catchError(promise);

      chrome.test.assertEq(0, await remoteProvider.getOpenedFiles());
      chrome.test.assertTrue(
          !!error, 'Unexpectedly succeeded after read aborted.');
      chrome.test.assertEq('AbortError', error.name);
      chrome.test.succeed();
    },

    // Abort opening a file while trying to read it without an abort handler
    // wired up. This should cause closing the file anyway.
    async function abortViaCloseSuccess() {
      await remoteProvider.resetState();
      await remoteProvider.setHandlerEnabled('onAbortRequested', false);

      const fileEntry = await fileSystem.getFileEntry(
          TestFileSystemProvider.FILE_BLOCK_IO,
          {create: false, exclusive: false},
      );
      const file = await openFile(fileEntry);
      const {promise, reader} = startReadTextFromBlob(file);
      // Wait until the read request made it to the FSP.
      await remoteProvider.waitForEvent('onReadFileRequested');
      chrome.test.assertEq(1, await remoteProvider.getOpenedFiles());
      reader.abort();
      await remoteProvider.waitForEvent('onCloseFileRequested');
      const error = await catchError(promise);

      chrome.test.assertEq(0, await remoteProvider.getOpenedFiles());
      chrome.test.assertTrue(
          !!error, 'Unexpectedly succeeded after read aborted.');
      chrome.test.assertEq('AbortError', error.name);
      chrome.test.succeed();
    },

    // Abort opening a file while trying to read it without an abort handler
    // wired up, then quickly try to open it again while having a limit of 1
    // opened files at once. This is a regression test for: crbug.com/519063.
    async function abortOpenedAndReopenSuccess() {
      await remoteProvider.resetState();
      await fileSystem.remount(/*openedFilesLimit=*/ 1);
      await remoteProvider.setHandlerEnabled('onAbortRequested', false);

      // Start reading two files, the first read should get stuck on open.
      const fileEntry1 = await fileSystem.getFileEntry(
          TestFileSystemProvider.FILE_BLOCK_OPEN,
          {create: false, exclusive: false},
      );
      const fileEntry2 = await fileSystem.getFileEntry(
          TestFileSystemProvider.FILE_READ_SUCCESS,
          {create: false, exclusive: false},
      );
      const read1 = startReadTextFromBlob(await openFile(fileEntry1));
      const read2 = startReadTextFromBlob(await openFile(fileEntry2));
      const openRequest1 =
          await remoteProvider.waitForEvent('onOpenFileRequested');
      // Wait until the first open request is blocked.
      const {requestId} =
          await remoteProvider.waitForEvent('onOpenFileRequestedStalled');
      chrome.test.assertEq(1, await remoteProvider.getOpenedFiles());
      // Abort the read and let the open request go, which should proceed to
      // immediately close.
      read1.reader.abort();
      await remoteProvider.continueRequest(requestId);

      // The second read request should continue successfully.
      const openRequest2 =
          await remoteProvider.waitForEvent('onOpenFileRequested');
      chrome.test.assertEq(
          `/${TestFileSystemProvider.FILE_READ_SUCCESS}`,
          openRequest2.filePath);
      chrome.test.assertEq(
          openRequest2.requestId,
          (await remoteProvider.waitForEvent('onReadFileRequested'))
              .openRequestId);
      chrome.test.assertEq(
          TestFileSystemProvider.INITIAL_TEXT, await read2.promise)
      // The first file should have been closed.
      chrome.test.assertEq(
          openRequest1.requestId,
          (await remoteProvider.waitForEvent('onCloseFileRequested'))
              .openRequestId);
      chrome.test.assertEq(0, await remoteProvider.getOpenedFiles());
      chrome.test.succeed();
    },
  ]);
}

main();