# Copyright (C) 2015 Chintalagiri Shashank
#
# This file is part of Tendril.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This file is part of tendril
See the COPYING, README, and INSTALL files for more information
"""
import os
import arrow
import json
from collections import namedtuple
from tendril.utils.fsutils import TEMPDIR
from tendril.utils.fsutils import get_tempname
from colorama import Fore
from tendril.utils import terminal
from tendril.utils import log
logger = log.get_logger(__name__, log.INFO)
TestLine = namedtuple('TestLine', ['desc', 'expected', 'measured'])
[docs]class TestPrepBase(object):
""" Object representing a preparatory step for a Test """
def __init__(self):
self._parent = None
@property
def parent(self):
return self._parent
@parent.setter
def parent(self, value):
self._parent = value
[docs] def run_prep(self):
raise NotImplementedError
[docs]class TestPrepUser(TestPrepBase):
def __init__(self, string):
super(TestPrepUser, self).__init__()
self._string = string
[docs] def run_prep(self):
print Fore.YELLOW + self._string + Fore.RESET
raw_input(Fore.YELLOW +
"Press Enter to continue..." +
Fore.RESET)
[docs]class RunnableTest(object):
def __init__(self):
self._serialno = None
self._parent = None
self._desc = None
self._title = None
self._ts = None
self._dummy = None
@property
def parent(self):
return self._parent
@parent.setter
def parent(self, value):
self._parent = value
@property
def dummy(self):
if self._dummy is not None:
return self._dummy
else:
if self._parent is not None:
return self._parent.dummy
else:
return False
@dummy.setter
def dummy(self, value):
self._dummy = value
@property
def serialno(self):
# override_sno = "QTJDT7SL"
# return override_sno
if self._serialno is not None:
return self._serialno
else:
return self._parent.serialno
@serialno.setter
def serialno(self, value):
self._serialno = value
[docs] def run_test(self):
raise NotImplementedError
@property
def passed(self):
raise NotImplementedError
[docs] def render(self):
raise NotImplementedError
[docs] def finish(self):
logger.info(repr(self) + " :: Result : " + str(self.passed))
[docs] def destroy(self):
raise NotImplementedError
@property
def desc(self):
return self._desc
@desc.setter
def desc(self, value):
self._desc = value
@property
def title(self):
return self._title
@title.setter
def title(self, value):
self._title = value
@property
def ts(self):
return self._ts
@ts.setter
def ts(self, value):
if isinstance(value, arrow.Arrow):
self._ts = value
else:
self._ts = arrow.get(value)
def __repr__(self):
return "<{0} {1}>".format(self.__class__, self.desc)
[docs]class TestBase(RunnableTest):
"""
Object representing a full runnable Test of the same Measurement type
"""
def __init__(self, offline=False):
super(TestBase, self).__init__()
self._prep = []
self._measurements = []
self._result = None
self.variables = {}
self._bom_object = None
self._offline = offline
self._inststr = None
self._passfailonly = False
@property
def offline(self):
return self._offline
[docs] def add_measurement(self, measurement):
measurement.parent = self
self._measurements.append(measurement)
return measurement
[docs] def run_test(self):
logger.debug("Running Test : " + repr(self))
if self._offline is True:
raise IOError("Cannot run an offline test")
for prep in self._prep:
prep.run_prep()
for measurement in self._measurements:
measurement.do_measurement()
self.ts = arrow.utcnow()
[docs] def _load_variable(self, name, typeclass, default=None):
if name not in self.variables.keys():
assert isinstance(default, typeclass)
return default
if isinstance(self.variables[name], typeclass):
return self.variables[name]
try:
if isinstance(self.variables[name], typeclass):
return self.variables[name]
rv = typeclass(self.variables[name])
return rv
except ValueError:
value = self.variables[name]
if ':' in value:
motif_refdes, elem = value.split(':')
motif = self._bom_object.get_motif_by_refdes(motif_refdes)
try:
value = getattr(motif, elem)
except (TypeError, AttributeError):
logger.error("Error getting element " + repr(elem) +
" from Motif " + repr(motif) +
", required by " + value)
logger.error("Bomobject configured for " +
self._bom_object.configured_for)
raise
if isinstance(value, typeclass):
return value
value = typeclass(value)
except KeyError:
raise
return value
[docs] def use_bom(self, bomobj):
self._bom_object = bomobj
[docs] def add_prep(self, prep):
prep.parent = self
self._prep.append(prep)
@property
def passed(self):
raise NotImplementedError
@property
def render(self):
raise NotImplementedError
[docs] def render_dox(self):
gd = [{'x': x, 'y': y, 'title': t, 'params': p} for x, y, p, t in self.graphs_data]
graphs_data = json.dumps(gd)
test_dict = {'desc': self.desc,
'title': self.title,
'ts': self.ts.format(),
'passed':
('PASSED' if self.passed is True else 'FAILED'),
'measurements':
[x.render_dox() for x in self._measurements],
'instrument': self._inststr,
'lines': self.lines,
'passfailonly': self.passfailonly,
'graphs_data': graphs_data,
}
return test_dict
@property
def passfailonly(self):
try:
return self._passfailonly
except AttributeError:
return False
@passfailonly.setter
def passfailonly(self, value):
self._passfailonly = value
@property
def lines(self):
return []
@property
def graphs(self):
return self._make_graphs()
@property
def histograms(self):
return self._make_histograms()
@staticmethod
[docs] def get_new_graph_path():
return os.path.join(TEMPDIR, 'graph' + get_tempname() + '.png')
@staticmethod
[docs] def _make_graph(*args, **kwargs):
from tendril.dox.render import make_graph
return make_graph(*args, **kwargs)
@staticmethod
[docs] def _make_histogram(*args, **kwargs):
from tendril.dox.render import make_histogram
return make_histogram(*args, **kwargs)
[docs] def _get_graphs_data(self):
return []
[docs] def _get_histograms_data(self):
return []
@property
def graphs_data(self):
return self._get_graphs_data()
@property
def histograms_data(self):
return self._get_histograms_data()
[docs] def _make_graphs(self):
rval = []
for plotdata_x, plotdata_y, params, title in self._get_graphs_data():
plt_path = self.get_new_graph_path()
self._make_graph(plt_path, plotdata_y, plotdata_x, **params)
rval.append((plt_path, self.desc + ' ' + title))
return rval
[docs] def _make_histograms(self):
rval = []
for plotdata_y, params, title in self._get_histograms_data():
plt_path = self.get_new_graph_path()
self._make_histogram(plt_path, plotdata_y, **params)
rval.append((plt_path, self.desc + ' ' + title))
return rval
@staticmethod
[docs] def _pr_repr(string):
if string[0] == string[-1] == "'":
return string[1:-1]
else:
return string
[docs] def load_result_from_obj(self, result_db_obj):
raise NotImplementedError
[docs] def finish(self):
if self.passed is True:
result = Fore.GREEN + '[PASSED]' + Fore.RESET
else:
result = Fore.RED + '[FAILED]' + Fore.RESET
width = terminal.get_terminal_width()
hline = '-' * 80
print Fore.YELLOW + hline + Fore.RESET
fstring = "{0}{1:<" + str(width-10) + "}{2} {3:>9}"
print fstring.format(
Fore.YELLOW, (self.desc or 'None'), Fore.WHITE, result
)
print "{0}".format(self.title)
print "{0}".format(repr(self))
[docs] def destroy(self):
pass
[docs]class TestSuiteBase(RunnableTest):
""" Object representing a full runnable Test Suite on a single entity """
def __init__(self):
super(TestSuiteBase, self).__init__()
self._tests = []
self._prep = []
[docs] def add_prep(self, prep):
assert isinstance(prep, TestPrepBase)
prep.parent = self
self._prep.append(prep)
[docs] def add_test(self, test):
test.parent = self
self._tests.append(test)
[docs] def run_test(self):
logger.debug("Running Test Suite : " + repr(self))
for prep in self._prep:
prep.run_prep()
for test in self._tests:
test.run_test()
@property
def passed(self):
rval = True
for test in self._tests:
if test.passed is False:
rval = False
return rval
[docs] def render(self):
raise NotImplementedError
[docs] def render_dox(self):
suite_dict = {
'desc': self.desc,
'title': self.title,
'ts': self.ts.format(),
'passed': ('PASSED' if self.passed is True else 'FAILED'),
'tests': [x.render_dox() for x in self._tests]
}
return suite_dict
[docs] def finish(self):
for test in self._tests:
test.finish()
width = terminal.get_terminal_width()
hline = '-' * width
hcolor = Fore.CYAN
print hcolor + hline + Fore.RESET
if self.passed is True:
result = Fore.GREEN + '[PASSED]' + Fore.RESET
else:
result = Fore.RED + '[FAILED]' + Fore.RESET
fstring = "{0}{1:<" + str(width-10) + "}{2} {3:>9}"
print fstring.format(
hcolor, (self.desc or 'None'), Fore.WHITE, result
)
print "{0}{1}{2}".format(hcolor, repr(self), Fore.RESET)
print hcolor + hline + Fore.RESET
[docs] def destroy(self):
for test in self._tests:
test.destroy()
@property
def tests(self):
return self._tests
@property
def test_descs(self):
return [x.desc for x in self._tests]
[docs] def get_test_by_desc(self, desc):
for test in self._tests:
if test.desc == desc:
return test