blob: 79066e8a12ef024ead477fe9eba93cf2181701e0 [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001#
2# builder.py - PJSIP test scenarios builder
3#
4# Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19#
20
21import ccdash
22import os
23import platform
24import re
25import subprocess
26import sys
27import time
28
29class Operation:
30 """\
31 The Operation class describes the individual ccdash operation to be
32 performed.
33
34 """
35 # Types:
36 UPDATE = "update" # Update operation
37 CONFIGURE = "configure" # Configure operation
38 BUILD = "build" # Build operation
39 TEST = "test" # Unit test operation
40
41 def __init__(self, type, cmdline, name="", wdir=""):
42 self.type = type
43 self.cmdline = cmdline
44 self.name = name
45 self.wdir = wdir
46 if self.type==self.TEST and not self.name:
47 raise "name required for tests"
48
49 def encode(self, base_dir):
50 s = [self.type]
51 if self.type == self.TEST:
52 s.append(self.name)
53 if self.type != self.UPDATE:
54 s.append(self.cmdline)
55 s.append("-w")
56 if self.wdir:
57 s.append(base_dir + "/" + self.wdir)
58 else:
59 s.append(base_dir)
60 return s
61
62
63#
64# Update operation
65#
66update_ops = [Operation(Operation.UPDATE, "")]
67
68#
69# The standard library tests (e.g. pjlib-test, pjsip-test, etc.)
70#
71std_test_ops= [
72 Operation(Operation.TEST, "./pjlib-test$SUFFIX", name="pjlib test",
73 wdir="pjlib/bin"),
74 Operation(Operation.TEST, "./pjlib-util-test$SUFFIX",
75 name="pjlib-util test", wdir="pjlib-util/bin"),
76 Operation(Operation.TEST, "./pjnath-test$SUFFIX", name="pjnath test",
77 wdir="pjnath/bin"),
78 Operation(Operation.TEST, "./pjmedia-test$SUFFIX", name="pjmedia test",
79 wdir="pjmedia/bin"),
80 Operation(Operation.TEST, "./pjsip-test$SUFFIX", name="pjsip test",
81 wdir="pjsip/bin")
82]
83
84#
85# These are pjsua Python based unit test operations
86#
87def build_pjsua_test_ops(pjsua_exe=""):
88 ops = []
89 if pjsua_exe:
90 exe = " -e ../../pjsip-apps/bin/" + pjsua_exe
91 else:
92 exe = ""
93 cwd = os.getcwd()
94 os.chdir("../pjsua")
95 os.system("python runall.py --list > list")
96 f = open("list", "r")
97 for e in f:
98 e = e.rstrip("\r\n ")
99 (mod,param) = e.split(None,2)
100 name = mod[4:mod.find(".py")] + "_" + \
101 param[param.find("/")+1:param.find(".py")]
102 ops.append(Operation(Operation.TEST, "python run.py" + exe + " " + \
103 e, name=name, wdir="tests/pjsua"))
104 f.close()
105 os.remove("list")
106 os.chdir(cwd)
107 return ops
108
109#
110# Get gcc version
111#
112def gcc_version(gcc):
113 proc = subprocess.Popen(gcc + " -v", stdout=subprocess.PIPE,
114 stderr=subprocess.STDOUT, shell=True)
115 ver = ""
116 while True:
117 s = proc.stdout.readline()
118 if not s:
119 break
120 if s.find("gcc version") >= 0:
121 ver = s.split(None, 3)[2]
122 break
123 proc.wait()
124 return "gcc-" + ver
125
126#
127# Get Visual Studio version
128#
129def vs_get_version():
130 proc = subprocess.Popen("cl", stdout=subprocess.PIPE,
131 stderr=subprocess.STDOUT)
132 while True:
133 s = proc.stdout.readline()
134 if s=="":
135 break
136 pos = s.find("Version")
137 if pos > 0:
138 proc.wait()
139 s = s[pos+8:]
140 ver = s.split(None, 1)[0]
141 major = ver[0:2]
142 if major=="12":
143 return "vs6"
144 elif major=="13":
145 return "vs2003"
146 elif major=="14":
147 return "vs2005"
148 elif major=="15":
149 return "vs2008"
150 else:
151 return "vs-" + major
152 proc.wait()
153 return "vs-unknown"
154
155
156#
157# Test config
158#
159class BaseConfig:
160 def __init__(self, base_dir, url, site, group, options=None):
161 self.base_dir = base_dir
162 self.url = url
163 self.site = site
164 self.group = group
165 self.options = options
166
167#
168# Base class for test configurator
169#
170class TestBuilder:
171 def __init__(self, config, build_config_name="",
172 user_mak="", config_site="", exclude=[], not_exclude=[]):
173 self.config = config # BaseConfig instance
174 self.build_config_name = build_config_name # Optional build suffix
175 self.user_mak = user_mak # To be put in user.mak
176 self.config_site = config_site # To be put in config_s..
177 self.saved_user_mak = "" # To restore user.mak
178 self.saved_config_site = "" # To restore config_s..
179 self.exclude = exclude # List of exclude pattern
180 self.not_exclude = not_exclude # List of include pattern
181 self.ccdash_args = [] # ccdash cmd line
182
183 def stamp(self):
184 return time.strftime("%Y%m%d-%H%M", time.localtime())
185
186 def pre_action(self):
187 # Override user.mak
188 name = self.config.base_dir + "/user.mak"
189 if os.access(name, os.F_OK):
190 f = open(name, "r")
191 self.saved_user_mak = f.read()
192 f.close()
193 if True:
194 f = open(name, "w")
195 f.write(self.user_mak)
196 f.close()
197 # Override config_site.h
198 name = self.config.base_dir + "/pjlib/include/pj/config_site.h"
199 if os.access(name, os.F_OK):
200 f = open(name, "r")
201 self.saved_config_site= f.read()
202 f.close()
203 if True:
204 f = open(name, "wt")
205 f.write(self.config_site)
206 f.close()
207
208
209 def post_action(self):
210 # Restore user.mak
211 name = self.config.base_dir + "/user.mak"
212 f = open(name, "wt")
213 f.write(self.saved_user_mak)
214 f.close()
215 # Restore config_site.h
216 name = self.config.base_dir + "/pjlib/include/pj/config_site.h"
217 f = open(name, "wt")
218 f.write(self.saved_config_site)
219 f.close()
220
221 def build_tests(self):
222 # This should be overridden by subclasses
223 pass
224
225 def execute(self):
226 if len(self.ccdash_args)==0:
227 self.build_tests()
228 self.pre_action()
229 mandatory_op = ["update", "configure", "build"]
230 counter = 0
231 for a in self.ccdash_args:
232 # Check if this test is in exclusion list
233 fullcmd = " ".join(a)
234 excluded = False
235 included = False
236 for pat in self.exclude:
237 if pat and re.search(pat, fullcmd) != None:
238 excluded = True
239 break
240 if excluded:
241 for pat in self.not_exclude:
242 if pat and re.search(pat, fullcmd) != None:
243 included = True
244 break
245 if excluded and not included:
246 if len(fullcmd)>60:
247 fullcmd = fullcmd[0:60] + ".."
248 print "Skipping '%s'" % (fullcmd)
249 continue
250
251 b = ["ccdash.py"]
252 b.extend(a)
253 a = b
254 #print a
255 try:
256 rc = ccdash.main(a)
257 except Exception, e:
258 errmsg = str(e)
259 print "**** Error: ccdash got exception %s ****" % errmsg
260 rc = -1
261 except:
262 print "**** Error: ccdash got unknown exception ****"
263 rc = -1
264
265 if rc!=0 and a[1] in mandatory_op:
266 print "Stopping because of error.."
267 break
268 counter = counter + 1
269 self.post_action()
270
271
272#
273# GNU test configurator
274#
275class GNUTestBuilder(TestBuilder):
276 """\
277 This class creates list of tests suitable for GNU targets.
278
279 """
280 def __init__(self, config, build_config_name="", user_mak="", \
281 config_site="", cross_compile="", exclude=[], not_exclude=[]):
282 """\
283 Parameters:
284 config - BaseConfig instance
285 build_config_name - Optional name to be added as suffix to the build
286 name. Sample: "min-size", "O4", "TLS", etc.
287 user_mak - Contents to be put on user.mak
288 config_site - Contents to be put on config_site.h
289 cross_compile - Optional cross-compile prefix. Must include the
290 trailing dash, e.g. "arm-unknown-linux-"
291 exclude - List of regular expression patterns for tests
292 that will be excluded from the run
293 not_exclude - List of regular expression patterns for tests
294 that will be run regardless of whether they
295 match the excluded pattern.
296
297 """
298 TestBuilder.__init__(self, config, build_config_name=build_config_name,
299 user_mak=user_mak, config_site=config_site,
300 exclude=exclude, not_exclude=not_exclude)
301 self.cross_compile = cross_compile
302 if self.cross_compile and self.cross_compile[-1] != '-':
303 self.cross_compile.append("-")
304
305 def build_tests(self):
306 if self.cross_compile:
307 suffix = "-" + self.cross_compile[0:-1]
308 build_name = self.cross_compile + \
309 gcc_version(self.cross_compile + "gcc")
310 else:
311 proc = subprocess.Popen("sh "+self.config.base_dir+"/config.guess",
312 shell=True, stdout=subprocess.PIPE)
313 plat = proc.stdout.readline().rstrip(" \r\n")
314 build_name = plat + "-"+gcc_version(self.cross_compile + "gcc")
315 suffix = "-" + plat
316
317 if self.build_config_name:
318 build_name = build_name + "-" + self.build_config_name
319 cmds = []
320 cmds.extend(update_ops)
321 cmds.append(Operation(Operation.CONFIGURE, "sh ./configure"))
322 if sys.platform=="win32":
323 # Don't build python module on Mingw
324 cmds.append(Operation(Operation.BUILD,
325 "sh -c 'make distclean && make dep && make'"))
326 else:
327 cmds.append(Operation(Operation.BUILD,
328 "sh -c 'make distclean && make dep && make" + \
329 " && cd pjsip-apps/src/python && " + \
330 "python setup.py clean build'"))
331
332 cmds.extend(std_test_ops)
333 cmds.extend(build_pjsua_test_ops())
334 self.ccdash_args = []
335 for c in cmds:
336 c.cmdline = c.cmdline.replace("$SUFFIX", suffix)
337 args = c.encode(self.config.base_dir)
338 args.extend(["-U", self.config.url,
339 "-S", self.config.site,
340 "-T", self.stamp(),
341 "-B", build_name,
342 "-G", self.config.group])
343 args.extend(self.config.options)
344 self.ccdash_args.append(args)
345
346#
347# MSVC test configurator
348#
349class MSVCTestBuilder(TestBuilder):
350 """\
351 This class creates list of tests suitable for Visual Studio builds.
352 You need to set the MSVC environment variables (typically by calling
353 vcvars32.bat) prior to running this class.
354
355 """
356 def __init__(self, config, target="Release|Win32", build_config_name="",
357 config_site="", exclude=[], not_exclude=[]):
358 """\
359 Parameters:
360 config - BaseConfig instance
361 target - Visual Studio build configuration to build.
362 Sample: "Debug|Win32", "Release|Win32".
363 build_config_name - Optional name to be added as suffix to the build
364 name. Sample: "Debug", "Release", "IPv6", etc.
365 config_site - Contents to be put on config_site.h
366 exclude - List of regular expression patterns for tests
367 that will be excluded from the run
368 not_exclude - List of regular expression patterns for tests
369 that will be run regardless of whether they
370 match the excluded pattern.
371
372 """
373 TestBuilder.__init__(self, config, build_config_name=build_config_name,
374 config_site=config_site, exclude=exclude,
375 not_exclude=not_exclude)
376 self.target = target.lower()
377
378 def build_tests(self):
379
380 (vsbuild,sys) = self.target.split("|",2)
381
382 build_name = sys + "-" + vs_get_version() + "-" + vsbuild
383
384 if self.build_config_name:
385 build_name = build_name + "-" + self.build_config_name
386
387 vccmd = "vcbuild.exe /nologo /nohtmllog /nocolor /rebuild " + \
388 "pjproject-vs8.sln " + " \"" + self.target + "\""
389
390 suffix = "-i386-win32-vc8-" + vsbuild
391 pjsua = "pjsua_vc8"
392 if vsbuild=="debug":
393 pjsua = pjsua + "d"
394
395 cmds = []
396 cmds.extend(update_ops)
397 cmds.append(Operation(Operation.CONFIGURE, "CMD /C echo Nothing to do"))
398 cmds.append(Operation(Operation.BUILD, vccmd))
399 cmds.extend(std_test_ops)
400 cmds.extend(build_pjsua_test_ops(pjsua))
401
402 self.ccdash_args = []
403 for c in cmds:
404 c.cmdline = c.cmdline.replace("$SUFFIX", suffix)
405 args = c.encode(self.config.base_dir)
406 args.extend(["-U", self.config.url,
407 "-S", self.config.site,
408 "-T", self.stamp(),
409 "-B", build_name,
410 "-G", self.config.group])
411 args.extend(self.config.options)
412 self.ccdash_args.append(args)
413
414
415#
416# Symbian test configurator
417#
418class SymbianTestBuilder(TestBuilder):
419 """\
420 This class creates list of tests suitable for Symbian builds. You need to
421 set the command line build settings prior to running this class (typically
422 that involves setting the EPOCROOT variable and current device).
423
424 """
425 def __init__(self, config, target="gcce urel", build_config_name="",
426 config_site="", exclude=[], not_exclude=[]):
427 """\
428 Parameters:
429 config - BaseConfig instance
430 target - Symbian target to build. Default is "gcce urel".
431 build_config_name - Optional name to be added as suffix to the build
432 name. Sample: "APS", "VAS", etc.
433 config_site - Contents to be put on config_site.h
434 exclude - List of regular expression patterns for tests
435 that will be excluded from the run
436 not_exclude - List of regular expression patterns for tests
437 that will be run regardless of whether they
438 match the excluded pattern.
439
440 """
441 TestBuilder.__init__(self, config, build_config_name=build_config_name,
442 config_site=config_site, exclude=exclude,
443 not_exclude=not_exclude)
444 self.target = target.lower()
445
446 def build_tests(self):
447
448 # Check that EPOCROOT is set
449 if not "EPOCROOT" in os.environ:
450 print "Error: EPOCROOT environment variable is not set"
451 sys.exit(1)
452 epocroot = os.environ["EPOCROOT"]
453 # EPOCROOT must have trailing backslash
454 if epocroot[-1] != "\\":
455 epocroot = epocroot + "\\"
456 os.environ["EPOCROOT"] = epocroot
457 sdk1 = epocroot.split("\\")[-2]
458
459 # Check that correct device is set
460 proc = subprocess.Popen("devices", stdout=subprocess.PIPE,
461 stderr=subprocess.STDOUT, shell=True)
462 sdk2 = ""
463 while True:
464 line = proc.stdout.readline()
465 if line.find("- default") > 0:
466 sdk2 = line.split(":",1)[0]
467 break
468 proc.wait()
469
470 if sdk1 != sdk2:
471 print "Error: default SDK in device doesn't match EPOCROOT"
472 print "Default device SDK =", sdk2
473 print "EPOCROOT SDK =", sdk1
474 sys.exit(1)
475
476 build_name = sdk2.replace("_", "-") + "-" + \
477 self.target.replace(" ", "-")
478
479 if self.build_config_name:
480 build_name = build_name + "-" + self.build_config_name
481
482 cmdline = "cmd /C \"cd build.symbian && bldmake bldfiles && abld build %s\"" % (self.target)
483
484 cmds = []
485 cmds.extend(update_ops)
486 cmds.append(Operation(Operation.CONFIGURE, "CMD /C echo Nothing to do"))
487 cmds.extend([Operation(Operation.BUILD, cmdline)])
488
489 self.ccdash_args = []
490 suffix = ""
491 for c in cmds:
492 c.cmdline = c.cmdline.replace("$SUFFIX", suffix)
493 args = c.encode(self.config.base_dir)
494 args.extend(["-U", self.config.url,
495 "-S", self.config.site,
496 "-T", self.stamp(),
497 "-B", build_name,
498 "-G", self.config.group])
499 args.extend(self.config.options)
500 self.ccdash_args.append(args)
501