]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/lib/lutil.py
Merge pull request #12798 from donaldsharp/rib_match_multicast
[mirror_frr.git] / tests / topotests / lib / lutil.py
CommitLineData
199a7c79 1#!/usr/bin/env python
acddc0ed 2# SPDX-License-Identifier: GPL-2.0-or-later
199a7c79
LB
3
4# Copyright 2017, LabN Consulting, L.L.C.
199a7c79
LB
5
6import os
7import re
8import sys
9import time
cc3cf6a8 10import json
2a76b0a8 11import math
2f17f52a 12import time
10870bbc 13from lib.topolog import logger
4e3828b7 14from lib.topotest import json_cmp
199a7c79
LB
15
16
17# L utility functions
18#
19# These functions are inteneted to provide support for CI testing within MiniNet
20# environments.
21
701a0192 22
199a7c79 23class lUtil:
701a0192 24 # to be made configurable in the future
25 base_script_dir = "."
26 base_log_dir = "."
27 fout_name = "output.log"
28 fsum_name = "summary.txt"
d850e667 29 l_level = 6
a582a8e9 30 CallOnFail = False
199a7c79
LB
31
32 l_total = 0
33 l_pass = 0
34 l_fail = 0
701a0192 35 l_filename = ""
66c5287e 36 l_last = None
199a7c79 37 l_line = 0
c63906e8
PZ
38 l_dotall_experiment = False
39 l_last_nl = None
25cdcf1f 40 l_wait_strict = 1
199a7c79 41
701a0192 42 fout = ""
43 fsum = ""
44 net = ""
199a7c79 45
d850e667 46 def log(self, str, level=6):
199a7c79 47 if self.l_level > 0:
701a0192 48 if self.fout == "":
0e232bb8 49 self.fout = open(self.fout_name, "w")
701a0192 50 self.fout.write(str + "\n")
d850e667 51 if level <= self.l_level:
199a7c79
LB
52 print(str)
53
4348027f 54 def summary(self, str):
701a0192 55 if self.fsum == "":
0e232bb8 56 self.fsum = open(self.fsum_name, "w")
701a0192 57 self.fsum.write(
58 "\
59******************************************************************************\n"
60 )
61 self.fsum.write(
62 "\
63Test Target Summary Pass Fail\n"
64 )
65 self.fsum.write(
66 "\
67******************************************************************************\n"
68 )
69 self.fsum.write(str + "\n")
4348027f 70
ffd3f544 71 def result(self, target, success, str, logstr=None):
199a7c79
LB
72 if success:
73 p = 1
74 f = 0
75 self.l_pass += 1
ffd3f544 76 sstr = "PASS"
199a7c79
LB
77 else:
78 f = 1
79 p = 0
80 self.l_fail += 1
ffd3f544 81 sstr = "FAIL"
c941311f 82 self.l_total += 1
ffd3f544
LB
83 if logstr != None:
84 self.log("R:%d %s: %s" % (self.l_total, sstr, logstr))
a582a8e9 85 res = "%-4d %-6s %-56s %-4d %d" % (self.l_total, target, str, p, f)
701a0192 86 self.log("R:" + res)
4348027f 87 self.summary(res)
a582a8e9
LB
88 if f == 1 and self.CallOnFail != False:
89 self.CallOnFail()
199a7c79
LB
90
91 def closeFiles(self):
701a0192 92 ret = (
93 "\
199a7c79 94******************************************************************************\n\
a582a8e9 95Total %-4d %-4d %d\n\
701a0192 96******************************************************************************"
97 % (self.l_total, self.l_pass, self.l_fail)
98 )
99 if self.fsum != "":
100 self.fsum.write(ret + "\n")
199a7c79 101 self.fsum.close()
701a0192 102 self.fsum = ""
103 if self.fout != "":
199a7c79 104 if os.path.isfile(self.fsum_name):
701a0192 105 r = open(self.fsum_name, "r")
199a7c79
LB
106 self.fout.write(r.read())
107 r.close()
108 self.fout.close()
701a0192 109 self.fout = ""
199a7c79
LB
110 return ret
111
112 def setFilename(self, name):
701a0192 113 str = "FILE: " + name
4348027f
LB
114 self.log(str)
115 self.summary(str)
199a7c79
LB
116 self.l_filename = name
117 self.line = 0
118
a582a8e9
LB
119 def getCallOnFail(self):
120 return self.CallOnFail
121
122 def setCallOnFail(self, CallOnFail):
123 self.CallOnFail = CallOnFail
124
199a7c79
LB
125 def strToArray(self, string):
126 a = []
127 c = 0
701a0192 128 end = ""
199a7c79 129 words = string.split()
701a0192 130 if len(words) < 1 or words[0].startswith("#"):
199a7c79
LB
131 return a
132 words = string.split()
133 for word in words:
134 if len(end) == 0:
135 a.append(word)
136 else:
701a0192 137 a[c] += str(" " + word)
138 if end == "\\":
139 end = ""
140 if not word.endswith("\\"):
199a7c79
LB
141 if end != '"':
142 if word.startswith('"'):
143 end = '"'
144 else:
145 c += 1
146 else:
147 if word.endswith('"'):
701a0192 148 end = ""
199a7c79
LB
149 c += 1
150 else:
151 c += 1
152 else:
701a0192 153 end = "\\"
154 # if len(end) == 0:
155 # print('%d:%s:' % (c, a[c-1]))
199a7c79
LB
156
157 return a
158
159 def execTestFile(self, tstFile):
160 if os.path.isfile(tstFile):
161 f = open(tstFile)
162 for line in f:
163 if len(line) > 1:
164 a = self.strToArray(line)
165 if len(a) >= 6:
166 luCommand(a[1], a[2], a[3], a[4], a[5])
167 else:
168 self.l_line += 1
701a0192 169 self.log("%s:%s %s" % (self.l_filename, self.l_line, line))
199a7c79 170 if len(a) >= 2:
701a0192 171 if a[0] == "sleep":
199a7c79 172 time.sleep(int(a[1]))
701a0192 173 elif a[0] == "include":
199a7c79
LB
174 self.execTestFile(a[1])
175 f.close()
176 else:
701a0192 177 self.log("unable to read: " + tstFile)
199a7c79
LB
178 sys.exit(1)
179
25cdcf1f 180 def command(self, target, command, regexp, op, result, returnJson, startt=None, force_result=False):
199a7c79 181 global net
4e3828b7
DS
182 if op == "jsoncmp_pass" or op == "jsoncmp_fail":
183 returnJson = True
184
701a0192 185 self.log(
2f17f52a 186 "%s (#%d) %s:%s COMMAND:%s:%s:%s:%s:%s:"
701a0192 187 % (
2f17f52a 188 time.asctime(),
701a0192 189 self.l_total + 1,
190 self.l_filename,
191 self.l_line,
192 target,
193 command,
194 regexp,
195 op,
196 result,
197 )
198 )
199 if self.net == "":
199a7c79 200 return False
701a0192 201 # self.log("Running %s %s" % (target, command))
cc3cf6a8 202 js = None
199a7c79 203 out = self.net[target].cmd(command).rstrip()
50b15737
LB
204 if len(out) == 0:
205 report = "<no output>"
206 else:
207 report = out
cc3cf6a8
LB
208 if returnJson == True:
209 try:
210 js = json.loads(out)
211 except:
212 js = None
701a0192 213 self.log(
214 "WARNING: JSON load failed -- confirm command output is in JSON format."
215 )
216 self.log("COMMAND OUTPUT:%s:" % report)
c63906e8 217
4e3828b7
DS
218 # JSON comparison
219 if op == "jsoncmp_pass" or op == "jsoncmp_fail":
220 try:
221 expect = json.loads(regexp)
222 except:
223 expect = None
224 self.log(
225 "WARNING: JSON load failed -- confirm regex input is in JSON format."
226 )
227 json_diff = json_cmp(js, expect)
228 if json_diff != None:
229 if op == "jsoncmp_fail":
230 success = True
231 else:
232 success = False
233 self.log("JSON DIFF:%s:" % json_diff)
234 ret = success
235 else:
236 if op == "jsoncmp_fail":
237 success = False
238 else:
239 success = True
240 self.result(target, success, result)
241 if js != None:
242 return js
243 return ret
244
11761ab0
MS
245 # Experiment: can we achieve the same match behavior via DOTALL
246 # without converting newlines to spaces?
247 out_nl = out
701a0192 248 search_nl = re.search(regexp, out_nl, re.DOTALL)
11761ab0
MS
249 self.l_last_nl = search_nl
250 # Set up for comparison
251 if search_nl != None:
252 group_nl = search_nl.group()
253 group_nl_converted = " ".join(group_nl.splitlines())
518874f4 254 else:
11761ab0 255 group_nl_converted = None
c63906e8 256
199a7c79
LB
257 out = " ".join(out.splitlines())
258 search = re.search(regexp, out)
259 self.l_last = search
260 if search == None:
701a0192 261 if op == "fail":
199a7c79
LB
262 success = True
263 else:
264 success = False
265 ret = success
266 else:
267 ret = search.group()
701a0192 268 if op != "fail":
199a7c79 269 success = True
d850e667 270 level = 7
199a7c79
LB
271 else:
272 success = False
d850e667 273 level = 5
701a0192 274 self.log("found:%s:" % ret, level)
11761ab0
MS
275 # Experiment: compare matched strings obtained each way
276 if self.l_dotall_experiment and (group_nl_converted != ret):
701a0192 277 self.log(
278 "DOTALL experiment: strings differ dotall=[%s] orig=[%s]"
279 % (group_nl_converted, ret),
280 9,
281 )
6804af73 282 if startt != None:
25cdcf1f 283 if js != None or ret is not False or force_result is not False:
6804af73
LB
284 delta = time.time() - startt
285 self.result(target, success, "%s +%4.2f secs" % (result, delta))
286 elif op == "pass" or op == "fail":
199a7c79 287 self.result(target, success, result)
cc3cf6a8
LB
288 if js != None:
289 return js
199a7c79
LB
290 return ret
291
701a0192 292 def wait(
293 self, target, command, regexp, op, result, wait, returnJson, wait_time=0.5
294 ):
295 self.log(
296 "%s:%s WAIT:%s:%s:%s:%s:%s:%s:%s:"
297 % (
298 self.l_filename,
299 self.l_line,
300 target,
301 command,
302 regexp,
303 op,
304 result,
305 wait,
306 wait_time,
307 )
308 )
199a7c79
LB
309 found = False
310 n = 0
311 startt = time.time()
2a76b0a8 312
25cdcf1f
PZ
313 if (op == "wait-strict") or ((op == "wait") and self.l_wait_strict):
314 strict = True
315 else:
316 strict = False
317
2a76b0a8 318 # Calculate the amount of `sleep`s we are going to peform.
0f94985f 319 wait_count = int(math.ceil(wait / wait_time)) + 1
2a76b0a8 320
25cdcf1f 321 force_result = False
0f94985f
RZ
322 while wait_count > 0:
323 n += 1
25cdcf1f
PZ
324
325 # log a failure on last iteration if we don't get desired regexp
326 if strict and (wait_count == 1):
327 force_result = True
328
329 found = self.command(target, command, regexp, op, result, returnJson, startt, force_result)
0f94985f
RZ
330 if found is not False:
331 break
332
2a76b0a8 333 wait_count -= 1
0f94985f
RZ
334 if wait_count > 0:
335 time.sleep(wait_time)
2a76b0a8
RZ
336
337 delta = time.time() - startt
701a0192 338 self.log("Done after %d loops, time=%s, Found=%s" % (n, delta, found))
199a7c79
LB
339 return found
340
199a7c79 341
701a0192 342# initialized by luStart
343LUtil = None
344
345# entry calls
346def luStart(
347 baseScriptDir=".",
348 baseLogDir=".",
349 net="",
350 fout="output.log",
351 fsum="summary.txt",
352 level=None,
353):
9e219b9a 354 global LUtil
701a0192 355 # init class
356 LUtil = lUtil()
a582a8e9
LB
357 LUtil.base_script_dir = baseScriptDir
358 LUtil.base_log_dir = baseLogDir
199a7c79 359 LUtil.net = net
701a0192 360 if fout != "":
361 LUtil.fout_name = baseLogDir + "/" + fout
a582a8e9 362 if fsum != None:
701a0192 363 LUtil.fsum_name = baseLogDir + "/" + fsum
d850e667
LB
364 if level != None:
365 LUtil.l_level = level
c63906e8
PZ
366 LUtil.l_dotall_experiment = False
367 LUtil.l_dotall_experiment = True
199a7c79 368
701a0192 369
370def luCommand(
371 target,
372 command,
373 regexp=".",
374 op="none",
375 result="",
376 time=10,
377 returnJson=False,
378 wait_time=0.5,
379):
25cdcf1f
PZ
380 waitops = ["wait", "wait-strict", "wait-nostrict"]
381
382 if op in waitops:
701a0192 383 return LUtil.wait(
384 target, command, regexp, op, result, time, returnJson, wait_time
385 )
25cdcf1f
PZ
386 else:
387 return LUtil.command(target, command, regexp, op, result, returnJson)
701a0192 388
199a7c79 389
c63906e8
PZ
390def luLast(usenl=False):
391 if usenl:
11761ab0 392 if LUtil.l_last_nl != None:
701a0192 393 LUtil.log("luLast:%s:" % LUtil.l_last_nl.group(), 7)
11761ab0 394 return LUtil.l_last_nl
c63906e8 395 else:
11761ab0 396 if LUtil.l_last != None:
701a0192 397 LUtil.log("luLast:%s:" % LUtil.l_last.group(), 7)
11761ab0 398 return LUtil.l_last
66c5287e 399
701a0192 400
a582a8e9 401def luInclude(filename, CallOnFail=None):
701a0192 402 tstFile = LUtil.base_script_dir + "/" + filename
199a7c79 403 LUtil.setFilename(filename)
a582a8e9
LB
404 if CallOnFail != None:
405 oldCallOnFail = LUtil.getCallOnFail()
406 LUtil.setCallOnFail(CallOnFail)
701a0192 407 if filename.endswith(".py"):
408 LUtil.log("luInclude: execfile " + tstFile)
6cb6c403
PG
409 with open(tstFile) as infile:
410 exec(infile.read())
199a7c79 411 else:
701a0192 412 LUtil.log("luInclude: execTestFile " + tstFile)
199a7c79 413 LUtil.execTestFile(tstFile)
a582a8e9
LB
414 if CallOnFail != None:
415 LUtil.setCallOnFail(oldCallOnFail)
199a7c79 416
701a0192 417
199a7c79 418def luFinish():
9e219b9a
LB
419 global LUtil
420 ret = LUtil.closeFiles()
701a0192 421 # done
9e219b9a 422 LUtil = None
701a0192 423 return ret
424
199a7c79
LB
425
426def luNumFail():
427 return LUtil.l_fail
428
701a0192 429
199a7c79
LB
430def luNumPass():
431 return LUtil.l_pass
432
701a0192 433
ffd3f544
LB
434def luResult(target, success, str, logstr=None):
435 return LUtil.result(target, success, str, logstr)
f7af60f2 436
701a0192 437
dccb75bb
LB
438def luShowResults(prFunction):
439 printed = 0
701a0192 440 sf = open(LUtil.fsum_name, "r")
dccb75bb 441 for line in sf:
701a0192 442 printed += 1
dccb75bb
LB
443 prFunction(line.rstrip())
444 sf.close()
445
701a0192 446
a582a8e9
LB
447def luShowFail():
448 printed = 0
701a0192 449 sf = open(LUtil.fsum_name, "r")
a582a8e9
LB
450 for line in sf:
451 if line[-2] != "0":
701a0192 452 printed += 1
2fdcc342 453 logger.error(line.rstrip())
a582a8e9
LB
454 sf.close()
455 if printed > 0:
701a0192 456 logger.error("See %s for details of errors" % LUtil.fout_name)
457
25cdcf1f
PZ
458#
459# Sets default wait type for luCommand(op="wait) (may be overridden by
460# specifying luCommand(op="wait-strict") or luCommand(op="wait-nostrict")).
461#
462# "nostrict" is the historical default behavior, which is to ignore
463# failures to match the specified regexp in the specified time.
464#
465# "strict" means that failure to match the specified regexp in the
466# specified time yields an explicit, logged failure result
467#
468def luSetWaitType(waittype):
469 if waittype == "strict":
470 LUtil.l_wait_strict = 1
471 else:
472 if waittype == "nostrict":
473 LUtil.l_wait_strict = 0
474 else:
475 raise ValueError('waittype must be one of "strict" or "nostrict"')
476
a582a8e9 477
701a0192 478# for testing
479if __name__ == "__main__":
480 print(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/lib")
199a7c79
LB
481 luStart()
482 for arg in sys.argv[1:]:
483 luInclude(arg)
484 luFinish()
485 sys.exit(0)