Bug 784519 - Part 4: signalingState mochi tests r=jsmith
authorAdam Roach [:abr] <adam@nostrum.com>
Thu, 16 May 2013 21:47:50 -0500
changeset 145796 347f88a0effa347584eab4fe3550c393d9aa6bbf
parent 145795 263154173cc8741b702be3f2cf1226444e867190
child 145797 e66c8746d23e7a0e115ac96465f7815b27506065
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjsmith
bugs784519
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 784519 - Part 4: signalingState mochi tests r=jsmith
dom/media/tests/mochitest/Makefile.in
dom/media/tests/mochitest/head.js
dom/media/tests/mochitest/pc.js
dom/media/tests/mochitest/test_peerConnection_addCandidateInHaveLocalOffer.html
dom/media/tests/mochitest/test_peerConnection_setLocalAnswerInHaveLocalOffer.html
dom/media/tests/mochitest/test_peerConnection_setLocalAnswerInStable.html
dom/media/tests/mochitest/test_peerConnection_setLocalOfferInHaveRemoteOffer.html
dom/media/tests/mochitest/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html
dom/media/tests/mochitest/test_peerConnection_setRemoteAnswerInStable.html
dom/media/tests/mochitest/test_peerConnection_setRemoteOfferInHaveLocalOffer.html
--- a/dom/media/tests/mochitest/Makefile.in
+++ b/dom/media/tests/mochitest/Makefile.in
@@ -30,16 +30,23 @@ MOCHITEST_FILES = \
   test_peerConnection_basicAudioVideo.html \
   test_peerConnection_basicAudioVideoCombined.html \
   test_peerConnection_basicVideo.html \
   test_peerConnection_errorCallbacks.html \
   test_peerConnection_offerRequiresReceiveAudio.html \
   test_peerConnection_offerRequiresReceiveVideo.html \
   test_peerConnection_offerRequiresReceiveVideoAudio.html \
   test_peerConnection_throwInCallbacks.html \
+  test_peerConnection_setLocalAnswerInStable.html \
+  test_peerConnection_setRemoteAnswerInStable.html \
+  test_peerConnection_setLocalAnswerInHaveLocalOffer.html \
+  test_peerConnection_setRemoteOfferInHaveLocalOffer.html \
+  test_peerConnection_setLocalOfferInHaveRemoteOffer.html \
+  test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html \
+  test_peerConnection_addCandidateInHaveLocalOffer.html \
   test_peerConnection_bug822674.html \
   test_peerConnection_bug825703.html \
   test_peerConnection_bug827843.html \
   test_peerConnection_bug834153.html \
   test_peerConnection_bug835370.html \
   head.js \
   mediaStreamPlayback.js \
   pc.js \
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -177,16 +177,28 @@ function checkMediaStreamTracks(constrai
 function unexpectedCallbackAndFinish(error) {
   /**
    * @param {object} aObj
    *        The object fired back from the callback
    */
   return function(aObj) {
     var where = error.fileName + ":" + error.lineNumber;
     if (aObj && aObj.name && aObj.message) {
-      ok(false, "Unexpected error callback from " + where + " with name = '" +
+      ok(false, "Unexpected callback/event from " + where + " with name = '" +
                 aObj.name + "', message = '" + aObj.message + "'");
     } else {
-      ok(false, "Unexpected error callback from " + where + " with " + aObj);
+      ok(false, "Unexpected callback/event from " + where + " with " + aObj);
     }
     SimpleTest.finish();
   }
 }
+
+/**
+ * Generates a callback function suitable for putting int a success
+ * callback in circumstances where success is unexpected. The callback,
+ * if activated, will kill off the test gracefully.
+ */
+
+function unexpectedSuccessCallbackAndFinish(error, reason) {
+  return function() {
+    unexpectedCallbackAndFinish(error)(message);
+  }
+}
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -242,61 +242,71 @@ var commandsPeerConnection = [
     'PC_REMOTE_GUM',
     function (test) {
       test.pcRemote.getAllUserMedia(function () {
         test.next();
       });
     }
   ],
   [
+    'PC_CHECK_INITIAL_SIGNALINGSTATE',
+    function (test) {
+      is(test.pcLocal.signalingState,"stable", "Initial local signalingState is stable");
+      is(test.pcRemote.signalingState,"stable", "Initial remote signalingState is stable");
+      test.next();
+    }
+  ],
+  [
     'PC_LOCAL_CREATE_OFFER',
     function (test) {
       test.pcLocal.createOffer(function () {
+        is(test.pcLocal.signalingState, "stable", "Local create offer does not change signaling state");
         test.next();
       });
     }
   ],
   [
     'PC_LOCAL_SET_LOCAL_DESCRIPTION',
     function (test) {
-      test.pcLocal.setLocalDescription(test.pcLocal._last_offer, function () {
-        test.next();
-      });
+      test.expectStateChange(test.pcLocal, "have-local-offer", test);
+      test.pcLocal.setLocalDescription(test.pcLocal._last_offer,
+        test.checkStateInCallback(test.pcLocal, "have-local-offer", test));
     }
   ],
   [
     'PC_REMOTE_SET_REMOTE_DESCRIPTION',
     function (test) {
-      test.pcRemote.setRemoteDescription(test.pcLocal._last_offer, function () {
-        test.next();
-      });
+      test.expectStateChange(test.pcRemote, "have-remote-offer", test);
+      test.pcRemote.setRemoteDescription(test.pcLocal._last_offer,
+        test.checkStateInCallback(test.pcRemote, "have-remote-offer", test));
     }
   ],
   [
     'PC_REMOTE_CREATE_ANSWER',
     function (test) {
       test.pcRemote.createAnswer(function () {
+        is(test.pcRemote.signalingState, "have-remote-offer", "Remote create offer does not change signaling state");
         test.next();
       });
     }
   ],
   [
     'PC_LOCAL_SET_REMOTE_DESCRIPTION',
     function (test) {
-      test.pcLocal.setRemoteDescription(test.pcRemote._last_answer, function () {
-        test.next();
-      });
+      test.expectStateChange(test.pcLocal, "stable", test);
+      test.pcLocal.setRemoteDescription(test.pcRemote._last_answer,
+        test.checkStateInCallback(test.pcLocal, "stable", test));
     }
   ],
   [
     'PC_REMOTE_SET_LOCAL_DESCRIPTION',
     function (test) {
-      test.pcRemote.setLocalDescription(test.pcRemote._last_answer, function () {
-        test.next();
-      });
+      test.expectStateChange(test.pcRemote, "stable", test);
+      test.pcRemote.setLocalDescription(test.pcRemote._last_answer,
+        test.checkStateInCallback(test.pcRemote, "stable", test));
     }
   ],
   [
     'PC_LOCAL_CHECK_MEDIA',
     function (test) {
       test.pcLocal.checkMedia(test.pcRemote.constraints);
       test.next();
     }
@@ -389,16 +399,74 @@ PeerConnectionTest.prototype.teardown = 
     this.pcRemote.close();
     this.pcRemote = null;
   }
 
   info("Test finished");
   SimpleTest.finish();
 };
 
+/**
+ * Sets up the "onsignalingstatechange" handler for the indicated peerconnection
+ * as a one-shot test. If the test.commandSuccess flag is set when the event
+ * happens, then the next test in the command chain is triggered. After
+ * running, this sets the event handler so that it will fail the test if
+ * it fires again before we expect it. This is intended to be used in
+ * conjunction with checkStateInCallback, below.
+ *
+ * @param {pcw} PeerConnectionWrapper
+ *        The peer connection to expect a state change on
+ * @param {state} string
+ *        The state that we expect to change to
+ * @param {test} PeerConnectionTest
+ *        The test strucure currently in use.
+ */
+PeerConnectionTest.prototype.expectStateChange =
+function PCT_expectStateChange(pcw, state, test) {
+  pcw.signalingChangeEvent = false;
+  pcw._pc.onsignalingstatechange = function() {
+    pcw._pc.onsignalingstatechange = unexpectedCallbackAndFinish(new Error);
+    is(pcw._pc.signalingState, state, pcw.label + ": State is " + state + " in onsignalingstatechange");
+    pcw.signalingChangeEvent = true;
+    if (pcw.commandSuccess) {
+      test.next();
+    } else {
+      info("Waiting for success callback...");
+    }
+  };
+}
+
+/**
+ * Returns a function, suitable for use as a success callback, that
+ * checks the signaling state of the PC; and, if the signalingstatechange
+ * event has already fired, moves on to the next test case. This is
+ * intended to be used in conjunction with expectStateChange, above.
+ *
+ * @param {pcw} PeerConnectionWrapper
+ *        The peer connection to expect a state change on
+ * @param {state} string
+ *        The state that we expect to change to
+ * @param {test} PeerConnectionTest
+ *        The test strucure currently in use.
+ */
+
+PeerConnectionTest.prototype.checkStateInCallback =
+function PCT_checkStateInCallback(pcw, state, test) {
+  pcw.commandSuccess = false;
+  return function() {
+    pcw.commandSuccess = true;
+    is(pcw.signalingState, state, pcw.label + ": State is " + state + " in success callback");
+    if (pcw.signalingChangeEvent) {
+      test.next();
+    } else {
+      info("Waiting for signalingstatechange event...");
+    }
+  };
+}
+
 
 /**
  * This class handles acts as a wrapper around a PeerConnection instance.
  *
  * @constructor
  * @param {string} label
  *        Description for the peer connection instance
  * @param {object} configuration
@@ -415,16 +483,19 @@ function PeerConnectionWrapper(label, co
   info("Creating new PeerConnectionWrapper: " + this.label);
   this._pc = new mozRTCPeerConnection(this.configuration);
 
   var self = this;
   this._pc.onaddstream = function (event) {
     // Bug 834835: Assume type is video until we get get{Audio,Video}Tracks.
     self.attachMedia(event.stream, 'video', 'remote');
   };
+
+  // Make sure no signaling state changes are fired until we expect them to
+  this._pc.onsignalingstatechange = unexpectedCallbackAndFinish(new Error);
 }
 
 PeerConnectionWrapper.prototype = {
 
   /**
    * Returns the local description.
    *
    * @returns {object} The local description
@@ -458,16 +529,25 @@ PeerConnectionWrapper.prototype = {
    * @param {object} desc
    *        The new remote description
    */
   set remoteDescription(desc) {
     this._pc.remoteDescription = desc;
   },
 
   /**
+   * Returns the remote signaling state.
+   *
+   * @returns {object} The local description
+   */
+  get signalingState() {
+    return this._pc.signalingState;
+  },
+
+  /**
    * Callback when we get media from either side. Also an appropriate
    * HTML media element will be created.
    *
    * @param {MediaStream} stream
    *        Media stream to handle
    * @param {string} type
    *        The type of media stream ('audio' or 'video')
    * @param {string} side
@@ -567,32 +647,107 @@ PeerConnectionWrapper.prototype = {
     var self = this;
     this._pc.setLocalDescription(desc, function () {
       info("Successfully set the local description for " + self.label);
       onSuccess();
     }, unexpectedCallbackAndFinish(new Error));
   },
 
   /**
+   * Tries to set the local description and expect failure. Automatically
+   * causes the test case to fail if the call succeeds.
+   *
+   * @param {object} desc
+   *        mozRTCSessionDescription for the local description request
+   * @param {function} onFailure
+   *        Callback to execute if the call fails.
+   */
+  setLocalDescriptionAndFail : function PCW_setLocalDescriptionAndFail(desc, onFailure) {
+    var self = this;
+    this._pc.setLocalDescription(desc,
+      unexpectedSuccessCallbackAndFinish(new Error, "setLocalDescription should have failed."),
+      function (err) {
+        info("As expected, failed to set the local description for " + self.label);
+        onFailure(err);
+    });
+  },
+
+  /**
    * Sets the remote description and automatically handles the failure case.
    *
    * @param {object} desc
    *        mozRTCSessionDescription for the remote description request
    * @param {function} onSuccess
    *        Callback to execute if the remote description was set successfully
    */
   setRemoteDescription : function PCW_setRemoteDescription(desc, onSuccess) {
     var self = this;
     this._pc.setRemoteDescription(desc, function () {
       info("Successfully set remote description for " + self.label);
       onSuccess();
     }, unexpectedCallbackAndFinish(new Error));
   },
 
   /**
+   * Tries to set the remote description and expect failure. Automatically
+   * causes the test case to fail if the call succeeds.
+   *
+   * @param {object} desc
+   *        mozRTCSessionDescription for the remote description request
+   * @param {function} onFailure
+   *        Callback to execute if the call fails.
+   */
+  setRemoteDescriptionAndFail : function PCW_setRemoteDescriptionAndFail(desc, onFailure) {
+    var self = this;
+    this._pc.setRemoteDescription(desc,
+      unexpectedSuccessCallbackAndFinish(new Error, "setRemoteDescription should have failed."),
+      function (err) {
+        info("As expected, failed to set the remote description for " + self.label);
+        onFailure(err);
+    });
+  },
+
+  /**
+   * Adds an ICE candidate and automatically handles the failure case.
+   *
+   * @param {object} candidate
+   *        SDP candidate
+   * @param {function} onSuccess
+   *        Callback to execute if the local description was set successfully
+   */
+  addIceCandidate : function PCW_addIceCandidate(candidate, onSuccess) {
+    var self = this;
+
+    this._pc.addIceCandidate(candidate, function () {
+      info("Successfully added an ICE candidate to " + self.label);
+      onSuccess();
+    }, unexpectedCallbackAndFinish(new Error));
+  },
+
+  /**
+   * Tries to add an ICE candidate and expects failure. Automatically
+   * causes the test case to fail if the call succeeds.
+   *
+   * @param {object} candidate
+   *        SDP candidate
+   * @param {function} onFailure
+   *        Callback to execute if the call fails.
+   */
+  addIceCandidateAndFail : function PCW_addIceCandidateAndFail(candidate, onFailure) {
+    var self = this;
+
+    this._pc.addIceCandidate(candidate,
+      unexpectedSuccessCallbackAndFinish(new Error, "addIceCandidate should have failed."),
+      function (err) {
+        info("As expected, failed to add an ICE candidate to " + self.label);
+        onFailure(err);
+    }) ;
+  },
+
+  /**
    * Checks that we are getting the media we expect.
    *
    * @param {object} constraintsRemote
    *        The media constraints of the remote peer connection object
    */
   checkMedia : function PCW_checkMedia(constraintsRemote) {
     is(this._pc.localStreams.length, this.constraints.length,
        this.label + ' has ' + this.constraints.length + ' local streams');
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_addCandidateInHaveLocalOffer.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "784519",
+    title: "addCandidate (answer) in 'have-local-offer'"
+  });
+
+  var test;
+  runTest(function () {
+    test = new PeerConnectionTest();
+    test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    test.chain.removeAfter("PC_LOCAL_SET_LOCAL_DESCRIPTION");
+
+    test.chain.append([[
+      "PC_LOCAL_ADD_CANDIDATE",
+      function (test) {
+        test.pcLocal.addIceCandidateAndFail(
+          mozRTCIceCandidate(
+            {candidate:"1 1 UDP 2130706431 192.168.2.1 50005 typ host",
+             sdpMLineIndex: 1}),
+          function(err) {
+            is(err.name, "INVALID_STATE", "Error is INVALID_STATE");
+            test.next();
+          } );
+      }
+    ]]);
+
+    test.run();
+  });
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_setLocalAnswerInHaveLocalOffer.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "784519",
+    title: "setLocalDescription (answer) in 'have-local-offer'"
+  });
+
+  var test;
+  runTest(function () {
+    test = new PeerConnectionTest();
+    test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    test.chain.removeAfter("PC_LOCAL_SET_LOCAL_DESCRIPTION");
+
+    test.chain.append([[
+      "PC_LOCAL_SET_LOCAL_ANSWER",
+      function (test) {
+        test.pcLocal._last_offer.type="answer";
+        test.pcLocal.setLocalDescriptionAndFail(test.pcLocal._last_offer,
+          function(err) {
+            is(err.name, "INVALID_STATE", "Error is INVALID_STATE");
+            test.next();
+          } );
+      }
+    ]]);
+
+    test.run();
+  });
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_setLocalAnswerInStable.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "784519",
+    title: "setLocalDescription (answer) in 'stable'"
+  });
+
+  var test;
+  runTest(function () {
+    test = new PeerConnectionTest();
+    test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    test.chain.removeAfter("PC_LOCAL_CREATE_OFFER");
+
+    test.chain.append([[
+      "PC_LOCAL_SET_LOCAL_ANSWER",
+      function (test) {
+        test.pcLocal._last_offer.type="answer";
+        test.pcLocal.setLocalDescriptionAndFail(test.pcLocal._last_offer,
+          function(err) {
+            is(err.name, "INVALID_STATE", "Error is INVALID_STATE");
+            test.next();
+          } );
+      }
+    ]]);
+
+    test.run();
+  });
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_setLocalOfferInHaveRemoteOffer.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "784519",
+    title: "setLocalDescription (offer) in 'have-remote-offer'"
+  });
+
+  var test;
+  runTest(function () {
+    test = new PeerConnectionTest();
+    test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    test.chain.removeAfter("PC_REMOTE_SET_REMOTE_DESCRIPTION");
+
+    test.chain.append([[
+      "PC_REMOTE_SET_LOCAL_OFFER",
+      function (test) {
+        test.pcRemote.setLocalDescriptionAndFail(test.pcLocal._last_offer,
+          function(err) {
+            is(err.name, "INVALID_STATE", "Error is INVALID_STATE");
+            test.next();
+          } );
+      }
+    ]]);
+
+    test.run();
+  });
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "784519",
+    title: "setRemoteDescription (answer) in 'have-remote-offer'"
+  });
+
+  var test;
+  runTest(function () {
+    test = new PeerConnectionTest();
+    test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    test.chain.removeAfter("PC_REMOTE_SET_REMOTE_DESCRIPTION");
+
+    test.chain.append([[
+      "PC_REMOTE_SET_REMOTE_ANSWER",
+      function (test) {
+        test.pcLocal._last_offer.type="answer";
+        test.pcRemote.setRemoteDescriptionAndFail(test.pcLocal._last_offer,
+          function(err) {
+            is(err.name, "INVALID_STATE", "Error is INVALID_STATE");
+            test.next();
+          } );
+      }
+    ]]);
+
+    test.run();
+  });
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_setRemoteAnswerInStable.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "784519",
+    title: "setRemoteDescription (answer) in 'stable'"
+  });
+
+  var test;
+  runTest(function () {
+    test = new PeerConnectionTest();
+    test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    test.chain.removeAfter("PC_LOCAL_CREATE_OFFER");
+
+    test.chain.append([[
+      "PC_LOCAL_SET_REMOTE_ANSWER",
+      function (test) {
+        test.pcLocal._last_offer.type="answer";
+        test.pcLocal.setRemoteDescriptionAndFail(test.pcLocal._last_offer,
+          function(err) {
+            is(err.name, "INVALID_STATE", "Error is INVALID_STATE");
+            test.next();
+          } );
+      }
+    ]]);
+
+    test.run();
+  });
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_setRemoteOfferInHaveLocalOffer.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "784519",
+    title: "setRemoteDescription (offer) in 'have-local-offer'"
+  });
+
+  var test;
+  runTest(function () {
+    test = new PeerConnectionTest();
+    test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    test.chain.removeAfter("PC_LOCAL_SET_LOCAL_DESCRIPTION");
+
+    test.chain.append([[
+      "PC_LOCAL_SET_REMOTE_OFFER",
+      function (test) {
+        test.pcLocal.setRemoteDescriptionAndFail(test.pcLocal._last_offer,
+          function(err) {
+            is(err.name, "INVALID_STATE", "Error is INVALID_STATE");
+            test.next();
+          } );
+      }
+    ]]);
+
+    test.run();
+  });
+</script>
+</pre>
+</body>
+</html>