00001
00002 """
00003 Simple script for automatic validation of a patch.
00004
00005 Usage:
00006 validate_patch.py <savannah patch id>
00007 validate_patch.py file.patch
00008 """
00009
00010 __author__ = "Marco Clemencic <marco.clemencic@cern.ch>"
00011
00012 import os, sys
00013 import logging
00014 from shutil import rmtree
00015 from tempfile import mkdtemp
00016 from subprocess import Popen, PIPE
00017
00018 from HTMLParser import HTMLParser
00019 from urllib import urlopen
00020
00021 class PatchData(object):
00022 def __init__(self, id = None, title = None, files = None):
00023 self.id = None
00024 self.title = None
00025 if files is None:
00026 self.files = []
00027 def __repr__(self):
00028 r = self.__class__.__name__ + "("
00029 fields = []
00030 if self.id is not None:
00031 fields.append("id=%r" % self.id)
00032 if self.title is not None:
00033 fields.append("title=%r" % self.title)
00034 if self.files:
00035 fields.append("files=%r" % self.files)
00036 return "%s(%s)" % (self.__class__.__name__, ",".join(fields))
00037
00038
00039 class SavannahParser(HTMLParser):
00040 __attachments_id__ = "hidsubpartcontentattached"
00041 def __init__(self):
00042 HTMLParser.__init__(self)
00043
00044 self.patch = None
00045
00046
00047 self._parsingAttachments = 0
00048 self._currentFileId = None
00049 self._currentFileData = ""
00050
00051
00052
00053 def handle_starttag(self, tag, attrs):
00054 attrs = dict(attrs)
00055 if tag == "html":
00056
00057 self.patch = PatchData()
00058 elif tag == "span":
00059 if attrs.get("id", None) == self.__attachments_id__:
00060 self._parsingAttachments = 1
00061 elif self._parsingAttachments:
00062 self._parsingAttachments += 1
00063 elif (self._parsingAttachments
00064 and tag == "a"
00065 and "file_id=" in attrs.get("href", "")):
00066 self._currentFileId = attrs["href"].split("file_id=")[-1]
00067 def handle_endtag(self, tag):
00068 if tag == "span" and self._parsingAttachments:
00069 self._parsingAttachments -= 1
00070 elif tag == "a" and self._currentFileId:
00071
00072 filename = self._currentFileData.split(":")[-1].strip()
00073
00074 self.patch.files.append((filename, int(self._currentFileId)))
00075 self._currentFileId = None
00076 self._currentFileData = ""
00077
00078 def handle_data(self, data):
00079 if self._currentFileId:
00080 data = data.replace(" ", " ")
00081 self._currentFileData += data
00082
00083 def get_patch_info_x(patch):
00084 patch = int(patch)
00085 server = "savannah.cern.ch"
00086 path = "/patch/?%d" % patch
00087 conn = httplib.HTTPSConnection(server)
00088 conn.request("GET", path)
00089 r = conn.getresponse()
00090 if r.status == 200:
00091 pass
00092 else:
00093 raise RuntimeError(r.status, r.reason, "https://" + server + path)
00094 parser = SavannahParser()
00095 parser.feed(r.read())
00096 parser.close()
00097 conn.close()
00098 return parser.patch
00099
00100 def get_patch_info(patch):
00101 patch = int(patch)
00102 parser = SavannahParser()
00103 parser.feed(urlopen("https://savannah.cern.ch/patch/?%d" % patch).read())
00104 parser.close()
00105 return parser.patch
00106
00107 def get_patch_data(file_id):
00108 file_id = int(file_id)
00109 return urlopen("https://savannah.cern.ch/patch/download.php?file_id=%d" % file_id).read()
00110
00111 class TempDir(object):
00112 """Class to create a temporary directory."""
00113 def __init__(self, suffix="", prefix="tmp", dir=None, keep_var="KEEPTEMPDIR"):
00114 """Constructor.
00115
00116 'keep_var' is used to define which environment variable will prevent the
00117 deletion of the directory.
00118
00119 The other arguments are the same as tempfile.mkdtemp.
00120 """
00121 self._keep_var = keep_var
00122 self._name = mkdtemp(suffix, prefix, dir)
00123
00124 def getName(self):
00125 """Returns the name of the temporary directory"""
00126 return self._name
00127
00128 def __str__(self):
00129 """Convert to string."""
00130 return self.getName()
00131
00132 def __del__(self):
00133 """Destructor.
00134
00135 Remove the temporary directory.
00136 """
00137 if self._name:
00138 if self._keep_var in os.environ:
00139 logging.info("%s set: I do not remove the temporary directory '%s'",
00140 self._keep_var, self._name)
00141 return
00142 rmtree(self._name)
00143
00144 def check_out_gaudi(path):
00145 return Popen(["svn", "co", "http://svnweb.cern.ch/guest/gaudi/Gaudi/trunk", os.path.join(path, "Gaudi")]).wait()
00146
00147 def apply_patch(patch_data, path):
00148 proc = Popen(["patch", "-p0", "--batch"], cwd = path, stdin = PIPE)
00149 proc.communicate(patch_data)
00150 return proc.returncode
00151
00152 def check(path):
00153 return Popen(" ".join(["cmt", "show", "projects"]), shell = True, cwd = path).wait()
00154
00155 def build(path):
00156 if "LBCONFIGURATIONROOT" in os.environ:
00157 cmd = ["make",
00158 "-f", os.path.join(os.environ["LBCONFIGURATIONROOT"], "data", "Makefile")]
00159 if "use-distcc" in os.environ.get("CMTEXTRATAGS",""):
00160 cmd += ["-j", "6"]
00161 else:
00162 cmd = ["cmt", "-pack=GaudiRelease", "broadcast", "cmt", "make", "all_groups"]
00163 return Popen(" ".join(cmd),
00164 shell = True,
00165 cwd = path).wait()
00166 def test(path):
00167 cmd = ["cmt", "-pack=GaudiRelease", "TestProject"]
00168 proc = Popen(" ".join(cmd),
00169 stdout = PIPE,
00170 shell = True,
00171 cwd = path)
00172 output = []
00173 while proc.poll() is None:
00174 chunk = proc.stdout.read(256)
00175 output.append(chunk)
00176 sys.stdout.write(chunk)
00177 sys.stdout.flush()
00178 chunk = proc.stdout.read(256)
00179 output.append(chunk)
00180 sys.stdout.write(chunk)
00181 sys.stdout.flush()
00182 if proc.returncode:
00183
00184 return proc.returncode
00185
00186 output = ("".join(output)).splitlines()
00187 for l in output:
00188 l = l.strip()
00189 if ": FAIL" in l or ": ERROR" in l:
00190 return 1
00191 return 0
00192
00193 def main():
00194 logging.basicConfig()
00195 if len(sys.argv) != 2:
00196 print """Usage:
00197 validate_patch.py <savannah patch id>
00198 validate_patch.py file.patch
00199 """
00200 return 2
00201 patch_id = sys.argv[1]
00202 if os.path.isfile(patch_id):
00203 patch_data = open(patch_id, "rb").read()
00204 else:
00205 patch = get_patch_info(patch_id)
00206 patch_file_id = patch.files[0][1]
00207 patch_data = get_patch_data(patch_file_id)
00208
00209 td = TempDir(prefix = patch_id + "-")
00210 if check_out_gaudi(str(td)) != 0:
00211 print "Sorry, problems checking out Gaudi. Try again."
00212 return 0
00213 top_dir = os.path.join(str(td), "Gaudi")
00214 open(os.path.join(top_dir, patch_id) ,"wb").write(patch_data)
00215
00216 revision = -1
00217 for l in Popen(["svn", "info", top_dir], stdout = PIPE).communicate()[0].splitlines():
00218 if l.startswith("Revision:"):
00219 revision = int(l.split()[-1])
00220 break
00221
00222 actions = [(lambda path: apply_patch(patch_data, path), "application of the patch"),
00223 (check, "check of the configuration"),
00224 (build, "build"),
00225 (test, "test"),
00226 ]
00227 failure = False
00228 for action, title in actions:
00229 if action(top_dir) != 0:
00230 failure = title
00231 break
00232
00233 if failure:
00234 print "*** Patch %s failed during %s (using revision r%d) ***" % (patch_id, failure, revision)
00235 return 1
00236
00237 print "*** Patch %s succeeded (using revision r%d) ***" % (patch_id, revision)
00238 return 0
00239
00240 if __name__ == "__main__":
00241 sys.exit(main())