/ test / tquickstart.nim
tquickstart.nim
  1  #            NimYAML - YAML implementation in Nim
  2  #        (c) Copyright 2015-2023 Felix Krause
  3  #
  4  #    See the file "copying.txt", included in this
  5  #    distribution, for details about the copyright.
  6  
  7  import unittest, os, osproc, macros, strutils, streams
  8  
  9  const baseDir = parentDir(staticExec("pwd"))
 10  let (nimPathRaw, nimPathRet) =
 11      execCmdEx("which nim", {poStdErrToStdOut, poUsePath})
 12  if nimPathRet != 0: quit "could not locate nim executable:\n" & nimPathRaw
 13  let nimPath =
 14      if nimPathRaw[0] == '/': nimPathRaw.strip else: baseDir / nimPathRaw.strip
 15  
 16  proc inputTest(basePath, path: string): bool =
 17    let
 18      absolutePath = basePath / path
 19      inFileOrig = absolutePath / "01-in.yaml"
 20      inFileDest = absolutePath / "in.yaml"
 21      codeFileOrig = absolutePath / "00-code.nim"
 22      codeFileDest = absolutePath / "code.nim"
 23      exeFileDest = when defined(windows): absolutePath / "code.exe" else:
 24          absolutePath / "code"
 25    copyFile(inFileOrig, inFileDest)
 26    copyFile(codeFileOrig, codeFileDest)
 27    defer:
 28      removeFile(inFileDest)
 29      removeFile(codeFileDest)
 30    var process = startProcess(nimPath & " c --hints:off -p:" & escape(basePath) &
 31        " code.nim", absolutePath, [], nil, {poStdErrToStdOut, poEvalCommand})
 32    defer:
 33      process.close()
 34    if process.waitForExit() != 0:
 35      echo "compiler output:"
 36      echo "================\n"
 37      echo process.outputStream().readAll()
 38      result = false
 39    else:
 40      defer: removeFile(exeFileDest)
 41      process.close()
 42      process = startProcess(absolutePath / "code", absolutePath, [], nil,
 43          {poStdErrToStdOut, poEvalCommand})
 44      if process.waitForExit() != 0:
 45        echo "executable output:"
 46        echo "==================\n"
 47        echo process.outputStream().readAll()
 48        result = false
 49      else: result = true
 50  
 51  proc outputTest(basePath, path: string): bool =
 52    let
 53      absolutePath = basePath / path
 54      codeFileOrig = absolutePath / "00-code.nim"
 55      codeFileDest = absolutePath / "code.nim"
 56      exeFileDest = when defined(windows): absolutePath / "code.exe" else:
 57          absolutePath / "code"
 58      outFileExpected = absolutePath / "01-out.yaml"
 59      outFileActual = absolutePath / "out.yaml"
 60    copyFile(codeFileOrig, codeFileDest)
 61    defer: removeFile(codeFileDest)
 62    var process = startProcess(nimPath & " c --hints:off -p:" & escape(basePath) &
 63        " code.nim", absolutePath, [], nil, {poStdErrToStdOut, poEvalCommand})
 64    defer: process.close()
 65    if process.waitForExit() != 0:
 66      echo "compiler output:"
 67      echo "================\n"
 68      echo process.outputStream().readAll()
 69      result = false
 70    else:
 71      defer: removeFile(exeFileDest)
 72      process.close()
 73      process = startProcess(absolutePath / "code", absolutePath, [], nil,
 74          {poStdErrToStdOut, poEvalCommand})
 75      if process.waitForExit() != 0:
 76        echo "executable output:"
 77        echo "==================\n"
 78        echo process.outputStream().readAll()
 79        result = false
 80      else:
 81        defer: removeFile(outFileActual)
 82        var
 83          expected = open(outFileExpected, fmRead)
 84          actual = open(outFileActual, fmRead)
 85          lineNumber = 1
 86        defer:
 87          expected.close()
 88          actual.close()
 89        var
 90          expectedLine = ""
 91          actualLine = ""
 92        while true:
 93          if expected.readLine(expectedLine):
 94            if actual.readLine(actualLine):
 95              if expectedLine != actualLine:
 96                echo "difference at line #", lineNumber, ':'
 97                echo "expected: ", escape(expectedLine)
 98                echo "  actual: ", escape(actualLine)
 99                return false
100            else:
101              echo "actual output has fewer lines than expected; ",
102                  "first missing line: #", lineNumber
103              echo "expected: ", escape(expectedLine)
104              return false
105          else:
106            if actual.readLine(actualLine):
107              echo "actual output has more lines than expected; ",
108                  "first unexpected line: #", lineNumber
109              echo "content: ", escape(actualLine)
110              return false
111            else: break
112          lineNumber.inc()
113        result = true
114  
115  proc testsFor(path: string, root: bool = true, titlePrefix: string = ""):
116      NimNode {.compileTime.} =
117    result = newStmtList()
118    let
119      title = titlePrefix & slurp(baseDir / path / "title").splitLines()[0]
120    if fileExists(path / "00-code.nim"):
121      var test = newCall("test", newLit(title))
122      if fileExists(path / "01-in.yaml"):
123        test.add(newCall("doAssert", newCall("inputTest", newLit(baseDir),
124            newLit(path))))
125      elif fileExists(path / "01-out.yaml"):
126        test.add(newCall("doAssert", newCall("outputTest", newLit(baseDir),
127            newLit(path))))
128      else:
129        error("Error: neither 01-in.yaml nor 01-out.yaml exists in " & path & '!')
130      result.add(test)
131    for kind, childPath in walkDir(path):
132      if kind == pcDir:
133        if childPath != path / "nimcache":
134          result.add(testsFor(childPath, false, if root: "" else: title & ' '))
135    if root:
136      result = newCall("suite", newLit(title), result)
137  
138  macro genTests(): untyped = testsFor("doc/snippets/quickstart")
139  
140  genTests()