123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- # Tester for nimsuggest.
- # Every test file can have a #[!]# comment that is deleted from the input
- # before 'nimsuggest' is invoked to ensure this token doesn't make a
- # crucial difference for Nim's parser.
- # When debugging, to run a single test, use for e.g.:
- # `nim r nimsuggest/tester.nim nimsuggest/tests/tsug_accquote.nim`
- import os, osproc, strutils, streams, re, sexp, net
- from sequtils import toSeq
- type
- Test = object
- filename, cmd, dest: string
- startup: seq[string]
- script: seq[(string, string)]
- disabled: bool
- const
- DummyEof = "!EOF!"
- tpath = "nimsuggest/tests"
- # we could also use `stdtest/specialpaths`
- import std/compilesettings
- proc parseTest(filename: string; epcMode=false): Test =
- const cursorMarker = "#[!]#"
- let nimsug = "bin" / addFileExt("nimsuggest_testing", ExeExt)
- doAssert nimsug.fileExists, nimsug
- const libpath = querySetting(libPath)
- result.filename = filename
- result.dest = getTempDir() / extractFilename(filename)
- result.cmd = nimsug & " --tester " & result.dest
- result.script = @[]
- result.startup = @[]
- var tmp = open(result.dest, fmWrite)
- var specSection = 0
- var markers = newSeq[string]()
- var i = 1
- for x in lines(filename):
- let marker = x.find(cursorMarker)
- if marker >= 0:
- if epcMode:
- markers.add "(\"" & filename & "\" " & $i & " " & $marker & " \"" & result.dest & "\")"
- else:
- markers.add "\"" & filename & "\";\"" & result.dest & "\":" & $i & ":" & $marker
- tmp.writeLine x.replace(cursorMarker, "")
- else:
- tmp.writeLine x
- if x.contains("""""""""):
- inc specSection
- elif specSection == 1:
- if x.startsWith("disabled:"):
- if x.startsWith("disabled:true"):
- result.disabled = true
- else:
- # be strict about format
- doAssert x.startsWith("disabled:false")
- result.disabled = false
- elif x.startsWith("$nimsuggest"):
- result.cmd = x % ["nimsuggest", nimsug, "file", filename, "lib", libpath]
- elif x.startsWith("!"):
- if result.cmd.len == 0:
- result.startup.add x
- else:
- result.script.add((x, ""))
- elif x.startsWith(">"):
- # since 'markers' here are not complete yet, we do the $substitutions
- # afterwards
- result.script.add((x.substr(1).replaceWord("$path", tpath).replaceWord("$file", filename), ""))
- elif x.len > 0:
- # expected output line:
- let x = x % ["file", filename, "lib", libpath]
- result.script[^1][1].add x.replace(";;", "\t") & '\L'
- # else: ignore empty lines for better readability of the specs
- inc i
- tmp.close()
- # now that we know the markers, substitute them:
- for a in mitems(result.script):
- a[0] = a[0] % markers
- proc parseCmd(c: string): seq[string] =
- # we don't support double quotes for now so that
- # we can later support them properly with escapes and stuff.
- result = @[]
- var i = 0
- var a = ""
- while i < c.len:
- setLen(a, 0)
- # eat all delimiting whitespace
- while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i)
- if i >= c.len: break
- case c[i]
- of '"': raise newException(ValueError, "double quotes not yet supported: " & c)
- of '\'':
- var delim = c[i]
- inc(i) # skip ' or "
- while i < c.len and c[i] != delim:
- add a, c[i]
- inc(i)
- if i < c.len: inc(i)
- else:
- while i < c.len and c[i] > ' ':
- add(a, c[i])
- inc(i)
- add(result, a)
- proc edit(tmpfile: string; x: seq[string]) =
- if x.len != 3 and x.len != 4:
- quit "!edit takes two or three arguments"
- let f = if x.len >= 4: tpath / x[3] else: tmpfile
- try:
- let content = readFile(f)
- let newcontent = content.replace(x[1], x[2])
- if content == newcontent:
- quit "wrong test case: edit had no effect"
- writeFile(f, newcontent)
- except IOError:
- quit "cannot edit file " & tmpfile
- proc exec(x: seq[string]) =
- if x.len != 2: quit "!exec takes one argument"
- if execShellCmd(x[1]) != 0:
- quit "External program failed " & x[1]
- proc copy(x: seq[string]) =
- if x.len != 3: quit "!copy takes two arguments"
- let rel = tpath
- copyFile(rel / x[1], rel / x[2])
- proc del(x: seq[string]) =
- if x.len != 2: quit "!del takes one argument"
- removeFile(tpath / x[1])
- proc runCmd(cmd, dest: string): bool =
- result = cmd[0] == '!'
- if not result: return
- let x = cmd.parseCmd()
- case x[0]
- of "!edit":
- edit(dest, x)
- of "!exec":
- exec(x)
- of "!copy":
- copy(x)
- of "!del":
- del(x)
- else:
- quit "unknown command: " & cmd
- proc smartCompare(pattern, x: string): bool =
- if pattern.contains('*'):
- result = match(x, re(escapeRe(pattern).replace("\\x2A","(.*)"), {}))
- proc sendEpcStr(socket: Socket; cmd: string) =
- let s = cmd.find(' ')
- doAssert s > 0
- var args = cmd.substr(s+1)
- if not args.startsWith("("): args = escapeJson(args)
- let c = "(call 567 " & cmd.substr(0, s) & args & ")"
- socket.send toHex(c.len, 6)
- socket.send c
- proc recvEpc(socket: Socket): string =
- var L = newStringOfCap(6)
- if socket.recv(L, 6) != 6:
- raise newException(ValueError, "recv A failed #" & L & "#")
- let x = parseHexInt(L)
- result = newString(x)
- if socket.recv(result, x) != x:
- raise newException(ValueError, "recv B failed")
- proc sexpToAnswer(s: SexpNode): string =
- result = ""
- doAssert s.kind == SList
- doAssert s.len >= 3
- let m = s[2]
- if m.kind != SList:
- echo s
- doAssert m.kind == SList
- for a in m:
- doAssert a.kind == SList
- #s.section,
- #s.symkind,
- #s.qualifiedPath.map(newSString),
- #s.filePath,
- #s.forth,
- #s.line,
- #s.column,
- #s.doc
- if a.len >= 9:
- let section = a[0].getStr
- let symk = a[1].getStr
- let qp = a[2]
- let file = a[3].getStr
- let typ = a[4].getStr
- let line = a[5].getNum
- let col = a[6].getNum
- let doc = a[7].getStr.escape
- result.add section
- result.add '\t'
- result.add symk
- result.add '\t'
- var i = 0
- if qp.kind == SList:
- for aa in qp:
- if i > 0: result.add '.'
- result.add aa.getStr
- inc i
- result.add '\t'
- result.add typ
- result.add '\t'
- result.add file
- result.add '\t'
- result.addInt line
- result.add '\t'
- result.addInt col
- result.add '\t'
- result.add doc
- result.add '\t'
- result.addInt a[8].getNum
- if a.len >= 11:
- result.add '\t'
- result.addInt a[9].getNum
- result.add '\t'
- result.addInt a[10].getNum
- elif a.len >= 10:
- result.add '\t'
- result.add a[9].getStr
- result.add '\L'
- proc doReport(filename, answer, resp: string; report: var string) =
- if resp != answer and not smartCompare(resp, answer):
- report.add "\nTest failed: " & filename
- var hasDiff = false
- for i in 0..min(resp.len-1, answer.len-1):
- if resp[i] != answer[i]:
- report.add "\n Expected:\n" & resp
- report.add "\n But got:\n" & answer
- hasDiff = true
- break
- if not hasDiff:
- report.add "\n Expected: " & resp
- report.add "\n But got: " & answer
- proc skipDisabledTest(test: Test): bool =
- if test.disabled:
- echo "disabled: " & test.filename
- result = test.disabled
- proc runEpcTest(filename: string): int =
- let s = parseTest(filename, true)
- if s.skipDisabledTest: return 0
- for req, _ in items(s.script):
- if req.startsWith("highlight"):
- echo "disabled epc: " & s.filename
- return 0
- for cmd in s.startup:
- if not runCmd(cmd, s.dest):
- quit "invalid command: " & cmd
- let epccmd = if s.cmd.contains("--v3"):
- s.cmd.replace("--tester", "--epc --log")
- else:
- s.cmd.replace("--tester", "--epc --v2 --log")
- let cl = parseCmdLine(epccmd)
- var p = startProcess(command=cl[0], args=cl[1 .. ^1],
- options={poStdErrToStdOut, poUsePath,
- poInteractive, poDaemon})
- let outp = p.outputStream
- var report = ""
- var socket = newSocket()
- try:
- # read the port number:
- when defined(posix):
- var a = newStringOfCap(120)
- discard outp.readLine(a)
- else:
- var i = 0
- while not osproc.hasData(p) and i < 100:
- os.sleep(50)
- inc i
- let a = outp.readAll().strip()
- var port: int
- try:
- port = parseInt(a)
- except ValueError:
- echo "Error parsing port number: " & a
- echo outp.readAll()
- quit 1
- socket.connect("localhost", Port(port))
- for req, resp in items(s.script):
- if not runCmd(req, s.dest):
- socket.sendEpcStr(req)
- let sx = parseSexp(socket.recvEpc())
- if not req.startsWith("mod "):
- let answer = if sx[2].kind == SNil: "" else: sexpToAnswer(sx)
- doReport(filename, answer, resp, report)
- socket.sendEpcStr "return arg"
- # bugfix: this was in `finally` block, causing the original error to be
- # potentially masked by another one in case `socket.sendEpcStr` raises
- # (e.g. if socket couldn't connect in the 1st place)
- finally:
- close(p)
- if report.len > 0:
- echo "==== EPC ========================================"
- echo report
- result = report.len
- proc runTest(filename: string): int =
- let s = parseTest filename
- if s.skipDisabledTest: return 0
- for cmd in s.startup:
- if not runCmd(cmd, s.dest):
- quit "invalid command: " & cmd
- let cl = parseCmdLine(s.cmd)
- var p = startProcess(command=cl[0], args=cl[1 .. ^1],
- options={poStdErrToStdOut, poUsePath,
- poInteractive, poDaemon})
- let outp = p.outputStream
- let inp = p.inputStream
- var report = ""
- var a = newStringOfCap(120)
- try:
- # read and ignore anything nimsuggest says at startup:
- while outp.readLine(a):
- if a == DummyEof: break
- for req, resp in items(s.script):
- if not runCmd(req, s.dest):
- inp.writeLine(req)
- inp.flush()
- var answer = ""
- while outp.readLine(a):
- if a == DummyEof: break
- answer.add a
- answer.add '\L'
- doReport(filename, answer, resp, report)
- finally:
- try:
- inp.writeLine("quit")
- inp.flush()
- except IOError, OSError:
- # assume it's SIGPIPE, ie, the child already died
- discard
- close(p)
- if report.len > 0:
- echo "==== STDIN ======================================"
- echo report
- result = report.len
- proc main() =
- var failures = 0
- if os.paramCount() > 0:
- let x = os.paramStr(1)
- let xx = expandFilename x
- # run only stdio when running single test
- failures += runTest(xx)
- else:
- let files = toSeq(walkFiles(tpath / "t*.nim"))
- for i, x in files:
- echo "$#/$# test: $#" % [$i, $files.len, x]
- when defined(i386):
- if x == "nimsuggest/tests/tmacro_highlight.nim":
- echo "skipping" # workaround bug #17945
- continue
- let xx = expandFilename x
- when not defined(windows):
- # XXX Windows IO redirection seems bonkers:
- failures += runTest(xx)
- failures += runEpcTest(xx)
- if failures > 0:
- quit 1
- main()
|