blob: 64b45c5bb72aad718c5c9ba309570858ea01d44f [file] [log] [blame]
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +01001# osmo_gsm_tester: test class
2#
3# Copyright (C) 2017 by sysmocom - s.f.m.c. GmbH
4#
5# Author: Pau Espin Pedrol <pespin@sysmocom.de>
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20import os
21import sys
22import time
23import traceback
24from . import testenv
25
Pau Espin Pedrole8bbcbf2020-04-10 19:51:31 +020026from .core import log, util
27from . import resource
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +010028
29class Test(log.Origin):
Pau Espin Pedrol02e8a8d2020-03-05 17:22:40 +010030 UNKNOWN = 'UNKNOWN' # matches junit 'error'
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +010031 SKIP = 'skip'
32 PASS = 'pass'
33 FAIL = 'FAIL'
34
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +010035 def __init__(self, suite_run, test_basename):
36 self.basename = test_basename
37 super().__init__(log.C_TST, self.basename)
Pau Espin Pedrol58603672018-08-09 13:45:55 +020038 self._run_dir = None
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +010039 self.suite_run = suite_run
40 self.path = os.path.join(self.suite_run.definition.suite_dir, self.basename)
41 self.status = Test.UNKNOWN
42 self.start_timestamp = 0
43 self.duration = 0
44 self.fail_type = None
45 self.fail_message = None
Pau Espin Pedrol5eae2c52019-09-18 16:50:38 +020046 self.log_target = None
Pau Espin Pedrol644cb412020-03-04 16:14:31 +010047 self._report_stdout = None
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +010048
49 def get_run_dir(self):
50 if self._run_dir is None:
51 self._run_dir = util.Dir(self.suite_run.get_run_dir().new_dir(self._name))
52 return self._run_dir
53
54 def run(self):
55 try:
Pau Espin Pedrol5eae2c52019-09-18 16:50:38 +020056 self.log_target = log.FileLogTarget(self.get_run_dir().new_child('log')).set_all_levels(log.L_DBG).style_change(trace=True)
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +010057 log.large_separator(self.suite_run.trial.name(), self.suite_run.name(), self.name(), sublevel=3)
58 self.status = Test.UNKNOWN
59 self.start_timestamp = time.time()
Pau Espin Pedrole8bbcbf2020-04-10 19:51:31 +020060 from .core import process
61 from .core.event_loop import MainLoop
Pau Espin Pedrole1a58bd2020-04-10 20:46:07 +020062 from .obj import sms
63 from . import suite
Pau Espin Pedrol878b2c62018-05-18 15:57:06 +020064 testenv.setup(self.suite_run, self, suite, MainLoop, sms, process)
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +010065 with self.redirect_stdout():
66 util.run_python_file('%s.%s' % (self.suite_run.definition.name(), self.basename),
67 self.path)
68 if self.status == Test.UNKNOWN:
69 self.set_pass()
70 except Exception as e:
71 if hasattr(e, 'msg'):
72 msg = e.msg
73 else:
74 msg = str(e)
75 if isinstance(e, AssertionError):
76 # AssertionError lacks further information on what was
77 # asserted. Find the line where the code asserted:
78 msg += log.get_src_from_exc_info(sys.exc_info())
79 # add source file information to failure report
80 if hasattr(e, 'origins'):
81 msg += ' [%s]' % e.origins
82 tb_str = traceback.format_exc()
83 if isinstance(e, resource.NoResourceExn):
84 tb_str += self.suite_run.resource_status_str()
85 self.set_fail(type(e).__name__, msg, tb_str, log.get_src_from_exc_info())
86 except BaseException as e:
87 # when the program is aborted by a signal (like Ctrl-C), escalate to abort all.
88 self.err('TEST RUN ABORTED: %s' % type(e).__name__)
89 raise
Pau Espin Pedrol5eae2c52019-09-18 16:50:38 +020090 finally:
91 if self.log_target:
92 self.log_target.remove()
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +010093
94 def name(self):
95 l = log.get_line_for_src(self.path)
96 if l is not None:
97 return '%s:%s' % (self._name, l)
98 return super().name()
99
100 def set_fail(self, fail_type, fail_message, tb_str=None, src=4):
101 self.status = Test.FAIL
102 self.duration = time.time() - self.start_timestamp
103 self.fail_type = fail_type
104 self.fail_message = fail_message
105
106 if tb_str is None:
107 # populate an exception-less call to set_fail() with traceback info
108 tb_str = ''.join(traceback.format_stack()[:-1])
109
110 self.fail_tb = tb_str
111 self.err('%s: %s' % (self.fail_type, self.fail_message), _src=src)
112 if self.fail_tb:
113 self.log(self.fail_tb, _level=log.L_TRACEBACK)
114 self.log('Test FAILED (%.1f sec)' % self.duration)
115
116 def set_pass(self):
117 self.status = Test.PASS
118 self.duration = time.time() - self.start_timestamp
119 self.log('Test passed (%.1f sec)' % self.duration)
120
121 def set_skip(self):
122 self.status = Test.SKIP
123 self.duration = 0
124
Pau Espin Pedrol644cb412020-03-04 16:14:31 +0100125 def set_report_stdout(self, text):
126 'Overwrite stdout text stored in report from inside a test'
127 self._report_stdout = text
128
129 def report_stdout(self):
130 # If test overwrote the text, provide it:
131 if self._report_stdout is not None:
132 return self._report_stdout
133 # Otherwise vy default provide the entire test log:
134 if self.log_target is not None and self.log_target.log_file_path() is not None:
135 with open(self.log_target.log_file_path(), 'r') as myfile:
136 return myfile.read()
137 else:
138 return 'test log file not available'
Pau Espin Pedrol5bbdab82020-02-24 18:19:10 +0100139
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +0100140# vim: expandtab tabstop=4 shiftwidth=4