// // LANforge-GUI Source Code // Copyright (C) 1999-2020 Candela Technologies Inc // http://www.candelatech.com // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU Library General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // Contact: Candela Technologies if you have any // questions. // import java.text.*; import java.util.concurrent.*; import java.io.*; import java.net.URL; import java.util.*; import java.nio.file.*; /** Process a set of test results generated by the CI/CD process for a test type in a single testbed. * There are currently no automated comparisons done across different testbeds. * Generate historical graphs and links to each test run. * Each test results directory would be packaged up by the lf_gui_report_summary.pl script, * called by the basic_regression.bash automated regression test script. * Example: java kpi --dir /var/www/html/tip/testbeds/ferndale-basic-01/reports/basic */ public class kpi { String lc_osname; String home_dir; static final String out_sep = "\t"; static final String in_sep = "\t"; public static int PRIORITY_IDX = 6; public static int TEST_ID_IDX = 7; public static int SHORT_DESC_IDX = 8; public static int PASS_FAIL_IDX = 9; public static int NUMERIC_SCORE_IDX = 10; public static int NOTES_IDX = 11; public static int UNITS_IDX = 12; public static int GRAPH_GROUP_IDX = 13; public static int SUBTEST_PASS_IDX = 14; public static int SUBTEST_FAIL_IDX = 15; public static String TESTBED_TEMPLATE = "testbed_template.html"; public static String AP_AUTO_BASIC_CX = "ap_auto_basic_cx"; public static String HTML_COLOR_PASS = "#bee7aa"; public static String HTML_COLOR_FAIL = "#f9c5c0"; public static String HTML_COLOR_WARNING = "#f8f6ad"; public static String PF = "PASS-FAIL"; public kpi() { priv_init(); } static String yellowTd(String txt) { return "" + txt + ""; } static String redTd(String txt) { return "" + txt + ""; } static String greenTd(String txt) { return "" + txt + ""; } public boolean is_mac() { return lc_osname.startsWith("mac os x"); } public boolean is_win() { return lc_osname.startsWith("windows"); } public boolean is_linux() { return lc_osname.startsWith("linux"); } private void priv_init() { lc_osname = System.getProperty("os.name").toLowerCase(); home_dir = System.getProperty("user.home"); } public static void main(String[] args) { kpi k = new kpi(); try { k.work(args); } catch (Exception ee) { ee.printStackTrace(); } } public static String toStringNum(double i, int precision) { NumberFormat nf = NumberFormat.getInstance(); if (0.0 == i) { // Don't pad precision on '0', just adds needless clutter. return nf.format(i); } else { nf = (NumberFormat)(nf.clone()); nf.setMaximumFractionDigits(precision); nf.setMinimumFractionDigits(precision); return nf.format(i); } } public void work(String[] args) { String dir = null; String results_url = ""; String caseid = ""; String slack_fname = ""; String testbed_name = "LANforge CICD"; for (int i = 0; i test_names = new Hashtable(); Vector test_namesv = new Vector(); Vector runs = new Vector(); test_names.put(PF, PF); test_namesv.add(PF); try { DirectoryStream stream = Files.newDirectoryStream(Paths.get(dir)); for (Path file: stream) { File f = file.toFile(); // this is the test run dir // Inside of it is individual tests. if (!f.isDirectory()) { continue; } //System.out.println("Checking sub-directory/file (run): " + f.getAbsolutePath()); DirectoryStream stream2 = Files.newDirectoryStream(file); Run run = null; for (Path file2: stream2) { File f2 = file2.toFile(); // this is the test case dir in the test run //System.out.println("Checking test-case directory/file: " + f2.getAbsolutePath()); // Directory full of test results? if (f2.isDirectory()) { DirectoryStream stream3 = Files.newDirectoryStream(file2); Test test = new Test(f2.getName()); if (run == null) { run = new Run(f.getName()); runs.add(run); } System.out.println("Run: " + run + " adding test: " + test); run.addTest(test); // TODO: // Add log processing. In particular, look in console logs for things like: // "ath10k_pci 0000:01:00.0: wmi command 36967 timeout, restarting hardware" // "WARNING: CPU: 0 PID: 8907 at backports-4. for (Path file3: stream3) { File f3 = file3.toFile(); String fname = f3.getName(); if (fname.equals("kpi.csv")) { try { BufferedReader br = new BufferedReader(new FileReader(f3)); if (test_names.get(f2.getName()) == null) { test_names.put(f2.getName(), f2.getName()); test_namesv.add(f2.getName()); } String line; while ((line = br.readLine()) != null) { test.addLine(line); } } catch (FileNotFoundException enf) { // ignore } catch (Exception e) { e.printStackTrace(); } }// if kpi.csv else if (fname.startsWith("kpi-") && fname.endsWith(".png")) { test.addKpiImage(fname); } else if (fname.equals("logs")) { File logs_csv = new File(f3.getAbsolutePath() + File.separator + "logs.csv"); if (logs_csv.exists()) { try { BufferedReader br = new BufferedReader(new FileReader(logs_csv)); String line; while ((line = br.readLine()) != null) { test.addLogCsv(line); } } catch (Exception e) { e.printStackTrace(); } } } }// for all files in the test dir } } } } catch (IOException | DirectoryIteratorException x) { // IOException can never be thrown by the iteration. // In this snippet, it can only be thrown by newDirectoryStream. System.err.println(x); } // Sort runs so that earliest is first. class SortbyDate implements Comparator { // Used for sorting in ascending order of // roll number public int compare(Run a, Run b) { long c = a.getDateMs() - b.getDateMs(); if (c < 0) return -1; if (c > 0) return 1; return 0; } } runs.sort(new SortbyDate()); // Create meta-test results for each run. for (int i = 0; i test_id_links = new Hashtable(); // We have read everything into memory. // For each test, generate data over time. Hashtable hist_data = new Hashtable(); Vector hist_datav = new Vector(); for (String tname: test_namesv) { System.out.println("Checking testname: " + tname + " runs.size: " + runs.size()); // For each test, find all runs that have this test and consolidate data for (int i = 0; i groupsh = new Hashtable(); Vector groupsn = new Vector(); System.out.println("Checking history-data: " + hk); for (HistRow csv : hist.csv) { String ck = csv.getName(); String title = csv.getTitle(); String units = csv.getUnits(); String g = csv.getGraphGroup(); System.out.println("csv name: " + ck); if (!(g.equals("NA") || units.equals(""))) { Vector gv = groupsh.get(g); if (gv == null) { gv = new Vector(); groupsh.put(g, gv); groupsn.add(g); } gv.add(csv); } if (units.equals("NA") || units.equals("")) { units = "Data"; } try { String cf = dir + File.separator + hk + "::" + ck + ".csv"; System.out.println("Creating gnuplot csv: " + cf); FileWriter f = new FileWriter(cf); f.write(csv.csv.toString()); f.close(); csv.setFname(cf); ShellExec exec = new ShellExec(true, true); int rv = exec.execute("gnuplot", null, true, "-e", "set ylabel \"" + units + "\"", "-e", "filename='" + cf + "'", "-e", "set title '" + title + "'", "default.plot"); if (rv != 0) { System.out.println("gnuplot for filename: " + cf + " rv: " + rv); System.out.println(exec.getOutput()); System.out.println(exec.getError()); } File png = new File("plot.png"); if (!png.exists()) { System.out.println("ERROR: File: " + png + " does not exist."); } String npng = hk + "::" + ck + ".png"; String nplot_name = dir + File.separator + npng; System.out.println("Rename plot: " + png + " to: " + nplot_name); File nf = new File(nplot_name); Path nfp = Files.move(png.toPath(), nf.toPath(), StandardCopyOption.REPLACE_EXISTING);//boolean renamed = png.renameTo(nf); if (!nf.exists()) { System.out.println("ERROR: New file: " + nf + " does not exist."); } exec = new ShellExec(true, true); rv = exec.execute("gnuplot", null, true, "-e", "filename='" + cf + "'", "mini.plot"); if (rv != 0) { System.out.println("mini gnuplot for filename: " + cf + " rv: " + rv); System.out.println(exec.getOutput()); System.out.println(exec.getError()); } png = new File("plot.png"); String npngt = hk + "::" + ck + "-thumb.png"; Files.move(png.toPath(), new File(dir + File.separator + npngt).toPath(), StandardCopyOption.REPLACE_EXISTING); String hk_str = hk; Run last = test_id_links.get(hk); if (!hk.equals(PF)) { if (last != null) { hk_str = "" + hk + ""; } } StringBuffer change = new StringBuffer(); Double cur = csv.scores.elementAt(csv.scores.size() - 1); change.append("Last Value: " + toStringNum(cur, 2)); if (csv.scores.size() > 1) { Double prev = csv.scores.elementAt(csv.scores.size() - 2); change.append("
Delta for last run: " + toStringNum((cur - prev), 2)); if (cur != 0) { change.append("
Percentage change: " + toStringNum(100.0 * ((cur - prev) / cur), 2) + "%"); } } String row_str = ("" + hk_str + "" + title + "" + change + "\n"); if (csv.getPriority() >= 100) { scores.append(row_str); } else { plots.append(row_str); } } catch (Exception ee) { ee.printStackTrace(); } } // Graph groups for (String g : groupsn) { Vector datasets = groupsh.get(g); if (datasets.size() < 2) { continue; // group of 1 doesn't count } HistRow csv0 = datasets.elementAt(0); String title = g; String units = csv0.getUnits(); // Don't group scores if (units.equalsIgnoreCase("Score")) { continue; } if (units.equals("NA") || units.equals("")) { units = "Data"; } System.out.println("title: " + title + " units: " + units); try { StringBuffer plot = new StringBuffer("plot "); StringBuffer mplot = new StringBuffer("plot "); boolean first = true; for (HistRow csv: datasets) { if (!first) { plot.append(", "); mplot.append(", "); } plot.append("\'" + csv.getFname() + "\' using 1:2 with lines title \'" + csv.getName().replace("_", " ") + "\'"); mplot.append("\'" + csv.getFname() + "\' using 1:2 with lines notitle"); first = false; } BufferedReader br = new BufferedReader(new FileReader("default_group.plot")); FileWriter f = new FileWriter("tmp.plot"); String line; while ((line = br.readLine()) != null) { f.write(line); f.write(System.lineSeparator()); } f.write(plot.toString()); f.write(System.lineSeparator()); f.close(); br.close(); //System.out.println("group plot: " + plot); ShellExec exec = new ShellExec(true, true); int rv = exec.execute("gnuplot", null, true, "-e", "set ylabel '" + units + "'", "-e", "set title '" + title + "'", "tmp.plot"); if (rv != 0) { System.out.println("gnuplot for group: " + title + " rv: " + rv); System.out.println(exec.getOutput()); System.out.println(exec.getError()); } File png = new File("plot.png"); String npng = hk + "::" + g + ".png"; Files.move(png.toPath(), new File(dir + File.separator + npng).toPath(), StandardCopyOption.REPLACE_EXISTING); br = new BufferedReader(new FileReader("mini_group.plot")); f = new FileWriter("tmp.plot"); while ((line = br.readLine()) != null) { f.write(line); f.write(System.lineSeparator()); } f.write(mplot.toString()); f.write(System.lineSeparator()); f.close(); br.close(); exec = new ShellExec(true, true); rv = exec.execute("gnuplot", null, true, "tmp.plot"); if (rv != 0) { System.out.println("mini gnuplot for group: " + title + " rv: " + rv); System.out.println(exec.getOutput()); System.out.println(exec.getError()); } png = new File("plot.png"); String npngt = hk + "::" + g + "-thumb.png"; Files.move(png.toPath(), new File(dir + File.separator + npngt).toPath(), StandardCopyOption.REPLACE_EXISTING); String hk_str = hk; Run last = test_id_links.get(hk); if (!hk.equals(PF)) { if (last != null) { hk_str = "" + hk + ""; } } groups.append("" + hk_str + "" + title + "\n"); } catch (Exception ee) { ee.printStackTrace(); } } } String test_bed = "Test Bed"; String last_run = ""; String last_run_kpi_images = ""; StringBuffer pngs = new StringBuffer(); boolean cp = true; for (int i = 0; i 0) { logs_str.append(redTd(logv + "")); } else { logs_str.append(greenTd(logv + "")); } logv = run.getLogWarnings(); if (logv > 0) { logs_str.append(redTd(logv + "")); } else { logs_str.append(greenTd(logv + "")); } logv = run.getLogCrashes(); if (logv > 0) { logs_str.append(redTd(logv + "")); } else { logs_str.append(greenTd(logv + "")); } logv = run.getLogRestarting(); if (logv > 0) { logs_str.append(redTd(logv + "")); } else { logs_str.append(greenTd(logv + "")); } String row_text = ("" + i + "" + run.getName() + "" + run.getDate() + "" + run.getDutHwVer() + "" + run.getDutSwVer() + "" + run.getDutModelNum() + "" + greenTd(run.getPass() + "") + redTd(run.getFail() + "") + logs_str + "\n"); if (i == (runs.size() - 1)) { // Last run int png_row_count = 0; boolean needs_tr = true; last_run = row_text; for (Test t : run.testsv) { for (String png : t.kpi_images) { if (png.indexOf("-print") >= 0) { continue; // skip the print variants of the image. } if (needs_tr) { pngs.append(""); needs_tr = false; } String img_title = "Test: " + t.getName(); String fname = run.getName() + "/" + t.getName() + "/" + png; pngs.append("\n"); png_row_count++; if (png_row_count == 2) { png_row_count = 0; pngs.append("\n"); needs_tr = true; } } } if ((!needs_tr) && pngs.length() > 0) { pngs.append("\n"); } String testrails_msg = ("MSG Run: " + run.getName() + " Date: " + run.getDate() + " DUT-HW: " + run.getDutHwVer() + "DUT-SW: " + run.getDutSwVer() + " Passed: " + run.getPass() + " Fail: " + run.getFail() + " Log-Bugs: " + run.getLogBugs() + " Log-Warnings: " + run.getLogWarnings() + " Log-Crashes: " + run.getLogCrashes() + " Log-Restarting: " + run.getLogRestarting()); if (!caseid.equals("")) { try { // Create file for propagating results into external tool String ofile = dir + File.separator + "testrails.txt"; BufferedWriter bw = new BufferedWriter(new FileWriter(ofile)); bw.write("CASE_ID " + caseid); bw.write(System.lineSeparator()); bw.write("RUN_ID " + i); bw.write(System.lineSeparator()); bw.write(testrails_msg); bw.write(System.lineSeparator()); bw.write("MSG URL: " + results_url + "/" + run.getName()); bw.write(System.lineSeparator()); bw.close(); System.out.println("See testrails results: " + ofile); } catch (Exception eee) { eee.printStackTrace(); } } if (!slack_fname.equals("")) { String slack_content_type = "Content-type: application/json"; String slack_msg = "{\"text\":\"<" + results_url + "|" + testbed_name + ">" + " Results: <" + results_url + "/" + run.getName() + "|" + run.getName() + ">" + " Errors: " + (run.getLogCrashes() + run.getLogRestarting() + run.getLogBugs()) + " Warnings: " + run.getLogWarnings() + "\"}"; try { BufferedReader br = new BufferedReader(new FileReader(slack_fname)); String slack = br.readLine(); String cmd = "curl -X POST -H '" + slack_content_type + "' --data '" + slack_msg + "' " + slack; System.out.println(cmd); ShellExec exec = new ShellExec(true, true); int rv = exec.execute("curl", null, true, "-X", "POST", "-H", slack_content_type, "--data", slack_msg, slack); if (rv != 0) { System.out.println("curl slack post of msg: " + slack_msg + " to: " + slack + " failed.\n"); System.out.println(exec.getOutput()); System.out.println(exec.getError()); } } catch (Exception eeee) { System.out.println("slack_msg: " + slack_msg + " slack_fname: " + slack_fname); eeee.printStackTrace(); } } } runs_rows.append(row_text); if (cp) { try { String fname; copy("CandelaLogo2-90dpi-200x90-trans.png", dir + File.separator + run.getName(), dir); copy("candela_swirl_small-72h.png", dir + File.separator + run.getName(), dir); copy("canvil.ico", dir + File.separator + run.getName(), dir); copy("custom.css", dir + File.separator + run.getName(), dir); copy("report.css", dir + File.separator + run.getName(), dir); cp = false; } catch (Exception ee) { ee.printStackTrace(); } } } try { // Read in the testbed_template.html and update it with our info BufferedReader br = new BufferedReader(new FileReader(new File(kpi.TESTBED_TEMPLATE))); String ofile = dir + File.separator + "index.html"; BufferedWriter bw = new BufferedWriter(new FileWriter(ofile)); String line; while ((line = br.readLine()) != null) { line = line.replace("___TITLE___", test_bed + " Report History"); line = line.replace("___SCORE_RUNS___", scores.toString()); line = line.replace("___GROUP_GRAPHS___", groups.toString()); line = line.replace("___DATA_GRAPHS___", plots.toString()); line = line.replace("___TEST_RUNS___", runs_rows.toString()); line = line.replace("___LATEST_RUN___", last_run); line = line.replace("___LATEST_RUN_PNGS___", pngs.toString()); bw.write(line); bw.write(System.lineSeparator()); } br.close(); bw.close(); System.out.println("See " + ofile); } catch (Exception eee) { eee.printStackTrace(); } } // ~work() public void copy(String fname, String from, String to) throws Exception { Path fromp = Paths.get(from + File.separator + fname); Path top = Paths.get(to + File.separator + fname); Files.copy(fromp, top, StandardCopyOption.REPLACE_EXISTING); } } /** This holds a datapoint for a particular test result for each of the Runs. * This is used to generate historical graphs and comparisons. * If a test generates two KPI results, there will be two HistRows for that * test case. */ class HistRow { String fname; String name; String title = ""; String units = ""; String graph_group = ""; int prio = 0; StringBuffer csv = new StringBuffer(); Vector scores = new Vector(); public HistRow(Row r) { name = r.getShortDescKey(); title = r.getShortDesc(); units = r.getUnits(); graph_group = r.getGraphGroup(); prio = r.getPriority(); } int getPriority() { return prio; } String getFname() { return fname; } void setFname(String s) { fname = s; } String getName() { return name; } String getTitle() { return title; } String getUnits() { return units; } String getGraphGroup() { return graph_group; } } /** This holds all HistRow objects for each test name for each run. * It is used for generating historical graphs and comparisons. */ class History { String name; Vector csv = new Vector(); Hashtable csvh = new Hashtable(); // lookup by name public History(String n) { name = n; } public String getName() { return name; } HistRow findRow(String n) { //System.out.println("findCsv, n: " + n); return csvh.get(n); } void addRow(HistRow r) { csv.add(r); csvh.put(r.getName(), r); } } /** A Row represents a single KPI csv data point. The csv is split into tokens * for easier manipulation by other code. */ class Row { Vector rdata = new Vector(); String short_desc_key = null; String getNotes() { return rdata.elementAt(kpi.NOTES_IDX); } String getSubtestPassed() { if (rdata.size() > kpi.SUBTEST_PASS_IDX) return rdata.elementAt(kpi.SUBTEST_PASS_IDX); return ""; } String getSubtestFailed() { if (rdata.size() > kpi.SUBTEST_FAIL_IDX) return rdata.elementAt(kpi.SUBTEST_FAIL_IDX); return ""; } String getUnits() { try { return rdata.elementAt(kpi.UNITS_IDX); } catch (Exception e) { return ""; } } String getGraphGroup() { try { return rdata.elementAt(kpi.GRAPH_GROUP_IDX); } catch (Exception e) { return ""; } } int getPriority() { try { return Long.valueOf(rdata.elementAt(kpi.PRIORITY_IDX)).intValue(); } catch (Exception e) { return 0; } } String getPassFail() { return rdata.elementAt(kpi.PASS_FAIL_IDX); } String getScore() { return rdata.elementAt(kpi.NUMERIC_SCORE_IDX); } String getShortDesc() { return rdata.elementAt(kpi.SHORT_DESC_IDX); } String getTestId() { return rdata.elementAt(kpi.TEST_ID_IDX); } String getShortDescKey() { return short_desc_key; } void setShortDescKey(String s) { short_desc_key = s; } public String toString() { StringBuffer sb = new StringBuffer(); sb.append("Row " + getShortDescKey() + " "); for (int i = 0; i titles = null; Vector data = new Vector(); Hashtable descs = new Hashtable(); Vector kpi_images = new Vector(); Vector log_csv = new Vector(); int pass = 0; int fail = 0; int log_bugs = 0; int log_warnings = 0; int log_crashes = 0; int log_restarting = 0; long date_ms = 0; public String date = "NA"; public String test_rig = "NA"; public String dut_hw_version = "NA"; public String dut_sw_version = "NA"; public String dut_model_num = "NA"; public String dut_serial_num = "NA"; public Test(String n) { name = n; } public int getLogBugs() { return log_bugs; } public int getLogWarnings() { return log_warnings; } public int getLogCrashes() { return log_crashes; } public int getLogRestarting() { return log_restarting; } public String toString() { return "Name: " + name; } public int getPass() { return pass; } public int getFail() { return fail; } void addKpiImage(String s) { kpi_images.add(s); } long getDateMs() { return date_ms; } String getTestRig() { return test_rig; } String getDutHwVer() { return dut_hw_version; } String getDutSwVer() { return dut_sw_version; } String getDutSerialNum() { return dut_serial_num; } String getDutModelNum() { //System.out.println("Test: " + getName() + " model-num: " + dut_model_num); return dut_model_num; } String getDate() { return date; } String getName() { return name; } void addLogCsv(String l) { log_csv.add(l); try { StringTokenizer st = new StringTokenizer(l, "\t"); String tok = st.nextToken(); if (tok.equals("FILE")) { // title, ignore rest of this title return; } log_bugs += Long.valueOf(st.nextToken()); log_warnings += Long.valueOf(st.nextToken()); log_crashes += Long.valueOf(st.nextToken()); log_restarting += Long.valueOf(st.nextToken()); } catch (Exception e) { e.printStackTrace(); } } void addLine(String l) { if (titles == null) { titles = new Vector(); StringTokenizer st = new StringTokenizer(l, kpi.in_sep, true); boolean last_was_sep = false; while (st.hasMoreTokens()) { String tok = st.nextToken(); if (tok.equals(kpi.in_sep)) { if (last_was_sep) { titles.add(new String()); } last_was_sep = true; } else { titles.add(tok); last_was_sep = false; } } } else { Row row = new Row(); data.add(row); StringTokenizer st = new StringTokenizer(l, kpi.in_sep, true); int idx = 0; System.out.println("new line: " + l); boolean last_was_sep = false; while (st.hasMoreTokens()) { String rtok = st.nextToken(); if (rtok.equals(kpi.in_sep)) { if (last_was_sep) { row.rdata.add(new String()); idx++; } last_was_sep = true; } else { row.rdata.add(rtok); idx++; last_was_sep = false; } if ((data.size() >= 1) && (!last_was_sep) && dut_sw_version.equals("NA")) { // first row is being added if (titles.elementAt(idx - 1).equalsIgnoreCase("test-rig")) { test_rig = rtok; } else if (titles.elementAt(idx - 1).equalsIgnoreCase("dut-hw-version")) { dut_hw_version = rtok; } else if (titles.elementAt(idx - 1).equalsIgnoreCase("dut-sw-version")) { dut_sw_version = rtok; } else if (titles.elementAt(idx - 1).equalsIgnoreCase("dut-model-num")) { dut_model_num = rtok; } else if (titles.elementAt(idx - 1).equalsIgnoreCase("Date")) { //System.out.println("idx: " + idx + " rtok: " + rtok); date_ms = Long.valueOf(rtok).longValue(); date = new Date(date_ms).toString(); } } //System.out.println("idx: " + idx); } //System.out.println("done tok reading loop"); String pf = row.getPassFail().toLowerCase(); if (pf.indexOf("pass") >= 0) { pass++; } else if (pf.indexOf("fail") >= 0) { fail++; } String spass = row.getSubtestPassed(); try { if (!spass.equals("")) { pass += Long.valueOf(spass).longValue(); } } catch (Exception ee) { ee.printStackTrace(); } String sfail = row.getSubtestFailed(); try { if (!sfail.equals("")) { fail += Long.valueOf(sfail).longValue(); } } catch (Exception ee) { ee.printStackTrace(); } row.setShortDescKey(row.getShortDesc().replace(" ", "_")); //System.out.println("Row: " + row); descs.put(row.getShortDesc(), row.getShortDesc()); } }//addline }//Test /** A Run is a collection of tests. This class encompasses the entire regression test * for a particular flavor of test (basic, fast, etc). * The CI/CD process will create one or more Runs per build artifact per testbed. */ class Run { String name; Hashtable tests = new Hashtable(); Vector testsv = new Vector(); public Run(String n) { name = n; } public String toString() { return "Name: " + name; } int getPass() { int pass = 0; for (Test t: testsv) { pass += t.getPass(); } return pass; } int getFail() { int fail = 0; for (Test t: testsv) { fail += t.getFail(); } return fail; } int getLogBugs() { int fail = 0; for (Test t: testsv) { fail += t.getLogBugs(); } return fail; } int getLogWarnings() { int fail = 0; for (Test t: testsv) { fail += t.getLogWarnings(); } return fail; } int getLogCrashes() { int fail = 0; for (Test t: testsv) { fail += t.getLogCrashes(); } return fail; } int getLogRestarting() { int fail = 0; for (Test t: testsv) { fail += t.getLogRestarting(); } return fail; } Test getFirstTest() { return testsv.elementAt(0); } String getDate() { Test t = getFirstTest(); if (t != null) return t.getDate(); return ""; } long getDateMs() { Test t = getFirstTest(); if (t != null) return t.getDateMs(); return 0; } String getTestRig() { Test t = getFirstTest(); if (t != null) return t.getTestRig(); return ""; } String getDutHwVer() { Test t = getFirstTest(); if (t != null) return t.getDutHwVer(); return ""; } String getDutSwVer() { Test t = getFirstTest(); if (t != null) return t.getDutSwVer(); return ""; } String getDutSerNum() { Test t = getFirstTest(); if (t != null) return t.getDutSerialNum(); return ""; } String getDutModelNum() { Test t = getFirstTest(); if (t != null) return t.getDutModelNum(); return ""; } String getName() { return name; } void addTest(Test t) { tests.put(t.getName(), t); testsv.add(t); } Test findTest(String n) { return tests.get(n); } }//Run // From: https://stackoverflow.com/questions/882772/capturing-stdout-when-calling-runtime-exec /** * Execute external process and optionally read output buffer. */ class ShellExec { private int exitCode; private boolean readOutput, readError; private StreamGobbler errorGobbler, outputGobbler; public ShellExec() { this(false, false); } public ShellExec(boolean readOutput, boolean readError) { this.readOutput = readOutput; this.readError = readError; } /** * Execute a command. * @param command command ("c:/some/folder/script.bat" or "some/folder/script.sh") * @param workdir working directory or NULL to use command folder * @param wait wait for process to end * @param args 0..n command line arguments * @return process exit code */ public int execute(String command, String workdir, boolean wait, String...args) throws IOException { String[] cmdArr; StringBuffer dbg = new StringBuffer(); dbg.append("Command: " + command); if (args != null && args.length > 0) { cmdArr = new String[1+args.length]; cmdArr[0] = command; System.arraycopy(args, 0, cmdArr, 1, args.length); for (int i = 0; i