- # Generate tables:
- # All tests in csv:
- tbl_names = ["{0}-ndr-1t1c-full{1}".format(table["output-file"],
- table["output-file-ext"]),
- "{0}-ndr-2t2c-full{1}".format(table["output-file"],
- table["output-file-ext"]),
- "{0}-ndr-4t4c-full{1}".format(table["output-file"],
- table["output-file-ext"]),
- "{0}-pdr-1t1c-full{1}".format(table["output-file"],
- table["output-file-ext"]),
- "{0}-pdr-2t2c-full{1}".format(table["output-file"],
- table["output-file-ext"]),
- "{0}-pdr-4t4c-full{1}".format(table["output-file"],
- table["output-file-ext"])
- ]
- for file_name in tbl_names:
- logging.info(" Writing file: '{}'".format(file_name))
- with open(file_name, "w") as file_handler:
- file_handler.write(header_str)
- for test in tbl_lst:
- if (file_name.split("-")[-3] in test[0] and # NDR vs PDR
- file_name.split("-")[-2] in test[0]): # cores
- test[0] = "-".join(test[0].split("-")[:-1])
- file_handler.write(",".join([str(item) for item in test]) +
- "\n")
-
- # All tests in txt:
- tbl_names_txt = ["{0}-ndr-1t1c-full.txt".format(table["output-file"]),
- "{0}-ndr-2t2c-full.txt".format(table["output-file"]),
- "{0}-ndr-4t4c-full.txt".format(table["output-file"]),
- "{0}-pdr-1t1c-full.txt".format(table["output-file"]),
- "{0}-pdr-2t2c-full.txt".format(table["output-file"]),
- "{0}-pdr-4t4c-full.txt".format(table["output-file"])
- ]
-
- for i, txt_name in enumerate(tbl_names_txt):
- txt_table = None
- logging.info(" Writing file: '{}'".format(txt_name))
- with open(tbl_names[i], 'rb') as csv_file:
- csv_content = csv.reader(csv_file, delimiter=',', quotechar='"')
- for row in csv_content:
- if txt_table is None:
- txt_table = prettytable.PrettyTable(row)
- else:
- txt_table.add_row(row)
- with open(txt_name, "w") as txt_file:
- txt_file.write(str(txt_table))
-
- # Selected tests in csv:
- input_file = "{0}-ndr-1t1c-full{1}".format(table["output-file"],
- table["output-file-ext"])
- with open(input_file, "r") as in_file:
- lines = list()
- for line in in_file:
- lines.append(line)
-
- output_file = "{0}-ndr-1t1c-top{1}".format(table["output-file"],
- table["output-file-ext"])
- logging.info(" Writing file: '{}'".format(output_file))
- with open(output_file, "w") as out_file:
- out_file.write(header_str)
- for i, line in enumerate(lines[1:]):
- if i == table["nr-of-tests-shown"]:
- break
- out_file.write(line)
-
- output_file = "{0}-ndr-1t1c-bottom{1}".format(table["output-file"],
- table["output-file-ext"])
- logging.info(" Writing file: '{}'".format(output_file))
- with open(output_file, "w") as out_file:
- out_file.write(header_str)
- for i, line in enumerate(lines[-1:0:-1]):
- if i == table["nr-of-tests-shown"]:
- break
- out_file.write(line)
-
- input_file = "{0}-pdr-1t1c-full{1}".format(table["output-file"],
- table["output-file-ext"])
- with open(input_file, "r") as in_file:
- lines = list()
- for line in in_file:
- lines.append(line)
-
- output_file = "{0}-pdr-1t1c-top{1}".format(table["output-file"],
- table["output-file-ext"])
- logging.info(" Writing file: '{}'".format(output_file))
- with open(output_file, "w") as out_file:
- out_file.write(header_str)
- for i, line in enumerate(lines[1:]):
- if i == table["nr-of-tests-shown"]:
- break
- out_file.write(line)
-
- output_file = "{0}-pdr-1t1c-bottom{1}".format(table["output-file"],
- table["output-file-ext"])
- logging.info(" Writing file: '{}'".format(output_file))
- with open(output_file, "w") as out_file:
- out_file.write(header_str)
- for i, line in enumerate(lines[-1:0:-1]):
- if i == table["nr-of-tests-shown"]:
- break
- out_file.write(line)
+ # Generate csv tables:
+ csv_file = f"{table[u'output-file']}.csv"
+ with open(csv_file, u"wt") as file_handler:
+ file_handler.write(header_str)
+ for test in tbl_lst:
+ file_handler.write(u",".join([str(item) for item in test]) + u"\n")
+
+ convert_csv_to_pretty_txt(csv_file, f"{table[u'output-file']}.txt")
+
+ # Generate html table:
+ _tpc_generate_html_table(header, tbl_lst, f"{table[u'output-file']}.html")
+
+
+def table_perf_trending_dash(table, input_data):
+ """Generate the table(s) with algorithm:
+ table_perf_trending_dash
+ specified in the specification file.
+
+ :param table: Table to generate.
+ :param input_data: Data to process.
+ :type table: pandas.Series
+ :type input_data: InputData
+ """
+
+ logging.info(f" Generating the table {table.get(u'title', u'')} ...")
+
+ # Transform the data
+ logging.info(
+ f" Creating the data set for the {table.get(u'type', u'')} "
+ f"{table.get(u'title', u'')}."
+ )
+ data = input_data.filter_data(table, continue_on_error=True)
+
+ # Prepare the header of the tables
+ header = [
+ u"Test Case",
+ u"Trend [Mpps]",
+ u"Short-Term Change [%]",
+ u"Long-Term Change [%]",
+ u"Regressions [#]",
+ u"Progressions [#]"
+ ]
+ header_str = u",".join(header) + u"\n"
+
+ # Prepare data to the table:
+ tbl_dict = dict()
+ for job, builds in table[u"data"].items():
+ for build in builds:
+ for tst_name, tst_data in data[job][str(build)].items():
+ if tst_name.lower() in table.get(u"ignore-list", list()):
+ continue
+ if tbl_dict.get(tst_name, None) is None:
+ groups = re.search(REGEX_NIC, tst_data[u"parent"])
+ if not groups:
+ continue
+ nic = groups.group(0)
+ tbl_dict[tst_name] = {
+ u"name": f"{nic}-{tst_data[u'name']}",
+ u"data": OrderedDict()
+ }
+ try:
+ tbl_dict[tst_name][u"data"][str(build)] = \
+ tst_data[u"result"][u"receive-rate"]
+ except (TypeError, KeyError):
+ pass # No data in output.xml for this test
+
+ tbl_lst = list()
+ for tst_name in tbl_dict:
+ data_t = tbl_dict[tst_name][u"data"]
+ if len(data_t) < 2:
+ continue
+
+ classification_lst, avgs = classify_anomalies(data_t)
+
+ win_size = min(len(data_t), table[u"window"])
+ long_win_size = min(len(data_t), table[u"long-trend-window"])
+
+ try:
+ max_long_avg = max(
+ [x for x in avgs[-long_win_size:-win_size]
+ if not isnan(x)])
+ except ValueError:
+ max_long_avg = nan
+ last_avg = avgs[-1]
+ avg_week_ago = avgs[max(-win_size, -len(avgs))]
+
+ if isnan(last_avg) or isnan(avg_week_ago) or avg_week_ago == 0.0:
+ rel_change_last = nan
+ else:
+ rel_change_last = round(
+ ((last_avg - avg_week_ago) / avg_week_ago) * 100, 2)
+
+ if isnan(max_long_avg) or isnan(last_avg) or max_long_avg == 0.0:
+ rel_change_long = nan
+ else:
+ rel_change_long = round(
+ ((last_avg - max_long_avg) / max_long_avg) * 100, 2)
+
+ if classification_lst:
+ if isnan(rel_change_last) and isnan(rel_change_long):
+ continue
+ if isnan(last_avg) or isnan(rel_change_last) or \
+ isnan(rel_change_long):
+ continue
+ tbl_lst.append(
+ [tbl_dict[tst_name][u"name"],
+ round(last_avg / 1000000, 2),
+ rel_change_last,
+ rel_change_long,
+ classification_lst[-win_size:].count(u"regression"),
+ classification_lst[-win_size:].count(u"progression")])
+
+ tbl_lst.sort(key=lambda rel: rel[0])
+
+ tbl_sorted = list()
+ for nrr in range(table[u"window"], -1, -1):
+ tbl_reg = [item for item in tbl_lst if item[4] == nrr]
+ for nrp in range(table[u"window"], -1, -1):
+ tbl_out = [item for item in tbl_reg if item[5] == nrp]
+ tbl_out.sort(key=lambda rel: rel[2])
+ tbl_sorted.extend(tbl_out)
+
+ file_name = f"{table[u'output-file']}{table[u'output-file-ext']}"
+
+ logging.info(f" Writing file: {file_name}")
+ with open(file_name, u"wt") as file_handler:
+ file_handler.write(header_str)
+ for test in tbl_sorted:
+ file_handler.write(u",".join([str(item) for item in test]) + u'\n')
+
+ logging.info(f" Writing file: {table[u'output-file']}.txt")
+ convert_csv_to_pretty_txt(file_name, f"{table[u'output-file']}.txt")
+
+
+def _generate_url(testbed, test_name):
+ """Generate URL to a trending plot from the name of the test case.
+
+ :param testbed: The testbed used for testing.
+ :param test_name: The name of the test case.
+ :type testbed: str
+ :type test_name: str
+ :returns: The URL to the plot with the trending data for the given test
+ case.
+ :rtype str
+ """
+
+ if u"x520" in test_name:
+ nic = u"x520"
+ elif u"x710" in test_name:
+ nic = u"x710"
+ elif u"xl710" in test_name:
+ nic = u"xl710"
+ elif u"xxv710" in test_name:
+ nic = u"xxv710"
+ elif u"vic1227" in test_name:
+ nic = u"vic1227"
+ elif u"vic1385" in test_name:
+ nic = u"vic1385"
+ elif u"x553" in test_name:
+ nic = u"x553"
+ elif u"cx556" in test_name or u"cx556a" in test_name:
+ nic = u"cx556a"
+ else:
+ nic = u""
+
+ if u"64b" in test_name:
+ frame_size = u"64b"
+ elif u"78b" in test_name:
+ frame_size = u"78b"
+ elif u"imix" in test_name:
+ frame_size = u"imix"
+ elif u"9000b" in test_name:
+ frame_size = u"9000b"
+ elif u"1518b" in test_name:
+ frame_size = u"1518b"
+ elif u"114b" in test_name:
+ frame_size = u"114b"
+ else:
+ frame_size = u""
+
+ if u"1t1c" in test_name or \
+ (u"-1c-" in test_name and
+ testbed in (u"3n-hsw", u"3n-tsh", u"2n-dnv", u"3n-dnv")):
+ cores = u"1t1c"
+ elif u"2t2c" in test_name or \
+ (u"-2c-" in test_name and
+ testbed in (u"3n-hsw", u"3n-tsh", u"2n-dnv", u"3n-dnv")):
+ cores = u"2t2c"
+ elif u"4t4c" in test_name or \
+ (u"-4c-" in test_name and
+ testbed in (u"3n-hsw", u"3n-tsh", u"2n-dnv", u"3n-dnv")):
+ cores = u"4t4c"
+ elif u"2t1c" in test_name or \
+ (u"-1c-" in test_name and
+ testbed in (u"2n-skx", u"3n-skx", u"2n-clx")):
+ cores = u"2t1c"
+ elif u"4t2c" in test_name or \
+ (u"-2c-" in test_name and
+ testbed in (u"2n-skx", u"3n-skx", u"2n-clx")):
+ cores = u"4t2c"
+ elif u"8t4c" in test_name or \
+ (u"-4c-" in test_name and
+ testbed in (u"2n-skx", u"3n-skx", u"2n-clx")):
+ cores = u"8t4c"
+ else:
+ cores = u""
+
+ if u"testpmd" in test_name:
+ driver = u"testpmd"
+ elif u"l3fwd" in test_name:
+ driver = u"l3fwd"
+ elif u"avf" in test_name:
+ driver = u"avf"
+ elif u"rdma" in test_name:
+ driver = u"rdma"
+ elif u"dnv" in testbed or u"tsh" in testbed:
+ driver = u"ixgbe"
+ else:
+ driver = u"i40e"
+
+ if u"acl" in test_name or \
+ u"macip" in test_name or \
+ u"nat" in test_name or \
+ u"policer" in test_name or \
+ u"cop" in test_name:
+ bsf = u"features"
+ elif u"scale" in test_name:
+ bsf = u"scale"
+ elif u"base" in test_name:
+ bsf = u"base"
+ else:
+ bsf = u"base"
+
+ if u"114b" in test_name and u"vhost" in test_name:
+ domain = u"vts"
+ elif u"testpmd" in test_name or u"l3fwd" in test_name:
+ domain = u"dpdk"
+ elif u"memif" in test_name:
+ domain = u"container_memif"
+ elif u"srv6" in test_name:
+ domain = u"srv6"
+ elif u"vhost" in test_name:
+ domain = u"vhost"
+ if u"vppl2xc" in test_name:
+ driver += u"-vpp"
+ else:
+ driver += u"-testpmd"
+ if u"lbvpplacp" in test_name:
+ bsf += u"-link-bonding"
+ elif u"ch" in test_name and u"vh" in test_name and u"vm" in test_name:
+ domain = u"nf_service_density_vnfc"
+ elif u"ch" in test_name and u"mif" in test_name and u"dcr" in test_name:
+ domain = u"nf_service_density_cnfc"
+ elif u"pl" in test_name and u"mif" in test_name and u"dcr" in test_name:
+ domain = u"nf_service_density_cnfp"
+ elif u"ipsec" in test_name:
+ domain = u"ipsec"
+ if u"sw" in test_name:
+ bsf += u"-sw"
+ elif u"hw" in test_name:
+ bsf += u"-hw"
+ elif u"ethip4vxlan" in test_name:
+ domain = u"ip4_tunnels"
+ elif u"ip4base" in test_name or u"ip4scale" in test_name:
+ domain = u"ip4"
+ elif u"ip6base" in test_name or u"ip6scale" in test_name:
+ domain = u"ip6"
+ elif u"l2xcbase" in test_name or \
+ u"l2xcscale" in test_name or \
+ u"l2bdbasemaclrn" in test_name or \
+ u"l2bdscale" in test_name or \
+ u"l2patch" in test_name:
+ domain = u"l2"
+ else:
+ domain = u""
+
+ file_name = u"-".join((domain, testbed, nic)) + u".html#"
+ anchor_name = u"-".join((frame_size, cores, bsf, driver))
+
+ return file_name + anchor_name
+
+
+def table_perf_trending_dash_html(table, input_data):
+ """Generate the table(s) with algorithm:
+ table_perf_trending_dash_html specified in the specification
+ file.
+
+ :param table: Table to generate.
+ :param input_data: Data to process.
+ :type table: dict
+ :type input_data: InputData
+ """
+
+ _ = input_data
+
+ if not table.get(u"testbed", None):
+ logging.error(
+ f"The testbed is not defined for the table "
+ f"{table.get(u'title', u'')}."
+ )
+ return
+
+ logging.info(f" Generating the table {table.get(u'title', u'')} ...")
+
+ try:
+ with open(table[u"input-file"], u'rt') as csv_file:
+ csv_lst = list(csv.reader(csv_file, delimiter=u',', quotechar=u'"'))
+ except KeyError:
+ logging.warning(u"The input file is not defined.")
+ return
+ except csv.Error as err:
+ logging.warning(
+ f"Not possible to process the file {table[u'input-file']}.\n"
+ f"{repr(err)}"
+ )
+ return
+
+ # Table:
+ dashboard = ET.Element(u"table", attrib=dict(width=u"100%", border=u'0'))
+
+ # Table header:
+ trow = ET.SubElement(dashboard, u"tr", attrib=dict(bgcolor=u"#7eade7"))
+ for idx, item in enumerate(csv_lst[0]):
+ alignment = u"left" if idx == 0 else u"center"
+ thead = ET.SubElement(trow, u"th", attrib=dict(align=alignment))
+ thead.text = item
+
+ # Rows:
+ colors = {
+ u"regression": (
+ u"#ffcccc",
+ u"#ff9999"
+ ),
+ u"progression": (
+ u"#c6ecc6",
+ u"#9fdf9f"
+ ),
+ u"normal": (
+ u"#e9f1fb",
+ u"#d4e4f7"
+ )
+ }
+ for r_idx, row in enumerate(csv_lst[1:]):
+ if int(row[4]):
+ color = u"regression"
+ elif int(row[5]):
+ color = u"progression"
+ else:
+ color = u"normal"
+ trow = ET.SubElement(
+ dashboard, u"tr", attrib=dict(bgcolor=colors[color][r_idx % 2])
+ )
+
+ # Columns:
+ for c_idx, item in enumerate(row):
+ tdata = ET.SubElement(
+ trow,
+ u"td",
+ attrib=dict(align=u"left" if c_idx == 0 else u"center")
+ )
+ # Name:
+ if c_idx == 0:
+ ref = ET.SubElement(
+ tdata,
+ u"a",
+ attrib=dict(
+ href=f"../trending/"
+ f"{_generate_url(table.get(u'testbed', ''), item)}"
+ )
+ )
+ ref.text = item
+ else:
+ tdata.text = item
+ try:
+ with open(table[u"output-file"], u'w') as html_file:
+ logging.info(f" Writing file: {table[u'output-file']}")
+ html_file.write(u".. raw:: html\n\n\t")
+ html_file.write(str(ET.tostring(dashboard, encoding=u"unicode")))
+ html_file.write(u"\n\t<p><br><br></p>\n")
+ except KeyError:
+ logging.warning(u"The output file is not defined.")
+ return
+
+
+def table_last_failed_tests(table, input_data):
+ """Generate the table(s) with algorithm: table_last_failed_tests
+ specified in the specification file.
+
+ :param table: Table to generate.
+ :param input_data: Data to process.
+ :type table: pandas.Series
+ :type input_data: InputData
+ """
+
+ logging.info(f" Generating the table {table.get(u'title', u'')} ...")
+
+ # Transform the data
+ logging.info(
+ f" Creating the data set for the {table.get(u'type', u'')} "
+ f"{table.get(u'title', u'')}."
+ )
+
+ data = input_data.filter_data(table, continue_on_error=True)
+
+ if data is None or data.empty:
+ logging.warning(
+ f" No data for the {table.get(u'type', u'')} "
+ f"{table.get(u'title', u'')}."
+ )
+ return
+
+ tbl_list = list()
+ for job, builds in table[u"data"].items():
+ for build in builds:
+ build = str(build)
+ try:
+ version = input_data.metadata(job, build).get(u"version", u"")
+ except KeyError:
+ logging.error(f"Data for {job}: {build} is not present.")
+ return
+ tbl_list.append(build)
+ tbl_list.append(version)
+ failed_tests = list()
+ passed = 0
+ failed = 0
+ for tst_data in data[job][build].values:
+ if tst_data[u"status"] != u"FAIL":
+ passed += 1
+ continue
+ failed += 1
+ groups = re.search(REGEX_NIC, tst_data[u"parent"])
+ if not groups:
+ continue
+ nic = groups.group(0)
+ failed_tests.append(f"{nic}-{tst_data[u'name']}")
+ tbl_list.append(str(passed))
+ tbl_list.append(str(failed))
+ tbl_list.extend(failed_tests)
+
+ file_name = f"{table[u'output-file']}{table[u'output-file-ext']}"
+ logging.info(f" Writing file: {file_name}")
+ with open(file_name, u"wt") as file_handler:
+ for test in tbl_list:
+ file_handler.write(test + u'\n')
+
+
+def table_failed_tests(table, input_data):
+ """Generate the table(s) with algorithm: table_failed_tests
+ specified in the specification file.
+
+ :param table: Table to generate.
+ :param input_data: Data to process.
+ :type table: pandas.Series
+ :type input_data: InputData
+ """
+
+ logging.info(f" Generating the table {table.get(u'title', u'')} ...")
+
+ # Transform the data
+ logging.info(
+ f" Creating the data set for the {table.get(u'type', u'')} "
+ f"{table.get(u'title', u'')}."
+ )
+ data = input_data.filter_data(table, continue_on_error=True)
+
+ # Prepare the header of the tables
+ header = [
+ u"Test Case",
+ u"Failures [#]",
+ u"Last Failure [Time]",
+ u"Last Failure [VPP-Build-Id]",
+ u"Last Failure [CSIT-Job-Build-Id]"
+ ]
+
+ # Generate the data for the table according to the model in the table
+ # specification
+
+ now = dt.utcnow()
+ timeperiod = timedelta(int(table.get(u"window", 7)))
+
+ tbl_dict = dict()
+ for job, builds in table[u"data"].items():
+ for build in builds:
+ build = str(build)
+ for tst_name, tst_data in data[job][build].items():
+ if tst_name.lower() in table.get(u"ignore-list", list()):
+ continue
+ if tbl_dict.get(tst_name, None) is None:
+ groups = re.search(REGEX_NIC, tst_data[u"parent"])
+ if not groups:
+ continue
+ nic = groups.group(0)
+ tbl_dict[tst_name] = {
+ u"name": f"{nic}-{tst_data[u'name']}",
+ u"data": OrderedDict()
+ }
+ try:
+ generated = input_data.metadata(job, build).\
+ get(u"generated", u"")
+ if not generated:
+ continue
+ then = dt.strptime(generated, u"%Y%m%d %H:%M")
+ if (now - then) <= timeperiod:
+ tbl_dict[tst_name][u"data"][build] = (
+ tst_data[u"status"],
+ generated,
+ input_data.metadata(job, build).get(u"version",
+ u""),
+ build
+ )
+ except (TypeError, KeyError) as err:
+ logging.warning(f"tst_name: {tst_name} - err: {repr(err)}")
+
+ max_fails = 0
+ tbl_lst = list()
+ for tst_data in tbl_dict.values():
+ fails_nr = 0
+ fails_last_date = u""
+ fails_last_vpp = u""
+ fails_last_csit = u""
+ for val in tst_data[u"data"].values():
+ if val[0] == u"FAIL":
+ fails_nr += 1
+ fails_last_date = val[1]
+ fails_last_vpp = val[2]
+ fails_last_csit = val[3]
+ if fails_nr:
+ max_fails = fails_nr if fails_nr > max_fails else max_fails
+ tbl_lst.append(
+ [
+ tst_data[u"name"],
+ fails_nr,
+ fails_last_date,
+ fails_last_vpp,
+ f"mrr-daily-build-{fails_last_csit}"
+ ]
+ )
+
+ tbl_lst.sort(key=lambda rel: rel[2], reverse=True)
+ tbl_sorted = list()
+ for nrf in range(max_fails, -1, -1):
+ tbl_fails = [item for item in tbl_lst if item[1] == nrf]
+ tbl_sorted.extend(tbl_fails)
+
+ file_name = f"{table[u'output-file']}{table[u'output-file-ext']}"
+ logging.info(f" Writing file: {file_name}")
+ with open(file_name, u"wt") as file_handler:
+ file_handler.write(u",".join(header) + u"\n")
+ for test in tbl_sorted:
+ file_handler.write(u",".join([str(item) for item in test]) + u'\n')
+
+ logging.info(f" Writing file: {table[u'output-file']}.txt")
+ convert_csv_to_pretty_txt(file_name, f"{table[u'output-file']}.txt")
+
+
+def table_failed_tests_html(table, input_data):
+ """Generate the table(s) with algorithm: table_failed_tests_html
+ specified in the specification file.
+
+ :param table: Table to generate.
+ :param input_data: Data to process.
+ :type table: pandas.Series
+ :type input_data: InputData
+ """
+
+ _ = input_data
+
+ if not table.get(u"testbed", None):
+ logging.error(
+ f"The testbed is not defined for the table "
+ f"{table.get(u'title', u'')}."
+ )
+ return
+
+ logging.info(f" Generating the table {table.get(u'title', u'')} ...")
+
+ try:
+ with open(table[u"input-file"], u'rt') as csv_file:
+ csv_lst = list(csv.reader(csv_file, delimiter=u',', quotechar=u'"'))
+ except KeyError:
+ logging.warning(u"The input file is not defined.")
+ return
+ except csv.Error as err:
+ logging.warning(
+ f"Not possible to process the file {table[u'input-file']}.\n"
+ f"{repr(err)}"
+ )
+ return
+
+ # Table:
+ failed_tests = ET.Element(u"table", attrib=dict(width=u"100%", border=u'0'))
+
+ # Table header:
+ trow = ET.SubElement(failed_tests, u"tr", attrib=dict(bgcolor=u"#7eade7"))
+ for idx, item in enumerate(csv_lst[0]):
+ alignment = u"left" if idx == 0 else u"center"
+ thead = ET.SubElement(trow, u"th", attrib=dict(align=alignment))
+ thead.text = item
+
+ # Rows:
+ colors = (u"#e9f1fb", u"#d4e4f7")
+ for r_idx, row in enumerate(csv_lst[1:]):
+ background = colors[r_idx % 2]
+ trow = ET.SubElement(
+ failed_tests, u"tr", attrib=dict(bgcolor=background)
+ )
+
+ # Columns:
+ for c_idx, item in enumerate(row):
+ tdata = ET.SubElement(
+ trow,
+ u"td",
+ attrib=dict(align=u"left" if c_idx == 0 else u"center")
+ )
+ # Name:
+ if c_idx == 0:
+ ref = ET.SubElement(
+ tdata,
+ u"a",
+ attrib=dict(
+ href=f"../trending/"
+ f"{_generate_url(table.get(u'testbed', ''), item)}"
+ )
+ )
+ ref.text = item
+ else:
+ tdata.text = item
+ try:
+ with open(table[u"output-file"], u'w') as html_file:
+ logging.info(f" Writing file: {table[u'output-file']}")
+ html_file.write(u".. raw:: html\n\n\t")
+ html_file.write(str(ET.tostring(failed_tests, encoding=u"unicode")))
+ html_file.write(u"\n\t<p><br><br></p>\n")
+ except KeyError:
+ logging.warning(u"The output file is not defined.")
+ return