specs.nim 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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. nimout*: string
  64. parseErrors*: string # when the spec definition is invalid, this is not empty.
  65. unjoinable*: bool
  66. useValgrind*: bool
  67. timeout*: float # in seconds, fractions possible,
  68. # but don't rely on much precision
  69. proc getCmd*(s: TSpec): string =
  70. if s.cmd.len == 0:
  71. result = compilerPrefix & " $target --hints:on -d:testing --nimblePath:tests/deps $options $file"
  72. else:
  73. result = s.cmd
  74. const
  75. targetToExt*: array[TTarget, string] = ["nim.c", "nim.cpp", "nim.m", "js"]
  76. targetToCmd*: array[TTarget, string] = ["c", "cpp", "objc", "js"]
  77. when not declared(parseCfgBool):
  78. # candidate for the stdlib:
  79. proc parseCfgBool(s: string): bool =
  80. case normalize(s)
  81. of "y", "yes", "true", "1", "on": result = true
  82. of "n", "no", "false", "0", "off": result = false
  83. else: raise newException(ValueError, "cannot interpret as a bool: " & s)
  84. proc extractSpec(filename: string): string =
  85. const tripleQuote = "\"\"\""
  86. var x = readFile(filename).string
  87. var a = x.find(tripleQuote)
  88. var b = x.find(tripleQuote, a+3)
  89. # look for """ only in the first section
  90. if a >= 0 and b > a and a < 40:
  91. result = x.substr(a+3, b-1).replace("'''", tripleQuote)
  92. else:
  93. #echo "warning: file does not contain spec: " & filename
  94. result = ""
  95. when not defined(nimhygiene):
  96. {.pragma: inject.}
  97. proc parseTargets*(value: string): set[TTarget] =
  98. for v in value.normalize.splitWhitespace:
  99. case v
  100. of "c": result.incl(targetC)
  101. of "cpp", "c++": result.incl(targetCpp)
  102. of "objc": result.incl(targetObjC)
  103. of "js": result.incl(targetJS)
  104. else: echo "target ignored: " & v
  105. proc addLine*(self: var string; a: string) =
  106. self.add a
  107. self.add "\n"
  108. proc addLine*(self: var string; a,b: string) =
  109. self.add a
  110. self.add b
  111. self.add "\n"
  112. proc initSpec*(filename: string): TSpec =
  113. result.file = filename
  114. proc parseSpec*(filename: string): TSpec =
  115. result.file = filename
  116. let specStr = extractSpec(filename)
  117. var ss = newStringStream(specStr)
  118. var p: CfgParser
  119. open(p, ss, filename, 1)
  120. while true:
  121. var e = next(p)
  122. case e.kind
  123. of cfgKeyValuePair:
  124. case normalize(e.key)
  125. of "action":
  126. case e.value.normalize
  127. of "compile":
  128. result.action = actionCompile
  129. of "run":
  130. result.action = actionRun
  131. of "reject":
  132. result.action = actionReject
  133. else:
  134. result.parseErrors.addLine "cannot interpret as action: ", e.value
  135. of "file":
  136. if result.msg.len == 0 and result.nimout.len == 0:
  137. result.parseErrors.addLine "errormsg or msg needs to be specified before file"
  138. result.file = e.value
  139. of "line":
  140. if result.msg.len == 0 and result.nimout.len == 0:
  141. result.parseErrors.addLine "errormsg, msg or nimout needs to be specified before line"
  142. discard parseInt(e.value, result.line)
  143. of "column":
  144. if result.msg.len == 0 and result.nimout.len == 0:
  145. result.parseErrors.addLine "errormsg or msg needs to be specified before column"
  146. discard parseInt(e.value, result.column)
  147. of "tfile":
  148. result.tfile = e.value
  149. of "tline":
  150. discard parseInt(e.value, result.tline)
  151. of "tcolumn":
  152. discard parseInt(e.value, result.tcolumn)
  153. of "output":
  154. result.outputCheck = ocEqual
  155. result.output = strip(e.value)
  156. of "input":
  157. result.input = e.value
  158. of "outputsub":
  159. result.outputCheck = ocSubstr
  160. result.output = strip(e.value)
  161. of "sortoutput":
  162. try:
  163. result.sortoutput = parseCfgBool(e.value)
  164. except:
  165. result.parseErrors.addLine getCurrentExceptionMsg()
  166. of "exitcode":
  167. discard parseInt(e.value, result.exitCode)
  168. result.action = actionRun
  169. of "msg":
  170. result.msg = e.value
  171. if result.action != actionRun:
  172. result.action = actionCompile
  173. of "errormsg", "errmsg":
  174. result.msg = e.value
  175. result.action = actionReject
  176. of "nimout":
  177. result.nimout = e.value
  178. of "joinable":
  179. result.unjoinable = not parseCfgBool(e.value)
  180. of "valgrind":
  181. when defined(linux) and sizeof(int) == 8:
  182. result.useValgrind = parseCfgBool(e.value)
  183. result.unjoinable = true
  184. else:
  185. # Windows lacks valgrind. Silly OS.
  186. # Valgrind only supports OSX <= 17.x
  187. result.useValgrind = false
  188. of "disabled":
  189. case e.value.normalize
  190. of "y", "yes", "true", "1", "on": result.err = reDisabled
  191. of "n", "no", "false", "0", "off": discard
  192. of "win", "windows":
  193. when defined(windows): result.err = reDisabled
  194. of "linux":
  195. when defined(linux): result.err = reDisabled
  196. of "bsd":
  197. when defined(bsd): result.err = reDisabled
  198. of "macosx":
  199. when defined(macosx): result.err = reDisabled
  200. of "unix":
  201. when defined(unix): result.err = reDisabled
  202. of "posix":
  203. when defined(posix): result.err = reDisabled
  204. of "travis":
  205. if isTravis: result.err = reDisabled
  206. of "appveyor":
  207. if isAppVeyor: result.err = reDisabled
  208. of "azure":
  209. if isAzure: result.err = reDisabled
  210. of "32bit":
  211. if sizeof(int) == 4:
  212. result.err = reDisabled
  213. else:
  214. result.parseErrors.addLine "cannot interpret as a bool: ", e.value
  215. of "cmd":
  216. if e.value.startsWith("nim "):
  217. result.cmd = compilerPrefix & e.value[3..^1]
  218. else:
  219. result.cmd = e.value
  220. of "ccodecheck":
  221. result.ccodeCheck = e.value
  222. of "maxcodesize":
  223. discard parseInt(e.value, result.maxCodeSize)
  224. of "timeout":
  225. try:
  226. result.timeout = parseFloat(e.value)
  227. except ValueError:
  228. result.parseErrors.addLine "cannot interpret as a float: ", e.value
  229. of "target", "targets":
  230. for v in e.value.normalize.splitWhitespace:
  231. case v
  232. of "c":
  233. result.targets.incl(targetC)
  234. of "cpp", "c++":
  235. result.targets.incl(targetCpp)
  236. of "objc":
  237. result.targets.incl(targetObjC)
  238. of "js":
  239. result.targets.incl(targetJS)
  240. else:
  241. result.parseErrors.addLine "cannot interpret as a target: ", e.value
  242. else:
  243. result.parseErrors.addLine "invalid key for test spec: ", e.key
  244. of cfgSectionStart:
  245. result.parseErrors.addLine "section ignored: ", e.section
  246. of cfgOption:
  247. result.parseErrors.addLine "command ignored: ", e.key & ": " & e.value
  248. of cfgError:
  249. result.parseErrors.addLine e.msg
  250. of cfgEof:
  251. break
  252. close(p)
  253. if skips.anyIt(it in result.file):
  254. result.err = reDisabled