All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
validate_patch.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 """
3 Simple script for automatic validation of a patch.
4 
5 Usage:
6  validate_patch.py <savannah patch id>
7  validate_patch.py file.patch
8 """
9 
10 __author__ = "Marco Clemencic <marco.clemencic@cern.ch>"
11 
12 import os, sys
13 import logging
14 from shutil import rmtree
15 from tempfile import mkdtemp
16 from subprocess import Popen, PIPE
17 
18 from HTMLParser import HTMLParser
19 from urllib import urlopen
20 
21 class PatchData(object):
22  def __init__(self, id = None, title = None, files = None):
23  self.id = None
24  self.title = None
25  if files is None:
26  self.files = []
27  def __repr__(self):
28  r = self.__class__.__name__ + "("
29  fields = []
30  if self.id is not None:
31  fields.append("id=%r" % self.id)
32  if self.title is not None:
33  fields.append("title=%r" % self.title)
34  if self.files:
35  fields.append("files=%r" % self.files)
36  return "%s(%s)" % (self.__class__.__name__, ",".join(fields))
37 
38 ## parse the
39 class SavannahParser(HTMLParser):
40  __attachments_id__ = "hidsubpartcontentattached"
41  def __init__(self):
42  HTMLParser.__init__(self)
43  # data collected
44  self.patch = None
45 
46  # parsing flags and temporary data
47  self._parsingAttachments = 0 # depth of span tag in the attachment block
48  self._currentFileId = None
49  self._currentFileData = ""
50 
51  # temporary storage
52 
53  def handle_starttag(self, tag, attrs):
54  attrs = dict(attrs)
55  if tag == "html":
56  # new file: new patch data
57  self.patch = PatchData()
58  elif tag == "span":
59  if attrs.get("id", None) == self.__attachments_id__:
60  self._parsingAttachments = 1
61  elif self._parsingAttachments:
62  self._parsingAttachments += 1
63  elif (self._parsingAttachments
64  and tag == "a"
65  and "file_id=" in attrs.get("href", "")):
66  self._currentFileId = attrs["href"].split("file_id=")[-1]
67  def handle_endtag(self, tag):
68  if tag == "span" and self._parsingAttachments:
69  self._parsingAttachments -= 1
70  elif tag == "a" and self._currentFileId:
71  #print self._currentFileData
72  filename = self._currentFileData.split(":")[-1].strip()
73  #print filename, self._currentFileId
74  self.patch.files.append((filename, int(self._currentFileId)))
75  self._currentFileId = None
76  self._currentFileData = ""
77 
78  def handle_data(self, data):
79  if self._currentFileId:
80  data = data.replace("&nbsp;", " ")
81  self._currentFileData += data
82 
83 def get_patch_info_x(patch):
84  patch = int(patch)
85  server = "savannah.cern.ch"
86  path = "/patch/?%d" % patch
87  conn = httplib.HTTPSConnection(server)
88  conn.request("GET", path)
89  r = conn.getresponse()
90  if r.status == 200:
91  pass
92  else:
93  raise RuntimeError(r.status, r.reason, "https://" + server + path)
94  parser = SavannahParser()
95  parser.feed(r.read())
96  parser.close()
97  conn.close()
98  return parser.patch
99 
100 def get_patch_info(patch):
101  patch = int(patch)
102  parser = SavannahParser()
103  parser.feed(urlopen("https://savannah.cern.ch/patch/?%d" % patch).read())
104  parser.close()
105  return parser.patch
106 
107 def get_patch_data(file_id):
108  file_id = int(file_id)
109  return urlopen("https://savannah.cern.ch/patch/download.php?file_id=%d" % file_id).read()
110 
111 class TempDir(object):
112  """Class to create a temporary directory."""
113  def __init__(self, suffix="", prefix="tmp", dir=None, keep_var="KEEPTEMPDIR"):
114  """Constructor.
115 
116  'keep_var' is used to define which environment variable will prevent the
117  deletion of the directory.
118 
119  The other arguments are the same as tempfile.mkdtemp.
120  """
121  self._keep_var = keep_var
122  self._name = mkdtemp(suffix, prefix, dir)
123 
124  def getName(self):
125  """Returns the name of the temporary directory"""
126  return self._name
127 
128  def __str__(self):
129  """Convert to string."""
130  return self.getName()
131 
132  def __del__(self):
133  """Destructor.
134 
135  Remove the temporary directory.
136  """
137  if self._name:
138  if self._keep_var in os.environ:
139  logging.info("%s set: I do not remove the temporary directory '%s'",
140  self._keep_var, self._name)
141  return
142  rmtree(self._name)
143 
144 def check_out_gaudi(path):
145  return Popen(["svn", "co", "http://svnweb.cern.ch/guest/gaudi/Gaudi/trunk", os.path.join(path, "Gaudi")]).wait()
146 
147 def apply_patch(patch_data, path):
148  proc = Popen(["patch", "-p0", "--batch"], cwd = path, stdin = PIPE)
149  proc.communicate(patch_data)
150  return proc.returncode
151 
152 def check(path):
153  return Popen(" ".join(["cmt", "show", "projects"]), shell = True, cwd = path).wait()
154 
155 def build(path):
156  if "LBCONFIGURATIONROOT" in os.environ:
157  cmd = ["make",
158  "-f", os.path.join(os.environ["LBCONFIGURATIONROOT"], "data", "Makefile")]
159  if "use-distcc" in os.environ.get("CMTEXTRATAGS",""):
160  cmd += ["-j", "6"]
161  else:
162  cmd = ["cmt", "-pack=GaudiRelease", "broadcast", "cmt", "make", "all_groups"]
163  return Popen(" ".join(cmd),
164  shell = True,
165  cwd = path).wait()
166 def test(path):
167  cmd = ["cmt", "-pack=GaudiRelease", "TestProject"]
168  proc = Popen(" ".join(cmd),
169  stdout = PIPE,
170  shell = True,
171  cwd = path)
172  output = []
173  while proc.poll() is None:
174  chunk = proc.stdout.read(256)
175  output.append(chunk)
176  sys.stdout.write(chunk)
177  sys.stdout.flush()
178  chunk = proc.stdout.read(256)
179  output.append(chunk)
180  sys.stdout.write(chunk)
181  sys.stdout.flush()
182  if proc.returncode:
183  # immediately return in case of failure
184  return proc.returncode
185  # look for failures in the output
186  output = ("".join(output)).splitlines()
187  for l in output:
188  l = l.strip()
189  if ": FAIL" in l or ": ERROR" in l:
190  return 1
191  return 0
192 
193 def main():
194  logging.basicConfig()
195  if len(sys.argv) != 2:
196  print """Usage:
197  validate_patch.py <savannah patch id>
198  validate_patch.py file.patch
199 """
200  return 2
201  patch_id = sys.argv[1]
202  if os.path.isfile(patch_id):
203  patch_data = open(patch_id, "rb").read()
204  else:
205  patch = get_patch_info(patch_id)
206  patch_file_id = patch.files[0][1]
207  patch_data = get_patch_data(patch_file_id)
208 
209  td = TempDir(prefix = patch_id + "-")
210  if check_out_gaudi(str(td)) != 0:
211  print "Sorry, problems checking out Gaudi. Try again."
212  return 0
213  top_dir = os.path.join(str(td), "Gaudi")
214  open(os.path.join(top_dir, patch_id) ,"wb").write(patch_data)
215 
216  revision = -1
217  for l in Popen(["svn", "info", top_dir], stdout = PIPE).communicate()[0].splitlines():
218  if l.startswith("Revision:"):
219  revision = int(l.split()[-1])
220  break
221 
222  actions = [(lambda path: apply_patch(patch_data, path), "application of the patch"),
223  (check, "check of the configuration"),
224  (build, "build"),
225  (test, "test"),
226  ]
227  failure = False
228  for action, title in actions:
229  if action(top_dir) != 0:
230  failure = title
231  break
232 
233  if failure:
234  print "*** Patch %s failed during %s (using revision r%d) ***" % (patch_id, failure, revision)
235  return 1
236 
237  print "*** Patch %s succeeded (using revision r%d) ***" % (patch_id, revision)
238  return 0
239 
240 if __name__ == "__main__":
241  sys.exit(main())
string action
Definition: merge_files.py:89