specs.nim 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. #
  2. #
  3. # Nim Tester
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. import sequtils, parseutils, strutils, os, streams, parsecfg
  10. var compilerPrefix* = findExe("nim")
  11. let isTravis* = existsEnv("TRAVIS")
  12. let isAppVeyor* = existsEnv("APPVEYOR")
  13. let isAzure* = existsEnv("TF_BUILD")
  14. var skips*: seq[string]
  15. type
  16. TTestAction* = enum
  17. actionRun = "run"
  18. actionCompile = "compile"
  19. actionReject = "reject"
  20. TOutputCheck* = enum
  21. ocIgnore = "ignore"
  22. ocEqual = "equal"
  23. ocSubstr = "substr"
  24. TResultEnum* = enum
  25. reNimcCrash, # nim compiler seems to have crashed
  26. reMsgsDiffer, # error messages differ
  27. reFilesDiffer, # expected and given filenames differ
  28. reLinesDiffer, # expected and given line numbers differ
  29. reOutputsDiffer,
  30. reExitcodesDiffer,
  31. reTimeout,
  32. reInvalidPeg,
  33. reCodegenFailure,
  34. reCodeNotFound,
  35. reExeNotFound,
  36. reInstallFailed # package installation failed
  37. reBuildFailed # package building failed
  38. reDisabled, # test is disabled
  39. reJoined, # test is disabled because it was joined into the megatest
  40. reSuccess # test was successful
  41. reInvalidSpec # test had problems to parse the spec
  42. TTarget* = enum
  43. targetC = "C"
  44. targetCpp = "C++"
  45. targetObjC = "ObjC"
  46. targetJS = "JS"
  47. TSpec* = object
  48. action*: TTestAction
  49. file*, cmd*: string
  50. input*: string
  51. outputCheck*: TOutputCheck
  52. sortoutput*: bool
  53. output*: string
  54. line*, column*: int
  55. tfile*: string
  56. tline*, tcolumn*: int
  57. exitCode*: int
  58. msg*: string
  59. ccodeCheck*: string
  60. maxCodeSize*: int
  61. err*: TResultEnum
  62. targets*: set[TTarget]
  63. matrix*: seq[string]
  64. nimout*: string
  65. parseErrors*: string # when the spec definition is invalid, this is not empty.
  66. unjoinable*: bool
  67. useValgrind*: bool
  68. timeout*: float # in seconds, fractions possible,
  69. # but don't rely on much precision
  70. proc getCmd*(s: TSpec): string =
  71. if s.cmd.len == 0:
  72. result = compilerPrefix & " $target --hints:on -d:testing --nimblePath:tests/deps $options $file"
  73. else:
  74. result = s.cmd
  75. const
  76. targetToExt*: array[TTarget, string] = ["nim.c", "nim.cpp", "nim.m", "js"]
  77. targetToCmd*: array[TTarget, string] = ["c", "cpp", "objc", "js"]
  78. when not declared(parseCfgBool):
  79. # candidate for the stdlib:
  80. proc parseCfgBool(s: string): bool =
  81. case normalize(s)
  82. of "y", "yes", "true", "1", "on": result = true
  83. of "n", "no", "false", "0", "off": result = false
  84. else: raise newException(ValueError, "cannot interpret as a bool: " & s)
  85. proc extractSpec(filename: string): string =
  86. const tripleQuote = "\"\"\""
  87. var x = readFile(filename).string
  88. var a = x.find(tripleQuote)
  89. var b = x.find(tripleQuote, a+3)
  90. # look for """ only in the first section
  91. if a >= 0 and b > a and a < 40:
  92. result = x.substr(a+3, b-1).replace("'''", tripleQuote)
  93. else:
  94. #echo "warning: file does not contain spec: " & filename
  95. result = ""
  96. when not defined(nimhygiene):
  97. {.pragma: inject.}
  98. proc parseTargets*(value: string): set[TTarget] =
  99. for v in value.normalize.splitWhitespace:
  100. case v
  101. of "c": result.incl(targetC)
  102. of "cpp", "c++": result.incl(targetCpp)
  103. of "objc": result.incl(targetObjC)
  104. of "js": result.incl(targetJS)
  105. else: echo "target ignored: " & v
  106. proc addLine*(self: var string; a: string) =
  107. self.add a
  108. self.add "\n"
  109. proc addLine*(self: var string; a,b: string) =
  110. self.add a
  111. self.add b
  112. self.add "\n"
  113. proc initSpec*(filename: string): TSpec =
  114. result.file = filename
  115. proc parseSpec*(filename: string): TSpec =
  116. result.file = filename
  117. let specStr = extractSpec(filename)
  118. var ss = newStringStream(specStr)
  119. var p: CfgParser
  120. open(p, ss, filename, 1)
  121. while true:
  122. var e = next(p)
  123. case e.kind
  124. of cfgKeyValuePair:
  125. case normalize(e.key)
  126. of "action":
  127. case e.value.normalize
  128. of "compile":
  129. result.action = actionCompile
  130. of "run":
  131. result.action = actionRun
  132. of "reject":
  133. result.action = actionReject
  134. else:
  135. result.parseErrors.addLine "cannot interpret as action: ", e.value
  136. of "file":
  137. if result.msg.len == 0 and result.nimout.len == 0:
  138. result.parseErrors.addLine "errormsg or msg needs to be specified before file"
  139. result.file = e.value
  140. of "line":
  141. if result.msg.len == 0 and result.nimout.len == 0:
  142. result.parseErrors.addLine "errormsg, msg or nimout needs to be specified before line"
  143. discard parseInt(e.value, result.line)
  144. of "column":
  145. if result.msg.len == 0 and result.nimout.len == 0:
  146. result.parseErrors.addLine "errormsg or msg needs to be specified before column"
  147. discard parseInt(e.value, result.column)
  148. of "tfile":
  149. result.tfile = e.value
  150. of "tline":
  151. discard parseInt(e.value, result.tline)
  152. of "tcolumn":
  153. discard parseInt(e.value, result.tcolumn)
  154. of "output":
  155. if result.outputCheck != ocSubstr:
  156. result.outputCheck = ocEqual
  157. result.output = strip(e.value)
  158. of "input":
  159. result.input = e.value
  160. of "outputsub":
  161. result.outputCheck = ocSubstr
  162. result.output = strip(e.value)
  163. of "sortoutput":
  164. try:
  165. result.sortoutput = parseCfgBool(e.value)
  166. except:
  167. result.parseErrors.addLine getCurrentExceptionMsg()
  168. of "exitcode":
  169. discard parseInt(e.value, result.exitCode)
  170. result.action = actionRun
  171. of "msg":
  172. result.msg = e.value
  173. if result.action != actionRun:
  174. result.action = actionCompile
  175. of "errormsg", "errmsg":
  176. result.msg = e.value
  177. result.action = actionReject
  178. of "nimout":
  179. result.nimout = e.value
  180. of "joinable":
  181. result.unjoinable = not parseCfgBool(e.value)
  182. of "valgrind":
  183. when defined(linux) and sizeof(int) == 8:
  184. result.useValgrind = parseCfgBool(e.value)
  185. result.unjoinable = true
  186. if result.useValgrind:
  187. result.outputCheck = ocSubstr
  188. else:
  189. # Windows lacks valgrind. Silly OS.
  190. # Valgrind only supports OSX <= 17.x
  191. result.useValgrind = false
  192. of "disabled":
  193. case e.value.normalize
  194. of "y", "yes", "true", "1", "on": result.err = reDisabled
  195. of "n", "no", "false", "0", "off": discard
  196. of "win", "windows":
  197. when defined(windows): result.err = reDisabled
  198. of "linux":
  199. when defined(linux): result.err = reDisabled
  200. of "bsd":
  201. when defined(bsd): result.err = reDisabled
  202. of "macosx":
  203. when defined(macosx): result.err = reDisabled
  204. of "unix":
  205. when defined(unix): result.err = reDisabled
  206. of "posix":
  207. when defined(posix): result.err = reDisabled
  208. of "travis":
  209. if isTravis: result.err = reDisabled
  210. of "appveyor":
  211. if isAppVeyor: result.err = reDisabled
  212. of "azure":
  213. if isAzure: result.err = reDisabled
  214. of "32bit":
  215. if sizeof(int) == 4:
  216. result.err = reDisabled
  217. of "freebsd":
  218. when defined(freebsd): result.err = reDisabled
  219. of "arm64":
  220. when defined(arm64): result.err = reDisabled
  221. else:
  222. result.parseErrors.addLine "cannot interpret as a bool: ", e.value
  223. of "cmd":
  224. if e.value.startsWith("nim "):
  225. result.cmd = compilerPrefix & e.value[3..^1]
  226. else:
  227. result.cmd = e.value
  228. of "ccodecheck":
  229. result.ccodeCheck = e.value
  230. of "maxcodesize":
  231. discard parseInt(e.value, result.maxCodeSize)
  232. of "timeout":
  233. try:
  234. result.timeout = parseFloat(e.value)
  235. except ValueError:
  236. result.parseErrors.addLine "cannot interpret as a float: ", e.value
  237. of "target", "targets":
  238. for v in e.value.normalize.splitWhitespace:
  239. case v
  240. of "c":
  241. result.targets.incl(targetC)
  242. of "cpp", "c++":
  243. result.targets.incl(targetCpp)
  244. of "objc":
  245. result.targets.incl(targetObjC)
  246. of "js":
  247. result.targets.incl(targetJS)
  248. else:
  249. result.parseErrors.addLine "cannot interpret as a target: ", e.value
  250. of "matrix":
  251. for v in e.value.split(';'):
  252. result.matrix.add(v.strip)
  253. else:
  254. result.parseErrors.addLine "invalid key for test spec: ", e.key
  255. of cfgSectionStart:
  256. result.parseErrors.addLine "section ignored: ", e.section
  257. of cfgOption:
  258. result.parseErrors.addLine "command ignored: ", e.key & ": " & e.value
  259. of cfgError:
  260. result.parseErrors.addLine e.msg
  261. of cfgEof:
  262. break
  263. close(p)
  264. if skips.anyIt(it in result.file):
  265. result.err = reDisabled