# Copyright 2017 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Unit tests for StatsManager.""" from __future__ import print_function import json import os import re import shutil import tempfile import unittest import stats_manager # pylint:disable=import-error class TestStatsManager(unittest.TestCase): """Test to verify StatsManager methods work as expected. StatsManager should collect raw data, calculate their statistics, and save them in expected format. """ def _populate_mock_stats(self): """Create a populated & processed StatsManager to test data retrieval.""" self.data.AddSample("A", 99999.5) self.data.AddSample("A", 100000.5) self.data.SetUnit("A", "uW") self.data.SetUnit("A", "mW") self.data.AddSample("B", 1.5) self.data.AddSample("B", 2.5) self.data.AddSample("B", 3.5) self.data.SetUnit("B", "mV") self.data.CalculateStats() def _populate_mock_stats_no_unit(self): self.data.AddSample("B", 1000) self.data.AddSample("A", 200) self.data.SetUnit("A", "blue") def setUp(self): """Set up StatsManager and create a temporary directory for test.""" self.tempdir = tempfile.mkdtemp() self.data = stats_manager.StatsManager() def tearDown(self): """Delete the temporary directory and its content.""" shutil.rmtree(self.tempdir) def test_AddSample(self): """Adding a sample successfully adds a sample.""" self.data.AddSample("Test", 1000) self.data.SetUnit("Test", "test") self.data.CalculateStats() summary = self.data.GetSummary() self.assertEqual(1, summary["Test"]["count"]) def test_AddSampleNoFloatAcceptNaN(self): """Adding a non-number adds 'NaN' and doesn't raise an exception.""" self.data.AddSample("Test", 10) self.data.AddSample("Test", 20) # adding a fake NaN: one that gets converted into NaN internally self.data.AddSample("Test", "fiesta") # adding a real NaN self.data.AddSample("Test", float("NaN")) self.data.SetUnit("Test", "test") self.data.CalculateStats() summary = self.data.GetSummary() # assert that 'NaN' as added. self.assertEqual(4, summary["Test"]["count"]) # assert that mean, min, and max calculatings ignore the 'NaN' self.assertEqual(10, summary["Test"]["min"]) self.assertEqual(20, summary["Test"]["max"]) self.assertEqual(15, summary["Test"]["mean"]) def test_AddSampleNoFloatNotAcceptNaN(self): """Adding a non-number raises a StatsManagerError if accept_nan is False.""" self.data = stats_manager.StatsManager(accept_nan=False) with self.assertRaisesRegexp( stats_manager.StatsManagerError, "accept_nan is false. Cannot add NaN sample.", ): # adding a fake NaN: one that gets converted into NaN internally self.data.AddSample("Test", "fiesta") with self.assertRaisesRegexp( stats_manager.StatsManagerError, "accept_nan is false. Cannot add NaN sample.", ): # adding a real NaN self.data.AddSample("Test", float("NaN")) def test_AddSampleNoUnit(self): """Not adding a unit does not cause an exception on CalculateStats().""" self.data.AddSample("Test", 17) self.data.CalculateStats() summary = self.data.GetSummary() self.assertEqual(1, summary["Test"]["count"]) def test_UnitSuffix(self): """Unit gets appended as a suffix in the displayed summary.""" self.data.AddSample("test", 250) self.data.SetUnit("test", "mw") self.data.CalculateStats() summary_str = self.data.SummaryToString() self.assertIn("test_mw", summary_str) def test_DoubleUnitSuffix(self): """If domain already ends in unit, verify that unit doesn't get appended.""" self.data.AddSample("test_mw", 250) self.data.SetUnit("test_mw", "mw") self.data.CalculateStats() summary_str = self.data.SummaryToString() self.assertIn("test_mw", summary_str) self.assertNotIn("test_mw_mw", summary_str) def test_GetRawData(self): """GetRawData returns exact same data as fed in.""" self._populate_mock_stats() raw_data = self.data.GetRawData() self.assertListEqual([99999.5, 100000.5], raw_data["A"]) self.assertListEqual([1.5, 2.5, 3.5], raw_data["B"]) def test_GetSummary(self): """GetSummary returns expected stats about the data fed in.""" self._populate_mock_stats() summary = self.data.GetSummary() self.assertEqual(2, summary["A"]["count"]) self.assertAlmostEqual(100000.5, summary["A"]["max"]) self.assertAlmostEqual(99999.5, summary["A"]["min"]) self.assertAlmostEqual(0.5, summary["A"]["stddev"]) self.assertAlmostEqual(100000.0, summary["A"]["mean"]) self.assertEqual(3, summary["B"]["count"]) self.assertAlmostEqual(3.5, summary["B"]["max"]) self.assertAlmostEqual(1.5, summary["B"]["min"]) self.assertAlmostEqual(0.81649658092773, summary["B"]["stddev"]) self.assertAlmostEqual(2.5, summary["B"]["mean"]) def test_SaveRawData(self): """SaveRawData stores same data as fed in.""" self._populate_mock_stats() dirname = "unittest_raw_data" expected_files = set(["A_mW.txt", "B_mV.txt"]) fnames = self.data.SaveRawData(self.tempdir, dirname) files_returned = set([os.path.basename(f) for f in fnames]) # Assert that only the expected files got returned. self.assertEqual(expected_files, files_returned) # Assert that only the returned files are in the outdir. self.assertEqual( set(os.listdir(os.path.join(self.tempdir, dirname))), files_returned ) for fname in fnames: with open(fname, "r") as f: if "A_mW" in fname: self.assertEqual("99999.50", f.readline().strip()) self.assertEqual("100000.50", f.readline().strip()) if "B_mV" in fname: self.assertEqual("1.50", f.readline().strip()) self.assertEqual("2.50", f.readline().strip()) self.assertEqual("3.50", f.readline().strip()) def test_SaveRawDataNoUnit(self): """SaveRawData appends no unit suffix if the unit is not specified.""" self._populate_mock_stats_no_unit() self.data.CalculateStats() outdir = "unittest_raw_data" files = self.data.SaveRawData(self.tempdir, outdir) files = [os.path.basename(f) for f in files] # Verify nothing gets appended to domain for filename if no unit exists. self.assertIn("B.txt", files) def test_SaveRawDataSMID(self): """SaveRawData uses the smid when creating output filename.""" identifier = "ec" self.data = stats_manager.StatsManager(smid=identifier) self._populate_mock_stats() files = self.data.SaveRawData(self.tempdir) for fname in files: self.assertTrue(os.path.basename(fname).startswith(identifier)) def test_SummaryToStringNaNHelp(self): """NaN containing row gets tagged with *, help banner gets added.""" help_banner_exp = "%s %s" % ( stats_manager.STATS_PREFIX, stats_manager.NAN_DESCRIPTION, ) nan_domain = "A-domain" nan_domain_exp = "%s%s" % (nan_domain, stats_manager.NAN_TAG) # NaN helper banner is added when a NaN domain is found & domain gets tagged data = stats_manager.StatsManager() data.AddSample(nan_domain, float("NaN")) data.AddSample(nan_domain, 17) data.AddSample("B-domain", 17) data.CalculateStats() summarystr = data.SummaryToString() self.assertIn(help_banner_exp, summarystr) self.assertIn(nan_domain_exp, summarystr) # NaN helper banner is not added when no NaN domain output, no tagging data = stats_manager.StatsManager() # nan_domain in this scenario does not contain any NaN data.AddSample(nan_domain, 19) data.AddSample("B-domain", 17) data.CalculateStats() summarystr = data.SummaryToString() self.assertNotIn(help_banner_exp, summarystr) self.assertNotIn(nan_domain_exp, summarystr) def test_SummaryToStringTitle(self): """Title shows up in SummaryToString if title specified.""" title = "titulo" data = stats_manager.StatsManager(title=title) self._populate_mock_stats() summary_str = data.SummaryToString() self.assertIn(title, summary_str) def test_SummaryToStringHideDomains(self): """Keys indicated in hide_domains are not printed in the summary.""" data = stats_manager.StatsManager(hide_domains=["A-domain"]) data.AddSample("A-domain", 17) data.AddSample("B-domain", 17) data.CalculateStats() summary_str = data.SummaryToString() self.assertIn("B-domain", summary_str) self.assertNotIn("A-domain", summary_str) def test_SummaryToStringOrder(self): """Order passed into StatsManager is honoured when formatting summary.""" # StatsManager that should print D & B first, and the subsequent elements # are sorted. d_b_a_c_regexp = re.compile( "D-domain.*B-domain.*A-domain.*C-domain", re.DOTALL ) data = stats_manager.StatsManager(order=["D-domain", "B-domain"]) data.AddSample("A-domain", 17) data.AddSample("B-domain", 17) data.AddSample("C-domain", 17) data.AddSample("D-domain", 17) data.CalculateStats() summary_str = data.SummaryToString() self.assertRegexpMatches(summary_str, d_b_a_c_regexp) def test_MakeUniqueFName(self): data = stats_manager.StatsManager() testfile = os.path.join(self.tempdir, "testfile.txt") with open(testfile, "w") as f: f.write("") expected_fname = os.path.join(self.tempdir, "testfile0.txt") self.assertEqual(expected_fname, data._MakeUniqueFName(testfile)) def test_SaveSummary(self): """SaveSummary properly dumps the summary into a file.""" self._populate_mock_stats() fname = "unittest_summary.txt" expected_fname = os.path.join(self.tempdir, fname) fname = self.data.SaveSummary(self.tempdir, fname) # Assert the reported fname is the same as the expected fname self.assertEqual(expected_fname, fname) # Assert only the reported fname is output (in the tempdir) self.assertEqual( set([os.path.basename(fname)]), set(os.listdir(self.tempdir)) ) with open(fname, "r") as f: self.assertEqual( "@@ NAME COUNT MEAN STDDEV MAX MIN\n", f.readline(), ) self.assertEqual( "@@ A_mW 2 100000.00 0.50 100000.50 99999.50\n", f.readline(), ) self.assertEqual( "@@ B_mV 3 2.50 0.82 3.50 1.50\n", f.readline(), ) def test_SaveSummarySMID(self): """SaveSummary uses the smid when creating output filename.""" identifier = "ec" self.data = stats_manager.StatsManager(smid=identifier) self._populate_mock_stats() fname = os.path.basename(self.data.SaveSummary(self.tempdir)) self.assertTrue(fname.startswith(identifier)) def test_SaveSummaryJSON(self): """SaveSummaryJSON saves the added data properly in JSON format.""" self._populate_mock_stats() fname = "unittest_summary.json" expected_fname = os.path.join(self.tempdir, fname) fname = self.data.SaveSummaryJSON(self.tempdir, fname) # Assert the reported fname is the same as the expected fname self.assertEqual(expected_fname, fname) # Assert only the reported fname is output (in the tempdir) self.assertEqual( set([os.path.basename(fname)]), set(os.listdir(self.tempdir)) ) with open(fname, "r") as f: summary = json.load(f) self.assertAlmostEqual(100000.0, summary["A"]["mean"]) self.assertEqual("milliwatt", summary["A"]["unit"]) self.assertAlmostEqual(2.5, summary["B"]["mean"]) self.assertEqual("millivolt", summary["B"]["unit"]) def test_SaveSummaryJSONSMID(self): """SaveSummaryJSON uses the smid when creating output filename.""" identifier = "ec" self.data = stats_manager.StatsManager(smid=identifier) self._populate_mock_stats() fname = os.path.basename(self.data.SaveSummaryJSON(self.tempdir)) self.assertTrue(fname.startswith(identifier)) def test_SaveSummaryJSONNoUnit(self): """SaveSummaryJSON marks unknown units properly as N/A.""" self._populate_mock_stats_no_unit() self.data.CalculateStats() fname = "unittest_summary.json" fname = self.data.SaveSummaryJSON(self.tempdir, fname) with open(fname, "r") as f: summary = json.load(f) self.assertEqual("blue", summary["A"]["unit"]) # if no unit is specified, JSON should save 'N/A' as the unit. self.assertEqual("N/A", summary["B"]["unit"]) if __name__ == "__main__": unittest.main()