| # |
| # builder.py - PJSIP test scenarios builder |
| # |
| # Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program; if not, write to the Free Software |
| # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| # |
| |
| import ccdash |
| import os |
| import platform |
| import re |
| import subprocess |
| import sys |
| import time |
| |
| class Operation: |
| """\ |
| The Operation class describes the individual ccdash operation to be |
| performed. |
| |
| """ |
| # Types: |
| UPDATE = "update" # Update operation |
| CONFIGURE = "configure" # Configure operation |
| BUILD = "build" # Build operation |
| TEST = "test" # Unit test operation |
| |
| def __init__(self, type, cmdline, name="", wdir=""): |
| self.type = type |
| self.cmdline = cmdline |
| self.name = name |
| self.wdir = wdir |
| if self.type==self.TEST and not self.name: |
| raise "name required for tests" |
| |
| def encode(self, base_dir): |
| s = [self.type] |
| if self.type == self.TEST: |
| s.append(self.name) |
| if self.type != self.UPDATE: |
| s.append(self.cmdline) |
| s.append("-w") |
| if self.wdir: |
| s.append(base_dir + "/" + self.wdir) |
| else: |
| s.append(base_dir) |
| return s |
| |
| |
| # |
| # Update operation |
| # |
| update_ops = [Operation(Operation.UPDATE, "")] |
| |
| # |
| # The standard library tests (e.g. pjlib-test, pjsip-test, etc.) |
| # |
| std_test_ops= [ |
| Operation(Operation.TEST, "./pjlib-test$SUFFIX", name="pjlib test", |
| wdir="pjlib/bin"), |
| Operation(Operation.TEST, "./pjlib-util-test$SUFFIX", |
| name="pjlib-util test", wdir="pjlib-util/bin"), |
| Operation(Operation.TEST, "./pjnath-test$SUFFIX", name="pjnath test", |
| wdir="pjnath/bin"), |
| Operation(Operation.TEST, "./pjmedia-test$SUFFIX", name="pjmedia test", |
| wdir="pjmedia/bin"), |
| Operation(Operation.TEST, "./pjsip-test$SUFFIX", name="pjsip test", |
| wdir="pjsip/bin") |
| ] |
| |
| # |
| # These are pjsua Python based unit test operations |
| # |
| def build_pjsua_test_ops(pjsua_exe=""): |
| ops = [] |
| if pjsua_exe: |
| exe = " -e ../../pjsip-apps/bin/" + pjsua_exe |
| else: |
| exe = "" |
| cwd = os.getcwd() |
| os.chdir("../pjsua") |
| os.system("python runall.py --list > list") |
| f = open("list", "r") |
| for e in f: |
| e = e.rstrip("\r\n ") |
| (mod,param) = e.split(None,2) |
| name = mod[4:mod.find(".py")] + "_" + \ |
| param[param.find("/")+1:param.find(".py")] |
| ops.append(Operation(Operation.TEST, "python run.py" + exe + " " + \ |
| e, name=name, wdir="tests/pjsua")) |
| f.close() |
| os.remove("list") |
| os.chdir(cwd) |
| return ops |
| |
| # |
| # Get gcc version |
| # |
| def gcc_version(gcc): |
| proc = subprocess.Popen(gcc + " -v", stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, shell=True) |
| ver = "" |
| while True: |
| s = proc.stdout.readline() |
| if not s: |
| break |
| if s.find("gcc version") >= 0: |
| ver = s.split(None, 3)[2] |
| break |
| proc.wait() |
| return "gcc-" + ver |
| |
| # |
| # Get Visual Studio version |
| # |
| def vs_get_version(): |
| proc = subprocess.Popen("cl", stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT) |
| while True: |
| s = proc.stdout.readline() |
| if s=="": |
| break |
| pos = s.find("Version") |
| if pos > 0: |
| proc.wait() |
| s = s[pos+8:] |
| ver = s.split(None, 1)[0] |
| major = ver[0:2] |
| if major=="12": |
| return "vs6" |
| elif major=="13": |
| return "vs2003" |
| elif major=="14": |
| return "vs2005" |
| elif major=="15": |
| return "vs2008" |
| else: |
| return "vs-" + major |
| proc.wait() |
| return "vs-unknown" |
| |
| |
| # |
| # Test config |
| # |
| class BaseConfig: |
| def __init__(self, base_dir, url, site, group, options=None): |
| self.base_dir = base_dir |
| self.url = url |
| self.site = site |
| self.group = group |
| self.options = options |
| |
| # |
| # Base class for test configurator |
| # |
| class TestBuilder: |
| def __init__(self, config, build_config_name="", |
| user_mak="", config_site="", exclude=[], not_exclude=[]): |
| self.config = config # BaseConfig instance |
| self.build_config_name = build_config_name # Optional build suffix |
| self.user_mak = user_mak # To be put in user.mak |
| self.config_site = config_site # To be put in config_s.. |
| self.saved_user_mak = "" # To restore user.mak |
| self.saved_config_site = "" # To restore config_s.. |
| self.exclude = exclude # List of exclude pattern |
| self.not_exclude = not_exclude # List of include pattern |
| self.ccdash_args = [] # ccdash cmd line |
| |
| def stamp(self): |
| return time.strftime("%Y%m%d-%H%M", time.localtime()) |
| |
| def pre_action(self): |
| # Override user.mak |
| name = self.config.base_dir + "/user.mak" |
| if os.access(name, os.F_OK): |
| f = open(name, "r") |
| self.saved_user_mak = f.read() |
| f.close() |
| if True: |
| f = open(name, "w") |
| f.write(self.user_mak) |
| f.close() |
| # Override config_site.h |
| name = self.config.base_dir + "/pjlib/include/pj/config_site.h" |
| if os.access(name, os.F_OK): |
| f = open(name, "r") |
| self.saved_config_site= f.read() |
| f.close() |
| if True: |
| f = open(name, "wt") |
| f.write(self.config_site) |
| f.close() |
| |
| |
| def post_action(self): |
| # Restore user.mak |
| name = self.config.base_dir + "/user.mak" |
| f = open(name, "wt") |
| f.write(self.saved_user_mak) |
| f.close() |
| # Restore config_site.h |
| name = self.config.base_dir + "/pjlib/include/pj/config_site.h" |
| f = open(name, "wt") |
| f.write(self.saved_config_site) |
| f.close() |
| |
| def build_tests(self): |
| # This should be overridden by subclasses |
| pass |
| |
| def execute(self): |
| if len(self.ccdash_args)==0: |
| self.build_tests() |
| self.pre_action() |
| mandatory_op = ["update", "configure", "build"] |
| counter = 0 |
| for a in self.ccdash_args: |
| # Check if this test is in exclusion list |
| fullcmd = " ".join(a) |
| excluded = False |
| included = False |
| for pat in self.exclude: |
| if pat and re.search(pat, fullcmd) != None: |
| excluded = True |
| break |
| if excluded: |
| for pat in self.not_exclude: |
| if pat and re.search(pat, fullcmd) != None: |
| included = True |
| break |
| if excluded and not included: |
| if len(fullcmd)>60: |
| fullcmd = fullcmd[0:60] + ".." |
| print "Skipping '%s'" % (fullcmd) |
| continue |
| |
| b = ["ccdash.py"] |
| b.extend(a) |
| a = b |
| #print a |
| try: |
| rc = ccdash.main(a) |
| except Exception, e: |
| errmsg = str(e) |
| print "**** Error: ccdash got exception %s ****" % errmsg |
| rc = -1 |
| except: |
| print "**** Error: ccdash got unknown exception ****" |
| rc = -1 |
| |
| if rc!=0 and a[1] in mandatory_op: |
| print "Stopping because of error.." |
| break |
| counter = counter + 1 |
| self.post_action() |
| |
| |
| # |
| # GNU test configurator |
| # |
| class GNUTestBuilder(TestBuilder): |
| """\ |
| This class creates list of tests suitable for GNU targets. |
| |
| """ |
| def __init__(self, config, build_config_name="", user_mak="", \ |
| config_site="", cross_compile="", exclude=[], not_exclude=[]): |
| """\ |
| Parameters: |
| config - BaseConfig instance |
| build_config_name - Optional name to be added as suffix to the build |
| name. Sample: "min-size", "O4", "TLS", etc. |
| user_mak - Contents to be put on user.mak |
| config_site - Contents to be put on config_site.h |
| cross_compile - Optional cross-compile prefix. Must include the |
| trailing dash, e.g. "arm-unknown-linux-" |
| exclude - List of regular expression patterns for tests |
| that will be excluded from the run |
| not_exclude - List of regular expression patterns for tests |
| that will be run regardless of whether they |
| match the excluded pattern. |
| |
| """ |
| TestBuilder.__init__(self, config, build_config_name=build_config_name, |
| user_mak=user_mak, config_site=config_site, |
| exclude=exclude, not_exclude=not_exclude) |
| self.cross_compile = cross_compile |
| if self.cross_compile and self.cross_compile[-1] != '-': |
| self.cross_compile.append("-") |
| |
| def build_tests(self): |
| if self.cross_compile: |
| suffix = "-" + self.cross_compile[0:-1] |
| build_name = self.cross_compile + \ |
| gcc_version(self.cross_compile + "gcc") |
| else: |
| proc = subprocess.Popen("sh "+self.config.base_dir+"/config.guess", |
| shell=True, stdout=subprocess.PIPE) |
| plat = proc.stdout.readline().rstrip(" \r\n") |
| build_name = plat + "-"+gcc_version(self.cross_compile + "gcc") |
| suffix = "-" + plat |
| |
| if self.build_config_name: |
| build_name = build_name + "-" + self.build_config_name |
| cmds = [] |
| cmds.extend(update_ops) |
| cmds.append(Operation(Operation.CONFIGURE, "sh ./configure")) |
| if sys.platform=="win32": |
| # Don't build python module on Mingw |
| cmds.append(Operation(Operation.BUILD, |
| "sh -c 'make distclean && make dep && make'")) |
| else: |
| cmds.append(Operation(Operation.BUILD, |
| "sh -c 'make distclean && make dep && make" + \ |
| " && cd pjsip-apps/src/python && " + \ |
| "python setup.py clean build'")) |
| |
| cmds.extend(std_test_ops) |
| cmds.extend(build_pjsua_test_ops()) |
| self.ccdash_args = [] |
| for c in cmds: |
| c.cmdline = c.cmdline.replace("$SUFFIX", suffix) |
| args = c.encode(self.config.base_dir) |
| args.extend(["-U", self.config.url, |
| "-S", self.config.site, |
| "-T", self.stamp(), |
| "-B", build_name, |
| "-G", self.config.group]) |
| args.extend(self.config.options) |
| self.ccdash_args.append(args) |
| |
| # |
| # MSVC test configurator |
| # |
| class MSVCTestBuilder(TestBuilder): |
| """\ |
| This class creates list of tests suitable for Visual Studio builds. |
| You need to set the MSVC environment variables (typically by calling |
| vcvars32.bat) prior to running this class. |
| |
| """ |
| def __init__(self, config, target="Release|Win32", build_config_name="", |
| config_site="", exclude=[], not_exclude=[]): |
| """\ |
| Parameters: |
| config - BaseConfig instance |
| target - Visual Studio build configuration to build. |
| Sample: "Debug|Win32", "Release|Win32". |
| build_config_name - Optional name to be added as suffix to the build |
| name. Sample: "Debug", "Release", "IPv6", etc. |
| config_site - Contents to be put on config_site.h |
| exclude - List of regular expression patterns for tests |
| that will be excluded from the run |
| not_exclude - List of regular expression patterns for tests |
| that will be run regardless of whether they |
| match the excluded pattern. |
| |
| """ |
| TestBuilder.__init__(self, config, build_config_name=build_config_name, |
| config_site=config_site, exclude=exclude, |
| not_exclude=not_exclude) |
| self.target = target.lower() |
| |
| def build_tests(self): |
| |
| (vsbuild,sys) = self.target.split("|",2) |
| |
| build_name = sys + "-" + vs_get_version() + "-" + vsbuild |
| |
| if self.build_config_name: |
| build_name = build_name + "-" + self.build_config_name |
| |
| vccmd = "vcbuild.exe /nologo /nohtmllog /nocolor /rebuild " + \ |
| "pjproject-vs8.sln " + " \"" + self.target + "\"" |
| |
| suffix = "-i386-win32-vc8-" + vsbuild |
| pjsua = "pjsua_vc8" |
| if vsbuild=="debug": |
| pjsua = pjsua + "d" |
| |
| cmds = [] |
| cmds.extend(update_ops) |
| cmds.append(Operation(Operation.CONFIGURE, "CMD /C echo Nothing to do")) |
| cmds.append(Operation(Operation.BUILD, vccmd)) |
| cmds.extend(std_test_ops) |
| cmds.extend(build_pjsua_test_ops(pjsua)) |
| |
| self.ccdash_args = [] |
| for c in cmds: |
| c.cmdline = c.cmdline.replace("$SUFFIX", suffix) |
| args = c.encode(self.config.base_dir) |
| args.extend(["-U", self.config.url, |
| "-S", self.config.site, |
| "-T", self.stamp(), |
| "-B", build_name, |
| "-G", self.config.group]) |
| args.extend(self.config.options) |
| self.ccdash_args.append(args) |
| |
| |
| # |
| # Symbian test configurator |
| # |
| class SymbianTestBuilder(TestBuilder): |
| """\ |
| This class creates list of tests suitable for Symbian builds. You need to |
| set the command line build settings prior to running this class (typically |
| that involves setting the EPOCROOT variable and current device). |
| |
| """ |
| def __init__(self, config, target="gcce urel", build_config_name="", |
| config_site="", exclude=[], not_exclude=[]): |
| """\ |
| Parameters: |
| config - BaseConfig instance |
| target - Symbian target to build. Default is "gcce urel". |
| build_config_name - Optional name to be added as suffix to the build |
| name. Sample: "APS", "VAS", etc. |
| config_site - Contents to be put on config_site.h |
| exclude - List of regular expression patterns for tests |
| that will be excluded from the run |
| not_exclude - List of regular expression patterns for tests |
| that will be run regardless of whether they |
| match the excluded pattern. |
| |
| """ |
| TestBuilder.__init__(self, config, build_config_name=build_config_name, |
| config_site=config_site, exclude=exclude, |
| not_exclude=not_exclude) |
| self.target = target.lower() |
| |
| def build_tests(self): |
| |
| # Check that EPOCROOT is set |
| if not "EPOCROOT" in os.environ: |
| print "Error: EPOCROOT environment variable is not set" |
| sys.exit(1) |
| epocroot = os.environ["EPOCROOT"] |
| # EPOCROOT must have trailing backslash |
| if epocroot[-1] != "\\": |
| epocroot = epocroot + "\\" |
| os.environ["EPOCROOT"] = epocroot |
| sdk1 = epocroot.split("\\")[-2] |
| |
| # Check that correct device is set |
| proc = subprocess.Popen("devices", stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, shell=True) |
| sdk2 = "" |
| while True: |
| line = proc.stdout.readline() |
| if line.find("- default") > 0: |
| sdk2 = line.split(":",1)[0] |
| break |
| proc.wait() |
| |
| if sdk1 != sdk2: |
| print "Error: default SDK in device doesn't match EPOCROOT" |
| print "Default device SDK =", sdk2 |
| print "EPOCROOT SDK =", sdk1 |
| sys.exit(1) |
| |
| build_name = sdk2.replace("_", "-") + "-" + \ |
| self.target.replace(" ", "-") |
| |
| if self.build_config_name: |
| build_name = build_name + "-" + self.build_config_name |
| |
| cmdline = "cmd /C \"cd build.symbian && bldmake bldfiles && abld build %s\"" % (self.target) |
| |
| cmds = [] |
| cmds.extend(update_ops) |
| cmds.append(Operation(Operation.CONFIGURE, "CMD /C echo Nothing to do")) |
| cmds.extend([Operation(Operation.BUILD, cmdline)]) |
| |
| self.ccdash_args = [] |
| suffix = "" |
| for c in cmds: |
| c.cmdline = c.cmdline.replace("$SUFFIX", suffix) |
| args = c.encode(self.config.base_dir) |
| args.extend(["-U", self.config.url, |
| "-S", self.config.site, |
| "-T", self.stamp(), |
| "-B", build_name, |
| "-G", self.config.group]) |
| args.extend(self.config.options) |
| self.ccdash_args.append(args) |
| |