6 Base classes for steps in Trigger ART tests
16 from threading
import Timer
17 from TrigValTools.TrigValSteering.Common
import get_logger, art_result, running_in_CI
18 from TestTools.logfiles
import grep_with_context
19 from TrigValTools.TrigARTUtils
import remember_cwd
23 '''Base class for a step of a Trigger ART test'''
56 Can be implemented by derived classes.
57 Base class implementation only prints the configuration to debug log.
60 'Step configuration:\n-- %s\n',
61 '\n-- '.
join([
'{}: {}'.
format(k, v)
for k, v
in self.__dict__.
items()]))
65 if self.
result is not None:
71 if self.
name is not None:
80 Print an error message (arguments passed to logging.error),
81 report non-zero art-result and exit the process with non-zero code
83 self.
log.
error(
'Misconfiguration in %s: '+error_msg, self.
name, *args, **kwargs)
89 Produce a backtrace for a process and its children, then call
90 os.killpg on the process. The last argument is a list of strings
91 where the first is filled with the backtrace by this function
92 (it has to be a list to be mutable).
97 parent = psutil.Process(pid)
99 for proc
in [parent] + parent.children(recursive=
True):
100 backtrace +=
'\nTraceback for {} PID {}:\n'.
format(proc.name(), proc.pid)
101 backtrace += subprocess.check_output(
'$ROOTSYS/etc/gdb-backtrace.sh {}'.
format(proc.pid),
102 stderr=subprocess.STDOUT, shell=
True).
decode(
'utf-8')
103 backtrace_list[0] = backtrace
106 os.killpg(pid, signal)
108 except Exception
as e:
110 msg =
'Caught exception while generating backtrace: ' +
str(e)
111 backtrace_list[0] = msg
116 Execute a shell process and kill it if it doesn't finish
117 before timeout_sec seconds pass. The implementation is based on
118 https://stackoverflow.com/a/10012262 and https://stackoverflow.com/a/4791612
119 In addition, a backtrace is produced for the timed out process and its children.
121 proc = subprocess.Popen(cmd, shell=
True, preexec_fn=os.setsid)
122 backtrace_list = [
'']
124 [os.getpgid(proc.pid), signal.SIGKILL, backtrace_list])
131 if proc.returncode == -signal.SIGKILL:
132 self.
log.
error(
'%s killed on timeout of %s seconds',
138 'ERROR process killed on timeout '
139 'of {} seconds, command was {}\n'.
format(
141 log_file.write(backtrace_list[0])
142 return signal.SIGKILL
144 return proc.returncode
147 self.
log.
debug(
'Starting prmon for pid %d', os.getpid())
149 prmon_cmd +=
' --filename prmon.{name:s}.txt --json-summary prmon.summary.{name:s}.json'.
format(name=self.
name)
150 prmon_cmd +=
' --log-filename prmon.{name:s}.log'.
format(name=self.
name)
151 return subprocess.Popen(prmon_cmd, shell=
True)
156 prmon_proc.send_signal(signal.SIGUSR1)
158 while (
not prmon_proc.poll())
and countWait < 10:
161 except OSError
as err:
162 self.
log.warning(
'Error while stopping prmon: %s', err)
165 def run(self, dry_run=False):
172 cmd +=
' >/dev/null 2>&1'
180 self.
log.
info(
'Running %s step using command:\n%s', self.
name, cmd)
186 assert '..' not in self.
workdir,
"Illegal path for workdir -- must be a subdirectory of CWD"
187 assert not self.
workdir.startswith(
'/'),
"Illegal path for workdir -- no absolute paths!"
188 os.makedirs(self.
workdir,exist_ok=
True)
196 self.
result = subprocess.call(cmd, shell=
True)
211 error_patterns =
'^ERROR| ERROR | FATAL |[Tt]raceback'
217 step_matches = re.findall(
'Logs for (.*) are in (.*)', log)
219 self.
log.warning(
'Failed to determine sub-step log names, cannot print the full sub-step logs')
221 step_log_names = [m[1]
for m
in step_matches]
222 for step_log_name
in step_log_names:
223 if os.path.isfile(step_log_name):
224 self.
log.
info(
'Printing partial sub-step log file %s', step_log_name)
232 Retrieve the first test matching the name from the list. Returns None if
235 for step
in step_list:
236 if step.name
is not None and step_name
in step.name:
243 Retrieve the first test matching the type from the list. Returns None if
246 for step
in step_list:
247 if isinstance(step, step_type):