Bug 908012 - B2G RIL: Add telephony marionette tests for conference call. r=vicamo
authorHsin-Yi Tsai <htsai@mozilla.com>
Mon, 09 Sep 2013 21:13:13 +0800
changeset 159280 980c155d507ead3929b3a67a379db8b4a041da82
parent 159279 566aa5309ecc4b06588766e8dc4d2143e588ed8a
child 159281 38d48d8323e3e8cc00bf4f5e23465109fb367442
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvicamo
bugs908012
milestone26.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 908012 - B2G RIL: Add telephony marionette tests for conference call. r=vicamo
dom/telephony/test/marionette/manifest.ini
dom/telephony/test/marionette/test_conference.js
--- a/dom/telephony/test/marionette/manifest.ini
+++ b/dom/telephony/test/marionette/manifest.ini
@@ -36,8 +36,9 @@ disabled = Bug 820802
 [test_outgoing_onstatechange.js]
 disabled = Bug 821966
 [test_redundant_operations.js]
 disabled = Bug 821927
 [test_multiple_hold.js]
 disabled = Bug 821958
 [test_outgoing_emergency_in_airplane_mode.js]
 [test_emergency_label.js]
+[test_conference.js]
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_conference.js
@@ -0,0 +1,632 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+
+SpecialPowers.addPermission("telephony", true, document);
+
+let telephony = window.navigator.mozTelephony;
+let conference = telephony.conferenceGroup;
+let outNumber = "5555551111";
+let inNumber = "5555552222";
+let inNumber2 = "5555553333";
+let outgoingCall;
+let incomingCall;
+let incomingCall2;
+let gotOriginalConnected = false;
+
+let pendingEmulatorCmdCount = 0;
+function sendCmdToEmulator(cmd, callback) {
+  ++pendingEmulatorCmdCount;
+  runEmulatorCmd(cmd, function(result) {
+    --pendingEmulatorCmdCount;
+    if (callback) {
+      callback(result);
+    }
+  });
+}
+
+// Make sure there's no pending event before we jump to the next case.
+function receivedPending(received, pending, nextTest) {
+  let index = pending.indexOf(received);
+  if (index != -1) {
+    pending.splice(index, 1);
+  }
+  if (pending.length == 0) {
+    nextTest();
+  }
+}
+
+function checkState(telephonyActive, telephonyCalls, conferenceState,
+                    conferenceCalls) {
+  is(telephony.active, telephonyActive);
+
+  is(telephony.calls.length, telephonyCalls.length);
+  for (let i = 0; i < telephonyCalls.length; i++) {
+    is(telephony.calls[i], telephonyCalls[i]);
+  }
+
+  is(conference.state, conferenceState);
+  is(conference.calls.length, conferenceCalls.length);
+  for (let i = 0; i < conferenceCalls.length; i++) {
+    is(conference.calls[i], conferenceCalls[i]);
+  }
+}
+
+function verifyInitialState() {
+  log("Verifying initial state.");
+  ok(telephony);
+  ok(conference);
+
+  checkState(null, [], '', []);
+
+  sendCmdToEmulator("gsm clear", function(result) {
+    log("Clear up calls from a previous test if any.");
+    is(result[0], "OK");
+    dial();
+  });
+}
+
+function dial() {
+  log("Making an outgoing call.");
+  outgoingCall = telephony.dial(outNumber);
+  ok(outgoingCall);
+  is(outgoingCall.number, outNumber);
+  is(outgoingCall.state, "dialing");
+
+  checkState(outgoingCall, [outgoingCall], '', []);
+
+  outgoingCall.onalerting = function(event) {
+    log("Received 'onalerting' call event.");
+
+    outgoingCall.onalerting = null;
+
+    is(outgoingCall, event.call);
+    is(outgoingCall.state, "alerting");
+
+    sendCmdToEmulator("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "outbound to  " + outNumber + " : ringing");
+      is(result[1], "OK");
+
+      answer();
+    });
+  };
+}
+
+function answer() {
+  log("Answering the outgoing call.");
+
+  // We get no "connecting" event when the remote party answers the call.
+  outgoingCall.onconnected = function(event) {
+    log("Received 'connected' call event for the original outgoing call.");
+
+    outgoingCall.onconnected = null;
+
+    is(outgoingCall, event.call);
+    is(outgoingCall.state, "connected");
+    is(outgoingCall, telephony.active);
+
+    sendCmdToEmulator("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "outbound to  " + outNumber + " : active");
+      is(result[1], "OK");
+
+      if(!gotOriginalConnected){
+        gotOriginalConnected = true;
+        simulateIncoming();
+      } else {
+        // Received connected event for original call multiple times (fail).
+        ok(false,
+           "Received 'connected' event for original call multiple times");
+      }
+    });
+  };
+  sendCmdToEmulator("gsm accept " + outNumber);
+}
+
+// With one connected call already, simulate an incoming call.
+function simulateIncoming() {
+  log("Simulating an incoming call (with one call already connected).");
+
+  telephony.onincoming = function(event) {
+    log("Received 'incoming' call event.");
+
+    telephony.onincoming = null;
+
+    incomingCall = event.call;
+    ok(incomingCall);
+    is(incomingCall.number, inNumber);
+    is(incomingCall.state, "incoming");
+
+    // Should be two calls now.
+    checkState(outgoingCall, [outgoingCall, incomingCall], '', []);
+
+    sendCmdToEmulator("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "outbound to  " + outNumber + " : active");
+      is(result[1], "inbound from " + inNumber + " : incoming");
+      is(result[2], "OK");
+
+      answerIncoming();
+    });
+  };
+  sendCmdToEmulator("gsm call " + inNumber);
+}
+
+// Answer incoming call; original outgoing call should be held.
+function answerIncoming() {
+  log("Answering the incoming call.");
+
+  let gotConnecting = false;
+  incomingCall.onconnecting = function(event) {
+    log("Received 'connecting' call event for incoming/2nd call.");
+
+    incomingCall.onconnecting = null;
+
+    is(incomingCall, event.call);
+    is(incomingCall.state, "connecting");
+    gotConnecting = true;
+  };
+
+  incomingCall.onconnected = function(event) {
+    log("Received 'connected' call event for incoming/2nd call.");
+
+    incomingCall.onconnected = null;
+
+    is(incomingCall, event.call);
+    is(incomingCall.state, "connected");
+    ok(gotConnecting);
+
+    is(outgoingCall.state, "held");
+    checkState(incomingCall, [outgoingCall, incomingCall], '', []);
+
+    sendCmdToEmulator("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "outbound to  " + outNumber + " : held");
+      is(result[1], "inbound from " + inNumber + " : active");
+      is(result[2], "OK");
+
+      conferenceAddTwoCalls();
+    });
+  };
+  incomingCall.answer();
+}
+
+// Create a conference call. The calls in conference share the same call state.
+function conferenceAddTwoCalls() {
+  log("Creating a conference call.");
+
+  let pending = ["conference.oncallschanged", "conference.onconnected",
+                 "outgoingCall.onstatechange", "outgoingCall.ongroupchange",
+                 "incomingCall.onstatechange", "incomingCall.ongroupchange"];
+
+  // We are expecting to receive conference.oncallschanged two times since
+  // two calls are added into conference.
+  let expected = [outgoingCall, incomingCall];
+  conference.oncallschanged = function(event) {
+    log("Received 'callschanged' event for the conference call.");
+
+    let index = expected.indexOf(event.call);
+    ok(index != -1);
+    expected.splice(index, 1);
+    is(conference.calls[conference.calls.length - 1].number, event.call.number);
+
+    if (expected.length == 0) {
+      conference.oncallschanged = null;
+      receivedPending("conference.oncallschanged", pending, conferenceHold);
+    }
+  };
+
+  conference.onconnected = function(event) {
+    log("Received 'connected' event for the conference call.");
+    conference.onconnected = null;
+
+    ok(!conference.oncallschanged);
+
+    checkState(conference, [], 'connected', [outgoingCall, incomingCall]);
+
+    sendCmdToEmulator("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "outbound to  " + outNumber + " : active");
+      is(result[1], "inbound from " + inNumber + " : active");
+      is(result[2], "OK");
+
+      receivedPending("conference.onconnected", pending, conferenceHold);
+    });
+  };
+
+  outgoingCall.ongroupchange = function(event) {
+    log("Received 'groupchange' event for the outgoing call.");
+    outgoingCall.ongroupchange = null;
+
+    ok(outgoingCall.group);
+    is(outgoingCall.group, conference);
+
+    receivedPending("outgoingCall.ongroupchange", pending, conferenceHold);
+  };
+
+  outgoingCall.onstatechange = function(event) {
+    log("Received 'statechange' event for the outgoing call.");
+    outgoingCall.onstatechange = null;
+
+    ok(!outgoingCall.ongroupchange);
+    is(outgoingCall.state, conference.state);
+
+    receivedPending("outgoingCall.onstatechange", pending, conferenceHold);
+  };
+
+  incomingCall.ongroupchange = function(event) {
+    log("Received 'groupchange' event for the incoming call.");
+    incomingCall.ongroupchange = null;
+
+    ok(incomingCall.group);
+    is(incomingCall.group, conference);
+
+    receivedPending("incomingCall.ongroupchange", pending, conferenceHold);
+  };
+
+  incomingCall.onstatechange = function(event) {
+    log("Received 'statechange' event for the incoming call.");
+    incomingCall.onstatechange = null;
+
+    ok(!incomingCall.ongroupchange);
+    is(incomingCall.state, conference.state);
+
+    receivedPending("incomingCall.onstatechange", pending, conferenceHold);
+  };
+
+  conference.add(outgoingCall, incomingCall);
+}
+
+function conferenceHold() {
+  log("Holding the conference call.");
+
+  let pending = ["conference.onholding", "conference.onheld",
+                 "outgoingCall.onholding", "outgoingCall.onheld",
+                 "incomingCall.onholding", "incomingCall.onheld"];
+
+  conference.onholding = function(event) {
+    log("Received 'holding' event for the conference call.");
+    conference.onholding = null;
+
+    is(conference.state, 'holding');
+
+    receivedPending("conference.onholding", pending, conferenceResume);
+  };
+
+  conference.onheld = function(event) {
+    log("Received 'held' event for the conference call.");
+    conference.onheld = null;
+
+    ok(!conference.onholding);
+    checkState(null, [], 'held', [outgoingCall, incomingCall]);
+
+    sendCmdToEmulator("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "outbound to  " + outNumber + " : held");
+      is(result[1], "inbound from " + inNumber + " : held");
+      is(result[2], "OK");
+
+      receivedPending("conference.onheld", pending, conferenceResume);
+    });
+  };
+
+  outgoingCall.onholding = function(event) {
+    log("Received 'holding' event for the outgoing call in conference.");
+    outgoingCall.onholding = null;
+
+    is(outgoingCall.state, 'holding');
+
+    receivedPending("outgoingCall.onholding", pending, conferenceResume);
+  };
+
+  outgoingCall.onheld = function(event) {
+    log("Received 'held' event for the outgoing call in conference.");
+    outgoingCall.onheld = null;
+
+    ok(!outgoingCall.onholding);
+    is(outgoingCall.state, 'held');
+
+    receivedPending("outgoingCall.onheld", pending, conferenceResume);
+  };
+
+  incomingCall.onholding = function(event) {
+    log("Received 'holding' event for the incoming call in conference.");
+    incomingCall.onholding = null;
+
+    is(incomingCall.state, 'holding');
+
+    receivedPending("incomingCall.onholding", pending, conferenceResume);
+  };
+
+  incomingCall.onheld = function(event) {
+    log("Received 'held' event for the incoming call in conference.");
+    incomingCall.onheld = null;
+
+    ok(!incomingCall.onholding);
+    is(incomingCall.state, 'held');
+
+    receivedPending("incomingCall.onheld", pending, conferenceResume);
+  };
+
+  conference.hold();
+}
+
+function conferenceResume() {
+  log("Resuming the held conference call.");
+
+  let pending = ["conference.onresuming", "conference.onconnected",
+                 "outgoingCall.onresuming", "outgoingCall.onconnected",
+                 "incomingCall.onresuming", "incomingCall.onconnected"];
+
+  conference.onresuming = function(event) {
+    log("Received 'resuming' event for the conference call.");
+    conference.onresuming = null;
+
+    is(conference.state, 'resuming');
+
+    receivedPending("conference.onresuming", pending, simulate2ndIncoming);
+  };
+
+  conference.onconnected = function(event) {
+    log("Received 'connected' event for the conference call.");
+    conference.onconnected = null;
+
+    ok(!conference.onresuming);
+    checkState(conference, [], 'connected', [outgoingCall, incomingCall]);
+
+    sendCmdToEmulator("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "outbound to  " + outNumber + " : active");
+      is(result[1], "inbound from " + inNumber + " : active");
+      is(result[2], "OK");
+
+      receivedPending("conference.onconnected", pending, simulate2ndIncoming);
+    });
+  };
+
+  outgoingCall.onresuming = function(event) {
+    log("Received 'resuming' event for the outgoing call in conference.");
+    outgoingCall.onresuming = null;
+
+    is(outgoingCall.state, 'resuming');
+
+    receivedPending("outgoingCall.onresuming", pending, simulate2ndIncoming);
+  };
+
+  outgoingCall.onconnected = function(event) {
+    log("Received 'connected' event for the outgoing call in conference.");
+    outgoingCall.onconnected = null;
+
+    ok(!outgoingCall.onresuming);
+    is(outgoingCall.state, 'connected');
+
+    receivedPending("outgoingCall.onconnected", pending, simulate2ndIncoming);
+  };
+
+  incomingCall.onresuming = function(event) {
+    log("Received 'resuming' event for the incoming call in conference.");
+    incomingCall.onresuming = null;
+
+    is(incomingCall.state, 'resuming');
+
+    receivedPending("incomingCall.onresuming", pending, simulate2ndIncoming);
+  };
+
+  incomingCall.onconnected = function(event) {
+    log("Received 'connected' event for the incoming call in conference.");
+    incomingCall.onconnected = null;
+
+    ok(!incomingCall.onresuming);
+    is(incomingCall.state, 'connected');
+
+    receivedPending("incomingCall.onconnected", pending, simulate2ndIncoming);
+  };
+
+  conference.resume();
+}
+
+function simulate2ndIncoming() {
+  log("Simulating 2nd incoming call (with one conference call already).");
+
+  telephony.onincoming = function(event) {
+    log("Received 'incoming' call event.");
+
+    telephony.onincoming = null;
+
+    incomingCall2 = event.call;
+    ok(incomingCall2);
+    is(incomingCall2.number, inNumber2);
+    is(incomingCall2.state, "incoming");
+
+    checkState(conference, [incomingCall2], 'connected', [outgoingCall, incomingCall]);
+
+    sendCmdToEmulator("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "outbound to  " + outNumber + " : active");
+      is(result[1], "inbound from " + inNumber + " : active");
+      is(result[2], "inbound from " + inNumber2 + " : incoming");
+      is(result[3], "OK");
+
+      answer2ndIncoming();
+    });
+  };
+  sendCmdToEmulator("gsm call " + inNumber2);
+}
+
+function answer2ndIncoming() {
+  log("Answering the 2nd incoming call when there's a connected conference call.");
+
+  let gotConnecting = false;
+  incomingCall2.onconnecting = function(event) {
+    log("Received 'connecting' event for the 2nd incoming call.");
+    incomingCall2.onconnecting = null;
+
+    is(incomingCall2, event.call);
+    is(incomingCall2.state, "connecting");
+    gotConnecting = true;
+  };
+
+  incomingCall2.onconnected = function(event) {
+    incomingCall2.onconnected = null;
+
+    is(incomingCall2, event.call);
+    is(incomingCall2.state, "connected");
+    ok(gotConnecting);
+
+    is(incomingCall2, telephony.active);
+
+    sendCmdToEmulator("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "outbound to  " + outNumber + " : held");
+      is(result[1], "inbound from " + inNumber + " : held");
+      is(result[2], "inbound from " + inNumber2 + " : active");
+      is(result[3], "OK");
+
+      conferenceAddOneCall();
+    });
+  };
+  incomingCall2.answer();
+}
+
+function conferenceAddOneCall() {
+  log("Adding one more call to the conference call.");
+
+  let callToAdd = incomingCall2;
+  let pending = ["conference.oncallschanged", "conference.onconnected",
+                 "callToAdd.ongroupchange", "callToAdd.onconnected"];
+
+  ok(!callToAdd.group);
+
+  conference.oncallschanged = function(event) {
+    log("Received 'callschanged' event for the conference call.");
+    conference.oncallschanged = null;
+
+    ok(event.call, callToAdd);
+    is(conference.calls.length, 3);
+    is(conference.calls[2].number, event.call.number);
+
+    receivedPending("conference.oncallschanged", pending, conferenceRemove);
+  };
+
+  conference.onconnected = function(event) {
+    log("Received 'connected' event for the conference call.");
+    conference.oncallschanged = null;
+
+    ok(!conference.oncallschanged);
+
+    checkState(conference, [], 'connected',
+               [outgoingCall, incomingCall, incomingCall2]);
+
+    sendCmdToEmulator("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "outbound to  " + outNumber + " : active");
+      is(result[1], "inbound from " + inNumber + " : active");
+      is(result[2], "inbound from " + inNumber2 + " : active");
+      is(result[3], "OK");
+
+      receivedPending("conference.onconnected", pending, conferenceRemove);
+    });
+  };
+
+  callToAdd.ongroupchange = function(event) {
+    log("Received 'groupchange' event for the call to add.");
+    callToAdd.ongroupchange = null;
+
+    is(callToAdd.group, conference);
+    receivedPending("callToAdd.ongroupchange", pending, conferenceRemove);
+  };
+
+  callToAdd.onconnected = function(event) {
+    log("Received 'connected' event for the call to add.");
+    callToAdd.onconnected = null;
+
+    ok(!callToAdd.ongroupchange);
+    is(callToAdd.state, 'connected');
+
+    receivedPending("callToAdd.onconnected", pending, conferenceRemove);
+  };
+  conference.add(callToAdd);
+}
+
+// Remove a call from the conference. The state of that call remains 'connected'
+// while the state of the conference becomes 'held.'
+function conferenceRemove() {
+  log("Removing a participant from the conference call.");
+
+  is(conference.state, 'connected');
+
+  let callToRemove = conference.calls[0];
+  let pending = ["callToRemove.ongroupchange", "telephony.oncallschanged",
+                 "conference.oncallschanged", "conference.onstatechange"];
+
+  callToRemove.ongroupchange = function(event) {
+    log("Received 'groupchange' event for the call to remove.");
+    callToRemove.ongroupchange = null;
+
+    ok(!callToRemove.group);
+    is(callToRemove.state, 'connected');
+
+    receivedPending("callToRemove.ongroupchange", pending, cleanUp);
+  };
+
+  telephony.oncallschanged = function(event) {
+    log("Received 'callschanged' event for telephony.");
+    if (event.call) {
+      // Bug 823958 triggers one more callschanged event without carrying a
+      // call object.
+      telephony.oncallschanged = null;
+
+      is(event.call.number, callToRemove.number);
+      is(telephony.calls.length, 1);
+      is(telephony.calls[0].number, event.call.number);
+
+      receivedPending("telephony.oncallschanged", pending, cleanUp);
+    }
+  };
+
+  conference.oncallschanged = function(event) {
+    log("Received 'callschanged' event for the conference.");
+    conference.oncallschanged = null;
+
+    is(event.call.number, callToRemove.number);
+    is(conference.calls.length, 2);
+
+    receivedPending("conference.oncallschanged", pending, cleanUp);
+  };
+
+  conference.onstatechange = function(event) {
+    log("Received 'statechange' event for the conference.");
+    conference.onstatechange = null;
+
+    ok(!conference.oncallschanged);
+
+    checkState(callToRemove, [callToRemove], 'held',
+               [incomingCall, incomingCall2]);
+
+    sendCmdToEmulator("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "outbound to  " + outNumber + " : active");
+      is(result[1], "inbound from " + inNumber + " : held");
+      is(result[2], "inbound from " + inNumber2 + " : held");
+      is(result[3], "OK");
+
+      receivedPending("conference.onstatechange", pending, cleanUp);
+    });
+  };
+
+  conference.remove(callToRemove);
+}
+
+function cleanUp() {
+  if (pendingEmulatorCmdCount) {
+    window.setTimeout(cleanUp, 100);
+    return;
+  }
+  SpecialPowers.removePermission("telephony", document);
+  finish();
+}
+
+// Start the test
+verifyInitialState();