blob: fd29218bffc88025b51ba7553a554ef3564b0aa5 [file] [log] [blame] [edit]
# 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
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_placeholder_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_placeholder_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_placeholder_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_placeholder_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_placeholder_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_placeholder_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_placeholder_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_placeholder_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_placeholder_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_placeholder_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_placeholder_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_placeholder_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_placeholder_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()