From 14f2808bef0abafd12931057b0270571c6e4e901 Mon Sep 17 00:00:00 2001 From: Fred Hornsey Date: Tue, 10 Sep 2024 19:48:35 -0500 Subject: [PATCH 1/4] Convert Test Matrix to Python3 This was done using https://github.com/hhatto/autopep8 to fix indentation and various issues then https://docs.python.org/3.10/library/2to3.html. I only tested that `GenerateTestMatrix.py` was still working. I don't think it would be easy to test anything else. Also: - Correct usage of executable bits, shebangs, `__name__ == "__main__"` on Python scripts. - Change `sys.path` code to allow running from any directory. - Add `.editorconfig` file. - Some misc cleanup --- .editorconfig | 8 + .gitignore | 2 + autobuild_rss.py | 269 +++--- autobuild_vars.py | 66 +- matrix_database/CompilationDB.py | 262 +++--- matrix_database/CompilationDBFileHandle.py | 14 +- matrix_database/CompilationPlatform.py | 127 +-- matrix_database/CreateDBTables.py | 33 +- matrix_database/DBConfig.py | 44 +- matrix_database/DBConnection.py | 35 +- matrix_database/README.txt | 12 +- matrix_database/RecentCompilationDBFiles.py | 8 +- matrix_database/RecentTestDBFiles.py | 7 +- matrix_database/SaveCompilationsToDB.py | 12 +- matrix_database/SaveTestsToDB.py | 14 +- matrix_database/TestDB.py | 267 +++--- matrix_database/TestDBFileHandle.py | 17 +- matrix_database/TestPlatform.py | 607 +++++++------- matrix_database/UpdateBuildTable.py | 28 +- matrix_database/utils.py | 39 +- testmatrix/GenerateTestMatrix.py | 346 ++++---- testmatrix/HTMLScoreboard.py | 858 ++++++++++---------- 22 files changed, 1582 insertions(+), 1493 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitignore mode change 100755 => 100644 matrix_database/CompilationPlatform.py mode change 100644 => 100755 matrix_database/CreateDBTables.py mode change 100644 => 100755 matrix_database/RecentTestDBFiles.py mode change 100644 => 100755 matrix_database/SaveCompilationsToDB.py mode change 100644 => 100755 matrix_database/SaveTestsToDB.py mode change 100755 => 100644 matrix_database/TestPlatform.py mode change 100644 => 100755 matrix_database/UpdateBuildTable.py mode change 100755 => 100644 matrix_database/utils.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..84a817db9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true +charset = utf-8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..9f7550b1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +.venv diff --git a/autobuild_rss.py b/autobuild_rss.py index ad236f304..f499e5919 100755 --- a/autobuild_rss.py +++ b/autobuild_rss.py @@ -1,7 +1,8 @@ -#!/usr/bin/python +#!/usr/bin/env python3 from optparse import OptionParser -import sys, string +import sys +import string import xml.sax import xml.sax.handler from xml.dom.minidom import getDOMImplementation @@ -9,32 +10,39 @@ rfc822 = "%a, %d %b %Y %H:%M:%S GMT" -def parse_args (): - parser = OptionParser () - parser.add_option ("-i", dest="input", default="", - help="Scoreboard configuration file to generate reports") - parser.add_option ("-o", dest="output", default="", - help="Filename to output report to") - parser.add_option ("-n", dest="number", default=10, - help="Number of recent builds to include") - parser.add_option ("--uri-regex", dest="uri_regex", default="", nargs=2, - help="Regular expression used to transform URIs. Must be two strings, separated by a space, ie: --uri-regex search replace") - parser.add_option ("-a", dest="name", default="DOC Group Scoreboard", - help="Feed name") - return parser.parse_args () +def parse_args(): + parser = OptionParser() + + parser.add_option("-i", dest="input", default="", + help="Scoreboard configuration file to generate reports") + parser.add_option("-o", dest="output", default="", + help="Filename to output report to") + parser.add_option("-n", dest="number", default=10, + help="Number of recent builds to include") + parser.add_option( + "--uri-regex", + dest="uri_regex", + default="", + nargs=2, + help="Regular expression used to transform URIs. Must be two strings, separated by a space, ie: --uri-regex search replace") + parser.add_option("-a", dest="name", default="DOC Group Scoreboard", + help="Feed name") + return parser.parse_args() + + +(opts, args) = parse_args() -(opts, args) = parse_args () class ScoreboardHandler (xml.sax.handler.ContentHandler): - def __init__ (self): - self.pos = list () - self.state = dict () - self.builds = list () + def __init__(self): + self.pos = list() + self.state = dict() + self.builds = list() return - def startElement (self, name, attrs): - self.pos.append (name) + def startElement(self, name, attrs): + self.pos.append(name) if name == "build": self.state["name"] = "" @@ -43,184 +51,197 @@ def startElement (self, name, attrs): return - def characters (self, content): + def characters(self, content): name = self.pos[-1] self.state[name] = content - def endElement (self, name): + def endElement(self, name): - if self.pos.pop () != name: - print "ERROR: endElement called for a state we shouldn't be in: " + name + if self.pos.pop() != name: + print("ERROR: endElement called for a state we shouldn't be in: " + name) return if name == "build": - self.builds.append ((self.state["name"], self.state["url"], self.state["sponsor"])) + self.builds.append( + (self.state["name"], + self.state["url"], + self.state["sponsor"])) + +# Helper methond to append a text node onto a DOM tree. + + +def appendTextNode(doc, parent, name, value): + ele = doc.createElement(name) + ele.appendChild(doc.createTextNode(str(value))) + parent.appendChild(ele) -### Helper methond to append a text node onto a DOM tree. -def appendTextNode (doc, parent, name, value) : - ele = doc.createElement (name) - ele.appendChild (doc.createTextNode (str (value))) - parent.appendChild (ele) -class RSSItem : - def __init__ (self): +class RSSItem: + def __init__(self): self.title = "" self.link = "" self.description = "" - self.pubDate = datetime.utcnow () + self.pubDate = datetime.utcnow() self.guid = "" - def to_xml (self, parent, doc): - item = doc.createElement ("item") + def to_xml(self, parent, doc): + item = doc.createElement("item") - appendTextNode (doc, item, "title", self.title) - appendTextNode (doc, item, "link", self.link) - appendTextNode (doc, item, "description", self.description) - appendTextNode (doc, item, "pubDate", self.pubDate.strftime (rfc822)) - appendTextNode (doc, item, "guid", self.guid) + appendTextNode(doc, item, "title", self.title) + appendTextNode(doc, item, "link", self.link) + appendTextNode(doc, item, "description", self.description) + appendTextNode(doc, item, "pubDate", self.pubDate.strftime(rfc822)) + appendTextNode(doc, item, "guid", self.guid) - parent.appendChild (item) + parent.appendChild(item) return -class RSSChannel : - def __init__ (self): + +class RSSChannel: + def __init__(self): self.title = "" self.link = "" self.desc = "" self.language = "en-us" - self.pubDate = datetime.utcnow () + self.pubDate = datetime.utcnow() self.lastBuildDate = self.pubDate self.generator = "DOC Group Scoreboard RSS System" self.managingEditor = "bczar@dre.vanderbilt.edu" self.webMaster = "bczar@dre.vanderbilt.edu" - self.items = list () - - def add_item (self, item): - self.items.append (item) - - def to_xml (self, parent, doc): - channel = doc.createElement ("channel") - appendTextNode (doc, channel, "title", self.title) - appendTextNode (doc, channel, "link", self.link) - appendTextNode (doc, channel, "description", self.desc) - appendTextNode (doc, channel, "language", self.language) - appendTextNode (doc, channel, "pubDate", self.pubDate.strftime (rfc822)) - appendTextNode (doc, channel, "lastBuildDate", self.lastBuildDate.strftime (rfc822)) - appendTextNode (doc, channel, "generator", self.generator) - appendTextNode (doc, channel, "managingEditor", self.managingEditor) - appendTextNode (doc, channel, "webMaster", self.webMaster) - - parent.appendChild (channel) + self.items = list() + + def add_item(self, item): + self.items.append(item) + + def to_xml(self, parent, doc): + channel = doc.createElement("channel") + appendTextNode(doc, channel, "title", self.title) + appendTextNode(doc, channel, "link", self.link) + appendTextNode(doc, channel, "description", self.desc) + appendTextNode(doc, channel, "language", self.language) + appendTextNode(doc, channel, "pubDate", self.pubDate.strftime(rfc822)) + appendTextNode( + doc, + channel, + "lastBuildDate", + self.lastBuildDate.strftime(rfc822)) + appendTextNode(doc, channel, "generator", self.generator) + appendTextNode(doc, channel, "managingEditor", self.managingEditor) + appendTextNode(doc, channel, "webMaster", self.webMaster) + + parent.appendChild(channel) for item in self.items: - item.to_xml (channel, doc) + item.to_xml(channel, doc) + -class RSS : - def __init__ (self): - self.channels = list () +class RSS: + def __init__(self): + self.channels = list() - def add_channel (self, channel): - self.channels.append (channel) + def add_channel(self, channel): + self.channels.append(channel) - def to_xml (self, outFile): - impl = xml.dom.minidom.getDOMImplementation () - rssdoc = impl.createDocument (None, "rss", None) + def to_xml(self, outFile): + impl = xml.dom.minidom.getDOMImplementation() + rssdoc = impl.createDocument(None, "rss", None) top = rssdoc.documentElement - top.setAttribute ("version", "2.0") + top.setAttribute("version", "2.0") for channel in self.channels: - channel.to_xml (top, rssdoc) + channel.to_xml(top, rssdoc) - outfile = file (opts.output, 'w') - outfile.write (rssdoc.toprettyxml (" ")) + outfile = file(opts.output, 'w') + outfile.write(rssdoc.toprettyxml(" ")) outfile.close # Clean up - rssdoc.unlink () + rssdoc.unlink() + -def parse (filename): - handler = ScoreboardHandler () - parser = xml.sax.make_parser () - parser.setContentHandler (handler) +def parse(filename): + handler = ScoreboardHandler() + parser = xml.sax.make_parser() + parser.setContentHandler(handler) - infile = file (filename, 'r') - parser.parse (infile) - infile.close () + infile = file(filename, 'r') + parser.parse(infile) + infile.close() return handler -def fetch_latest (builds): + +def fetch_latest(builds): import re - from urllib import urlopen + from urllib.request import urlopen - latest_builds = list () + latest_builds = list() - valid_latest = re.compile ("\d\d\d\d_\d\d") + valid_latest = re.compile("\\d\\d\\d\\d_\\d\\d") for build in builds: try: uri = build[1] - if opts.uri_regex != "": - uri = re.sub (opts.uri_regex[0], - opts.uri_regex[1], - uri) + if opts.uri_regex: + uri = re.sub(opts.uri_regex[0], + opts.uri_regex[1], + uri) - latest = urlopen (uri + "/latest.txt") + latest = urlopen(uri + "/latest.txt") - #Get the contents, and make sure it represents a valid latest file - string = latest.read () - if valid_latest.match (string) != None: - latest_builds.append ((build[0],build[1],string)) + # Get the contents, and make sure it represents a valid latest file + string = latest.read() + if valid_latest.match (string) is not None: + latest_builds.append((build[0], build[1], string)) else: - print "ERROR: " + build[0] + " returned an invalid latest file!" - latest.close () - except: - print "Error: Failed to open latest file for " + build[0] + print("ERROR: " + build[0] + " returned an invalid latest file!") + latest.close() + except BaseException: + print("Error: Failed to open latest file for " + build[0]) return latest_builds -def main (): -# (opts, args) = parse_args () +def main(): if (opts.input == "") or (opts.output == ""): - print "Error: Must supply both -i and -o arguments." - return -1 + print("Error: Must supply both -i and -o arguments.") + return 1 - handler = parse (opts.input) - latest = fetch_latest (handler.builds) + handler = parse(opts.input) + latest = fetch_latest(handler.builds) - ## Sort in decending order of completion - latest.sort (cmp=lambda x, y: cmp(x[2], y[2]), reverse=True) + # Sort in decending order of completion + latest.sort(cmp=lambda x, y: cmp(x[2], y[2]), reverse=True) # Prune off all but the request number of entries... - latest = latest[0:int (opts.number)] + latest = latest[0:int(opts.number)] - chan = RSSChannel () + chan = RSSChannel() chan.title = opts.name chan.desc = "Build results" chan.link = "http://www.dre.vanderbilt.edu/scoreboard" for build in latest: - item = RSSItem () + item = RSSItem() item.title = build[0] item.link = build[1] + "/index.html" item.guid = build[1] + "/" + build[2][0:16] + "_Totals.html" item.description = build[2] - item.pubDate = datetime (int (build[2][0:4]), # Year - int (build[2][5:7]), - int (build[2][8:10]), - int (build[2][11:13]), - int (build[2][14:16])) - chan.add_item (item) + item.pubDate = datetime(int(build[2][0:4]), # Year + int(build[2][5:7]), + int(build[2][8:10]), + int(build[2][11:13]), + int(build[2][14:16])) + chan.add_item(item) - rss = RSS () - rss.add_channel (chan) + rss = RSS() + rss.add_channel(chan) - rss.to_xml (opts.output) + rss.to_xml(opts.output) return 0 -if __name__ == "__main__": - main () +if __name__ == "__main__": + main() diff --git a/autobuild_vars.py b/autobuild_vars.py index 0ff9fb62c..916064355 100755 --- a/autobuild_vars.py +++ b/autobuild_vars.py @@ -1,52 +1,57 @@ +#!/usr/bin/env python3 from optparse import OptionParser -import sys, string +import sys +import string import xml.sax import xml.sax.handler -def parse_args (): - parser = OptionParser () - parser.add_option ("-u", action="store_true", dest="unix", default=False, - help="Use Unix environment characteristics") - parser.add_option ("-w", action="store_true", dest="windows", default=False, - help="Use Windows environment characteristics") +def parse_args(): + parser = OptionParser() + + parser.add_option("-u", action="store_true", dest="unix", default=False, + help="Use Unix environment characteristics") + parser.add_option("-w", action="store_true", dest="windows", default=False, + help="Use Windows environment characteristics") + + return parser.parse_args() - return parser.parse_args () class AutobuildHandler (xml.sax.handler.ContentHandler): - def __init__ (self): - self.env_map = dict () + def __init__(self): + self.env_map = dict() # Constructor return - def startElement (self, name, attrs): + def startElement(self, name, attrs): if name == "environment": - self.env_map [attrs.get ("name")] = attrs.get ("value") + self.env_map[attrs.get("name")] = attrs.get("value") -def main (): - (opts, args) = parse_args () + +def main(): + (opts, args) = parse_args() if len(args) != 1: - print "Must pass exactly one argument to this script." + print("Must pass exactly one argument to this script.") return - + # Create the parser - handler = AutobuildHandler () - parser = xml.sax.make_parser () - parser.setContentHandler (handler) + handler = AutobuildHandler() + parser = xml.sax.make_parser() + parser.setContentHandler(handler) # Parse - inFile = file (args[0], 'r') - parser.parse (inFile) - inFile.close () + inFile = file(args[0], 'r') + parser.parse(inFile) + inFile.close() # Set up characteristics set_expr = "" sep_expr = "" var_pre_expr = "" var_post_expr = "" - + if opts.unix: set_expr = "export " sep_expr = ":" @@ -57,18 +62,17 @@ def main (): var_pre_expr = "%" var_post_expr = "%" else: - print "You must specify either unix or windows!" + print("You must specify either unix or windows!") exit - - for variable, value in handler.env_map.iteritems (): - command = set_expr + variable + "=" + value - if variable.find ("PATH") != -1: - command += sep_expr + var_pre_expr + variable + var_post_expr + for variable, value in handler.env_map.items(): + command = set_expr + variable + "=" + value - print command + if variable.find("PATH") != -1: + command += sep_expr + var_pre_expr + variable + var_post_expr + print(command) if __name__ == "__main__": - main () + main() diff --git a/matrix_database/CompilationDB.py b/matrix_database/CompilationDB.py index a91be750e..924096d89 100644 --- a/matrix_database/CompilationDB.py +++ b/matrix_database/CompilationDB.py @@ -1,5 +1,3 @@ -#!/usr/bin/python2.1 - import sys import MySQLdb import _mysql_exceptions @@ -9,141 +7,151 @@ from CompilationPlatform import * -def SaveCompilationResults2DB (builds, dbname): +def SaveCompilationResults2DB(builds, dbname): db = CompilationDB(dbname) try: - db.FillTables(builds) - except: - print "ERROR: failed to insert compilation results to database", dbname, sys.exc_type, sys.exc_value - sys.exit(-1) + db.FillTables(builds) + except BaseException: + print("ERROR: failed to insert compilation results to database", dbname, sys.exc_info()[0], sys.exc_info()[1]) + sys.exit(1) + class CompilationDB: - def __init__ (self, dbname): - self.project_ids = {} - self.build_ids = {} - self.build_instance_ids = {} - self.connection = DBConnection(dbname) - self.curs = self.connection.curs + def __init__(self, dbname): + self.project_ids = {} + self.build_ids = {} + self.build_instance_ids = {} + self.connection = DBConnection(dbname) + self.curs = self.connection.curs + + def FillTables(self, builds): + builds_no_dup = [] + for m in range(0, len(builds)): + dbfile_load_status, build_instance_id = self.BuildLogLoaded( + builds[m].name, builds[m].raw_file) + if dbfile_load_status == 1: + print("********* Already saved compilation results", builds[m].name, builds[m].raw_file) + continue + else: + print("********* Save compilation results", builds[m].name, builds[m].raw_file) + self.AddBuild(builds[m]) + self.AddBuildInstance( + builds[m], dbfile_load_status, build_instance_id) + if m == 0: + self.AddProjects(builds[m]) + self.AddCompilationInstance(builds[m]) - def FillTables (self, builds): - builds_no_dup = [] - for m in range(0, len(builds)): - dbfile_load_status, build_instance_id = self.BuildLogLoaded(builds -[m].name, builds[m].raw_file) - if dbfile_load_status == 1: - print "********* Already saved compilation results", builds[m].name, builds[m].raw_file - continue - else: - print "********* Save compilation results", builds[m].name, builds[m].raw_file - self.AddBuild(builds[m]) - self.AddBuildInstance(builds[m], dbfile_load_status, build_instance_id) - if m == 0: - self.AddProjects(builds[m]) - self.AddCompilationInstance(builds[m]) + def AddBuild(self, build): + name = build.name + build_id = self.GetBuildId(name) + # print "GetBuildId", name, build_id + if build_id != 0: + self.build_ids[name] = build_id + else: + if (name in BuildConfig.config) == 0: + query = "INSERT INTO build VALUES (NULL, '%s', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);" % ( + name) + else: + config = BuildConfig.config[name] + query = "INSERT INTO build VALUES (NULL, '%s', '%s', '%s', %d, '%s', %d, %d, %d, %d);" % ( + name, config[0], config[1], config[2], config[3], config[4], config[5], config[6], config[7]) + # print "AddBuild ", query + self.curs.execute(query) + self.build_ids[name] = self.curs.insert_id() + # print "AddBuild: insert ", self.curs.insert_id() - def AddBuild (self, build): - name = build.name - build_id = self.GetBuildId(name) - #print "GetBuildId", name, build_id - if build_id != 0: - self.build_ids[name] = build_id - else: - if BuildConfig.config.has_key(name) == 0: - query = "INSERT INTO build VALUES (NULL, '%s', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);" % (name) - else: - config = BuildConfig.config[name] - query = "INSERT INTO build VALUES (NULL, '%s', '%s', '%s', %d, '%s', %d, %d, %d, %d);" %(name, config[0], config[1], config[2], config[3], config[4], config[5], config[6], config[7]) - #print "AddBuild ", query - self.curs.execute(query) - self.build_ids[name] = self.curs.insert_id() - #print "AddBuild: insert ", self.curs.insert_id() + def AddBuildInstance(self, build, dbfile_load_status, build_instance_id + ): + if (build.name in self.build_ids) == 0: + self.AddBuild(build.name) + if dbfile_load_status == -1: + query = "INSERT INTO build_instance VALUES (NULL, %d, '%s', '%s', NULL, '%s', NULL, NOW());" % (self.build_ids[build.name], str( + Parser.DateTimeFromString(build.start_time)), str(Parser.DateTimeFromString(build.end_time)), build.raw_file) + self.curs.execute(query) + self.build_instance_ids[build.name] = self.curs.insert_id() + else: + query = "UPDATE build_instance SET compilation_insert_time=NOW() where build_instance_id=%d;" % (build_instance_id) + self.curs.execute(query) + self.build_instance_ids[build.name] = build_instance_id + # print "AddBuildInstance ", query - def AddBuildInstance (self, build, dbfile_load_status, build_instance_id -): - if self.build_ids.has_key(build.name) == 0: - self.AddBuild(build.name) - if dbfile_load_status == -1: - query = "INSERT INTO build_instance VALUES (NULL, %d, '%s', '%s', NULL, '%s', NULL, NOW());" % (self.build_ids[build.name], str(Parser.DateTimeFromString(build.start_time)), str(Parser.DateTimeFromString(build.end_time)), build.raw_file) - self.curs.execute(query) - self.build_instance_ids[build.name] = self.curs.insert_id() - else: - query = "UPDATE build_instance SET compilation_insert_time=NOW() where build_instance_id=%d;" % (build_instance_id) - self.curs.execute(query) - self.build_instance_ids[build.name] = build_instance_id - #print "AddBuildInstance ", query + def AddProjects(self, build): + for m in range(0, len(build.compilation_results)): + name = build.compilation_results[m].name + self.AddProject(name) - def AddProjects(self, build): - for m in range (0, len(build.compilation_results)): - name = build.compilation_results[m].name; - self.AddProject(name) + def AddProject(self, name): + project_id = self.GetProjectId(name) + if project_id == 0: + query = "INSERT INTO project VALUES (NULL, '%s');" % (name) + self.curs.execute(query) + # print "AddProject", query + self.project_ids[name] = self.curs.insert_id() + # print "AddProject: insert ", self.curs.insert_id() + else: + self.project_ids[name] = project_id - def AddProject(self, name): - project_id = self.GetProjectId(name) - if project_id == 0: - query = "INSERT INTO project VALUES (NULL, '%s');" % (name) - self.curs.execute(query) - #print "AddProject", query - self.project_ids[name] = self.curs.insert_id() - #print "AddProject: insert ", self.curs.insert_id() - else: - self.project_ids[name] = project_id + def AddCompilationInstance(self, build): + for n in range(0, len(build.compilation_results)): + if ( + build.compilation_results[n].name in self.project_ids) == 0: + self.AddProject(build.compilation_results[n].name) + if build.compilation_results[n].skipped == 1: + query = "INSERT INTO compilation_instance VALUES(%d, %d, 1, NULL, NULL);" % ( + self.project_ids[build.compilation_results[n].name], self.build_instance_ids[build.name]) + else: + query = "INSERT INTO compilation_instance VALUES(%d, %d, 0, %d, %d);" % ( + self.project_ids[build.compilation_results[n].name], self.build_instance_ids[build.name], build.compilation_results[n].num_errors, build.compilation_results[n].num_warnings) + # print "AddCompilationInstance ", query + try: + self.curs.execute(query) + except _mysql_exceptions.IntegrityError: + print("AddCompilationInstance failed: ", build.compilation_results[n].name, build.raw_file, sys.exc_info()[0], sys.exc_info()[1]) - def AddCompilationInstance(self, build): - for n in range (0, len(build.compilation_results)): - if self.project_ids.has_key(build.compilation_results[n].name) == 0: - self.AddProject(build.compilation_results[n].name) - if build.compilation_results[n].skipped == 1: - query = "INSERT INTO compilation_instance VALUES(%d, %d, 1, NULL, NULL);" % (self.project_ids[build.compilation_results[n].name], self.build_instance_ids[build.name]) - else: - query = "INSERT INTO compilation_instance VALUES(%d, %d, 0, %d, %d);" % (self.project_ids[build.compilation_results[n].name], self.build_instance_ids[build.name], build.compilation_results[n].num_errors, build.compilation_results[n].num_warnings) - #print "AddCompilationInstance ", query - try: - self.curs.execute(query) - except _mysql_exceptions.IntegrityError: - print "AddCompilationInstance failed: ", build.compilation_results[n].name, build.raw_file, sys.exc_type, sys.exc_value - - def BuildLogLoaded(self, build_name, log_fname): - dbfile_load_status = -1 - build_instance_id = -1 - query = "SELECT build_instance_id, compilation_insert_time FROM build_instance WHERE log_fname='%s';" % (log_fname) - result = self.curs.execute(query) - if result != 0: - build_instance_id = self.curs.fetchall()[0][0] - insert_time = str(self.curs.fetchall()[0][1]) - dbfile_load_status = (insert_time != "None") - #print "BuildLogLoaded ", query, "build_instance_id=", build_instance_id, "compilation_loaded=", dbfile_load_status - return dbfile_load_status, build_instance_id + def BuildLogLoaded(self, build_name, log_fname): + dbfile_load_status = -1 + build_instance_id = -1 + query = "SELECT build_instance_id, compilation_insert_time FROM build_instance WHERE log_fname='%s';" % ( + log_fname) + result = self.curs.execute(query) + if result != 0: + build_instance_id = self.curs.fetchall()[0][0] + insert_time = str(self.curs.fetchall()[0][1]) + dbfile_load_status = (insert_time != "None") + # print "BuildLogLoaded ", query, "build_instance_id=", + # build_instance_id, "compilation_loaded=", dbfile_load_status + return dbfile_load_status, build_instance_id - def GetBuildId(self, build_name): - build_id = 0 - query = "SELECT build_id FROM build WHERE build_name='%s';" % (build_name) - #print "GetBuildId ", query - ret = self.curs.execute(query) - if ret == 1: - build_id = self.curs.fetchall()[0][0] - #print "GetBuildId: ", build_id - elif ret > 1: - print "ERROR: duplicate entry for build ", build_name - return build_id + def GetBuildId(self, build_name): + build_id = 0 + query = "SELECT build_id FROM build WHERE build_name='%s';" % ( + build_name) + # print "GetBuildId ", query + ret = self.curs.execute(query) + if ret == 1: + build_id = self.curs.fetchall()[0][0] + # print "GetBuildId: ", build_id + elif ret > 1: + print("ERROR: duplicate entry for build ", build_name) + return build_id - - def GetProjectId(self, project_name): - project_id = 0 - query = "SELECT project_id FROM project WHERE project_name='%s';" % (project_name) - #print "GetProjectId", query - ret = self.curs.execute(query) - if ret == 1: - project_id = self.curs.fetchall()[0][0] - #print "GetTestId: ", project_id - elif ret > 1: - print "ERROR: duplicate entry for project", project_name - return project_id + def GetProjectId(self, project_name): + project_id = 0 + query = "SELECT project_id FROM project WHERE project_name='%s';" % ( + project_name) + # print "GetProjectId", query + ret = self.curs.execute(query) + if ret == 1: + project_id = self.curs.fetchall()[0][0] + # print "GetTestId: ", project_id + elif ret > 1: + print("ERROR: duplicate entry for project", project_name) + return project_id - def GetRecentBuildInstance(self): - query = "SELECT log_fname FROM build_instance WHERE compilation_insert_time IS NOT NULL and NOW() < compilation_insert_time + INTERVAL 4 DAY;"; - ret = self.curs.execute(query) - if ret > 0: - results = self.curs.fetchall() - for m in range(0, len(results)): - print txt2DbFname(results[m][0]) - + def GetRecentBuildInstance(self): + query = "SELECT log_fname FROM build_instance WHERE compilation_insert_time IS NOT NULL and NOW() < compilation_insert_time + INTERVAL 4 DAY;" + ret = self.curs.execute(query) + if ret > 0: + results = self.curs.fetchall() + for m in range(0, len(results)): + print(txt2DbFname(results[m][0])) diff --git a/matrix_database/CompilationDBFileHandle.py b/matrix_database/CompilationDBFileHandle.py index 68022eff4..cb4bbf588 100644 --- a/matrix_database/CompilationDBFileHandle.py +++ b/matrix_database/CompilationDBFileHandle.py @@ -1,5 +1,3 @@ -#!/usr/bin/python - from CompilationPlatform import * from utils import * @@ -9,13 +7,9 @@ def ReadCompilationDBFiles(lsfile): builds = [] for dbfile in fh.readlines(): file = trim(dbfile) - if file != "": - build = CompilationPlatform(file) - if build.valid_db_file == 1: - builds.append (build) + if file: + build = CompilationPlatform(file) + if build.valid_db_file == 1: + builds.append(build) fh.close() return builds - - - - diff --git a/matrix_database/CompilationPlatform.py b/matrix_database/CompilationPlatform.py old mode 100755 new mode 100644 index f46691c60..2da1f62b1 --- a/matrix_database/CompilationPlatform.py +++ b/matrix_database/CompilationPlatform.py @@ -1,67 +1,76 @@ -#!/usr/bin/python2.1 - -import os, sys, string, fileinput, re, math, time +import os +import sys +import string +import fileinput +import re +import math +import time from utils import * # represents one instance of one project class ACE_TAO_Compilation: - def __init__ (self, name, skipped, num_errors, num_warnings): - self.name = name - self.skipped = skipped - self.num_errors = num_errors - self.num_warnings = num_warnings + def __init__(self, name, skipped, num_errors, num_warnings): + self.name = name + self.skipped = skipped + self.num_errors = num_errors + self.num_warnings = num_warnings + class CompilationPlatform: - def __init__(self, db_file): - self.valid_db_file = 1 - self.db_file = db_file - self.name = "" - self.raw_file = "" - self.compilation_results = [] - self.start_time = "" - self.end_time = "" - self.processDBFile() - #print "platform ", self.name, self.raw_file, self.start_time, self.end_time - - def processDBFile(self): - try: - fh=open(self.db_file, "r") - except IOError: - print "ERROR: Cannot open db file", self.db_file - return + def __init__(self, db_file): + self.valid_db_file = 1 + self.db_file = db_file + self.name = "" + self.raw_file = "" + self.compilation_results = [] + self.start_time = "" + self.end_time = "" + self.processDBFile() + # print "platform ", self.name, self.raw_file, self.start_time, + # self.end_time + + def processDBFile(self): + try: + fh = open(self.db_file, "r") + except IOError: + print("ERROR: Cannot open db file", self.db_file) + return + + self.name = fh.readline().strip() + self.raw_file = fh.readline().strip() + self.start_time = fh.readline().strip() + self.end_time = fh.readline().strip() + # print "processDBFile ", self.db_file, self.name, self.raw_file, + # self.start_time, self.end_time + line = fh.readline().strip() + parse_error = 0 + while line: + splits = line.split(";") + length = len(splits) + skipped = 0 + num_errors = -1 + num_warnings = -1 + if splits[0] == "": + parse_error = 1 + break + name = splits[0] + if length == 1: + name = name.strip() + skipped = 1 + elif length == 3: + num_errors = int(splits[1]) + num_warnings = int(splits[2]) + else: + parse_error = 1 + # print "compilation_result: ", line + self.compilation_results.append( + ACE_TAO_Compilation( + name, skipped, num_errors, num_warnings)) + line = fh.readline().strip() + if (parse_error == 1 or self.name == "" or self.raw_file == "" or self.start_time == + "" or self.end_time == "" or len(self.compilation_results) == 0): + print("ERROR: invalid db file: ", self.db_file) + self.valid_db_file = 0 - self.name = trim(fh.readline()) - self.raw_file = trim(fh.readline()) - self.start_time = trim(fh.readline()) - self.end_time = trim(fh.readline()) - #print "processDBFile ", self.db_file, self.name, self.raw_file, self.start_time, self.end_time - line = trim(fh.readline()) - parse_error = 0 - while line != "": - splits = line.split(";") - length = len(splits) - skipped = 0 - num_errors = -1 - num_warnings = -1 - if splits[0] == "": - parse_error = 1 - break; - name = splits[0] - if length == 1: - name = trim(name); - skipped = 1 - elif length == 3: - num_errors = string.atoi(splits[1]) - num_warnings = string.atoi(splits[2]) - else: - parse_error = 1 - #print "compilation_result: ", line - self.compilation_results.append(ACE_TAO_Compilation (name, skipped, num_errors, num_warnings)) - line = trim(fh.readline()) - if (parse_error == 1 or self.name == "" or self.raw_file == "" or self.start_time == "" or self.end_time == "" or len(self.compilation_results) == 0): - print "ERROR: invalid db file: ", self.db_file - self.valid_db_file = 0 - - fh.close() - + fh.close() diff --git a/matrix_database/CreateDBTables.py b/matrix_database/CreateDBTables.py old mode 100644 new mode 100755 index ba278f4c3..2876ce177 --- a/matrix_database/CreateDBTables.py +++ b/matrix_database/CreateDBTables.py @@ -1,5 +1,4 @@ -#!/usr/bin/python2.1 - +#!/usr/bin/env python3 import MySQLdb import sys @@ -7,20 +6,20 @@ def CreateTables(db_name): - curs = DBConnection(db_name).curs - query = "CREATE TABLE IF NOT EXISTS build(build_id SMALLINT NOT NULL AUTO_INCREMENT, build_name VARCHAR(100) NOT NULL, hostname VARCHAR(50), os VARCHAR(20), 64bit TINYINT(1), compiler VARCHAR(20), debug TINYINT(1), optimized TINYINT(1), static TINYINT(1), minimum TINYINT(1), PRIMARY KEY(build_id));" - curs.execute(query) - query = "CREATE TABLE IF NOT EXISTS build_instance(build_instance_id INT NOT NULL AUTO_INCREMENT, build_id SMALLINT NOT NULL, start_time DATETIME, end_time DATETIME, baseline VARCHAR(20), log_fname VARCHAR(200), test_insert_time DATETIME, compilation_insert_time DATETIME, PRIMARY KEY(build_instance_id));" - curs.execute(query) - query = "CREATE TABLE IF NOT EXISTS test(test_id SMALLINT NOT NULL AUTO_INCREMENT, test_name VARCHAR(100) NOT NULL, PRIMARY KEY(test_id));" - curs.execute(query) - query = "CREATE TABLE IF NOT EXISTS test_instance(test_id SMALLINT NOT NULL, build_instance_id INT NOT NULL, status VARCHAR(1), duration_time INT, PRIMARY KEY(test_id, build_instance_id));" - curs.execute(query) - query = "CREATE TABLE IF NOT EXISTS project(project_id SMALLINT NOT NULL AUTO_INCREMENT, project_name VARCHAR(100) NOT NULL, PRIMARY KEY(project_id));" - curs.execute(query) - query = "CREATE TABLE IF NOT EXISTS compilation_instance(project_id SMALLINT NOT NULL, build_instance_id INT NOT NULL, skipped TINYINT(1), num_errors INT, num_warnings INT, PRIMARY KEY(project_id, build_instance_id));" - curs.execute(query) - + curs = DBConnection(db_name).curs + query = "CREATE TABLE IF NOT EXISTS build(build_id SMALLINT NOT NULL AUTO_INCREMENT, build_name VARCHAR(100) NOT NULL, hostname VARCHAR(50), os VARCHAR(20), 64bit TINYINT(1), compiler VARCHAR(20), debug TINYINT(1), optimized TINYINT(1), static TINYINT(1), minimum TINYINT(1), PRIMARY KEY(build_id));" + curs.execute(query) + query = "CREATE TABLE IF NOT EXISTS build_instance(build_instance_id INT NOT NULL AUTO_INCREMENT, build_id SMALLINT NOT NULL, start_time DATETIME, end_time DATETIME, baseline VARCHAR(20), log_fname VARCHAR(200), test_insert_time DATETIME, compilation_insert_time DATETIME, PRIMARY KEY(build_instance_id));" + curs.execute(query) + query = "CREATE TABLE IF NOT EXISTS test(test_id SMALLINT NOT NULL AUTO_INCREMENT, test_name VARCHAR(100) NOT NULL, PRIMARY KEY(test_id));" + curs.execute(query) + query = "CREATE TABLE IF NOT EXISTS test_instance(test_id SMALLINT NOT NULL, build_instance_id INT NOT NULL, status VARCHAR(1), duration_time INT, PRIMARY KEY(test_id, build_instance_id));" + curs.execute(query) + query = "CREATE TABLE IF NOT EXISTS project(project_id SMALLINT NOT NULL AUTO_INCREMENT, project_name VARCHAR(100) NOT NULL, PRIMARY KEY(project_id));" + curs.execute(query) + query = "CREATE TABLE IF NOT EXISTS compilation_instance(project_id SMALLINT NOT NULL, build_instance_id INT NOT NULL, skipped TINYINT(1), num_errors INT, num_warnings INT, PRIMARY KEY(project_id, build_instance_id));" + curs.execute(query) -CreateTables(sys.argv[1]) +if __name__ == '__main__': + CreateTables(sys.argv[1]) diff --git a/matrix_database/DBConfig.py b/matrix_database/DBConfig.py index 46fb04003..ec7bb8342 100644 --- a/matrix_database/DBConfig.py +++ b/matrix_database/DBConfig.py @@ -1,21 +1,23 @@ -#!/usr/bin/python - # Configuration information for the database accessing. class DBConfig: - hostname = "" - dbname = "" - username = "" - password = "" + hostname = "" + dbname = "" + username = "" + password = "" # Configuration of the *.db files. + + class DBFileConfig: - # Output directory for the *.db files. - dbdir_w = "/export/web/www/scoreboard/test_matrix_db" + # Output directory for the *.db files. + dbdir_w = "/export/web/www/scoreboard/test_matrix_db" # Configuration of the compilation db files. + + class CompilationDBFileConfig: - # Output directory for the *.db files. - dbdir_w = "/export/web/www/scoreboard/compilation_matrix_db" + # Output directory for the *.db files. + dbdir_w = "/export/web/www/scoreboard/compilation_matrix_db" # Edit your build system configuration here. # The format of the configuration: @@ -24,6 +26,24 @@ class CompilationDBFileConfig: # Note the 'is_64bit', 'is_debug', 'is_optimized', 'is_static' and 'is_minimum_corba' # are flags, the values are 0 or 1. + class BuildConfig: - config = {'Redhat_Enterprise_Linux_3_Debug': ['jane', 'RH_Enterprise_ES', 0, 'g++3.2.3', 1, 0, 0, 0], - 'Redhat_Enterprise_Linux_3_Static_Release': ['jane', 'RH_Enterprise_ES', 0, 'g++3.2.3', 0, 0, 1, 0]} + config = { + 'Redhat_Enterprise_Linux_3_Debug': [ + 'jane', + 'RH_Enterprise_ES', + 0, + 'g++3.2.3', + 1, + 0, + 0, + 0], + 'Redhat_Enterprise_Linux_3_Static_Release': [ + 'jane', + 'RH_Enterprise_ES', + 0, + 'g++3.2.3', + 0, + 0, + 1, + 0]} diff --git a/matrix_database/DBConnection.py b/matrix_database/DBConnection.py index 336f6f0dd..9b310ee5f 100644 --- a/matrix_database/DBConnection.py +++ b/matrix_database/DBConnection.py @@ -1,24 +1,25 @@ -#!/usr/bin/python2.1 - import MySQLdb import sys from utils import * from DBConfig import * + class DBConnection: - def __init__ (self, dbname): - if dbname != "": - DBConfig.dbname = dbname - self.build_instance_ids = {} - self.curs = self.Connect() + def __init__(self, dbname): + if dbname: + DBConfig.dbname = dbname + self.build_instance_ids = {} + self.curs = self.Connect() - def Connect (self): - try: - db = MySQLdb.connect(DBConfig.hostname, DBConfig.username, DBConfig.password, DBConfig.dbname); - except: - print "ERROR: failed to connect to database", DBConfig.dbname, sys.exc_type, sys.exc_value - sys.exit(-1) - curs = db.cursor(); - return curs - - + def Connect(self): + try: + db = MySQLdb.connect( + DBConfig.hostname, + DBConfig.username, + DBConfig.password, + DBConfig.dbname) + except BaseException: + sys.exit('ERROR: failed to connect to database' + ' '.join([ + DBConfig.dbname, sys.exc_info()[0], sys.exc_info()[1]]) + curs = db.cursor() + return curs diff --git a/matrix_database/README.txt b/matrix_database/README.txt index 9cf18e06d..370d77611 100644 --- a/matrix_database/README.txt +++ b/matrix_database/README.txt @@ -6,7 +6,7 @@ This README file is intended to give the basic setup of a builds database. System Requirements: Python - ~ version 2.2 + ~ version 3.10 ~ http://www.python.org/ MySQL (database) @@ -31,20 +31,20 @@ Configure the database connection settings: username - database user that will be modifying the database password - password of the database user DBFileConfig - dbdir_w - directory where the test matrix generated database files will go + dbdir_w - directory where the test matrix generated database files will go CompilationDBFileConfig dbdir_w - directory where the build matrix generated database files will go. This is NOT being used currently. The current place to specify this is in the buildmatrix/buildmatrix.pl file. BuildConfig - config - defines the nightly builds stored in the database. Each nightly build + config - defines the nightly builds stored in the database. Each nightly build should have a defined configuration. The config should be added BEFORE a running of the build is added to the database. If the running of the build was added before the config was modified then the "build" table will have to be manually updated with the information. -Create the database: +Create the database: run `python CreateDBTables.py dbname` dbname should be the same as the dbname variable in DBConfig class in DBConfig.py @@ -82,7 +82,7 @@ Adding Test Matrix db files to the Database: WEBSERVER - remote machine that has the files generated by GenerateTestMatrix.py WEBDIR - webserver directory where the files are stored on the remote machine. The directory used in a URL. - SCRIPT_DIRECTORY - full path to the matrix_database directory + SCRIPT_DIRECTORY - full path to the matrix_database directory local files - modify and then run matrix_database/InsertLocalTestDbFiles.sh > @@ -100,7 +100,7 @@ Adding Build Matrix db files to the Database: WEBSERVER - remote machine that has the files generated by GenerateTestMatrix.py WEBDIR - webserver directory where the files are stored on the remote machine. The directory used in a URL. - SCRIPT_DIRECTORY - full path to the matrix_database directory + SCRIPT_DIRECTORY - full path to the matrix_database directory local files - modify and then run matrix_database/InsertLocalCompilationDbFiles.sh > diff --git a/matrix_database/RecentCompilationDBFiles.py b/matrix_database/RecentCompilationDBFiles.py index 604a35432..f31ffd020 100755 --- a/matrix_database/RecentCompilationDBFiles.py +++ b/matrix_database/RecentCompilationDBFiles.py @@ -1,6 +1,8 @@ -#!/usr/bin/python2.1 +#!/usr/bin/env python3 import sys from CompilationDB import * -compilationdb = CompilationDB(sys.argv[1]) -compilationdb.GetRecentBuildInstance() + +if __name__ == '__main__': + compilationdb = CompilationDB(sys.argv[1]) + compilationdb.GetRecentBuildInstance() diff --git a/matrix_database/RecentTestDBFiles.py b/matrix_database/RecentTestDBFiles.py old mode 100644 new mode 100755 index caaddfd21..69d9f1123 --- a/matrix_database/RecentTestDBFiles.py +++ b/matrix_database/RecentTestDBFiles.py @@ -1,6 +1,7 @@ -#!/usr/bin/python2.1 +#!/usr/bin/env python3 from TestDB import * -testdb = TestDB(sys.argv[1]) -testdb.GetRecentBuildInstance() +if __name__ == '__main__': + testdb = TestDB(sys.argv[1]) + testdb.GetRecentBuildInstance() diff --git a/matrix_database/SaveCompilationsToDB.py b/matrix_database/SaveCompilationsToDB.py old mode 100644 new mode 100755 index b835a72ba..ebe2b1d68 --- a/matrix_database/SaveCompilationsToDB.py +++ b/matrix_database/SaveCompilationsToDB.py @@ -1,11 +1,11 @@ -#!/usr/bin/python2.1 +#!/usr/bin/env python3 import sys from CompilationDB import * from CompilationDBFileHandle import * -lsfile = sys.argv[1] -dbname = sys.argv[2] -builds = ReadCompilationDBFiles(lsfile) -SaveCompilationResults2DB (builds, dbname) - +if __name__ == '__main__': + lsfile = sys.argv[1] + dbname = sys.argv[2] + builds = ReadCompilationDBFiles(lsfile) + SaveCompilationResults2DB(builds, dbname) diff --git a/matrix_database/SaveTestsToDB.py b/matrix_database/SaveTestsToDB.py old mode 100644 new mode 100755 index 501abc571..f3c64b9a0 --- a/matrix_database/SaveTestsToDB.py +++ b/matrix_database/SaveTestsToDB.py @@ -1,11 +1,11 @@ -#!/usr/bin/python2.1 +#!/usr/bin/env python3 import sys from TestDB import * -from TestDBFileHandle import* +from TestDBFileHandle import * -lsfile = sys.argv[1] -dbname = sys.argv[2] -builds = ReadTestDBFiles(lsfile) -SaveTestResults2DB (builds, dbname) - +if __name__ == '__main__': + lsfile = sys.argv[1] + dbname = sys.argv[2] + builds = ReadTestDBFiles(lsfile) + SaveTestResults2DB(builds, dbname) diff --git a/matrix_database/TestDB.py b/matrix_database/TestDB.py index 48bdc46c7..9367d00ee 100644 --- a/matrix_database/TestDB.py +++ b/matrix_database/TestDB.py @@ -1,5 +1,3 @@ -#!/usr/bin/python2.1 - import sys import MySQLdb import _mysql_exceptions @@ -9,142 +7,151 @@ from TestPlatform import * -def SaveTestResults2DB (builds, dbname): +def SaveTestResults2DB(builds, dbname): db = TestDB(dbname) try: - db.FillTables(builds) - except: - print "ERROR: failed to insert test results to database", dbname, sys.exc_type, sys.exc_value - sys.exit(-1) + db.FillTables(builds) + except BaseException: + sys.exit('ERROR: failed to insert test results to database' + ' '.join([ + dbname, sys.exc_info()[0], sys.exc_info()[1]])) + class TestDB: - def __init__ (self, dbname): - self.test_ids = {} - self.build_ids = {} - self.build_instance_ids = {} - self.connection = DBConnection(dbname) - self.curs = self.connection.curs + def __init__(self, dbname): + self.test_ids = {} + self.build_ids = {} + self.build_instance_ids = {} + self.connection = DBConnection(dbname) + self.curs = self.connection.curs + + def FillTables(self, builds): + builds_no_dup = [] + for m in range(0, len(builds)): + dbfile_load_status, build_instance_id = self.BuildLogLoaded( + builds[m].name, builds[m].raw_file) + if dbfile_load_status == 1: + print("********* Already saved test results", builds[m].name, builds[m].raw_file) + continue + else: + print("********* Save test results", builds[m].name, builds[m].raw_file) + self.AddBuild(builds[m]) + self.AddBuildInstance( + builds[m], dbfile_load_status, build_instance_id) + if m == 0: + self.AddTests(builds[m]) + self.AddTestInstance(builds[m]) + + def AddBuild(self, build): + name = build.name + build_id = self.GetBuildId(name) + # print "GetBuildId", name, build_id + if build_id != 0: + self.build_ids[name] = build_id + else: + if (name in BuildConfig.config) == 0: + query = "INSERT INTO build VALUES (NULL, '%s', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);" % ( + name) + else: + config = BuildConfig.config[name] + query = "INSERT INTO build VALUES (NULL, '%s', '%s', '%s', %d, '%s', %d, %d, %d, %d);" % ( + name, config[0], config[1], config[2], config[3], config[4], config[5], config[6], config[7]) + # print "AddBuild ", query + self.curs.execute(query) + self.build_ids[name] = self.curs.insert_id() + # print "AddBuild: insert ", self.curs.insert_id() + + def AddBuildInstance(self, build, dbfile_load_status, build_instance_id): + if (build.name in self.build_ids) == 0: + self.AddBuild(build.name) + if dbfile_load_status == -1: + query = "INSERT INTO build_instance VALUES (NULL, %d, '%s', '%s', NULL, '%s', NOW(), NULL);" % (self.build_ids[build.name], str( + Parser.DateTimeFromString(build.start_time)), str(Parser.DateTimeFromString(build.end_time)), build.raw_file) + self.curs.execute(query) + self.build_instance_ids[build.name] = self.curs.insert_id() + else: + query = "UPDATE build_instance SET test_insert_time=NOW() where build_instance_id=%d;" % ( + build_instance_id) + self.curs.execute(query) + self.build_instance_ids[build.name] = build_instance_id + # print "AddBuildInstance ", query - def FillTables (self, builds): - builds_no_dup = [] - for m in range(0, len(builds)): - dbfile_load_status, build_instance_id = self.BuildLogLoaded(builds[m].name, builds[m].raw_file) - if dbfile_load_status == 1: - print "********* Already saved test results", builds[m].name, builds[m].raw_file - continue - else: - print "********* Save test results", builds[m].name, builds[m].raw_file - self.AddBuild(builds[m]) - self.AddBuildInstance(builds[m], dbfile_load_status, build_instance_id) - if m == 0: - self.AddTests(builds[m]) - self.AddTestInstance(builds[m]) - - def AddBuild (self, build): - name = build.name - build_id = self.GetBuildId(name) - #print "GetBuildId", name, build_id - if build_id != 0: - self.build_ids[name] = build_id - else: - if BuildConfig.config.has_key(name) == 0: - query = "INSERT INTO build VALUES (NULL, '%s', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);" % (name) - else: - config = BuildConfig.config[name] - query = "INSERT INTO build VALUES (NULL, '%s', '%s', '%s', %d, '%s', %d, %d, %d, %d);" %(name, config[0], config[1], config[2], config[3], config[4], config[5], config[6], config[7]) - #print "AddBuild ", query - self.curs.execute(query) - self.build_ids[name] = self.curs.insert_id() - #print "AddBuild: insert ", self.curs.insert_id() + def AddTests(self, build): + for m in range(0, len(build.test_results)): + name = build.test_results[m].name + self.AddTest(name) - def AddBuildInstance (self, build, dbfile_load_status, build_instance_id): - if self.build_ids.has_key(build.name) == 0: - self.AddBuild(build.name) - if dbfile_load_status == -1: - query = "INSERT INTO build_instance VALUES (NULL, %d, '%s', '%s', NULL, '%s', NOW(), NULL);" % (self.build_ids[build.name], str(Parser.DateTimeFromString(build.start_time)), str(Parser.DateTimeFromString(build.end_time)), build.raw_file) - self.curs.execute(query) - self.build_instance_ids[build.name] = self.curs.insert_id() - else: - query = "UPDATE build_instance SET test_insert_time=NOW() where build_instance_id=%d;" % (build_instance_id) - self.curs.execute(query) - self.build_instance_ids[build.name] = build_instance_id - #print "AddBuildInstance ", query + def AddTest(self, name): + test_id = self.GetTestId(name) + if test_id == 0: + query = "INSERT INTO test VALUES (NULL, '%s');" % (name) + self.curs.execute(query) + # print "AddTest ", query + self.test_ids[name] = self.curs.insert_id() + # print "AddTest: insert ", self.curs.insert_id() + else: + self.test_ids[name] = test_id - def AddTests(self, build): - for m in range (0, len(build.test_results)): - name = build.test_results[m].name; - self.AddTest(name) + def AddTestInstance(self, build): + for n in range(0, len(build.test_results)): + if (build.test_results[n].passFlag == PASS): + pass_flag = 'P' + elif (build.test_results[n].passFlag == FAIL): + pass_flag = 'F' + else: + pass_flag = 'S' + if (build.test_results[n].name in self.test_ids) == 0: + self.AddTest(build.test_results[n].name) + query = "INSERT INTO test_instance VALUES(%d, %d, '%s', '%s');" % ( + self.test_ids[build.test_results[n].name], self.build_instance_ids[build.name], pass_flag, build.test_results[n].time) + # print "AddTestInstance ", query + try: + self.curs.execute(query) + except _mysql_exceptions.IntegrityError: + print("AddTestInstance failed: ", build.test_results[n].name, build.raw_file, sys.exc_info()[0], sys.exc_info()[1]) - def AddTest(self, name): - test_id = self.GetTestId(name) - if test_id == 0: - query = "INSERT INTO test VALUES (NULL, '%s');" % (name) - self.curs.execute(query) - #print "AddTest ", query - self.test_ids[name] = self.curs.insert_id() - #print "AddTest: insert ", self.curs.insert_id() - else: - self.test_ids[name] = test_id + def BuildLogLoaded(self, build_name, log_fname): + db_load_status = -1 + build_instance_id = -1 + query = "SELECT build_instance_id, test_insert_time FROM build_instance WHERE log_fname='%s';" % ( + log_fname) + result = self.curs.execute(query) + if result != 0: + build_instance_id = self.curs.fetchall()[0][0] + insert_time = str(self.curs.fetchall()[0][1]) + db_load_status = (insert_time != "None") + # print "BuildLogLoaded ", query, "build_instance_id=", + # build_instance_id, "test_loaded=", db_load_status + return db_load_status, build_instance_id - def AddTestInstance(self, build): - for n in range (0, len(build.test_results)): - if (build.test_results[n].passFlag == PASS): - pass_flag = 'P' - elif (build.test_results[n].passFlag == FAIL): - pass_flag = 'F' - else: - pass_flag = 'S' - if self.test_ids.has_key(build.test_results[n].name) == 0: - self.AddTest(build.test_results[n].name) - query = "INSERT INTO test_instance VALUES(%d, %d, '%s', '%s');" % (self.test_ids[build.test_results[n].name], self.build_instance_ids[build.name], pass_flag, build.test_results[n].time) - #print "AddTestInstance ", query - try: - self.curs.execute(query) - except _mysql_exceptions.IntegrityError: - print "AddTestInstance failed: ", build.test_results[n].name, build.raw_file, sys.exc_type, sys.exc_value - - def BuildLogLoaded(self, build_name, log_fname): - db_load_status = -1 - build_instance_id = -1 - query = "SELECT build_instance_id, test_insert_time FROM build_instance WHERE log_fname='%s';" % (log_fname) - result = self.curs.execute(query) - if result != 0: - build_instance_id = self.curs.fetchall()[0][0] - insert_time= str(self.curs.fetchall()[0][1]) - db_load_status = (insert_time != "None") - #print "BuildLogLoaded ", query, "build_instance_id=", build_instance_id, "test_loaded=", db_load_status - return db_load_status, build_instance_id - - def GetBuildId(self, build_name): - build_id = 0 - query = "SELECT build_id FROM build WHERE build_name='%s';" % (build_name) - #print "GetBuildId ", query - ret = self.curs.execute(query) - if ret == 1: - build_id = self.curs.fetchall()[0][0] - #print "GetBuildId: ", build_id - elif ret > 1: - print "ERROR: duplicate entry for build ", build_name - return build_id + def GetBuildId(self, build_name): + build_id = 0 + query = "SELECT build_id FROM build WHERE build_name='%s';" % ( + build_name) + # print "GetBuildId ", query + ret = self.curs.execute(query) + if ret == 1: + build_id = self.curs.fetchall()[0][0] + # print "GetBuildId: ", build_id + elif ret > 1: + print("ERROR: duplicate entry for build ", build_name) + return build_id - - def GetTestId(self, test_name): - test_id = 0 - query = "SELECT test_id FROM test WHERE test_name='%s';" % (test_name) - #print "GetTestId ", query - ret = self.curs.execute(query) - if ret == 1: - test_id = self.curs.fetchall()[0][0] - #print "GetTestId: ", test_id - elif ret > 1: - print "ERROR: duplicate entry for test ", test_name - return test_id + def GetTestId(self, test_name): + test_id = 0 + query = "SELECT test_id FROM test WHERE test_name='%s';" % (test_name) + # print "GetTestId ", query + ret = self.curs.execute(query) + if ret == 1: + test_id = self.curs.fetchall()[0][0] + # print "GetTestId: ", test_id + elif ret > 1: + print("ERROR: duplicate entry for test ", test_name) + return test_id - def GetRecentBuildInstance(self): - query = "SELECT log_fname FROM build_instance WHERE test_insert_time IS NOT NULL and NOW() < test_insert_time + INTERVAL 4 DAY;"; - ret = self.curs.execute(query) - if ret > 0: - results = self.curs.fetchall() - for m in range(0, len(results)): - print txt2DbFname(results[m][0]) - + def GetRecentBuildInstance(self): + query = "SELECT log_fname FROM build_instance WHERE test_insert_time IS NOT NULL and NOW() < test_insert_time + INTERVAL 4 DAY;" + ret = self.curs.execute(query) + if ret > 0: + results = self.curs.fetchall() + for m in range(0, len(results)): + print(txt2DbFname(results[m][0])) diff --git a/matrix_database/TestDBFileHandle.py b/matrix_database/TestDBFileHandle.py index c90dde96c..79a033116 100644 --- a/matrix_database/TestDBFileHandle.py +++ b/matrix_database/TestDBFileHandle.py @@ -1,5 +1,3 @@ -#!/usr/bin/python - from TestPlatform import * from utils import * @@ -8,18 +6,15 @@ def WriteTestDBFiles(builds): for m in range(0, len(builds)): builds[m].writeDBLog() + def ReadTestDBFiles(lsfile): fh = open(lsfile, "r") builds = [] for dbfile in fh.readlines(): - file = trim(dbfile) - if file != "": - build = TestPlatform("", "", file) - if build.valid_db_file == 1: - builds.append (build) + file = dbfile.strip() + if file: + build = TestPlatform("", "", file) + if build.valid_db_file == 1: + builds.append(build) fh.close() return builds - - - - diff --git a/matrix_database/TestPlatform.py b/matrix_database/TestPlatform.py old mode 100755 new mode 100644 index dc7b9b32e..acc2ed44c --- a/matrix_database/TestPlatform.py +++ b/matrix_database/TestPlatform.py @@ -1,340 +1,341 @@ -#!/usr/bin/python - # ****************************************************************** # Author: Heather Drury # Date: 3/25/2004 # ****************************************************************** -import os, sys, string, fileinput, re, math, time +import os +import sys +import string +import fileinput +import re +import math +import time from utils import * from DBConfig import * -#patterns for log file -compile_error = re.compile(r'error',re.IGNORECASE) +# patterns for log file +compile_error = re.compile(r'error', re.IGNORECASE) test_error = re.compile(r'Error[s]?[^A-Za-z]|ERROR|fatal|FAIL:|FAILED|EXCEPTION|ACE_ASSERT|Assertion|Mismatched free|are definitely lost in loss record|: parse error|Invalid write of size|Invalid read of size|Source and destination overlap|error while loading shared libraries|free\(\): invalid pointer:|pure virtual ') -ace_test_start = re.compile (r'Running') -build_begin = re.compile(r'#################### Begin'); -build_end = re.compile(r'#################### End'); +ace_test_start = re.compile(r'Running') +build_begin = re.compile(r'#################### Begin') +build_end = re.compile(r'#################### End') -#ASSUMPTION -# each test start signfified by "auto_run_tests:" and ends with "auto_run_tests_finished:" -test_start = re.compile (r'auto_run_tests:') -test_finished = re.compile (r'auto_run_tests_finished:') +# ASSUMPTION +# each test start signfified by "auto_run_tests:" and ends with +# "auto_run_tests_finished:" +test_start = re.compile(r'auto_run_tests:') +test_finished = re.compile(r'auto_run_tests_finished:') # represents one instance of one test + + class ACE_TAO_Test: - def __init__ (self, name, result, time, flag): - self.name = name - self.result = int(result) - self.time = float(time) - self.passFlag = flag + def __init__(self, name, result, time, flag): + self.name = name + self.result = int(result) + self.time = float(time) + self.passFlag = flag + class TestMatrix: - def __init__ (self, num_platforms): - self.name = "Test Matrix for ACE/TAO" - self.max_tests = 1200 - self.testResults =[[0]*num_platforms for row in range(self.max_tests)] - for n in range (0, self.max_tests): - for m in range (0, num_platforms): - self.testResults[n][m] = SKIP - self.testNames = [] - self.fileNames = [] + def __init__(self, num_platforms): + self.name = "Test Matrix for ACE/TAO" + self.max_tests = 1200 + self.testResults = [ + [0] * + num_platforms for row in range( + self.max_tests)] + for n in range(0, self.max_tests): + for m in range(0, num_platforms): + self.testResults[n][m] = SKIP + self.testNames = [] + self.fileNames = [] - def getTestResults (self, build_num): - npass = 0 - nfail = 0 - nskip = 0 - for n in range (0, len(self.testNames)): - if self.testResults[n][build_num] == SKIP: - nskip = nskip+1 - if self.testResults[n][build_num] == FAIL: - nfail = nfail+1 - if self.testResults[n][build_num] == PASS: - npass = npass+1 - return npass, nfail, nskip + def getTestResults(self, build_num): + npass = 0 + nfail = 0 + nskip = 0 + for n in range(0, len(self.testNames)): + if self.testResults[n][build_num] == SKIP: + nskip = nskip + 1 + if self.testResults[n][build_num] == FAIL: + nfail = nfail + 1 + if self.testResults[n][build_num] == PASS: + npass = npass + 1 + return npass, nfail, nskip - def getAllTestResults (self, nbuilds): - npass = 0 - nfail = 0 - nskip = 0 - print "***", ntests, nplatforms - for m in range (0, nbuilds): - a, b, c = self.computeTestResults(m) - npass = npass + a - nfail = nfail + b - nskip = nskip + c - return npass, nfail, nskip + def getAllTestResults(self, nbuilds): + npass = 0 + nfail = 0 + nskip = 0 + print("***", ntests, nplatforms) + for m in range(0, nbuilds): + a, b, c = self.computeTestResults(m) + npass = npass + a + nfail = nfail + b + nskip = nskip + c + return npass, nfail, nskip - def addTestResult (self, build_num, name, result): - if len(self.testNames) >= self.max_tests: - print "Error: exceeded maximum number of tests" - return - if name not in self.testNames: - # test is not yet added, so add it - self.testNames.append(name) - else: - pass - # idx is index corresponding to this test - idx = self.testNames.index(name) - self.testResults[idx][build_num] = result + def addTestResult(self, build_num, name, result): + if len(self.testNames) >= self.max_tests: + print("Error: exceeded maximum number of tests") + return + if name not in self.testNames: + # test is not yet added, so add it + self.testNames.append(name) + else: + pass + # idx is index corresponding to this test + idx = self.testNames.index(name) + self.testResults[idx][build_num] = result - # get filename for later HTML writing - splits = name.split("/") - fname = "" - if len(splits) == 1: - fname = name - else: - l = len(splits) - fname = splits [l-2] - self.fileNames.append(fname) + # get filename for later HTML writing + splits = name.split("/") + fname = "" + if len(splits) == 1: + fname = name + else: + l = len(splits) + fname = splits[l - 2] + self.fileNames.append(fname) -class TestPlatform: - def __init__(self, name, raw_file, db_file=""): - self.valid_raw_file = 1 - self.valid_db_file = 1 - self.db_file = db_file - self.name = name - self.raw_file = raw_file - self.compile = PASS - self.npass = 0 - self.nfail = 0 - self.nskip = 0 - self.ACEtotal = 0 - self.ACEfail = 0 - self.TAOtotal = 0 - self.TAOfail = 0 - self.testMin = 4000.0 - self.testMax = 0.0 - self.timeTotal = 0.0 - self.test_results = [] - self.start_time = "" - self.end_time = "" - if self.db_file != "": - self.processDBLog() - else: - self.processLog() - if (self.start_time == "" or self.end_time == ""): - self.valid_raw_file = 0 - if (len(self.test_results) == 0): - print "NO TESTS RUN:" - self.valid_raw_file = 0 - #print "platform ", self.name, self.raw_file, self.start_time, self.end_time +class TestPlatform: + def __init__(self, name, raw_file, db_file=""): + self.valid_raw_file = 1 + self.valid_db_file = 1 + self.db_file = db_file + self.name = name + self.raw_file = raw_file + self.compile = PASS + self.npass = 0 + self.nfail = 0 + self.nskip = 0 + self.ACEtotal = 0 + self.ACEfail = 0 + self.TAOtotal = 0 + self.TAOfail = 0 + self.testMin = 4000.0 + self.testMax = 0.0 + self.timeTotal = 0.0 + self.test_results = [] + self.start_time = "" + self.end_time = "" + if self.db_file: + self.processDBLog() + else: + self.processLog() + if (self.start_time == "" or self.end_time == ""): + self.valid_raw_file = 0 + if (len(self.test_results) == 0): + print("NO TESTS RUN:") + self.valid_raw_file = 0 - def writeDBLog(self): - fname = TestDBFileConfig.dbdir_w + "/" + txt2DbFname(self.raw_file) - tmpfname = fname + ".tmp" - fh = open(tmpfname, "w") - fh.write(self.name + "\n") - fh.write(self.raw_file + "\n") - fh.write(self.start_time + "\n") - fh.write(self.end_time + "\n") - fh.write(str(self.compile) + "\n") - for n in range(0, len(self.test_results)): - test_str = self.test_results[n].name + ";" + str(self.test_results[n].passFlag) + ";" + str(self.test_results[n].time) - fh.write (test_str + "\n") - fh.close() - os.rename(tmpfname, fname) + # print "platform ", self.name, self.raw_file, self.start_time, + # self.end_time - def writeDBLog(self): - fname = DBFileConfig.dbdir_w + "/" + txt2DbFname(self.raw_file) - tmpfname = fname + ".tmp" - fh = open(tmpfname, "w") - fh.write(self.name + "\n") - fh.write(self.raw_file + "\n") - fh.write(self.start_time + "\n") - fh.write(self.end_time + "\n") - fh.write(str(self.compile) + "\n") - for n in range(0, len(self.test_results)): - test_str = self.test_results[n].name + ";" + str(self.test_results[n].passFlag) + ";" + str(self.test_results[n].time) - fh.write (test_str + "\n") - fh.close() - os.rename(tmpfname, fname) + def writeDBLog(self): + fname = DBFileConfig.dbdir_w + "/" + txt2DbFname(self.raw_file) + tmpfname = fname + ".tmp" + fh = open(tmpfname, "w") + fh.write(self.name + "\n") + fh.write(self.raw_file + "\n") + fh.write(self.start_time + "\n") + fh.write(self.end_time + "\n") + fh.write(str(self.compile) + "\n") + for n in range(0, len(self.test_results)): + test_str = self.test_results[n].name + ";" + str( + self.test_results[n].passFlag) + ";" + str(self.test_results[n].time) + fh.write(test_str + "\n") + fh.close() + os.rename(tmpfname, fname) - def processDBLog(self): - try: - fh=open(self.db_file, "r") - except IOError: - print "ERROR: Cannot open db file", self.db_file - return + def processDBLog(self): + try: + fh = open(self.db_file, "r") + except IOError: + print("ERROR: Cannot open db file", self.db_file) + return - self.name = trim(fh.readline()) - self.raw_file = trim(fh.readline()) - self.start_time = trim(fh.readline()) - self.end_time = trim(fh.readline()) - self.compile = string.atoi(trim(fh.readline())) - #print "processDBLog ", self.db_file, self.name, self.raw_file, self.start_time, self.end_time, self.compile - line = trim(fh.readline()) - parse_error = 0 - while line != "": - splits = line.split(";") - if len(splits) != 3 or splits[0] == "": - parse_error = 1 - break - name = splits[0] - passflag = string.atoi(splits[1]) - time = string.atof(trim(splits[2])) - #print "test_result: ", line - self.test_results.append(ACE_TAO_Test (name, 0, time, passflag)) - line = trim(fh.readline()) - if (parse_error == 1 or self.name == "" or self.raw_file == "" or self.start_time == "" or self.end_time == "" or len(self.test_results) == 0): - print "ERROR: invalid db file: ", self.db_file - return + self.name = fh.readline().strip() + self.raw_file = fh.readline().strip() + self.start_time = fh.readline().strip() + self.end_time = fh.readline().strip() + self.compile = int(fh.readline().strip()) + # print "processDBLog ", self.db_file, self.name, self.raw_file, + # self.start_time, self.end_time, self.compile + parse_error = 0 + for line in fh: + line = line.strip() + splits = line.split(";") + if len(splits) != 3 or splits[0] == "": + parse_error = 1 + break + name = splits[0] + passflag = int(splits[1]) + time = float(splits[2].strip()) + # print "test_result: ", line + self.test_results.append(ACE_TAO_Test(name, 0, time, passflag)) + if (parse_error == 1 or self.name == "" or self.raw_file == "" or self.start_time == + "" or self.end_time == "" or len(self.test_results) == 0): + print("ERROR: invalid db file: ", self.db_file) + return - fh.close() + fh.close() - def addtest (self, name, result, time, flag): - self.test_results.append(ACE_TAO_Test (name, result, time, flag)) + def addtest(self, name, result, time, flag): + self.test_results.append(ACE_TAO_Test(name, result, time, flag)) - def testTime (self, name, time): - if time > self.testMax: - # Don't count the ACE test as the maximum time test - if name != "tests/run_test.pl": - self.testMax = time - if time < self.testMin: - self.testMin = time - self.timeTotal = self.timeTotal + time + def testTime(self, name, time): + if time > self.testMax: + # Don't count the ACE test as the maximum time test + if name != "tests/run_test.pl": + self.testMax = time + if time < self.testMin: + self.testMin = time + self.timeTotal = self.timeTotal + time - def checkTestTime (self, line): - tokens = line.split() - result = 0 - time = 0.0 + def checkTestTime(self, line): + tokens = line.split() + result = 0 + time = 0.0 - # this strips off the "auto_run_tests:, and the time and status - # leaves the test name plus any arguments - splits = line.split() - testname = ' '.join(splits[1 : -2]) - if len(tokens) > 2: - ttime=tokens[2] - if len(tokens) < 3: - ttime = "" - tresult = "" - else: - ttime = tokens[len(tokens)-2] - tresult = tokens[len(tokens)-1] - if ttime != "": - e,f=ttime.split(":") - time=int(f.replace('s','')) - self.testTime (testname, time) + # this strips off the "auto_run_tests:, and the time and status + # leaves the test name plus any arguments + splits = line.split() + testname = ' '.join(splits[1: -2]) + if len(tokens) > 2: + ttime = tokens[2] + if len(tokens) < 3: + ttime = "" + tresult = "" + else: + ttime = tokens[len(tokens) - 2] + tresult = tokens[len(tokens) - 1] + if ttime: + e, f = ttime.split(":") + time = int(f.replace('s', '')) + self.testTime(testname, time) - if tresult != "": - f,h=tresult.split(":") - result=h - return testname, result, time + if tresult: + f, h = tresult.split(":") + result = h + return testname, result, time - def sortByTime (self): - # Sort test result by test time - self.test_results.sort (lambda x, y: cmp (x.time, y.time)) - self.test_results.reverse() + def sortByTime(self): + # Sort test result by test time + self.test_results.sort(lambda x, y: cmp(x.time, y.time)) + self.test_results.reverse() - def printResults (self): - print "\n********************" - print "Results for build: ", self.name - print "ACE:" - print "\tTotal tests run = ", self.ACEtotal - print "\tTests failed = ", self.ACEfail - perc = 0.0 - try: - perc = (float(self.ACEfail)/float(self.ACEtotal))*100.0 - except ZeroDivisionError: - print "divide by zero attempt" - print "\tPercentage: %.1f" % perc - print "TAO:" - print "\tTotal tests run = ", self.TAOtotal - print "\tTests failed = ", self.TAOfail - try: - perc = (float(self.TAOfail)/float(self.TAOtotal))*100.0 - except ZeroDivisionError: - print "divide by zero attempt" - print "\tPercentage: %.1f" % perc - if self.testMin != 4000.0: - print "Tests:" - print "\tmin Test = %.1f min" % (float(self.testMin)/60.0) - print "\tmax Test = %.1f min" % (float(self.testMax)/60.0) - print "\tTotal Test = %.1f min" % (float(self.timeTotal)/60.0) + def printResults(self): + print("\n********************") + print("Results for build: ", self.name) + print("ACE:") + print("\tTotal tests run = ", self.ACEtotal) + print("\tTests failed = ", self.ACEfail) + perc = 0.0 + try: + perc = (float(self.ACEfail) / float(self.ACEtotal)) * 100.0 + except ZeroDivisionError: + print("divide by zero attempt") + print("\tPercentage: %.1f" % perc) + print("TAO:") + print("\tTotal tests run = ", self.TAOtotal) + print("\tTests failed = ", self.TAOfail) + try: + perc = (float(self.TAOfail) / float(self.TAOtotal)) * 100.0 + except ZeroDivisionError: + print("divide by zero attempt") + print("\tPercentage: %.1f" % perc) + if self.testMin != 4000.0: + print("Tests:") + print("\tmin Test = %.1f min" % (float(self.testMin) / 60.0)) + print("\tmax Test = %.1f min" % (float(self.testMax) / 60.0)) + print("\tTotal Test = %.1f min" % (float(self.timeTotal) / 60.0)) - def ACEorTAOTest (self, testname): - splits = testname.split("/") - if splits[0] == "TAO": - testType = TAO_TEST - else: - testType = ACE_TEST - return testType + def ACEorTAOTest(self, testname): + splits = testname.split("/") + if splits[0] == "TAO": + testType = TAO_TEST + else: + testType = ACE_TEST + return testType - def scanForTestFailure (self, fh, stop_strings): - line = fh.readline() - fail = 0 - time = 0.0 - testname = "" - stop = findString (line, stop_strings) - while line != "" and stop == 1: - if test_error.search(line): - fail = 1 - if test_finished.match(line): - testname, resultCode, time = self.checkTestTime(line) - line = fh.readline() - stop = findString (line, stop_strings) - if build_end.match(line): - m = line.find('[') - n = line.find('UTC') - self.end_time = line[m+1:n] - break - return line, fail, time, testname + def scanForTestFailure(self, fh, stop_strings): + fail = 0 + time = 0.0 + testname = "" + for line in fh: + line = line.strip() + if any([line.startswith(s) for s in stop_strings]): + break + elif test_error.search(line): + fail = 1 + elif test_finished.match(line): + testname, resultCode, time = self.checkTestTime(line) + elif build_end.match(line): + m = line.find('[') + n = line.find('UTC') + self.end_time = line[m + 1:n] + break + return line, fail, time, testname - def processLog (self): - try: - file = open(self.raw_file, "r") - except IOError: - print "ERROR: Cannot open file", self.raw_file - return - state = 0 - line = file.readline() - if line == "": - print "ERROR: file is empty:", self.raw_file - return + def processLog(self): + try: + file = open(self.raw_file, "r") + except IOError: + print("ERROR: Cannot open file", self.raw_file) + return + state = 0 + line = file.readline() + if line == "": + print("ERROR: file is empty:", self.raw_file) + return - time = 0.0 - # scan thru the file, state=0 while in pre-test stuff and state=1 while in tests - while line != "": - readline = 0 - if build_begin.match(line): - m = line.find('[') - n = line.find('UTC') - self.start_time = line[m+1:n] - line = file.readline() - continue - if build_end.match(line): - m = line.find('[') - n = line.find('UTC') - self.end_time = line[m+1:n] - break - if test_start.match(line): - if (state < 1): - # we found where tests are starting - state=state+1 - if state==0: - # this matches only lines that begin with "Error" or "ERROR" - if compile_error.match(line) and self.compile == PASS: - self.compile = FAIL - if state==1 and test_start.match(line): - testname = (line.split())[1] - orig_testname = testname - stop_strings = ["auto_run_tests:"] - test_type = self.ACEorTAOTest (testname) - line, fail, time, testname = self.scanForTestFailure (file, stop_strings) - if testname == "": - testname = orig_testname - fail = 1 - self.addtest (testname, 0, time, fail) - if test_type == TAO_TEST: - self.TAOtotal = self.TAOtotal + 1 - if fail==1: - self.TAOfail = self.TAOfail + 1 - elif test_type == ACE_TEST: - self.ACEtotal = self.ACEtotal + 1 - if fail==1: - self.ACEfail = self.ACEfail + 1 - readline = 1 - if readline == 0: - line = file.readline() - file.close() + time = 0.0 + # scan thru the file, state=0 while in pre-test stuff and state=1 while + # in tests + while line: + readline = 0 + if build_begin.match(line): + m = line.find('[') + n = line.find('UTC') + self.start_time = line[m + 1:n] + line = file.readline() + continue + if build_end.match(line): + m = line.find('[') + n = line.find('UTC') + self.end_time = line[m + 1:n] + break + if test_start.match(line): + if (state < 1): + # we found where tests are starting + state = state + 1 + if state == 0: + # this matches only lines that begin with "Error" or "ERROR" + if compile_error.match(line) and self.compile == PASS: + self.compile = FAIL + if state == 1 and test_start.match(line): + testname = (line.split())[1] + orig_testname = testname + stop_strings = ["auto_run_tests:"] + test_type = self.ACEorTAOTest(testname) + line, fail, time, testname = self.scanForTestFailure( + file, stop_strings) + if testname == "": + testname = orig_testname + fail = 1 + self.addtest(testname, 0, time, fail) + if test_type == TAO_TEST: + self.TAOtotal = self.TAOtotal + 1 + if fail == 1: + self.TAOfail = self.TAOfail + 1 + elif test_type == ACE_TEST: + self.ACEtotal = self.ACEtotal + 1 + if fail == 1: + self.ACEfail = self.ACEfail + 1 + readline = 1 + if readline == 0: + line = file.readline() + file.close() diff --git a/matrix_database/UpdateBuildTable.py b/matrix_database/UpdateBuildTable.py old mode 100644 new mode 100755 index a422ba5e6..0ed89d307 --- a/matrix_database/UpdateBuildTable.py +++ b/matrix_database/UpdateBuildTable.py @@ -1,5 +1,4 @@ -#!/usr/bin/python2.1 - +#!/usr/bin/env python3 import MySQLdb import sys @@ -7,17 +6,18 @@ def UpdateBuildTable(db_name): - curs = DBConnection(db_name).curs - for k in BuildConfig.config.keys(): - query = "SELECT * from build where build_name='%s';" % k; - #print query - result = curs.execute(query) - if result != 0: - v = BuildConfig.config[k] - query = "UPDATE build set hostname='%s', os='%s', 64bit=%d, compiler='%s', debug=%d, optimized=%d, static=%d, minimum=%d where build_name='%s';" % (v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], k) - #print query - curs.execute(query) - + curs = DBConnection(db_name).curs + for k in list(BuildConfig.config.keys()): + query = "SELECT * from build where build_name='%s';" % k + # print query + result = curs.execute(query) + if result != 0: + v = BuildConfig.config[k] + query = "UPDATE build set hostname='%s', os='%s', 64bit=%d, compiler='%s', debug=%d, optimized=%d, static=%d, minimum=%d where build_name='%s';" % ( + v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], k) + # print query + curs.execute(query) -UpdateBuildTable(sys.argv[1]) +if __name__ == '__main__': + UpdateBuildTable(sys.argv[1]) diff --git a/matrix_database/utils.py b/matrix_database/utils.py old mode 100755 new mode 100644 index 8949136b9..ec07fde49 --- a/matrix_database/utils.py +++ b/matrix_database/utils.py @@ -1,5 +1,3 @@ -#!/usr/bin/python2.1 - PASS = 0 FAIL = 1 SKIP = 3 @@ -10,29 +8,20 @@ ACE_TEST = 0 TAO_TEST = 1 -def findString (string, string_array): - found = 1 - for m in range (0, len(string_array)): - if string.find(string_array[m]) == 0: -# print "Found string", line - found = 0 - return found -def ComputePercentage (numerator, denominator): - perc = 0.0 - try: - perc = (float(numerator)/float(denominator))*100.0 - except ZeroDivisionError: - pass -# print "divide by zero attempt" - return perc +def ComputePercentage(numerator, denominator): + perc = 0.0 + try: + perc = (float(numerator) / float(denominator)) * 100.0 + except ZeroDivisionError: + pass + # print("divide by zero attempt") + return perc -def trim(line): - return line.strip(); -def txt2DbFname (txtFname): - splits = txtFname.split("/") - length = len(splits) - dbFname = splits[length - 2] + "_" + splits[length - 1] - dbFname = dbFname.replace(".txt", ".db") - return dbFname +def txt2DbFname(txtFname): + splits = txtFname.split("/") + length = len(splits) + dbFname = splits[length - 2] + "_" + splits[length - 1] + dbFname = dbFname.replace(".txt", ".db") + return dbFname diff --git a/testmatrix/GenerateTestMatrix.py b/testmatrix/GenerateTestMatrix.py index 39b9131ed..fa7a32650 100755 --- a/testmatrix/GenerateTestMatrix.py +++ b/testmatrix/GenerateTestMatrix.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 # ****************************************************************** # Author: Heather Drury @@ -7,179 +7,191 @@ # ****************************************************************** import sys -sys.path.append("../matrix_database") +from pathlib import Path + +matrix_database = str(Path(__file__).resolve().parent.parent / 'matrix_database') +if matrix_database not in sys.path: + sys.path.append(matrix_database) from HTMLScoreboard import * from TestPlatform import * from utils import * from TestDBFileHandle import * -import string - -#Uncomment when you need save the test results to database -# from TestDB import * - - -def ReadLogFiles (): - fh=open(infile,"r") - builds = [] - # read in text files containing test results for each platform - for line in fh.readlines(): - name, file = line.split() - build = TestPlatform (name, file) - if (build.valid_raw_file == 1): - print "********* BUILD", name - builds.append (build) - else: - print "ERROR: invalid log file:", name, file - fh.close() - return builds - -def SaveBuildResults2HTML (directory, fname): - # now print the results to an HTML file - for n in range (0, len(builds)): - builds[n].HTMLtestResults = HTMLPlatformTestTable(builds[n].name, directory) - - # print out the failed tests - for m in range (0, len(builds[n].test_results)): - if builds[n].test_results[m].passFlag == FAIL: - builds[n].HTMLtestResults.addData2 (n, builds[n].test_results[m].passFlag, \ - builds[n].test_results[m].time, \ - builds[n].test_results[m].name) - # print out the successful tests - for m in range (0, len(builds[n].test_results)): - if builds[n].test_results[m].passFlag == PASS: - builds[n].HTMLtestResults.addData2 (n, builds[n].test_results[m].passFlag, \ - builds[n].test_results[m].time, \ - builds[n].test_results[m].name) - builds[n].HTMLtestResults.writeHTML() + + +def ReadLogFiles(): + fh = open(infile, "r") + builds = [] + # read in text files containing test results for each platform + for line in fh.readlines(): + name, file = line.split() + build = TestPlatform(name, file) + if (build.valid_raw_file == 1): + print("********* BUILD", name) + builds.append(build) + else: + print("ERROR: invalid log file:", name, file) + fh.close() + return builds + + +def SaveBuildResults2HTML(directory, fname): + # now print the results to an HTML file + for n, build in enumerate(builds): + build.HTMLtestResults = HTMLPlatformTestTable(build.name, directory) + + # print out the failed, then passed tests + for pass_state in (FAIL, PASS): + for test_result in build.test_results: + if test_result.passFlag == FAIL: + build.HTMLtestResults.addData2( + n, + test_result.passFlag, + test_result.time, + test_result.name) + + build.HTMLtestResults.writeHTML() # build up test matrix -def BuildTestMatrix (builds, directory, fname, filename): - testMatrix = TestMatrix (len(builds)) - for n in range (0, len(builds)): - for m in range (0, len(builds[n].test_results)): - testMatrix.addTestResult (n, builds[n].test_results[m].name, \ - builds[n].test_results[m].passFlag) - - # write out the HTML - HTMLtestMatrix = HTMLTestMatrix2 (fname, directory) - for n in range (0, len(testMatrix.testNames)): - HTMLtestMatrix.addTestData (testMatrix.testNames [n], testMatrix.testResults[n], \ - testMatrix.fileNames [n], builds) - - totalPass = 0 - totalFail = 0 - totalSkip = 0 - for n in range (0, len(builds)): - builds[n].npass, builds[n].nfail, builds[n].nskip = testMatrix.getTestResults(n) - totalPass = totalPass + builds[n].npass - totalFail = totalFail + builds[n].nfail - totalSkip = totalSkip + builds[n].nskip - HTMLtestMatrix.writeBriefs (totalPass, totalFail, totalSkip) - - for n in range (0, len(builds)): - HTMLtestMatrix.writeBuildSummary (n, builds[n]) - getSummaryResults(HTMLtestMatrix, builds, filename) - HTMLtestMatrix.writeHTML() - HTMLtestMatrix.writeHTMLsummary() - return testMatrix + + +def BuildTestMatrix(builds, directory, fname, filename): + testMatrix = TestMatrix(len(builds)) + for n, build in enumerate(builds): + for test_result in build.test_results: + testMatrix.addTestResult(n, test_result.name, test_result.passFlag) + + # write out the HTML + HTMLtestMatrix = HTMLTestMatrix2(fname, directory) + for n, name in enumerate(testMatrix.testNames): + HTMLtestMatrix.addTestData( + name, + testMatrix.testResults[n], + testMatrix.fileNames[n], + builds) + + totalPass = 0 + totalFail = 0 + totalSkip = 0 + for n, build in enumerate(builds): + build.npass, build.nfail, build.nskip = testMatrix.getTestResults(n) + totalPass += build.npass + totalFail += build.nfail + totalSkip += build.nskip + HTMLtestMatrix.writeBriefs(totalPass, totalFail, totalSkip) + + for n, build in enumerate(builds): + HTMLtestMatrix.writeBuildSummary(n, build) + getSummaryResults(HTMLtestMatrix, builds, filename) + HTMLtestMatrix.writeHTML() + HTMLtestMatrix.writeHTMLsummary() + return testMatrix # Write one HTML file for each test with each row representing a platform -def WriteTestHTML (testMatrix, builds): - # print out HTML file for each test with results for all platforms - for n in range (0, len(builds)): - print "\tPlatform: ", builds[n].name, builds[n].ACEtotal, builds[n].ACEfail, builds[n].TAOtotal, builds[n].TAOfail - for m in range (0, len(testMatrix.testNames)): - fname = testMatrix.fileNames[m] + '.html' - html = HTMLTestFile (fname) - for n in range (0, len(builds)): - html.addData2 (testMatrix.testResults[m][n], builds[n].name) - html.writeHTML() - -def getSummaryResults (HTMLtestMatrix, builds, fname): - ACEtotal = 0 - TAOtotal = 0 - ACEfail = 0 - TAOfail = 0 - ACEpass = 0 - TAOpass = 0 - ACEperc = 0 - TAOperc = 0 - overall = 0 - skip = 0 - for n in range (0, len(builds)): - print "\tPlatform: ", builds[n].name, builds[n].ACEtotal, builds[n].ACEfail, builds[n].TAOtotal, builds[n].TAOfail - ACEtotal = ACEtotal+builds[n].ACEtotal - TAOtotal = TAOtotal+builds[n].TAOtotal - ACEfail = ACEfail+builds[n].ACEfail - TAOfail = TAOfail+builds[n].TAOfail - skip = skip+builds[n].nskip - ACEpass = ACEtotal-ACEfail - TAOpass = TAOtotal-TAOfail - ACEperc = ComputePercentage(ACEpass, ACEtotal) - TAOperc = ComputePercentage(TAOpass, TAOtotal) - overall = ComputePercentage((TAOpass+ACEpass), (TAOtotal+ACEtotal)) - print "ACE tests ****", ACEtotal, ACEfail, ACEperc - print "TAO tests ****", TAOtotal, TAOfail, TAOperc - file = "/tmp/matrix_output." + fname + ".txt" - fh=open(file,"w") - percent='%\n' - str = "# of ACE tests passed: %d\n" % (ACEtotal) - fh.write (str) - str = "# of ACE tests failed: %d\n" % (ACEfail) - fh.write (str) - str = "ACE percentage: %.2f" % ACEperc - str = str+percent - fh.write (str) - fh.write("\n") - - str = "# of TAO tests passed: %d\n" % (TAOtotal) - fh.write (str) - str = "# of TAO tests failed: %d\n" % (TAOfail) - fh.write (str) - str = "TAO percentage: %.2f" % TAOperc - str = str+percent - fh.write (str) - fh.write("\n") - - str = "# of tests passed: %d\n" % (TAOtotal+ACEtotal) - fh.write (str) - str = "# of tests failed: %d\n" % (TAOfail+ACEfail) - fh.write (str) - str = "# of tests skipped: %d\n" % (skip) - fh.write (str) - - str = "Overall percentage: %.0f" % overall - str = str+percent - fh.write (str) - fh.close() -# - fname = outfile + ".TestMatrix" - HTMLtestMatrix.writeSummary (ACEpass, ACEtotal, ACEperc, TAOpass, TAOtotal, TAOperc) - -option=string.atoi(sys.argv[1]) -infile=sys.argv[2] -directory=sys.argv[3] -outfile=sys.argv[4] -fname=outfile + ".matrix" - -builds = ReadLogFiles () -testMatrix = BuildTestMatrix (builds, directory, fname, outfile) -SaveBuildResults2HTML(directory,outfile) - -if option == 1: - WriteTestDBFiles(builds) - -# Temporary comment out to avoid the requirement of MySQLdb. -# Uncomment when you need save the test results to database. -#if option == 2: -# database_name = sys.argv[5] -# SaveTestResults2DB(builds, database_name) - -if option > 2: - print "ERROR: invalid option", option -# there is no standardization of how the test names are created for ACE, TAO, until there is, -# we shouldn't do this HAD 2.11.04 -#WriteTestHTML (testMatrix, builds) -print "Normal execution!" + +def WriteTestHTML(testMatrix, builds): + # print out HTML file for each test with results for all platforms + for b in builds: + print("\tPlatform: ", b.name, b.ACEtotal, b.ACEfail, b.TAOtotal, b.TAOfail) + for m, test_name in enumerate(testMatrix.testNames): + html = HTMLTestFile(test_name + '.html') + for n, build in enumerate(builds): + html.addData2(testMatrix.testResults[m][n], build.name) + html.writeHTML() + + +def getSummaryResults(HTMLtestMatrix, builds, fname): + ACEtotal = 0 + TAOtotal = 0 + ACEfail = 0 + TAOfail = 0 + ACEpass = 0 + TAOpass = 0 + ACEperc = 0 + TAOperc = 0 + overall = 0 + skip = 0 + for b in builds: + print("\tPlatform: ", b.name, b.ACEtotal, b.ACEfail, b.TAOtotal, b.TAOfail) + ACEtotal += b.ACEtotal + TAOtotal += b.TAOtotal + ACEfail += b.ACEfail + TAOfail += b.TAOfail + skip += b.nskip + ACEpass = ACEtotal - ACEfail + TAOpass = TAOtotal - TAOfail + ACEperc = ComputePercentage(ACEpass, ACEtotal) + TAOperc = ComputePercentage(TAOpass, TAOtotal) + overall = ComputePercentage((TAOpass + ACEpass), (TAOtotal + ACEtotal)) + print("ACE tests ****", ACEtotal, ACEfail, ACEperc) + print("TAO tests ****", TAOtotal, TAOfail, TAOperc) + file = "/tmp/matrix_output." + fname + ".txt" + fh = open(file, "w") + percent = '%\n' + str = "# of ACE tests passed: %d\n" % (ACEtotal) + fh.write(str) + str = "# of ACE tests failed: %d\n" % (ACEfail) + fh.write(str) + str = "ACE percentage: %.2f" % ACEperc + str = str + percent + fh.write(str) + fh.write("\n") + + str = "# of TAO tests passed: %d\n" % (TAOtotal) + fh.write(str) + str = "# of TAO tests failed: %d\n" % (TAOfail) + fh.write(str) + str = "TAO percentage: %.2f" % TAOperc + str = str + percent + fh.write(str) + fh.write("\n") + + str = "# of tests passed: %d\n" % (TAOtotal + ACEtotal) + fh.write(str) + str = "# of tests failed: %d\n" % (TAOfail + ACEfail) + fh.write(str) + str = "# of tests skipped: %d\n" % (skip) + fh.write(str) + + str = "Overall percentage: %.0f" % overall + str = str + percent + fh.write(str) + fh.close() + + fname = outfile + ".TestMatrix" + HTMLtestMatrix.writeSummary( + ACEpass, + ACEtotal, + ACEperc, + TAOpass, + TAOtotal, + TAOperc) + + +if __name__ == '__main__': + option = int(sys.argv[1]) + if option > 2: + sys.exit("ERROR: invalid option", option) + infile = sys.argv[2] + directory = sys.argv[3] + outfile = sys.argv[4] + fname = outfile + ".matrix" + if len(sys.argv) > 5: + database_name = sys.argv[5] + + builds = ReadLogFiles() + testMatrix = BuildTestMatrix(builds, directory, fname, outfile) + SaveBuildResults2HTML(directory, outfile) + + if option == 1: + import TestDB + TestDB.WriteTestDBFiles(builds) + elif option == 2: + import TestDB + TestDB.SaveTestResults2DB(builds, database_name) + + # there is no standardization of how the test names are created for ACE, TAO, until there is, + # we shouldn't do this HAD 2.11.04 + # WriteTestHTML (testMatrix, builds) + print("Normal execution!") diff --git a/testmatrix/HTMLScoreboard.py b/testmatrix/HTMLScoreboard.py index ebf669503..0e51f6d7e 100644 --- a/testmatrix/HTMLScoreboard.py +++ b/testmatrix/HTMLScoreboard.py @@ -1,5 +1,3 @@ -#!/usr/bin/python - # ****************************************************************** # Author: Heather Drury # Justin Michel @@ -7,36 +5,44 @@ # Date: 7/12/2004 # ****************************************************************** -import sys, string, fileinput, re, math, os, time +import sys +import re +import os +import time +from pathlib import Path + +matrix_database = str(Path(__file__).resolve().parent.parent / 'matrix_database') +if matrix_database not in sys.path: + sys.path.append(matrix_database) -sys.path.append("../matrix_database") from utils import * + class HTMLTestMatrix2: - def __init__ (self, title, directory): - self.title = title - self.directory = directory - self.matrix_html = None - self.matrix_header = None - self.build_summary_html = None - self.main_summary_html = None - self.tao_summary_html = None - self.ace_summary_html = None - - self.matrix_row = 0 - self.passed = 0 - self.failed = 0 - - self.highlight_html = "onmouseover=\"this.style.backgroundColor='hotpink';\" onmouseout=\"this.style.backgroundColor='';\"" - - self.html_start = """ + def __init__(self, title, directory): + self.title = title + self.directory = directory + self.matrix_html = None + self.matrix_header = None + self.build_summary_html = None + self.main_summary_html = None + self.tao_summary_html = None + self.ace_summary_html = None + + self.matrix_row = 0 + self.passed = 0 + self.failed = 0 + + self.highlight_html = "onmouseover=\"this.style.backgroundColor='hotpink';\" onmouseout=\"this.style.backgroundColor='';\"" + + self.html_start = """ - Scoreboard Matrix - - + Scoreboard Matrix + + - - + + """ - self.html_end = """ + self.html_end = """ """ - self.key_html = """ + self.key_html = """ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + +
Key
PassFailWarnSkipCompile Fail
100%<50%<90%
Key
PassFailWarnSkipCompile Fail
100%<50%<90%
""" - def getTestDataHeader(self, name, results, builds): - """Adds another header row to the test results table.""" - html = """ + def getTestDataHeader(self, name, results, builds): + """Adds another header row to the test results table.""" + html = """ - # Pass - # Fail - # Skip - % Pass + # Pass + # Fail + # Skip + % Pass """ - for n in range (0, len(results)): - number = str(n+1) - ## Insert spaces between two digit numbers, so that they will wrap - if len(number) == 2: - number = number[0]+ ' ' + number[1] - if builds: - bldname = builds[n].name - html += '' + number + '' - else: - html += '' + number + '' - - ## Add the final and end the header row - return html + 'Test Name\n' - - - def addTestData (self, name, results, linkfile, builds=None): - - if not self.matrix_html: - width = 160 + (10 * len(results)) + 500 - ## Mozilla ignores the table-layout css attribute unless you specify ' % width - html += """""" - ## Add specifiers for each build - for n in range (0, len(results)): - html += '' - ## Add the final - html += '\n' - - ## Add a caption - html += '\n' % (len(results) + 5) - - html += self.getTestDataHeader(name, results, builds) - - self.matrix_html = html - - if not self.matrix_header: - self.matrix_header = self.getTestDataHeader(name, results, builds) - - self.matrix_row += 1 - - html = self.matrix_html - - # Repeat the header row every now and then - if self.matrix_row % 20 == 0: - html += self.matrix_header - - npass = results.count (PASS) - nfail = results.count (FAIL) - nskip = results.count (SKIP) - perc = ComputePercentage (npass, npass + nfail) - sperc = "%.0f" % perc - - # If any failed - if nfail > 0: - self.failed += 1 - elif npass > 0: - self.passed += 1 - - if self.matrix_row & 1: - html += '" - - html += "" % npass - if nfail == 0: - html += "" % nfail - elif nfail == 1: - html += "" % nfail - else: - html += "" % nfail - - html += "" % nskip - html += '" - - ## now write out the ' - elif res == FAIL: - html += '' - else: - html += '' - - ## now write out the name of the test - ##fname = str(linkfile) + '.html' - if nfail == 0: - html += '' - elif nfail == 1: - html += '' - else: - html += '' - - self.matrix_html = html + "\n" - - def getSummaryHTML (self, name, npass, nfail, nskip): - total = npass + nfail - pperc = ComputePercentage (npass, total) - fperc = ComputePercentage (nfail, total) - html = """ + for n in range(0, len(results)): + number = str(n + 1) + # Insert spaces between two digit numbers, so that they will wrap + if len(number) == 2: + number = number[0] + ' ' + number[1] + if builds: + bldname = builds[n].name + html += '' + else: + html += '' + + # Add the final \n' + + def addTestData(self, name, results, linkfile, builds=None): + + if not self.matrix_html: + width = 160 + (10 * len(results)) + 500 + # Mozilla ignores the table-layout css attribute unless you specify + #
Test Results
%d%d%d%d%d' + sperc + "s for test results - for res in results: - if res == PASS: - html += '' + name + '' + name + '' + name + '
' + number + '' + number + ' and end the header row + return html + 'Test Name
' % width + html += """""" + # Add specifiers for each build + for n in range(0, len(results)): + html += '' + # Add the final + html += '\n' + + # Add a caption + html += '\n' % ( + len(results) + 5) + + html += self.getTestDataHeader(name, results, builds) + + self.matrix_html = html + + if not self.matrix_header: + self.matrix_header = self.getTestDataHeader(name, results, builds) + + self.matrix_row += 1 + + html = self.matrix_html + + # Repeat the header row every now and then + if self.matrix_row % 20 == 0: + html += self.matrix_header + + npass = results.count(PASS) + nfail = results.count(FAIL) + nskip = results.count(SKIP) + perc = ComputePercentage(npass, npass + nfail) + sperc = "%.0f" % perc + + # If any failed + if nfail > 0: + self.failed += 1 + elif npass > 0: + self.passed += 1 + + if self.matrix_row & 1: + html += '" + + html += "" % npass + if nfail == 0: + html += "" % nfail + elif nfail == 1: + html += "" % nfail + else: + html += "" % nfail + + html += "" % nskip + html += '" + + # now write out the ' + elif res == FAIL: + html += '' + else: + html += '' + + # now write out the name of the test + # fname = str(linkfile) + '.html' + if nfail == 0: + html += '' + elif nfail == 1: + html += '' + else: + html += '' + + self.matrix_html = html + "\n" + + def getSummaryHTML(self, name, npass, nfail, nskip): + total = npass + nfail + pperc = ComputePercentage(npass, total) + fperc = ComputePercentage(nfail, total) + html = """
Test Results
%d%d%d%d%d' + sperc + "s for test results + for res in results: + if res == PASS: + html += '' + name + '' + name + '' + name + '
- - - + + - - - - - - + + + + + + """ - html += '' % total - html += '' % npass - html += '' % nfail - if nskip == -1: - html += '' - else: - html += '' % nskip - html += '' % pperc - html += '' % fperc - html += '\n' - html += '
+
""" - html += name + """ + html += name + """ Summary
# Total# Pass# Fail# Skip% Pass% Fail# Total# Pass# Fail# Skip% Pass% Fail
%d%d%d-%d%.2f%.2f
\n' - html += '

%d tests passed, ' % self.passed - html += '%d failed

\n' % self.failed - return html - - def writeBriefs (self, npass, nfail, nskip): - self.main_summary_html = self.getSummaryHTML("", npass, nfail, nskip) - - def writeSummary (self, ACEpass, ACEtotal, ACEperc, TAOpass, TAOtotal, TAOperc): - if TAOtotal > 0: - self.ace_summary_html = self.getSummaryHTML("ACE ", ACEpass, (ACEtotal - ACEpass), -1) - self.tao_summary_html = self.getSummaryHTML("TAO ", TAOpass, (TAOtotal - TAOpass), -1) - else: - self.ace_summary_html = "" - self.tao_summary_html = "" - - def writeBuildSummary (self, num, build): - num += 1 - if not self.build_summary_html: - self.build_summary_html = """ + html += '%d' % total + html += '%d' % npass + html += '%d' % nfail + if nskip == -1: + html += '-' + else: + html += '%d' % nskip + html += '%.2f' % pperc + html += '%.2f' % fperc + html += '\n' + html += '\n' + html += '

%d tests passed, ' % self.passed + html += '%d failed

\n' % self.failed + return html + + def writeBriefs(self, npass, nfail, nskip): + self.main_summary_html = self.getSummaryHTML("", npass, nfail, nskip) + + def writeSummary( + self, + ACEpass, + ACEtotal, + ACEperc, + TAOpass, + TAOtotal, + TAOperc): + if TAOtotal > 0: + self.ace_summary_html = self.getSummaryHTML( + "ACE ", ACEpass, (ACEtotal - ACEpass), -1) + self.tao_summary_html = self.getSummaryHTML( + "TAO ", TAOpass, (TAOtotal - TAOpass), -1) + else: + self.ace_summary_html = "" + self.tao_summary_html = "" + + def writeBuildSummary(self, num, build): + num += 1 + if not self.build_summary_html: + self.build_summary_html = """ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """ - html = self.build_summary_html - - # now we have to write out the key to the build numbers - ace = tao = 0.0 - acecls = None - taocls = None - - if build.ACEtotal > 0: - npass = build.ACEtotal - build.ACEfail - ace = ComputePercentage (npass, build.ACEtotal) - if ace < 50.0: - acecls = "f" - elif ace < 90.0: - acecls = "w" - - if build.TAOtotal > 0: - npass = build.TAOtotal - build.TAOfail - tao = ComputePercentage (npass, build.TAOtotal) - if tao < 50.0: - taocls = "f" - elif tao < 90.0: - taocls = "w" - - if build.compile == FAIL: - html += '\n" % fname - - html += "" % num - html += '" - html += "" % build.ACEtotal - html += "" % build.ACEfail - sperc = "%.1f" % ace - if acecls: - html += '" - html += "" % build.TAOtotal - html += "" % build.TAOfail - sperc = "%.1f" % tao - if taocls: - html += '" - time = float(build.timeTotal) / 60.0 - timestr = "%.0f" % time - html += "" % build.nskip - html += "" - - self.build_summary_html = html - - def writeHTML (self): - fname = self.directory + "/" + self.title + ".html" - fname = os.path.normpath(fname) - print "Writing html to '" + fname + "'" - - if not self.build_summary_html: - self.build_summary_html = "" - if not self.matrix_html: - self.matrix_html = "" - if not self.matrix_header: - self.matrix_header = "" - - f = open(fname, "w", 1) - try: - f.write(self.html_start) - f.write(self.main_summary_html) - f.write(self.ace_summary_html) - f.write(self.tao_summary_html) - f.write(self.key_html) - f.write(self.build_summary_html) - f.write("
Build Summary
ACETAO
#Name## Fail% Pass## Fail% Pass# SkipTime (min)
Build Summary
ACETAO
#Name## Fail% Pass## Fail% Pass# SkipTime (min)
%d' + build.name + "%d%d' - else: - html += '' - html += sperc + "%d%d' - else: - html += '' - html += sperc + "%d" + timestr + "
") - f.write(self.matrix_html) - f.write(self.matrix_header) - f.write("") - f.write("
Last updated at "); - f.write(time.asctime(time.localtime())); - f.write("
"); - f.write(self.html_end) - finally: - f.close() - - def writeHTMLsummary (self): - fname = self.directory + "/" + self.title + "-summary.html" - fname = os.path.normpath(fname) - print "Writing html summary to '" + fname + "'" - - f = open(fname, "w", 1) - try: - f.write(self.html_start) - f.write(self.main_summary_html) - f.write(self.ace_summary_html) - f.write(self.tao_summary_html) - f.write(self.html_end) - finally: - f.close() + html = self.build_summary_html + + # now we have to write out the key to the build numbers + ace = tao = 0.0 + acecls = None + taocls = None + + if build.ACEtotal > 0: + npass = build.ACEtotal - build.ACEfail + ace = ComputePercentage(npass, build.ACEtotal) + if ace < 50.0: + acecls = "f" + elif ace < 90.0: + acecls = "w" + + if build.TAOtotal > 0: + npass = build.TAOtotal - build.TAOfail + tao = ComputePercentage(npass, build.TAOtotal) + if tao < 50.0: + taocls = "f" + elif tao < 90.0: + taocls = "w" + + if build.compile == FAIL: + html += '\n" % fname + + html += "%d" % num + html += '' + build.name + "" + html += "%d" % build.ACEtotal + html += "%d" % build.ACEfail + sperc = "%.1f" % ace + if acecls: + html += '' + else: + html += '' + html += sperc + "" + html += "%d" % build.TAOtotal + html += "%d" % build.TAOfail + sperc = "%.1f" % tao + if taocls: + html += '' + else: + html += '' + html += sperc + "" + time = float(build.timeTotal) / 60.0 + timestr = "%.0f" % time + html += "%d" % build.nskip + html += "" + timestr + "" + + self.build_summary_html = html + + def writeHTML(self): + fname = self.directory + "/" + self.title + ".html" + fname = os.path.normpath(fname) + print("Writing html to '" + fname + "'") + + if not self.build_summary_html: + self.build_summary_html = "" + if not self.matrix_html: + self.matrix_html = "" + if not self.matrix_header: + self.matrix_header = "" + + f = open(fname, "w", 1) + try: + f.write(self.html_start) + f.write(self.main_summary_html) + f.write(self.ace_summary_html) + f.write(self.tao_summary_html) + f.write(self.key_html) + f.write(self.build_summary_html) + f.write("") + f.write(self.matrix_html) + f.write(self.matrix_header) + f.write("") + f.write("
Last updated at ") + f.write(time.asctime(time.localtime())) + f.write("
") + f.write(self.html_end) + finally: + f.close() + + def writeHTMLsummary(self): + fname = self.directory + "/" + self.title + "-summary.html" + fname = os.path.normpath(fname) + print("Writing html summary to '" + fname + "'") + + f = open(fname, "w", 1) + try: + f.write(self.html_start) + f.write(self.main_summary_html) + f.write(self.ace_summary_html) + f.write(self.tao_summary_html) + f.write(self.html_end) + finally: + f.close() class HTMLPlatformTestTable: - def __init__ (self, title, dir): - self.title = title - self.directory = dir - self.test_table_html = None - self.rownum = 0 + def __init__(self, title, dir): + self.title = title + self.directory = dir + self.test_table_html = None + self.rownum = 0 - self.html_start = """ + self.html_start = """ - - - - + + + + """ - self.html_end = """ - + self.html_end = """ + """ - def addData2 (self, num, flag, time, name): - if not self.test_table_html: - self.test_table_html = """ + def addData2(self, num, flag, time, name): + if not self.test_table_html: + self.test_table_html = """ - - - - - - - - - + + + + + + + + + """ % self.title - html = self.test_table_html - - self.rownum += 1 - - html += "" - html += "" - if time == 0.0: - html += "" - else: - timestr = "%.0f" % time - if time > 300.0: - html += '" - - self.test_table_html = html - - def writeHTML (self): - fname = self.directory + "/" + self.title + "_j.html" - fname = os.path.normpath(fname) - print "Writing html to '" + fname + "'" - - f = open(fname, "w", 1) - try: - f.write(self.html_start) - if self.test_table_html: - f.write(self.test_table_html) - f.write("
%s Details
NamePass/FailTime
%s Details
NamePass/FailTime
" + result + "-' - elif time > 10.0: - html += '' - else: - html += '' - html += timestr + "
") - f.write(self.html_end) - finally: - f.close() + html = self.test_table_html + + self.rownum += 1 + + html += "" + html += "" + result + "" + if time == 0.0: + html += "-" + else: + timestr = "%.0f" % time + if time > 300.0: + html += '' + elif time > 10.0: + html += '' + else: + html += '' + html += timestr + "" + + self.test_table_html = html + + def writeHTML(self): + fname = self.directory + "/" + self.title + "_j.html" + fname = os.path.normpath(fname) + print("Writing html to '" + fname + "'") + + f = open(fname, "w", 1) + try: + f.write(self.html_start) + if self.test_table_html: + f.write(self.test_table_html) + f.write("") + f.write(self.html_end) + finally: + f.close() -class HTMLTestFile: - def __init__ (self, title, dir): - self.title = title - self.directory = dir - def addData2 (self, flag, name): - pass +class HTMLTestFile: + def __init__(self, title, dir): + self.title = title + self.directory = dir - def writeHTML (self): - print "HTMLTestFile not supported." + def addData2(self, flag, name): + pass + def writeHTML(self): + print("HTMLTestFile not supported.") From 570196c8249b0a5e569238c1ea2a06d1debde3a7 Mon Sep 17 00:00:00 2001 From: Fred Hornsey Date: Thu, 12 Sep 2024 02:41:41 -0500 Subject: [PATCH 2/4] Rewrite Most the Python Matrix The old code relied on parsing the autobuild output like the scoreboard, but because it's a different parser it can have different results. It now uses JSON output from scoreboard to get all of the information it needs. Also completely-removed database parts and anything that doesn't look useful anymore. --- common/prettify.pm | 122 +++++- common/utility.pm | 17 + matrix.py | 25 ++ matrix_database/CompilationDB.py | 157 ------- matrix_database/CompilationDBFileHandle.py | 15 - matrix_database/CompilationPlatform.py | 76 ---- matrix_database/CreateDBTables.py | 25 -- matrix_database/CreateDBTables.sh | 12 - matrix_database/DBConfig.py | 49 --- matrix_database/DBConnection.py | 25 -- .../GetAndInsertRemoteCompilationDbFiles.sh | 114 ------ .../GetAndInsertRemoteTestDbFiles.sh | 114 ------ .../InsertLocalCompilationDbFiles.sh | 94 ----- matrix_database/InsertLocalTestDbFiles.sh | 94 ----- matrix_database/README.txt | 123 ------ matrix_database/RecentCompilationDBFiles.py | 8 - matrix_database/RecentCompilationDBFiles.sh | 12 - matrix_database/RecentTestDBFiles.py | 7 - matrix_database/RecentTestDBFiles.sh | 12 - .../RemoveAndListCompilationDbFiles.sh | 17 - matrix_database/RemoveAndListTestDbFiles.sh | 17 - matrix_database/SaveCompilationsToDB.py | 11 - matrix_database/SaveCompilationsToDB.sh | 27 -- matrix_database/SaveTestsToDB.py | 11 - matrix_database/SaveTestsToDB.sh | 27 -- matrix_database/TestDB.py | 157 ------- matrix_database/TestDBFileHandle.py | 20 - matrix_database/TestPlatform.py | 341 ---------------- matrix_database/UpdateBuildTable.py | 23 -- matrix_database/UpdateBuildTable.sh | 12 - matrix_database/utils.py | 27 -- scoreboard.pl | 44 +- testmatrix/00README | 43 -- testmatrix/GenerateTestMatrix.py | 197 --------- testmatrix/HTMLScoreboard.py | 385 +++++++++--------- testmatrix/__init__.py | 0 testmatrix/buildMatrix | 51 --- testmatrix/matrix.css | 64 +-- testmatrix/matrix.py | 198 +++++++++ testmatrix/test-list-extract.pl | 108 ----- testmatrix/update_scoreboard.sh | 116 ------ 41 files changed, 598 insertions(+), 2399 deletions(-) create mode 100755 matrix.py delete mode 100644 matrix_database/CompilationDB.py delete mode 100644 matrix_database/CompilationDBFileHandle.py delete mode 100644 matrix_database/CompilationPlatform.py delete mode 100755 matrix_database/CreateDBTables.py delete mode 100755 matrix_database/CreateDBTables.sh delete mode 100644 matrix_database/DBConfig.py delete mode 100644 matrix_database/DBConnection.py delete mode 100755 matrix_database/GetAndInsertRemoteCompilationDbFiles.sh delete mode 100755 matrix_database/GetAndInsertRemoteTestDbFiles.sh delete mode 100755 matrix_database/InsertLocalCompilationDbFiles.sh delete mode 100644 matrix_database/InsertLocalTestDbFiles.sh delete mode 100644 matrix_database/README.txt delete mode 100755 matrix_database/RecentCompilationDBFiles.py delete mode 100755 matrix_database/RecentCompilationDBFiles.sh delete mode 100755 matrix_database/RecentTestDBFiles.py delete mode 100755 matrix_database/RecentTestDBFiles.sh delete mode 100755 matrix_database/RemoveAndListCompilationDbFiles.sh delete mode 100755 matrix_database/RemoveAndListTestDbFiles.sh delete mode 100755 matrix_database/SaveCompilationsToDB.py delete mode 100755 matrix_database/SaveCompilationsToDB.sh delete mode 100755 matrix_database/SaveTestsToDB.py delete mode 100755 matrix_database/SaveTestsToDB.sh delete mode 100644 matrix_database/TestDB.py delete mode 100644 matrix_database/TestDBFileHandle.py delete mode 100644 matrix_database/TestPlatform.py delete mode 100755 matrix_database/UpdateBuildTable.py delete mode 100755 matrix_database/UpdateBuildTable.sh delete mode 100644 matrix_database/utils.py delete mode 100644 testmatrix/00README delete mode 100755 testmatrix/GenerateTestMatrix.py create mode 100644 testmatrix/__init__.py delete mode 100755 testmatrix/buildMatrix create mode 100644 testmatrix/matrix.py delete mode 100755 testmatrix/test-list-extract.pl delete mode 100755 testmatrix/update_scoreboard.sh diff --git a/common/prettify.pm b/common/prettify.pm index 9bd0c83b9..26b713881 100644 --- a/common/prettify.pm +++ b/common/prettify.pm @@ -23,6 +23,15 @@ sub escape_html_entities_keep_headers_and_links return $s; } +sub parse_art_finished +{ + if (shift() =~ /^auto_run_tests_finished: (.*) Time:(\d+)s\s+Result:([-\d]+)/) + { + return [$1, $2 + 0, $3 + 0]; + } + return undef; +} + ############################################################################### ############################################################################### @@ -883,10 +892,11 @@ sub Normal ($) return unless defined $test; my $separator = '=' x 78 . "\n"; - if ($line =~ /^auto_run_tests_finished: .*Time:(\d+)s\s+Result:([-\d]+)/) + my $finished = Prettify::parse_art_finished($line); + if ($finished) { - $test->{TIME} = $1; - $test->{RESULT} = $2; + $test->{TIME} = $finished->[1]; + $test->{RESULT} = $finished->[2]; } elsif ($line ne $separator) { @@ -896,6 +906,110 @@ sub Normal ($) } +############################################################################### +############################################################################### + +package Prettify::BuildJson; + +use strict; +use warnings; + +############################################################################### + +sub new ($) +{ + my $proto = shift (); + my $class = ref ($proto) || $proto; + my $buildname = shift (); + my $basename = shift (); + + return bless ({ + in_test => undef, + in_tests => 0, + data => { + buildname => $buildname, + basename => $basename, + tests => [], + }, + }, $class); +} + +sub Header () +{ +} + +sub Footer () +{ + my $self = shift (); + + utility::write_obj_to_json ("$self->{data}->{basename}.build.json", $self->{data}); +} + +sub Section ($) +{ + my $self = shift (); + my $name = shift (); + + $self->{in_tests} = $name eq 'Test'; + $self->{in_test} = undef; +} + +sub Description ($) +{ +} + +sub Timestamp ($) +{ +} + +sub Subsection ($) +{ + my $self = shift (); + my $name = shift (); + + $self->{in_test} = undef; + if ($self->{in_tests}) { + my $test = { + name => $name, + extra_name => undef, + result => undef, + time => undef, + }; + $self->{in_test} = $test; + push(@{$self->{data}->{tests}}, $test); + } +} + +sub Error ($) +{ + my $self = shift (); + my $line = shift (); +} + +sub Warning ($) +{ + my $self = shift (); + my $line = shift (); +} + +sub Normal ($) +{ + my $self = shift (); + my $line = shift (); + + my $test = $self->{in_test}; + if ($test) { + my $finished = Prettify::parse_art_finished($line); + if ($finished) { + $test->{time} = $finished->[1]; + $test->{result} = $finished->[2]; + } + elsif ($line =~ /^The CMake name of this test is "(.*)"/) { + $test->{extra_name} = $1; + } + } +} + ############################################################################### ############################################################################### @@ -1319,6 +1433,8 @@ sub new ($$$$$$$$) push @{$self->{OUTPUT}}, new Prettify::JUnit ($junit); } + push @{$self->{OUTPUT}}, new Prettify::BuildJson ($buildname, $basename); + # Output the header for the files foreach my $output (@{$self->{OUTPUT}}) { unless ($output) { diff --git a/common/utility.pm b/common/utility.pm index 0b6713b90..eb3a243b5 100644 --- a/common/utility.pm +++ b/common/utility.pm @@ -5,6 +5,23 @@ package utility; use File::Path qw(rmtree); +use JSON::PP; + +sub obj_to_json +{ + return JSON::PP->new->pretty(1)->utf8->encode(shift()); +} + +sub write_obj_to_json +{ + my $path = shift(); + my $obj = shift(); + + open(my $f, '>', $path) or die("Failed to open $path: $!"); + print $f (obj_to_json($obj)); + close($f); +} + # Run command, returns 0 if there was an error. If the second argument is # passed, it's assumed to be an autobuild command result hashref and "failure" # will be set to "fatal" if it is a total failure is fatal and "non-fatal" if diff --git a/matrix.py b/matrix.py new file mode 100755 index 000000000..3ce340bd6 --- /dev/null +++ b/matrix.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +import sys +import argparse +import json +from pathlib import Path +import enum + +from testmatrix.HTMLScoreboard import write_html_matrix +from testmatrix.matrix import Builds, Matrix + + +if __name__ == '__main__': + arg_parser = argparse.ArgumentParser( + description='Generate a test status matrix from autobuild output') + arg_parser.add_argument('builds_dir', type=Path, + help='Directory with autobuild/scoreboard build contents') + arg_parser.add_argument('prefix') + args = arg_parser.parse_args() + + builds = Builds(args.builds_dir) + matrix = Matrix(builds) + matrix.dump() + + write_html_matrix(matrix, args.prefix) diff --git a/matrix_database/CompilationDB.py b/matrix_database/CompilationDB.py deleted file mode 100644 index 924096d89..000000000 --- a/matrix_database/CompilationDB.py +++ /dev/null @@ -1,157 +0,0 @@ -import sys -import MySQLdb -import _mysql_exceptions -import mx.DateTime.Parser as Parser -from DBConnection import * -from utils import * -from CompilationPlatform import * - - -def SaveCompilationResults2DB(builds, dbname): - db = CompilationDB(dbname) - try: - db.FillTables(builds) - except BaseException: - print("ERROR: failed to insert compilation results to database", dbname, sys.exc_info()[0], sys.exc_info()[1]) - sys.exit(1) - - -class CompilationDB: - def __init__(self, dbname): - self.project_ids = {} - self.build_ids = {} - self.build_instance_ids = {} - self.connection = DBConnection(dbname) - self.curs = self.connection.curs - - def FillTables(self, builds): - builds_no_dup = [] - for m in range(0, len(builds)): - dbfile_load_status, build_instance_id = self.BuildLogLoaded( - builds[m].name, builds[m].raw_file) - if dbfile_load_status == 1: - print("********* Already saved compilation results", builds[m].name, builds[m].raw_file) - continue - else: - print("********* Save compilation results", builds[m].name, builds[m].raw_file) - self.AddBuild(builds[m]) - self.AddBuildInstance( - builds[m], dbfile_load_status, build_instance_id) - if m == 0: - self.AddProjects(builds[m]) - self.AddCompilationInstance(builds[m]) - - def AddBuild(self, build): - name = build.name - build_id = self.GetBuildId(name) - # print "GetBuildId", name, build_id - if build_id != 0: - self.build_ids[name] = build_id - else: - if (name in BuildConfig.config) == 0: - query = "INSERT INTO build VALUES (NULL, '%s', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);" % ( - name) - else: - config = BuildConfig.config[name] - query = "INSERT INTO build VALUES (NULL, '%s', '%s', '%s', %d, '%s', %d, %d, %d, %d);" % ( - name, config[0], config[1], config[2], config[3], config[4], config[5], config[6], config[7]) - # print "AddBuild ", query - self.curs.execute(query) - self.build_ids[name] = self.curs.insert_id() - # print "AddBuild: insert ", self.curs.insert_id() - - def AddBuildInstance(self, build, dbfile_load_status, build_instance_id - ): - if (build.name in self.build_ids) == 0: - self.AddBuild(build.name) - if dbfile_load_status == -1: - query = "INSERT INTO build_instance VALUES (NULL, %d, '%s', '%s', NULL, '%s', NULL, NOW());" % (self.build_ids[build.name], str( - Parser.DateTimeFromString(build.start_time)), str(Parser.DateTimeFromString(build.end_time)), build.raw_file) - self.curs.execute(query) - self.build_instance_ids[build.name] = self.curs.insert_id() - else: - query = "UPDATE build_instance SET compilation_insert_time=NOW() where build_instance_id=%d;" % (build_instance_id) - self.curs.execute(query) - self.build_instance_ids[build.name] = build_instance_id - # print "AddBuildInstance ", query - - def AddProjects(self, build): - for m in range(0, len(build.compilation_results)): - name = build.compilation_results[m].name - self.AddProject(name) - - def AddProject(self, name): - project_id = self.GetProjectId(name) - if project_id == 0: - query = "INSERT INTO project VALUES (NULL, '%s');" % (name) - self.curs.execute(query) - # print "AddProject", query - self.project_ids[name] = self.curs.insert_id() - # print "AddProject: insert ", self.curs.insert_id() - else: - self.project_ids[name] = project_id - - def AddCompilationInstance(self, build): - for n in range(0, len(build.compilation_results)): - if ( - build.compilation_results[n].name in self.project_ids) == 0: - self.AddProject(build.compilation_results[n].name) - if build.compilation_results[n].skipped == 1: - query = "INSERT INTO compilation_instance VALUES(%d, %d, 1, NULL, NULL);" % ( - self.project_ids[build.compilation_results[n].name], self.build_instance_ids[build.name]) - else: - query = "INSERT INTO compilation_instance VALUES(%d, %d, 0, %d, %d);" % ( - self.project_ids[build.compilation_results[n].name], self.build_instance_ids[build.name], build.compilation_results[n].num_errors, build.compilation_results[n].num_warnings) - # print "AddCompilationInstance ", query - try: - self.curs.execute(query) - except _mysql_exceptions.IntegrityError: - print("AddCompilationInstance failed: ", build.compilation_results[n].name, build.raw_file, sys.exc_info()[0], sys.exc_info()[1]) - - def BuildLogLoaded(self, build_name, log_fname): - dbfile_load_status = -1 - build_instance_id = -1 - query = "SELECT build_instance_id, compilation_insert_time FROM build_instance WHERE log_fname='%s';" % ( - log_fname) - result = self.curs.execute(query) - if result != 0: - build_instance_id = self.curs.fetchall()[0][0] - insert_time = str(self.curs.fetchall()[0][1]) - dbfile_load_status = (insert_time != "None") - # print "BuildLogLoaded ", query, "build_instance_id=", - # build_instance_id, "compilation_loaded=", dbfile_load_status - return dbfile_load_status, build_instance_id - - def GetBuildId(self, build_name): - build_id = 0 - query = "SELECT build_id FROM build WHERE build_name='%s';" % ( - build_name) - # print "GetBuildId ", query - ret = self.curs.execute(query) - if ret == 1: - build_id = self.curs.fetchall()[0][0] - # print "GetBuildId: ", build_id - elif ret > 1: - print("ERROR: duplicate entry for build ", build_name) - return build_id - - def GetProjectId(self, project_name): - project_id = 0 - query = "SELECT project_id FROM project WHERE project_name='%s';" % ( - project_name) - # print "GetProjectId", query - ret = self.curs.execute(query) - if ret == 1: - project_id = self.curs.fetchall()[0][0] - # print "GetTestId: ", project_id - elif ret > 1: - print("ERROR: duplicate entry for project", project_name) - return project_id - - def GetRecentBuildInstance(self): - query = "SELECT log_fname FROM build_instance WHERE compilation_insert_time IS NOT NULL and NOW() < compilation_insert_time + INTERVAL 4 DAY;" - ret = self.curs.execute(query) - if ret > 0: - results = self.curs.fetchall() - for m in range(0, len(results)): - print(txt2DbFname(results[m][0])) diff --git a/matrix_database/CompilationDBFileHandle.py b/matrix_database/CompilationDBFileHandle.py deleted file mode 100644 index cb4bbf588..000000000 --- a/matrix_database/CompilationDBFileHandle.py +++ /dev/null @@ -1,15 +0,0 @@ -from CompilationPlatform import * -from utils import * - - -def ReadCompilationDBFiles(lsfile): - fh = open(lsfile, "r") - builds = [] - for dbfile in fh.readlines(): - file = trim(dbfile) - if file: - build = CompilationPlatform(file) - if build.valid_db_file == 1: - builds.append(build) - fh.close() - return builds diff --git a/matrix_database/CompilationPlatform.py b/matrix_database/CompilationPlatform.py deleted file mode 100644 index 2da1f62b1..000000000 --- a/matrix_database/CompilationPlatform.py +++ /dev/null @@ -1,76 +0,0 @@ -import os -import sys -import string -import fileinput -import re -import math -import time -from utils import * - - -# represents one instance of one project -class ACE_TAO_Compilation: - def __init__(self, name, skipped, num_errors, num_warnings): - self.name = name - self.skipped = skipped - self.num_errors = num_errors - self.num_warnings = num_warnings - - -class CompilationPlatform: - def __init__(self, db_file): - self.valid_db_file = 1 - self.db_file = db_file - self.name = "" - self.raw_file = "" - self.compilation_results = [] - self.start_time = "" - self.end_time = "" - self.processDBFile() - # print "platform ", self.name, self.raw_file, self.start_time, - # self.end_time - - def processDBFile(self): - try: - fh = open(self.db_file, "r") - except IOError: - print("ERROR: Cannot open db file", self.db_file) - return - - self.name = fh.readline().strip() - self.raw_file = fh.readline().strip() - self.start_time = fh.readline().strip() - self.end_time = fh.readline().strip() - # print "processDBFile ", self.db_file, self.name, self.raw_file, - # self.start_time, self.end_time - line = fh.readline().strip() - parse_error = 0 - while line: - splits = line.split(";") - length = len(splits) - skipped = 0 - num_errors = -1 - num_warnings = -1 - if splits[0] == "": - parse_error = 1 - break - name = splits[0] - if length == 1: - name = name.strip() - skipped = 1 - elif length == 3: - num_errors = int(splits[1]) - num_warnings = int(splits[2]) - else: - parse_error = 1 - # print "compilation_result: ", line - self.compilation_results.append( - ACE_TAO_Compilation( - name, skipped, num_errors, num_warnings)) - line = fh.readline().strip() - if (parse_error == 1 or self.name == "" or self.raw_file == "" or self.start_time == - "" or self.end_time == "" or len(self.compilation_results) == 0): - print("ERROR: invalid db file: ", self.db_file) - self.valid_db_file = 0 - - fh.close() diff --git a/matrix_database/CreateDBTables.py b/matrix_database/CreateDBTables.py deleted file mode 100755 index 2876ce177..000000000 --- a/matrix_database/CreateDBTables.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 - -import MySQLdb -import sys -from DBConnection import * - - -def CreateTables(db_name): - curs = DBConnection(db_name).curs - query = "CREATE TABLE IF NOT EXISTS build(build_id SMALLINT NOT NULL AUTO_INCREMENT, build_name VARCHAR(100) NOT NULL, hostname VARCHAR(50), os VARCHAR(20), 64bit TINYINT(1), compiler VARCHAR(20), debug TINYINT(1), optimized TINYINT(1), static TINYINT(1), minimum TINYINT(1), PRIMARY KEY(build_id));" - curs.execute(query) - query = "CREATE TABLE IF NOT EXISTS build_instance(build_instance_id INT NOT NULL AUTO_INCREMENT, build_id SMALLINT NOT NULL, start_time DATETIME, end_time DATETIME, baseline VARCHAR(20), log_fname VARCHAR(200), test_insert_time DATETIME, compilation_insert_time DATETIME, PRIMARY KEY(build_instance_id));" - curs.execute(query) - query = "CREATE TABLE IF NOT EXISTS test(test_id SMALLINT NOT NULL AUTO_INCREMENT, test_name VARCHAR(100) NOT NULL, PRIMARY KEY(test_id));" - curs.execute(query) - query = "CREATE TABLE IF NOT EXISTS test_instance(test_id SMALLINT NOT NULL, build_instance_id INT NOT NULL, status VARCHAR(1), duration_time INT, PRIMARY KEY(test_id, build_instance_id));" - curs.execute(query) - query = "CREATE TABLE IF NOT EXISTS project(project_id SMALLINT NOT NULL AUTO_INCREMENT, project_name VARCHAR(100) NOT NULL, PRIMARY KEY(project_id));" - curs.execute(query) - query = "CREATE TABLE IF NOT EXISTS compilation_instance(project_id SMALLINT NOT NULL, build_instance_id INT NOT NULL, skipped TINYINT(1), num_errors INT, num_warnings INT, PRIMARY KEY(project_id, build_instance_id));" - curs.execute(query) - - -if __name__ == '__main__': - CreateTables(sys.argv[1]) diff --git a/matrix_database/CreateDBTables.sh b/matrix_database/CreateDBTables.sh deleted file mode 100755 index 1301648bd..000000000 --- a/matrix_database/CreateDBTables.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash - -if [ $# -ne 1 ]; then - echo "usage: $0 " - exit 1 -fi - -DATABASE_NAME=$1 -TMDIR=$HOME/nightly/matrix_database -/usr/bin/python2.1 $TMDIR/CreateDBTables.py $DATABASE_NAME - - diff --git a/matrix_database/DBConfig.py b/matrix_database/DBConfig.py deleted file mode 100644 index ec7bb8342..000000000 --- a/matrix_database/DBConfig.py +++ /dev/null @@ -1,49 +0,0 @@ -# Configuration information for the database accessing. -class DBConfig: - hostname = "" - dbname = "" - username = "" - password = "" - -# Configuration of the *.db files. - - -class DBFileConfig: - # Output directory for the *.db files. - dbdir_w = "/export/web/www/scoreboard/test_matrix_db" - -# Configuration of the compilation db files. - - -class CompilationDBFileConfig: - # Output directory for the *.db files. - dbdir_w = "/export/web/www/scoreboard/compilation_matrix_db" - -# Edit your build system configuration here. -# The format of the configuration: -# 'build_name':['hostname', 'os_type', 'is_64bit', 'compiler_type', 'is_debug', -# 'is_optimized', 'is_static', 'is_minimum_corba'] -# Note the 'is_64bit', 'is_debug', 'is_optimized', 'is_static' and 'is_minimum_corba' -# are flags, the values are 0 or 1. - - -class BuildConfig: - config = { - 'Redhat_Enterprise_Linux_3_Debug': [ - 'jane', - 'RH_Enterprise_ES', - 0, - 'g++3.2.3', - 1, - 0, - 0, - 0], - 'Redhat_Enterprise_Linux_3_Static_Release': [ - 'jane', - 'RH_Enterprise_ES', - 0, - 'g++3.2.3', - 0, - 0, - 1, - 0]} diff --git a/matrix_database/DBConnection.py b/matrix_database/DBConnection.py deleted file mode 100644 index 9b310ee5f..000000000 --- a/matrix_database/DBConnection.py +++ /dev/null @@ -1,25 +0,0 @@ -import MySQLdb -import sys -from utils import * -from DBConfig import * - - -class DBConnection: - def __init__(self, dbname): - if dbname: - DBConfig.dbname = dbname - self.build_instance_ids = {} - self.curs = self.Connect() - - def Connect(self): - try: - db = MySQLdb.connect( - DBConfig.hostname, - DBConfig.username, - DBConfig.password, - DBConfig.dbname) - except BaseException: - sys.exit('ERROR: failed to connect to database' + ' '.join([ - DBConfig.dbname, sys.exc_info()[0], sys.exc_info()[1]]) - curs = db.cursor() - return curs diff --git a/matrix_database/GetAndInsertRemoteCompilationDbFiles.sh b/matrix_database/GetAndInsertRemoteCompilationDbFiles.sh deleted file mode 100755 index 07c521ee8..000000000 --- a/matrix_database/GetAndInsertRemoteCompilationDbFiles.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/bin/sh - -The buildmatrix database -DATABASE_NAME="" -# Webserver of the remote db files -WEBSERVER="" -# Directory on the webserver where the db files are stored -WEBDIR="" -# Script directory -SCRIPT_DIRECTORY="" - -# File on the remote server that contains the list of available db files -REMOTE_FILE_LIST=available_db_files.log - -# File for preventing multiple instances occuring -PROTECTION_FILE=/tmp/GETTING_REMOTE_COMPILATION_DB_FILES.LOCK -# List of files to add to the database -FILES_TO_ADD_TO_DB=files_to_add_to_DB -# Full path to the list of files currently in the database -CURRENT_DB_FILES=files_in_DB -# Script that generates list of files currently in the DB -CURRENT_DB_FILES_SCRIPT=RecentCompilationDBFiles.sh -# Script to add the files to the database -ADD_FILES_TO_DB_SCRIPT=SaveCompilationsToDB.sh -# Return value of this program -RETVAL=0 - - -function getfile() -{ - wget --quiet --timestamping --tries=3 --wait=10 "http://$WEBSERVER/$WEBDIR/$1" -} - - -# Check the file lock -if [ -f $PROTECTION_FILE ]; then - STARTED_TIME=`cat $PROTECTION_FILE` - - if [ -n "$STARTED_TIME" ]; then - # add 4 hours (60*60*4) to the number of seconds - LATE_TIME=`expr $STARTED_TIME + 14400` - CURRENT_TIME=`date +%s` - - if [ $CURRENT_TIME -gt $LATE_TIME ]; then - echo "Detected stuck retrieval. Removing the protection file." - rm "$PROTECTION_FILE" - else - echo "Files still being retrieved." - exit 0; - fi - # end of if [ $CURRENT_TIME -gt $LATE_TIME ] - - else - # More likely it is a munged attempt than two processes - # spawned at about the same time. - echo "Found empty protection file. Removing." - rm "$PROTECTION_FILE" - fi - exit 1; -fi - -# Create the lock file -date +%s > $PROTECTION_FILE - -# Directory to hold the downloaded db files -DB_TEMP_DIR=`mktemp -q -d $PROTECTION_FILE.XXXXXX` -if [ $? -ne 0 ]; then - echo "$0: failed to make temp directory" - rm "$PROTECTION_FILE" - exit 1; -fi - -cd $DB_TEMP_DIR - -# Download the list of db files available on the remote server -getfile $REMOTE_FILE_LIST -if [ ! -r $REMOTE_FILE_LIST ]; then - echo "File $REMOTE_FILE_LIST was not downloaded properly." - rm -rf $DB_TEMP_DIR - rm "$PROTECTION_FILE" - exit 1; -fi - -# Get the list of files in the Database -cd $SCRIPT_DIRECTORY -$SCRIPT_DIRECTORY/$CURRENT_DB_FILES_SCRIPT > $DB_TEMP_DIR/$CURRENT_DB_FILES -cd $DB_TEMP_DIR - -# Find the files that are not currently in the database -for file in `cat $REMOTE_FILE_LIST` -do - if [ `fgrep $file $CURRENT_DB_FILES 2> /dev/null | wc -l` -eq 0 ]; then - getfile $file - if [ $? -eq 0 ]; then - # Add the file to the list of files to add - echo "$DB_TEMP_DIR/$file" >> $FILES_TO_ADD_TO_DB - else - echo "Failed to retrieve file: $file" - RETVAL=1 - fi - fi -done - -# If there are files to add then add them -if [ -r $FILES_TO_ADD_TO_DB ]; then - cd $SCRIPT_DIRECTORY - $SCRIPT_DIRECTORY/$ADD_FILES_TO_DB_SCRIPT $DB_TEMP_DIR/$FILES_TO_ADD_TO_DB $DATABASE_NAME -fi - -rm -rf $DB_TEMP_DIR -rm "$PROTECTION_FILE" - -exit $RETVAL; - diff --git a/matrix_database/GetAndInsertRemoteTestDbFiles.sh b/matrix_database/GetAndInsertRemoteTestDbFiles.sh deleted file mode 100755 index 4132c5118..000000000 --- a/matrix_database/GetAndInsertRemoteTestDbFiles.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/bin/sh - -# The testmatrix database -DATABASE_NAME="" -# Webserver of the remote db files -WEBSERVER="" -# Directory on the webserver where the db files are stored -WEBDIR="" -# Script directory -SCRIPT_DIRECTORY="" - -# File on the remote server that contains the list of available db files -REMOTE_FILE_LIST=available_db_files.log - -# File for preventing multiple instances occuring -PROTECTION_FILE=/tmp/GETTING_REMOTE_TEST_DB_FILES.LOCK -# List of files to add to the database -FILES_TO_ADD_TO_DB=files_to_add_to_DB -# Full path to the list of files currently in the database -CURRENT_DB_FILES=files_in_DB -# Script that generates list of files currently in the DB -CURRENT_DB_FILES_SCRIPT=RecentTestDBFiles.sh -# Script to add the files to the database -ADD_FILES_TO_DB_SCRIPT=SaveTestsToDB.sh -# Return value of this program -RETVAL=0 - - -function getfile() -{ - wget --quiet --timestamping --tries=3 --wait=10 "http://$WEBSERVER/$WEBDIR/$1" -} - - -# Check the file lock -if [ -f $PROTECTION_FILE ]; then - STARTED_TIME=`cat $PROTECTION_FILE` - - if [ -n "$STARTED_TIME" ]; then - # add 4 hours (60*60*4) to the number of seconds - LATE_TIME=`expr $STARTED_TIME + 14400` - CURRENT_TIME=`date +%s` - - if [ $CURRENT_TIME -gt $LATE_TIME ]; then - echo "Detected stuck retrieval. Removing the protection file." - rm "$PROTECTION_FILE" - else - echo "Files still being retrieved." - exit 0; - fi - # end of if [ $CURRENT_TIME -gt $LATE_TIME ] - - else - # More likely it is a munged attempt than two processes - # spawned at about the same time. - echo "Found empty protection file. Removing." - rm "$PROTECTION_FILE" - fi - exit 1; -fi - -# Create the lock file -date +%s > $PROTECTION_FILE - -# Directory to hold the downloaded db files -DB_TEMP_DIR=`mktemp -q -d $PROTECTION_FILE.XXXXXX` -if [ $? -ne 0 ]; then - echo "$0: failed to make temp directory" - rm "$PROTECTION_FILE" - exit 1; -fi - -cd $DB_TEMP_DIR - -# Download the list of db files available on the remote server -getfile $REMOTE_FILE_LIST -if [ ! -r $REMOTE_FILE_LIST ]; then - echo "File $REMOTE_FILE_LIST was not downloaded properly." - rm -rf $DB_TEMP_DIR - rm "$PROTECTION_FILE" - exit 1; -fi - -# Get the list of files in the Database -cd $SCRIPT_DIRECTORY -$SCRIPT_DIRECTORY/$CURRENT_DB_FILES_SCRIPT > $DB_TEMP_DIR/$CURRENT_DB_FILES -cd $DB_TEMP_DIR - -# Find the files that are not currently in the database -for file in `cat $REMOTE_FILE_LIST` -do - if [ `fgrep $file $CURRENT_DB_FILES 2> /dev/null | wc -l` -eq 0 ]; then - getfile $file - if [ $? -eq 0 ]; then - # Add the file to the list of files to add - echo "$DB_TEMP_DIR/$file" >> $FILES_TO_ADD_TO_DB - else - echo "Failed to retrieve file: $file" - RETVAL=1 - fi - fi -done - -# If there are files to add then add them -if [ -r $FILES_TO_ADD_TO_DB ]; then - cd $SCRIPT_DIRECTORY - $SCRIPT_DIRECTORY/$ADD_FILES_TO_DB_SCRIPT $DB_TEMP_DIR/$FILES_TO_ADD_TO_DB $DATABASE_NAME -fi - -rm -rf $DB_TEMP_DIR -rm "$PROTECTION_FILE" - -exit $RETVAL; - diff --git a/matrix_database/InsertLocalCompilationDbFiles.sh b/matrix_database/InsertLocalCompilationDbFiles.sh deleted file mode 100755 index 373a00311..000000000 --- a/matrix_database/InsertLocalCompilationDbFiles.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/sh - -#The buildmatrix database for saving the compilation results locally. -DATABASE_NAME="" - -# directory of local db files -DB_FILE_DIR="" - -# Script directory -SCRIPT_DIRECTORY="" - -# File that contains the list of available db files -REMOTE_FILE_LIST=$DB_FILE_DIR/available_db_files.log - -# File for preventing multiple instances occuring -PROTECTION_FILE=$DB_FILE_DIR/GETTING_LOCAL_COMPILATION_DB_FILES.LOCK -# List of files to add to the database -FILES_TO_ADD_TO_DB=files_to_add_to_DB -# Full path to the list of files currently in the database -CURRENT_DB_FILES=files_in_DB -# Script that generates list of files currently in the DB -CURRENT_DB_FILES_SCRIPT=RecentCompilationDBFiles.sh -# Script to add the files to the database -ADD_FILES_TO_DB_SCRIPT=SaveCompilationsToDB.sh -# Return value of this program -RETVAL=0 - - -# Check the file lock -if [ -f $PROTECTION_FILE ]; then - STARTED_TIME=`cat $PROTECTION_FILE` - - if [ -n "$STARTED_TIME" ]; then - # add 4 hours (60*60*4) to the number of seconds - LATE_TIME=`expr $STARTED_TIME + 14400` - CURRENT_TIME=`date +%s` - - if [ $CURRENT_TIME -gt $LATE_TIME ]; then - echo "Detected stuck retrieval. Removing the protection file." - rm "$PROTECTION_FILE" - else - echo "Files still being retrieved." - exit 0; - fi - # end of if [ $CURRENT_TIME -gt $LATE_TIME ] - - else - # More likely it is a munged attempt than two processes - # spawned at about the same time. - echo "Found empty protection file. Removing." - rm "$PROTECTION_FILE" - fi - exit 1; -fi - -# Create the lock file -date +%s > $PROTECTION_FILE - -cd $DB_FILE_DIR - -if [ ! -r $REMOTE_FILE_LIST ]; then - echo "File $REMOTE_FILE_LIST does not exist." - rm "$PROTECTION_FILE" - exit 1; -fi - -# Get the list of files in the Database -cd $SCRIPT_DIRECTORY -$SCRIPT_DIRECTORY/$CURRENT_DB_FILES_SCRIPT $DATABASE_NAME > $DB_FILE_DIR/$CURRENT_DB_FILES -cd $DB_FILE_DIR - -if [ -f $FILES_TO_ADD_TO_DB ]; then - rm $FILES_TO_ADD_TO_DB -fi - -# Find the files that are not currently in the database -for file in `cat $REMOTE_FILE_LIST` -do - if [ `fgrep $file $CURRENT_DB_FILES 2> /dev/null | wc -l` -eq 0 ]; then - # Add the file to the list of files to add - echo "$DB_FILE_DIR/$file" >> $FILES_TO_ADD_TO_DB - fi -done - -# If there are files to add then add them -if [ -r $FILES_TO_ADD_TO_DB ]; then - cd $SCRIPT_DIRECTORY - $SCRIPT_DIRECTORY/$ADD_FILES_TO_DB_SCRIPT $DB_FILE_DIR/$FILES_TO_ADD_TO_DB $DATABASE_NAME -fi - -rm "$PROTECTION_FILE" - -exit $RETVAL; - diff --git a/matrix_database/InsertLocalTestDbFiles.sh b/matrix_database/InsertLocalTestDbFiles.sh deleted file mode 100644 index 2c83065a8..000000000 --- a/matrix_database/InsertLocalTestDbFiles.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/sh - -#The testmatrix database for saving the test results locally. -DATABASE_NAME="" - -# directory of local db files -DB_FILE_DIR="" - -# Script directory -SCRIPT_DIRECTORY="" - -# File that contains the list of available db files -REMOTE_FILE_LIST=$DB_FILE_DIR/available_db_files.log - -# File for preventing multiple instances occuring -PROTECTION_FILE=$DB_FILE_DIR/GETTING_LOCAL_TEST_DB_FILES.LOCK -# List of files to add to the database -FILES_TO_ADD_TO_DB=files_to_add_to_DB -# Full path to the list of files currently in the database -CURRENT_DB_FILES=files_in_DB -# Script that generates list of files currently in the DB -CURRENT_DB_FILES_SCRIPT=RecentTestDBFiles.sh -# Script to add the files to the database -ADD_FILES_TO_DB_SCRIPT=SaveTestsToDB.sh -# Return value of this program -RETVAL=0 - - -# Check the file lock -if [ -f $PROTECTION_FILE ]; then - STARTED_TIME=`cat $PROTECTION_FILE` - - if [ -n "$STARTED_TIME" ]; then - # add 4 hours (60*60*4) to the number of seconds - LATE_TIME=`expr $STARTED_TIME + 14400` - CURRENT_TIME=`date +%s` - - if [ $CURRENT_TIME -gt $LATE_TIME ]; then - echo "Detected stuck retrieval. Removing the protection file." - rm "$PROTECTION_FILE" - else - echo "Files still being retrieved." - exit 0; - fi - # end of if [ $CURRENT_TIME -gt $LATE_TIME ] - - else - # More likely it is a munged attempt than two processes - # spawned at about the same time. - echo "Found empty protection file. Removing." - rm "$PROTECTION_FILE" - fi - exit 1; -fi - -# Create the lock file -date +%s > $PROTECTION_FILE - -cd $DB_FILE_DIR - -if [ ! -r $REMOTE_FILE_LIST ]; then - echo "File $REMOTE_FILE_LIST does not exist." - rm "$PROTECTION_FILE" - exit 1; -fi - -# Get the list of files in the Database -cd $SCRIPT_DIRECTORY -$SCRIPT_DIRECTORY/$CURRENT_DB_FILES_SCRIPT $DATABASE_NAME > $DB_FILE_DIR/$CURRENT_DB_FILES -cd $DB_FILE_DIR - -if [ -f $FILES_TO_ADD_TO_DB ]; then - rm $FILES_TO_ADD_TO_DB -fi - -# Find the files that are not currently in the database -for file in `cat $REMOTE_FILE_LIST` -do - if [ `fgrep $file $CURRENT_DB_FILES 2> /dev/null | wc -l` -eq 0 ]; then - # Add the file to the list of files to add - echo "$DB_FILE_DIR/$file" >> $FILES_TO_ADD_TO_DB - fi -done - -# If there are files to add then add them -if [ -r $FILES_TO_ADD_TO_DB ]; then - cd $SCRIPT_DIRECTORY - $SCRIPT_DIRECTORY/$ADD_FILES_TO_DB_SCRIPT $DB_FILE_DIR/$FILES_TO_ADD_TO_DB $DATABASE_NAME -fi - -rm "$PROTECTION_FILE" - -exit $RETVAL; - diff --git a/matrix_database/README.txt b/matrix_database/README.txt deleted file mode 100644 index 370d77611..000000000 --- a/matrix_database/README.txt +++ /dev/null @@ -1,123 +0,0 @@ -Jan 12 2005 -This README file is intended to give the basic setup of a builds database. - As of the time of writing, there were no queries written for generating the html matrix pages. - - -System Requirements: - - Python - ~ version 3.10 - ~ http://www.python.org/ - - MySQL (database) - ~ version 4.1.x - ~ http://www.mysql.com/ - - MySQL for Python (Python module on machine running the db scripts) - ~ version 1.0.0 - ~ http://sourceforge.net/projects/mysql-python - - mx Extensions for Python (Python module on machine running the db scripts) - ~ version base-2.0.5-py2.2_1 - ~ http://www.egenix.com/files/python/eGenix-mx-Extensions.html - - -Configure the database connection settings: - - modify matrix_database/DBConfig.py > - DBConfig - hostname - machine hosting the database - dbname - name of the database where you are storing the statistics - username - database user that will be modifying the database - password - password of the database user - DBFileConfig - dbdir_w - directory where the test matrix generated database files will go - CompilationDBFileConfig - dbdir_w - directory where the build matrix generated database files will go. - This is NOT being used currently. The current place to specify this - is in the buildmatrix/buildmatrix.pl file. - BuildConfig - config - defines the nightly builds stored in the database. Each nightly build - should have a defined configuration. The config should be added BEFORE - a running of the build is added to the database. If the running of the - build was added before the config was modified then the "build" table - will have to be manually updated with the information. - - -Create the database: - - run `python CreateDBTables.py dbname` - dbname should be the same as the dbname variable in DBConfig class in DBConfig.py - - -Creating Test Matrix db files: - - make the first command line option to GenerateTestMatrix the number one "1" instead of 0 - e.g. python GenerateTestMatrix.py 1 test_spread.raw /my/builds/scoreboard tests - - -Creating Build Matrix db files: - - Add the number one "1" as the final command line parameter - e.g. perl buildmatrix.pl /autobuild/configs/scoreboard/ace.xml /my/builds/scoreboard 1 - - -Removing and Listing Test Matrix db files: - - modify and run matrix_database/RemoveAndListTestDbFiles.sh > - DB_FILE_DIR - directory holding the db files. - - -Removing and Listing Build Matrix db files: - - modify and run matrix_database/RemoveAndListCompilationDbFiles.sh > - DB_FILE_DIR - directory holding the db files. - - -Adding Test Matrix db files to the Database: - - remote files - - modify and then run matrix_database/GetAndInsertRemoteTestDbFiles.sh > - DATABASE_NAME - name of the database where you are storing the statistics - WEBSERVER - remote machine that has the files generated by GenerateTestMatrix.py - WEBDIR - webserver directory where the files are stored on the remote machine. - The directory used in a URL. - SCRIPT_DIRECTORY - full path to the matrix_database directory - - local files - - modify and then run matrix_database/InsertLocalTestDbFiles.sh > - DATABASE_NAME - name of the database where you are storing the statistics - DB_FILE_DIR - directory where the files are being stored locally - SCRIPT_DIRECTORY - full path to the matrix_database directory - REMOTE_FILE_LIST - list of files available to be put into the database - - -Adding Build Matrix db files to the Database: - - remote files - - modify and then run matrix_database/GetAndInsertRemoteCompilationDbFiles.sh > - DATABASE_NAME - name of the database where you are storing the statistics - WEBSERVER - remote machine that has the files generated by GenerateTestMatrix.py - WEBDIR - webserver directory where the files are stored on the remote machine. - The directory used in a URL. - SCRIPT_DIRECTORY - full path to the matrix_database directory - - local files - - modify and then run matrix_database/InsertLocalCompilationDbFiles.sh > - DATABASE_NAME - name of the database where you are storing the statistics - DB_FILE_DIR - directory where the files are being stored locally - SCRIPT_DIRECTORY - full path to the matrix_database directory - REMOTE_FILE_LIST - list of files available to be put into the database - - -Adding Test Matrix information directly to the Database: - - make the first command line option to GenerateTestMatrix the number two "2" - Add the name of the database as the final parameter - e.g. python GenerateTestMatrix.py 2 test_spread.raw /my/builds/scoreboard tests BuildDB - - -Adding Build Matrix information directly to the Database: - - Not Implemented. - diff --git a/matrix_database/RecentCompilationDBFiles.py b/matrix_database/RecentCompilationDBFiles.py deleted file mode 100755 index f31ffd020..000000000 --- a/matrix_database/RecentCompilationDBFiles.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from CompilationDB import * - -if __name__ == '__main__': - compilationdb = CompilationDB(sys.argv[1]) - compilationdb.GetRecentBuildInstance() diff --git a/matrix_database/RecentCompilationDBFiles.sh b/matrix_database/RecentCompilationDBFiles.sh deleted file mode 100755 index 3eb860f87..000000000 --- a/matrix_database/RecentCompilationDBFiles.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash - -if [ $# -ne 1 ]; then - echo "usage: $0 " - exit 1 -fi - -DATABASE_NAME=$1 -TMDIR=$HOME/nightly/matrix_database -/usr/bin/python2.1 $TMDIR/RecentCompilationDBFiles.py $DATABASE_NAME - - diff --git a/matrix_database/RecentTestDBFiles.py b/matrix_database/RecentTestDBFiles.py deleted file mode 100755 index 69d9f1123..000000000 --- a/matrix_database/RecentTestDBFiles.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python3 - -from TestDB import * - -if __name__ == '__main__': - testdb = TestDB(sys.argv[1]) - testdb.GetRecentBuildInstance() diff --git a/matrix_database/RecentTestDBFiles.sh b/matrix_database/RecentTestDBFiles.sh deleted file mode 100755 index 189e1535a..000000000 --- a/matrix_database/RecentTestDBFiles.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash - -if [ $# -ne 1 ]; then - echo "usage: $0 " - exit 1 -fi - -DATABASE_NAME=$1 -TMDIR=$HOME/nightly/matrix_database -/usr/bin/python2.1 $TMDIR/RecentTestDBFiles.py $DATABASE_NAME - - diff --git a/matrix_database/RemoveAndListCompilationDbFiles.sh b/matrix_database/RemoveAndListCompilationDbFiles.sh deleted file mode 100755 index 977f660eb..000000000 --- a/matrix_database/RemoveAndListCompilationDbFiles.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -SCOREBOARD=/export/web/www/scoreboard -DB_FILE_DIR=$SCOREBOARD/compilation_matrix_db -OLD_NUMBER_DAYS=+3 -LIST_FILENAME=available_db_files.log - -cd $DB_FILE_DIR - -rm $LIST_FILENAME - -find . -name '*.db' -ctime $OLD_NUMBER_DAYS -exec rm {} \; - -ls *.db > $LIST_FILENAME.tmp - -mv $LIST_FILENAME.tmp $LIST_FILENAME - diff --git a/matrix_database/RemoveAndListTestDbFiles.sh b/matrix_database/RemoveAndListTestDbFiles.sh deleted file mode 100755 index 1f7d09f2b..000000000 --- a/matrix_database/RemoveAndListTestDbFiles.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -SCOREBOARD=/export/web/www/scoreboard -DB_FILE_DIR=$SCOREBOARD/test_matrix_db -OLD_NUMBER_DAYS=+3 -LIST_FILENAME=available_db_files.log - -cd $DB_FILE_DIR - -rm $LIST_FILENAME - -find . -name '*.db' -ctime $OLD_NUMBER_DAYS -exec rm {} \; - -ls *.db > $LIST_FILENAME.tmp - -mv $LIST_FILENAME.tmp $LIST_FILENAME - diff --git a/matrix_database/SaveCompilationsToDB.py b/matrix_database/SaveCompilationsToDB.py deleted file mode 100755 index ebe2b1d68..000000000 --- a/matrix_database/SaveCompilationsToDB.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from CompilationDB import * -from CompilationDBFileHandle import * - -if __name__ == '__main__': - lsfile = sys.argv[1] - dbname = sys.argv[2] - builds = ReadCompilationDBFiles(lsfile) - SaveCompilationResults2DB(builds, dbname) diff --git a/matrix_database/SaveCompilationsToDB.sh b/matrix_database/SaveCompilationsToDB.sh deleted file mode 100755 index 1afd25306..000000000 --- a/matrix_database/SaveCompilationsToDB.sh +++ /dev/null @@ -1,27 +0,0 @@ -#! /bin/bash - - -TMDIR=$HOME/nightly/matrix_database -DB_LOCK=$TMDIR/DATABASE_COMPILATION_INSERTION.LOCK - -if [ $# -ne 2 ]; then - echo "usage: $0 " - echo " Listdbfiles - absolute filename of the file containing the" - echo " list of the db files" - exit 1 -fi - -DBFILELIST=$1 -DATABASE_NAME=$2 - -if [ -f $DB_LOCK ]; then - echo "Another database update hasn't finished" - exit 1 -fi - -touch $DB_LOCK - -/usr/bin/python2.1 $TMDIR/SaveCompilationsToDB.py $DBFILELIST $DATABASE_NAME - -rm -f $DB_LOCK - diff --git a/matrix_database/SaveTestsToDB.py b/matrix_database/SaveTestsToDB.py deleted file mode 100755 index f3c64b9a0..000000000 --- a/matrix_database/SaveTestsToDB.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from TestDB import * -from TestDBFileHandle import * - -if __name__ == '__main__': - lsfile = sys.argv[1] - dbname = sys.argv[2] - builds = ReadTestDBFiles(lsfile) - SaveTestResults2DB(builds, dbname) diff --git a/matrix_database/SaveTestsToDB.sh b/matrix_database/SaveTestsToDB.sh deleted file mode 100755 index ea7a560a0..000000000 --- a/matrix_database/SaveTestsToDB.sh +++ /dev/null @@ -1,27 +0,0 @@ -#! /bin/bash - - -TMDIR=$HOME/nightly/matrix_database -DB_LOCK=$TMDIR/DATABASE_TEST_INSERTION.LOCK - -if [ $# -ne 2 ]; then - echo "usage: $0 " - echo " Listdbfiles - absolute filename of the file containing the" - echo " list of the db files" - exit 1 -fi - -DBFILELIST=$1 -DATABASE_NAME=$2 - -if [ -f $DB_LOCK ]; then - echo "Another database update hasn't finished" - exit 1 -fi - -touch $DB_LOCK - -/usr/bin/python2.1 $TMDIR/SaveTestsToDB.py $DBFILELIST $DATABASE_NAME - -rm -f $DB_LOCK - diff --git a/matrix_database/TestDB.py b/matrix_database/TestDB.py deleted file mode 100644 index 9367d00ee..000000000 --- a/matrix_database/TestDB.py +++ /dev/null @@ -1,157 +0,0 @@ -import sys -import MySQLdb -import _mysql_exceptions -import mx.DateTime.Parser as Parser -from DBConnection import * -from utils import * -from TestPlatform import * - - -def SaveTestResults2DB(builds, dbname): - db = TestDB(dbname) - try: - db.FillTables(builds) - except BaseException: - sys.exit('ERROR: failed to insert test results to database' + ' '.join([ - dbname, sys.exc_info()[0], sys.exc_info()[1]])) - - -class TestDB: - def __init__(self, dbname): - self.test_ids = {} - self.build_ids = {} - self.build_instance_ids = {} - self.connection = DBConnection(dbname) - self.curs = self.connection.curs - - def FillTables(self, builds): - builds_no_dup = [] - for m in range(0, len(builds)): - dbfile_load_status, build_instance_id = self.BuildLogLoaded( - builds[m].name, builds[m].raw_file) - if dbfile_load_status == 1: - print("********* Already saved test results", builds[m].name, builds[m].raw_file) - continue - else: - print("********* Save test results", builds[m].name, builds[m].raw_file) - self.AddBuild(builds[m]) - self.AddBuildInstance( - builds[m], dbfile_load_status, build_instance_id) - if m == 0: - self.AddTests(builds[m]) - self.AddTestInstance(builds[m]) - - def AddBuild(self, build): - name = build.name - build_id = self.GetBuildId(name) - # print "GetBuildId", name, build_id - if build_id != 0: - self.build_ids[name] = build_id - else: - if (name in BuildConfig.config) == 0: - query = "INSERT INTO build VALUES (NULL, '%s', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);" % ( - name) - else: - config = BuildConfig.config[name] - query = "INSERT INTO build VALUES (NULL, '%s', '%s', '%s', %d, '%s', %d, %d, %d, %d);" % ( - name, config[0], config[1], config[2], config[3], config[4], config[5], config[6], config[7]) - # print "AddBuild ", query - self.curs.execute(query) - self.build_ids[name] = self.curs.insert_id() - # print "AddBuild: insert ", self.curs.insert_id() - - def AddBuildInstance(self, build, dbfile_load_status, build_instance_id): - if (build.name in self.build_ids) == 0: - self.AddBuild(build.name) - if dbfile_load_status == -1: - query = "INSERT INTO build_instance VALUES (NULL, %d, '%s', '%s', NULL, '%s', NOW(), NULL);" % (self.build_ids[build.name], str( - Parser.DateTimeFromString(build.start_time)), str(Parser.DateTimeFromString(build.end_time)), build.raw_file) - self.curs.execute(query) - self.build_instance_ids[build.name] = self.curs.insert_id() - else: - query = "UPDATE build_instance SET test_insert_time=NOW() where build_instance_id=%d;" % ( - build_instance_id) - self.curs.execute(query) - self.build_instance_ids[build.name] = build_instance_id - # print "AddBuildInstance ", query - - def AddTests(self, build): - for m in range(0, len(build.test_results)): - name = build.test_results[m].name - self.AddTest(name) - - def AddTest(self, name): - test_id = self.GetTestId(name) - if test_id == 0: - query = "INSERT INTO test VALUES (NULL, '%s');" % (name) - self.curs.execute(query) - # print "AddTest ", query - self.test_ids[name] = self.curs.insert_id() - # print "AddTest: insert ", self.curs.insert_id() - else: - self.test_ids[name] = test_id - - def AddTestInstance(self, build): - for n in range(0, len(build.test_results)): - if (build.test_results[n].passFlag == PASS): - pass_flag = 'P' - elif (build.test_results[n].passFlag == FAIL): - pass_flag = 'F' - else: - pass_flag = 'S' - if (build.test_results[n].name in self.test_ids) == 0: - self.AddTest(build.test_results[n].name) - query = "INSERT INTO test_instance VALUES(%d, %d, '%s', '%s');" % ( - self.test_ids[build.test_results[n].name], self.build_instance_ids[build.name], pass_flag, build.test_results[n].time) - # print "AddTestInstance ", query - try: - self.curs.execute(query) - except _mysql_exceptions.IntegrityError: - print("AddTestInstance failed: ", build.test_results[n].name, build.raw_file, sys.exc_info()[0], sys.exc_info()[1]) - - def BuildLogLoaded(self, build_name, log_fname): - db_load_status = -1 - build_instance_id = -1 - query = "SELECT build_instance_id, test_insert_time FROM build_instance WHERE log_fname='%s';" % ( - log_fname) - result = self.curs.execute(query) - if result != 0: - build_instance_id = self.curs.fetchall()[0][0] - insert_time = str(self.curs.fetchall()[0][1]) - db_load_status = (insert_time != "None") - # print "BuildLogLoaded ", query, "build_instance_id=", - # build_instance_id, "test_loaded=", db_load_status - return db_load_status, build_instance_id - - def GetBuildId(self, build_name): - build_id = 0 - query = "SELECT build_id FROM build WHERE build_name='%s';" % ( - build_name) - # print "GetBuildId ", query - ret = self.curs.execute(query) - if ret == 1: - build_id = self.curs.fetchall()[0][0] - # print "GetBuildId: ", build_id - elif ret > 1: - print("ERROR: duplicate entry for build ", build_name) - return build_id - - def GetTestId(self, test_name): - test_id = 0 - query = "SELECT test_id FROM test WHERE test_name='%s';" % (test_name) - # print "GetTestId ", query - ret = self.curs.execute(query) - if ret == 1: - test_id = self.curs.fetchall()[0][0] - # print "GetTestId: ", test_id - elif ret > 1: - print("ERROR: duplicate entry for test ", test_name) - return test_id - - def GetRecentBuildInstance(self): - query = "SELECT log_fname FROM build_instance WHERE test_insert_time IS NOT NULL and NOW() < test_insert_time + INTERVAL 4 DAY;" - ret = self.curs.execute(query) - if ret > 0: - results = self.curs.fetchall() - for m in range(0, len(results)): - print(txt2DbFname(results[m][0])) diff --git a/matrix_database/TestDBFileHandle.py b/matrix_database/TestDBFileHandle.py deleted file mode 100644 index 79a033116..000000000 --- a/matrix_database/TestDBFileHandle.py +++ /dev/null @@ -1,20 +0,0 @@ -from TestPlatform import * -from utils import * - - -def WriteTestDBFiles(builds): - for m in range(0, len(builds)): - builds[m].writeDBLog() - - -def ReadTestDBFiles(lsfile): - fh = open(lsfile, "r") - builds = [] - for dbfile in fh.readlines(): - file = dbfile.strip() - if file: - build = TestPlatform("", "", file) - if build.valid_db_file == 1: - builds.append(build) - fh.close() - return builds diff --git a/matrix_database/TestPlatform.py b/matrix_database/TestPlatform.py deleted file mode 100644 index acc2ed44c..000000000 --- a/matrix_database/TestPlatform.py +++ /dev/null @@ -1,341 +0,0 @@ -# ****************************************************************** -# Author: Heather Drury -# Date: 3/25/2004 -# ****************************************************************** - -import os -import sys -import string -import fileinput -import re -import math -import time -from utils import * -from DBConfig import * - -# patterns for log file -compile_error = re.compile(r'error', re.IGNORECASE) -test_error = re.compile(r'Error[s]?[^A-Za-z]|ERROR|fatal|FAIL:|FAILED|EXCEPTION|ACE_ASSERT|Assertion|Mismatched free|are definitely lost in loss record|: parse error|Invalid write of size|Invalid read of size|Source and destination overlap|error while loading shared libraries|free\(\): invalid pointer:|pure virtual ') -ace_test_start = re.compile(r'Running') -build_begin = re.compile(r'#################### Begin') -build_end = re.compile(r'#################### End') - -# ASSUMPTION -# each test start signfified by "auto_run_tests:" and ends with -# "auto_run_tests_finished:" -test_start = re.compile(r'auto_run_tests:') -test_finished = re.compile(r'auto_run_tests_finished:') - -# represents one instance of one test - - -class ACE_TAO_Test: - def __init__(self, name, result, time, flag): - self.name = name - self.result = int(result) - self.time = float(time) - self.passFlag = flag - - -class TestMatrix: - def __init__(self, num_platforms): - self.name = "Test Matrix for ACE/TAO" - self.max_tests = 1200 - self.testResults = [ - [0] * - num_platforms for row in range( - self.max_tests)] - for n in range(0, self.max_tests): - for m in range(0, num_platforms): - self.testResults[n][m] = SKIP - self.testNames = [] - self.fileNames = [] - - def getTestResults(self, build_num): - npass = 0 - nfail = 0 - nskip = 0 - for n in range(0, len(self.testNames)): - if self.testResults[n][build_num] == SKIP: - nskip = nskip + 1 - if self.testResults[n][build_num] == FAIL: - nfail = nfail + 1 - if self.testResults[n][build_num] == PASS: - npass = npass + 1 - return npass, nfail, nskip - - def getAllTestResults(self, nbuilds): - npass = 0 - nfail = 0 - nskip = 0 - print("***", ntests, nplatforms) - for m in range(0, nbuilds): - a, b, c = self.computeTestResults(m) - npass = npass + a - nfail = nfail + b - nskip = nskip + c - return npass, nfail, nskip - - def addTestResult(self, build_num, name, result): - if len(self.testNames) >= self.max_tests: - print("Error: exceeded maximum number of tests") - return - if name not in self.testNames: - # test is not yet added, so add it - self.testNames.append(name) - else: - pass - # idx is index corresponding to this test - idx = self.testNames.index(name) - self.testResults[idx][build_num] = result - - # get filename for later HTML writing - splits = name.split("/") - fname = "" - if len(splits) == 1: - fname = name - else: - l = len(splits) - fname = splits[l - 2] - self.fileNames.append(fname) - - -class TestPlatform: - def __init__(self, name, raw_file, db_file=""): - self.valid_raw_file = 1 - self.valid_db_file = 1 - self.db_file = db_file - self.name = name - self.raw_file = raw_file - self.compile = PASS - self.npass = 0 - self.nfail = 0 - self.nskip = 0 - self.ACEtotal = 0 - self.ACEfail = 0 - self.TAOtotal = 0 - self.TAOfail = 0 - self.testMin = 4000.0 - self.testMax = 0.0 - self.timeTotal = 0.0 - self.test_results = [] - self.start_time = "" - self.end_time = "" - if self.db_file: - self.processDBLog() - else: - self.processLog() - if (self.start_time == "" or self.end_time == ""): - self.valid_raw_file = 0 - if (len(self.test_results) == 0): - print("NO TESTS RUN:") - self.valid_raw_file = 0 - - # print "platform ", self.name, self.raw_file, self.start_time, - # self.end_time - - def writeDBLog(self): - fname = DBFileConfig.dbdir_w + "/" + txt2DbFname(self.raw_file) - tmpfname = fname + ".tmp" - fh = open(tmpfname, "w") - fh.write(self.name + "\n") - fh.write(self.raw_file + "\n") - fh.write(self.start_time + "\n") - fh.write(self.end_time + "\n") - fh.write(str(self.compile) + "\n") - for n in range(0, len(self.test_results)): - test_str = self.test_results[n].name + ";" + str( - self.test_results[n].passFlag) + ";" + str(self.test_results[n].time) - fh.write(test_str + "\n") - fh.close() - os.rename(tmpfname, fname) - - def processDBLog(self): - try: - fh = open(self.db_file, "r") - except IOError: - print("ERROR: Cannot open db file", self.db_file) - return - - self.name = fh.readline().strip() - self.raw_file = fh.readline().strip() - self.start_time = fh.readline().strip() - self.end_time = fh.readline().strip() - self.compile = int(fh.readline().strip()) - # print "processDBLog ", self.db_file, self.name, self.raw_file, - # self.start_time, self.end_time, self.compile - parse_error = 0 - for line in fh: - line = line.strip() - splits = line.split(";") - if len(splits) != 3 or splits[0] == "": - parse_error = 1 - break - name = splits[0] - passflag = int(splits[1]) - time = float(splits[2].strip()) - # print "test_result: ", line - self.test_results.append(ACE_TAO_Test(name, 0, time, passflag)) - if (parse_error == 1 or self.name == "" or self.raw_file == "" or self.start_time == - "" or self.end_time == "" or len(self.test_results) == 0): - print("ERROR: invalid db file: ", self.db_file) - return - - fh.close() - - def addtest(self, name, result, time, flag): - self.test_results.append(ACE_TAO_Test(name, result, time, flag)) - - def testTime(self, name, time): - if time > self.testMax: - # Don't count the ACE test as the maximum time test - if name != "tests/run_test.pl": - self.testMax = time - if time < self.testMin: - self.testMin = time - self.timeTotal = self.timeTotal + time - - def checkTestTime(self, line): - tokens = line.split() - result = 0 - time = 0.0 - - # this strips off the "auto_run_tests:, and the time and status - # leaves the test name plus any arguments - splits = line.split() - testname = ' '.join(splits[1: -2]) - if len(tokens) > 2: - ttime = tokens[2] - if len(tokens) < 3: - ttime = "" - tresult = "" - else: - ttime = tokens[len(tokens) - 2] - tresult = tokens[len(tokens) - 1] - if ttime: - e, f = ttime.split(":") - time = int(f.replace('s', '')) - self.testTime(testname, time) - - if tresult: - f, h = tresult.split(":") - result = h - return testname, result, time - - def sortByTime(self): - # Sort test result by test time - self.test_results.sort(lambda x, y: cmp(x.time, y.time)) - self.test_results.reverse() - - def printResults(self): - print("\n********************") - print("Results for build: ", self.name) - print("ACE:") - print("\tTotal tests run = ", self.ACEtotal) - print("\tTests failed = ", self.ACEfail) - perc = 0.0 - try: - perc = (float(self.ACEfail) / float(self.ACEtotal)) * 100.0 - except ZeroDivisionError: - print("divide by zero attempt") - print("\tPercentage: %.1f" % perc) - print("TAO:") - print("\tTotal tests run = ", self.TAOtotal) - print("\tTests failed = ", self.TAOfail) - try: - perc = (float(self.TAOfail) / float(self.TAOtotal)) * 100.0 - except ZeroDivisionError: - print("divide by zero attempt") - print("\tPercentage: %.1f" % perc) - if self.testMin != 4000.0: - print("Tests:") - print("\tmin Test = %.1f min" % (float(self.testMin) / 60.0)) - print("\tmax Test = %.1f min" % (float(self.testMax) / 60.0)) - print("\tTotal Test = %.1f min" % (float(self.timeTotal) / 60.0)) - - def ACEorTAOTest(self, testname): - splits = testname.split("/") - if splits[0] == "TAO": - testType = TAO_TEST - else: - testType = ACE_TEST - return testType - - def scanForTestFailure(self, fh, stop_strings): - fail = 0 - time = 0.0 - testname = "" - for line in fh: - line = line.strip() - if any([line.startswith(s) for s in stop_strings]): - break - elif test_error.search(line): - fail = 1 - elif test_finished.match(line): - testname, resultCode, time = self.checkTestTime(line) - elif build_end.match(line): - m = line.find('[') - n = line.find('UTC') - self.end_time = line[m + 1:n] - break - return line, fail, time, testname - - def processLog(self): - try: - file = open(self.raw_file, "r") - except IOError: - print("ERROR: Cannot open file", self.raw_file) - return - state = 0 - line = file.readline() - if line == "": - print("ERROR: file is empty:", self.raw_file) - return - - time = 0.0 - # scan thru the file, state=0 while in pre-test stuff and state=1 while - # in tests - while line: - readline = 0 - if build_begin.match(line): - m = line.find('[') - n = line.find('UTC') - self.start_time = line[m + 1:n] - line = file.readline() - continue - if build_end.match(line): - m = line.find('[') - n = line.find('UTC') - self.end_time = line[m + 1:n] - break - if test_start.match(line): - if (state < 1): - # we found where tests are starting - state = state + 1 - if state == 0: - # this matches only lines that begin with "Error" or "ERROR" - if compile_error.match(line) and self.compile == PASS: - self.compile = FAIL - if state == 1 and test_start.match(line): - testname = (line.split())[1] - orig_testname = testname - stop_strings = ["auto_run_tests:"] - test_type = self.ACEorTAOTest(testname) - line, fail, time, testname = self.scanForTestFailure( - file, stop_strings) - if testname == "": - testname = orig_testname - fail = 1 - self.addtest(testname, 0, time, fail) - if test_type == TAO_TEST: - self.TAOtotal = self.TAOtotal + 1 - if fail == 1: - self.TAOfail = self.TAOfail + 1 - elif test_type == ACE_TEST: - self.ACEtotal = self.ACEtotal + 1 - if fail == 1: - self.ACEfail = self.ACEfail + 1 - readline = 1 - if readline == 0: - line = file.readline() - file.close() diff --git a/matrix_database/UpdateBuildTable.py b/matrix_database/UpdateBuildTable.py deleted file mode 100755 index 0ed89d307..000000000 --- a/matrix_database/UpdateBuildTable.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 - -import MySQLdb -import sys -from DBConnection import * - - -def UpdateBuildTable(db_name): - curs = DBConnection(db_name).curs - for k in list(BuildConfig.config.keys()): - query = "SELECT * from build where build_name='%s';" % k - # print query - result = curs.execute(query) - if result != 0: - v = BuildConfig.config[k] - query = "UPDATE build set hostname='%s', os='%s', 64bit=%d, compiler='%s', debug=%d, optimized=%d, static=%d, minimum=%d where build_name='%s';" % ( - v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], k) - # print query - curs.execute(query) - - -if __name__ == '__main__': - UpdateBuildTable(sys.argv[1]) diff --git a/matrix_database/UpdateBuildTable.sh b/matrix_database/UpdateBuildTable.sh deleted file mode 100755 index a855111c4..000000000 --- a/matrix_database/UpdateBuildTable.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash - -if [ $# -ne 1 ]; then - echo "usage: $0 " - exit 1 -fi - -DATABASE_NAME=$1 -TMDIR=$HOME/nightly/matrix_database -/usr/bin/python2.1 $TMDIR/UpdateBuildTable.py $DATABASE_NAME - - diff --git a/matrix_database/utils.py b/matrix_database/utils.py deleted file mode 100644 index ec07fde49..000000000 --- a/matrix_database/utils.py +++ /dev/null @@ -1,27 +0,0 @@ -PASS = 0 -FAIL = 1 -SKIP = 3 - -NEW_FORMAT = 1 -OLD_FORMAT = 0 - -ACE_TEST = 0 -TAO_TEST = 1 - - -def ComputePercentage(numerator, denominator): - perc = 0.0 - try: - perc = (float(numerator) / float(denominator)) * 100.0 - except ZeroDivisionError: - pass - # print("divide by zero attempt") - return perc - - -def txt2DbFname(txtFname): - splits = txtFname.split("/") - length = len(splits) - dbFname = splits[length - 2] + "_" + splits[length - 1] - dbFname = dbFname.replace(".txt", ".db") - return dbFname diff --git a/scoreboard.pl b/scoreboard.pl index 6790f74b4..e6081080f 100755 --- a/scoreboard.pl +++ b/scoreboard.pl @@ -163,6 +163,8 @@ sub set_latest_build_info my $latest = shift; my $buildname = shift; + $latest =~ s/^\s+|\s+$//g; + if ($latest =~ m/Config: (\d+)/) { $builds{$buildname}{CONFIG_SECTION} = $1; } @@ -1594,10 +1596,8 @@ ($) my $v = shift; if ($v eq 'junit_xml_output') { return $junit_xml_output; - } else { - my %a=(); - return $a{'UNDEFINED'}; } + return undef; } ############################################################################### @@ -1713,6 +1713,29 @@ sub get_time_str return format_time(timegm(gmtime()), 1); } +############################################################################### + +sub write_builds_json +{ + my $dir = shift (); + my $builds_hash = shift (); + my $ordered_names = shift (); + + my @builds = (); + for my $name (@{$ordered_names}) { + my $orig = $builds_hash->{$name}; + my $new = {name => $name}; + for my $key (keys(%{$orig})) { + $new->{lc($key)} = $orig->{$key}; + } + push(@builds, $new); + } + + utility::write_obj_to_json ("$dir/builds.json", { + builds => \@builds, + }); +} + ############################################################################### # # Callbacks for commands @@ -1812,10 +1835,10 @@ sub get_time_str } if (defined $opt_i){ -$index = $opt_i; -print 'Running Index Page Update at ' . get_time_str() . "\n" if ($verbose); -build_index_page ($dir, $index); -exit (1); + $index = $opt_i; + print 'Running Index Page Update at ' . get_time_str() . "\n" if ($verbose); + build_index_page ($dir, $index); + exit (1); } if (defined $opt_b) { @@ -1832,9 +1855,9 @@ sub get_time_str } if (defined $opt_z) { -print 'Running Integrated Page Update at ' . get_time_str() . "\n" if ($verbose); -build_integrated_page ($dir, $opt_j); -exit (1); + print 'Running Integrated Page Update at ' . get_time_str() . "\n" if ($verbose); + build_integrated_page ($dir, $opt_j); + exit (1); } $inp_file = $opt_f; @@ -1872,6 +1895,7 @@ sub get_time_str query_history (); } } +write_builds_json ($dir, \%builds, \@ordered); update_html ($dir,"$dir/$out_file",$rss_file); print 'Finished Scoreboard Update at ' . get_time_str() . "\n" if ($verbose); diff --git a/testmatrix/00README b/testmatrix/00README deleted file mode 100644 index d92f3c1ce..000000000 --- a/testmatrix/00README +++ /dev/null @@ -1,43 +0,0 @@ -The files in this directory are used for creating html pages - with statistical information about the builds. - -The bash shell script buildMatrix parses the log files and calls - the python program to generate the html pages. buildMatrix takes - 2 parameters, the list of builds and the beginning of the name of - the test matrix page. The string ".matrix.html" will be appended - to the name of the page when it is generated. - -The python program requires python >= 2.1 - - -Setting up: - Edit update_scoreboard.sh - - change the log directory variable LOG_DIR to the directory of - your log directories. This is the directory below where the - actual log .html files are. - - change the TEST_MATRIX_DIR variable to the location of this - directory. - - change the BUILD_LIST variable to the name of the file list - that was added to the Makefile. - - change the SCOREBOARD_CONFIG_DIR variable to the directory - that contains the xml configuration file for the scoreboard - - Change the name of the test matrix generated file - (TEST_MATRIX) - - [if necessary] change the 'perl' command to have the full - path to perl. - - [Optional] Uncomment the autobuild command to have the shell - script run the build. - - Edit buildMatrix - - change the "logdir" variable to the location of the log files - on your system. This is the same value as LOG_DIR in - update_scoreboard.sh - - change the path to the python executable [if necessary] - - Edit *.py - - change the path to the python executable [if necessary] - - -Running: - Run the update_scoreboard.sh script. - diff --git a/testmatrix/GenerateTestMatrix.py b/testmatrix/GenerateTestMatrix.py deleted file mode 100755 index fa7a32650..000000000 --- a/testmatrix/GenerateTestMatrix.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python3 - -# ****************************************************************** -# Author: Heather Drury -# Justin Michel -# Date: 7/06/2004 -# ****************************************************************** - -import sys -from pathlib import Path - -matrix_database = str(Path(__file__).resolve().parent.parent / 'matrix_database') -if matrix_database not in sys.path: - sys.path.append(matrix_database) - -from HTMLScoreboard import * -from TestPlatform import * -from utils import * -from TestDBFileHandle import * - - -def ReadLogFiles(): - fh = open(infile, "r") - builds = [] - # read in text files containing test results for each platform - for line in fh.readlines(): - name, file = line.split() - build = TestPlatform(name, file) - if (build.valid_raw_file == 1): - print("********* BUILD", name) - builds.append(build) - else: - print("ERROR: invalid log file:", name, file) - fh.close() - return builds - - -def SaveBuildResults2HTML(directory, fname): - # now print the results to an HTML file - for n, build in enumerate(builds): - build.HTMLtestResults = HTMLPlatformTestTable(build.name, directory) - - # print out the failed, then passed tests - for pass_state in (FAIL, PASS): - for test_result in build.test_results: - if test_result.passFlag == FAIL: - build.HTMLtestResults.addData2( - n, - test_result.passFlag, - test_result.time, - test_result.name) - - build.HTMLtestResults.writeHTML() - -# build up test matrix - - -def BuildTestMatrix(builds, directory, fname, filename): - testMatrix = TestMatrix(len(builds)) - for n, build in enumerate(builds): - for test_result in build.test_results: - testMatrix.addTestResult(n, test_result.name, test_result.passFlag) - - # write out the HTML - HTMLtestMatrix = HTMLTestMatrix2(fname, directory) - for n, name in enumerate(testMatrix.testNames): - HTMLtestMatrix.addTestData( - name, - testMatrix.testResults[n], - testMatrix.fileNames[n], - builds) - - totalPass = 0 - totalFail = 0 - totalSkip = 0 - for n, build in enumerate(builds): - build.npass, build.nfail, build.nskip = testMatrix.getTestResults(n) - totalPass += build.npass - totalFail += build.nfail - totalSkip += build.nskip - HTMLtestMatrix.writeBriefs(totalPass, totalFail, totalSkip) - - for n, build in enumerate(builds): - HTMLtestMatrix.writeBuildSummary(n, build) - getSummaryResults(HTMLtestMatrix, builds, filename) - HTMLtestMatrix.writeHTML() - HTMLtestMatrix.writeHTMLsummary() - return testMatrix - -# Write one HTML file for each test with each row representing a platform - - -def WriteTestHTML(testMatrix, builds): - # print out HTML file for each test with results for all platforms - for b in builds: - print("\tPlatform: ", b.name, b.ACEtotal, b.ACEfail, b.TAOtotal, b.TAOfail) - for m, test_name in enumerate(testMatrix.testNames): - html = HTMLTestFile(test_name + '.html') - for n, build in enumerate(builds): - html.addData2(testMatrix.testResults[m][n], build.name) - html.writeHTML() - - -def getSummaryResults(HTMLtestMatrix, builds, fname): - ACEtotal = 0 - TAOtotal = 0 - ACEfail = 0 - TAOfail = 0 - ACEpass = 0 - TAOpass = 0 - ACEperc = 0 - TAOperc = 0 - overall = 0 - skip = 0 - for b in builds: - print("\tPlatform: ", b.name, b.ACEtotal, b.ACEfail, b.TAOtotal, b.TAOfail) - ACEtotal += b.ACEtotal - TAOtotal += b.TAOtotal - ACEfail += b.ACEfail - TAOfail += b.TAOfail - skip += b.nskip - ACEpass = ACEtotal - ACEfail - TAOpass = TAOtotal - TAOfail - ACEperc = ComputePercentage(ACEpass, ACEtotal) - TAOperc = ComputePercentage(TAOpass, TAOtotal) - overall = ComputePercentage((TAOpass + ACEpass), (TAOtotal + ACEtotal)) - print("ACE tests ****", ACEtotal, ACEfail, ACEperc) - print("TAO tests ****", TAOtotal, TAOfail, TAOperc) - file = "/tmp/matrix_output." + fname + ".txt" - fh = open(file, "w") - percent = '%\n' - str = "# of ACE tests passed: %d\n" % (ACEtotal) - fh.write(str) - str = "# of ACE tests failed: %d\n" % (ACEfail) - fh.write(str) - str = "ACE percentage: %.2f" % ACEperc - str = str + percent - fh.write(str) - fh.write("\n") - - str = "# of TAO tests passed: %d\n" % (TAOtotal) - fh.write(str) - str = "# of TAO tests failed: %d\n" % (TAOfail) - fh.write(str) - str = "TAO percentage: %.2f" % TAOperc - str = str + percent - fh.write(str) - fh.write("\n") - - str = "# of tests passed: %d\n" % (TAOtotal + ACEtotal) - fh.write(str) - str = "# of tests failed: %d\n" % (TAOfail + ACEfail) - fh.write(str) - str = "# of tests skipped: %d\n" % (skip) - fh.write(str) - - str = "Overall percentage: %.0f" % overall - str = str + percent - fh.write(str) - fh.close() - - fname = outfile + ".TestMatrix" - HTMLtestMatrix.writeSummary( - ACEpass, - ACEtotal, - ACEperc, - TAOpass, - TAOtotal, - TAOperc) - - -if __name__ == '__main__': - option = int(sys.argv[1]) - if option > 2: - sys.exit("ERROR: invalid option", option) - infile = sys.argv[2] - directory = sys.argv[3] - outfile = sys.argv[4] - fname = outfile + ".matrix" - if len(sys.argv) > 5: - database_name = sys.argv[5] - - builds = ReadLogFiles() - testMatrix = BuildTestMatrix(builds, directory, fname, outfile) - SaveBuildResults2HTML(directory, outfile) - - if option == 1: - import TestDB - TestDB.WriteTestDBFiles(builds) - elif option == 2: - import TestDB - TestDB.SaveTestResults2DB(builds, database_name) - - # there is no standardization of how the test names are created for ACE, TAO, until there is, - # we shouldn't do this HAD 2.11.04 - # WriteTestHTML (testMatrix, builds) - print("Normal execution!") diff --git a/testmatrix/HTMLScoreboard.py b/testmatrix/HTMLScoreboard.py index 0e51f6d7e..4d655b91e 100644 --- a/testmatrix/HTMLScoreboard.py +++ b/testmatrix/HTMLScoreboard.py @@ -10,31 +10,117 @@ import os import time from pathlib import Path - -matrix_database = str(Path(__file__).resolve().parent.parent / 'matrix_database') -if matrix_database not in sys.path: - sys.path.append(matrix_database) - -from utils import * - - -class HTMLTestMatrix2: - def __init__(self, title, directory): +from io import StringIO + +from .matrix import Status + + +this_dir = Path(__file__).resolve().parent +indent = ' ' + + +class Html: + def __init__(self, indent_by=0): + self.f = StringIO() + self.indent_by = indent_by + + def print(self, *args, end='', **kw): + print(*args, end=end, file=self.f, **kw) + + def print_indent(self): + self.print(indent * self.indent_by) + + def println(self, *args, **kw): + self.print_indent() + self.print(*args, end='\n', **kw) + + def println_push(self, *args, **kw): + self.println(*args, **kw) + self.indent_by += 1 + + def pop_println(self, *args, **kw): + assert self.indent_by > 0 + self.indent_by -= 1 + self.println(*args, **kw) + + def __str__(self): + return self.f.getvalue() + + +def tag(name, classes=None, attrs=None): + t = '<' + name + if attrs is None: + attrs = {} + if classes is None: + classes = [] + if classes: + attrs['class'] = ' '.join(classes) + if attrs: + t += ' ' + ' '.join([f'{k}="{v}"' for k, v in attrs.items()]) + return t + '>' + + +def full_tag(name, text, classes=None, attrs=None): + if attrs is None: + attrs = {} + if classes is None: + classes = [] + return tag(name, classes, attrs) + f'{text}' + + +class HtmlTable: + def __init__(self, name, cols, classes=[], attrs={}): + self.name = name + self.cols = cols + self.header = (None, [], {}) + self.rows = [self.header] + self.classes = classes + self.attrs = attrs + + def row(self, cells, classes=[], attrs=None): + if len(cells) != len(self.cols): + raise ValueError(f'Got {len(cells)} cells, but we have {len(self.cols)} columns!') + if attrs is None: + attrs = {} + self.rows.append(([c if type(c) is tuple else (c, [], {}) for c in cells], classes, attrs)) + + def extra_header(self): + self.rows.append(self.header) + + def done(self, html): + html.println_push(tag('table', self.classes, self.attrs)) + for row, row_classes, row_attrs in self.rows: + html.println_push(tag('tr', row_classes, row_attrs)) + html.print_indent() + if row is None: # Header + for col in self.cols: + html.print(full_tag('th', col)) + else: # Normal row + for cell, cell_classes, cell_attrs in row: + html.print(full_tag('td', cell, cell_classes, cell_attrs)) + html.println('') + html.pop_println('') + html.pop_println('') + + +class HTMLTestMatrix: + def __init__(self, matrix, title): + self.matrix = matrix self.title = title - self.directory = directory - self.matrix_html = None + self.directory = matrix.builds.dir + self.matrix_html_table = None self.matrix_header = None self.build_summary_html = None self.main_summary_html = None - self.tao_summary_html = None - self.ace_summary_html = None self.matrix_row = 0 self.passed = 0 self.failed = 0 + # TODO: Remove self.highlight_html = "onmouseover=\"this.style.backgroundColor='hotpink';\" onmouseout=\"this.style.backgroundColor='';\"" + # TODO: Remove JQuery self.html_start = """ @@ -102,67 +188,30 @@ def __init__(self, title, directory): """ - def getTestDataHeader(self, name, results, builds): - """Adds another header row to the test results table.""" - html = """ - - # Pass - # Fail - # Skip - % Pass -""" - for n in range(0, len(results)): - number = str(n + 1) - # Insert spaces between two digit numbers, so that they will wrap - if len(number) == 2: - number = number[0] + ' ' + number[1] - if builds: - bldname = builds[n].name - html += '' + number + '' - else: - html += '' + number + '' - - # Add the final and end the header row - return html + 'Test Name\n' - - def addTestData(self, name, results, linkfile, builds=None): - - if not self.matrix_html: - width = 160 + (10 * len(results)) + 500 - # Mozilla ignores the table-layout css attribute unless you specify - # ' % width - html += """""" - # Add specifiers for each build - for n in range(0, len(results)): - html += '' - # Add the final - html += '\n' - - # Add a caption - html += '\n' % ( - len(results) + 5) + def addTestData(self, name, results): + if self.matrix_html_table is None: + cols = ['# Pass', '# Fail', '# Skip', '% Pass'] + cols += [str(n + 1) for n in range(len(self.matrix.builds))] + cols += ['Test Name'] + self.matrix_html_table = HtmlTable('Test Results', cols) - html += self.getTestDataHeader(name, results, builds) - - self.matrix_html = html - - if not self.matrix_header: - self.matrix_header = self.getTestDataHeader(name, results, builds) + tb = self.matrix_html_table self.matrix_row += 1 - html = self.matrix_html - # Repeat the header row every now and then if self.matrix_row % 20 == 0: - html += self.matrix_header + tb.extra_header() - npass = results.count(PASS) - nfail = results.count(FAIL) - nskip = results.count(SKIP) - perc = ComputePercentage(npass, npass + nfail) - sperc = "%.0f" % perc + row_classes = [] + + # Alternate row color + if self.matrix_row & 1: + row_classes.append('odd') + + npass = results.stats.passed + nfail = results.stats.failed + nskip = results.stats.skipped # If any failed if nfail > 0: @@ -170,47 +219,27 @@ def addTestData(self, name, results, linkfile, builds=None): elif npass > 0: self.passed += 1 - if self.matrix_row & 1: - html += '" - - html += "" % npass - if nfail == 0: - html += "" % nfail - elif nfail == 1: - html += "" % nfail - else: - html += "" % nfail - - html += "" % nskip - html += '" - - # now write out the ' - elif res == FAIL: - html += '' - else: - html += '' - # now write out the name of the test - # fname = str(linkfile) + '.html' if nfail == 0: - html += '' + status_classes = [] elif nfail == 1: - html += '' + status_classes = ['warn'] else: - html += '' - - self.matrix_html = html + "\n" - - def getSummaryHTML(self, name, npass, nfail, nskip): - total = npass + nfail - pperc = ComputePercentage(npass, total) - fperc = ComputePercentage(nfail, total) + status_classes = ['fail'] + + row = [ + npass, + (nfail, status_classes, {}), + nskip, + results.stats.perc_passed, + ] + for status in self.matrix.statuses_for_test(results): + row.append(('', [status.name[0].lower()], {})) + row.append((results.name, status_classes, {})) + tb.row(row, row_classes) + + def getSummaryHTML(self, name, stats): + # TODO: Use HtmlTable html = """
Test Results
%d%d%d%d%d' + sperc + "s for test results - for res in results: - if res == PASS: - html += '' + name + '' + name + '' + name + '
@@ -231,46 +260,30 @@ def getSummaryHTML(self, name, npass, nfail, nskip): """ - html += '' % total - html += '' % npass - html += '' % nfail - if nskip == -1: + html += '' % stats.total + html += '' % stats.passed + html += '' % stats.failed + if stats.skipped == 0: html += '' else: - html += '' % nskip - html += '' % pperc - html += '' % fperc + html += '' % stats.skipped + html += f'' + html += f'' html += '\n' html += '
%d%d%d%d%d%d-%d%.2f%.2f%d{stats.perc_passed}{stats.perc_failed}
\n' html += '

%d tests passed, ' % self.passed html += '%d failed

\n' % self.failed return html - def writeBriefs(self, npass, nfail, nskip): - self.main_summary_html = self.getSummaryHTML("", npass, nfail, nskip) - - def writeSummary( - self, - ACEpass, - ACEtotal, - ACEperc, - TAOpass, - TAOtotal, - TAOperc): - if TAOtotal > 0: - self.ace_summary_html = self.getSummaryHTML( - "ACE ", ACEpass, (ACEtotal - ACEpass), -1) - self.tao_summary_html = self.getSummaryHTML( - "TAO ", TAOpass, (TAOtotal - TAOpass), -1) - else: - self.ace_summary_html = "" - self.tao_summary_html = "" + def writeBriefs(self): + self.main_summary_html = self.getSummaryHTML("", self.matrix.all_stats) def writeBuildSummary(self, num, build): num += 1 + # TODO: Use HtmlTable if not self.build_summary_html: self.build_summary_html = """ - +
@@ -278,27 +291,15 @@ def writeBuildSummary(self, num, build): - - - - - - - - - - - - @@ -307,110 +308,86 @@ def writeBuildSummary(self, num, build): html = self.build_summary_html # now we have to write out the key to the build numbers - ace = tao = 0.0 + # TODO: Rename and cleanup + ace = 0.0 acecls = None - taocls = None - if build.ACEtotal > 0: - npass = build.ACEtotal - build.ACEfail - ace = ComputePercentage(npass, build.ACEtotal) + if build.stats.ran: + ace = (build.stats.passed / build.stats.ran) * 100 if ace < 50.0: acecls = "f" elif ace < 90.0: acecls = "w" - if build.TAOtotal > 0: - npass = build.TAOtotal - build.TAOfail - tao = ComputePercentage(npass, build.TAOtotal) - if tao < 50.0: - taocls = "f" - elif tao < 90.0: - taocls = "w" - - if build.compile == FAIL: + # TODO? + # if build.compile == FAIL: + if False: html += '\n" % fname + html += " onmousedown=\"window.location ='%s';\">\n" % fname html += "" % num html += '" - html += "" % build.ACEtotal - html += "" % build.ACEfail + html += "" % build.stats.total + html += "" % build.stats.failed sperc = "%.1f" % ace if acecls: html += '" - html += "" % build.TAOtotal - html += "" % build.TAOfail - sperc = "%.1f" % tao - if taocls: - html += '" - time = float(build.timeTotal) / 60.0 + time = float(build.time) / 60.0 timestr = "%.0f" % time - html += "" % build.nskip + html += "" % build.stats.skipped html += "" self.build_summary_html = html def writeHTML(self): - fname = self.directory + "/" + self.title + ".html" - fname = os.path.normpath(fname) - print("Writing html to '" + fname + "'") + path = (self.directory / (self.title + ".html")).resolve() + print(f'Writing html to {path}') if not self.build_summary_html: self.build_summary_html = "" - if not self.matrix_html: - self.matrix_html = "" + if self.matrix_html_table is None: + matrix_html = '' + else: + self.matrix_html_table.extra_header() + matrix_html = Html() + self.matrix_html_table.done(matrix_html) if not self.matrix_header: self.matrix_header = "" - f = open(fname, "w", 1) - try: + with path.open('w') as f: f.write(self.html_start) f.write(self.main_summary_html) - f.write(self.ace_summary_html) - f.write(self.tao_summary_html) f.write(self.key_html) f.write(self.build_summary_html) f.write("
Build Summary
ACETAO
# Name # # Fail % Pass## Fail% Pass # Skip Time (min)
%d' + build.name + "%d%d%d%d' else: html += '' html += sperc + "%d%d' - else: - html += '' - html += sperc + "%d%d" + timestr + "
") - f.write(self.matrix_html) - f.write(self.matrix_header) - f.write("") + f.write(str(matrix_html)) f.write("
Last updated at ") f.write(time.asctime(time.localtime())) f.write("
") f.write(self.html_end) - finally: - f.close() def writeHTMLsummary(self): - fname = self.directory + "/" + self.title + "-summary.html" - fname = os.path.normpath(fname) - print("Writing html summary to '" + fname + "'") + path = (self.directory / (self.title + "-summary.html")).resolve() + print(f'Writing html summary to {path}') - f = open(fname, "w", 1) - try: + with path.open('w') as f: f.write(self.html_start) f.write(self.main_summary_html) - f.write(self.ace_summary_html) - f.write(self.tao_summary_html) f.write(self.html_end) - finally: - f.close() class HTMLPlatformTestTable: @@ -437,6 +414,7 @@ def __init__(self, title, dir): """ def addData2(self, num, flag, time, name): + # TODO: Use HtmlTable if not self.test_table_html: self.test_table_html = """ @@ -500,13 +478,16 @@ def writeHTML(self): f.close() -class HTMLTestFile: - def __init__(self, title, dir): - self.title = title - self.directory = dir +def write_html_matrix(matrix, prefix): + html_tm = HTMLTestMatrix(matrix, prefix) + for name, test in matrix.tests.items(): + html_tm.addTestData(name, test) - def addData2(self, flag, name): - pass + html_tm.writeBriefs() + for n, build in enumerate(matrix.builds): + html_tm.writeBuildSummary(n, build) + html_tm.writeHTML() + html_tm.writeHTMLsummary() - def writeHTML(self): - print("HTMLTestFile not supported.") + css = 'matrix.css' + (matrix.builds.dir / css).write_text((this_dir / css).read_text()) diff --git a/testmatrix/__init__.py b/testmatrix/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/testmatrix/buildMatrix b/testmatrix/buildMatrix deleted file mode 100755 index d41b7dcbb..000000000 --- a/testmatrix/buildMatrix +++ /dev/null @@ -1,51 +0,0 @@ -#! /bin/sh - -# this is a script to build a simple html matrix of test failures. - -# The first parameter implies the additional operation when creating test matrix# and write to html. -# 0 -- no additional operation -# 1 -- create the *.db files. -# Note: This does not require the database accessing. -# 2 -- save tests to database. -# Note: To access database, the database name needs to be passed -# as the last parameter to the GenerateMatrix.sh command or -# specified in the TestDBConfig.py. The database name from -# the command line will overwrite the one specified in the -# static DBConfig object if it's not empty. - -operation=$1 -buildlist=$2 -if [ -z "$buildlist" ]; then - buildlist=test-list -fi - -othtml=$3 -if [ -z "$othtml" ]; then - othtml=test_complex -fi - -logdir=$4 -if [ -z $logdir ]; then -logdir="/export/web/www/scoreboard" -fi - -otraw=test_spread.raw -filelist=`cat $buildlist` - -rm -f $otraw - -for filedir in $filelist -do - latest="$logdir/$filedir/latest.txt" - if file=`cut -f 1 -d ' ' $latest 2>/dev/null` - then - path="$logdir/$filedir/$file.txt" - echo "$filedir $path" >>$otraw - fi -done - -python GenerateTestMatrix.py $operation $otraw $logdir $othtml - -rm -f $otraw -exit 0 - diff --git a/testmatrix/matrix.css b/testmatrix/matrix.css index 265688dcc..874fb2386 100644 --- a/testmatrix/matrix.css +++ b/testmatrix/matrix.css @@ -1,58 +1,38 @@ body {background-color: white;} table { - background-color: #D3D3D3; - color: black; - border: 4px solid black; - margin: 5px 0; - border-collapse: collapse; - table-layout: fixed; - font: 10pt arial; + background-color: #d9d9d9; + border: 4px solid black; + margin: 5px 0; + border-collapse: collapse; + table-layout: fixed; + font: 10pt arial; } tr {overflow: hidden; } -tr.odd {background-color: #D3D3D3; color: black; } +tr.odd {background-color: #c0c0c0;} -tr.oddlnk {background-color: gray; color: white; cursor: pointer; } +tr.oddlnk {background-color: #c0c0c0; cursor: pointer;} th.head { - background-color: #A9A9A9; - font: bold 12pt verdana, arial; - cursor: arrow; + background-color: #a9a9a9; + font: bold 12pt verdana, arial; + cursor: arrow; } th { - background-color: #A9A9A9; - border: 2px solid white; - padding:2px 2px; - cursor: default; - font: bold 10pt verdana, arial; + background-color: #a9a9a9; + border: 2px solid white; + padding:2px 2px; + cursor: default; + font: bold 10pt verdana, arial; } -td {border: 2px solid white; padding:0 2px; overflow: hidden; text-align: right;} -td.txt {text-align: left;} - -table.matrix tr td { - border: 1px solid white; -} - -table.matrix tr th.head { - background-color: #A9A9A9; - font: bold 12pt verdana, arial; - cursor: default; -} - -table.matrix tr th { - border: 1px solid white; - cursor: default; - background-color: #D3D3D3; - color: black; -} - -table.matrix tr th.colhead { - font: 8pt sans-serif; - background-color: steelblue; - color: black; +td { + border: 2px solid white; + padding:0 2px; + overflow: hidden; + text-align: right; } - +td.txt {text-align: left;} .p {background-color:green} .f,.fail {background-color:red; color: white;} diff --git a/testmatrix/matrix.py b/testmatrix/matrix.py new file mode 100644 index 000000000..592be23a1 --- /dev/null +++ b/testmatrix/matrix.py @@ -0,0 +1,198 @@ +import json +import enum +from pathlib import Path + + +class Status(enum.Enum): + Passed = (True, 0, '✅') + Failed = (True, 1, '❌') + Skipped = (False, 2, '__') + + @property + def is_a_run(self): + return self.value[0] + + @property + def index(self): + return self.value[1] + + @property + def emoji(self): + return self.value[2] + + +class TestStats: + statuses = {s.name.lower(): s for s in Status} + + def __init__(self): + self.total = 0 + self.ran = 0 + self._counts = [0] * len(Status) + + def __getitem__(self, status): + return self._counts[status.index] + + def __iadd__(self, status): + self._counts[status.index] += 1 + self.total += 1 + if status.is_a_run: + self.ran += 1 + return self + + def __bool__(self): + return bool(self.ran) + + def perc(self, status): + d = self.ran if status.is_a_run else self.total + return format(self[status] / d, '.2%') + + def __str__(self): + s = f'{self.ran} ran' + for name, status in self.statuses.items(): + count = self[status] + if count: + name = status.name.lower() + perc = self.perc(status) + s += f', {count} {name} ({perc})' + return s + + +for name, status in TestStats.statuses.items(): + # Make a copy for each status, otherwise the lambdas get the same Status + # object. + setattr(TestStats, name, property(lambda self, s=Status(status): self[s])) + setattr(TestStats, f'perc_{name}', property(lambda self, s=Status(status): self.perc(s))) + + +class TestRun: + def __init__(self, name: str, result: int, time: int, misc=None): + if misc is None: + misc = {} + self.name = name + extra_name = misc.get('extra_name', None) + if extra_name: + self.name += '-' + misc['extra_name'] + self.result = result + self.time = time + self.misc = misc + + def __bool__(self): + return self.result == 0 + + @property + def status(self): + return Status.Passed if self else Status.Failed + + def __str__(self): + return f'{self.name}: time={self.time} result={self.result}' + + +class Build: + def __init__(self, name, builds_dir, misc=None): + if misc is None: + misc = {} + self.name = name + self.dir = builds_dir / name + self.misc = misc + self.stats = TestStats() + + build_json = self.dir / (misc['basename'] + '.build.json') + with build_json.open('r') as f: + data = json.load(f) + + self.tests = {} + self.time = 0 + for t in data['tests']: + test_run = TestRun(t['name'], int(t['result']), int(t['time']), t) + if test_run.name in self.tests: + raise ValueError(f'Multiple {repr(test_run.name)} in {self.name}!') + self.tests[test_run.name] = test_run + self.time += test_run.time + + def __iter__(self): + return iter(self.tests.values()) + + def __getitem__(self, key): + return self.tests[key] + + def __contains__(self, key): + return key in self.tests + + +class Builds: + def __init__(self, builds_dir): + self.dir = builds_dir + + builds_json = builds_dir / 'builds.json' + with builds_json.open('r') as f: + data = json.load(f) + + self.builds = {} + for b in data['builds']: + name = b['name'] + self.builds[name] = Build(name, builds_dir, b) + + def __len__(self): + return len(self.builds) + + def __iter__(self): + return iter(self.builds.values()) + + +class Test: + def __init__(self, name): + self.name = name + self.runs = {} + self.stats = TestStats() + + def add_run(self, build: Build, test: TestRun): + self.runs[build.name] = (build, test) + + def status_for_build(self, build_name): + return self.runs[build_name][1].status if build_name in self.runs else Status.Skipped + + def __len__(self): + return len(self.runs) + + +class Matrix: + def __init__(self, builds: Builds): + self.builds = builds + self.tests = {} + self.all_stats = TestStats() + + # Collect all the tests + for build in builds: + for test_run in build: + n = test_run.name + if n in self.tests: + test = self.tests[n] + else: + test = Test(n) + self.tests[n] = test + test.add_run(build, test_run) + + # Colect stats + for name, test in self.tests.items(): + for build in builds: + status = test.status_for_build(build.name) + test.stats += status + build.stats += status + self.all_stats += status + + def statuses_for_test(self, test): + for build in self.builds: + yield test.status_for_build(build.name) + + def dump(self): + for name, test in self.tests.items(): + for status in self.statuses_for_test(test): + print(status.emoji, end='') + print(' ', test.stats, name) + + print() + for n, build in enumerate(self.builds): + print(f'[{n}] {build.name}: {build.stats}') + + print() + print('All:', self.all_stats) diff --git a/testmatrix/test-list-extract.pl b/testmatrix/test-list-extract.pl deleted file mode 100755 index 1037a0237..000000000 --- a/testmatrix/test-list-extract.pl +++ /dev/null @@ -1,108 +0,0 @@ -eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' - & eval 'exec perl -S $0 $argv:q' - if 0; - -# - -use strict; -use warnings; -use diagnostics; -use Cwd; -use Config; -use File::Basename; -use FileHandle; -use Getopt::Std; - -my($basePath) = getExecutePath($0); -unshift(@INC, $basePath . '/..'); - -require common::scoreparser; - -my %builds; - -sub which { - my($prog) = shift; - my($exec) = $prog; - - if (defined $ENV{'PATH'}) { - my($part) = ''; - my($envSep) = $Config{'path_sep'}; - foreach $part (split(/$envSep/, $ENV{'PATH'})) { - $part .= "/$prog"; - if ( -x $part ) { - $exec = $part; - last; - } - } - } - - return $exec; -} - -sub getExecutePath { - my($prog) = shift; - my($loc) = ''; - - if ($prog ne basename($prog)) { - if ($prog =~ /^[\/\\]/ || - $prog =~ /^[A-Za-z]:[\/\\]?/) { - $loc = dirname($prog); - } - else { - $loc = getcwd() . '/' . dirname($prog); - } - } - else { - $loc = dirname(which($prog)); - } - - if ($loc eq '.') { - $loc = getcwd(); - } - - if ($loc ne '') { - $loc .= '/'; - } - - return $loc; -} - -sub load_build_list ($) -{ - my $file = shift; - my $parser = new ScoreboardParser; - $parser->Parse ($file, \%builds); -} - - -sub print_build_names () -{ - my @buildlist; - - foreach my $buildname (keys %builds) { - push @buildlist, $buildname; - } - - @buildlist = sort(@buildlist); - - foreach my $name (@buildlist) { - print $name."\n"; - } -} - - -use vars qw/$opt_h $opt_i/; - -if (!getopts ('i:h') - || !defined $opt_i - || defined $opt_h) { - print "test-list-extract.pl -i file -o file [-h]\n"; - print " -h display this help\n"; - print " -i file for which list should be generated\n"; - exit (1); -} - -load_build_list ($opt_i); -print_build_names(); - - diff --git a/testmatrix/update_scoreboard.sh b/testmatrix/update_scoreboard.sh deleted file mode 100755 index e67c7db35..000000000 --- a/testmatrix/update_scoreboard.sh +++ /dev/null @@ -1,116 +0,0 @@ -#!/bin/sh - -# Purpose: Update the scoreboard and test matrix -# Original author: Byron Harris -# Modified : Aug 17 2004 : Trevor Fields - -# Directory where the build log directories are -LOG_DIR="/export/web/www/scoreboard" - -# location of the autobuild directory -AUTOBUILD_DIR="$HOME/autobuild" -TEST_MATRIX_DIR="$AUTOBUILD_DIR/testmatrix" -# Location of database scripts directory -DB_SCRIPT_DIRECTORY="$AUTOBUILD_DIR/matrix_database" - - -# name of the file used to prevent running the update at the same time -TEST_MATRIX_PROTECTION_FILE="$TEST_MATRIX_DIR/MATRIX_UNDER_CONSTRUCTION" - -# location of the scoreboard configuration files -SCOREBOARD_CONFIG_DIR="$AUTOBUILD_DIR/configs/scoreboard" - -PERLLIB=$AUTOBUILD_DIR -export PERLLIB - -function update_local_scoreboard() -{ - - # Update the scoreboard - # $HOME/autobuild/scoreboard.pl -f $SCOREBOARD_CONFIG_DIR/acetao.xml -d $LOG_DIR -o acetao.html - - # change directory to the testmatrix directory - cd $TEST_MATRIX_DIR - - # directory for output the db file - DBLOGDIR=$LOG_DIR/test_matrix_db - - # create the db log file directory if it does not exist - if [ ! -d $DBLOGDIR ]; then - mkdir -p $DBLOGDIR - fi - - # file with the list of builds - BUILD_LIST=$TEST_MATRIX_DIR/ace-list - - # beginning of the name of the test matrix - # the name will have ".matrix.html" appended to it - TEST_MATRIX=ace_detailed - - # generate the list of files - perl ./test-list-extract.pl -i $SCOREBOARD_CONFIG_DIR/ace.xml > $BUILD_LIST - - # generate the matrix and the *.db files - ./buildMatrix 1 $BUILD_LIST $TEST_MATRIX - - - BUILD_LIST=$TEST_MATRIX_DIR/tao-list - TEST_MATRIX=tao_detailed - perl ./test-list-extract.pl -i $SCOREBOARD_CONFIG_DIR/tao.xml > $BUILD_LIST - - # generate the matrix and the *.db files - ./buildMatrix 1 $BUILD_LIST $TEST_MATRIX - - - BUILD_LIST=$TEST_MATRIX_DIR/ciao-list - TEST_MATRIX=ciao_detailed - perl ./test-list-extract.pl -i $SCOREBOARD_CONFIG_DIR/ciao.xml > $BUILD_LIST - ./buildMatrix 0 $BUILD_LIST $TEST_MATRIX - - cp -f matrix.css $LOG_DIR/matrix.css - - # update the list of db files - $DB_SCRIPT_DIRECTORY/RemoveAndListTestDbFiles.sh -} - - - -if [ -d "$LOG_DIR" ]; then - - # Protect against simultaneous attempts to build the - # test matrix. - if [ ! -f "$TEST_MATRIX_PROTECTION_FILE" ]; then - date +%s > $TEST_MATRIX_PROTECTION_FILE - update_local_scoreboard - rm "$TEST_MATRIX_PROTECTION_FILE" - else - STARTED_TIME=`cat $TEST_MATRIX_PROTECTION_FILE` - - if [ -n "$STARTED_TIME" ]; then - # add 4 hours (60*60*4) to the number of seconds - LATE_TIME=`expr $STARTED_TIME + 14400` - CURRENT_TIME=`date +%s` - - if [ $CURRENT_TIME -gt $LATE_TIME ]; then - echo "Detected stuck Matrix generation. Removing the protection file." - rm "$TEST_MATRIX_PROTECTION_FILE" - else - echo "Matrix is still being built." - fi - # end of if [ $CURRENT_TIME -gt $LATE_TIME ] - - else - # More likely it is a munged attempt than two processes - # spawned at about the same time. - echo "Found empty protection file. Removing." - rm "$TEST_MATRIX_PROTECTION_FILE" - fi - # end of if [ -n $STARTED_TIME ] - - fi - # end of if [ ! -f "$TEST_MATRIX_PROTECTION_FILE" ] - -fi - -exit 0 - From 0fe28dfc0b39faf2bc674d9cf8a1a0b0cfa2f1fb Mon Sep 17 00:00:00 2001 From: Fred Hornsey Date: Mon, 16 Sep 2024 12:26:50 -0500 Subject: [PATCH 3/4] Improvements to Test Matrix - Status box links to output of tests - Test name links to source code - Add skip counts - Add time - Also: - Control header name of index page in scoreboard.pl - Don't exit with status on with -i or -z options of scoreboard.pl --- common/betterparser.pm | 15 +- common/indexparser.pm | 16 +- common/prettify.pm | 4 + common/scoreparser.pm | 11 +- common/utility.pm | 30 +- docs/scoreboard.txt | 11 + matrix.py | 35 +- scoreboard.pl | 58 +-- testmatrix/HTMLScoreboard.py | 699 +++++++++++++---------------------- testmatrix/__init__.py | 2 + testmatrix/matrix.css | 78 ++-- testmatrix/matrix.js | 40 ++ testmatrix/matrix.py | 203 +++++++--- 13 files changed, 610 insertions(+), 592 deletions(-) create mode 100644 testmatrix/matrix.js diff --git a/common/betterparser.pm b/common/betterparser.pm index 8a4571e09..6e8089a79 100644 --- a/common/betterparser.pm +++ b/common/betterparser.pm @@ -11,6 +11,8 @@ use File::Basename; use POSIX qw(strftime); use B qw(perlstring); +use utility::common; + ############################################################################## # Constructor # @@ -1456,15 +1458,6 @@ sub DealWithCommandTag ($$$$$\%) } } -# Replace XML Entity References -sub replace_entities ($) { - my $str = shift; - $str =~ s/<//g; - $str =~ s/&/&/g; - return $str; -} - ############################################################################### # Parse # This attempts to find each of the XML tags within the file and process them. @@ -1518,7 +1511,7 @@ sub Parse ($$\%) $preMode = 0; } else { - $preModeText .= replace_entities($_); + $preModeText .= utility::replace_entities($_); next; } } @@ -1631,7 +1624,7 @@ sub Parse ($$\%) if ($inputFromFile =~ s/^<\s*(?:[^>"]*(?:"(?:[^"\\]*(?:\\(?:0?[xX][[:xdigit:]]{0,2}|0[0-2]?[0-7]{0,2}|.))*)*")*)*>//) { if ($tag =~ /<[^<>]+>(.*)<[^<>]+>/) { - $same_line_tag_contents = replace_entities($1); + $same_line_tag_contents = utility::replace_entities($1); } # OK, we have found a valid closing tag (the >) character. We need to diff --git a/common/indexparser.pm b/common/indexparser.pm index 1e38a42b3..80eda3b74 100644 --- a/common/indexparser.pm +++ b/common/indexparser.pm @@ -1,4 +1,3 @@ - package IndexParser; use strict; @@ -6,6 +5,8 @@ use warnings; use FileHandle; +use common::utility; + ############################################################################### # Constructor @@ -26,7 +27,8 @@ sub Parse ($) { my $self = shift; my $file = shift; - my $group_name; + my $preamble = shift; + my $props = shift; my $file_handle = new FileHandle ($file, 'r'); @@ -54,7 +56,7 @@ sub Parse ($) next if (m/^\s*$/); if(m//) { - $main::preamble = $self->parse_preamble($file_handle); + ${$preamble} = $self->parse_preamble($file_handle); next; } @@ -74,6 +76,8 @@ sub Parse ($) if (m/^\s*<\/intropage>\s*$/i) { $state = 'none'; } + elsif (utility::parse_prop($_, $props)) { + } else { print STDERR "Error: Unexpected in state <$state>: $_\n"; return 0; @@ -111,7 +115,7 @@ sub parse_comment($\@) while(1){ $ch = $result->getc(); - # determine if we have hit an EOF or not + # determine if we have hit an EOF or not if( ! defined $ch) { last; # break out of the whlie loop } @@ -128,7 +132,7 @@ sub parse_comment($\@) if($tag eq "-->") { last; # break out of the while loop } - + # Pop off the first element of the array and shift everything up shift(@c); $i=1; @@ -160,7 +164,7 @@ sub parse_preamble($\@) while(1){ $ch = $result->getc(); - # determine if we have hit an EOF or not + # determine if we have hit an EOF or not if( ! defined $ch) { last; # break out of the whlie loop } diff --git a/common/prettify.pm b/common/prettify.pm index 26b713881..a531c5b46 100644 --- a/common/prettify.pm +++ b/common/prettify.pm @@ -927,6 +927,7 @@ sub new ($) in_test => undef, in_tests => 0, data => { + subsection_count => 0, buildname => $buildname, basename => $basename, tests => [], @@ -967,6 +968,8 @@ sub Subsection ($) my $self = shift (); my $name = shift (); + my $subsec = $self->{data}->{subsection_count} += 1; + $self->{in_test} = undef; if ($self->{in_tests}) { my $test = { @@ -974,6 +977,7 @@ sub Subsection ($) extra_name => undef, result => undef, time => undef, + subsection => $subsec, }; $self->{in_test} = $test; push(@{$self->{data}->{tests}}, $test); diff --git a/common/scoreparser.pm b/common/scoreparser.pm index 866bfe69d..3714ef09d 100644 --- a/common/scoreparser.pm +++ b/common/scoreparser.pm @@ -6,6 +6,8 @@ use warnings; use FileHandle; +use common::utility; + ############################################################################### # Constructor @@ -28,9 +30,10 @@ sub Parse ($\@) my $file = shift; my $data = shift; my $order = shift; + my $props = shift (); my $group_name; - my %build_info; + my %build_info = (props=>{}); my $file_handle = new FileHandle ($file, 'r'); @@ -80,6 +83,8 @@ sub Parse ($\@) if (m/^\s*<\/scoreboard>\s*$/i) { $state = 'none'; } + elsif (utility::parse_prop ($_, $props)) { + } elsif (m/^\s*\s*$/i) { $state = 'group'; } @@ -116,7 +121,7 @@ sub Parse ($\@) $build_info{GROUP} = $group_name; %{$data->{$build_info{NAME}}} = %build_info; - %build_info = (); + %build_info = (props=>{}); $state = 'group'; } @@ -171,6 +176,8 @@ sub Parse ($\@) elsif (m/^\s*\s*$/i) { $build_info{CACHE} = 1; } + elsif (parse_prop ($_, $build_info{props})) { + } else { print STDERR "Error: $lineno: Unexpected in state <$state>: $_\n"; return 0; diff --git a/common/utility.pm b/common/utility.pm index eb3a243b5..ca66b59f4 100644 --- a/common/utility.pm +++ b/common/utility.pm @@ -4,7 +4,6 @@ use warnings; package utility; use File::Path qw(rmtree); - use JSON::PP; sub obj_to_json @@ -238,4 +237,33 @@ sub remove_tree ($) return 1; } +sub replace_entities +{ + my $str = shift (); + + $str =~ s/"/"/g; + $str =~ s/'/'/g; + $str =~ s/<//g; + $str =~ s/&/&/g; + + return $str; +} + +sub parse_prop +{ + my $line = shift (); + my $props = shift (); + + if ($line =~ m/^\s*\s*$/i) { + if (defined ($props)) { + my $name = $1; + my $value = replace_entities ($2); + $props->{$name} = $value + } + return 1; + } + return 0; +} + 1; diff --git a/docs/scoreboard.txt b/docs/scoreboard.txt index daec6f01b..946a70e3d 100644 --- a/docs/scoreboard.txt +++ b/docs/scoreboard.txt @@ -12,3 +12,14 @@ Within each group, there can be an unlimited number of builds. Each build is defined with the element which contains a , a , and an optional and . The specifies where the log files, from the autobuild.pl script, are located. + + elements can be used to provide additonal optional +properties in XML files: +- Index XML file + - + - title: Override the default header +- Scoreboard XML file + - + - matrix_title: Title of Matrix + - matrix_basename: Prefix for all matrix files + - source_link: URL to view source code diff --git a/matrix.py b/matrix.py index 3ce340bd6..bafa4b1fa 100755 --- a/matrix.py +++ b/matrix.py @@ -6,20 +6,39 @@ from pathlib import Path import enum -from testmatrix.HTMLScoreboard import write_html_matrix -from testmatrix.matrix import Builds, Matrix +from testmatrix import Matrix, write_html_files + + +def opt_or_prop(matrix, args, name): + opt = getattr(args, name) + if opt is not None: + return opt + prop_name = f'matrix_{name}' + if prop_name in matrix.props: + return matrix.props[prop_name] + sys.exit(f'Need to either pass --{name} VALUE or define in the ' + 'scoreboard XML file.') if __name__ == '__main__': arg_parser = argparse.ArgumentParser( - description='Generate a test status matrix from autobuild output') + description='Generates a test status matrix from autobuild output.') arg_parser.add_argument('builds_dir', type=Path, - help='Directory with autobuild/scoreboard build contents') - arg_parser.add_argument('prefix') + help='Directory with the autobuild/scoreboard build contents') + arg_parser.add_argument('--title', + help='Title of the main matrix HTML page. Overrides in the ' + 'scoreboard XML files.') + arg_parser.add_argument('--basename', + help='Prefix of all created files. Overrides in the ' + 'scoreboard XML files.') + arg_parser.add_argument('--dump-only', action='store_true', + help='Don\'t create any files, only dump the matrix in the CLI.') args = arg_parser.parse_args() - builds = Builds(args.builds_dir) - matrix = Matrix(builds) + matrix = Matrix(args.builds_dir) matrix.dump() - write_html_matrix(matrix, args.prefix) + if not args.dump_only: + write_html_files(matrix, + opt_or_prop(matrix, args, 'title'), + opt_or_prop(matrix, args, 'basename')) diff --git a/scoreboard.pl b/scoreboard.pl index e6081080f..b7858ed90 100755 --- a/scoreboard.pl +++ b/scoreboard.pl @@ -53,6 +53,7 @@ # ->{CVS_TIMESTAMP} <- The time AFTER the last cvs operation (PRISMTECH still use some cvs, please leave) my %builds; +my %props; # %groups->{$name} <- list of builds for $name @@ -291,7 +292,7 @@ ($) print "Loading Build List\n" if ($verbose); my $parser = new ScoreboardParser; - $parser->Parse ($file, \%builds, \@ordered); + $parser->Parse ($file, \%builds, \@ordered, \%props); } ############################################################################### @@ -312,6 +313,12 @@ ($$) my $index = shift; my $filename = "$dir/index.html"; + my $parser = new IndexParser; + my $index_preamble; + my %index_props = (); + $parser->Parse ($index, \$index_preamble, \%index_props); + my $index_title = $index_props{title} // 'Welcome to DOCGroup Distributed Scoreboard'; + my $indexhtml = new FileHandle; print "Generating index page\n" if ($verbose); @@ -323,15 +330,13 @@ ($$) ### Print Header print $indexhtml "\n"; - print $indexhtml "\n\nWelcome to DOCGroup Distributed Scoreboard\n"; + print $indexhtml "\n\n$index_title\n"; print $indexhtml "\n"; ### Start body - print $indexhtml "

Welcome to DOCGroup Distributed Scoreboard\n

\n
\n"; - my $parser = new IndexParser; - $parser->Parse ($index, \%builds); - print $indexhtml "$preamble\n"; + print $indexhtml "

$index_title

\n
\n"; + print $indexhtml "$index_preamble\n"; print $indexhtml "\n
\n"; ### Failed Test Reports @@ -357,10 +362,6 @@ ($$) print $indexhtml "\n\n"; $indexhtml->close (); - - my $file = shift; - - print "Creating index page\n" if ($verbose); } ############################################################################### @@ -627,9 +628,8 @@ ($) my $description = ''; if ($timestamp =~ m/(\d\d\d\d)_(\d\d)_(\d\d)_(\d\d)_(\d\d)/) { - - my $buildtime = timegm (0, $5, $4, $3, $2 - 1, $1); - $description = format_time($buildtime); + my $buildtime = timegm (0, $5, $4, $3, $2 - 1, $1); + $description = format_time($buildtime); } else { warn 'Unable to decode time'; @@ -1678,24 +1678,24 @@ sub format_time my $use_long_format = shift; if ($use_local) { - my @tmp = localtime($time_in_secs); + my @tmp = localtime($time_in_secs); my $hour = int($tmp[2]); my $ampm = ($hour >= 12 ? 'pm' : 'am'); if ($hour > 12) { - $hour -= 12; - } - elsif ($hour == 0) { - $hour = 12; - } + $hour -= 12; + } + elsif ($hour == 0) { + $hour = 12; + } my $year = int($tmp[5]) + 1900; - if (defined $use_long_format && $use_long_format) { - return sprintf("%d/%02d/%s %02d:%02d:%02d %s", - int($tmp[4]) + 1, int($tmp[3]), $year, $hour, $tmp[1], $tmp[0], $ampm); - } else { - return sprintf("%02d/%02d %02d:%02d %s", - int($tmp[4]) + 1, int($tmp[3]), $hour, $tmp[1], $ampm); + if (defined $use_long_format && $use_long_format) { + return sprintf("%d/%02d/%s %02d:%02d:%02d %s", + int($tmp[4]) + 1, int($tmp[3]), $year, $hour, $tmp[1], $tmp[0], $ampm); + } else { + return sprintf("%02d/%02d %02d:%02d %s", + int($tmp[4]) + 1, int($tmp[3]), $hour, $tmp[1], $ampm); - } + } } return scalar(gmtime($time_in_secs)); } @@ -1733,6 +1733,7 @@ sub write_builds_json utility::write_obj_to_json ("$dir/builds.json", { builds => \@builds, + props => \%props, }); } @@ -1836,9 +1837,10 @@ sub write_builds_json if (defined $opt_i){ $index = $opt_i; + load_build_list ($inp_file); print 'Running Index Page Update at ' . get_time_str() . "\n" if ($verbose); build_index_page ($dir, $index); - exit (1); + exit (0); } if (defined $opt_b) { @@ -1857,7 +1859,7 @@ sub write_builds_json if (defined $opt_z) { print 'Running Integrated Page Update at ' . get_time_str() . "\n" if ($verbose); build_integrated_page ($dir, $opt_j); - exit (1); + exit (0); } $inp_file = $opt_f; diff --git a/testmatrix/HTMLScoreboard.py b/testmatrix/HTMLScoreboard.py index 4d655b91e..f69e1963c 100644 --- a/testmatrix/HTMLScoreboard.py +++ b/testmatrix/HTMLScoreboard.py @@ -9,23 +9,33 @@ import re import os import time +import json from pathlib import Path from io import StringIO +from contextlib import contextmanager -from .matrix import Status +from .matrix import Status, Build, Matrix this_dir = Path(__file__).resolve().parent -indent = ' ' +indent = ' ' -class Html: - def __init__(self, indent_by=0): - self.f = StringIO() - self.indent_by = indent_by +def relative_to(a, b): + return os.path.relpath(a.resolve(), b.resolve().parent) + + +class HtmlPage: + def __init__(self, path: Path, title: str, js: Path = None, css: Path = None): + self.path = path + self.title = title + self.js = js + self.css = css + self.file = None + self.indent_by = 0 def print(self, *args, end='', **kw): - print(*args, end=end, file=self.f, **kw) + print(*args, end=end, file=self.file, **kw) def print_indent(self): self.print(indent * self.indent_by) @@ -43,451 +53,254 @@ def pop_println(self, *args, **kw): self.indent_by -= 1 self.println(*args, **kw) - def __str__(self): - return self.f.getvalue() - - -def tag(name, classes=None, attrs=None): - t = '<' + name - if attrs is None: - attrs = {} - if classes is None: - classes = [] - if classes: - attrs['class'] = ' '.join(classes) - if attrs: - t += ' ' + ' '.join([f'{k}="{v}"' for k, v in attrs.items()]) - return t + '>' - + @contextmanager + def block(self, tag): + tag = Tag(tag) + self.println_push(tag.begin()) + try: + yield None + finally: + self.pop_println(tag.end()) + + def __enter__(self): + if self.path is not None: + self.file = self.path.open('w') + self.println('') + self.println('') + with self.block('head'): + self.println(Tag('title', self.title)) + if self.css: + self.println(Tag('link', rel='stylesheet', href=self.css)) + if self.js: + self.println(Tag('script', '', src=self.js)) + self.println('') + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + if exc_traceback is None: + self.println('') + self.println('') + if self.file is not None: + self.file.close() + + +class Tag: + def __init__(self, n=None, t=None, c=[], **attrs): + if type(n) is Tag: + self.name = n.name + self.text = n.text + self._classes = n._classes + self._attrs = n._attrs + else: + self.name = n + self.text = t + self._classes = c + self._attrs = attrs + + def with_name(self, name): + t = Tag(self) + if self.name is None: + t.name = name + return t + + def with_text(self, text): + t = Tag(self) + if self.text is None: + t.text = text + return t + + def begin(self): + rv = '<' + self.name + attrs = self._attrs.copy() + if self._classes: + attrs['class'] = ' '.join(self._classes) + if attrs: + rv += ' ' + ' '.join([f'{k}="{v}"' for k, v in attrs.items()]) + return rv + '>' + + def end(self): + return f'' -def full_tag(name, text, classes=None, attrs=None): - if attrs is None: - attrs = {} - if classes is None: - classes = [] - return tag(name, classes, attrs) + f'{text}' + def __str__(self): + rv = self.begin() + if self.text is not None: + rv += str(self.text) + self.end() + return rv class HtmlTable: - def __init__(self, name, cols, classes=[], attrs={}): - self.name = name + def __init__(self, title, cols, page=None, tag=Tag(), row_tag=Tag()): + self.title = title self.cols = cols - self.header = (None, [], {}) - self.rows = [self.header] - self.classes = classes - self.attrs = attrs - - def row(self, cells, classes=[], attrs=None): + self.page = page + self.tag = tag + self.title_row = [Tag('th', self.title, ['head'], colspan=len(cols))] + self.header = [Tag('th', c) for c in cols] + self.rows = [self.title_row, self.header] + self.row_tag = Tag(row_tag).with_name('tr') + + def row(self, cells, default_tag=Tag()): if len(cells) != len(self.cols): raise ValueError(f'Got {len(cells)} cells, but we have {len(self.cols)} columns!') - if attrs is None: - attrs = {} - self.rows.append(([c if type(c) is tuple else (c, [], {}) for c in cells], classes, attrs)) + tags = [] + for cell in cells: + tag = cell if type(cell) is Tag else default_tag.with_text(cell) + tags.append(tag.with_name('td')) + self.rows.append(tags) def extra_header(self): self.rows.append(self.header) - def done(self, html): - html.println_push(tag('table', self.classes, self.attrs)) - for row, row_classes, row_attrs in self.rows: - html.println_push(tag('tr', row_classes, row_attrs)) - html.print_indent() - if row is None: # Header - for col in self.cols: - html.print(full_tag('th', col)) - else: # Normal row - for cell, cell_classes, cell_attrs in row: - html.print(full_tag('td', cell, cell_classes, cell_attrs)) - html.println('') - html.pop_println('') - html.pop_println('
') - - -class HTMLTestMatrix: - def __init__(self, matrix, title): - self.matrix = matrix - self.title = title - self.directory = matrix.builds.dir - self.matrix_html_table = None - self.matrix_header = None - self.build_summary_html = None - self.main_summary_html = None - - self.matrix_row = 0 - self.passed = 0 - self.failed = 0 - - # TODO: Remove - self.highlight_html = "onmouseover=\"this.style.backgroundColor='hotpink';\" onmouseout=\"this.style.backgroundColor='';\"" - - # TODO: Remove JQuery - self.html_start = """ - - - Scoreboard Matrix - - - - - - -""" - self.html_end = """ -""" - - self.key_html = """ - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Key
PassFailWarnSkipCompile Fail
100%<50%<90%
-""" - - def addTestData(self, name, results): - if self.matrix_html_table is None: - cols = ['# Pass', '# Fail', '# Skip', '% Pass'] - cols += [str(n + 1) for n in range(len(self.matrix.builds))] - cols += ['Test Name'] - self.matrix_html_table = HtmlTable('Test Results', cols) - - tb = self.matrix_html_table - - self.matrix_row += 1 - - # Repeat the header row every now and then - if self.matrix_row % 20 == 0: - tb.extra_header() - - row_classes = [] - - # Alternate row color - if self.matrix_row & 1: - row_classes.append('odd') - - npass = results.stats.passed - nfail = results.stats.failed - nskip = results.stats.skipped - - # If any failed - if nfail > 0: - self.failed += 1 - elif npass > 0: - self.passed += 1 - - - if nfail == 0: - status_classes = [] - elif nfail == 1: - status_classes = ['warn'] - else: - status_classes = ['fail'] - - row = [ - npass, - (nfail, status_classes, {}), - nskip, - results.stats.perc_passed, - ] - for status in self.matrix.statuses_for_test(results): - row.append(('', [status.name[0].lower()], {})) - row.append((results.name, status_classes, {})) - tb.row(row, row_classes) - - def getSummaryHTML(self, name, stats): - # TODO: Use HtmlTable - html = """ - - - - - - - - - - - - - - -""" - - html += '' % stats.total - html += '' % stats.passed - html += '' % stats.failed - if stats.skipped == 0: - html += '' - else: - html += '' % stats.skipped - html += f'' - html += f'' - html += '\n' - html += '
-""" - html += name + """ -Summary
# Total# Pass# Fail# Skip% Pass% Fail
%d%d%d-%d{stats.perc_passed}{stats.perc_failed}
\n' - html += '

%d tests passed, ' % self.passed - html += '%d failed

\n' % self.failed - return html - - def writeBriefs(self): - self.main_summary_html = self.getSummaryHTML("", self.matrix.all_stats) - - def writeBuildSummary(self, num, build): - num += 1 - # TODO: Use HtmlTable - if not self.build_summary_html: - self.build_summary_html = """ - - - - - - - - - - - - - - - - - - - - -""" - - html = self.build_summary_html - - # now we have to write out the key to the build numbers - # TODO: Rename and cleanup - ace = 0.0 - acecls = None - - if build.stats.ran: - ace = (build.stats.passed / build.stats.ran) * 100 - if ace < 50.0: - acecls = "f" - elif ace < 90.0: - acecls = "w" - - # TODO? - # if build.compile == FAIL: - if False: - html += '\n" % fname - - html += "" % num - html += '" - html += "" % build.stats.total - html += "" % build.stats.failed - sperc = "%.1f" % ace - if acecls: - html += '" - time = float(build.time) / 60.0 - timestr = "%.0f" % time - html += "" % build.stats.skipped - html += "" - - self.build_summary_html = html - - def writeHTML(self): - path = (self.directory / (self.title + ".html")).resolve() - print(f'Writing html to {path}') - - if not self.build_summary_html: - self.build_summary_html = "" - if self.matrix_html_table is None: - matrix_html = '' - else: - self.matrix_html_table.extra_header() - matrix_html = Html() - self.matrix_html_table.done(matrix_html) - if not self.matrix_header: - self.matrix_header = "" - - with path.open('w') as f: - f.write(self.html_start) - f.write(self.main_summary_html) - f.write(self.key_html) - f.write(self.build_summary_html) - f.write("
Build Summary
#Name## Fail% Pass# SkipTime (min)
%d' + build.name + "%d%d' - else: - html += '' - html += sperc + "%d" + timestr + "
") - f.write(str(matrix_html)) - f.write("
Last updated at ") - f.write(time.asctime(time.localtime())) - f.write("
") - f.write(self.html_end) - - def writeHTMLsummary(self): - path = (self.directory / (self.title + "-summary.html")).resolve() - print(f'Writing html summary to {path}') - - with path.open('w') as f: - f.write(self.html_start) - f.write(self.main_summary_html) - f.write(self.html_end) - - -class HTMLPlatformTestTable: - def __init__(self, title, dir): - self.title = title - self.directory = dir - self.test_table_html = None - self.rownum = 0 - - self.html_start = """ - - - - - - -""" - - self.html_end = """ - - -""" - - def addData2(self, num, flag, time, name): - # TODO: Use HtmlTable - if not self.test_table_html: - self.test_table_html = """ - - - - - - - - - - -""" % self.title - html = self.test_table_html - - self.rownum += 1 - - html += "" - html += "" - if time == 0.0: - html += "" - else: - timestr = "%.0f" % time - if time > 300.0: - html += '" - - self.test_table_html = html - - def writeHTML(self): - fname = self.directory + "/" + self.title + "_j.html" - fname = os.path.normpath(fname) - print("Writing html to '" + fname + "'") - - f = open(fname, "w", 1) - try: - f.write(self.html_start) - if self.test_table_html: - f.write(self.test_table_html) - f.write("
%s Details
NamePass/FailTime
" + result + "-' - elif time > 10.0: - html += '' + def done(self, page): + with page.block(self.tag.with_name('table')): + for cells in self.rows: + with page.block(self.row_tag): + page.print_indent() + for cell in cells: + page.print(cell) + page.print('\n') + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + if exc_traceback is None: + self.done(self.page) + + @classmethod + def write_simple(cls, page, title, cols, rows): + with cls(title, cols, page) as table: + for row in rows: + table.row(row) + + +def get_build_details_path(build, basename): + return build.dir / f'{basename}-build-details.html' + + +def write_build_summary_table(page, matrix, basename): + cols = ['#', 'Name', '# Ran', '# Failed', '% Passed', '# Skipped', 'Total Time'] + with HtmlTable('Build Summary', cols, page=page) as tb: + for n, build in enumerate(matrix.builds.values()): + perc_classes = [] + s = build.stats + if s.ran: + perc = s.perc_passed + if perc < 50.0: + perc_classes.append('f') + elif perc < 90.0: + perc_classes.append('w') + link = Tag('a', t=build.name, + href=relative_to(get_build_details_path(build, basename), page.path)) + tb.row([n, Tag(t=link, c='txt'), s.ran, s.failed, + Tag(t=s.perc_passed_str, c=perc_classes), s.skipped, s.time_str()]) + + +def write_test_matrix_table(page, matrix): + cols = ['# Pass', '# Fail', '# Skip', '% Pass', 'Avg Time'] + cols += [Tag('div', '', ['empty']) for n in range(len(matrix.builds))] + cols += ['Test Name'] + + with HtmlTable('Test Results', cols, page, + tag=Tag(c=['test_results']), row_tag=Tag(c=['test_row'])) as tb: + matrix_row = 0 + for name, results in matrix.tests.items(): + matrix_row += 1 + + # Repeat the header row every now and then + if matrix_row % 40 == 0: + tb.extra_header() + + npass = results.stats.passed + nfail = results.stats.failed + nskip = results.stats.skipped + if nfail == 0: + status_classes = [] + elif nfail == 1: + status_classes = ['w'] else: - html += '' - html += timestr + "
") - f.write(self.html_end) - finally: - f.close() - - -def write_html_matrix(matrix, prefix): - html_tm = HTMLTestMatrix(matrix, prefix) - for name, test in matrix.tests.items(): - html_tm.addTestData(name, test) - - html_tm.writeBriefs() - for n, build in enumerate(matrix.builds): - html_tm.writeBuildSummary(n, build) - html_tm.writeHTML() - html_tm.writeHTMLsummary() - - css = 'matrix.css' - (matrix.builds.dir / css).write_text((this_dir / css).read_text()) + status_classes = ['f'] + + row = [ + npass, + Tag(t=nfail, c=status_classes), + nskip, + results.stats.perc_passed_str, + results.stats.avg_time_str(), + ] + for build_n, build in enumerate(matrix.builds.values()): + run, status = results.run_and_status_for_build(build.name) + attrs = {'build': build_n} + if run is not None: + attrs['test'] = run.subsection + row.append(Tag(t='', c=[status.name[0].lower()], **attrs)) + row.append(Tag(t=results.name, c=status_classes + ['test_name'])) + tb.row(row) + + tb.extra_header() + + +def write_matrix_html(matrix: Matrix, title: str, basename: str, get_css, get_js): + path = matrix.dir / f'{basename}-matrix.html' + with HtmlPage(path, title, get_css(path), get_js(path)) as page: + page.println(Tag('h1', title)) + + s = matrix.all_stats + HtmlTable.write_simple(page, 'Summary', + ['# Ran', '# Pass', '# Fail', '# Skip', '% Pass', '% Fail', 'Time'], + [[s.ran, s.passed, s.failed, s.skipped, + s.perc_passed_str, s.perc_failed_str, s.time_str()]]) + + HtmlTable.write_simple(page, 'Key', + ['Pass', 'Fail', 'Warn', 'Skip'], + [[Tag(t='100% Passed', c=['p']), Tag(t='<50% Passed', c=['f']), + Tag(t='<90% Passed', c=['w']), '']]) + + write_build_summary_table(page, matrix, basename) + + write_test_matrix_table(page, matrix) + + page.println("
Last updated at ") + page.println(time.asctime(time.localtime())) + + +def write_build_details_html(build: Build, build_n, basename: str, get_css, get_js): + path = get_build_details_path(build, basename) + with HtmlPage(path, f'{build.name} Details', get_css(path), get_js(path)) as p: + table = HtmlTable(p.title, ['', 'Time', 'Name'], tag=Tag(c=['test_results'])) + for test in build: + table.row([ + Tag(c=[test.status.name[0].lower()], t='', test=test.subsection, build=build_n,), + test.time_str(), + Tag(t=test.name, c=['test_name']), + ]) + table.done(p) + + +def copy_assets(matrix, basename): + out_path = matrix.dir + + matrix_js = 'matrix.js' + js_out = out_path / f'{basename}-{matrix_js}' + js_out.write_text('\n'.join(['var {} = {};'.format(k, json.dumps(v)) for k, v in dict( + build_info=[{ + 'name': b.name, + 'basename' : b.basename, + 'props': b.props, + } for b in matrix.builds.values()], + props=matrix.props, + ).items()]) + '\n\n' + (this_dir / matrix_js).read_text()) + + matrix_css = 'matrix.css' + css_out = out_path / f'{basename}-{matrix_css}' + css_out.write_text((this_dir / matrix_css).read_text()) + return [(lambda html_path, p=p: relative_to(p, html_path)) for p in [js_out, css_out]] + + +def write_html_files(matrix, title, basename): + get_js, get_css = copy_assets(matrix, basename) + + write_matrix_html(matrix, title, basename, get_js, get_css) + + for n, build in enumerate(matrix.builds.values()): + write_build_details_html(build, n, basename, get_js, get_css) diff --git a/testmatrix/__init__.py b/testmatrix/__init__.py index e69de29bb..ace69868f 100644 --- a/testmatrix/__init__.py +++ b/testmatrix/__init__.py @@ -0,0 +1,2 @@ +from testmatrix.HTMLScoreboard import write_html_files +from testmatrix.matrix import Matrix diff --git a/testmatrix/matrix.css b/testmatrix/matrix.css index 874fb2386..179c48201 100644 --- a/testmatrix/matrix.css +++ b/testmatrix/matrix.css @@ -1,55 +1,69 @@ -body {background-color: white;} +body { + background-color: white; + font-family: verdana, arial; +} table { - background-color: #d9d9d9; border: 4px solid black; margin: 5px 0; border-collapse: collapse; table-layout: fixed; - font: 10pt arial; + font-size: 10pt; } -tr {overflow: hidden; } -tr.odd {background-color: #c0c0c0;} - -tr.oddlnk {background-color: #c0c0c0; cursor: pointer;} +tr { + overflow: hidden; + line-height: 1.25em; +} +tr:nth-child(even) td { + filter: brightness(90%); +} +tr:hover:has(td) { + outline: 0.15rem solid blue; + outline-offset: -0.1rem +} th.head { - background-color: #a9a9a9; - font: bold 12pt verdana, arial; + background-color: darkgrey; + font-size: 12pt; + font-weight: bold; cursor: arrow; } th { - background-color: #a9a9a9; + background-color: darkgrey; border: 2px solid white; - padding:2px 2px; + padding: 2px 2px; cursor: default; - font: bold 10pt verdana, arial; + font-size: 10pt; + font-weight: bold; } + td { + background-color: lightgrey; border: 2px solid white; - padding:0 2px; + padding: 0; overflow: hidden; text-align: right; + height: 1.25em; + width: 1em; +} +td.empty { + height: 1.25em; + width: 1em; +} +table.test_results td[test] a { + display: block; + height: 1.25em; + width: 1em; +} +td.test_name { + font-family: monospace; + text-align: left; + overflow-x: scroll; + white-space: nowrap; } td.txt {text-align: left;} -.p {background-color:green} -.f,.fail {background-color:red; color: white;} -.w,.warn {background-color:gold; color: black;} -.s {background-color:gainsboro; color: black; text-decoration: line-through;} - -.lnk {cursor: pointer;} -.faillnk {background-color:firebrick; cursor: pointer; color: white; } -.warnlnk {background-color:gold; color: black; cursor: pointer;} -.skiplnk {background-color:gainsboro; color: black; text-decoration: line-through; cursor: pointer;} - - -.lnk:hover {background-color: hotpink;} -.odd:hover {background-color: hotpink;} -.oddlnk:hover {background-color: hotpink;} -.faillnk:hover {background-color: hotpink;} -.warnlnk:hover {background-color: hotpink;} -.skiplnk:hover {background-color: hotpink;} - - +.p {background-color: green; color: white;} +.f {background-color: red; color: white;} +.w {background-color: gold; color: black;} diff --git a/testmatrix/matrix.js b/testmatrix/matrix.js new file mode 100644 index 000000000..07cc6930b --- /dev/null +++ b/testmatrix/matrix.js @@ -0,0 +1,40 @@ +window.addEventListener('load', (event) => { + document.querySelectorAll('.test_results td').forEach((td) => { + var buildn = td.getAttribute('build'); + var build = build_info[buildn]; + console.log(build); + if (build) { + // Put build name in tooltip + td.setAttribute('title', `#${buildn} ${build.name}`); + + var test_subsec = td.getAttribute('test'); + if (test_subsec) { + // Link to test run output + var a = document.createElement('a'); + var url = `${build.name}/${build.basename}_`; + if (td.classList.contains('f')) { + url += 'Brief'; + } else { + url += 'Full'; + } + a.href = `${url}.html#subsection_${test_subsec}`; + // a.textContent = td.textContent; + td.replaceChildren(a); + } + } + }); + + if (props.source_link) { + document.querySelectorAll('.test_results td.test_name').forEach((td) => { + var a = document.createElement('a'); + var parts = td.textContent.split(" "); + a.href = props.source_link + parts[0]; + a.textContent = parts[0]; + + var span = document.createElement('span'); + span.textContent = ' ' + parts.slice(1).join(' '); + + td.replaceChildren(a, span) + }); + } +}); diff --git a/testmatrix/matrix.py b/testmatrix/matrix.py index 592be23a1..d025914f5 100644 --- a/testmatrix/matrix.py +++ b/testmatrix/matrix.py @@ -1,12 +1,18 @@ +import sys +import os import json import enum +from datetime import timedelta from pathlib import Path +use_ansi = os.name != 'nt' and sys.stdout.isatty() + + class Status(enum.Enum): - Passed = (True, 0, '✅') - Failed = (True, 1, '❌') - Skipped = (False, 2, '__') + Passed = (True, 0, '\033[0;42m+\033[0m', '+') + Failed = (True, 1, '\033[0;41mX\033[0m', 'X') + Skipped = (False, 2, '.', '.') @property def is_a_run(self): @@ -17,34 +23,91 @@ def index(self): return self.value[1] @property - def emoji(self): - return self.value[2] + def text(self): + return self.value[2 if use_ansi else 3] + + +def timedelta_str(td): + # str(td) would work, but it returns a string that's longer than it + # needs to be. + days, rem_hours = divmod(round(td.total_seconds()), 24 * 60 * 60) + hours, rem_mins = divmod(rem_hours, 60 * 60) + mins, secs = divmod(rem_mins, 60) + + s = '' + + if days: + s += f'{days} day' + if td.days > 1: + s += 's' + if rem_hours: + s += ' ' + + if hours: + s += f'{hours}:' + + if mins: + if hours: + s += f'{mins:02}:' + else: + s = f'{mins}:' + + if hours or mins: + s += f'{secs:02}' + else: + s += f'{secs} s' + + return s class TestStats: statuses = {s.name.lower(): s for s in Status} def __init__(self): - self.total = 0 self.ran = 0 self._counts = [0] * len(Status) + self.time = None + self.max_time = timedelta() + self.min_time = timedelta.max def __getitem__(self, status): return self._counts[status.index] - def __iadd__(self, status): + def add(self, status: Status, time=None): self._counts[status.index] += 1 - self.total += 1 if status.is_a_run: self.ran += 1 - return self + if time is not None: + if self.time is None: + self.time = timedelta() + self.time += time + self.max_time = max(self.max_time, time) + self.min_time = max(self.min_time, time) + + def time_str(self): + return timedelta_str(self.time) + + def avg_time(self): + return timedelta(seconds=(self.time.total_seconds() / self.ran)) + + def avg_time_str(self): + return timedelta_str(self.avg_time()) def __bool__(self): return bool(self.ran) + def set_skipped(self, n): + self._counts[Status.Skipped.index] = n + + def total(self): + return sum(self._counts) + def perc(self, status): - d = self.ran if status.is_a_run else self.total - return format(self[status] / d, '.2%') + d = self.ran if status.is_a_run else self.total() + return (self[status] / d) * 100 if d else 0 + + def perc_str(self, status): + return format(self.perc(status) / 100, '.2%') def __str__(self): s = f'{self.ran} ran' @@ -52,8 +115,10 @@ def __str__(self): count = self[status] if count: name = status.name.lower() - perc = self.perc(status) + perc = self.perc_str(status) s += f', {count} {name} ({perc})' + if self.time is not None: + s += ', avg ' + self.avg_time_str() return s @@ -62,17 +127,20 @@ def __str__(self): # object. setattr(TestStats, name, property(lambda self, s=Status(status): self[s])) setattr(TestStats, f'perc_{name}', property(lambda self, s=Status(status): self.perc(s))) + setattr(TestStats, f'perc_{name}_str', property(lambda self, s=Status(status): self.perc_str(s))) class TestRun: - def __init__(self, name: str, result: int, time: int, misc=None): + def __init__(self, name: str, result: int, time: timedelta, misc=None): if misc is None: misc = {} self.name = name extra_name = misc.get('extra_name', None) if extra_name: - self.name += '-' + misc['extra_name'] + self.name += ' ' + misc['extra_name'] + self.subsection = misc['subsection'] self.result = result + assert type(time) is timedelta self.time = time self.misc = misc @@ -83,8 +151,11 @@ def __bool__(self): def status(self): return Status.Passed if self else Status.Failed + def time_str(self): + return timedelta_str(self.time) + def __str__(self): - return f'{self.name}: time={self.time} result={self.result}' + return f'{self.name}: time={self.time_str()} result={self.result}' class Build: @@ -95,19 +166,20 @@ def __init__(self, name, builds_dir, misc=None): self.dir = builds_dir / name self.misc = misc self.stats = TestStats() + self.basename = misc['basename'] + self.props = misc.get('props', {}) - build_json = self.dir / (misc['basename'] + '.build.json') + build_json = self.dir / (self.basename + '.build.json') with build_json.open('r') as f: data = json.load(f) self.tests = {} - self.time = 0 for t in data['tests']: - test_run = TestRun(t['name'], int(t['result']), int(t['time']), t) + test_run = TestRun(t['name'], int(t['result']), timedelta(seconds=int(t['time'])), t) + self.stats.add(test_run.status, test_run.time) if test_run.name in self.tests: raise ValueError(f'Multiple {repr(test_run.name)} in {self.name}!') self.tests[test_run.name] = test_run - self.time += test_run.time def __iter__(self): return iter(self.tests.values()) @@ -119,79 +191,88 @@ def __contains__(self, key): return key in self.tests -class Builds: - def __init__(self, builds_dir): - self.dir = builds_dir - - builds_json = builds_dir / 'builds.json' - with builds_json.open('r') as f: - data = json.load(f) - - self.builds = {} - for b in data['builds']: - name = b['name'] - self.builds[name] = Build(name, builds_dir, b) - - def __len__(self): - return len(self.builds) - - def __iter__(self): - return iter(self.builds.values()) - - class Test: - def __init__(self, name): + def __init__(self, name, build_test_run_pairs, build_count): self.name = name self.runs = {} self.stats = TestStats() - def add_run(self, build: Build, test: TestRun): - self.runs[build.name] = (build, test) + for build, test_run in build_test_run_pairs: + self.runs[build.name] = (build, test_run) + self.stats.add(test_run.status, test_run.time) + + self.stats.set_skipped(build_count - len(build_test_run_pairs)) + + def run_and_status_for_build(self, build_name): + if build_name in self.runs: + run = self.runs[build_name][1] + return (run, run.status) + return (None, Status.Skipped) def status_for_build(self, build_name): - return self.runs[build_name][1].status if build_name in self.runs else Status.Skipped + return self.run_and_status_for_build(build_name)[1] def __len__(self): return len(self.runs) class Matrix: - def __init__(self, builds: Builds): - self.builds = builds - self.tests = {} + def __init__(self, builds_dir: Path): + self.dir = builds_dir self.all_stats = TestStats() + builds_json = builds_dir / 'builds.json' + with builds_json.open('r') as f: + data = json.load(f) + + self.props = data['props'] + + # Collect all the builds + self.builds = {} + for b in data['builds']: + name = b['name'] + build = Build(name, builds_dir, b) + self.builds[name] = build + # Collect all the tests - for build in builds: + all_tests = {} + for build in self.builds.values(): for test_run in build: n = test_run.name - if n in self.tests: - test = self.tests[n] + if n in all_tests: + all_tests[n].append((build, test_run)) else: - test = Test(n) - self.tests[n] = test - test.add_run(build, test_run) + all_tests[n] = [(build, test_run)] + + # We now know both the total number of builds and tests. + self.tests = {} + for test_name, build_test_run_pairs in all_tests.items(): + self.tests[test_name] = Test(test_name, build_test_run_pairs, len(self.builds)) + for build in self.builds.values(): + build.stats.set_skipped(len(self.tests) - build.stats.ran) + + # Sort tests by % percent passed ascending, then name (case insensitive) + self.tests = {n: t for n, t in sorted(self.tests.items(), + key=lambda nt: (nt[1].stats.perc_passed, nt[0].lower()))} - # Colect stats + # Collect all stats for name, test in self.tests.items(): - for build in builds: - status = test.status_for_build(build.name) - test.stats += status - build.stats += status - self.all_stats += status + for build in self.builds.values(): + run, status = test.run_and_status_for_build(build.name) + self.all_stats.add(status, None if run is None else run.time) def statuses_for_test(self, test): - for build in self.builds: + for build in self.builds.values(): yield test.status_for_build(build.name) def dump(self): for name, test in self.tests.items(): for status in self.statuses_for_test(test): - print(status.emoji, end='') + print(status.text, end='') print(' ', test.stats, name) print() - for n, build in enumerate(self.builds): + for n, build in enumerate(self.builds.values()): print(f'[{n}] {build.name}: {build.stats}') print() From 6219f6e938ced35321fcaad1fd6c19b1a127e473 Mon Sep 17 00:00:00 2001 From: Fred Hornsey Date: Wed, 18 Sep 2024 13:23:50 -0500 Subject: [PATCH 4/4] Add Test for Matrix, Fix Scoreboard Typo --- .github/workflows/tests.yml | 2 +- common/betterparser.pm | 2 +- common/prettify.pm | 4 -- testmatrix/HTMLScoreboard.py | 9 ++-- testmatrix/matrix.js | 2 - testmatrix/matrix.py | 3 -- tests/scoreboard/MultiRun/run_test.pl | 49 +++++++++---------- .../{MultiRun => build_logs}/test.xml | 4 ++ tests/scoreboard/matrix/.gitignore | 1 + tests/scoreboard/matrix/run_test.pl | 42 ++++++++++++++++ 10 files changed, 78 insertions(+), 40 deletions(-) rename tests/scoreboard/{MultiRun => build_logs}/test.xml (55%) create mode 100644 tests/scoreboard/matrix/.gitignore create mode 100755 tests/scoreboard/matrix/run_test.pl diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3047cfcc8..e20f3f64a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,6 +17,6 @@ jobs: - name: Install Perl dependencies run: | sudo apt-get update - sudo apt-get install libwww-perl + sudo apt-get install libwww-perl libfile-copy-recursive-perl - name: Run Tests run: perl run_tests.pl diff --git a/common/betterparser.pm b/common/betterparser.pm index 6e8089a79..e725c8d9e 100644 --- a/common/betterparser.pm +++ b/common/betterparser.pm @@ -11,7 +11,7 @@ use File::Basename; use POSIX qw(strftime); use B qw(perlstring); -use utility::common; +use common::utility; ############################################################################## # Constructor diff --git a/common/prettify.pm b/common/prettify.pm index a531c5b46..c595a01a8 100644 --- a/common/prettify.pm +++ b/common/prettify.pm @@ -974,7 +974,6 @@ sub Subsection ($) if ($self->{in_tests}) { my $test = { name => $name, - extra_name => undef, result => undef, time => undef, subsection => $subsec, @@ -1008,9 +1007,6 @@ sub Normal ($) $test->{time} = $finished->[1]; $test->{result} = $finished->[2]; } - elsif ($line =~ /^The CMake name of this test is "(.*)"/) { - $test->{extra_name} = $1; - } } } diff --git a/testmatrix/HTMLScoreboard.py b/testmatrix/HTMLScoreboard.py index f69e1963c..eac57dd0c 100644 --- a/testmatrix/HTMLScoreboard.py +++ b/testmatrix/HTMLScoreboard.py @@ -10,6 +10,7 @@ import os import time import json +import html from pathlib import Path from io import StringIO from contextlib import contextmanager @@ -114,8 +115,9 @@ def begin(self): attrs = self._attrs.copy() if self._classes: attrs['class'] = ' '.join(self._classes) - if attrs: - rv += ' ' + ' '.join([f'{k}="{v}"' for k, v in attrs.items()]) + for k, v in attrs.items(): + v = html.escape(str(v)) + rv += f' {k}="{html.escape(v)}"' return rv + '>' def end(self): @@ -124,7 +126,8 @@ def end(self): def __str__(self): rv = self.begin() if self.text is not None: - rv += str(self.text) + self.end() + text = str(self.text) if type(self.text) is Tag else html.escape(str(self.text)) + rv += text + self.end() return rv diff --git a/testmatrix/matrix.js b/testmatrix/matrix.js index 07cc6930b..c13e86b10 100644 --- a/testmatrix/matrix.js +++ b/testmatrix/matrix.js @@ -2,7 +2,6 @@ window.addEventListener('load', (event) => { document.querySelectorAll('.test_results td').forEach((td) => { var buildn = td.getAttribute('build'); var build = build_info[buildn]; - console.log(build); if (build) { // Put build name in tooltip td.setAttribute('title', `#${buildn} ${build.name}`); @@ -18,7 +17,6 @@ window.addEventListener('load', (event) => { url += 'Full'; } a.href = `${url}.html#subsection_${test_subsec}`; - // a.textContent = td.textContent; td.replaceChildren(a); } } diff --git a/testmatrix/matrix.py b/testmatrix/matrix.py index d025914f5..e06c9c562 100644 --- a/testmatrix/matrix.py +++ b/testmatrix/matrix.py @@ -135,9 +135,6 @@ def __init__(self, name: str, result: int, time: timedelta, misc=None): if misc is None: misc = {} self.name = name - extra_name = misc.get('extra_name', None) - if extra_name: - self.name += ' ' + misc['extra_name'] self.subsection = misc['subsection'] self.result = result assert type(time) is timedelta diff --git a/tests/scoreboard/MultiRun/run_test.pl b/tests/scoreboard/MultiRun/run_test.pl index f3b9cfe2a..363cb5ef5 100755 --- a/tests/scoreboard/MultiRun/run_test.pl +++ b/tests/scoreboard/MultiRun/run_test.pl @@ -5,7 +5,11 @@ use strict; use warnings; +use File::Path qw(make_path remove_tree); use FindBin; + +use File::Copy::Recursive qw(dircopy fcopy); + use constant autobuild_root => "$FindBin::Bin/../../../"; $ENV{'PATH'} = "$ENV{'PATH'}:" . autobuild_root; use lib autobuild_root; @@ -14,38 +18,30 @@ use common::utility; use common::test_utils; -use File::Path qw(make_path remove_tree); - my $runs = "runs"; my $run = "$runs/run"; my $run1 = "$runs/run1"; my $run2 = "$runs/run2"; sub run_cmd { - my $cmd = shift; - print "$cmd\n"; - return system($cmd); -} - -sub copy_to ($;$) { - my $des_dir = shift; - my $src_dir = shift // $run; - run_cmd("cp -R $src_dir $des_dir"); + my $cmd = shift(); + print("RUN: $cmd\n"); + die() if (!utility::run_command($cmd)); } sub run_scoreboard { - run_cmd("scoreboard.pl -c -f ./test.xml -o test.html -d $run"); + run_cmd("scoreboard.pl -c -f ./runs/test.xml -o test.html -d $run"); } sub compare_runs { - my $r = 0; - my $index = "build1/index.html"; - my $total = "build3/2021_03_09_17_33_Totals.html"; - $r += compare_files("$run1/$index", "$run2/$index"); - $r += compare_files("$run1/$index", "$run/$index"); - $r += compare_files("$run1/$total", "$run2/$total"); - $r += compare_files("$run1/$total", "$run/$total"); - return $r; + my $r = 0; + my $index = "build1/index.html"; + my $total = "build3/2021_03_09_17_33_Totals.html"; + $r += compare_files("$run1/$index", "$run2/$index"); + $r += compare_files("$run1/$index", "$run/$index"); + $r += compare_files("$run1/$total", "$run2/$total"); + $r += compare_files("$run1/$total", "$run/$total"); + return $r; } remove_tree($runs); @@ -55,21 +51,22 @@ sub compare_runs { } make_path($runs); -copy_to($run, "../build_logs"); +fcopy("../build_logs/test.xml", $runs); +dircopy("../build_logs", $run); run_scoreboard(); -copy_to($run1); +dircopy($run, $run1); run_scoreboard(); -copy_to($run2); +dircopy($run, $run2); run_scoreboard(); -my $exit_status = compare_runs(); +my $exit_status = compare_runs(); if ($exit_status) { - print("Test Failed\n"); + print("Test Failed\n"); } else { - print("Test Passed\n"); + print("Test Passed\n"); } exit ($exit_status); diff --git a/tests/scoreboard/MultiRun/test.xml b/tests/scoreboard/build_logs/test.xml similarity index 55% rename from tests/scoreboard/MultiRun/test.xml rename to tests/scoreboard/build_logs/test.xml index 5f5ce0591..ea51c3ad0 100644 --- a/tests/scoreboard/MultiRun/test.xml +++ b/tests/scoreboard/build_logs/test.xml @@ -1,4 +1,8 @@ + + + + Group 1 diff --git a/tests/scoreboard/matrix/.gitignore b/tests/scoreboard/matrix/.gitignore new file mode 100644 index 000000000..64ad435a4 --- /dev/null +++ b/tests/scoreboard/matrix/.gitignore @@ -0,0 +1 @@ +/builds diff --git a/tests/scoreboard/matrix/run_test.pl b/tests/scoreboard/matrix/run_test.pl new file mode 100755 index 000000000..981bfbe8b --- /dev/null +++ b/tests/scoreboard/matrix/run_test.pl @@ -0,0 +1,42 @@ +eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' + & eval 'exec perl -S $0 $argv:q' + if 0; + +use strict; +use warnings; + +use File::Path qw(make_path remove_tree); +use FindBin; + +use File::Copy::Recursive qw(dircopy); + +use constant scoreboard_tests => "$FindBin::Bin/../"; +use constant build_logs => scoreboard_tests . "build_logs/"; +use constant autobuild_root => scoreboard_tests . "../../"; +$ENV{'PATH'} = "$ENV{'PATH'}:" . autobuild_root; +use lib autobuild_root; +chdir ($FindBin::Bin); + +use common::utility; +use common::test_utils; + +sub run_cmd { + my $cmd = shift(); + print("RUN: $cmd\n"); + die() if (!utility::run_command($cmd)); +} + +sub run_scoreboard { + my $dest = shift() // 'builds'; + my $src = shift() // build_logs; + + my $name = 'test'; + my $xml = "$src/$name.xml"; + my $html = "$name.html"; + remove_tree($dest); + dircopy($src, $dest); + run_cmd("scoreboard.pl -c -f $xml -o $html -d $dest"); +} + +run_scoreboard(); +run_cmd("matrix.py builds");