tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

disposable.test.ts (5350B)


      1 /**
      2 * @license
      3 * Copyright 2025 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import {describe, it, beforeEach} from 'node:test';
      8 
      9 import expect from 'expect';
     10 import sinon from 'sinon';
     11 
     12 import {
     13  DisposableStack,
     14  disposeSymbol,
     15  AsyncDisposableStack,
     16  asyncDisposeSymbol,
     17  SuppressedError,
     18 } from './disposable.js';
     19 
     20 describe('DisposableStack', () => {
     21  let stack: DisposableStack;
     22 
     23  beforeEach(() => {
     24    stack = new DisposableStack();
     25  });
     26 
     27  it('should dispose resources in LIFO order', () => {
     28    const dispose1 = sinon.spy();
     29    const dispose2 = sinon.spy();
     30    stack.adopt({}, dispose1);
     31    stack.adopt({}, dispose2);
     32    stack.dispose();
     33    expect(dispose2.calledBefore(dispose1)).toBeTruthy();
     34  });
     35 
     36  it('should not dispose resources if already disposed', () => {
     37    const dispose = sinon.spy();
     38    stack.adopt({}, dispose);
     39    stack.dispose();
     40    stack.dispose();
     41    expect(dispose.calledOnce).toBeTruthy();
     42  });
     43 
     44  it('should use disposable resources', () => {
     45    const resource = {
     46      [disposeSymbol]: sinon.spy(),
     47    };
     48    stack.use(resource);
     49    stack.dispose();
     50    expect(resource[disposeSymbol].calledOnce).toBeTruthy();
     51  });
     52 
     53  it('should defer disposal callbacks', () => {
     54    const onDispose = sinon.spy();
     55    stack.defer(onDispose);
     56    stack.dispose();
     57    expect(onDispose.calledOnce).toBeTruthy();
     58  });
     59 
     60  it('should move resources to a new stack', () => {
     61    const dispose = sinon.spy();
     62    stack.adopt({}, dispose);
     63    const newStack = stack.move();
     64    expect(stack.disposed).toBeTruthy();
     65    expect(newStack.disposed).toBeFalsy();
     66    newStack.dispose();
     67    expect(dispose.calledOnce).toBeTruthy();
     68  });
     69 
     70  it('should throw error if moving a disposed stack', () => {
     71    stack.dispose();
     72    expect(() => {
     73      return stack.move();
     74    }).toThrow(ReferenceError);
     75  });
     76 
     77  it('should collect errors from disposals', async () => {
     78    const dispose1 = sinon.stub().throws(new Error('dispose1'));
     79    const dispose2 = sinon.stub().throws(new Error('dispose2'));
     80    const dispose3 = sinon.stub().throws(new Error('dispose3'));
     81    stack.adopt({}, dispose1);
     82    stack.adopt({}, dispose2);
     83    stack.adopt({}, dispose3);
     84    let error;
     85    try {
     86      stack.dispose();
     87    } catch (e) {
     88      error = e;
     89    }
     90 
     91    expect(error instanceof SuppressedError).toBeTruthy();
     92    expect((error as SuppressedError).name).toEqual('SuppressedError');
     93    expect((error as SuppressedError).message).toEqual(
     94      'An error was suppressed during disposal',
     95    );
     96    expect((error as SuppressedError).error).toEqual(new Error('dispose3'));
     97    expect((error as SuppressedError).suppressed).toEqual(
     98      new SuppressedError('dispose2', new Error('dispose1')),
     99    );
    100  });
    101 });
    102 
    103 describe('AsyncDisposableStack', () => {
    104  let stack: AsyncDisposableStack;
    105 
    106  beforeEach(() => {
    107    stack = new AsyncDisposableStack();
    108  });
    109 
    110  it('should dispose resources in LIFO order', async () => {
    111    const dispose1 = sinon.stub().resolves();
    112    const dispose2 = sinon.stub().resolves();
    113    stack.adopt({}, dispose1);
    114    stack.adopt({}, dispose2);
    115    await stack.dispose();
    116    expect(dispose2.calledBefore(dispose1)).toBeTruthy();
    117  });
    118 
    119  it('should not dispose resources if already disposed', async () => {
    120    const dispose = sinon.stub().resolves();
    121    stack.adopt({}, dispose);
    122    await stack.dispose();
    123    await stack.dispose();
    124    expect(dispose.calledOnce).toBeTruthy();
    125  });
    126 
    127  it('should use async disposable resources', async () => {
    128    const resource = {
    129      [asyncDisposeSymbol]: sinon.stub().resolves(),
    130    };
    131    stack.use(resource);
    132    await stack.dispose();
    133    expect(resource[asyncDisposeSymbol].calledOnce).toBeTruthy();
    134  });
    135 
    136  it('should defer async disposal callbacks', async () => {
    137    const onDispose = sinon.stub().resolves();
    138    stack.defer(onDispose);
    139    await stack.dispose();
    140    expect(onDispose.calledOnce).toBeTruthy();
    141  });
    142 
    143  it('should move resources to a new stack', async () => {
    144    const dispose = sinon.stub().resolves();
    145    stack.adopt({}, dispose);
    146    const newStack = stack.move();
    147    expect(stack.disposed).toBeTruthy();
    148    expect(newStack.disposed).toBeFalsy();
    149    await newStack.dispose();
    150    expect(dispose.calledOnce).toBeTruthy();
    151  });
    152 
    153  it('should throw error if moving a disposed stack', () => {
    154    stack.dispose();
    155    expect(() => {
    156      return stack.move();
    157    }).toThrow(ReferenceError);
    158  });
    159 
    160  it('should collect errors from async disposals', async () => {
    161    const dispose1 = sinon.stub().rejects(new Error('dispose1'));
    162    const dispose2 = sinon.stub().rejects(new Error('dispose2'));
    163    const dispose3 = sinon.stub().rejects(new Error('dispose3'));
    164    stack.adopt({}, dispose1);
    165    stack.adopt({}, dispose2);
    166    stack.adopt({}, dispose3);
    167    let error;
    168    try {
    169      await stack.dispose();
    170    } catch (e) {
    171      error = e;
    172    }
    173 
    174    expect(error instanceof SuppressedError).toBeTruthy();
    175    expect((error as SuppressedError).name).toEqual('SuppressedError');
    176    expect((error as SuppressedError).message).toEqual(
    177      'An error was suppressed during disposal',
    178    );
    179    expect((error as SuppressedError).error).toEqual(new Error('dispose3'));
    180    expect((error as SuppressedError).suppressed).toEqual(
    181      new SuppressedError('dispose2', new Error('dispose1')),
    182    );
    183  });
    184 });