text: // Copyright 2015 Software Freedom Conservancy. All Rights Reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //     http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  /**
   * @fileoverview Contains tests against promise error handling. Many tests use
   * goog.Promise to control test termination independent of webdriver.promise
   * (and notably webdriver.promise.ControlFlow).
   */
  
  'use strict';
  
  goog.require('goog.Promise');
  goog.require('goog.async.run');
  goog.require('goog.testing.jsunit');
  goog.require('goog.userAgent');
  goog.require('webdriver.promise');
  goog.require('webdriver.test.testutil');
  
  
  
  var StubError = webdriver.test.testutil.StubError,
      throwStubError = webdriver.test.testutil.throwStubError,
      assertIsStubError = webdriver.test.testutil.assertIsStubError;
  
  
  var flow, uncaughtExceptions;
  
  
  function shouldRunTests() {
    return !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10);
  }
  
  
  function setUp() {
    flow = webdriver.promise.controlFlow();
    uncaughtExceptions = [];
    flow.on('uncaughtException', onUncaughtException);
  }
  
  
  function tearDown() {
    return waitForIdle(flow).then(function() {
      assertArrayEquals('There were uncaught exceptions',
          [], uncaughtExceptions);
      flow.reset();
    });
  }
  
  
  function onUncaughtException(e) {
    uncaughtExceptions.push(e);
  }
  
  
  function waitForAbort(opt_flow, opt_n) {
    var n = opt_n || 1;
    var theFlow = opt_flow || flow;
    theFlow.removeAllListeners(
        webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);
    return new goog.Promise(function(fulfill, reject) {
      theFlow.once('idle', function() {
        reject(Error('expected flow to report an unhandled error'));
      });
  
      var errors = [];
      theFlow.on('uncaughtException', onError);
      function onError(e) {
        errors.push(e);
        if (errors.length === n) {
          theFlow.removeListener('uncaughtException', onError);
          fulfill(n === 1 ? errors[0] : errors);
        }
      }
    });
  }
  
  
  function waitForIdle(opt_flow) {
    var theFlow = opt_flow || flow;
    return new goog.Promise(function(fulfill, reject) {
      if (!theFlow.activeFrame_ && !theFlow.yieldCount_) {
        fulfill();
        return;
      }
      theFlow.once('idle', fulfill);
      theFlow.once('uncaughtException', reject);
    });
  }
  
  
  function testRejectedPromiseTriggersErrorCallback() {
    return webdriver.promise.rejected(new StubError).
        then(fail, assertIsStubError);
  }
  
  
  function testCallbackThrowsTriggersSubsequentErrorCallback_fulfilledPromise() {
    return webdriver.promise.fulfilled().
        then(throwStubError).
        then(fail, assertIsStubError);
  }
  
  
  function testCallbackThrowsTriggersSubsequentErrorCallback_rejectedPromise() {
    var e = Error('not the droids you are looking for');
    return webdriver.promise.rejected(e).
        then(fail, throwStubError).
        then(fail, assertIsStubError);
  }
  
  
  function
  testCallbackReturnsRejectedPromiseTriggersSubsequentErrback_fulfilled() {
    return webdriver.promise.fulfilled().then(function() {
      return webdriver.promise.rejected(new StubError);
    }).then(fail, assertIsStubError);
  }
  
  
  function
  testCallbackReturnsRejectedPromiseTriggersSubsequentErrback_rejected() {
    var e = Error('not the droids you are looking for');
    return webdriver.promise.rejected(e).
        then(fail, function() {
          return webdriver.promise.rejected(new StubError);
        }).
        then(fail, assertIsStubError);
  }
  
  
  function testReportsUnhandledRejectionsThroughTheControlFlow() {
    webdriver.promise.rejected(new StubError);
    return waitForAbort().then(assertIsStubError);
  }
  
  
  function testMultipleUnhandledRejectionsOutsideATask_reportedInOrderOccurred() {
    var e1 = Error('error 1');
    var e2 = Error('error 2');
  
    webdriver.promise.rejected(e1);
    webdriver.promise.rejected(e2);
  
    return waitForAbort(flow, 2).then(function(errors) {
      assertArrayEquals([e1, e2], errors);
    });
  }
  
  
  function testDoesNotReportUnhandledErrorIfHandlerAddedBeforeNextTick() {
    var promise = webdriver.promise.rejected(new StubError);
    promise.then(fail, assertIsStubError);
    return waitForIdle();
  }
  
  
  function testDoesNotReportUnhandledErrorIfHandlerAddedAsyncBeforeReport() {
    var called = false;
    return new goog.Promise(function(fulfill, reject) {
      var promise;
      goog.async.run(function() {
        promise.then(fail, function(e) {
          called = true;
          assertIsStubError(e);
        });
        waitForIdle().then(fulfill, reject);
      });
      promise = webdriver.promise.rejected(new StubError);
    }).then(function() {
      assertTrue(called);
    })
  }
  
  
  function testTaskThrows() {
    return flow.execute(throwStubError).then(fail, assertIsStubError);
  }
  
  
  function testTaskReturnsRejectedPromise() {
    return flow.execute(function() {
      return webdriver.promise.rejected(new StubError)
    }).then(fail, assertIsStubError);
  }
  
  
  function testTaskHasUnhandledRejection() {
    return flow.execute(function() {
      webdriver.promise.rejected(new StubError)
    }).then(fail, assertIsStubError);
  }
  
  
  function testTaskFails_returnedPromiseIsUnhandled() {
    flow.execute(throwStubError);
    return waitForAbort().then(assertIsStubError);
  }
  
  
  function testSubTaskFails_caughtByParentTask() {
    return flow.execute(function() {
      flow.execute(throwStubError);
    }).then(fail, assertIsStubError);
  }
  
  
  function testSubTaskFails_uncaughtErrorBubblesUpToFlow() {
    flow.execute(function() {
      flow.execute(throwStubError);
    });
    return waitForAbort().then(assertIsStubError);
  }
  
  
  function testNestedTaskFails_returnsUpToParent() {
    return flow.execute(function() {
      return flow.execute(function() {
        return flow.execute(throwStubError);
      });
    }).then(fail, assertIsStubError);
  }
  
  
  function testNestedTaskFails_uncaughtErrorBubblesUp_taskThrows() {
    flow.execute(function() {
      flow.execute(function() {
        flow.execute(throwStubError);
      });
    });
    return waitForAbort().then(assertIsStubError);
  }
  
  
  function testNestedTaskFails_uncaughtErrorBubblesUp_taskThrows_caughtAtRoot() {
    flow.execute(function() {
      flow.execute(function() {
        flow.execute(throwStubError);
      });
    }).then(fail, assertIsStubError);
    return waitForIdle();
  }
  
  
  function testNestedTaskFails_uncaughtErrorBubblesUp_promise() {
    flow.execute(function() {
      flow.execute(function() {
        flow.execute(function() {
          webdriver.promise.rejected(new StubError);
        });
      });
    });
    return waitForAbort().then(assertIsStubError);
  }
  
  
  function testNestedTaskFails_uncaughtErrorBubblesUp_promise_caughtAtRoot() {
    flow.execute(function() {
      flow.execute(function() {
        webdriver.promise.rejected(new StubError);
      });
    }).then(fail, assertIsStubError);
    return waitForIdle();
  }
  
  
  function testNestedTaskFails_mixtureOfHangingAndFreeSubTasks() {
    flow.execute(function() {
      return flow.execute(function() {
        flow.execute(throwStubError);
      });
    });
    return waitForAbort().then(assertIsStubError);
  }
  
  
  function testTaskReturnsPromiseLikeObjectThatInvokesErrback() {
    return flow.execute(function() {
      return {
        'then': function(_, errback) {
          errback('abc123');
        }
      };
    }).then(fail, function(value) {
      assertEquals('abc123', value);
    });
  }
  
  
  function testWaitConditionThrows_waitFailureIsCaught() {
    return flow.wait(throwStubError, 50).then(fail, assertIsStubError);
  }
  
  
  function testWaitConditionThrows_waitFailureIsNotCaught() {
    flow.wait(throwStubError, 50);
    return waitForAbort().then(assertIsStubError);
  }
  
  
  function testWaitConditionReturnsRejectedPromise_waitFailureIsCaught() {
    return flow.wait(function() {
      return webdriver.promise.rejected(new StubError);
    }, 50).then(fail, assertIsStubError);
  }
  
  
  function testWaitConditionReturnsRejectedPromise_waitFailureIsNotCaught() {
    flow.wait(function() {
      return webdriver.promise.rejected(new StubError);
    }, 50);
    return waitForAbort().then(assertIsStubError);
  }
  
  
  function testWaitConditionHasUnhandledPromiseRejection_waitFailureCaught() {
    return flow.wait(function() {
      webdriver.promise.rejected(new StubError);
    }, 50).then(fail, assertIsStubError);
  }
  
  
  function testWaitConditionHasUnhandledPromiseRejection_waitFailureNotCaught() {
    flow.wait(function() {
      webdriver.promise.rejected(new StubError);
    }, 50);
    return waitForAbort().then(assertIsStubError);
  }
  
  
  function testWaitConditionHasSubTaskFailure_caughtByWait() {
    return flow.wait(function() {
      flow.execute(function() {
        flow.execute(throwStubError);
      });
    }, 50).then(fail, assertIsStubError);
  }
  
  
  function testWaitConditionHasSubTaskFailure_notCaughtByWait() {
    flow.wait(function() {
      flow.execute(function() {
        flow.execute(throwStubError);
      });
    }, 50);
    return waitForAbort().then(assertIsStubError);
  }
  
  
  function testErrbackMayThrowANewError_startWithNormalPromise() {
    var error = Error('an error');
    return webdriver.promise.rejected(error).
        thenCatch(function(e) {
          assertEquals(e, error);
          throw new StubError;
        }).
        thenCatch(assertIsStubError);
  }
  
  
  function testErrbackMayThrowANewError_startWithTaskResult() {
    var error = Error('an error');
    return flow.execute(function() {
      throw error;
    }).
    thenCatch(function(e) {
      assertEquals(e, error);
      throw new StubError;
    }).
    thenCatch(assertIsStubError);
  }
  
  
  function testErrbackMayThrowANewError_uncaught_startWithNormalPromise() {
    var error = Error('an error');
    webdriver.promise.rejected(error).
        thenCatch(function(e) {
          assertEquals(e, error);
          throw new StubError;
        });
    return waitForAbort().then(assertIsStubError);
  }
  
  
  function testErrbackMayThrowANewError_uncaught_startWithTaskResult() {
    var error = Error('an error');
    flow.execute(function() {
      throw error;
    }).
    thenCatch(function(e) {
      assertEquals(e, error);
      throw new StubError;
    });
    return waitForAbort().then(assertIsStubError);
  }
  
  
  function testThrownPromiseIsHandledSameAsReturningPromise_promiseIsFulfilled() {
    return webdriver.promise.fulfilled().then(function() {
      throw webdriver.promise.fulfilled(1234);
    }).then(function(value) {
      assertEquals(1234, value);
    });
  }
  
  
  function testThrownPromiseIsHandledSameAsReturningPromise_promiseIsRejected() {
    return webdriver.promise.fulfilled().then(function() {
      throw webdriver.promise.rejected(new StubError);
    }).then(fail, assertIsStubError);
  }
  
  
  function testTaskThrowsPromise_taskSucceedsIfPromiseIsFulfilled() {
    flow.execute(function() {
      throw webdriver.promise.fulfilled(1234);
    }).then(function(value) {
      assertEquals(1234, value);
    });
    return waitForIdle();
  }
  
  
  function testTaskThrowsPromise_taskFailsIfPromiseIsRejected() {
    flow.execute(function() {
      throw webdriver.promise.rejected(new StubError);
    }).then(fail, assertIsStubError);
    return waitForIdle();
  }
  
  
  function testFailsTaskIfThereIsAnUnhandledErrorWhileWaitingOnTaskResult() {
    var d = webdriver.promise.defer();
    flow.execute(function() {
      setTimeout(function() {
        webdriver.promise.rejected(new StubError);
      }, 10);
      return d.promise;
    }).then(fail, assertIsStubError);
  
    return waitForIdle().then(function() {
      return d.promise;
    }).then(fail, function(e) {
      assertEquals('CancellationError: StubError', e.toString());
    });
  }
  
  
  function testFailsParentTaskIfAsyncScheduledTaskFails() {
    var d = webdriver.promise.defer();
    flow.execute(function() {
      setTimeout(function() {
        flow.execute(throwStubError);
      }, 10);
      return d.promise;
    }).then(fail, assertIsStubError);
   
    return waitForIdle().then(function() {
      return d.promise;
    }).then(fail, function(e) {
      assertEquals('CancellationError: StubError', e.toString());
    });
  }
  
  
  function testLongStackTraces_alwaysIncludesTaskStacksInFailures() {
    webdriver.promise.LONG_STACK_TRACES = false;
    flow.execute(function() {
      flow.execute(function() {
        flow.execute(throwStubError, 'throw error');
      }, 'two');
    }, 'three').
    then(fail, function(e) {
      assertIsStubError(e);
      if (!goog.isString(e.stack)) {
        return;
      }
      var messages = goog.array.filter(
          webdriver.stacktrace.getStack(e).split(/\n/), function(line, index) {
            return /^From: /.test(line);
          });
      assertArrayEquals([
        'From: Task: throw error',
        'From: Task: two',
        'From: Task: three'
      ], messages);
    });
    return waitForIdle();
  }
  
  
  function testLongStackTraces_doesNotIncludeCompletedTasks() {
    flow.execute(goog.nullFunction, 'succeeds');
    flow.execute(throwStubError, 'kaboom').then(fail, function(e) {
      assertIsStubError(e);
      if (!goog.isString(e.stack)) {
        return;
      }
      var messages = goog.array.filter(
          webdriver.stacktrace.getStack(e).split(/\n/), function(line, index) {
            return /^From: /.test(line);
          });
      assertArrayEquals(['From: Task: kaboom'], messages);
    });
    return waitForIdle();
  }
  
  
  function testLongStackTraces_doesNotIncludePromiseChainWhenDisabled() {
    webdriver.promise.LONG_STACK_TRACES = false;
    flow.execute(function() {
      flow.execute(function() {
        return webdriver.promise.fulfilled().
            then(goog.nullFunction).
            then(goog.nullFunction).
            then(throwStubError);
      }, 'eventually fails');
    }, 'start').
    then(fail, function(e) {
      assertIsStubError(e);
      if (!goog.isString(e.stack)) {
        return;
      }
      var messages = goog.array.filter(
          webdriver.stacktrace.getStack(e).split(/\n/), function(line, index) {
            return /^From: /.test(line);
          });
      assertArrayEquals([
        'From: Task: eventually fails',
        'From: Task: start'
      ], messages);
    });
    return waitForIdle();
  }
  
  
  function testLongStackTraces_includesPromiseChainWhenEnabled() {
    webdriver.promise.LONG_STACK_TRACES = true;
    flow.execute(function() {
      flow.execute(function() {
        return webdriver.promise.fulfilled().
            then(goog.nullFunction).
            then(goog.nullFunction).
            then(throwStubError);
      }, 'eventually fails');
    }, 'start').
    then(fail, function(e) {
      assertIsStubError(e);
      if (!goog.isString(e.stack)) {
        return;
      }
      var messages = goog.array.filter(
          webdriver.stacktrace.getStack(e).split(/\n/), function(line, index) {
            return /^From: /.test(line);
          });
      assertArrayEquals([
        'From: Promise: then',
        'From: Task: eventually fails',
        'From: Task: start'
      ], messages);
    });
    return waitForIdle();
  }
  