Bug 951976 - part 2, Test cases. r=gene
authorBorting Chen <btchen@mozilla.com>
Mon, 12 May 2014 18:55:49 +0800
changeset 191888 03d0a5fd13cf1e72d5755a6af6997bc5833497cb
parent 191887 1a22dd8f9d9173ee229aa51800bd0f2d3bf0f92d
child 191889 bd0703d4cd88067e4cf300932b4e25a013ab6667
push id45685
push usercbook@mozilla.com
push dateWed, 02 Jul 2014 13:09:48 +0000
treeherdermozilla-inbound@60133a85f8ae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgene
bugs951976
milestone33.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 951976 - part 2, Test cases. r=gene
dom/resourcestats/moz.build
dom/resourcestats/tests/mochitest/mochitest.ini
dom/resourcestats/tests/mochitest/test_basic.html
dom/resourcestats/tests/mochitest/test_disabled_pref.html
dom/resourcestats/tests/mochitest/test_network_alarm.html
dom/resourcestats/tests/mochitest/test_network_stats.html
dom/resourcestats/tests/mochitest/test_no_perm.html
dom/resourcestats/tests/mochitest/test_not_supported_type.html
dom/resourcestats/tests/mochitest/test_power_alarm.html
dom/resourcestats/tests/mochitest/test_power_stats.html
dom/resourcestats/tests/moz.build
dom/resourcestats/tests/xpcshell/test_resourcestats_db.js
dom/resourcestats/tests/xpcshell/xpcshell.ini
testing/xpcshell/xpcshell_b2g.ini
--- a/dom/resourcestats/moz.build
+++ b/dom/resourcestats/moz.build
@@ -9,10 +9,11 @@ EXTRA_COMPONENTS += [
     'ResourceStatsManager.manifest',
 ]
 
 EXTRA_JS_MODULES += [
     'ResourceStatsDB.jsm',
     'ResourceStatsService.jsm',
 ]
 
+TEST_DIRS += ['tests']
+
 FINAL_LIBRARY = 'gklayout'
-
new file mode 100644
--- /dev/null
+++ b/dom/resourcestats/tests/mochitest/mochitest.ini
@@ -0,0 +1,16 @@
+[test_basic.html]
+skip-if = toolkit != "gonk"
+[test_network_stats.html]
+skip-if = toolkit != "gonk"
+[test_power_stats.html]
+skip-if = toolkit != "gonk"
+[test_network_alarm.html]
+skip-if = toolkit != "gonk"
+[test_power_alarm.html]
+skip-if = toolkit != "gonk"
+[test_disabled_pref.html]
+skip-if = toolkit != "gonk"
+[test_no_perm.html]
+skip-if = toolkit != "gonk"
+[test_not_supported_type.html]
+skip-if = toolkit != "gonk"
new file mode 100644
--- /dev/null
+++ b/dom/resourcestats/tests/mochitest/test_basic.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test accessibility of interfaces</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// Test accessibility of interfaces.
+SpecialPowers.addPermission("resourcestats-manage", true, document);
+SpecialPowers.pushPrefEnv({ 'set': [
+                            ["dom.resource_stats.enabled", true],
+                            ["dom.ignore_webidl_scope_checks", true]
+                          ]}, function() {
+  ok(SpecialPowers.hasPermission("resourcestats-manage", document),
+     "Has permission 'resourcestats-manage'.");
+  ok(SpecialPowers.getBoolPref("dom.resource_stats.enabled"),
+     "Preference 'dom.resource_stats.enabled' is true.");
+
+  // Check all interfaces are accessible.
+  ok('ResourceStatsManager' in window, "ResourceStatsManager exist.");
+  ok('ResourceStatsAlarm' in window, "ResourceStatsAlarm exist.");
+  ok('ResourceStats' in window, "ResourceStats exist.");
+  ok('NetworkStatsData' in window, "NetworkStatsData exist.");
+  ok('PowerStatsData' in window, "PowerStatsData exist.");
+
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/resourcestats/tests/mochitest/test_disabled_pref.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test to ensure interface is not accessible when preference is disabled</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// Test to ensure interface is not accessible when preference is disabled.
+SpecialPowers.addPermission("resourcestats-manage", true, document);
+SpecialPowers.pushPrefEnv({ 'set': [
+                            ["dom.resource_stats.enabled", false],
+                            ["dom.ignore_webidl_scope_checks", true]
+                          ]}, function() {
+  ok(SpecialPowers.hasPermission("resourcestats-manage", document),
+     "Has permission 'resourcestats-manage'.");
+  ok(!(SpecialPowers.getBoolPref("dom.resource_stats.enabled")),
+     "Preference 'dom.resource_stats.enabled' is false.");
+
+  // Check accessibility.
+  is('ResourceStatsManager' in window, false, "ResourceStatsManager should not exist.");
+  is('ResourceStatsAlarm' in window, false, "ResourceStatsAlarm should not exist.");
+  is('ResourceStats' in window, false, "ResourceStats should not exist.");
+  is('NetworkStatsData' in window, false, "NetworkStatsData should not exist.");
+  is('PowerStatsData' in window, false, "PowerStatsData should not exist.");
+
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/resourcestats/tests/mochitest/test_network_alarm.html
@@ -0,0 +1,356 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for ResourceStats methods realted to network resource control</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+const invalidManifestURL = "app://invalid.gaiamobile.org/manifest.webapp";
+const wifiComponent = "wifi:0";
+const mobileComponent = "mobile:1";
+var networkStatsMgr = null; // ResourceStatsManager for network statistics.
+
+function errorCb(reason) {
+  ok(false, reason);
+}
+
+// Check the content returned by getAlarms.
+function checkAlarmsArray(alarms) {
+  // Check if data is an array.
+  if (!Array.isArray(alarms)) {
+    throw "getAlarms does not return an array.";
+  } else {
+    ok(true, "getAlarms returns an array.")
+  }
+
+  // Iterate the array and check the type of each element.
+  var obj = null;
+  var message = null; // Message for exception
+
+  for (var i = 0; i < alarms.length; i++) {
+    obj = alarms[i];
+
+    // Check if obj is an instance os ResourceStatsAlarm.
+    if (!(obj instanceof ResourceStatsAlarm)) {
+      message = "The array contains a non-ResourceStatsAlarm object.";
+      break;
+    }
+
+    // Check if obj.type is network.
+    if (obj.type != "network") {
+      message = "The type of a ResourceStatsAlarm object is not network.";
+      break;
+    }
+  }
+
+  if (message) {
+    throw message;
+  }
+
+  ok(true, "The return is an array of ResourceStatsAlarm objects.");
+}
+
+// Test Cases for testing WebIDL methods related to resource control.
+var testCases = [
+  function() {
+    // Test removeAllAlarms.
+    var promise = networkStatsMgr.removeAllAlarms();
+    promise.then(function() {
+      ok(true, "removeAllAlarms deleted all network alarms.");
+      testMethods();
+    }, function() {
+      ok(false, "removeAllAlarms failed to delete network alarms.");
+    });
+  },
+
+  function() {
+    // Test addAlarm.
+    var threshold = Math.floor(Math.random() * 1000);
+    var promise = networkStatsMgr.addAlarm(threshold,
+                                           { 'component': wifiComponent },
+                                           { 'startTime': Date.now() });
+    promise.then(function(value) {
+      // Check the value (alarmId).
+      if (value < 0) {
+        ok(false, "addAlarm failed to create an alarm.");
+      } else {
+        ok(true, "addAlarm created an alarm.");
+        testMethods();
+      }
+    }, function() {
+      ok(false, "addAlarm failed to create an alarm.");
+    });
+  },
+
+  function() {
+    // Test addAlarm with negative threshold.
+    var threshold = -1;
+    var promise = networkStatsMgr.addAlarm(threshold,
+                                           { 'component': wifiComponent },
+                                           { 'startTime': Date.now() });
+    promise.then(function() {
+      // Check the value.
+      ok(false,
+         "addAlarm did not throw an exception when negative threshold is set.");
+    }, function() {
+      ok(true, "addAlarm threw an exception when negative threshold is set.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test addAlarm with no threshold.
+    var promise = networkStatsMgr.addAlarm();
+    promise.then(function() {
+      // Check the value.
+      ok(false, "addAlarm did not throw an exception when no threshold.");
+    }, function() {
+      ok(true, "addAlarm threw an exception when no threshold.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test addAlarm with negative startTime.
+    var threshold = Math.floor(Math.random() * 1000);
+    var promise = networkStatsMgr.addAlarm(threshold,
+                                           { 'component': wifiComponent },
+                                           { 'startTime': -1 });
+    promise.then(function() {
+      // Check the value.
+      ok(false,
+         "addAlarm did not throw an exception when negative startTime is set.");
+    }, function() {
+      ok(true, "addAlarm threw an exception when negative startTime is set.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test addAlarm when manifestURL is invalid.
+    var threshold = Math.floor(Math.random() * 1000);
+    var promise = networkStatsMgr.addAlarm(threshold,
+                                           { 'component': wifiComponent,
+                                             'manifestURL': invalidManifestURL },
+                                           { 'startTime': Date.now() });
+    promise.then(function() {
+      // Check the value.
+      ok(false, "addAlarm did not throw an exception when manifestURL is invalid.");
+    }, function() {
+      ok(true, "addAlarm threw an exception when manifestURL is invalid.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test getAlarms.
+    var alarmId;
+    var alarms;
+    var threshold = Math.floor(Math.random() * 1000);
+
+    // Execution steps:
+    // 1. Add a new alarm.
+    var promise = networkStatsMgr.addAlarm(threshold,
+                                           { 'component': wifiComponent },
+                                           { 'startTime': Date.now() });
+
+    // 2. Test getAlarms if new alarm is added.
+    var runGetAlarms = function(value) {
+      alarmId = value;
+      return networkStatsMgr.getAlarms({ 'component': wifiComponent });
+    };
+
+    // 3. Check the content returned by getAlarms.
+    var checkGetAlarmsReturn = function(value) {
+      alarms = value;
+      checkAlarmsArray(value);
+    };
+
+    // 4. Check the alarm added in step 1 is inside the return of getAlarms.
+    var checkAlarm = function (value) {
+      // Find the alarm.
+      var index = alarms.map(function(e) { return e.alarmId; })
+                    .indexOf(alarmId);
+      if (index < 0) {
+        throw "getAlarms does not get the alarm added in previous step.";
+      }
+      var alarm = alarms[index];
+
+      // Evaluate the alarm.
+      ok(alarm.threshold == threshold, "threshold is equal.");
+      ok(alarm.component == wifiComponent, "component is equal.");
+      ok(alarm.serviceType == null, "serviceType should be null.");
+      ok(alarm.manifestURL == null, "manifestURL should be null.");
+    };
+
+    // Create promise chaining.
+    promise.then(runGetAlarms)
+      .then(checkGetAlarmsReturn)
+      .then(checkAlarm)
+      .then(testMethods, errorCb); // Execute next test case.
+  },
+
+  function() {
+    // Test getAlarms with invalid manifestURL.
+    var threshold = Math.floor(Math.random() * 1000);
+    var promise = networkStatsMgr.addAlarm(threshold,
+                                           { 'component': wifiComponent,
+                                             'manifestURL': invalidManifestURL },
+                                           { 'startTime': Date.now() });
+
+    promise.then(function() {
+      // Check the value.
+      ok(false, "getAlarms did not throw an exception when manifestURL is invalid.");
+    }, function() {
+      ok(true, "getAlarms threw an exception when manifestURL is invalid.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test getAlarms with incorrect parameter.
+    var threshold = Math.floor(Math.random() * 1000);
+
+    // Execution steps:
+    // 1. Add a new alarm.
+    var promise = networkStatsMgr.addAlarm(threshold,
+                                           { 'component': wifiComponent },
+                                           { 'startTime': Date.now() });
+
+    // 2. Call getAlarms with incorrect parameter.
+    var runGetAlarms = function() {
+      return networkStatsMgr.getAlarms({ 'component': mobileComponent });
+    };
+
+    // 3. check the content returned by getAlarms.
+    var checkGetAlarmsReturn = function(value) {
+      // Check array length
+      if (value.length) {
+        throw "getAlarms gets an alarm when using incorrect parameter.";
+      } else {
+        ok(true,
+           "getAlarms returns an empty array when using incorrect parameter.");
+      }
+    };
+
+    // Create pomise chaining.
+    promise.then(runGetAlarms)
+      .then(checkGetAlarmsReturn)
+      .then(testMethods, errorCb); // Execute next test case.
+  },
+
+  function() {
+    // Test removeAlarm
+    var alarmId;
+    var threshold = Math.floor(Math.random() * 1000);
+
+    // Execution steps:
+    // 1. Add a new alarm.
+    var promise = networkStatsMgr.addAlarm(threshold,
+                                           { 'component': wifiComponent },
+                                           { 'startTime': Date.now() });
+
+    // 2. Try to remove the new alarm.
+    var runRemoveAlarm = function(value) {
+      alarmId = value;
+      return networkStatsMgr.removeAlarm(alarmId);
+    }
+
+    // Create promise chaining.
+    promise.then(runRemoveAlarm)
+      .then(function() {
+        ok(true, "removeAlarm deleted the alarm.");
+        testMethods();
+      }, errorCb);
+  },
+
+  function() {
+    // Test removeAlarm with negative alarmId
+    var alarmId = -1;
+    var promise = networkStatsMgr.removeAlarm(alarmId);
+    promise.then(function() {
+      ok(false,
+         "removeAlarm did not throw an exception when negative alarmId is set.");
+    }, function() {
+      ok(true, "removeAlarm threw an exception when negative alarmId is set.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test removeAlarm with invalid alarmId
+    var alarmId;
+    var threshold = Math.floor(Math.random() * 1000);
+
+    // Execution steps:
+    // 1. Add a new alarm.
+    var promise = networkStatsMgr.addAlarm(threshold,
+                                           { 'component': wifiComponent },
+                                           { 'startTime': Date.now() });
+
+    // 2. Try to remove an invalid alarm.
+    var runRemoveAlarm = function(value) {
+      alarmId = value;
+      // Because alarmId is auto-increment, any alarmId larger than the
+      // latest alarmId should be invalid.
+      return networkStatsMgr.removeAlarm(alarmId + 10);
+    }
+
+    // Create promise chaining.
+    promise.then(runRemoveAlarm)
+      .then(function() {
+        // Input with incorrect alarmId should not be resolved.
+        throw "removeAlarm should fail with invalid alarmId.";
+      }, function(reason) {
+        if (reason == "alarm not existed") {
+          ok(true, "removeAlarm with invalid alarmId should fail.")
+        } else {
+          throw reason;
+        }
+      })
+      .then(testMethods, errorCb);
+  }
+];
+
+// Test WebIDL methods related stats operation.
+function testMethods() {
+  if (!testCases.length) {
+    ok(true, "Done.");
+    SpecialPowers.removePermission("resourcestats-manage", document);
+    SimpleTest.finish();
+    return;
+  }
+
+  var testCase = testCases.shift();
+  testCase();
+}
+
+function startTest() {
+  // Create an instance of ResourceStatsManager for network.
+  networkStatsMgr = new ResourceStatsManager("network");
+  ok(networkStatsMgr, "Create networkStatsMgr.");
+
+  // Test WebIDL methods related to resource control.
+  testMethods();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// Enable permission and preference.
+SpecialPowers.addPermission("resourcestats-manage", true, document);
+SpecialPowers.pushPrefEnv({ 'set': [
+                            ["dom.resource_stats.enabled", true],
+                            ["dom.ignore_webidl_scope_checks", true]
+                          ]}, startTest);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/resourcestats/tests/mochitest/test_network_stats.html
@@ -0,0 +1,345 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for ResourceStats methods realted to network statistics</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+const invalidManifestURL = "app://invalid.gaiamobile.org/manifest.webapp";
+var networkStatsMgr = null; // ResourceStatsManager for network statistics.
+var sampleRate = 0;
+
+// Test WebIDL attributes.
+function testAttributes() {
+  // Test sampleRate.
+  ok('sampleRate' in networkStatsMgr,
+   "sampleRate should be a ResourceStatsManager attribute.");
+  sampleRate = networkStatsMgr.sampleRate;
+  ok(sampleRate > 0, "sampleRate is greater than 0.");
+
+  // Test maxStorageAge.
+  ok('maxStorageAge' in networkStatsMgr,
+   "maxStorageAge should be a ResourceStatsManager attribute.");
+  ok(networkStatsMgr.maxStorageAge > 0,
+   "maxStorageAge is greater than 0.");
+
+  // Test whether "network" in resourceTypes array.
+  ok('resourceTypes' in networkStatsMgr,
+   "resourceTypes should be a ResourceStatsManager attribute.");
+  ok(Array.isArray(networkStatsMgr.resourceTypes),
+   "networkStatsMgr.resourceTypes is an array.");
+  ok(networkStatsMgr.resourceTypes.indexOf("network") > -1,
+   "'network' is an element of networkStatsMgr.resourceTypes.");
+}
+
+// Check the content returned by ResourceStats.getData().
+function checkData(data, start, end) {
+  // Check if data is an array.
+  if (!Array.isArray(data)) {
+    ok(false, "getData does not return an array.")
+    return;
+  } else {
+    ok(true, "getData returns an array.")
+  }
+
+  // Iterate the array and check the timestamp and type of each element.
+  var success = true;
+  var obj = null;
+  var timestamp = start;
+  var i = 0;
+  var length = data.length;
+
+  do {
+    obj = data[i++];
+
+    // Check object type.
+    if (!(obj instanceof NetworkStatsData)) {
+      success = false;
+      ok(false, "The array contains a non-NetworkStatsData object.");
+      break;
+    }
+
+    // Check if the timestamp is continuous.
+    if (obj.timestamp !== timestamp) {
+      success = false;
+      ok(false, "The timestamp of NetworkStatsData object is correct.");
+      break;
+    }
+
+    timestamp += sampleRate;
+  } while (i < length);
+
+  if (!success) {
+    return;
+  }
+
+  // Check the timestamp of the last element is equal to end.
+  if (obj.timestamp != end) {
+    ok(false,
+       "The timestamp of the last element of the array is equal to end.");
+    return;
+  }
+
+  // Execute next test case.
+  ok(true, "The return of getData is an array of NetworkStatsData objects.");
+  testMethods();
+}
+
+// Test Cases for testing WebIDL methods.
+var testCases = [
+  function() {
+    // Test clearAllStats.
+    var promise = networkStatsMgr.clearAllStats();
+    promise.then(function() {
+      ok(true, "clearAllStats clears the network store.");
+      testMethods();
+    }, function() {
+      ok(false, "clearAllStats fails to clear the network store.");
+    });
+  },
+
+  function() {
+    // Test clearStats.
+    var promise = networkStatsMgr.clearStats();
+    promise.then(function() {
+      ok(true, "clearStats clears the network store.");
+      testMethods();
+    }, function() {
+      ok(false, "clearStats fails to clear the network store.");
+    });
+  },
+
+  function() {
+    // Check if clearStats throw exception when start is great than end.
+    var end = Date.now();
+    var start = end + 1000;
+    var promise = networkStatsMgr.clearStats(null, start, end);
+    promise.then(function() {
+      ok(false,
+         "clearStats does not throw exception when start is great than end.");
+    }, function() {
+      ok(true, "clearStats throw exception when start is great than end.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Check if clearStats throw exception when start is less than 0.
+    var end = Date.now();
+    var start = -1;
+    var promise = networkStatsMgr.clearStats(null, start, end);
+    promise.then(function() {
+      ok(false,
+         "clearStats dose not throw exception when start is less than 0.");
+    }, function() {
+      ok(true, "clearStats throw exception when start is less than 0.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Check if clearStats throw exception when manifestURL is invalid.
+    var options = {manifestURL: invalidManifestURL};
+    var promise = networkStatsMgr.clearStats(options);
+    promise.then(function() {
+      ok(false,
+         "clearStats does not throw exception when manifestURL is invalid.");
+    }, function() {
+      ok(true, "clearStats throw exception when manifestURL is invalid.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test getAvailableComponents.
+    var promise = networkStatsMgr.getAvailableComponents();
+    promise.then(function(value) {
+      if (Array.isArray(value)) {
+        ok(true, "getAvailableComponents returns an array.");
+        testMethods();
+      } else {
+        ok(false, "getAvailableComponents does not return an array.");
+      }
+    }, function() {
+      ok(false, "Fail to execute getAvailableComponents.");
+    });
+  },
+
+  function() {
+    // Test getStats.
+    ok(true, "Get system stats when start and end are adapted to sampleRate.");
+
+    // Prepare start and end.
+    var offset = (new Date()).getTimezoneOffset() * 60 * 1000;
+    var end = Math.floor((Date.now() - offset) / sampleRate) * sampleRate + offset;
+    var start = end - sampleRate * 10;
+
+    // Launch request.
+    var promise = networkStatsMgr.getStats(null, start, end);
+    promise.then(function(value) {
+      // Check the object type.
+      if (value instanceof ResourceStats) {
+        ok(true, "Get a ResourceStats object.");
+      } else {
+        ok(false, "Fail to get a ResourceStats object.");
+        return;
+      }
+
+      // Check attributes of ResourceStats.
+      ok(value.type == "network", "type should be network.");
+      ok(value.component == null, "component should be null.");
+      ok(value.serviceType == null, "serviceType should be null.");
+      ok(value.manifestURL == null, "manifestURL should be null.");
+
+      // Check if the time range of ResourceStats is equal to the request.
+      ok(value.start == start, "start timestamp should be equal.");
+      ok(value.end == end, "end timestamp should be equal.");
+
+      // Check stats stored inside ResourceStats.
+      if ('getData' in value) {
+        checkData(value.getData(), start, end);
+      } else {
+        ok(false, "getData is not a method of ResourceStats.");
+        return;
+      }
+    }, function() {
+      ok(false, "Get network stats failed.");
+    });
+  },
+
+  function() {
+    // Test getStats when start and end are not adapted to sampleRate.
+    ok(true,
+       "Get system stats when start and end are not adapted to sampleRate.");
+
+    // Prepare start and end.
+    var end = Date.now();
+    var start = end - sampleRate * 10;
+
+    // Normalize start and end.
+    var offset = (new Date()).getTimezoneOffset() * 60 * 1000;
+    var normEnd = Math.floor((end - offset) / sampleRate)
+                    * sampleRate + offset;
+    var normStart = Math.floor((start - offset) / sampleRate)
+                      * sampleRate + offset;
+
+    // Launch request.
+    var promise = networkStatsMgr.getStats(null, start, end);
+    promise.then(function(value) {
+      // Check the object type.
+      if (value instanceof ResourceStats) {
+        ok(true, "Get a ResourceStats object.");
+      } else {
+        ok(false, "Fail to get a ResourceStats object.");
+        return;
+      }
+
+      // Check attributes of ResourceStats.
+      ok(value.type == "network", "type should be network.");
+      ok(value.component == null, "component should be null.");
+      ok(value.serviceType == null, "serviceType should be null.");
+      ok(value.manifestURL == null, "manifestURL should be null.");
+
+      // Check if time range of ResourceStats are normalized.
+      ok(value.start == normStart, "start timestamp should be normalized.");
+      ok(value.end == normEnd, "end timestamp should be normalized.");
+
+      // Check stats stored inside ResourceStats.
+      if ('getData' in value) {
+        checkData(value.getData(), normStart, normEnd);
+      } else {
+        ok(false, "getData is not a method of ResourceStats.");
+        return;
+      }
+    }, function() {
+      ok(false, "Get network stats failed.");
+    });
+  },
+
+  function () {
+    // Check if getStats throw exception when start is greater than end.
+    var end = Date.now();
+    var start = end + 1000;
+    var promise = networkStatsMgr.getStats(null, start, end);
+    promise.then(function() {
+      ok(false,
+         "getStats dose not throw exception when start is great than end.");
+    }, function() {
+      ok(true, "getStats throw exception when start is great than end.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Check if getStats throw exception when start is less than 0.
+    var end = Date.now();
+    var start = -1;
+    var promise = networkStatsMgr.getStats(null, start, end);
+    promise.then(function() {
+      ok(false,
+         "getStats dose not throw exception when start is less than 0.");
+    }, function() {
+      ok(true, "getStats throw exception when start is less than 0.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Check if getStats throw exception when manifestURL is invalid.
+    var options = {manifestURL: invalidManifestURL};
+    var promise = networkStatsMgr.getStats(options);
+    promise.then(function(value) {
+      ok(false,
+         "getStats does not throw exception when manifestURL is invalid.");
+    }, function() {
+      ok(true, "getStats throw exception when manifestURL is invalid.");
+      testMethods();
+    });
+  }
+];
+
+// Test WebIDL methods related stats operation.
+function testMethods() {
+  if (!testCases.length) {
+    ok(true, "Done.");
+    SpecialPowers.removePermission("resourcestats-manage", document);
+    SimpleTest.finish();
+    return;
+  }
+
+  var testCase = testCases.shift();
+  testCase();
+}
+
+function startTest() {
+  // Create an instance of ResourceStatsManager for network stats.
+  networkStatsMgr = new window.ResourceStatsManager("network");
+  ok(networkStatsMgr, "Create networkStatsMgr.");
+
+  // Test WebIDL attributes.
+  testAttributes();
+
+  // Test WebIDL methods related to stats operation.
+  testMethods();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// Enable permission and preference.
+SpecialPowers.addPermission("resourcestats-manage", true, document);
+SpecialPowers.pushPrefEnv({ 'set': [
+                            ["dom.resource_stats.enabled", true],
+                            ["dom.ignore_webidl_scope_checks", true]
+                          ]}, startTest);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/resourcestats/tests/mochitest/test_no_perm.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test to ensure ResourceStatsManager is not accessible without permission</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// Test to ensure ResourceStatsManager is not accessible without permission.
+SpecialPowers.removePermission("resourcestats-manage", document);
+SpecialPowers.pushPrefEnv({ 'set': [
+                            ["dom.resource_stats.enabled", true],
+                            ["dom.ignore_webidl_scope_checks", true]
+                          ]}, function() {
+  ok(!(SpecialPowers.hasPermission("resourcestats-manage", document)),
+     "Do not have permission 'resourcestats-manage'.");
+  ok(SpecialPowers.getBoolPref("dom.resource_stats.enabled"),
+     "Preference 'dom.resource_stats.enabled' is true.");
+
+  // Check ResourceStatsManager exist.
+  ok(!('ResourceStatsManager' in window),
+     "Cannot access window.ResourceStatsManager when have no permission");
+
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/resourcestats/tests/mochitest/test_not_supported_type.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test to ensure ResourceStatsManager does not create an instance for non-supported type</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+const type = "non-supported";
+
+SimpleTest.waitForExplicitFinish();
+
+// Test to ensure ResourceStatsManager does not create an instance for
+// non-supported type.
+SpecialPowers.addPermission("resourcestats-manage", true, document);
+SpecialPowers.pushPrefEnv({ 'set': [
+                            ["dom.resource_stats.enabled", true],
+                            ["dom.ignore_webidl_scope_checks", true]
+                          ]}, function() {
+  try {
+    var mgr = ResourceStatsManager(type);
+    ok(false,
+       "Creating an instance for non-supported type should throw an exeception.");
+  } catch (ex) {
+    ok(ex,
+       "Got an exception when creating an instance for non-supported type.");
+  }
+
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/resourcestats/tests/mochitest/test_power_alarm.html
@@ -0,0 +1,355 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for ResourceStats methods realted to power resource control</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+const invalidManifestURL = "app://invalid.gaiamobile.org/manifest.webapp";
+const cpuComponent = "cpu:0";
+const gpsComponent = "gps:0";
+var powerStatsMgr = null; // ResourceStatsManager for power statistics.
+
+function errorCb(reason) {
+  ok(false, reason);
+}
+
+// Check the content returned by getAlarms.
+function checkAlarmsArray(alarms) {
+  // Check if data is an array.
+  if (!Array.isArray(alarms)) {
+    throw "getAlarms does not return an array.";
+  } else {
+    ok(true, "getAlarms returns an array.")
+  }
+
+  // Iterate the array and check the type of each element.
+  var obj = null;
+  var message = null; // Message for exception
+
+  for (var i = 0; i < alarms.length; i++) {
+    obj = alarms[i];
+
+    // Check if obj is an instance os ResourceStatsAlarm.
+    if (!(obj instanceof ResourceStatsAlarm)) {
+      message = "The array contains a non-ResourceStatsAlarm object.";
+      break;
+    }
+
+    // Check if obj.type is power.
+    if (obj.type != "power") {
+      message = "The type of a ResourceStatsAlarm object is not power.";
+      break;
+    }
+  }
+
+  if (message) {
+    throw message;
+  }
+
+  ok(true, "The return is an array of ResourceStatsAlarm objects.");
+}
+
+// Test Cases for testing WebIDL methods related to resource control.
+var testCases = [
+  function() {
+    // Test removeAllAlarms.
+    var promise = powerStatsMgr.removeAllAlarms();
+    promise.then(function() {
+      ok(true, "removeAllAlarms deleted all power alarms.");
+      testMethods();
+    }, function() {
+      ok(false, "removeAllAlarms failed to delete power alarms.");
+    });
+  },
+
+  function() {
+    // Test addAlarm.
+    var threshold = Math.floor(Math.random() * 1000);
+    var promise = powerStatsMgr.addAlarm(threshold,
+                                         { 'component': cpuComponent },
+                                         { 'startTime': Date.now() });
+    promise.then(function(value) {
+      // Check the value (alarmId).
+      if (value < 0) {
+        ok(false, "addAlarm failed to create an alarm.");
+      } else {
+        ok(true, "addAlarm created an alarm.");
+        testMethods();
+      }
+    }, function() {
+      ok(false, "addAlarm failed to create an alarm.");
+    });
+  },
+
+  function() {
+    // Test addAlarm with negative threshold.
+    var threshold = -1;
+    var promise = powerStatsMgr.addAlarm(threshold,
+                                         { 'component': cpuComponent },
+                                         { 'startTime': Date.now() });
+    promise.then(function() {
+      // Check the value.
+      ok(false,
+         "addAlarm did not throw an exception when negative threshold is set.");
+    }, function() {
+      ok(true, "addAlarm threw an exception when negative threshold is set.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test addAlarm with no threshold.
+    var promise = powerStatsMgr.addAlarm();
+    promise.then(function() {
+      // Check the value.
+      ok(false, "addAlarm did not throw an exception when no threshold.");
+    }, function() {
+      ok(true, "addAlarm threw an exception when no threshold.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test addAlarm with negative startTime.
+    var threshold = Math.floor(Math.random() * 1000);
+    var promise = powerStatsMgr.addAlarm(threshold,
+                                         { 'component': cpuComponent },
+                                         { 'startTime': -1 });
+    promise.then(function() {
+      // Check the value.
+      ok(false,
+         "addAlarm did not throw an exception when negative startTime is set.");
+    }, function() {
+      ok(true, "addAlarm threw an exception when negative startTime is set.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test addAlarm when manifestURL is invalid.
+    var threshold = Math.floor(Math.random() * 1000);
+    var promise = powerStatsMgr.addAlarm(threshold,
+                                         { 'component': cpuComponent,
+                                           'manifestURL': invalidManifestURL },
+                                         { 'startTime': Date.now() });
+    promise.then(function() {
+      // Check the value.
+      ok(false, "addAlarm did not throw an exception when manifestURL is invalid.");
+    }, function() {
+      ok(true, "addAlarm threw an exception when manifestURL is invalid.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test getAlarms.
+    var alarmId;
+    var alarms;
+    var threshold = Math.floor(Math.random() * 1000);
+
+    // Execution steps:
+    // 1. Add a new alarm.
+    var promise = powerStatsMgr.addAlarm(threshold,
+                                         { 'component': cpuComponent },
+                                         { 'startTime': Date.now() });
+
+    // 2. Test getAlarms if new alarm is added.
+    var runGetAlarms = function(value) {
+      alarmId = value;
+      return powerStatsMgr.getAlarms({ 'component': cpuComponent });
+    };
+
+    // 3. Check the content returned by getAlarms.
+    var checkGetAlarmsReturn = function(value) {
+      alarms = value;
+      checkAlarmsArray(value);
+    };
+
+    // 4. Check the alarm added in step 1 is inside the return of getAlarms.
+    var checkAlarm = function (value) {
+      // Find the alarm.
+      var index = alarms.map(function(e) { return e.alarmId; })
+                    .indexOf(alarmId);
+      if (index < 0) {
+        throw "getAlarms does not get the alarm added in previous step.";
+      }
+      var alarm = alarms[index];
+
+      // Evaluate the alarm.
+      ok(alarm.threshold == threshold, "threshold is equal.");
+      ok(alarm.component == cpuComponent, "component is equal.");
+      ok(alarm.serviceType == null, "serviceType should be null.");
+      ok(alarm.manifestURL == null, "manifestURL should be null.");
+    };
+
+    // Create promise chaining.
+    promise.then(runGetAlarms)
+      .then(checkGetAlarmsReturn)
+      .then(checkAlarm)
+      .then(testMethods, errorCb); // Execute next test case.
+  },
+
+  function() {
+    // Test getAlarms with invalid manifestURL.
+    var threshold = Math.floor(Math.random() * 1000);
+    var promise = powerStatsMgr.addAlarm(threshold,
+                                         { 'component': cpuComponent,
+                                           'manifestURL': invalidManifestURL },
+                                         { 'startTime': Date.now() });
+
+    promise.then(function() {
+      // Check the value.
+      ok(false, "getAlarms did not throw an exception when manifestURL is invalid.");
+    }, function() {
+      ok(true, "getAlarms threw an exception when manifestURL is invalid.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test getAlarms with incorrect parameter.
+    var threshold = Math.floor(Math.random() * 1000);
+
+    // Execution steps:
+    // 1. Add a new alarm.
+    var promise = powerStatsMgr.addAlarm(threshold,
+                                         { 'component': cpuComponent },
+                                         { 'startTime': Date.now() });
+
+    // 2. Call getAlarms with incorrect parameter.
+    var runGetAlarms = function() {
+      return powerStatsMgr.getAlarms({ 'component': gpsComponent });
+    };
+
+    // 3. check the content returned by getAlarms.
+    var checkGetAlarmsReturn = function(value) {
+      // Check array length
+      if (value.length) {
+        throw "getAlarms gets an alarm when using incorrect parameter.";
+      } else {
+        ok(true,
+           "getAlarms returns an empty array when using incorrect parameter.");
+      }
+    };
+
+    // Create pomise chaining.
+    promise.then(runGetAlarms)
+      .then(checkGetAlarmsReturn)
+      .then(testMethods, errorCb); // Execute next test case.
+  },
+
+  function() {
+    // Test removeAlarm
+    var alarmId;
+    var threshold = Math.floor(Math.random() * 1000);
+
+    // Execution steps:
+    // 1. Add a new alarm.
+    var promise = powerStatsMgr.addAlarm(threshold,
+                                         { 'component': cpuComponent },
+                                         { 'startTime': Date.now() });
+
+    // 2. Try to remove the new alarm.
+    var runRemoveAlarm = function(value) {
+      alarmId = value;
+      return powerStatsMgr.removeAlarm(alarmId);
+    }
+
+    // Create promise chaining.
+    promise.then(runRemoveAlarm)
+      .then(function() {
+        ok(true, "removeAlarm deleted the alarm.");
+        testMethods();
+      }, errorCb);
+  },
+
+  function() {
+    // Test removeAlarm with negative alarmId
+    var alarmId = -1;
+    var promise = powerStatsMgr.removeAlarm(alarmId);
+    promise.then(function() {
+      ok(false,
+         "removeAlarm did not throw an exception when negative alarmId is set.");
+    }, function() {
+      ok(true, "removeAlarm threw an exception when negative alarmId is set.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test removeAlarm with invalid alarmId
+    var alarmId;
+    var threshold = Math.floor(Math.random() * 1000);
+
+    // Execution steps:
+    // 1. Add a new alarm.
+    var promise = powerStatsMgr.addAlarm(threshold,
+                                         { 'component': cpuComponent },
+                                         { 'startTime': Date.now() });
+
+    // 2. Try to remove an invalid alarm.
+    var runRemoveAlarm = function(value) {
+      alarmId = value;
+      // Because alarmId is auto-increment, any alarmId larger than the
+      // latest alarmId should be invalid.
+      return powerStatsMgr.removeAlarm(alarmId + 10);
+    }
+
+    // Create promise chaining.
+    promise.then(runRemoveAlarm)
+      .then(function() {
+        // Input with incorrect alarmId should not be resolved.
+        throw "removeAlarm should fail with invalid alarmId.";
+      }, function(reason) {
+        if (reason == "alarm not existed") {
+          ok(true, "removeAlarm with invalid alarmId should fail.")
+        } else {
+          throw reason;
+        }
+      })
+      .then(testMethods, errorCb);
+  }
+];
+
+// Test WebIDL methods related stats operation.
+function testMethods() {
+  if (!testCases.length) {
+    ok(true, "Done.");
+    SpecialPowers.removePermission("resourcestats-manage", document);
+    SimpleTest.finish();
+    return;
+  }
+
+  var testCase = testCases.shift();
+  testCase();
+}
+
+function startTest() {
+  // Create an instance of ResourceStatsManager for power.
+  powerStatsMgr = new ResourceStatsManager("power");
+  ok(powerStatsMgr, "Create powerStatsMgr.");
+
+  // Test WebIDL methods related to resource control.
+  testMethods();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// Enable permission and preference.
+SpecialPowers.addPermission("resourcestats-manage", true, document);
+SpecialPowers.pushPrefEnv({ 'set': [
+                            ["dom.resource_stats.enabled", true],
+                            ["dom.ignore_webidl_scope_checks", true]
+                          ]}, startTest);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/resourcestats/tests/mochitest/test_power_stats.html
@@ -0,0 +1,345 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for ResourceStats methods realted to power statistics</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+const invalidManifestURL = "app://invalid.gaiamobile.org/manifest.webapp";
+var powerStatsMgr = null; // ResourceStatsManager for power statistics.
+var sampleRate = 0;
+
+// Test WebIDL attributes.
+function testAttributes() {
+  // Test sampleRate.
+  ok('sampleRate' in powerStatsMgr,
+   "sampleRate should be a ResourceStatsManager attribute.");
+  sampleRate = powerStatsMgr.sampleRate;
+  ok(sampleRate > 0, "sampleRate is greater than 0.");
+
+  // Test maxStorageAge.
+  ok('maxStorageAge' in powerStatsMgr,
+   "maxStorageAge should be a ResourceStatsManager attribute.");
+  ok(powerStatsMgr.maxStorageAge > 0,
+   "maxStorageAge is greater than 0.");
+
+  // Test whether "power" in resourceTypes array.
+  ok('resourceTypes' in powerStatsMgr,
+   "resourceTypes should be a ResourceStatsManager attribute.");
+  ok(Array.isArray(powerStatsMgr.resourceTypes),
+   "powerStatsMgr.resourceTypes is an array.");
+  ok(powerStatsMgr.resourceTypes.indexOf("power") > -1,
+   "'power' is an element of powerStatsMgr.resourceTypes.");
+}
+
+// Check the content returned by ResourceStats.getData().
+function checkData(data, start, end) {
+  // Check if data is an array.
+  if (!Array.isArray(data)) {
+    ok(false, "getData does not return an array.")
+    return;
+  } else {
+    ok(true, "getData returns an array.")
+  }
+
+  // Iterate the array and check the timestamp and type of each element.
+  var success = true;
+  var obj = null;
+  var timestamp = start;
+  var i = 0;
+  var length = data.length;
+
+  do {
+    obj = data[i++];
+
+    // Check object type.
+    if (!(obj instanceof PowerStatsData)) {
+      success = false;
+      ok(false, "The array contains a non-PowerStatsData object.");
+      break;
+    }
+
+    // Check if the timestamp is continuous.
+    if (obj.timestamp != timestamp) {
+      success = false;
+      ok(false, "The timestamp of PowerStatsData object is correct.");
+      break;
+    }
+
+    timestamp += sampleRate;
+  } while (i < length);
+
+  if (!success) {
+    return;
+  }
+
+  // Check the timestamp of the last element is equal to end.
+  if (obj.timestamp != end) {
+    ok(false,
+       "The timestamp of the last element of the array is equal to end.");
+    return;
+  }
+
+  // Execute next test case.
+  ok(true, "The return of getData is an array of PowerStatsData objects.");
+  testMethods();
+}
+
+// Test Cases for testing WebIDL methods.
+var testCases = [
+  function() {
+    // Test clearAllStats.
+    var promise = powerStatsMgr.clearAllStats();
+    promise.then(function() {
+      ok(true, "clearAllStats clears the power store.");
+      testMethods();
+    }, function() {
+      ok(false, "clearAllStats fails to clear the power store.");
+    });
+  },
+
+  function() {
+    // Test clearStats.
+    var promise = powerStatsMgr.clearStats();
+    promise.then(function() {
+      ok(true, "clearStats clears the power store.");
+      testMethods();
+    }, function() {
+      ok(false, "clearStats fails to clear the power store.");
+    });
+  },
+
+  function() {
+    // Check if clearStats throw exception when start is great than end.
+    var end = Date.now();
+    var start = end + 1000;
+    var promise = powerStatsMgr.clearStats(null, start, end);
+    promise.then(function() {
+      ok(false,
+         "clearStats does not throw exception when start is great than end.");
+    }, function() {
+      ok(true, "clearStats throw exception when start is great than end.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Check if clearStats throw exception when start is less than 0.
+    var end = Date.now();
+    var start = -1;
+    var promise = powerStatsMgr.clearStats(null, start, end);
+    promise.then(function() {
+      ok(false,
+         "clearStats dose not throw exception when start is less than 0.");
+    }, function() {
+      ok(true, "clearStats throw exception when start is less than 0.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Check if clearStats throw exception when manifestURL is invalid.
+    var options = {manifestURL: invalidManifestURL};
+    var promise = powerStatsMgr.clearStats(options);
+    promise.then(function() {
+      ok(false,
+         "clearStats does not throw exception when manifestURL is invalid.");
+    }, function() {
+      ok(true, "clearStats throw exception when manifestURL is invalid.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Test getAvailableComponents.
+    var promise = powerStatsMgr.getAvailableComponents();
+    promise.then(function(value) {
+      if (Array.isArray(value)) {
+        ok(true, "getAvailableComponents returns an array.");
+        testMethods();
+      } else {
+        ok(false, "getAvailableComponents does not return an array.");
+      }
+    }, function() {
+      ok(false, "Fail to execute getAvailableComponents.");
+    });
+  },
+
+  function() {
+    // Test getStats.
+    ok(true, "Get system stats when start and end are adapted to sampleRate.");
+
+    // Prepare start and end.
+    var offset = (new Date()).getTimezoneOffset() * 60 * 1000;
+    var end = Math.floor((Date.now() - offset) / sampleRate) * sampleRate + offset;
+    var start = end - sampleRate * 10;
+
+    // Launch request.
+    var promise = powerStatsMgr.getStats(null, start, end);
+    promise.then(function(value) {
+      // Check the object type.
+      if (value instanceof ResourceStats) {
+        ok(true, "Get a ResourceStats object.");
+      } else {
+        ok(false, "Fail to get a ResourceStats object.");
+        return;
+      }
+
+      // Check attributes of ResourceStats.
+      ok(value.type == "power", "type should be power.");
+      ok(value.component == null, "component should be null.");
+      ok(value.serviceType == null, "serviceType should be null.");
+      ok(value.manifestURL == null, "manifestURL should be null.");
+
+      // Check if the time range of ResourceStats is equal to the request.
+      ok(value.start == start, "start timestamp should be equal.");
+      ok(value.end == end, "end timestamp should be equal.");
+
+      // Check stats stored inside ResourceStats.
+      if ('getData' in value) {
+        checkData(value.getData(), start, end);
+      } else {
+        ok(false, "getData is not a method of ResourceStats.");
+        return;
+      }
+    }, function() {
+      ok(false, "Get power stats failed.");
+    });
+  },
+
+  function() {
+    // Test getStats when start and end are not adapted to sampleRate.
+    ok(true,
+       "Get system stats when start and end are not adapted to sampleRate.");
+
+    // Prepare start and end.
+    var end = Date.now();
+    var start = end - sampleRate * 10;
+
+    // Normalize start and end.
+    var offset = (new Date()).getTimezoneOffset() * 60 * 1000;
+    var normEnd = Math.floor((end - offset) / sampleRate)
+                    * sampleRate + offset;
+    var normStart = Math.floor((start - offset) / sampleRate)
+                      * sampleRate + offset;
+
+    // Launch request.
+    var promise = powerStatsMgr.getStats(null, start, end);
+    promise.then(function(value) {
+      // Check the object type.
+      if (value instanceof ResourceStats) {
+        ok(true, "Get a ResourceStats object.");
+      } else {
+        ok(false, "Fail to get a ResourceStats object.");
+        return;
+      }
+
+      // Check attributes of ResourceStats.
+      ok(value.type == "power", "type should be power.");
+      ok(value.component == null, "component should be null.");
+      ok(value.serviceType == null, "serviceType should be null.");
+      ok(value.manifestURL == null, "manifestURL should be null.");
+
+      // Check if time range of ResourceStats are normalized.
+      ok(value.start == normStart, "start timestamp should be normalized.");
+      ok(value.end == normEnd, "end timestamp should be normalized.");
+
+      // Check stats stored inside ResourceStats.
+      if ('getData' in value) {
+        checkData(value.getData(), normStart, normEnd);
+      } else {
+        ok(false, "getData is not a method of ResourceStats.");
+        return;
+      }
+    }, function() {
+      ok(false, "Get power stats failed.");
+    });
+  },
+
+  function() {
+    // Check if getStats throw exception when start is greater than end.
+    var end = Date.now();
+    var start = end + 1000;
+    var promise = powerStatsMgr.getStats(null, start, end);
+    promise.then(function() {
+      ok(false,
+         "getStats dose not throw exception when start is great than end.");
+    }, function() {
+      ok(true, "getStats throw exception when start is great than end.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Check if getStats throw exception when start is less than 0.
+    var end = Date.now();
+    var start = -1;
+    var promise = powerStatsMgr.getStats(null, start, end);
+    promise.then(function() {
+      ok(false,
+         "getStats dose not throw exception when start is less than 0.");
+    }, function() {
+      ok(true, "getStats throw exception when start is less than 0.");
+      testMethods();
+    });
+  },
+
+  function() {
+    // Check if getStats throw exception when manifestURL is invalid.
+    var options = {manifestURL: invalidManifestURL};
+    var promise = powerStatsMgr.getStats(options);
+    promise.then(function(value) {
+      ok(false,
+         "getStats does not throw exception when manifestURL is invalid.");
+    }, function() {
+      ok(true, "getStats throw exception when manifestURL is invalid.");
+      testMethods();
+    });
+  }
+];
+
+// Test WebIDL methods related stats operation.
+function testMethods() {
+  if (!testCases.length) {
+    ok(true, "Done.");
+    SpecialPowers.removePermission("resourcestats-manage", document);
+    SimpleTest.finish();
+    return;
+  }
+
+  var testCase = testCases.shift();
+  testCase();
+}
+
+function startTest() {
+  // Create an instance of ResourceStatsManager for power stats.
+  powerStatsMgr = new ResourceStatsManager("power");
+  ok(powerStatsMgr, "Create powerStatsMgr.");
+
+  // Test WebIDL attributes.
+  testAttributes();
+
+  // Test WebIDL methods related to stats operation.
+  testMethods();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// Enable permission and preference.
+SpecialPowers.addPermission("resourcestats-manage", true, document);
+SpecialPowers.pushPrefEnv({ 'set': [
+                            ["dom.resource_stats.enabled", true],
+                            ["dom.ignore_webidl_scope_checks", true]
+                          ]}, startTest);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/resourcestats/tests/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['MOZ_B2G_RIL']:
+    XPCSHELL_TESTS_MANIFESTS += ['xpcshell/xpcshell.ini']
+
+MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
new file mode 100644
--- /dev/null
+++ b/dom/resourcestats/tests/xpcshell/test_resourcestats_db.js
@@ -0,0 +1,985 @@
+/* Any: copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/ResourceStatsDB.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const db = new ResourceStatsDB();
+
+// Components.
+const wifiComponent = "wifi:0";
+const mobileComponent = "mobile:1";
+const cpuComponent = "cpu:0";
+const gpsComponent = "gps:0";
+
+// List of available components.
+const networkComponents = [wifiComponent, mobileComponent];
+const powerComponents = [cpuComponent, gpsComponent];
+const offset = (new Date()).getTimezoneOffset() * 60 * 1000;
+
+// Clear store.
+function clearStore(store, callback) {
+  db._dbNewTxn(store, "readwrite", function(aTxn, aStore) {
+    aStore.openCursor().onsuccess = function (event) {
+      let cursor = event.target.result;
+      if (cursor){
+        cursor.delete();
+        cursor.continue();
+      }
+    };
+  }, callback);
+}
+
+// Clear all stores to avoid starting tests with unknown state.
+add_test(function prepareDatabase() {
+  clearStore('power_stats_store', function() {
+    clearStore('network_stats_store', function() {
+      clearStore('alarm_store', function() {
+        run_next_test();
+      });
+    });
+  });
+});
+
+// Dump data saved in a store.
+function dumpStore(store, callback) {
+  db._dbNewTxn(store, "readonly", function(aTxn, aStore) {
+    aStore.mozGetAll().onsuccess = function onsuccess(event) {
+      aTxn.result = event.target.result;
+    };
+  }, callback);
+}
+
+// Check sampleRate is unchangeable.
+add_test(function test_sampleRate() {
+  var sampleRate = db.sampleRate;
+  do_check_true(sampleRate > 0);
+
+  db.sampleRate = 0;
+  sampleRate = db.sampleRate;
+  do_check_true(sampleRate > 0);
+
+  run_next_test();
+});
+
+// Test maxStorageAge is unchangeable.
+add_test(function test_maxStorageAge() {
+  var maxStorageAge = db.maxStorageAge;
+  do_check_true(maxStorageAge > 0);
+
+  db.maxStorageAge = 0;
+  maxStorageAge = db.maxStorageAge;
+  do_check_true(maxStorageAge > 0);
+
+  run_next_test();
+});
+
+// Normalize timestamp to sampleRate precision.
+function normalizeTime(aTimeStamp) {
+  var sampleRate = db.sampleRate;
+
+  return Math.floor((aTimeStamp - offset) / sampleRate) * sampleRate;
+}
+
+// Generte record as input for saveNetworkStats() as well as the expected
+// result when calling getStats().
+function generateNetworkRecord(aAppId, aServiceType, aComponents) {
+  var result = [];
+  var componentStats = {};
+  var receivedBytes;
+  var sentBytes;
+  var totalReceivedBytes = 0;
+  var totalSentBytes = 0;
+
+  aComponents.forEach(function(comp) {
+    // Step 1: generate random value for receivedBytes and sentBytes.
+    receivedBytes = Math.floor(Math.random() * 1000);
+    sentBytes = Math.floor(Math.random() * 1000);
+    totalReceivedBytes += receivedBytes;
+    totalSentBytes += sentBytes;
+
+    // Step 2: add stats to record.componentStats.
+    componentStats[comp] = {
+      receivedBytes: receivedBytes,
+      sentBytes: sentBytes
+    };
+
+    // Step 3: generate expected results.
+    result.push({
+      appId: aAppId,
+      serviceType: aServiceType,
+      component: comp,
+      receivedBytes: receivedBytes,
+      sentBytes: sentBytes
+    });
+  });
+
+  // Step 4: generate expected total stats.
+  result.push({
+    appId: aAppId,
+    serviceType: aServiceType,
+    component: "",
+    receivedBytes: totalReceivedBytes,
+    sentBytes: totalSentBytes
+  });
+
+  // Step 5: get record.
+  var record = { appId: aAppId,
+                 serviceType: aServiceType,
+                 componentStats: componentStats };
+
+  return { record: record, result: result };
+}
+
+// Generte record as input for savePowerStats() as well as the expected
+// result when calling getStats().
+function generatePowerRecord(aAppId, aServiceType, aComponents) {
+  var result = [];
+  var componentStats = {};
+  var consumedPower;
+  var totalConsumedPower = 0;
+
+  aComponents.forEach(function(comp) {
+    // Step 1: generate random value for consumedPower.
+    consumedPower = Math.floor(Math.random() * 1000);
+    totalConsumedPower += consumedPower;
+
+    // Step 2: add stats to record.componentStats.
+    componentStats[comp] = consumedPower;
+
+    // Step 3: generate expected results.
+    result.push({
+      appId: aAppId,
+      serviceType: aServiceType,
+      component: comp,
+      consumedPower: consumedPower
+    });
+  });
+
+  // Step 4: generate expected total stats.
+  result.push({
+    appId: aAppId,
+    serviceType: aServiceType,
+    component: "",
+    consumedPower: totalConsumedPower
+  });
+
+  // Step 5: get record.
+  var record = { appId: aAppId,
+                 serviceType: aServiceType,
+                 componentStats: componentStats };
+
+  return { record: record, result: result };
+}
+
+// Compare stats saved in network store with expected results.
+function checkNetworkStatsStore(aExpectedResult, aDumpResult, aTimestamp) {
+  // Step 1: a quick check for the length of arrays first.
+  do_check_eq(aExpectedResult.length, aDumpResult.length);
+
+  // Step 2: create a map array for search by receivedBytes.
+  var mapArray = aExpectedResult.map(function(e) {return e.receivedBytes;});
+
+  // Step 3: compare each element to make sure both array are equal.
+  var index;
+  var target;
+
+  aDumpResult.forEach(function(stats) {
+    index = 0;
+    // Find the first equal receivedBytes since index.
+    while ((index = mapArray.indexOf(stats.receivedBytes, index)) > -1) {
+      // Compare all attributes.
+      target = aExpectedResult[index];
+      if (target.appId != stats.appId ||
+          target.serviceType != stats.serviceType ||
+          target.component != stats.component ||
+          target.sentBytes != stats.sentBytes ||
+          aTimestamp != stats.timestamp) {
+        index += 1;
+        continue;
+      } else {
+        // If found, remove that element from aExpectedResult and mapArray.
+        aExpectedResult.splice(index, 1);
+        mapArray.splice(index, 1);
+        break;
+      }
+    }
+    do_check_neq(index, -1);
+  });
+  run_next_test();
+}
+
+// Compare stats saved in power store with expected results.
+function checkPowerStatsStore(aExpectedResult, aDumpResult, aTimestamp) {
+  // Step 1: a quick check for the length of arrays first.
+  do_check_eq(aExpectedResult.length, aDumpResult.length);
+
+  // Step 2: create a map array for search by consumedPower.
+  var mapArray = aExpectedResult.map(function(e) {return e.consumedPower;});
+
+  // Step 3: compare each element to make sure both array are equal.
+  var index;
+  var target;
+
+  aDumpResult.forEach(function(stats) {
+    index = 0;
+    // Find the first equal consumedPower since index.
+    while ((index = mapArray.indexOf(stats.consumedPower, index)) > -1) {
+      // Compare all attributes.
+      target = aExpectedResult[index];
+      if (target.appId != stats.appId ||
+          target.serviceType != stats.serviceType ||
+          target.component != stats.component ||
+          aTimestamp != stats.timestamp) {
+        index += 1;
+        continue;
+      } else {
+        // If found, remove that element from aExpectedResult and mapArray.
+        aExpectedResult.splice(index, 1);
+        mapArray.splice(index, 1);
+        break;
+      }
+    }
+    do_check_neq(index, -1);
+  });
+  run_next_test();
+}
+
+// Prepare network store for testing.
+function prepareNetworkStatsStore(recordArray, timestamp, callback) {
+  // Step 1: clear store.
+  clearStore("network_stats_store", function() {
+    // Step 2: save record to store.
+    db.saveNetworkStats(recordArray, timestamp, callback);
+  });
+}
+
+// Prepare power store for testing.
+function preparePowerStatsStore(recordArray, timestamp, callback) {
+  // Step 1: clear store.
+  clearStore("power_stats_store", function() {
+    // Step 2: save record to store.
+    db.savePowerStats(recordArray, timestamp, callback);
+  });
+}
+
+// Test saveNetworkStats.
+add_test(function test_saveNetworkStats() {
+  var appId = 1;
+  var serviceType = "";
+
+  // Step 1: generate data saved to store.
+  var { record: record, result: expectedResult } =
+    generateNetworkRecord(appId, serviceType, networkComponents);
+  var recordArray = [record];
+  var timestamp = Date.now();
+
+  // Step 2: save recordArray to network store.
+  prepareNetworkStatsStore(recordArray, timestamp, function(error, callback) {
+    // Step 3: check if the function call succeed.
+    do_check_eq(error, null);
+    // Step 4: dump store for comparison.
+    dumpStore("network_stats_store", function(error, result) {
+      do_check_eq(error, null);
+      checkNetworkStatsStore(expectedResult, result, normalizeTime(timestamp));
+    });
+  });
+});
+
+// Test savePowerStats.
+add_test(function test_savePowerStats() {
+  var appId = 1;
+  var serviceType = "";
+
+  // Step 1: generate data saved to store.
+  var { record: record, result: expectedResult } =
+    generatePowerRecord(appId, serviceType, powerComponents);
+  var recordArray = [record];
+  var timestamp = Date.now();
+
+  // Step 2: save recordArray to power store.
+  preparePowerStatsStore(recordArray, timestamp, function(error, callback) {
+    // Step 3: check if the function call succeed.
+    do_check_eq(error, null);
+    // Step 4: dump store for comparison.
+    dumpStore("power_stats_store", function(error, result) {
+      do_check_eq(error, null);
+      checkPowerStatsStore(expectedResult, result, normalizeTime(timestamp));
+    });
+  });
+});
+
+// Test getting network stats via getStats.
+add_test(function test_getNetworkStats() {
+  var appId = 0;
+  var manifestURL = "";
+  var serviceType = "";
+  var component = "";
+
+  // Step 1: generate data saved to store.
+  var { record: record, result: result } =
+    generateNetworkRecord(appId, serviceType, networkComponents);
+  var recordArray = [record];
+  var expectedStats = result[result.length - 1]; // Check total stats only.
+  var timestamp = Date.now();
+  var end = normalizeTime(timestamp) + offset;
+  var start = end;
+
+  // Step 2: save record and prepare network store.
+  prepareNetworkStatsStore(recordArray, timestamp, function(error, callback) {
+    // Step 3: get network stats.
+    db.getStats("network", manifestURL, serviceType, component, start, end,
+      function(error, result) {
+      do_check_eq(error, null);
+
+      // Step 4: verify result.
+      do_check_eq(result.type, "network");
+      do_check_eq(result.manifestURL, manifestURL);
+      do_check_eq(result.serviceType, serviceType);
+      do_check_eq(result.component, component);
+      do_check_eq(result.start, start);
+      do_check_eq(result.end, end);
+      do_check_eq(result.sampleRate, db.sampleRate);
+      do_check_true(Array.isArray(result.statsData));
+      do_check_eq(result.statsData.length, 1);
+      var stats = result.statsData[0];
+      do_check_eq(stats.receivedBytes, expectedStats.receivedBytes);
+      do_check_eq(stats.sentBytes, expectedStats.sentBytes);
+
+      run_next_test(); // If success, run next test.
+    });
+  });
+});
+
+// Test getting power stats via getStats.
+add_test(function test_getPowerStats() {
+  var appId = 0;
+  var manifestURL = "";
+  var serviceType = "";
+  var component = "";
+
+  // Step 1: generate data saved to store.
+  var { record: record, result: result } =
+    generatePowerRecord(appId, serviceType, powerComponents);
+  var recordArray = [record];
+  var expectedStats = result[result.length - 1]; // Check total stats only.
+  var timestamp = Date.now();
+  var end = normalizeTime(timestamp) + offset;
+  var start = end;
+
+  // Step 2: save record and prepare power store.
+  preparePowerStatsStore(recordArray, timestamp, function(error, callback) {
+    // Step 3: get power stats.
+    db.getStats("power", manifestURL, serviceType, component, start, end,
+      function(error, result) {
+      do_check_eq(error, null);
+
+      // Step 4: verify result
+      do_check_eq(result.type, "power");
+      do_check_eq(result.manifestURL, manifestURL);
+      do_check_eq(result.serviceType, serviceType);
+      do_check_eq(result.component, component);
+      do_check_eq(result.start, start);
+      do_check_eq(result.end, end);
+      do_check_eq(result.sampleRate, db.sampleRate);
+      do_check_true(Array.isArray(result.statsData));
+      do_check_eq(result.statsData.length, 1);
+      var stats = result.statsData[0];
+      do_check_eq(stats.consumedPower, expectedStats.consumedPower);
+
+      run_next_test(); // If success, run next test.
+    });
+  });
+});
+
+// Test deleting network stats via clearStats.
+add_test(function test_clearNetworkStats() {
+  var appId = 0;
+  var manifestURL = "";
+  var serviceType = "";
+  var component = "";
+
+  // Step 1: genrate data saved to network store.
+  var { record: record, result: result } =
+    generateNetworkRecord(appId, serviceType, networkComponents);
+  var recordArray = [record];
+  var timestamp = Date.now();
+  var end = normalizeTime(timestamp) + offset;
+  var start = end;
+
+  // Step 2: save record and prepare network store.
+  prepareNetworkStatsStore(recordArray, timestamp, function(error, callback) {
+    // Step 3: clear network stats.
+    db.clearStats("network", appId, serviceType, component, start, end,
+      function(error, result) {
+      do_check_eq(error, null);
+
+      // Step 4: check if the stats is deleted.
+      db.getStats("network", manifestURL, serviceType, component, start, end,
+        function(error, result) {
+        do_check_eq(result.statsData.length, 0);
+        run_next_test();
+      });
+    });
+  });
+});
+
+// Test deleting power stats via clearStats.
+add_test(function test_clearPowerStats() {
+  var appId = 0;
+  var manifestURL = "";
+  var serviceType = "";
+  var component = "";
+
+  // Step 1: genrate data saved to power store.
+  var { record: record, result: result } =
+    generatePowerRecord(appId, serviceType, powerComponents);
+  var recordArray = [record];
+  var timestamp = Date.now();
+  var end = normalizeTime(timestamp) + offset;
+  var start = end;
+
+  // Step 2: save record and prepare power store.
+  preparePowerStatsStore(recordArray, timestamp, function(error, callback) {
+    // Step 3: clear power stats.
+    db.clearStats("power", appId, serviceType, component, start, end,
+      function(error, result) {
+      do_check_eq(error, null);
+
+      // Step 4: check if the stats is deleted.
+      db.getStats("power", manifestURL, serviceType, component, start, end,
+        function(error, result) {
+        do_check_eq(result.statsData.length, 0);
+        run_next_test();
+      });
+    });
+  });
+});
+
+// Test clearing all network stats.
+add_test(function test_clearAllNetworkStats() {
+  db.clearAllStats("network", function(error, result) {
+    do_check_eq(error, null);
+    run_next_test();
+  });
+});
+
+// Test clearing all power stats.
+add_test(function test_clearAllPowerStats() {
+  db.clearAllStats("power", function(error, result) {
+    do_check_eq(error, null);
+    run_next_test();
+  });
+});
+
+// Test getting network components.
+add_test(function test_getNetworkComponents() {
+  var appId = 0;
+  var serviceType = "";
+
+  // Step 1: generate data saved to store.
+  var { record: record, result: expectedResult } =
+    generateNetworkRecord(appId, serviceType, networkComponents);
+  var recordArray = [record];
+  var timestamp = Date.now();
+
+  // Step 2: save recordArray to network store.
+  prepareNetworkStatsStore(recordArray, timestamp, function(error, callback) {
+    // Step 3: call getComponents.
+    db.getComponents("network", function(error, result) {
+      do_check_eq(error, null);
+      do_check_true(Array.isArray(result));
+      do_check_eq(result.length, networkComponents.length);
+
+      // Check each element in result array is an element of networkComponents.
+      result.forEach(function(component) {
+        do_check_true(networkComponents.indexOf(component) > -1);
+      });
+
+      run_next_test(); // If success, run next test.
+    });
+  });
+});
+
+// Test getting power components.
+add_test(function test_getPowerComponents() {
+  var appId = 0;
+  var serviceType = "";
+
+  // Step 1: generate data saved to store.
+  var { record: record, result: expectedResult } =
+    generatePowerRecord(appId, serviceType, powerComponents);
+  var recordArray = [record];
+  var timestamp = Date.now();
+
+  // Step 2: save recordArray to power store.
+  preparePowerStatsStore(recordArray, timestamp, function(error, callback) {
+    // Step 3: call getComponents.
+    db.getComponents("power", function(error, result) {
+      do_check_eq(error, null);
+      do_check_true(Array.isArray(result));
+      do_check_eq(result.length, powerComponents.length);
+
+      // Check each element in result array is an element of powerComponents.
+      result.forEach(function(component) {
+        do_check_true(powerComponents.indexOf(component) > -1);
+      });
+
+      run_next_test(); // If success, run next test.
+    });
+  });
+});
+
+// Generate alarm object for addAlarm().
+function generateAlarmObject(aType, aManifestURL, aServiceType, aComponent) {
+  let alarm = {
+    type: aType,
+    component: aComponent,
+    serviceType: aServiceType,
+    manifestURL: aManifestURL,
+    threshold: Math.floor(Math.random() * 1000),
+    startTime: Math.floor(Math.random() * 1000),
+    data: null
+  };
+
+  return alarm;
+}
+
+// Test adding a network alarm.
+add_test(function test_addNetowrkAlarm() {
+  var manifestURL = "";
+  var serviceType = "";
+
+  // Step 1: generate a network alarm.
+  var alarm =
+    generateAlarmObject("network", manifestURL, serviceType, wifiComponent);
+
+  // Step 2: clear store.
+  clearStore("alarm_store", function() {
+    // Step 3: save the alarm to store.
+    db.addAlarm(alarm, function(error, result) {
+      // Step 4: check if the function call succeed.
+      do_check_eq(error, null);
+      do_check_true(result > -1);
+      let alarmId = result;
+
+      // Step 5: dump store for comparison.
+      dumpStore("alarm_store", function(error, result) {
+        do_check_eq(error, null);
+        do_check_true(Array.isArray(result));
+        do_check_true(result.length == 1);
+        do_check_eq(result[0].type, alarm.type);
+        do_check_eq(result[0].manifestURL, alarm.manifestURL);
+        do_check_eq(result[0].serviceType, alarm.serviceType);
+        do_check_eq(result[0].component, alarm.component);
+        do_check_eq(result[0].threshold, alarm.threshold);
+        do_check_eq(result[0].startTime, alarm.startTime);
+        do_check_eq(result[0].data, alarm.data);
+        do_check_eq(result[0].alarmId, alarmId);
+
+        run_next_test(); // If success, run next test.
+      });
+    });
+  });
+});
+
+// Test adding a power alarm.
+add_test(function test_addPowerAlarm() {
+  var manifestURL = "";
+  var serviceType = "";
+
+  // Step 1: generate a power alarm.
+  var alarm =
+    generateAlarmObject("power", manifestURL, serviceType, cpuComponent);
+
+  // Step 2: clear store.
+  clearStore("alarm_store", function() {
+    // Step 3: save the alarm to store.
+    db.addAlarm(alarm, function(error, result) {
+      // Step 4: check if the function call succeed.
+      do_check_eq(error, null);
+      do_check_true(result > -1);
+      let alarmId = result;
+
+      // Step 5: dump store for comparison.
+      dumpStore("alarm_store", function(error, result) {
+        do_check_eq(error, null);
+        do_check_true(Array.isArray(result));
+        do_check_true(result.length == 1);
+        do_check_eq(result[0].type, alarm.type);
+        do_check_eq(result[0].manifestURL, alarm.manifestURL);
+        do_check_eq(result[0].serviceType, alarm.serviceType);
+        do_check_eq(result[0].component, alarm.component);
+        do_check_eq(result[0].threshold, alarm.threshold);
+        do_check_eq(result[0].startTime, alarm.startTime);
+        do_check_eq(result[0].data, alarm.data);
+        do_check_eq(result[0].alarmId, alarmId);
+
+        run_next_test(); // If success, run next test.
+      });
+    });
+  });
+});
+
+// Add multiple alarms to store and record the obtained alarmId in each
+// alarm object.
+function addAlarmsToStore(alarms, index, callback) {
+  var alarm = alarms[index++];
+  if (index < alarms.length) {
+    db.addAlarm(alarm, function(error, result) {
+      alarm.alarmId = result;
+      addAlarmsToStore(alarms, index, callback);
+    });
+  } else {
+    db.addAlarm(alarm, function(error, result) {
+      alarm.alarmId = result;
+      callback(error, result);
+    });
+  }
+}
+
+// Prepare alarm store for testging.
+function prepareAlarmStore(alarms, callback) {
+  // Step 1: clear srore.
+  clearStore("alarm_store", function() {
+    // Step 2: save alarms to store one by one.
+    addAlarmsToStore(alarms, 0, callback);
+  });
+}
+
+// Compare alrams returned by getAlarms().
+function compareAlarms(aExpectedResult, aResult) {
+  // Step 1: a quick check for the length of arrays first.
+  do_check_eq(aExpectedResult.length, aResult.length);
+
+  // Step 2: create a map array for search by threshold.
+  var mapArray = aExpectedResult.map(function(e) {return e.threshold;});
+
+  // Step 3: compare each element to make sure both array are equal.
+  var index;
+  var target;
+
+  aResult.forEach(function(alarm) {
+    index = 0;
+    // Find the first equal receivedBytes since index.
+    while ((index = mapArray.indexOf(alarm.threshold, index)) > -1) {
+      // Compare all attributes.
+      target = aExpectedResult[index];
+      if (target.alarmId != alarm.alarmId ||
+          target.type != alarm.type ||
+          target.manifestURL != alarm.manifestURL ||
+          target.serviceType != alarm.serviceType ||
+          target.component != alarm.component ||
+          target.data != alarm.data) {
+        index += 1;
+        continue;
+      } else {
+        // If found, remove that element from aExpectedResult and mapArray.
+        aExpectedResult.splice(index, 1);
+        mapArray.splice(index, 1);
+        break;
+      }
+    }
+    do_check_neq(index, -1);
+  });
+  run_next_test();
+}
+
+// Test getting designate network alarms from store.
+add_test(function test_getNetworkAlarms() {
+  var manifestURL = "";
+  var serviceType = "";
+  var alarms = [];
+
+  // Step 1: generate two network alarms using same parameters.
+  alarms.push(generateAlarmObject("network", manifestURL, serviceType,
+                                  wifiComponent));
+  alarms.push(generateAlarmObject("network", manifestURL, serviceType,
+                                  wifiComponent));
+
+  // Step 2: generate another network alarm using different parameters.
+  alarms.push(generateAlarmObject("network", manifestURL, serviceType,
+                                  mobileComponent));
+
+  // Step 3: clear alarm store and save new alarms to store.
+  prepareAlarmStore(alarms, function(error, result) {
+    // Step 4: call getAlarms.
+    let options = {
+      manifestURL: manifestURL,
+      serviceType: serviceType,
+      component: wifiComponent
+    };
+    db.getAlarms("network", options, function(error, result) {
+      // Step 5: check if the function call succeed.
+      do_check_eq(error, null);
+
+      // Step 6: check results.
+      // The last element in alarms array is not our expected result,
+      // so pop that out first.
+      alarms.pop();
+      compareAlarms(alarms, result);
+    });
+  });
+});
+
+// Test getting designate power alarms from store.
+add_test(function test_getPowerAlarms() {
+  var manifestURL = "";
+  var serviceType = "";
+  var alarms = [];
+
+  // Step 1: generate two power alarms using same parameters.
+  alarms.push(generateAlarmObject("power", manifestURL, serviceType,
+                                  cpuComponent));
+  alarms.push(generateAlarmObject("power", manifestURL, serviceType,
+                                  cpuComponent));
+
+  // Step 2: generate another power alarm using different parameters.
+  alarms.push(generateAlarmObject("power", manifestURL, serviceType,
+                                  gpsComponent));
+
+  // Step 3: clear alarm store and save new alarms to store.
+  prepareAlarmStore(alarms, function(error, result) {
+    // Step 4: call getAlarms.
+    let options = {
+      manifestURL: manifestURL,
+      serviceType: serviceType,
+      component: cpuComponent
+    };
+    db.getAlarms("power", options, function(error, result) {
+      // Step 5: check if the function call succeed.
+      do_check_eq(error, null);
+
+      // Step 6: check results.
+      // The last element in alarms array is not our expected result,
+      // so pop that out first.
+      alarms.pop();
+      compareAlarms(alarms, result);
+    });
+  });
+});
+
+// Test getting all network alarms from store.
+add_test(function test_getAllNetworkAlarms() {
+  var manifestURL = "";
+  var serviceType = "";
+  var alarms = [];
+
+  // Step 1: generate two network alarms.
+  alarms.push(generateAlarmObject("network", manifestURL, serviceType,
+                                  wifiComponent));
+  alarms.push(generateAlarmObject("network", manifestURL, serviceType,
+                                  mobileComponent));
+
+  // Step 2: generate another power alarm.
+  alarms.push(generateAlarmObject("power", manifestURL, serviceType,
+                                  cpuComponent));
+
+  // Step 3: clear alarm store and save new alarms to store.
+  prepareAlarmStore(alarms, function(error, result) {
+    // Step 4: call getAlarms.
+    let options = null;
+    db.getAlarms("network", options, function(error, result) {
+      // Step 5: check if the function call succeed.
+      do_check_eq(error, null);
+
+      // Step 6: check results.
+      // The last element in alarms array is not our expected result,
+      // so pop that out first.
+      alarms.pop();
+      compareAlarms(alarms, result);
+    });
+  });
+});
+
+// Test getting all power alarms from store.
+add_test(function test_getAllPowerAlarms() {
+  var manifestURL = "";
+  var serviceType = "";
+  var alarms = [];
+
+  // Step 1: generate two power alarms.
+  alarms.push(generateAlarmObject("power", manifestURL, serviceType,
+                                  cpuComponent));
+  alarms.push(generateAlarmObject("power", manifestURL, serviceType,
+                                  gpsComponent));
+
+  // Step 2: generate another network alarm.
+  alarms.push(generateAlarmObject("network", manifestURL, serviceType,
+                                  wifiComponent));
+
+  // Step 3: clear alarm store and save new alarms to store.
+  prepareAlarmStore(alarms, function(error, result) {
+    // Step 4: call getAlarms.
+    let options = null;
+    db.getAlarms("power", options, function(error, result) {
+      // Step 5: check if the function call succeed.
+      do_check_eq(error, null);
+
+      // Step 6: check results.
+      // The last element in alarms array is not our expected result,
+      // so pop that out first.
+      alarms.pop();
+      compareAlarms(alarms, result);
+    });
+  });
+});
+
+// Test removing designate network alarm from store.
+add_test(function test_removeNetworkAlarm() {
+  var manifestURL = "";
+  var serviceType = "";
+  var alarms = [];
+
+  // Step 1: generate one network alarm.
+  alarms.push(generateAlarmObject("network", manifestURL, serviceType,
+                                  wifiComponent));
+
+  // Step 2: clear alarm store and save new alarms to store.
+  prepareAlarmStore(alarms, function(error, result) {
+    var alarmId = result;
+    // Step 3: remove the alarm.
+    db.removeAlarm("network", alarmId, function(error, result) {
+      // Step 4: check if the function call succeed.
+      do_check_eq(result, true);
+
+      // Step 5: dump store to check if the alarm is removed.
+      dumpStore("alarm_store", function(error, result) {
+        do_check_eq(error, null);
+        do_check_true(Array.isArray(result));
+        do_check_true(result.length === 0);
+
+        run_next_test(); // If success, run next test.
+      });
+    });
+  });
+});
+
+// Test removing designate power alarm from store.
+add_test(function test_removePowerAlarm() {
+  var manifestURL = "";
+  var serviceType = "";
+  var alarms = [];
+
+  // Step 1: generate one power alarm.
+  alarms.push(generateAlarmObject("power", manifestURL, serviceType,
+                                  cpuComponent));
+
+  // Step 2: clear alarm store and save new alarms to store.
+  prepareAlarmStore(alarms, function(error, result) {
+    var alarmId = result;
+    // Step 3: remove the alarm.
+    db.removeAlarm("power", alarmId, function(error, result) {
+      // Step 4: check if the function call succeed.
+      do_check_eq(result, true);
+
+      // Step 5: dump store to check if the alarm is removed.
+      dumpStore("alarm_store", function(error, result) {
+        do_check_eq(error, null);
+        do_check_true(Array.isArray(result));
+        do_check_true(result.length === 0);
+
+        run_next_test(); // If success, run next test.
+      });
+    });
+  });
+});
+
+// Test removing designate network alarm from store.
+add_test(function test_removeAllNetworkAlarms() {
+  var manifestURL = "";
+  var serviceType = "";
+  var alarms = [];
+
+  // Step 1: generate two network alarms.
+  alarms.push(generateAlarmObject("network", manifestURL, serviceType,
+                                  wifiComponent));
+  alarms.push(generateAlarmObject("network", manifestURL, serviceType,
+                                  mobileComponent));
+
+  // Step 2: generate another power alarm.
+  alarms.push(generateAlarmObject("power", manifestURL, serviceType,
+                                  cpuComponent));
+
+  // Step 3: clear alarm store and save new alarms to store.
+  prepareAlarmStore(alarms, function(error, result) {
+    // Step 4: remove all network alarms.
+    db.removeAllAlarms("network", function(error, result) {
+      // Step 5: check if the function call succeed.
+      do_check_eq(error, null);
+
+      // Step 6: dump store for comparison.
+      // Because the power alarm should not be removed, so it would be the
+      // only result returned by dumpStore.
+      var alarm = alarms.pop(); // The expected result.
+      dumpStore("alarm_store", function(error, result) {
+        do_check_eq(error, null);
+        do_check_true(Array.isArray(result));
+        do_check_true(result.length == 1);
+        do_check_eq(result[0].type, alarm.type);
+        do_check_eq(result[0].manifestURL, alarm.manifestURL);
+        do_check_eq(result[0].serviceType, alarm.serviceType);
+        do_check_eq(result[0].component, alarm.component);
+        do_check_eq(result[0].threshold, alarm.threshold);
+        do_check_eq(result[0].startTime, alarm.startTime);
+        do_check_eq(result[0].data, alarm.data);
+        do_check_eq(result[0].alarmId, alarm.alarmId);
+
+        run_next_test(); // If success, run next test.
+      });
+    });
+  });
+});
+
+// Test removing designate power alarm from store.
+add_test(function test_removeAllPowerAlarms() {
+  var manifestURL = "";
+  var serviceType = "";
+  var alarms = [];
+
+  // Step 1: generate two power alarms.
+  alarms.push(generateAlarmObject("power", manifestURL, serviceType,
+                                  cpuComponent));
+  alarms.push(generateAlarmObject("power", manifestURL, serviceType,
+                                  gpsComponent));
+
+  // Step 2: generate another network alarm.
+  alarms.push(generateAlarmObject("network", manifestURL, serviceType,
+                                  wifiComponent));
+
+  // Step 3: clear alarm store and save new alarms to store.
+  prepareAlarmStore(alarms, function(error, result) {
+    // Step 4: remove all power alarms.
+    db.removeAllAlarms("power", function(error, result) {
+      // Step 5: check if the function call succeed.
+      do_check_eq(error, null);
+
+      // Step 6: dump store for comparison.
+      // Because the network alarm should not be removed, so it would be the
+      // only result returned by dumpStore.
+      var alarm = alarms.pop(); // The expected result.
+      dumpStore("alarm_store", function(error, result) {
+        do_check_eq(error, null);
+        do_check_true(Array.isArray(result));
+        do_check_true(result.length == 1);
+        do_check_eq(result[0].type, alarm.type);
+        do_check_eq(result[0].manifestURL, alarm.manifestURL);
+        do_check_eq(result[0].serviceType, alarm.serviceType);
+        do_check_eq(result[0].component, alarm.component);
+        do_check_eq(result[0].threshold, alarm.threshold);
+        do_check_eq(result[0].startTime, alarm.startTime);
+        do_check_eq(result[0].data, alarm.data);
+        do_check_eq(result[0].alarmId, alarm.alarmId);
+
+        run_next_test(); // If success, run next test.
+      });
+    });
+  });
+});
+
+function run_test() {
+  do_get_profile();
+  run_next_test();
+}
new file mode 100644
--- /dev/null
+++ b/dom/resourcestats/tests/xpcshell/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head =
+tail =
+
+[test_resourcestats_db.js]
--- a/testing/xpcshell/xpcshell_b2g.ini
+++ b/testing/xpcshell/xpcshell_b2g.ini
@@ -1,15 +1,16 @@
 ; This Source Code Form is subject to the terms of the Mozilla Public
 ; License, v. 2.0. If a copy of the MPL was not distributed with this
 ; file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [include:dom/apps/tests/unit/xpcshell.ini]
 [include:dom/mobilemessage/tests/xpcshell/xpcshell.ini]
 [include:dom/network/tests/unit_stats/xpcshell.ini]
+[include:dom/resourcestats/tests/xpcshell/xpcshell.ini]
 [include:dom/system/gonk/tests/xpcshell.ini]
 [include:dom/wappush/tests/xpcshell.ini]
 [include:toolkit/components/osfile/tests/xpcshell/xpcshell.ini]
 [include:toolkit/components/captivedetect/test/unit/xpcshell.ini]
 [include:toolkit/devtools/apps/tests/unit/xpcshell.ini]
 [include:toolkit/devtools/debugger/tests/unit/xpcshell.ini]
 [include:toolkit/devtools/qrcode/tests/unit/xpcshell.ini]
 [include:toolkit/devtools/sourcemap/tests/unit/xpcshell.ini]