dom/mobileconnection/tests/marionette/head.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* Any copyright is dedicated to the Public Domain.
     2  * http://creativecommons.org/publicdomain/zero/1.0/ */
     4 const {Cc: Cc, Ci: Ci, Cr: Cr, Cu: Cu} = SpecialPowers;
     6 const SETTINGS_KEY_DATA_ENABLED = "ril.data.enabled";
     7 const SETTINGS_KEY_DATA_ROAMING_ENABLED = "ril.data.roaming_enabled";
     8 const SETTINGS_KEY_DATA_APN_SETTINGS = "ril.data.apnSettings";
    10 let Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise;
    12 let _pendingEmulatorCmdCount = 0;
    14 /**
    15  * Send emulator command with safe guard.
    16  *
    17  * We should only call |finish()| after all emulator command transactions
    18  * end, so here comes with the pending counter.  Resolve when the emulator
    19  * gives positive response, and reject otherwise.
    20  *
    21  * Fulfill params:
    22  *   result -- an array of emulator response lines.
    23  * Reject params:
    24  *   result -- an array of emulator response lines.
    25  *
    26  * @param aCommand
    27  *        A string command to be passed to emulator through its telnet console.
    28  *
    29  * @return A deferred promise.
    30  */
    31 function runEmulatorCmdSafe(aCommand) {
    32   let deferred = Promise.defer();
    34   ++_pendingEmulatorCmdCount;
    35   runEmulatorCmd(aCommand, function(aResult) {
    36     --_pendingEmulatorCmdCount;
    38     ok(true, "Emulator response: " + JSON.stringify(aResult));
    39     if (Array.isArray(aResult) &&
    40         aResult[aResult.length - 1] === "OK") {
    41       deferred.resolve(aResult);
    42     } else {
    43       deferred.reject(aResult);
    44     }
    45   });
    47   return deferred.promise;
    48 }
    50 /**
    51  * Wrap DOMRequest onsuccess/onerror events to Promise resolve/reject.
    52  *
    53  * Fulfill params: A DOMEvent.
    54  * Reject params: A DOMEvent.
    55  *
    56  * @param aRequest
    57  *        A DOMRequest instance.
    58  *
    59  * @return A deferred promise.
    60  */
    61 function wrapDomRequestAsPromise(aRequest) {
    62   let deferred = Promise.defer();
    64   ok(aRequest instanceof DOMRequest,
    65      "aRequest is instanceof " + aRequest.constructor);
    67   aRequest.addEventListener("success", function(aEvent) {
    68     deferred.resolve(aEvent);
    69   });
    70   aRequest.addEventListener("error", function(aEvent) {
    71     deferred.reject(aEvent);
    72   });
    74   return deferred.promise;
    75 }
    77 let workingFrame;
    79 /**
    80  * Get mozSettings value specified by @aKey.
    81  *
    82  * Resolve if that mozSettings value is retrieved successfully, reject
    83  * otherwise.
    84  *
    85  * Fulfill params:
    86  *   The corresponding mozSettings value of the key.
    87  * Reject params: (none)
    88  *
    89  * @param aKey
    90  *        A string.
    91  * @param aAllowError [optional]
    92  *        A boolean value.  If set to true, an error response won't be treated
    93  *        as test failure.  Default: false.
    94  *
    95  * @return A deferred promise.
    96  */
    97 function getSettings(aKey, aAllowError) {
    98   let request =
    99     workingFrame.contentWindow.navigator.mozSettings.createLock().get(aKey);
   100   return wrapDomRequestAsPromise(request)
   101     .then(function resolve(aEvent) {
   102       ok(true, "getSettings(" + aKey + ") - success");
   103       return aEvent.target.result[aKey];
   104     }, function reject(aEvent) {
   105       ok(aAllowError, "getSettings(" + aKey + ") - error");
   106     });
   107 }
   109 /**
   110  * Set mozSettings values.
   111  *
   112  * Resolve if that mozSettings value is set successfully, reject otherwise.
   113  *
   114  * Fulfill params: (none)
   115  * Reject params: (none)
   116  *
   117  * @param aSettings
   118  *        An object of format |{key1: value1, key2: value2, ...}|.
   119  * @param aAllowError [optional]
   120  *        A boolean value.  If set to true, an error response won't be treated
   121  *        as test failure.  Default: false.
   122  *
   123  * @return A deferred promise.
   124  */
   125 function setSettings(aSettings, aAllowError) {
   126   let request =
   127     workingFrame.contentWindow.navigator.mozSettings.createLock().set(aSettings);
   128   return wrapDomRequestAsPromise(request)
   129     .then(function resolve() {
   130       ok(true, "setSettings(" + JSON.stringify(aSettings) + ")");
   131     }, function reject() {
   132       ok(aAllowError, "setSettings(" + JSON.stringify(aSettings) + ")");
   133     });
   134 }
   136 /**
   137  * Set mozSettings value with only one key.
   138  *
   139  * Resolve if that mozSettings value is set successfully, reject otherwise.
   140  *
   141  * Fulfill params: (none)
   142  * Reject params: (none)
   143  *
   144  * @param aKey
   145  *        A string key.
   146  * @param aValue
   147  *        An object value.
   148  * @param aAllowError [optional]
   149  *        A boolean value.  If set to true, an error response won't be treated
   150  *        as test failure.  Default: false.
   151  *
   152  * @return A deferred promise.
   153  */
   154 function setSettings1(aKey, aValue, aAllowError) {
   155   let settings = {};
   156   settings[aKey] = aValue;
   157   return setSettings(settings, aAllowError);
   158 }
   160 /**
   161  * Convenient MozSettings getter for SETTINGS_KEY_DATA_ENABLED.
   162  */
   163 function getDataEnabled(aAllowError) {
   164   return getSettings(SETTINGS_KEY_DATA_ENABLED, aAllowError);
   165 }
   167 /**
   168  * Convenient MozSettings setter for SETTINGS_KEY_DATA_ENABLED.
   169  */
   170 function setDataEnabled(aEnabled, aAllowError) {
   171   return setSettings1(SETTINGS_KEY_DATA_ENABLED, aEnabled, aAllowError);
   172 }
   174 /**
   175  * Convenient MozSettings getter for SETTINGS_KEY_DATA_ROAMING_ENABLED.
   176  */
   177 function getDataRoamingEnabled(aAllowError) {
   178   return getSettings(SETTINGS_KEY_DATA_ROAMING_ENABLED, aAllowError);
   179 }
   181 /**
   182  * Convenient MozSettings setter for SETTINGS_KEY_DATA_ROAMING_ENABLED.
   183  */
   184 function setDataRoamingEnabled(aEnabled, aAllowError) {
   185   return setSettings1(SETTINGS_KEY_DATA_ROAMING_ENABLED, aEnabled, aAllowError);
   186 }
   188 /**
   189  * Convenient MozSettings getter for SETTINGS_KEY_DATA_APN_SETTINGS.
   190  */
   191 function getDataApnSettings(aAllowError) {
   192   return getSettings(SETTINGS_KEY_DATA_APN_SETTINGS, aAllowError);
   193 }
   195 /**
   196  * Convenient MozSettings setter for SETTINGS_KEY_DATA_APN_SETTINGS.
   197  */
   198 function setDataApnSettings(aApnSettings, aAllowError) {
   199   return setSettings1(SETTINGS_KEY_DATA_APN_SETTINGS, aApnSettings, aAllowError);
   200 }
   202 let mobileConnection;
   204 /**
   205  * Push required permissions and test if
   206  * |navigator.mozMobileConnections[<aServiceId>]| exists. Resolve if it does,
   207  * reject otherwise.
   208  *
   209  * Fulfill params:
   210  *   mobileConnection -- an reference to navigator.mozMobileMessage.
   211  *
   212  * Reject params: (none)
   213  *
   214  * @param aAdditonalPermissions [optional]
   215  *        An array of permission strings other than "mobileconnection" to be
   216  *        pushed. Default: empty string.
   217  * @param aServiceId [optional]
   218  *        A numeric DSDS service id. Default: 0.
   219  *
   220  * @return A deferred promise.
   221  */
   222 function ensureMobileConnection(aAdditionalPermissions, aServiceId) {
   223   let deferred = Promise.defer();
   225   aAdditionalPermissions = aAdditionalPermissions || [];
   226   aServiceId = aServiceId || 0;
   228   if (aAdditionalPermissions.indexOf("mobileconnection") < 0) {
   229     aAdditionalPermissions.push("mobileconnection");
   230   }
   231   let permissions = [];
   232   for (let perm of aAdditionalPermissions) {
   233     permissions.push({ "type": perm, "allow": 1, "context": document });
   234   }
   236   SpecialPowers.pushPermissions(permissions, function() {
   237     ok(true, "permissions pushed: " + JSON.stringify(permissions));
   239     // Permission changes can't change existing Navigator.prototype
   240     // objects, so grab our objects from a new Navigator.
   241     workingFrame = document.createElement("iframe");
   242     workingFrame.addEventListener("load", function load() {
   243       workingFrame.removeEventListener("load", load);
   245       mobileConnection =
   246         workingFrame.contentWindow.navigator.mozMobileConnections[aServiceId];
   248       if (mobileConnection) {
   249         log("navigator.mozMobileConnections[" + aServiceId + "] is instance of " +
   250             mobileConnection.constructor);
   251       } else {
   252         log("navigator.mozMobileConnections[" + aServiceId + "] is undefined");
   253       }
   255       if (mobileConnection instanceof MozMobileConnection) {
   256         deferred.resolve(mobileConnection);
   257       } else {
   258         deferred.reject();
   259       }
   260     });
   262     document.body.appendChild(workingFrame);
   263   });
   265   return deferred.promise;
   266 }
   268 /**
   269  * Wait for one named MobileConnection event.
   270  *
   271  * Resolve if that named event occurs.  Never reject.
   272  *
   273  * Fulfill params: the DOMEvent passed.
   274  *
   275  * @param aEventName
   276  *        A string event name.
   277  * @param aServiceId [optional]
   278  *        A numeric DSDS service id. Default: the one indicated in
   279  *        start*TestCommon() or 0 if not indicated.
   280  *
   281  * @return A deferred promise.
   282  */
   283 function waitForManagerEvent(aEventName, aServiceId) {
   284   let deferred = Promise.defer();
   286   let mobileConn = mobileConnection;
   287   if (aServiceId !== undefined) {
   288     mobileConn =
   289       workingFrame.contentWindow.navigator.mozMobileConnections[aServiceId];
   290   }
   292   mobileConn.addEventListener(aEventName, function onevent(aEvent) {
   293     mobileConn.removeEventListener(aEventName, onevent);
   295     ok(true, "MobileConnection event '" + aEventName + "' got.");
   296     deferred.resolve(aEvent);
   297   });
   299   return deferred.promise;
   300 }
   302 /**
   303  * Get available networks.
   304  *
   305  * Fulfill params:
   306  *   An array of nsIDOMMozMobileNetworkInfo.
   307  * Reject params:
   308  *   A DOMEvent.
   309  *
   310  * @return A deferred promise.
   311  */
   312 function getNetworks() {
   313   let request = mobileConnection.getNetworks();
   314   return wrapDomRequestAsPromise(request)
   315     .then(() => request.result);
   316 }
   318 /**
   319  * Manually select a network.
   320  *
   321  * Fulfill params: (none)
   322  * Reject params:
   323  *   'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure'
   324  *
   325  * @param aNetwork
   326  *        A nsIDOMMozMobileNetworkInfo.
   327  *
   328  * @return A deferred promise.
   329  */
   330 function selectNetwork(aNetwork) {
   331   let request = mobileConnection.selectNetwork(aNetwork);
   332   return wrapDomRequestAsPromise(request)
   333     .then(null, () => { throw request.error });
   334 }
   336 /**
   337  * Manually select a network and wait for a 'voicechange' event.
   338  *
   339  * Fulfill params: (none)
   340  * Reject params:
   341  *   'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure'
   342  *
   343  * @param aNetwork
   344  *        A nsIDOMMozMobileNetworkInfo.
   345  *
   346  * @return A deferred promise.
   347  */
   348 function selectNetworkAndWait(aNetwork) {
   349   let promises = [];
   351   promises.push(waitForManagerEvent("voicechange"));
   352   promises.push(selectNetwork(aNetwork));
   354   return Promise.all(promises);
   355 }
   357 /**
   358  * Automatically select a network.
   359  *
   360  * Fulfill params: (none)
   361  * Reject params:
   362  *   'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure'
   363  *
   364  * @return A deferred promise.
   365  */
   366 function selectNetworkAutomatically() {
   367   let request = mobileConnection.selectNetworkAutomatically();
   368   return wrapDomRequestAsPromise(request)
   369     .then(null, () => { throw request.error });
   370 }
   372 /**
   373  * Automatically select a network and wait for a 'voicechange' event.
   374  *
   375  * Fulfill params: (none)
   376  * Reject params:
   377  *   'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure'
   378  *
   379  * @return A deferred promise.
   380  */
   381 function selectNetworkAutomaticallyAndWait() {
   382   let promises = [];
   384   promises.push(waitForManagerEvent("voicechange"));
   385   promises.push(selectNetworkAutomatically());
   387   return Promise.all(promises);
   388 }
   390 /**
   391  * Set data connection enabling state and wait for "datachange" event.
   392  *
   393  * Resolve if data connection state changed to the expected one.  Never reject.
   394  *
   395  * Fulfill params: (none)
   396  *
   397  * @param aEnabled
   398  *        A boolean state.
   399  * @param aServiceId [optional]
   400  *        A numeric DSDS service id. Default: the one indicated in
   401  *        start*TestCommon() or 0 if not indicated.
   402  *
   403  * @return A deferred promise.
   404  */
   405 function setDataEnabledAndWait(aEnabled, aServiceId) {
   406   let deferred = Promise.defer();
   408   let promises = [];
   409   promises.push(waitForManagerEvent("datachange", aServiceId));
   410   promises.push(setDataEnabled(aEnabled));
   411   Promise.all(promises).then(function keepWaiting() {
   412     let mobileConn = mobileConnection;
   413     if (aServiceId !== undefined) {
   414       mobileConn =
   415         workingFrame.contentWindow.navigator.mozMobileConnections[aServiceId];
   416     }
   417     // To ignore some transient states, we only resolve that deferred promise
   418     // when the |connected| state equals to the expected one and never rejects.
   419     let connected = mobileConn.data.connected;
   420     if (connected == aEnabled) {
   421       deferred.resolve();
   422       return;
   423     }
   425     return waitForManagerEvent("datachange", aServiceId).then(keepWaiting);
   426   });
   428   return deferred.promise;
   429 }
   431 /**
   432  * Set voice/data state and wait for state change.
   433  *
   434  * Fulfill params: (none)
   435  *
   436  * @param aWhich
   437  *        "voice" or "data".
   438  * @param aState
   439  *        "unregistered", "searching", "denied", "roaming", or "home".
   440  * @param aServiceId [optional]
   441  *        A numeric DSDS service id. Default: the one indicated in
   442  *        start*TestCommon() or 0 if not indicated.
   443  *
   444  * @return A deferred promise.
   445  */
   446 function setEmulatorVoiceDataStateAndWait(aWhich, aState, aServiceId) {
   447   let promises = [];
   448   promises.push(waitForManagerEvent(aWhich + "change", aServiceId));
   450   let cmd = "gsm " + aWhich + " " + aState;
   451   promises.push(runEmulatorCmdSafe(cmd));
   452   return Promise.all(promises);
   453 }
   455 /**
   456  * Set voice and data roaming emulation and wait for state change.
   457  *
   458  * Fulfill params: (none)
   459  *
   460  * @param aRoaming
   461  *        A boolean state.
   462  * @param aServiceId [optional]
   463  *        A numeric DSDS service id. Default: the one indicated in
   464  *        start*TestCommon() or 0 if not indicated.
   465  *
   466  * @return A deferred promise.
   467  */
   468 function setEmulatorRoamingAndWait(aRoaming, aServiceId) {
   469   function doSetAndWait(aWhich, aRoaming, aServiceId) {
   470     let state = (aRoaming ? "roaming" : "home");
   471     return setEmulatorVoiceDataStateAndWait(aWhich, state, aServiceId)
   472       .then(() => {
   473         let mobileConn = mobileConnection;
   474         if (aServiceId !== undefined) {
   475           mobileConn =
   476             workingFrame.contentWindow.navigator.mozMobileConnections[aServiceId];
   477         }
   478         is(mobileConn[aWhich].roaming, aRoaming,
   479                      aWhich + ".roaming")
   480       });
   481   }
   483   // Set voice registration state first and then data registration state.
   484   return doSetAndWait("voice", aRoaming, aServiceId)
   485     .then(() => doSetAndWait("data", aRoaming, aServiceId));
   486 }
   488 /**
   489  * Get GSM location emulation.
   490  *
   491  * Fulfill params:
   492  *   { lac: <lac>, cid: <cid> }
   493  * Reject params:
   494  *   result -- an array of emulator response lines.
   495  *
   496  * @return A deferred promise.
   497  */
   498 function getEmulatorGsmLocation() {
   499   let cmd = "gsm location";
   500   return runEmulatorCmdSafe(cmd)
   501     .then(function(aResults) {
   502       // lac: <lac>
   503       // ci: <cid>
   504       // OK
   505       is(aResults[0].substring(0,3), "lac", "lac output");
   506       is(aResults[1].substring(0,2), "ci", "ci output");
   508       let lac = parseInt(aResults[0].substring(5));
   509       lac = (lac < 0 ? 65535 : lac);
   510       let cid = parseInt(aResults[1].substring(4));
   511       cid = (cid < 0 ? 268435455 : cid);
   513       return { lac: lac, cid: cid };
   514     });
   515 }
   517 /**
   518  * Set GSM location emulation.
   519  *
   520  * Fulfill params: (none)
   521  * Reject params: (none)
   522  *
   523  * @param aLac
   524  * @param aCid
   525  *
   526  * @return A deferred promise.
   527  */
   528 function setEmulatorGsmLocation(aLac, aCid) {
   529   let cmd = "gsm location " + aLac + " " + aCid;
   530   return runEmulatorCmdSafe(cmd);
   531 }
   533 /**
   534  * Get emulator operators info.
   535  *
   536  * Fulfill params:
   537  *   An array of { longName: <string>, shortName: <string>, mccMnc: <string> }.
   538  * Reject params:
   539  *   result -- an array of emulator response lines.
   540  *
   541  * @return A deferred promise.
   542  */
   543 function getEmulatorOperatorNames() {
   544   let cmd = "operator dumpall";
   545   return runEmulatorCmdSafe(cmd)
   546     .then(function(aResults) {
   547       let operators = [];
   549       for (let i = 0; i < aResults.length - 1; i++) {
   550         let names = aResults[i].split(',');
   551         operators.push({
   552           longName: names[0],
   553           shortName: names[1],
   554           mccMnc: names[2],
   555         });
   556       }
   558       ok(true, "emulator operators list: " + JSON.stringify(operators));
   559       return operators;
   560     });
   561 }
   563 /**
   564  * Set emulator operators info.
   565  *
   566  * Fulfill params: (none)
   567  * Reject params:
   568  *   result -- an array of emulator response lines.
   569  *
   570  * @param aOperator
   571  *        "home" or "roaming".
   572  * @param aLongName
   573  *        A string.
   574  * @param aShortName
   575  *        A string.
   576  * @param aMcc [optional]
   577  *        A string.
   578  * @param aMnc [optional]
   579  *        A string.
   580  *
   581  * @return A deferred promise.
   582  */
   583 function setEmulatorOperatorNames(aOperator, aLongName, aShortName, aMcc, aMnc) {
   584   const EMULATOR_OPERATORS = [ "home", "roaming" ];
   586   let index = EMULATOR_OPERATORS.indexOf(aOperator);
   587   if (index < 0) {
   588     throw "invalid operator";
   589   }
   591   let cmd = "operator set " + index + " " + aLongName + "," + aShortName;
   592   if (aMcc && aMnc) {
   593     cmd = cmd + "," + aMcc + aMnc;
   594   }
   595   return runEmulatorCmdSafe(cmd)
   596     .then(function(aResults) {
   597       let exp = "^" + aLongName + "," + aShortName + ",";
   598       if (aMcc && aMnc) {
   599         cmd = cmd + aMcc + aMnc;
   600       }
   602       let re = new RegExp(exp);
   603       ok(aResults[index].match(new RegExp(exp)),
   604          "Long/short name and/or mcc/mnc should be changed.");
   605     });
   606 }
   608 let _networkManager;
   610 /**
   611  * Get internal NetworkManager service.
   612  */
   613 function getNetworkManager() {
   614   if (!_networkManager) {
   615     _networkManager = Cc["@mozilla.org/network/manager;1"]
   616                     .getService(Ci.nsINetworkManager);
   617     ok(_networkManager, "NetworkManager");
   618   }
   620   return _networkManager;
   621 }
   623 let _numOfRadioInterfaces;
   625 /*
   626  * Get number of radio interfaces. Default is 1 if preference is not set.
   627  */
   628 function getNumOfRadioInterfaces() {
   629   if (!_numOfRadioInterfaces) {
   630     try {
   631       _numOfRadioInterfaces = SpecialPowers.getIntPref("ril.numRadioInterfaces");
   632     } catch (ex) {
   633       _numOfRadioInterfaces = 1;  // Pref not set.
   634     }
   635   }
   637   return _numOfRadioInterfaces;
   638 }
   640 /**
   641  * Flush permission settings and call |finish()|.
   642  */
   643 function cleanUp() {
   644   waitFor(function() {
   645     SpecialPowers.flushPermissions(function() {
   646       // Use ok here so that we have at least one test run.
   647       ok(true, "permissions flushed");
   649       finish();
   650     });
   651   }, function() {
   652     return _pendingEmulatorCmdCount === 0;
   653   });
   654 }
   656 /**
   657  * Basic test routine helper for mobile connection tests.
   658  *
   659  * This helper does nothing but clean-ups.
   660  *
   661  * @param aTestCaseMain
   662  *        A function that takes no parameter.
   663  */
   664 function startTestBase(aTestCaseMain) {
   665   Promise.resolve()
   666     .then(aTestCaseMain)
   667     .then(cleanUp, function() {
   668       ok(false, 'promise rejects during test.');
   669       cleanUp();
   670     });
   671 }
   673 /**
   674  * Common test routine helper for mobile connection tests.
   675  *
   676  * This function ensures global |mobileConnection| variable is available during
   677  * the process and performs clean-ups as well.
   678  *
   679  * @param aTestCaseMain
   680  *        A function that takes one parameter -- mobileConnection.
   681  * @param aAdditonalPermissions [optional]
   682  *        An array of permission strings other than "mobileconnection" to be
   683  *        pushed. Default: empty string.
   684  * @param aServiceId [optional]
   685  *        A numeric DSDS service id. Default: 0.
   686  */
   687 function startTestCommon(aTestCaseMain, aAdditionalPermissions, aServiceId) {
   688   startTestBase(function() {
   689     return ensureMobileConnection(aAdditionalPermissions, aServiceId)
   690       .then(aTestCaseMain);
   691   });
   692 }
   694 /**
   695  * Common test routine helper for multi-sim mobile connection tests. The test
   696  * ends immediately if the device tested is not multi-sim.
   697  *
   698  * This function ensures global |mobileConnection| variable is available during
   699  * the process and performs clean-ups as well.
   700  *
   701  * @param aTestCaseMain
   702  *        A function that takes one parameter -- mobileConnection.
   703  * @param aAdditonalPermissions [optional]
   704  *        An array of permission strings other than "mobileconnection" to be
   705  *        pushed. Default: empty string.
   706  * @param aServiceId [optional]
   707  *        A numeric DSDS service id. Default: 0.
   708  */
   709 function startDSDSTestCommon(aTestCaseMain, aAdditionalPermissions, aServiceId) {
   710     if (getNumOfRadioInterfaces() > 1) {
   711       startTestBase(function() {
   712         return ensureMobileConnection(aAdditionalPermissions, aServiceId)
   713           .then(aTestCaseMain);
   714       });
   715     } else {
   716       log("Skipping DSDS tests on single SIM device.")
   717       ok(true);  // We should run at least one test.
   718       cleanUp();
   719     }
   720 }

mercurial