summaryrefslogtreecommitdiffstats
path: root/toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js
blob: f1aebc3ad2014ceb334c71837977614e19a253bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";

Cu.import("resource://gre/modules/PromiseUtils.jsm", this);

function run_test() {
  run_next_test();
}

add_task(function* test_no_condition() {
  for (let kind of ["phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped"]) {
    do_print("Testing a barrier with no condition (" + kind + ")");
    let lock = makeLock(kind);
    yield lock.wait();
    do_print("Barrier with no condition didn't lock");
  }
});

add_task(function* test_phase_various_failures() {
  for (let kind of ["phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped"]) {
    do_print("Kind: " + kind);
    // Testing with wrong arguments
    let lock = makeLock(kind);

    Assert.throws(() => lock.addBlocker(), /TypeError|NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS/);
    Assert.throws(() => lock.addBlocker(null, true), /TypeError|NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS/);

    if (kind != "xpcom-barrier") {
      // xpcom-barrier actually expects a string in that position
      Assert.throws(() => lock.addBlocker("Test 2", () => true, "not a function"), /TypeError/);
    }

    // Attempting to add a blocker after we are done waiting
    yield lock.wait();
    Assert.throws(() => lock.addBlocker("Test 3", () => true), /is finished/);
  }
});

add_task(function* test_reentrant() {
  do_print("Ensure that we can call addBlocker from within a blocker");

  for (let kind of ["phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped"]) {
    do_print("Kind: " + kind);
    let lock = makeLock(kind);

    let deferredOuter = PromiseUtils.defer();
    let deferredInner = PromiseUtils.defer();
    let deferredBlockInner = PromiseUtils.defer();

    lock.addBlocker("Outer blocker", () => {
      do_print("Entering outer blocker");
      deferredOuter.resolve();
      lock.addBlocker("Inner blocker", () => {
        do_print("Entering inner blocker");
        deferredInner.resolve();
        return deferredBlockInner.promise;
      });
    });

    // Note that phase-style locks spin the event loop and do not return from
    // `lock.wait()` until after all blockers have been resolved. Therefore,
    // to be able to test them, we need to dispatch the following steps to the
    // event loop before calling `lock.wait()`, which we do by forcing
    // a Promise.resolve().
    //
    let promiseSteps = Task.spawn(function* () {
      yield Promise.resolve();

      do_print("Waiting until we have entered the outer blocker");
      yield deferredOuter.promise;

      do_print("Waiting until we have entered the inner blocker");
      yield deferredInner.promise;

      do_print("Allowing the lock to resolve")
      deferredBlockInner.resolve();
    });

    do_print("Starting wait");
    yield lock.wait();

    do_print("Waiting until all steps have been walked");
    yield promiseSteps;
  }
});


add_task(function* test_phase_removeBlocker() {
  do_print("Testing that we can call removeBlocker before, during and after the call to wait()");

  for (let kind of ["phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped"]) {

    do_print("Switching to kind " + kind);
    do_print("Attempt to add then remove a blocker before wait()");
    let lock = makeLock(kind);
    let blocker = () => {
      do_print("This promise will never be resolved");
      return Promise.defer().promise;
    };

    lock.addBlocker("Wait forever", blocker);
    let do_remove_blocker = function(aLock, aBlocker, aShouldRemove) {
      do_print("Attempting to remove blocker " + aBlocker + ", expecting result " + aShouldRemove);
      if (kind == "xpcom-barrier") {
        // The xpcom variant always returns `undefined`, so we can't
        // check its result.
        aLock.removeBlocker(aBlocker);
        return;
      }
      do_check_eq(aLock.removeBlocker(aBlocker), aShouldRemove);
    };
    do_remove_blocker(lock, blocker, true);
    do_remove_blocker(lock, blocker, false);
    do_print("Attempt to remove non-registered blockers before wait()");
    do_remove_blocker(lock, "foo", false);
    do_remove_blocker(lock, null, false);
    do_print("Waiting (should lift immediately)");
    yield lock.wait();

    do_print("Attempt to add a blocker then remove it during wait()");
    lock = makeLock(kind);
    let blockers = [
      () => {
        do_print("This blocker will self-destruct");
        do_remove_blocker(lock, blockers[0], true);
        return Promise.defer().promise;
      },
      () => {
        do_print("This blocker will self-destruct twice");
        do_remove_blocker(lock, blockers[1], true);
        do_remove_blocker(lock, blockers[1], false);
        return Promise.defer().promise;
      },
      () => {
        do_print("Attempt to remove non-registered blockers during wait()");
        do_remove_blocker(lock, "foo", false);
        do_remove_blocker(lock, null, false);
      }
    ];
    for (let i in blockers) {
      lock.addBlocker("Wait forever again: " + i, blockers[i]);
    }
    do_print("Waiting (should lift very quickly)");
    yield lock.wait();
    do_remove_blocker(lock, blockers[0], false);


    do_print("Attempt to remove a blocker after wait");
    lock = makeLock(kind);
    blocker = Promise.resolve.bind(Promise);
    yield lock.wait();
    do_remove_blocker(lock, blocker, false);

    do_print("Attempt to remove non-registered blocker after wait()");
    do_remove_blocker(lock, "foo", false);
    do_remove_blocker(lock, null, false);
  }

});

add_task(function* test_state() {
  do_print("Testing information contained in `state`");

  let BLOCKER_NAME = "test_state blocker " + Math.random();

  // Set up the barrier. Note that we cannot test `barrier.state`
  // immediately, as it initially contains "Not started"
  let barrier = new AsyncShutdown.Barrier("test_filename");
  let deferred = Promise.defer();
  let {filename, lineNumber} = Components.stack;
  barrier.client.addBlocker(BLOCKER_NAME,
    function() {
      return deferred.promise;
    });

  let promiseDone = barrier.wait();

  // Now that we have called `wait()`, the state contains interesting things
  let state = barrier.state[0];
  do_print("State: " + JSON.stringify(barrier.state, null, "\t"));
  Assert.equal(state.filename, filename);
  Assert.equal(state.lineNumber, lineNumber + 1);
  Assert.equal(state.name, BLOCKER_NAME);
  Assert.ok(state.stack.some(x => x.includes("test_state")), "The stack contains the caller function's name");
  Assert.ok(state.stack.some(x => x.includes(filename)), "The stack contains the calling file's name");

  deferred.resolve();
  yield promiseDone;
});

add_task(function*() {
  Services.prefs.clearUserPref("toolkit.asyncshutdown.testing");
});