tester.nim 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. # Tester for nimsuggest.
  2. # Every test file can have a #[!]# comment that is deleted from the input
  3. # before 'nimsuggest' is invoked to ensure this token doesn't make a
  4. # crucial difference for Nim's parser.
  5. import os, osproc, strutils, streams, re, sexp, net
  6. type
  7. Test = object
  8. cmd, dest: string
  9. startup: seq[string]
  10. script: seq[(string, string)]
  11. const
  12. curDir = when defined(windows): "" else: ""
  13. DummyEof = "!EOF!"
  14. template tpath(): untyped = getAppDir() / "tests"
  15. proc parseTest(filename: string; epcMode=false): Test =
  16. const cursorMarker = "#[!]#"
  17. let nimsug = curDir & addFileExt("nimsuggest", ExeExt)
  18. let libpath = findExe("nim").splitFile().dir /../ "lib"
  19. result.dest = getTempDir() / extractFilename(filename)
  20. result.cmd = nimsug & " --tester " & result.dest
  21. result.script = @[]
  22. result.startup = @[]
  23. var tmp = open(result.dest, fmWrite)
  24. var specSection = 0
  25. var markers = newSeq[string]()
  26. var i = 1
  27. for x in lines(filename):
  28. let marker = x.find(cursorMarker)
  29. if marker >= 0:
  30. if epcMode:
  31. markers.add "(\"" & filename & "\" " & $i & " " & $marker & " \"" & result.dest & "\")"
  32. else:
  33. markers.add "\"" & filename & "\";\"" & result.dest & "\":" & $i & ":" & $marker
  34. tmp.writeLine x.replace(cursorMarker, "")
  35. else:
  36. tmp.writeLine x
  37. if x.contains("""""""""):
  38. inc specSection
  39. elif specSection == 1:
  40. if x.startsWith("$nimsuggest"):
  41. result.cmd = x % ["nimsuggest", nimsug, "file", filename, "lib", libpath]
  42. elif x.startsWith("!"):
  43. if result.cmd.len == 0:
  44. result.startup.add x
  45. else:
  46. result.script.add((x, ""))
  47. elif x.startsWith(">"):
  48. # since 'markers' here are not complete yet, we do the $substitutions
  49. # afterwards
  50. result.script.add((x.substr(1).replaceWord("$path", tpath()), ""))
  51. elif x.len > 0:
  52. # expected output line:
  53. let x = x % ["file", filename, "lib", libpath]
  54. result.script[^1][1].add x.replace(";;", "\t") & '\L'
  55. # else: ignore empty lines for better readability of the specs
  56. inc i
  57. tmp.close()
  58. # now that we know the markers, substitute them:
  59. for a in mitems(result.script):
  60. a[0] = a[0] % markers
  61. proc parseCmd(c: string): seq[string] =
  62. # we don't support double quotes for now so that
  63. # we can later support them properly with escapes and stuff.
  64. result = @[]
  65. var i = 0
  66. var a = ""
  67. while true:
  68. setLen(a, 0)
  69. # eat all delimiting whitespace
  70. while c[i] in {' ', '\t', '\l', '\r'}: inc(i)
  71. case c[i]
  72. of '"': raise newException(ValueError, "double quotes not yet supported: " & c)
  73. of '\'':
  74. var delim = c[i]
  75. inc(i) # skip ' or "
  76. while c[i] != '\0' and c[i] != delim:
  77. add a, c[i]
  78. inc(i)
  79. if c[i] != '\0': inc(i)
  80. of '\0': break
  81. else:
  82. while c[i] > ' ':
  83. add(a, c[i])
  84. inc(i)
  85. add(result, a)
  86. proc edit(tmpfile: string; x: seq[string]) =
  87. if x.len != 3 and x.len != 4:
  88. quit "!edit takes two or three arguments"
  89. let f = if x.len >= 4: tpath() / x[3] else: tmpfile
  90. try:
  91. let content = readFile(f)
  92. let newcontent = content.replace(x[1], x[2])
  93. if content == newcontent:
  94. quit "wrong test case: edit had no effect"
  95. writeFile(f, newcontent)
  96. except IOError:
  97. quit "cannot edit file " & tmpfile
  98. proc exec(x: seq[string]) =
  99. if x.len != 2: quit "!exec takes one argument"
  100. if execShellCmd(x[1]) != 0:
  101. quit "External program failed " & x[1]
  102. proc copy(x: seq[string]) =
  103. if x.len != 3: quit "!copy takes two arguments"
  104. let rel = tpath()
  105. copyFile(rel / x[1], rel / x[2])
  106. proc del(x: seq[string]) =
  107. if x.len != 2: quit "!del takes one argument"
  108. removeFile(tpath() / x[1])
  109. proc runCmd(cmd, dest: string): bool =
  110. result = cmd[0] == '!'
  111. if not result: return
  112. let x = cmd.parseCmd()
  113. case x[0]
  114. of "!edit":
  115. edit(dest, x)
  116. of "!exec":
  117. exec(x)
  118. of "!copy":
  119. copy(x)
  120. of "!del":
  121. del(x)
  122. else:
  123. quit "unkown command: " & cmd
  124. proc smartCompare(pattern, x: string): bool =
  125. if pattern.contains('*'):
  126. result = match(x, re(escapeRe(pattern).replace("\\x2A","(.*)"), {}))
  127. proc sendEpcStr(socket: Socket; cmd: string) =
  128. let s = cmd.find(' ')
  129. doAssert s > 0
  130. var args = cmd.substr(s+1)
  131. if not args.startsWith("("): args = escapeJson(args)
  132. let c = "(call 567 " & cmd.substr(0, s) & args & ")"
  133. socket.send toHex(c.len, 6)
  134. socket.send c
  135. proc recvEpc(socket: Socket): string =
  136. var L = newStringOfCap(6)
  137. if socket.recv(L, 6) != 6:
  138. raise newException(ValueError, "recv A failed #" & L & "#")
  139. let x = parseHexInt(L)
  140. result = newString(x)
  141. if socket.recv(result, x) != x:
  142. raise newException(ValueError, "recv B failed")
  143. proc sexpToAnswer(s: SexpNode): string =
  144. result = ""
  145. doAssert s.kind == SList
  146. doAssert s.len >= 3
  147. let m = s[2]
  148. if m.kind != SList:
  149. echo s
  150. doAssert m.kind == SList
  151. for a in m:
  152. doAssert a.kind == SList
  153. #s.section,
  154. #s.symkind,
  155. #s.qualifiedPath.map(newSString),
  156. #s.filePath,
  157. #s.forth,
  158. #s.line,
  159. #s.column,
  160. #s.doc
  161. if a.len >= 9:
  162. let section = a[0].getStr
  163. let symk = a[1].getStr
  164. let qp = a[2]
  165. let file = a[3].getStr
  166. let typ = a[4].getStr
  167. let line = a[5].getNum
  168. let col = a[6].getNum
  169. let doc = a[7].getStr.escape
  170. result.add section
  171. result.add '\t'
  172. result.add symk
  173. result.add '\t'
  174. var i = 0
  175. if qp.kind == SList:
  176. for aa in qp:
  177. if i > 0: result.add '.'
  178. result.add aa.getStr
  179. inc i
  180. result.add '\t'
  181. result.add typ
  182. result.add '\t'
  183. result.add file
  184. result.add '\t'
  185. result.add line
  186. result.add '\t'
  187. result.add col
  188. result.add '\t'
  189. result.add doc
  190. result.add '\t'
  191. result.add a[8].getNum
  192. if a.len >= 10:
  193. result.add '\t'
  194. result.add a[9].getStr
  195. result.add '\L'
  196. proc doReport(filename, answer, resp: string; report: var string) =
  197. if resp != answer and not smartCompare(resp, answer):
  198. report.add "\nTest failed: " & filename
  199. var hasDiff = false
  200. for i in 0..min(resp.len-1, answer.len-1):
  201. if resp[i] != answer[i]:
  202. report.add "\n Expected: " & resp.substr(i, i+200)
  203. report.add "\n But got: " & answer.substr(i, i+200)
  204. hasDiff = true
  205. break
  206. if not hasDiff:
  207. report.add "\n Expected: " & resp
  208. report.add "\n But got: " & answer
  209. proc runEpcTest(filename: string): int =
  210. let s = parseTest(filename, true)
  211. for cmd in s.startup:
  212. if not runCmd(cmd, s.dest):
  213. quit "invalid command: " & cmd
  214. let epccmd = s.cmd.replace("--tester", "--epc --v2 --log")
  215. let cl = parseCmdLine(epccmd)
  216. var p = startProcess(command=cl[0], args=cl[1 .. ^1],
  217. options={poStdErrToStdOut, poUsePath,
  218. poInteractive, poDemon})
  219. let outp = p.outputStream
  220. let inp = p.inputStream
  221. var report = ""
  222. var socket = newSocket()
  223. try:
  224. # read the port number:
  225. when defined(posix):
  226. var a = newStringOfCap(120)
  227. discard outp.readLine(a)
  228. else:
  229. var i = 0
  230. while not osproc.hasData(p) and i < 100:
  231. os.sleep(50)
  232. inc i
  233. let a = outp.readAll().strip()
  234. let port = parseInt(a)
  235. socket.connect("localhost", Port(port))
  236. for req, resp in items(s.script):
  237. if not runCmd(req, s.dest):
  238. socket.sendEpcStr(req)
  239. let sx = parseSexp(socket.recvEpc())
  240. if not req.startsWith("mod "):
  241. let answer = sexpToAnswer(sx)
  242. doReport(filename, answer, resp, report)
  243. finally:
  244. socket.sendEpcStr "return arg"
  245. close(p)
  246. if report.len > 0:
  247. echo "==== EPC ========================================"
  248. echo report
  249. result = report.len
  250. proc runTest(filename: string): int =
  251. let s = parseTest filename
  252. for cmd in s.startup:
  253. if not runCmd(cmd, s.dest):
  254. quit "invalid command: " & cmd
  255. let cl = parseCmdLine(s.cmd)
  256. var p = startProcess(command=cl[0], args=cl[1 .. ^1],
  257. options={poStdErrToStdOut, poUsePath,
  258. poInteractive, poDemon})
  259. let outp = p.outputStream
  260. let inp = p.inputStream
  261. var report = ""
  262. var a = newStringOfCap(120)
  263. try:
  264. # read and ignore anything nimsuggest says at startup:
  265. while outp.readLine(a):
  266. if a == DummyEof: break
  267. for req, resp in items(s.script):
  268. if not runCmd(req, s.dest):
  269. inp.writeLine(req)
  270. inp.flush()
  271. var answer = ""
  272. while outp.readLine(a):
  273. if a == DummyEof: break
  274. answer.add a
  275. answer.add '\L'
  276. doReport(filename, answer, resp, report)
  277. finally:
  278. inp.writeLine("quit")
  279. inp.flush()
  280. close(p)
  281. if report.len > 0:
  282. echo "==== STDIN ======================================"
  283. echo report
  284. result = report.len
  285. proc main() =
  286. var failures = 0
  287. if os.paramCount() > 0:
  288. let x = os.paramStr(1)
  289. let xx = expandFilename x
  290. failures += runTest(xx)
  291. failures += runEpcTest(xx)
  292. else:
  293. for x in walkFiles(getAppDir() / "tests/t*.nim"):
  294. echo "Test ", x
  295. let xx = expandFilename x
  296. when not defined(windows):
  297. # XXX Windows IO redirection seems bonkers:
  298. failures += runTest(xx)
  299. failures += runEpcTest(xx)
  300. if failures > 0:
  301. quit 1
  302. main()