fingerprints 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. #!/usr/bin/env python
  2. #
  3. # Fingerprint-based regression tested for the INET Framework.
  4. #
  5. # Accepts one or more CSV files with 4 columns: working directory,
  6. # options to opp_run, simulation time limit, expected fingerprint.
  7. # The program runs the INET simulations in the CSV files, and
  8. # reports fingerprint mismatches as FAILed test cases. To facilitate
  9. # test suite maintenance, the program also creates a new file (or files)
  10. # with the updated fingerprints.
  11. #
  12. # Implementation is based on Python's unit testing library, so it can be
  13. # integrated into larger test suites with minimal effort
  14. #
  15. # Author: Andras Varga
  16. #
  17. import argparse
  18. import copy
  19. import csv
  20. import glob
  21. import multiprocessing
  22. import os
  23. import re
  24. import subprocess
  25. import sys
  26. import threading
  27. import time
  28. import unittest
  29. from StringIO import StringIO
  30. inetRoot = os.path.abspath("../..")
  31. sep = ";" if sys.platform == 'win32' else ':'
  32. nedPath = inetRoot + "/src" + sep + inetRoot + "/examples" + sep + inetRoot + "/showcases" + sep + inetRoot + "/tutorials" + sep + inetRoot + "/tests/networks"
  33. inetLib = inetRoot + "/src/INET"
  34. opp_run = "opp_run"
  35. cpuTimeLimit = "300s"
  36. logFile = "test.out"
  37. extraOppRunArgs = ""
  38. exitCode = 0
  39. class FingerprintTestCaseGenerator():
  40. fileToSimulationsMap = {}
  41. def generateFromCSV(self, csvFileList, filterRegexList, excludeFilterRegexList, repeat):
  42. testcases = []
  43. for csvFile in csvFileList:
  44. simulations = self.parseSimulationsTable(csvFile)
  45. self.fileToSimulationsMap[csvFile] = simulations
  46. testcases.extend(self.generateFromDictList(simulations, filterRegexList, excludeFilterRegexList, repeat))
  47. return testcases
  48. def generateFromDictList(self, simulations, filterRegexList, excludeFilterRegexList, repeat):
  49. class StoreFingerprintCallback:
  50. def __init__(self, simulation):
  51. self.simulation = simulation
  52. def __call__(self, fingerprint):
  53. self.simulation['computedFingerprint'] = fingerprint
  54. class StoreExitcodeCallback:
  55. def __init__(self, simulation):
  56. self.simulation = simulation
  57. def __call__(self, exitcode):
  58. self.simulation['exitcode'] = exitcode
  59. testcases = []
  60. for simulation in simulations:
  61. title = simulation['wd'] + " " + simulation['args']
  62. if not filterRegexList or ['x' for regex in filterRegexList if re.search(regex, title)]: # if any regex matches title
  63. if not excludeFilterRegexList or not ['x' for regex in excludeFilterRegexList if re.search(regex, title)]: # if NO exclude-regex matches title
  64. testcases.append(FingerprintTestCase(title, simulation['file'], simulation['wd'], simulation['args'],
  65. simulation['simtimelimit'], simulation['fingerprint'], StoreFingerprintCallback(simulation), StoreExitcodeCallback(simulation), repeat))
  66. return testcases
  67. def commentRemover(self, csvData):
  68. p = re.compile(' *#.*$')
  69. for line in csvData:
  70. yield p.sub('',line)
  71. # parse the CSV into a list of dicts
  72. def parseSimulationsTable(self, csvFile):
  73. simulations = []
  74. f = open(csvFile, 'rb')
  75. csvReader = csv.reader(self.commentRemover(f), delimiter=',', quotechar='"', skipinitialspace=True)
  76. for fields in csvReader:
  77. if len(fields) == 0:
  78. continue # empty line
  79. if len(fields) != 4:
  80. raise Exception("Line " + str(csvReader.line_num) + " must contain 4 items, but contains " + str(len(fields)) + ": " + '"' + '", "'.join(fields) + '"')
  81. simulations.append({'file': csvFile, 'line' : csvReader.line_num, 'wd': fields[0], 'args': fields[1], 'simtimelimit': fields[2], 'fingerprint': fields[3]})
  82. f.close()
  83. return simulations
  84. def writeUpdatedCSVFiles(self):
  85. for csvFile, simulations in self.fileToSimulationsMap.iteritems():
  86. updatedContents = self.formatUpdatedSimulationsTable(csvFile, simulations)
  87. if updatedContents:
  88. updatedFile = csvFile + ".UPDATED"
  89. ff = open(updatedFile, 'w')
  90. ff.write(updatedContents)
  91. ff.close()
  92. print "Check " + updatedFile + " for updated fingerprints"
  93. def writeFailedCSVFiles(self):
  94. for csvFile, simulations in self.fileToSimulationsMap.iteritems():
  95. failedContents = self.formatFailedSimulationsTable(csvFile, simulations)
  96. if failedContents:
  97. failedFile = csvFile + ".FAILED"
  98. ff = open(failedFile, 'w')
  99. ff.write(failedContents)
  100. ff.close()
  101. print "Check " + failedFile + " for failed fingerprints"
  102. def writeErrorCSVFiles(self):
  103. for csvFile, simulations in self.fileToSimulationsMap.iteritems():
  104. errorContents = self.formatErrorSimulationsTable(csvFile, simulations)
  105. if errorContents:
  106. errorFile = csvFile + ".ERROR"
  107. ff = open(errorFile, 'w')
  108. ff.write(errorContents)
  109. ff.close()
  110. print "Check " + errorFile + " for errors"
  111. def escape(self, str):
  112. if re.search(r'[\r\n\",]', str):
  113. str = '"' + re.sub('"','""',str) + '"'
  114. return str
  115. def formatUpdatedSimulationsTable(self, csvFile, simulations):
  116. # if there is a computed fingerprint, print that instead of existing one
  117. ff = open(csvFile, 'r')
  118. lines = ff.readlines()
  119. ff.close()
  120. lines.insert(0, '') # csv line count is 1..n; insert an empty item --> lines[1] is the first line
  121. containsComputedFingerprint = False
  122. for simulation in simulations:
  123. if 'computedFingerprint' in simulation:
  124. oldFingerprint = simulation['fingerprint']
  125. newFingerprint = simulation['computedFingerprint']
  126. oldFpList = oldFingerprint.split(' ')
  127. if '/' in newFingerprint:
  128. # keep old omnetpp4 fp
  129. keepFpList = [elem for elem in oldFpList if not '/' in elem]
  130. if keepFpList:
  131. newFingerprint = ' '.join(keepFpList) + ' ' + newFingerprint
  132. else:
  133. # keep all old omnetpp5 fp
  134. keepFpList = [elem for elem in oldFpList if '/' in elem]
  135. if keepFpList:
  136. newFingerprint = newFingerprint + ' ' + ' '.join(keepFpList)
  137. if ',' in newFingerprint:
  138. newFingerprint = '"' + newFingerprint + '"'
  139. containsComputedFingerprint = True
  140. line = simulation['line']
  141. pattern = "\\b" + oldFingerprint + "\\b"
  142. (newLine, cnt) = re.subn(pattern, newFingerprint, lines[line])
  143. if (cnt == 1):
  144. lines[line] = newLine
  145. else:
  146. print "ERROR: Cannot replace fingerprint '%s' to '%s' at '%s' line %d:\n %s" % (oldFingerprint, newFingerprint, csvFile, line, lines[line])
  147. return ''.join(lines) if containsComputedFingerprint else None
  148. def formatFailedSimulationsTable(self, csvFile, simulations):
  149. ff = open(csvFile, 'r')
  150. lines = ff.readlines()
  151. ff.close()
  152. lines.insert(0, '') # csv line count is 1..n; insert an empty item --> lines[1] is the first line
  153. result = []
  154. containsFailures = False
  155. for simulation in simulations:
  156. if 'computedFingerprint' in simulation:
  157. oldFingerprint = simulation['fingerprint']
  158. newFingerprint = simulation['computedFingerprint']
  159. if oldFingerprint != newFingerprint:
  160. if not containsFailures:
  161. containsFailures = True
  162. result.append("# Failures:\n");
  163. result.append(lines[simulation['line']])
  164. return ''.join(result) if containsFailures else None
  165. def formatErrorSimulationsTable(self, csvFile, simulations):
  166. ff = open(csvFile, 'r')
  167. lines = ff.readlines()
  168. ff.close()
  169. lines.insert(0, '') # csv line count is 1..n; insert an empty item --> lines[1] is the first line
  170. result = []
  171. containsErrors = False
  172. for simulation in simulations:
  173. if 'exitcode' in simulation and simulation['exitcode'] != 0:
  174. if not containsErrors:
  175. containsErrors = True
  176. result.append("# Errors:\n");
  177. result.append(lines[simulation['line']])
  178. return ''.join(result) if containsErrors else None
  179. class SimulationResult:
  180. def __init__(self, command, workingdir, exitcode, errorMsg=None, isFingerprintOK=None,
  181. computedFingerprint=None, simulatedTime=None, numEvents=None, elapsedTime=None, cpuTimeLimitReached=None):
  182. self.command = command
  183. self.workingdir = workingdir
  184. self.exitcode = exitcode
  185. self.errorMsg = errorMsg
  186. self.isFingerprintOK = isFingerprintOK
  187. self.computedFingerprint = computedFingerprint
  188. self.simulatedTime = simulatedTime
  189. self.numEvents = numEvents
  190. self.elapsedTime = elapsedTime
  191. self.cpuTimeLimitReached = cpuTimeLimitReached
  192. class SimulationTestCase(unittest.TestCase):
  193. def runSimulation(self, title, command, workingdir, resultdir):
  194. global logFile
  195. ensure_dir(workingdir + "/results")
  196. # run the program and log the output
  197. t0 = time.time()
  198. (exitcode, out) = self.runProgram(command, workingdir)
  199. elapsedTime = time.time() - t0
  200. FILE = open(logFile, "a")
  201. FILE.write("------------------------------------------------------\n"
  202. + "Running: " + title + "\n\n"
  203. + "$ cd " + workingdir + "\n"
  204. + "$ " + command + "\n\n"
  205. + out.strip() + "\n\n"
  206. + "Exit code: " + str(exitcode) + "\n"
  207. + "Elapsed time: " + str(round(elapsedTime,2)) + "s\n\n")
  208. FILE.close()
  209. FILE = open(resultdir + "/test.out", "w")
  210. FILE.write("------------------------------------------------------\n"
  211. + "Running: " + title + "\n\n"
  212. + "$ cd " + workingdir + "\n"
  213. + "$ " + command + "\n\n"
  214. + out.strip() + "\n\n"
  215. + "Exit code: " + str(exitcode) + "\n"
  216. + "Elapsed time: " + str(round(elapsedTime,2)) + "s\n\n")
  217. FILE.close()
  218. result = SimulationResult(command, workingdir, exitcode, elapsedTime=elapsedTime)
  219. # process error messages
  220. errorLines = re.findall("<!>.*", out, re.M)
  221. errorMsg = ""
  222. for err in errorLines:
  223. err = err.strip()
  224. if re.search("Fingerprint", err):
  225. if re.search("successfully", err):
  226. result.isFingerprintOK = True
  227. else:
  228. m = re.search("(computed|calculated): ([-a-zA-Z0-9]+(/[a-z0]+)?)", err)
  229. if m:
  230. result.isFingerprintOK = False
  231. result.computedFingerprint = m.group(2)
  232. else:
  233. raise Exception("Cannot parse fingerprint-related error message: " + err)
  234. else:
  235. errorMsg += "\n" + err
  236. if re.search("CPU time limit reached", err):
  237. result.cpuTimeLimitReached = True
  238. m = re.search("at event #([0-9]+), t=([0-9]*(\\.[0-9]+)?)", err)
  239. if m:
  240. result.numEvents = int(m.group(1))
  241. result.simulatedTime = float(m.group(2))
  242. result.errormsg = errorMsg.strip()
  243. return result
  244. def runProgram(self, command, workingdir):
  245. process = subprocess.Popen(command, shell=True, cwd=workingdir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  246. out = process.communicate()[0]
  247. out = re.sub("\r", "", out)
  248. return (process.returncode, out)
  249. class FingerprintTestCase(SimulationTestCase):
  250. def __init__(self, title, csvFile, wd, args, simtimelimit, fingerprint, storeFingerprintCallback, storeExitcodeCallback, repeat):
  251. SimulationTestCase.__init__(self)
  252. self.title = title
  253. self.csvFile = csvFile
  254. self.wd = wd
  255. self.args = args
  256. self.simtimelimit = simtimelimit
  257. self.fingerprint = fingerprint
  258. self.storeFingerprintCallback = storeFingerprintCallback
  259. self.storeExitcodeCallback = storeExitcodeCallback
  260. self.repeat = repeat
  261. def runTest(self):
  262. # CPU time limit is a safety guard: fingerprint checks shouldn't take forever
  263. global inetRoot, opp_run, nedPath, cpuTimeLimit, extraOppRunArgs
  264. # run the simulation
  265. workingdir = _iif(self.wd.startswith('/'), inetRoot + "/" + self.wd, self.wd)
  266. wdname = '' + self.wd + ' ' + self.args
  267. wdname = re.sub('/', '_', wdname);
  268. wdname = re.sub('[\W]+', '_', wdname);
  269. resultdir = os.path.abspath(".") + "/results/" + self.csvFile + "/" + wdname;
  270. if not os.path.exists(resultdir):
  271. try:
  272. os.makedirs(resultdir)
  273. except OSError:
  274. pass
  275. command = opp_run + " -n " + nedPath + " -l " + inetLib + " -u Cmdenv " + self.args + \
  276. _iif(self.simtimelimit != "", " --sim-time-limit=" + self.simtimelimit, "") + \
  277. " \"--fingerprint=" + self.fingerprint + "\" --cpu-time-limit=" + cpuTimeLimit + \
  278. " --vector-recording=false --scalar-recording=true" + \
  279. " --result-dir=" + resultdir + \
  280. extraOppRunArgs;
  281. # print "COMMAND: " + command + '\n'
  282. anyFingerprintBad = False;
  283. computedFingerprints = set()
  284. for rep in range(self.repeat):
  285. result = self.runSimulation(self.title, command, workingdir, resultdir)
  286. # process the result
  287. # note: fingerprint mismatch is technically NOT an error in 4.2 or before! (exitcode==0)
  288. self.storeExitcodeCallback(result.exitcode)
  289. if result.exitcode != 0:
  290. raise Exception("runtime error:" + result.errormsg)
  291. elif result.cpuTimeLimitReached:
  292. raise Exception("cpu time limit exceeded")
  293. elif result.simulatedTime == 0 and self.simtimelimit != '0s':
  294. raise Exception("zero time simulated")
  295. elif result.isFingerprintOK is None:
  296. raise Exception("other")
  297. elif result.isFingerprintOK == False:
  298. computedFingerprints.add(result.computedFingerprint)
  299. anyFingerprintBad = True
  300. else:
  301. # fingerprint OK:
  302. computedFingerprints.add(self.fingerprint)
  303. # pass
  304. if anyFingerprintBad:
  305. self.storeFingerprintCallback(",".join(computedFingerprints))
  306. assert False, "some fingerprint mismatch; actual " + " '" + ",".join(computedFingerprints) +"'"
  307. def __str__(self):
  308. return self.title
  309. class ThreadSafeIter:
  310. """Takes an iterator/generator and makes it thread-safe by
  311. serializing call to the `next` method of given iterator/generator.
  312. """
  313. def __init__(self, it):
  314. self.it = it
  315. self.lock = threading.Lock()
  316. def __iter__(self):
  317. return self
  318. def next(self):
  319. with self.lock:
  320. return self.it.next()
  321. class ThreadedTestSuite(unittest.BaseTestSuite):
  322. """ runs toplevel tests in n threads
  323. """
  324. # How many test process at the time.
  325. thread_count = multiprocessing.cpu_count()
  326. def run(self, result):
  327. it = ThreadSafeIter(self.__iter__())
  328. result.buffered = True
  329. threads = []
  330. for i in range(self.thread_count):
  331. # Create self.thread_count number of threads that together will
  332. # cooperate removing every ip in the list. Each thread will do the
  333. # job as fast as it can.
  334. t = threading.Thread(target=self.runThread, args=(result, it))
  335. t.daemon = True
  336. t.start()
  337. threads.append(t)
  338. # Wait until all the threads are done. .join() is blocking.
  339. #for t in threads:
  340. # t.join()
  341. runApp = True
  342. while runApp and threading.active_count() > 1:
  343. try:
  344. time.sleep(0.1)
  345. except KeyboardInterrupt:
  346. runApp = False
  347. return result
  348. def runThread(self, result, it):
  349. tresult = result.startThread()
  350. for test in it:
  351. if result.shouldStop:
  352. break
  353. test(tresult)
  354. tresult.stopThread()
  355. class ThreadedTestResult(unittest.TestResult):
  356. """TestResult with threads
  357. """
  358. def __init__(self, stream=None, descriptions=None, verbosity=None):
  359. super(ThreadedTestResult, self).__init__()
  360. self.parent = None
  361. self.lock = threading.Lock()
  362. def startThread(self):
  363. ret = copy.copy(self)
  364. ret.parent = self
  365. return ret
  366. def stop():
  367. super(ThreadedTestResult, self).stop()
  368. if self.parent:
  369. self.parent.stop()
  370. def stopThread(self):
  371. if self.parent == None:
  372. return 0
  373. self.parent.testsRun += self.testsRun
  374. return 1
  375. def startTest(self, test):
  376. "Called when the given test is about to be run"
  377. super(ThreadedTestResult, self).startTest(test)
  378. self.oldstream = self.stream
  379. self.stream = StringIO()
  380. def stopTest(self, test):
  381. """Called when the given test has been run"""
  382. super(ThreadedTestResult, self).stopTest(test)
  383. out = self.stream.getvalue()
  384. with self.lock:
  385. self.stream = self.oldstream
  386. self.stream.write(out)
  387. #
  388. # Copy/paste of TextTestResult, with minor modifications in the output:
  389. # we want to print the error text after ERROR and FAIL, but we don't want
  390. # to print stack traces.
  391. #
  392. class SimulationTextTestResult(ThreadedTestResult):
  393. """A test result class that can print formatted text results to a stream.
  394. Used by TextTestRunner.
  395. """
  396. separator1 = '=' * 70
  397. separator2 = '-' * 70
  398. def __init__(self, stream, descriptions, verbosity):
  399. super(SimulationTextTestResult, self).__init__()
  400. self.stream = stream
  401. self.showAll = verbosity > 1
  402. self.dots = verbosity == 1
  403. self.descriptions = descriptions
  404. def getDescription(self, test):
  405. doc_first_line = test.shortDescription()
  406. if self.descriptions and doc_first_line:
  407. return '\n'.join((str(test), doc_first_line))
  408. else:
  409. return str(test)
  410. def startTest(self, test):
  411. super(SimulationTextTestResult, self).startTest(test)
  412. if self.showAll:
  413. self.stream.write(self.getDescription(test))
  414. self.stream.write(" ... ")
  415. self.stream.flush()
  416. def addSuccess(self, test):
  417. super(SimulationTextTestResult, self).addSuccess(test)
  418. if self.showAll:
  419. self.stream.write(": PASS\n")
  420. elif self.dots:
  421. self.stream.write('.')
  422. self.stream.flush()
  423. def addError(self, test, err):
  424. # modified
  425. super(SimulationTextTestResult, self).addError(test, err)
  426. self.errors[-1] = (test, err[1]) # super class method inserts stack trace; we don't need that, so overwrite it
  427. if self.showAll:
  428. (cause, detail) = self._splitMsg(err[1])
  429. self.stream.write(": ERROR (%s)\n" % cause)
  430. if detail:
  431. self.stream.write(detail)
  432. self.stream.write("\n")
  433. elif self.dots:
  434. self.stream.write('E')
  435. self.stream.flush()
  436. global exitCode
  437. exitCode = 1 #"there were errors or failures"
  438. def addFailure(self, test, err):
  439. # modified
  440. super(SimulationTextTestResult, self).addFailure(test, err)
  441. self.failures[-1] = (test, err[1]) # super class method inserts stack trace; we don't need that, so overwrite it
  442. if self.showAll:
  443. (cause, detail) = self._splitMsg(err[1])
  444. self.stream.write(": FAIL (%s)\n" % cause)
  445. if detail:
  446. self.stream.write(detail)
  447. self.stream.write("\n")
  448. elif self.dots:
  449. self.stream.write('F')
  450. self.stream.flush()
  451. global exitCode
  452. exitCode = 1 #"there were errors or failures"
  453. def addSkip(self, test, reason):
  454. super(SimulationTextTestResult, self).addSkip(test, reason)
  455. if self.showAll:
  456. self.stream.write(": skipped {0!r}".format(reason))
  457. self.stream.write("\n")
  458. elif self.dots:
  459. self.stream.write("s")
  460. self.stream.flush()
  461. def addExpectedFailure(self, test, err):
  462. super(SimulationTextTestResult, self).addExpectedFailure(test, err)
  463. if self.showAll:
  464. self.stream.write(": expected failure\n")
  465. elif self.dots:
  466. self.stream.write("x")
  467. self.stream.flush()
  468. def addUnexpectedSuccess(self, test):
  469. super(SimulationTextTestResult, self).addUnexpectedSuccess(test)
  470. if self.showAll:
  471. self.stream.write(": unexpected success\n")
  472. elif self.dots:
  473. self.stream.write("u")
  474. self.stream.flush()
  475. def printErrors(self):
  476. # modified
  477. if self.dots or self.showAll:
  478. self.stream.write("\n")
  479. self.printErrorList('Errors', self.errors)
  480. self.printErrorList('Failures', self.failures)
  481. def printErrorList(self, flavour, errors):
  482. # modified
  483. if errors:
  484. self.stream.write("%s:\n" % flavour)
  485. for test, err in errors:
  486. self.stream.write(" %s (%s)\n" % (self.getDescription(test), self._splitMsg(err)[0]))
  487. def _splitMsg(self, msg):
  488. cause = str(msg)
  489. detail = None
  490. if cause.count(': '):
  491. (cause, detail) = cause.split(': ',1)
  492. return (cause, detail)
  493. def _iif(cond,t,f):
  494. return t if cond else f
  495. def ensure_dir(f):
  496. if not os.path.exists(f):
  497. os.makedirs(f)
  498. if __name__ == "__main__":
  499. parser = argparse.ArgumentParser(description='Run the fingerprint tests specified in the input files.')
  500. parser.add_argument('testspecfiles', nargs='*', metavar='testspecfile', help='CSV files that contain the tests to run (default: *.csv). Expected CSV file columns: workingdir, args, simtimelimit, fingerprint')
  501. parser.add_argument('-m', '--match', action='append', metavar='regex', help='Line filter: a line (more precisely, workingdir+SPACE+args) must match any of the regular expressions in order for that test case to be run')
  502. parser.add_argument('-x', '--exclude', action='append', metavar='regex', help='Negative line filter: a line (more precisely, workingdir+SPACE+args) must NOT match any of the regular expressions in order for that test case to be run')
  503. parser.add_argument('-t', '--threads', type=int, default=multiprocessing.cpu_count(), help='number of parallel threads (default: number of CPUs, currently '+str(multiprocessing.cpu_count())+')')
  504. parser.add_argument('-r', '--repeat', type=int, default=1, help='number of repeating each test (default: 1)')
  505. parser.add_argument('-a', '--oppargs', action='append', metavar='oppargs', nargs='*', help='extra opp_run arguments without leading "--", you can terminate list with " -- " ')
  506. args = parser.parse_args()
  507. if os.path.isfile(logFile):
  508. FILE = open(logFile, "w")
  509. FILE.close()
  510. if not args.testspecfiles:
  511. args.testspecfiles = glob.glob('*.csv')
  512. if args.oppargs:
  513. for oppArgList in args.oppargs:
  514. for oppArg in oppArgList:
  515. extraOppRunArgs += " --" + oppArg
  516. generator = FingerprintTestCaseGenerator()
  517. testcases = generator.generateFromCSV(args.testspecfiles, args.match, args.exclude, args.repeat)
  518. testSuite = ThreadedTestSuite()
  519. testSuite.addTests(testcases)
  520. testSuite.thread_count = args.threads
  521. testSuite.repeat = args.repeat
  522. testRunner = unittest.TextTestRunner(stream=sys.stdout, verbosity=9, resultclass=SimulationTextTestResult)
  523. testRunner.run(testSuite)
  524. print
  525. generator.writeUpdatedCSVFiles()
  526. generator.writeErrorCSVFiles()
  527. generator.writeFailedCSVFiles()
  528. print "Log has been saved to %s" % logFile
  529. exit(exitCode)