raptor_tests.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. #
  2. # Copyright (c) 2009-2011 Nokia Corporation and/or its subsidiary(-ies).
  3. # All rights reserved.
  4. # This component and the accompanying materials are made available
  5. # under the terms of the License "Eclipse Public License v1.0"
  6. # which accompanies this distribution, and is available
  7. # at the URL "http://www.eclipse.org/legal/epl-v10.html".
  8. #
  9. # Initial Contributors:
  10. # Nokia Corporation - initial contribution.
  11. #
  12. # Contributors:
  13. #
  14. # Description:
  15. #
  16. # run the smoke tests
  17. import os
  18. import re
  19. import stat
  20. import sys
  21. import subprocess
  22. import traceback
  23. from shutil import rmtree
  24. sys.path.append(os.environ["SBS_HOME"])
  25. from raptor.meta import BldInfFile
  26. if 'SMOKETESTLOGS' in os.environ:
  27. logDir = os.environ['SMOKETESTLOGS']
  28. else:
  29. logDir = "$(EPOCROOT)/epoc32/build/smoketestlogs"
  30. debug_mode_active = False
  31. # Environment #################################################################
  32. # On MYS there is USERNAME but not USER
  33. if 'USER' not in os.environ:
  34. os.environ['USER'] = os.environ['USERNAME']
  35. def activate_debug():
  36. """
  37. Activate debug-mode remotely
  38. """
  39. global debug_mode_active
  40. debug_mode_active = True
  41. # Determine the OS version in the epocroot we're testing
  42. # since some tests expect different outcomes for 9.4 and 9.5
  43. def getsymbianversion():
  44. epocroot = os.environ['EPOCROOT']
  45. b = open (epocroot+"/epoc32/data/buildinfo.txt","r")
  46. binfo = " ".join(b.readlines())
  47. vmatch = (re.compile("v(9\.[0-9])")).search(binfo)
  48. if vmatch:
  49. osversion = vmatch.group(1)
  50. else:
  51. osversion = '9.4'
  52. return osversion
  53. envRegex = re.compile("\$\((.+?)\)")
  54. fixEnvironment = ['EPOCROOT', 'SBS_HOME', 'SBS_CYGWIN', 'SBS_MINGW', 'SBS_PYTHON']
  55. def ReplaceEnvs(item):
  56. envs = envRegex.findall(item)
  57. for e in set(envs):
  58. try:
  59. val = os.environ[e]
  60. if e in fixEnvironment:
  61. # Raptor "fixes up" EPOCROOT etc. so we must do the same:
  62. # add the drive letter (make absolute)
  63. val = os.path.abspath(val)
  64. # use forward slashes
  65. val = val.replace("\\", "/")
  66. # remove trailing slashes
  67. val = val.rstrip("/")
  68. item = item.replace("$(" + e + ")", val)
  69. except KeyError:
  70. print(e+" is not set in the environment")
  71. raise ValueError
  72. return item
  73. # Utility functions ###########################################################
  74. def where(input_file):
  75. """Search for 'input_file' in the system path"""
  76. locations = []
  77. if sys.platform.startswith("win"):
  78. if not input_file.lower().endswith(".exe"):
  79. input_file += ".exe"
  80. for current_file in [loop_number + "\\" + input_file for loop_number in
  81. os.environ["PATH"].split(";")]:
  82. try:
  83. stat = os.stat(current_file)
  84. locations.append(current_file)
  85. except OSError as error:
  86. pass
  87. else:
  88. whichproc = subprocess.Popen(args=["which", input_file],
  89. stdout=subprocess.PIPE,
  90. stderr=subprocess.STDOUT,
  91. shell=False,
  92. universal_newlines=True)
  93. output,err = whichproc.communicate()
  94. output = output.split("\n")
  95. whichproc.wait()
  96. if len(output) > 0:
  97. locations.append(output[0:(len(output) - 1)])
  98. if len(locations) == 0:
  99. print("Error: {0} not defined in PATH environment variable".format(input_file))
  100. else:
  101. return locations[0]
  102. def clean_epocroot():
  103. """
  104. This method walks through epocroot and cleans every file and folder that is
  105. not present in the manifest file
  106. """
  107. epocroot = os.path.abspath(os.environ['EPOCROOT']).replace('\\','/')
  108. print("Cleaning Epocroot: {0}".format(epocroot))
  109. all_files = {} # dictionary to hold all files
  110. folders = [] # holds all unique folders in manifest
  111. host_platform = os.environ["HOSTPLATFORM_DIR"]
  112. try:
  113. mani = "$(EPOCROOT)/manifest"
  114. manifest = open(ReplaceEnvs(mani), "r")
  115. le = len(epocroot)
  116. for line in manifest:
  117. line = line.replace("$(HOSTPLATFORM_DIR)", host_platform)
  118. line = line.replace("./", epocroot+"/").rstrip("\n")
  119. all_files[line] = True
  120. # This bit makes a record of unique folders into a list
  121. pos = line.rfind("/", le)
  122. while pos > le: # Look through the parent folders
  123. f = line[:pos]
  124. if f not in folders:
  125. folders.append(f)
  126. pos = line.rfind("/", le, pos)
  127. # This algorithm walks through epocroot and handles files and folders
  128. walkpath = "$(EPOCROOT)"
  129. for (root, dirs, files) in os.walk(ReplaceEnvs(walkpath), topdown =
  130. False):
  131. if root.find(".hg") != -1:
  132. continue
  133. # This loop handles all files
  134. for name in files:
  135. name = os.path.join(root, name).replace("\\", "/")
  136. if name not in all_files:
  137. try:
  138. os.remove(name)
  139. except:
  140. # chmod to rw and try again
  141. try:
  142. os.chmod(name, stat.S_IRWXU)
  143. os.remove(name)
  144. except:
  145. print("\nEPOCROOT-CLEAN ERROR:")
  146. traceback.print_exc(None, sys.stdout)
  147. # This loop handles folders
  148. for name in dirs:
  149. if name.find(".hg") != -1:
  150. continue
  151. name = os.path.join(root, name).replace("\\", "/")
  152. if name not in all_files and name not in folders:
  153. # Remove the folder fully with no errors if full
  154. try:
  155. rmtree(ReplaceEnvs(name))
  156. except:
  157. print("\nEPOCROOT-CLEAN ERROR:")
  158. traceback.print_exc(None, sys.stdout)
  159. except IOError as e:
  160. print(e)
  161. print("Epocroot Cleaned")
  162. def grep(file, string):
  163. return
  164. # Test classes ################################################################
  165. class SmokeTest(object):
  166. """Base class for Smoke Test objects.
  167. Each test is defined (minimally) by,
  168. 1) an ID number as a string
  169. 2) a name
  170. 3) a raptor command-line
  171. 4) some parameters to check the command results against
  172. The run() method will,
  173. 1) delete all the listed target files
  174. 2) execute the raptor command
  175. 3) check that the test results match the test parameters
  176. 4) count the warnings and errors reported
  177. """
  178. PASS = "pass"
  179. FAIL = "fail"
  180. SKIP = "skip"
  181. def __init__(self):
  182. self.name = "smoketest"
  183. self.description = ""
  184. self.command = "sbs --do_what_i_want"
  185. self.targets = []
  186. self.missing = 0
  187. self.warnings = 0
  188. self.errors = 0
  189. self.exceptions = 0
  190. self.returncode = 0
  191. self.noclean = False
  192. self.onWindows = sys.platform.startswith("win")
  193. # These variables are for tests that treat the text as a list of lines. In
  194. # particular, "." will not match end-of-line. This means that, for example,
  195. # "abc.*def" will only match if "abc" and "def" appear on the same line.
  196. self.mustmatch = []
  197. self.mustnotmatch = []
  198. self.mustmatch_singleline = []
  199. self.mustnotmatch_singleline = []
  200. # These variables are for tests that treat the text as a single string of
  201. # characters. The pattern "." will match anything, including end-of-line.
  202. self.mustmatch_multiline = []
  203. self.mustnotmatch_multiline = []
  204. self.countmatch = []
  205. self.outputok = True
  206. self.usebash = False
  207. self.failsbecause = None
  208. self.result = SmokeTest.SKIP
  209. self.environ = {} # Allow tests to set the environment in which commands run.
  210. self.sbs_build_dir = "$(EPOCROOT)/epoc32/build"
  211. def run(self, platform = "all", noclean=False):
  212. self.noclean = noclean
  213. previousResult = self.result
  214. try:
  215. if self.runnable(platform):
  216. if not self.pretest():
  217. self.result = SmokeTest.FAIL
  218. elif not self.test():
  219. self.result = SmokeTest.FAIL
  220. elif not self.posttest():
  221. self.result = SmokeTest.FAIL
  222. else:
  223. self.result = SmokeTest.PASS
  224. else:
  225. self.skip(platform)
  226. except Exception as e:
  227. print(e)
  228. self.result = SmokeTest.FAIL
  229. # print the result of this run()
  230. self.print_result(internal = True)
  231. # if a previous run() failed then the overall result is a FAIL
  232. if previousResult == SmokeTest.FAIL:
  233. self.result = SmokeTest.FAIL
  234. def print_result(self, value = "", internal = False):
  235. # the test passed :-)
  236. result = self.result
  237. string = ""
  238. if not internal:
  239. string += "\n" + self.name + ": "
  240. if value:
  241. print(string + value)
  242. else:
  243. if result == SmokeTest.PASS:
  244. string += "TEST PASSED"
  245. elif result == SmokeTest.FAIL:
  246. string += "TEST FAILED"
  247. print(string)
  248. def runnable(self, platform):
  249. # can this test run on this platform?
  250. if platform == "all":
  251. return True
  252. isWin = self.onWindows
  253. wantWin = platform.startswith("win")
  254. return (isWin == wantWin)
  255. def skip(self, platform):
  256. print("\nSKIPPING: {0} for {1}".format(self.name, platform))
  257. def logfileOption(self):
  258. return "-f " + self.logfile();
  259. def logfile(self):
  260. return logDir + "/" + self.name.replace(" ","_") + ".log"
  261. def makefileOption(self):
  262. return "-m " + self.makefile();
  263. def makefile(self):
  264. return logDir + "/" + self.name + ".mk"
  265. def removeFiles(self, files):
  266. for t in files:
  267. tgt = os.path.normpath(ReplaceEnvs(t))
  268. if os.path.exists(tgt):
  269. try:
  270. os.chmod(tgt, stat.S_IRWXU)
  271. if os.path.isdir(tgt):
  272. rmtree(tgt)
  273. else:
  274. os.remove(tgt)
  275. except OSError:
  276. print("Could not remove {0} before the test".format(tgt))
  277. return False
  278. return True
  279. def clean(self):
  280. # remove all the target files
  281. # flatten any lists first (only 1 level of flattenening expected)
  282. # these indicate alternative files - one of them will exist after a build
  283. if not self.noclean:
  284. removables = []
  285. for i in self.targets:
  286. if type(i) is not list:
  287. removables.append(i)
  288. else:
  289. removables.extend(i)
  290. return self.removeFiles(removables)
  291. return True
  292. def pretest(self):
  293. # what to do before the test runs
  294. print("\nTEST: " + self.name)
  295. print("LOGFILE: " + self.logfile())
  296. return self.clean()
  297. def test(self):
  298. # run the actual test
  299. # put the makefile and log in $EPOCROOT/build/smoketestlogs
  300. if self.usebash:
  301. command = ReplaceEnvs(self.command)
  302. else:
  303. command = ReplaceEnvs(self.command +
  304. " " + self.makefileOption() +
  305. " " + self.logfileOption())
  306. print("COMMAND: "+ command)
  307. # Any environment settings specific to this test
  308. shellenv = os.environ.copy()
  309. for ev in self.environ:
  310. shellenv[ev] = self.environ[ev]
  311. if self.usebash:
  312. if 'SBS_SHELL' in os.environ:
  313. BASH = os.environ['SBS_SHELL']
  314. else:
  315. if self.onWindows:
  316. if 'SBS_CYGWIN' in shellenv:
  317. BASH = ReplaceEnvs("$(SBS_CYGWIN)/bin/bash.exe")
  318. else:
  319. BASH = ReplaceEnvs("$(SBS_HOME)/win32/cygwin/bin/bash.exe")
  320. else:
  321. BASH = "/bin/bash"
  322. shellenv['SBSMAKEFILE']=ReplaceEnvs(self.makefile())
  323. shellenv['SBSLOGFILE']=ReplaceEnvs(self.logfile())
  324. p = subprocess.Popen(args=[BASH, '-c', command],
  325. stdout=subprocess.PIPE,
  326. stderr=subprocess.PIPE,
  327. env=shellenv,
  328. shell=False,
  329. universal_newlines=True)
  330. (std_out, std_err) = p.communicate()
  331. self.output = std_out + std_err
  332. else:
  333. p = subprocess.Popen(command,
  334. stdout=subprocess.PIPE,
  335. stderr=subprocess.PIPE,
  336. env=shellenv,
  337. shell=True,
  338. universal_newlines=True)
  339. (std_out, std_err) = p.communicate()
  340. self.output = std_out + std_err
  341. if debug_mode_active:
  342. print(self.output)
  343. if p.returncode != self.returncode:
  344. print("RETURN: got {0} expected {1}".format(p.returncode, self.returncode))
  345. return False
  346. return True
  347. def posttest(self):
  348. # what to do after the test has run
  349. # count the targets that got built, recording those that are found
  350. # to be missing
  351. found = 0
  352. missing = []
  353. for t in self.targets:
  354. # Either we're looking at a single target file here, or a list of related target files where
  355. # at least one needs to match. We therefore use a list for the check that includes one or
  356. # more items
  357. if type(t) is not list:
  358. unresolved_targets=[t]
  359. else:
  360. unresolved_targets=t
  361. resolved_targets = []
  362. for target in unresolved_targets:
  363. resolved_targets.append(os.path.normpath(ReplaceEnvs(target)))
  364. found_flag = False
  365. for target in resolved_targets:
  366. if os.path.exists(target):
  367. found_flag = True
  368. break
  369. if found_flag:
  370. found += 1
  371. else:
  372. missing.append(resolved_targets)
  373. # count the errors and warnings
  374. warn = 0
  375. error = 0
  376. exception = 0
  377. lines = self.output.split("\n")
  378. for line in lines:
  379. if line.find("sbs: warning:") != -1 or line.find("<warning") != -1:
  380. warn += 1
  381. elif line.find("sbs: error:") != -1 or line.find("<error") != -1:
  382. error += 1
  383. elif line.startswith("Traceback"):
  384. exception += 1
  385. # Check the output for required, forbidden and counted regexp matches
  386. self.outputok = True
  387. for expr in self.mustmatch_singleline + self.mustmatch:
  388. if not re.search(expr, self.output, re.MULTILINE):
  389. self.outputok = False
  390. print("OUTPUTMISMATCH: output did not match: {0}".format(expr))
  391. for expr in self.mustnotmatch_singleline + self.mustnotmatch:
  392. if re.search(expr, self.output, re.MULTILINE):
  393. self.outputok = False
  394. print("OUTPUTMISMATCH: output should not have matched: {0}".format(expr))
  395. for expr in self.mustmatch_multiline:
  396. if not re.search(expr, self.output, re.DOTALL):
  397. self.outputok = False
  398. print("OUTPUTMISMATCH: output did not match: {0}".format(expr))
  399. for expr in self.mustnotmatch_multiline:
  400. if re.search(expr, self.output, re.DOTALL):
  401. self.outputok = False
  402. print("OUTPUTMISMATCH: output should not have matched: {0}".format(expr))
  403. for (expr,num) in self.countmatch:
  404. expr_re = re.compile(expr)
  405. matchnum = len(expr_re.findall(self.output))
  406. if matchnum != num:
  407. print("OUTPUTMISMATCH: {0:d} matches occurred when {1:d} were expected: {2}".format(matchnum, num, expr))
  408. self.outputok = False
  409. # Ignore errors/warnings if they are set to (-1)
  410. if self.errors == (-1):
  411. self.errors = error
  412. if self.warnings == (-1):
  413. self.warnings= warn
  414. # all as expected?
  415. if self.missing == len(missing) \
  416. and self.warnings == warn \
  417. and self.errors == error \
  418. and self.exceptions == exception \
  419. and self.outputok:
  420. return True
  421. # something was wrong :-(
  422. if len(missing) != self.missing:
  423. print("MISSING: {0:d}, expected {1}".format(len(missing), self.missing))
  424. # Missing entries are lists containing either a single missing file, or multiple alternative
  425. # files that were all found to be missing when checked
  426. for entry in missing:
  427. for index,file in enumerate(entry):
  428. if index != len(entry)-1:
  429. suffix = " or"
  430. else:
  431. suffix = ""
  432. print("\t{0}{1}".format(file,suffix))
  433. if warn != self.warnings:
  434. print("WARNINGS: {0:d}, expected {1:d}".format(warn, self.warnings))
  435. if error != self.errors:
  436. print("ERRORS: {0:d}, expected {1:d}".format( error, self.errors))
  437. if exception != self.exceptions:
  438. print("EXCEPTIONS: {0:d}, expected {1:d}".format(exception, self.exceptions))
  439. return False
  440. def addbuildtargets(self, bldinfsourcepath, targetsuffixes):
  441. """Add targets that are under epoc32/build whose path
  442. can change based on an md5 hash of the path to the bld.inf.
  443. """
  444. fragment = BldInfFile.outputPathFragment(bldinfsourcepath)
  445. for t in targetsuffixes:
  446. if type(t) is not list:
  447. newt=self.sbs_build_dir+'/'+fragment+"/"+t
  448. self.targets.append(newt)
  449. else:
  450. self.targets.append([self.sbs_build_dir+'/'+fragment+"/"+x for x in t])
  451. return
  452. # derived class for tests that invoke some process, which have no log file and no makefile
  453. # e.g. unit tests
  454. class GenericSmokeTest(SmokeTest):
  455. def __init__(self):
  456. SmokeTest.__init__(self)
  457. def logfileOption(self):
  458. return ""
  459. def makefileOption(self):
  460. return ""
  461. def posttest(self):
  462. # dump the standard output to a log file
  463. dir = ReplaceEnvs(logDir)
  464. logfile = os.path.join(dir, self.name + ".log")
  465. try:
  466. if not os.path.exists(dir):
  467. os.makedirs(dir)
  468. file = open(logfile, "w")
  469. file.write(self.output)
  470. file.close()
  471. except:
  472. print("Could not save stdout in " + logfile)
  473. return False
  474. # do the base class things too
  475. return SmokeTest.posttest(self)
  476. # derived class for --check, --what and .whatlog tests - these all write to stdout, but may
  477. # not actually build anything
  478. class CheckWhatSmokeTest(SmokeTest):
  479. def __init__(self):
  480. SmokeTest.__init__(self)
  481. # regular expression match object to restrict comparisons to specific lines
  482. self.regexlinefilter = None
  483. # paths in --what output are tailored to the host OS, hence slashes are converted appropriately
  484. # .whatlog output is used verbatim from the build/TEM/EM output
  485. self.hostossensitive = True
  486. # Indicate whether output is expected to appear only once. If so, set it to True
  487. self.output_expected_only_once = False
  488. def posttest(self):
  489. outlines = self.output.splitlines()
  490. if self.output_expected_only_once:
  491. outlines_left = list(outlines)
  492. ok = True
  493. seen = []
  494. # check for lines that we expected to see, optionally filtered
  495. for line in self.stdout:
  496. if self.regexlinefilter and not self.regexlinefilter.match(line):
  497. continue
  498. line = ReplaceEnvs(line)
  499. if self.hostossensitive and self.onWindows:
  500. line = line.replace("/", "\\")
  501. if line in outlines:
  502. seen.append(line)
  503. if self.output_expected_only_once:
  504. outlines_left.remove(line)
  505. else:
  506. print("OUTPUT NOT FOUND: " + line)
  507. ok = False
  508. # and check for extra lines that we didn't expect, optionally filtered
  509. for line in outlines:
  510. if self.regexlinefilter and not self.regexlinefilter.match(line):
  511. continue
  512. if not line in seen:
  513. print("UNEXPECTED OUTPUT: " + line)
  514. ok = False
  515. # and check for lines that we expected to see only once
  516. if self.output_expected_only_once:
  517. for line in outlines_left:
  518. print("OUTPUT MORE THAN ONCE: " + line)
  519. ok = False
  520. # do the base class things too
  521. return (SmokeTest.posttest(self) and ok)
  522. # derived class for tests that also need to make sure that certain files
  523. # are NOT created - sort of anti-targets.
  524. class AntiTargetSmokeTest(SmokeTest):
  525. def __init__(self):
  526. SmokeTest.__init__(self)
  527. self.antitargets = []
  528. def pretest(self):
  529. """ Prepare for the test """
  530. # parent pretest first
  531. ok = SmokeTest.pretest(self)
  532. # remove all the anti-target files
  533. return (self.removeFiles(self.antitargets) and ok)
  534. def posttest(self):
  535. """ look for antitargets """
  536. ok = True
  537. for t in self.antitargets:
  538. tgt = os.path.normpath(ReplaceEnvs(t))
  539. if os.path.exists(tgt):
  540. print("UNWANTED "+ tgt)
  541. ok = False
  542. # do the base class things too
  543. return (SmokeTest.posttest(self) and ok)
  544. def addbuildantitargets(self, bldinfsourcepath, targetsuffixes):
  545. """Add targets that are under epoc32/build whose path
  546. can change based on an md5 hash of the path to the bld.inf.
  547. """
  548. fragment = BldInfFile.outputPathFragment(bldinfsourcepath)
  549. for t in targetsuffixes:
  550. if type(t) is not list:
  551. newt="$(EPOCROOT)/epoc32/build/"+fragment+"/"+t
  552. self.antitargets.append(newt)
  553. else:
  554. self.antitargets.append(["$(EPOCROOT)/epoc32/build/"+fragment+"/"+x for x in t])
  555. return
  556. # the end