From 1556458dcda37f2cd37ac501aab0657add73ddae Mon Sep 17 00:00:00 2001 From: Matti Kortelainen Date: Tue, 9 Jul 2024 23:37:38 +0200 Subject: [PATCH 01/10] Replace atol with int --- FWCore/Utilities/scripts/edmCheckClassVersion | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/FWCore/Utilities/scripts/edmCheckClassVersion b/FWCore/Utilities/scripts/edmCheckClassVersion index 8737d180d203c..c00c3a951e582 100755 --- a/FWCore/Utilities/scripts/edmCheckClassVersion +++ b/FWCore/Utilities/scripts/edmCheckClassVersion @@ -1,9 +1,4 @@ #! /usr/bin/env python3 -from sys import version_info -if version_info[0] > 2: - atol = int -else: - from string import atol class XmlParser(object): """Parses a classes_def.xml file looking for class declarations that contain @@ -49,11 +44,11 @@ class XmlParser(object): if 'name' in attrs: if 'ClassVersion' in attrs: normalizedName = self.genNName(attrs['name']) - self.classes[normalizedName]=[attrs['name'],atol(attrs['ClassVersion']),[]] + self.classes[normalizedName]=[attrs['name'],int(attrs['ClassVersion']),[]] self._presentClass=normalizedName if name == 'version': - self.classes[self._presentClass][XmlParser.versionsToChecksumIndex].append([atol(attrs['ClassVersion']), - atol(attrs['checksum'])]) + self.classes[self._presentClass][XmlParser.versionsToChecksumIndex].append([int(attrs['ClassVersion']), + int(attrs['checksum'])]) pass def end_element(self,name): if name in ('class','struct'): From e35c790ef0fcc5ede08cf94da8fcffbcc0d43dee Mon Sep 17 00:00:00 2001 From: Matti Kortelainen Date: Wed, 10 Jul 2024 16:02:39 +0200 Subject: [PATCH 02/10] Add edmDumpClassVersion --- FWCore/Utilities/python/ClassesDefXmlUtils.py | 118 ++++++++++++++++++ FWCore/Utilities/scripts/edmCheckClassVersion | 116 ++--------------- FWCore/Utilities/scripts/edmDumpClassVersion | 34 +++++ 3 files changed, 160 insertions(+), 108 deletions(-) create mode 100644 FWCore/Utilities/python/ClassesDefXmlUtils.py create mode 100755 FWCore/Utilities/scripts/edmDumpClassVersion diff --git a/FWCore/Utilities/python/ClassesDefXmlUtils.py b/FWCore/Utilities/python/ClassesDefXmlUtils.py new file mode 100644 index 0000000000000..9c9ac1675f2da --- /dev/null +++ b/FWCore/Utilities/python/ClassesDefXmlUtils.py @@ -0,0 +1,118 @@ +class XmlParser(object): + """Parses a classes_def.xml file looking for class declarations that contain + ClassVersion attributes. Once found looks for sub-elements named 'version' + which contain the ClassVersion to checksum mappings. + """ + + #The following are constants used to describe what data is kept + # in which index in the 'classes' member data + originalNameIndex=0 + classVersionIndex=1 + versionsToChecksumIndex = 2 + + def __init__(self, filename, includeNonVersionedClasses=False, normalizeClassNames=True): + self._file = filename + self.classes = dict() + self._presentClass = None + self._includeNonVersionedClasses = includeNonVersionedClasses + self._normalizeClassNames = normalizeClassNames + self.readClassesDefXML() + def readClassesDefXML(self): + import xml.parsers.expat + p = xml.parsers.expat.ParserCreate() + p.StartElementHandler = self.start_element + p.EndElementHandler = self.end_element + f = open(self._file) + # Replace any occurence of <>& in the attribute values by the xml parameter + rxml, nxml = f.read(), '' + q1,q2 = 0,0 + for c in rxml : + if (q1 or q2) and c == '<' : nxml += '<' + elif (q1 or q2) and c == '>' : nxml += '>' + # elif (q1 or q2) and c == '&' : nxml += '&' + else : nxml += c + if c == '"' : q1 = not q1 + if c == "'" : q2 = not q2 + try : p.Parse(nxml) + except xml.parsers.expat.ExpatError as e : + print ('--->> edmCheckClassVersion: ERROR: parsing selection file ',self._file) + print ('--->> edmCheckClassVersion: ERROR: Error is:', e) + raise + f.close() + def start_element(self,name,attrs): + if name in ('class','struct'): + if 'name' in attrs: + if 'ClassVersion' in attrs: + normalizedName = self.genNName(attrs['name']) + self.classes[normalizedName]=[attrs['name'],int(attrs['ClassVersion']),[]] + self._presentClass=normalizedName + elif self._includeNonVersionedClasses: + # skip transient data products + if not ('persistent' in attrs and attrs['persistent'] == "false"): + normalizedName = self.genNName(attrs['name']) + self.classes[normalizedName]=[attrs['name'],-1,[]] + if name == 'version': + self.classes[self._presentClass][XmlParser.versionsToChecksumIndex].append([int(attrs['ClassVersion']), + int(attrs['checksum'])]) + pass + def end_element(self,name): + if name in ('class','struct'): + self._presentClass = None + def genNName(self, name ): + if not self._normalizeClassNames: + return name + n_name = " ".join(name.split()) + for e in [ ['long long unsigned int', 'unsigned long long'], + ['long long int', 'long long'], + ['unsigned short int', 'unsigned short'], + ['short unsigned int', 'unsigned short'], + ['short int', 'short'], + ['long unsigned int', 'unsigned long'], + ['unsigned long int', 'unsigned long'], + ['long int', 'long'], + ['std::string', 'std::basic_string']] : + n_name = n_name.replace(e[0],e[1]) + n_name = n_name.replace(' ','') + return n_name + +def initCheckClass(): + """Must be called before checkClass()""" + import ROOT + ROOT.gROOT.ProcessLine("class checkclass {public: int f(char const* name) {TClass* cl = TClass::GetClass(name); bool b = false; cl->GetCheckSum(b); return (int)b;} };") + ROOT.gROOT.ProcessLine("checkclass checkTheClass;") + + +#The following are error codes returned from checkClass +noError = 0 +errorRootDoesNotMatchClassDef =1 +errorMustUpdateClassVersion=2 +errorMustAddChecksum=3 + +def checkClass(name,version,versionsToChecksums): + import ROOT + c = ROOT.TClass.GetClass(name) + if not c: + raise RuntimeError("failed to load dictionary for class '"+name+"'") + temp = "checkTheClass.f(" + '"' + name + '"' + ");" + retval = ROOT.gROOT.ProcessLine(temp) + if retval == 0 : + raise RuntimeError("TClass::GetCheckSum: Failed to load dictionary for base class. See previous Error message") + classChecksum = c.GetCheckSum() + classVersion = c.GetClassVersion() + + #does this version match what is in the file? + if version != classVersion: + return (errorRootDoesNotMatchClassDef,classVersion,classChecksum) + + #is the version already in our list? + found = False + + for v,cs in versionsToChecksums: + if v == version: + found = True + if classChecksum != cs: + return (errorMustUpdateClassVersion,classVersion,classChecksum) + break + if not found and classVersion != 0: + return (errorMustAddChecksum,classVersion,classChecksum) + return (noError,classVersion,classChecksum) diff --git a/FWCore/Utilities/scripts/edmCheckClassVersion b/FWCore/Utilities/scripts/edmCheckClassVersion index c00c3a951e582..2bfa28737c0b0 100755 --- a/FWCore/Utilities/scripts/edmCheckClassVersion +++ b/FWCore/Utilities/scripts/edmCheckClassVersion @@ -1,72 +1,7 @@ #! /usr/bin/env python3 -class XmlParser(object): - """Parses a classes_def.xml file looking for class declarations that contain - ClassVersion attributes. Once found looks for sub-elements named 'version' - which contain the ClassVersion to checksum mappings. - """ - - #The following are constants used to describe what data is kept - # in which index in the 'classes' member data - originalNameIndex=0 - classVersionIndex=1 - versionsToChecksumIndex = 2 - - def __init__(self,filename): - self._file = filename - self.classes = dict() - self._presentClass = None - self.readClassesDefXML() - def readClassesDefXML(self): - import xml.parsers.expat - p = xml.parsers.expat.ParserCreate() - p.StartElementHandler = self.start_element - p.EndElementHandler = self.end_element - f = open(self._file) - # Replace any occurence of <>& in the attribute values by the xml parameter - rxml, nxml = f.read(), '' - q1,q2 = 0,0 - for c in rxml : - if (q1 or q2) and c == '<' : nxml += '<' - elif (q1 or q2) and c == '>' : nxml += '>' - # elif (q1 or q2) and c == '&' : nxml += '&' - else : nxml += c - if c == '"' : q1 = not q1 - if c == "'" : q2 = not q2 - try : p.Parse(nxml) - except xml.parsers.expat.ExpatError as e : - print ('--->> edmCheckClassVersion: ERROR: parsing selection file ',self._file) - print ('--->> edmCheckClassVersion: ERROR: Error is:', e) - raise - f.close() - def start_element(self,name,attrs): - if name in ('class','struct'): - if 'name' in attrs: - if 'ClassVersion' in attrs: - normalizedName = self.genNName(attrs['name']) - self.classes[normalizedName]=[attrs['name'],int(attrs['ClassVersion']),[]] - self._presentClass=normalizedName - if name == 'version': - self.classes[self._presentClass][XmlParser.versionsToChecksumIndex].append([int(attrs['ClassVersion']), - int(attrs['checksum'])]) - pass - def end_element(self,name): - if name in ('class','struct'): - self._presentClass = None - def genNName(self, name ): - n_name = " ".join(name.split()) - for e in [ ['long long unsigned int', 'unsigned long long'], - ['long long int', 'long long'], - ['unsigned short int', 'unsigned short'], - ['short unsigned int', 'unsigned short'], - ['short int', 'short'], - ['long unsigned int', 'unsigned long'], - ['unsigned long int', 'unsigned long'], - ['long int', 'long'], - ['std::string', 'std::basic_string']] : - n_name = n_name.replace(e[0],e[1]) - n_name = n_name.replace(' ','') - return n_name +from FWCore.Utilities.ClassesDefXmlUtils import XmlParser +import FWCore.Utilities.ClassesDefXmlUtils as ClassesDefUtils # recursively check the base classes for a class pointer # as building the streamer will crash if base classes are @@ -111,40 +46,6 @@ def checkDictionaries(name): return missingDict -#The following are error codes returned from checkClass -noError = 0 -errorRootDoesNotMatchClassDef =1 -errorMustUpdateClassVersion=2 -errorMustAddChecksum=3 - -def checkClass(name,version,versionsToChecksums): - c = ROOT.TClass.GetClass(name) - if not c: - raise RuntimeError("failed to load dictionary for class '"+name+"'") - temp = "checkTheClass.f(" + '"' + name + '"' + ");" - retval = ROOT.gROOT.ProcessLine(temp) - if retval == 0 : - raise RuntimeError("TClass::GetCheckSum: Failed to load dictionary for base class. See previous Error message") - classChecksum = c.GetCheckSum() - classVersion = c.GetClassVersion() - - #does this version match what is in the file? - if version != classVersion: - return (errorRootDoesNotMatchClassDef,classVersion,classChecksum) - - #is the version already in our list? - found = False - - for v,cs in versionsToChecksums: - if v == version: - found = True - if classChecksum != cs: - return (errorMustUpdateClassVersion,classVersion,classChecksum) - break - if not found and classVersion != 0: - return (errorMustAddChecksum,classVersion,classChecksum) - return (noError,classVersion,classChecksum) - #Setup the options from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter oparser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) @@ -175,14 +76,13 @@ else: missingDict = 0 -ROOT.gROOT.ProcessLine("class checkclass {public: int f(char const* name) {TClass* cl = TClass::GetClass(name); bool b = false; cl->GetCheckSum(b); return (int)b;} };") -ROOT.gROOT.ProcessLine("checkclass checkTheClass;") +ClassesDefUtils.initCheckClass() p = XmlParser(options.xmlfile) foundErrors = dict() for name,info in p.classes.items(): - errorCode,rootClassVersion,classChecksum = checkClass(name,info[XmlParser.classVersionIndex],info[XmlParser.versionsToChecksumIndex]) - if errorCode != noError: + errorCode,rootClassVersion,classChecksum = ClassesDefUtils.checkClass(name,info[XmlParser.classVersionIndex],info[XmlParser.versionsToChecksumIndex]) + if errorCode != ClassesDefUtils.noError: foundErrors[name]=(errorCode,classChecksum,rootClassVersion) if options.checkdict : missingDict += checkDictionaries(name) @@ -196,10 +96,10 @@ for name,retValues in foundErrors.items(): classVersion = p.classes[name][XmlParser.classVersionIndex] classChecksum = retValues[1] rootClassVersion = retValues[2] - if code == errorRootDoesNotMatchClassDef: + if code == ClassesDefUtils.errorRootDoesNotMatchClassDef: foundRootDoesNotMatchError=True print ("error: for class '"+name+"' ROOT says the ClassVersion is "+str(rootClassVersion)+" but classes_def.xml says it is "+str(classVersion)+". Are you sure everything compiled correctly?") - elif code == errorMustUpdateClassVersion and not options.generate: + elif code == ClassesDefUtils.errorMustUpdateClassVersion and not options.generate: print ("error: class '"+name+"' has a different checksum for ClassVersion "+str(classVersion)+". Increment ClassVersion to "+str(classVersion+1)+" and assign it to checksum "+str(classChecksum)) elif not options.generate: print ("error:class '"+name+"' needs to include the following as part of its 'class' declaration") @@ -222,7 +122,7 @@ if options.generate and not foundRootDoesNotMatchError and not missingDict: classVersion = p.classes[normName][XmlParser.classVersionIndex] code,checksum,rootClassVersion = foundErrors[normName] hasNoSubElements = (-1 != l.find('/>')) - if code == errorMustUpdateClassVersion: + if code == ClassesDefUtils.errorMustUpdateClassVersion: classVersion += 1 parts = splitArgs[:] indexToClassVersion = 0 diff --git a/FWCore/Utilities/scripts/edmDumpClassVersion b/FWCore/Utilities/scripts/edmDumpClassVersion new file mode 100755 index 0000000000000..4f94151dcf9e1 --- /dev/null +++ b/FWCore/Utilities/scripts/edmDumpClassVersion @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +import argparse + +import FWCore.Utilities.ClassesDefXmlUtils as ClassesDefUtils + +def main(args): + #Need to not have ROOT load .rootlogon.(C|py) since it can cause interference. + import ROOT + ROOT.PyConfig.DisableRootLogon = True + + #Keep ROOT from trying to use X11 + ROOT.gROOT.SetBatch(True) + ROOT.gROOT.ProcessLine(".autodict") + if args.library is not None: + if ROOT.gSystem.Load(args.library) < 0 : + raise RuntimeError("failed to load library '"+args.library+"'") + + ClassesDefUtils.initCheckClass() + p = ClassesDefUtils.XmlParser(args.xmlfile, includeNonVersionedClasses=True, normalizeClassNames=False) + for name, info in p.classes.items(): + (error, version, checksum) = ClassesDefUtils.checkClass(name, 0, {}) + print(name, version, checksum) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Prints class versions and checksums for all non-transient clases defined in a given classes_def.xml file") + parser.add_argument("-l","--lib", dest="library", type=str, + help="specify the library to load. If not set classes are found using the PluginManager") + parser.add_argument("-x","--xml_file", dest="xmlfile",default="./classes_def.xml", type=str, + help="the classes_def.xml file to read") + + args = parser.parse_args() + main(args) From 58b6a5276b1bb73891c122b6dc51dc2e3b6664ff Mon Sep 17 00:00:00 2001 From: Matti Kortelainen Date: Fri, 12 Jul 2024 16:55:45 +0200 Subject: [PATCH 03/10] Use XmlParser via ClassesDefUtils in edmCheckClassVersion --- FWCore/Utilities/scripts/edmCheckClassVersion | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/FWCore/Utilities/scripts/edmCheckClassVersion b/FWCore/Utilities/scripts/edmCheckClassVersion index 2bfa28737c0b0..a99c12614fa59 100755 --- a/FWCore/Utilities/scripts/edmCheckClassVersion +++ b/FWCore/Utilities/scripts/edmCheckClassVersion @@ -1,6 +1,5 @@ #! /usr/bin/env python3 -from FWCore.Utilities.ClassesDefXmlUtils import XmlParser import FWCore.Utilities.ClassesDefXmlUtils as ClassesDefUtils # recursively check the base classes for a class pointer @@ -78,10 +77,10 @@ missingDict = 0 ClassesDefUtils.initCheckClass() -p = XmlParser(options.xmlfile) +p = ClassesDefUtils.XmlParser(options.xmlfile) foundErrors = dict() for name,info in p.classes.items(): - errorCode,rootClassVersion,classChecksum = ClassesDefUtils.checkClass(name,info[XmlParser.classVersionIndex],info[XmlParser.versionsToChecksumIndex]) + errorCode,rootClassVersion,classChecksum = ClassesDefUtils.checkClass(name,info[ClassesDefUtils.XmlParser.classVersionIndex],info[ClassesDefUtils.XmlParser.versionsToChecksumIndex]) if errorCode != ClassesDefUtils.noError: foundErrors[name]=(errorCode,classChecksum,rootClassVersion) if options.checkdict : @@ -90,10 +89,10 @@ for name,info in p.classes.items(): foundRootDoesNotMatchError = False originalToNormalizedNames = dict() for name,retValues in foundErrors.items(): - origName = p.classes[name][XmlParser.originalNameIndex] + origName = p.classes[name][ClassesDefUtils.XmlParser.originalNameIndex] originalToNormalizedNames[origName]=name code = retValues[0] - classVersion = p.classes[name][XmlParser.classVersionIndex] + classVersion = p.classes[name][ClassesDefUtils.XmlParser.classVersionIndex] classChecksum = retValues[1] rootClassVersion = retValues[2] if code == ClassesDefUtils.errorRootDoesNotMatchClassDef: From 3b925b8fe6943627bda26c1081f4de31b0a8440f Mon Sep 17 00:00:00 2001 From: Matti Kortelainen Date: Fri, 12 Jul 2024 16:59:20 +0200 Subject: [PATCH 04/10] Abstract ROOT initialization --- FWCore/Utilities/python/ClassesDefXmlUtils.py | 12 ++++++++++++ FWCore/Utilities/scripts/edmCheckClassVersion | 16 +++------------- FWCore/Utilities/scripts/edmDumpClassVersion | 11 +---------- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/FWCore/Utilities/python/ClassesDefXmlUtils.py b/FWCore/Utilities/python/ClassesDefXmlUtils.py index 9c9ac1675f2da..41cb6382c1b43 100644 --- a/FWCore/Utilities/python/ClassesDefXmlUtils.py +++ b/FWCore/Utilities/python/ClassesDefXmlUtils.py @@ -75,6 +75,18 @@ def genNName(self, name ): n_name = n_name.replace(' ','') return n_name +def initROOT(library): + #Need to not have ROOT load .rootlogon.(C|py) since it can cause interference. + import ROOT + ROOT.PyConfig.DisableRootLogon = True + + #Keep ROOT from trying to use X11 + ROOT.gROOT.SetBatch(True) + ROOT.gROOT.ProcessLine(".autodict") + if library is not None: + if ROOT.gSystem.Load(library) < 0 : + raise RuntimeError("failed to load library '"+library+"'") + def initCheckClass(): """Must be called before checkClass()""" import ROOT diff --git a/FWCore/Utilities/scripts/edmCheckClassVersion b/FWCore/Utilities/scripts/edmCheckClassVersion index a99c12614fa59..a77248328e79d 100755 --- a/FWCore/Utilities/scripts/edmCheckClassVersion +++ b/FWCore/Utilities/scripts/edmCheckClassVersion @@ -59,19 +59,9 @@ oparser.add_argument("-g","--generate_new",dest="generate", action="store_true", options=oparser.parse_args() -#Need to not have ROOT load .rootlogon.(C|py) since it can cause interference. -import ROOT -ROOT.PyConfig.DisableRootLogon = True - -#Keep ROOT from trying to use X11 -ROOT.gROOT.SetBatch(True) -ROOT.gROOT.ProcessLine(".autodict") -if options.library is None: - if options.checkdict : - print ("Dictionary checks require a specific library") -else: - if ROOT.gSystem.Load(options.library) < 0 : - raise RuntimeError("failed to load library '"+options.library+"'") +ClassesDefUtils.initROOT(options.library) +if options.library is None and options.checkdict: + print ("Dictionary checks require a specific library") missingDict = 0 diff --git a/FWCore/Utilities/scripts/edmDumpClassVersion b/FWCore/Utilities/scripts/edmDumpClassVersion index 4f94151dcf9e1..5de5f796d95fa 100755 --- a/FWCore/Utilities/scripts/edmDumpClassVersion +++ b/FWCore/Utilities/scripts/edmDumpClassVersion @@ -5,16 +5,7 @@ import argparse import FWCore.Utilities.ClassesDefXmlUtils as ClassesDefUtils def main(args): - #Need to not have ROOT load .rootlogon.(C|py) since it can cause interference. - import ROOT - ROOT.PyConfig.DisableRootLogon = True - - #Keep ROOT from trying to use X11 - ROOT.gROOT.SetBatch(True) - ROOT.gROOT.ProcessLine(".autodict") - if args.library is not None: - if ROOT.gSystem.Load(args.library) < 0 : - raise RuntimeError("failed to load library '"+args.library+"'") + ClassesDefUtils.initROOT(args.library) ClassesDefUtils.initCheckClass() p = ClassesDefUtils.XmlParser(args.xmlfile, includeNonVersionedClasses=True, normalizeClassNames=False) From 39a102ae272b143b44ddaee78f2d9e23a616fd2f Mon Sep 17 00:00:00 2001 From: Matti Kortelainen Date: Fri, 12 Jul 2024 20:44:34 +0200 Subject: [PATCH 05/10] Sort the output --- FWCore/Utilities/scripts/edmDumpClassVersion | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/FWCore/Utilities/scripts/edmDumpClassVersion b/FWCore/Utilities/scripts/edmDumpClassVersion index 5de5f796d95fa..ea6094a38f95b 100755 --- a/FWCore/Utilities/scripts/edmDumpClassVersion +++ b/FWCore/Utilities/scripts/edmDumpClassVersion @@ -9,9 +9,13 @@ def main(args): ClassesDefUtils.initCheckClass() p = ClassesDefUtils.XmlParser(args.xmlfile, includeNonVersionedClasses=True, normalizeClassNames=False) + out = [] for name, info in p.classes.items(): (error, version, checksum) = ClassesDefUtils.checkClass(name, 0, {}) - print(name, version, checksum) + out.append(f"{name} {version} {checksum}") + out.sort() + for l in out: + print(l) if __name__ == "__main__": parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, From de1b07fb5c1c2c0da18cbf5591198a8979fc060d Mon Sep 17 00:00:00 2001 From: Matti Kortelainen Date: Thu, 18 Jul 2024 16:56:46 +0200 Subject: [PATCH 06/10] Output JSON, optionally to a file, and for now ignore errors from getting checksum --- FWCore/Utilities/scripts/edmDumpClassVersion | 27 +++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/FWCore/Utilities/scripts/edmDumpClassVersion b/FWCore/Utilities/scripts/edmDumpClassVersion index ea6094a38f95b..3efb399453d12 100755 --- a/FWCore/Utilities/scripts/edmDumpClassVersion +++ b/FWCore/Utilities/scripts/edmDumpClassVersion @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import json import argparse import FWCore.Utilities.ClassesDefXmlUtils as ClassesDefUtils @@ -9,21 +10,33 @@ def main(args): ClassesDefUtils.initCheckClass() p = ClassesDefUtils.XmlParser(args.xmlfile, includeNonVersionedClasses=True, normalizeClassNames=False) - out = [] + out = {} for name, info in p.classes.items(): - (error, version, checksum) = ClassesDefUtils.checkClass(name, 0, {}) - out.append(f"{name} {version} {checksum}") - out.sort() - for l in out: - print(l) + try: + (error, version, checksum) = ClassesDefUtils.checkClass(name, 0, {}) + except RuntimeError as e: + print(f"Ignoring class {name} as could not get its version and checksum, because: {e}") + continue + out[name] = dict( + version = version, + checksum = checksum + ) + out_js = json.dumps(out, sort_keys=True, indent=1) + if args.output is None: + print(out_js) + else: + with open(args.output, "w") as f: + f.write(out_js) if __name__ == "__main__": parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description="Prints class versions and checksums for all non-transient clases defined in a given classes_def.xml file") + description="Extracts class versions and checksums, in JSON format, for all non-transient clases defined in a given classes_def.xml file") parser.add_argument("-l","--lib", dest="library", type=str, help="specify the library to load. If not set classes are found using the PluginManager") parser.add_argument("-x","--xml_file", dest="xmlfile",default="./classes_def.xml", type=str, help="the classes_def.xml file to read") + parser.add_argument("-o", "--output", type=str, default=None, + help="File to save the output. If no file is specified, the JSON document is printed in stdout") args = parser.parse_args() main(args) From 1e910a3c3d8934b23eb88955940d4bf717ca99ed Mon Sep 17 00:00:00 2001 From: Matti Kortelainen Date: Thu, 18 Jul 2024 17:03:08 +0200 Subject: [PATCH 07/10] Move edm{Check,Dump}ClassVersion to FWCore/Reflection --- FWCore/{Utilities => Reflection}/python/ClassesDefXmlUtils.py | 0 FWCore/{Utilities => Reflection}/scripts/edmCheckClassVersion | 2 +- FWCore/{Utilities => Reflection}/scripts/edmDumpClassVersion | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename FWCore/{Utilities => Reflection}/python/ClassesDefXmlUtils.py (100%) rename FWCore/{Utilities => Reflection}/scripts/edmCheckClassVersion (98%) rename FWCore/{Utilities => Reflection}/scripts/edmDumpClassVersion (96%) diff --git a/FWCore/Utilities/python/ClassesDefXmlUtils.py b/FWCore/Reflection/python/ClassesDefXmlUtils.py similarity index 100% rename from FWCore/Utilities/python/ClassesDefXmlUtils.py rename to FWCore/Reflection/python/ClassesDefXmlUtils.py diff --git a/FWCore/Utilities/scripts/edmCheckClassVersion b/FWCore/Reflection/scripts/edmCheckClassVersion similarity index 98% rename from FWCore/Utilities/scripts/edmCheckClassVersion rename to FWCore/Reflection/scripts/edmCheckClassVersion index a77248328e79d..7d9221d497722 100755 --- a/FWCore/Utilities/scripts/edmCheckClassVersion +++ b/FWCore/Reflection/scripts/edmCheckClassVersion @@ -1,6 +1,6 @@ #! /usr/bin/env python3 -import FWCore.Utilities.ClassesDefXmlUtils as ClassesDefUtils +import FWCore.Reflection.ClassesDefXmlUtils as ClassesDefUtils # recursively check the base classes for a class pointer # as building the streamer will crash if base classes are diff --git a/FWCore/Utilities/scripts/edmDumpClassVersion b/FWCore/Reflection/scripts/edmDumpClassVersion similarity index 96% rename from FWCore/Utilities/scripts/edmDumpClassVersion rename to FWCore/Reflection/scripts/edmDumpClassVersion index 3efb399453d12..2b469e89f13d3 100755 --- a/FWCore/Utilities/scripts/edmDumpClassVersion +++ b/FWCore/Reflection/scripts/edmDumpClassVersion @@ -3,7 +3,7 @@ import json import argparse -import FWCore.Utilities.ClassesDefXmlUtils as ClassesDefUtils +import FWCore.Reflection.ClassesDefXmlUtils as ClassesDefUtils def main(args): ClassesDefUtils.initROOT(args.library) From 58f44d3851f7781fb60ddd25de1c31e6c2ffd6fe Mon Sep 17 00:00:00 2001 From: Matti Kortelainen Date: Thu, 18 Jul 2024 18:04:21 +0200 Subject: [PATCH 08/10] Add tests for edm{Check,Dump}ClassVersion Dump class version tests --- FWCore/Reflection/test/BuildFile.xml | 8 ++++++++ .../test/dumpClassVersion_reference.json | 10 ++++++++++ FWCore/Reflection/test/run_checkClassVersion.sh | 8 ++++++++ FWCore/Reflection/test/run_dumpClassVersion.sh | 9 +++++++++ FWCore/Reflection/test/stubs/TestObjects.cc | 5 +++++ FWCore/Reflection/test/stubs/TestObjects.h | 17 +++++++++++++++++ FWCore/Reflection/test/stubs/classes.h | 2 ++ FWCore/Reflection/test/stubs/classes_def.xml | 6 ++++++ 8 files changed, 65 insertions(+) create mode 100644 FWCore/Reflection/test/BuildFile.xml create mode 100644 FWCore/Reflection/test/dumpClassVersion_reference.json create mode 100755 FWCore/Reflection/test/run_checkClassVersion.sh create mode 100755 FWCore/Reflection/test/run_dumpClassVersion.sh create mode 100644 FWCore/Reflection/test/stubs/TestObjects.cc create mode 100644 FWCore/Reflection/test/stubs/TestObjects.h create mode 100644 FWCore/Reflection/test/stubs/classes.h create mode 100644 FWCore/Reflection/test/stubs/classes_def.xml diff --git a/FWCore/Reflection/test/BuildFile.xml b/FWCore/Reflection/test/BuildFile.xml new file mode 100644 index 0000000000000..10e0d1ae71daf --- /dev/null +++ b/FWCore/Reflection/test/BuildFile.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/FWCore/Reflection/test/dumpClassVersion_reference.json b/FWCore/Reflection/test/dumpClassVersion_reference.json new file mode 100644 index 0000000000000..2032fd1a08d78 --- /dev/null +++ b/FWCore/Reflection/test/dumpClassVersion_reference.json @@ -0,0 +1,10 @@ +{ + "edm::Wrapper": { + "checksum": 536952283, + "version": 4 + }, + "edmtest::reflection::IntObject": { + "checksum": 427917710, + "version": 3 + } +} \ No newline at end of file diff --git a/FWCore/Reflection/test/run_checkClassVersion.sh b/FWCore/Reflection/test/run_checkClassVersion.sh new file mode 100755 index 0000000000000..02917982f3208 --- /dev/null +++ b/FWCore/Reflection/test/run_checkClassVersion.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +function die { echo Failure $1: status $2 ; exit $2 ; } + +XMLPATH=${SCRAM_TEST_PATH}/stubs/ +LIBFILE=${LOCALTOP}/lib/${SCRAM_ARCH}/libFWCoreReflectionTestObjects.so + +edmCheckClassVersion -l ${LIBFILE} -x ${XMLPATH}/classes_def.xml || die "edmCheckClassVersion failed" $? diff --git a/FWCore/Reflection/test/run_dumpClassVersion.sh b/FWCore/Reflection/test/run_dumpClassVersion.sh new file mode 100755 index 0000000000000..6b00ad2ed46f2 --- /dev/null +++ b/FWCore/Reflection/test/run_dumpClassVersion.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +function die { echo Failure $1: status $2 ; exit $2 ; } + +XMLPATH=${SCRAM_TEST_PATH}/stubs/ +LIBFILE=${LOCALTOP}/lib/${SCRAM_ARCH}/libFWCoreReflectionTestObjects.so + +edmDumpClassVersion -l ${LIBFILE} -x ${XMLPATH}/classes_def.xml -o dump.json || die "edmDumpClassVersion failed" $? +diff -u ${SCRAM_TEST_PATH}/dumpClassVersion_reference.json dump.json || die "Unexpected class version dump" $? diff --git a/FWCore/Reflection/test/stubs/TestObjects.cc b/FWCore/Reflection/test/stubs/TestObjects.cc new file mode 100644 index 0000000000000..b5a6333f7d510 --- /dev/null +++ b/FWCore/Reflection/test/stubs/TestObjects.cc @@ -0,0 +1,5 @@ +#include "TestObjects.h" + +namespace edmtest::reflection { + IntObject::IntObject() = default; +} diff --git a/FWCore/Reflection/test/stubs/TestObjects.h b/FWCore/Reflection/test/stubs/TestObjects.h new file mode 100644 index 0000000000000..0ebbb9ec79fb4 --- /dev/null +++ b/FWCore/Reflection/test/stubs/TestObjects.h @@ -0,0 +1,17 @@ +#ifndef FWCore_Reflection_test_TestObjects_h +#define FWCore_Reflection_test_TestObjects_h + +namespace edmtest::reflection { + class IntObject { + public: + IntObject(); + IntObject(int v) : value_(v) {} + + int get() const { return value_; } + + private: + int value_ = 0; + }; +} // namespace edmtest::reflection + +#endif diff --git a/FWCore/Reflection/test/stubs/classes.h b/FWCore/Reflection/test/stubs/classes.h new file mode 100644 index 0000000000000..975b0eaca2005 --- /dev/null +++ b/FWCore/Reflection/test/stubs/classes.h @@ -0,0 +1,2 @@ +#include "DataFormats/Common/interface/Wrapper.h" +#include "FWCore/Reflection/test/stubs/TestObjects.h" diff --git a/FWCore/Reflection/test/stubs/classes_def.xml b/FWCore/Reflection/test/stubs/classes_def.xml new file mode 100644 index 0000000000000..c8a2b6538439b --- /dev/null +++ b/FWCore/Reflection/test/stubs/classes_def.xml @@ -0,0 +1,6 @@ + + + + + + From cfe793cebcb7ab567aee4eeeff09a6e08538a551 Mon Sep 17 00:00:00 2001 From: Matti Kortelainen Date: Thu, 18 Jul 2024 18:27:52 +0200 Subject: [PATCH 09/10] Improve error messages for some cases where an expected XML attribute is missing --- .../Reflection/python/ClassesDefXmlUtils.py | 23 +++++++++++++++---- .../Reflection/scripts/edmCheckClassVersion | 7 +++++- FWCore/Reflection/scripts/edmDumpClassVersion | 11 +++++++-- .../Reflection/test/run_checkClassVersion.sh | 18 ++++++++++++++- .../Reflection/test/run_dumpClassVersion.sh | 18 ++++++++++++++- .../test_def_ClassVersionMissingInClass.xml | 6 +++++ .../test_def_ClassVersionMissingInVersion.xml | 6 +++++ .../test_def_checksumMissingInVersion.xml | 6 +++++ .../test/stubs/test_def_nameMissing.xml | 6 +++++ 9 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 FWCore/Reflection/test/stubs/test_def_ClassVersionMissingInClass.xml create mode 100644 FWCore/Reflection/test/stubs/test_def_ClassVersionMissingInVersion.xml create mode 100644 FWCore/Reflection/test/stubs/test_def_checksumMissingInVersion.xml create mode 100644 FWCore/Reflection/test/stubs/test_def_nameMissing.xml diff --git a/FWCore/Reflection/python/ClassesDefXmlUtils.py b/FWCore/Reflection/python/ClassesDefXmlUtils.py index 41cb6382c1b43..5cbdf71c035c9 100644 --- a/FWCore/Reflection/python/ClassesDefXmlUtils.py +++ b/FWCore/Reflection/python/ClassesDefXmlUtils.py @@ -14,6 +14,7 @@ def __init__(self, filename, includeNonVersionedClasses=False, normalizeClassNam self._file = filename self.classes = dict() self._presentClass = None + self._presentClassForVersion = None self._includeNonVersionedClasses = includeNonVersionedClasses self._normalizeClassNames = normalizeClassNames self.readClassesDefXML() @@ -42,22 +43,34 @@ def readClassesDefXML(self): def start_element(self,name,attrs): if name in ('class','struct'): if 'name' in attrs: + self._presentClass=attrs['name'] + normalizedName = self.genNName(attrs['name']) if 'ClassVersion' in attrs: - normalizedName = self.genNName(attrs['name']) self.classes[normalizedName]=[attrs['name'],int(attrs['ClassVersion']),[]] - self._presentClass=normalizedName + self._presentClassForVersion=normalizedName elif self._includeNonVersionedClasses: # skip transient data products if not ('persistent' in attrs and attrs['persistent'] == "false"): - normalizedName = self.genNName(attrs['name']) self.classes[normalizedName]=[attrs['name'],-1,[]] + else: + raise RuntimeError(f"There is an element '{name}' without 'name' attribute.") if name == 'version': - self.classes[self._presentClass][XmlParser.versionsToChecksumIndex].append([int(attrs['ClassVersion']), - int(attrs['checksum'])]) + if self._presentClassForVersion is None: + raise RuntimeError(f"Class element for type '{self._presentClass}' contains a 'version' element, but 'ClassVersion' attribute is missing from the 'class' element") + try: + classVersion = int(attrs['ClassVersion']) + except KeyError: + raise RuntimeError(f"Version element for type '{self._presentClass}' is missing 'ClassVersion' attribute") + try: + checksum = int(attrs['checksum']) + except KeyError: + raise RuntimeError(f"Version element for type '{self._presentClass}' is missing 'checksum' attribute") + self.classes[self._presentClassForVersion][XmlParser.versionsToChecksumIndex].append([classVersion, checksum]) pass def end_element(self,name): if name in ('class','struct'): self._presentClass = None + self._presentClassForVersion = None def genNName(self, name ): if not self._normalizeClassNames: return name diff --git a/FWCore/Reflection/scripts/edmCheckClassVersion b/FWCore/Reflection/scripts/edmCheckClassVersion index 7d9221d497722..88fbcb58ba1f1 100755 --- a/FWCore/Reflection/scripts/edmCheckClassVersion +++ b/FWCore/Reflection/scripts/edmCheckClassVersion @@ -1,5 +1,6 @@ #! /usr/bin/env python3 +import sys import FWCore.Reflection.ClassesDefXmlUtils as ClassesDefUtils # recursively check the base classes for a class pointer @@ -67,7 +68,11 @@ missingDict = 0 ClassesDefUtils.initCheckClass() -p = ClassesDefUtils.XmlParser(options.xmlfile) +try: + p = ClassesDefUtils.XmlParser(options.xmlfile) +except RuntimeError as e: + print(f"Parsing {options.xmlfile} failed: {e}") + sys.exit(1) foundErrors = dict() for name,info in p.classes.items(): errorCode,rootClassVersion,classChecksum = ClassesDefUtils.checkClass(name,info[ClassesDefUtils.XmlParser.classVersionIndex],info[ClassesDefUtils.XmlParser.versionsToChecksumIndex]) diff --git a/FWCore/Reflection/scripts/edmDumpClassVersion b/FWCore/Reflection/scripts/edmDumpClassVersion index 2b469e89f13d3..a92011a342312 100755 --- a/FWCore/Reflection/scripts/edmDumpClassVersion +++ b/FWCore/Reflection/scripts/edmDumpClassVersion @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import sys import json import argparse @@ -9,7 +10,12 @@ def main(args): ClassesDefUtils.initROOT(args.library) ClassesDefUtils.initCheckClass() - p = ClassesDefUtils.XmlParser(args.xmlfile, includeNonVersionedClasses=True, normalizeClassNames=False) + try: + p = ClassesDefUtils.XmlParser(args.xmlfile, includeNonVersionedClasses=True, normalizeClassNames=False) + except RuntimeError as e: + print(f"Parsing {args.xmlfile} failed: {e}") + sys.exit(1) + out = {} for name, info in p.classes.items(): try: @@ -27,6 +33,7 @@ def main(args): else: with open(args.output, "w") as f: f.write(out_js) + return 0 if __name__ == "__main__": parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, @@ -39,4 +46,4 @@ if __name__ == "__main__": help="File to save the output. If no file is specified, the JSON document is printed in stdout") args = parser.parse_args() - main(args) + sys.exit(main(args)) diff --git a/FWCore/Reflection/test/run_checkClassVersion.sh b/FWCore/Reflection/test/run_checkClassVersion.sh index 02917982f3208..43bd19707b8e9 100755 --- a/FWCore/Reflection/test/run_checkClassVersion.sh +++ b/FWCore/Reflection/test/run_checkClassVersion.sh @@ -2,7 +2,23 @@ function die { echo Failure $1: status $2 ; exit $2 ; } -XMLPATH=${SCRAM_TEST_PATH}/stubs/ +XMLPATH=${SCRAM_TEST_PATH}/stubs LIBFILE=${LOCALTOP}/lib/${SCRAM_ARCH}/libFWCoreReflectionTestObjects.so edmCheckClassVersion -l ${LIBFILE} -x ${XMLPATH}/classes_def.xml || die "edmCheckClassVersion failed" $? + +function runFailure { + edmCheckClassVersion -l ${LIBFILE} -x ${XMLPATH}/$1 > log.txt && die "edmCheckClassVersion for $1 did not fail" 1 + grep -q "$2" log.txt + RET=$? + if [ "$RET" != "0" ]; then + echo "edmCheckClassVersion for $1 did not contain '$2', log is below" + cat log.txt + exit 1 + fi +} + +runFailure test_def_nameMissing.xml "There is an element 'class' without 'name' attribute" +runFailure test_def_ClassVersionMissingInClass.xml "Class element for type 'edmtest::reflection::IntObject' contains a 'version' element, but 'ClassVersion' attribute is missing from the 'class' element" +runFailure test_def_ClassVersionMissingInVersion.xml "Version element for type 'edmtest::reflection::IntObject' is missing 'ClassVersion' attribute" +runFailure test_def_checksumMissingInVersion.xml "Version element for type 'edmtest::reflection::IntObject' is missing 'checksum' attribute" diff --git a/FWCore/Reflection/test/run_dumpClassVersion.sh b/FWCore/Reflection/test/run_dumpClassVersion.sh index 6b00ad2ed46f2..824050547577b 100755 --- a/FWCore/Reflection/test/run_dumpClassVersion.sh +++ b/FWCore/Reflection/test/run_dumpClassVersion.sh @@ -2,8 +2,24 @@ function die { echo Failure $1: status $2 ; exit $2 ; } -XMLPATH=${SCRAM_TEST_PATH}/stubs/ +XMLPATH=${SCRAM_TEST_PATH}/stubs LIBFILE=${LOCALTOP}/lib/${SCRAM_ARCH}/libFWCoreReflectionTestObjects.so edmDumpClassVersion -l ${LIBFILE} -x ${XMLPATH}/classes_def.xml -o dump.json || die "edmDumpClassVersion failed" $? diff -u ${SCRAM_TEST_PATH}/dumpClassVersion_reference.json dump.json || die "Unexpected class version dump" $? + +function runFailure() { + edmDumpClassVersion -l ${LIBFILE} -x ${XMLPATH}/$1 > log.txt && die "edmDumpClassVersion for $1 did not fail" 1 + grep -q "$2" log.txt + RET=$? + if [ "$RET" != "0" ]; then + echo "edmDumpClassVersion for $1 did not contain '$2', log is below" + cat log.txt + exit 1 + fi +} + +runFailure test_def_nameMissing.xml "There is an element 'class' without 'name' attribute" +runFailure test_def_ClassVersionMissingInClass.xml "Class element for type 'edmtest::reflection::IntObject' contains a 'version' element, but 'ClassVersion' attribute is missing from the 'class' element" +runFailure test_def_ClassVersionMissingInVersion.xml "Version element for type 'edmtest::reflection::IntObject' is missing 'ClassVersion' attribute" +runFailure test_def_checksumMissingInVersion.xml "Version element for type 'edmtest::reflection::IntObject' is missing 'checksum' attribute" diff --git a/FWCore/Reflection/test/stubs/test_def_ClassVersionMissingInClass.xml b/FWCore/Reflection/test/stubs/test_def_ClassVersionMissingInClass.xml new file mode 100644 index 0000000000000..208aceb2d4399 --- /dev/null +++ b/FWCore/Reflection/test/stubs/test_def_ClassVersionMissingInClass.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/FWCore/Reflection/test/stubs/test_def_ClassVersionMissingInVersion.xml b/FWCore/Reflection/test/stubs/test_def_ClassVersionMissingInVersion.xml new file mode 100644 index 0000000000000..6024d072151da --- /dev/null +++ b/FWCore/Reflection/test/stubs/test_def_ClassVersionMissingInVersion.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/FWCore/Reflection/test/stubs/test_def_checksumMissingInVersion.xml b/FWCore/Reflection/test/stubs/test_def_checksumMissingInVersion.xml new file mode 100644 index 0000000000000..4d536eaf3b3a3 --- /dev/null +++ b/FWCore/Reflection/test/stubs/test_def_checksumMissingInVersion.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/FWCore/Reflection/test/stubs/test_def_nameMissing.xml b/FWCore/Reflection/test/stubs/test_def_nameMissing.xml new file mode 100644 index 0000000000000..3e1ba8ce07f77 --- /dev/null +++ b/FWCore/Reflection/test/stubs/test_def_nameMissing.xml @@ -0,0 +1,6 @@ + + + + + + From 444d6a3131b64c1ac1235cafecb0d0d6e289be90 Mon Sep 17 00:00:00 2001 From: Matti Kortelainen Date: Thu, 18 Jul 2024 21:44:01 +0200 Subject: [PATCH 10/10] Add checkDictionaryUpdate.py script and its tests --- .../scripts/checkDictionaryUpdate.py | 53 +++++++++++++++++++ Utilities/ReleaseScripts/test/BuildFile.xml | 2 + .../dumpClassVersion_baseline.json | 10 ++++ .../dumpClassVersion_newClass.json | 14 +++++ .../dumpClassVersion_removeClass.json | 6 +++ .../dumpClassVersion_versionUpdate.json | 10 ++++ .../test/run_checkDictionaryUpdate.sh | 41 ++++++++++++++ 7 files changed, 136 insertions(+) create mode 100755 Utilities/ReleaseScripts/scripts/checkDictionaryUpdate.py create mode 100644 Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_baseline.json create mode 100644 Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_newClass.json create mode 100644 Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_removeClass.json create mode 100644 Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_versionUpdate.json create mode 100755 Utilities/ReleaseScripts/test/run_checkDictionaryUpdate.sh diff --git a/Utilities/ReleaseScripts/scripts/checkDictionaryUpdate.py b/Utilities/ReleaseScripts/scripts/checkDictionaryUpdate.py new file mode 100755 index 0000000000000..c6539bbde6398 --- /dev/null +++ b/Utilities/ReleaseScripts/scripts/checkDictionaryUpdate.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +import sys +import json +import argparse + +# exit codes +NO_CHANGES = 0 +DATAFORMATS_CHANGED = 40 +POLICY_VIOLATION = 41 + +def policyChecks(document): + """Check policies on dictionary definitions. Return True if checks are fine.""" + # Contents to be added later + return True + +def updatePolicyChecks(reference, update): + """Check policies on dictionary updates. Return True if checks are fine.""" + # Contents to be added later + return True + +def main(args): + with open(args.baseline) as f: + baseline = json.load(f) + + if args.pr is not None: + with open(args.pr) as f: + pr = json.load(f) + pc1 = policyChecks(pr) + if baseline != pr: + pc2 = updatePolicyChecks(baseline, pr) + if not (pc1 and pc2): + return POLICY_VIOLATION + + print("Changes in persistable data formats") + return DATAFORMATS_CHANGED + if not pc1: + return POLICY_VIOLATION + else: + if not policyChecks(baseline): + return POLICY_VIOLATION + + return NO_CHANGES + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=f"Check dictionary policies of the JSON output of edmDumpClassVersion. If one JSON document is given (--baseline; e.g. in IBs), only the dictionary definition policy checks are done. If two JSON documents are given (--baseline and --pr; e.g. in PR tests), the dictionary definition policy checks are done on the --pr document, and, in addition, if any persistable data formats are changed, additional checks are done to ensure the dictionary update is done properly. Exits with {NO_CHANGES} if there are no changes to persistent data formats. Exits with {DATAFORMATS_CHANGED} if persistent data formats are changed, and the update complies with data format policies. Exits with {POLICY_VIOLATION} if some data format policy is violated. Other exit codes (e.g. 1, 2) denote some failure in the script itself.") + + parser.add_argument("--baseline", required=True, type=str, help="JSON file for baseline") + parser.add_argument("--pr", type=str, help="JSON file for baseline+PR") + parser.add_argument("--transientDataFormatPackage", action="store_true", help="The JSON files are for a package that can have only transient data formats") + + args = parser.parse_args() + sys.exit(main(args)) diff --git a/Utilities/ReleaseScripts/test/BuildFile.xml b/Utilities/ReleaseScripts/test/BuildFile.xml index 9c17b56d1c695..cb3a56d1959ad 100644 --- a/Utilities/ReleaseScripts/test/BuildFile.xml +++ b/Utilities/ReleaseScripts/test/BuildFile.xml @@ -11,3 +11,5 @@ + + diff --git a/Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_baseline.json b/Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_baseline.json new file mode 100644 index 0000000000000..2032fd1a08d78 --- /dev/null +++ b/Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_baseline.json @@ -0,0 +1,10 @@ +{ + "edm::Wrapper": { + "checksum": 536952283, + "version": 4 + }, + "edmtest::reflection::IntObject": { + "checksum": 427917710, + "version": 3 + } +} \ No newline at end of file diff --git a/Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_newClass.json b/Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_newClass.json new file mode 100644 index 0000000000000..0d370acd84448 --- /dev/null +++ b/Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_newClass.json @@ -0,0 +1,14 @@ +{ + "edm::Wrapper": { + "checksum": 536952283, + "version": 4 + }, + "edmtest::reflection::IntObject": { + "checksum": 427917710, + "version": 3 + }, + "edmtest::reflection::Another": { + "checksum": 427917711, + "version": 3 + } +} diff --git a/Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_removeClass.json b/Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_removeClass.json new file mode 100644 index 0000000000000..a3b92aec2d77e --- /dev/null +++ b/Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_removeClass.json @@ -0,0 +1,6 @@ +{ + "edmtest::reflection::IntObject": { + "checksum": 427917710, + "version": 3 + } +} diff --git a/Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_versionUpdate.json b/Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_versionUpdate.json new file mode 100644 index 0000000000000..3b7d96e50163a --- /dev/null +++ b/Utilities/ReleaseScripts/test/checkDictionaryUpdate/dumpClassVersion_versionUpdate.json @@ -0,0 +1,10 @@ +{ + "edm::Wrapper": { + "checksum": 536952283, + "version": 4 + }, + "edmtest::reflection::IntObject": { + "checksum": 3848242946, + "version": 4 + } +} diff --git a/Utilities/ReleaseScripts/test/run_checkDictionaryUpdate.sh b/Utilities/ReleaseScripts/test/run_checkDictionaryUpdate.sh new file mode 100755 index 0000000000000..151465dda1ed0 --- /dev/null +++ b/Utilities/ReleaseScripts/test/run_checkDictionaryUpdate.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# possible exit codes +SUCCESS=0 +FAILURE="fail" +DATAFORMATS_CHANGED=40 +POLICY_VIOLATION=41 + +function die { echo Failure $1: status $2 ; exit $2 ; } +function checkExitCode { + if [ "$1" == "$FAILURE" ]; then + if [[ "$2" = @("$SUCCESS"|"$DATAFORMATS_CHANGES"|"$POLICY_VIOLATIONS") ]]; then + echo "checkDictionaryUpdate.py $3: expected failure exit code, got $2" + exit 1 + fi + elif [ "$1" != "$2" ]; then + echo "checkDictionaryUpdate.py $3: expected exit code $1, got $2" + exit 1 + fi +} + +JSONPATH=${SCRAM_TEST_PATH}/checkDictionaryUpdate + +checkDictionaryUpdate.py --baseline ${JSONPATH}/dumpClassVersion_baseline.json || die "checkDictionaryUpdate.py baseline" $? +checkDictionaryUpdate.py --baseline ${JSONPATH}/dumpClassVersion_baseline.json --pr ${JSONPATH}/dumpClassVersion_baseline.json || die "checkDictionaryUpdate.py baseline baseline" $? + +checkDictionaryUpdate.py +RET=$? +checkExitCode ${FAILURE} $RET "" + +checkDictionaryUpdate.py --baseline ${JSONPATH}/dumpClassVersion_baseline.json --pr ${JSONPATH}/dumpClassVersion_versionUpdate.json +RET=$? +checkExitCode ${DATAFORMATS_CHANGED} $RET "baseline versionUpdate" + +checkDictionaryUpdate.py --baseline ${JSONPATH}/dumpClassVersion_baseline.json --pr ${JSONPATH}/dumpClassVersion_newClass.json +RET=$? +checkExitCode ${DATAFORMATS_CHANGED} $RET "baseline newClass" + +checkDictionaryUpdate.py --baseline ${JSONPATH}/dumpClassVersion_baseline.json --pr ${JSONPATH}/dumpClassVersion_removeClass.json +RET=$? +checkExitCode ${DATAFORMATS_CHANGED} $RET "baseline removeClass"