test.py revision c278c2343c7f25083a80cb164b6bdc761d50050b
1# SPDX-License-Identifier: Apache-2.0
2#
3# Copyright (C) 2015, ARM Limited and contributors.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import os
19import unittest
20import logging
21
22from bart.sched.SchedAssert import SchedAssert
23from bart.sched.SchedMultiAssert import SchedMultiAssert
24from devlib.utils.misc import memoized
25import wrapt
26
27from env import TestEnv
28from executor import Executor
29
30
31class LisaTest(unittest.TestCase):
32    """
33    A base class for LISA tests
34
35    This class is intended to be subclassed in order to create automated tests
36    for LISA. It sets up the TestEnv and Executor and provides convenience
37    methods for making assertions on results.
38
39    Subclasses should provide a test_conf to configure the TestEnv and an
40    experiments_conf to configure the executor.
41
42    Tests whose behaviour is dependent on target parameters, for example
43    presence of cpufreq governors or number of CPUs, can override
44    _getExperimentsConf to generate target-dependent experiments.
45
46    Example users of this class can be found under LISA's tests/ directory.
47    """
48
49    test_conf = None
50    """Override this with a dictionary or JSON path to configure the TestEnv"""
51
52    experiments_conf = None
53    """Override this with a dictionary to configure the Executor"""
54
55    @classmethod
56    def _init(cls, *args, **kwargs):
57        """
58        Set up logging and trigger running experiments
59        """
60
61        cls.logger = logging.getLogger('LisaTest')
62        cls._runExperiments()
63
64    @classmethod
65    def _getTestConf(cls):
66        if cls.test_conf is None:
67            raise NotImplementedError("Override `test_conf` attribute")
68        return cls.test_conf
69
70    @classmethod
71    def _getExperimentsConf(cls, test_env):
72        """
73        Get the experiments_conf used to configure the Executor
74
75        This method receives the initialized TestEnv as a parameter, so
76        subclasses can override it to configure workloads or target confs in a
77        manner dependent on the target. If not overridden, just returns the
78        experiments_conf attribute.
79        """
80        if cls.experiments_conf is None:
81            raise NotImplementedError("Override `experiments_conf` attribute")
82        return cls.experiments_conf
83
84    @classmethod
85    def _runExperiments(cls):
86        """
87        Default experiments execution engine
88        """
89
90        cls.logger.info('Setup tests execution engine...')
91        test_env = TestEnv(test_conf=cls._getTestConf())
92
93        experiments_conf = cls._getExperimentsConf(test_env)
94        cls.executor = Executor(test_env, experiments_conf)
95
96        # Alias executor objects to make less verbose tests code
97        cls.te = cls.executor.te
98        cls.target = cls.executor.target
99
100        # Execute pre-experiments code defined by the test
101        cls._experimentsInit()
102
103        cls.logger.info('Experiments execution...')
104        cls.executor.run()
105
106        # Execute post-experiments code defined by the test
107        cls._experimentsFinalize()
108
109    @classmethod
110    def _experimentsInit(cls):
111        """
112        Code executed before running the experiments
113        """
114
115    @classmethod
116    def _experimentsFinalize(cls):
117        """
118        Code executed after running the experiments
119        """
120
121    @memoized
122    def get_sched_assert(self, experiment, task):
123        """
124        Return a SchedAssert over the task provided
125        """
126        return SchedAssert(experiment.out_dir, self.te.topology, execname=task)
127
128    @memoized
129    def get_multi_assert(self, experiment, task_filter=""):
130        """
131        Return a SchedMultiAssert over the tasks whose names contain task_filter
132
133        By default, this includes _all_ the tasks that were executed for the
134        experiment.
135        """
136        tasks = experiment.wload.tasks.keys()
137        return SchedMultiAssert(experiment.out_dir,
138                                self.te.topology,
139                                [t for t in tasks if task_filter in t])
140
141    def get_start_time(self, experiment):
142        """
143        Get the time at which the experiment workload began executing
144        """
145        start_times_dict = self.get_multi_assert(experiment).getStartTime()
146        return min([t["starttime"] for t in start_times_dict.itervalues()])
147
148    def get_end_time(self, experiment):
149        """
150        Get the time at which the experiment workload finished executing
151        """
152        end_times_dict = self.get_multi_assert(experiment).getEndTime()
153        return max([t["endtime"] for t in end_times_dict.itervalues()])
154
155    def get_window(self, experiment):
156        return (self.get_start_time(experiment), self.get_end_time(experiment))
157
158    def get_end_times(self, experiment):
159        """
160        Get the time at which each task in the workload finished
161
162        Returned as a dict; {"task_name": finish_time, ...}
163        """
164
165        end_times = {}
166        for task in experiment.wload.tasks.keys():
167            sched_assert = SchedAssert(experiment.out_dir, self.te.topology,
168                                       execname=task)
169            end_times[task] = sched_assert.getEndTime()
170
171        return end_times
172
173
174@wrapt.decorator
175def experiment_test(wrapped_test, instance, args, kwargs):
176    """
177    Convert a LisaTest test method to be automatically called for each experiment
178
179    The method will be passed the experiment object and a list of the names of
180    tasks that were run as the experiment's workload.
181    """
182    for experiment in instance.executor.experiments:
183        tasks = experiment.wload.tasks.keys()
184        try:
185            wrapped_test(experiment, tasks, *args, **kwargs)
186        except AssertionError as e:
187            trace_relpath = os.path.join(experiment.out_dir, "trace.dat")
188            add_msg = "\n\tCheck trace file: " + os.path.abspath(trace_relpath)
189            orig_msg = e.args[0] if len(e.args) else ""
190            e.args = (orig_msg + add_msg,) + e.args[1:]
191            raise
192
193# Prevent nosetests from running experiment_test directly as a test case
194experiment_test.__test__ = False
195
196# vim :set tabstop=4 shiftwidth=4 expandtab
197