summary refs log blame commit diff stats
path: root/lib/pure/unittest.nim
blob: 9ff44632d73f00b615cff427b3ac45aa40b87ba4 (plain) (tree)
1
2
3
4
5
6
7
8
9


                                      
                                                




                                                    

                                         





                                                                            

       
                       

     

                                                             
 

                                      


                             
   









                                                
                                             

                                               
                                                   



                                                     





                                                                              




                                                                        
   
                                               

                                        

                      





                            



                                                                     

                         
                                    





                                                                       

                                 







                                       

                                                                            


                                                              

                                                        




                                        
                                                           
                                                                    
                                                                              






                                                                   

                                                                          



                                 
                       

                          


                                         















                                                                     

                                                                       






                                            
                                                                                













                                                                            
                                                              
 











                                                             
#
#
#            Nimrod's Runtime Library
#        (c) Copyright 2012 Nimrod Contributors
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## :Author: Zahary Karadjov (zah@github)
##
## This module implements the standard unit testing facilities such as
## suites, fixtures and test cases as well as facilities for combinatorial 
## and randomzied test case generation (not yet available) 
## and object mocking (not yet available)
##
## It is loosely based on C++'s boost.test and Haskell's QuickTest

import
  macros, terminal, os

type
  TTestStatus* = enum OK, FAILED
  TOutputLevel* = enum PRINT_ALL, PRINT_FAILURES, PRINT_NONE

var 
  # XXX: These better be thread-local
  AbortOnError*: bool
  OutputLevel*: TOutputLevel
  ColorOutput*: bool
  
  checkpoints: seq[string] = @[]

template TestSetupIMPL*: stmt = nil
template TestTeardownIMPL*: stmt = nil

proc shouldRun(testName: string): bool =
  result = true

template suite*(name: expr, body: stmt): stmt =
  block:
    template setup*(setupBody: stmt): stmt =
      template TestSetupIMPL: stmt = setupBody

    template teardown*(teardownBody: stmt): stmt =
      template TestTeardownIMPL: stmt = teardownBody

    body

proc testDone(name: string, s: TTestStatus) =
  if s == FAILED:
    program_result += 1

  if OutputLevel != PRINT_NONE and (OutputLevel == PRINT_ALL or s == FAILED):
    var color = (if s == OK: fgGreen else: fgRed)
    
    if ColorOutput:
      styledEcho styleBright, color, "[", $s, "] ", fgWhite, name, "\n"
    else:
      echo "[", $s, "] ", name, "\n"
  
template test*(name: expr, body: stmt): stmt =
  bind shouldRun, checkpoints, testDone

  if shouldRun(name):
    checkpoints = @[]
    var TestStatusIMPL = OK
    
    try:
      TestSetupIMPL()
      body

    except:
      checkpoint("Unhandled exception: " & getCurrentExceptionMsg())
      fail()

    finally:
      TestTeardownIMPL()
      testDone name, TestStatusIMPL

proc checkpoint*(msg: string) =
  checkpoints.add(msg)
  # TODO: add support for something like SCOPED_TRACE from Google Test

template fail* =
  bind checkpoints
  for msg in items(checkpoints):
    echo msg

  if AbortOnError: quit(1)
  
  TestStatusIMPL = FAILED
  checkpoints = @[]

macro check*(conditions: stmt): stmt =
  proc standardRewrite(e: PNimrodNode): PNimrodNode =
    template rewrite(Exp, lineInfoLit: expr, expLit: string): PNimrodNode =
      if not Exp:
        checkpoint(lineInfoLit & ": Check failed: " & expLit)
        fail()
 
    result = getAst(rewrite(e, e.lineinfo, e.toStrLit))
  
  case conditions.kind
  of nnkCall, nnkCommand, nnkMacroStmt:
    case conditions[1].kind
    of nnkInfix:
      proc rewriteBinaryOp(op: PNimrodNode): PNimrodNode =
        template rewrite(op, left, right, lineInfoLit: expr, opLit,
          leftLit, rightLit: string, printLhs, printRhs: bool): PNimrodNode =
          block:
            var 
              lhs = left
              rhs = right

            if not `op`(lhs, rhs):
              checkpoint(lineInfoLit & ": Check failed: " & opLit)
              when printLhs: checkpoint("  " & leftLit & " was " & $lhs)
              when printRhs: checkpoint("  " & rightLit & " was " & $rhs)
              fail()

        result = getAst(rewrite(
          op[0], op[1], op[2],
          op.lineinfo,
          op.toStrLit,
          op[1].toStrLit,
          op[2].toStrLit,
          op[1].kind notin nnkLiterals,
          op[2].kind notin nnkLiterals))
        
      result = rewriteBinaryOp(conditions[1])
  
    of nnkCall, nnkCommand:
      # TODO: We can print out the call arguments in case of failure
      result = standardRewrite(conditions[1])

    of nnkStmtList:
      result = newNimNode(nnkStmtList)
      for i in countup(0, conditions[1].len - 1):
        result.add(newCall(!"check", conditions[1][i]))

    else:
      result = standardRewrite(conditions[1])

  else:
    var ast = conditions.treeRepr
    error conditions.lineinfo & ": Malformed check statement:\n" & ast

template require*(conditions: stmt): stmt =
  block:
    const AbortOnError = true    
    check conditions

macro expect*(exp: stmt): stmt =
  template expectBody(errorTypes, lineInfoLit: expr, body: stmt): PNimrodNode =
    try:
      body
      checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.")
      fail()
    except errorTypes:
      nil

  var expectCall = exp[0]
  var body = exp[1]
  
  var errorTypes = newNimNode(nnkBracket)
  for i in countup(1, expectCall.len - 1):
    errorTypes.add(expectCall[i])

  result = getAst(expectBody(errorTypes, exp.lineinfo, body))


## Reading settings
var envOutLvl = os.getEnv("NIMTEST_OUTPUT_LVL").string

if envOutLvl.len > 0:
  for opt in countup(low(TOutputLevel), high(TOutputLevel)):
    if $opt == envOutLvl:
      OutputLevel = opt
      break

AbortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR")
ColorOutput  = not existsEnv("NIMTEST_NO_COLOR")
pre>

                            
                                             



                         
                                        



                       
                                                                 


                                                     

                                 


                       
                                      


                        
                        
 
                                   
             
                                  
           
             
              
 
                                     
                            
                                                                  
                        
                                  


                        






                                               
     



                                                                            

                                              


                                                                        
                            


                                                                        







                                                       
                                                                    

          
                                
                 
                                                                      

                  

                                                          
                   
                                       
                
                                         

                     
                                                                  
             
             
              
               
                                         
                  

                                               
             
                                         
              
                                            
 
                                                              
                 
             





                          
                 


                                         
                 


                            
                 




                                            

                                                 
             
                       
           
               

                        
             
                    

                                                   

                        
 
                                               

             
                       
           
               

                  
             


                                                
                                                              
             
                       
           

                  


                                               
             
                                         


                                             
                  
             
                       
               
               
                              
                                    
                   
             
                               

                                                         
                           
                                 

                                                           
                     
#
#
#           The Nim Compiler
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Low-level streams for high performance.

import
  strutils

# support '-d:useGnuReadline' for backwards compatibility:
when not defined(windows) and (defined(useGnuReadline) or defined(useLinenoise)):
  import rdstdin

type
  TLLStreamKind* = enum       # enum of different stream implementations
    llsNone,                  # null stream: reading and writing has no effect
    llsString,                # stream encapsulates a string
    llsFile,                  # stream encapsulates a file
    llsStdIn                  # stream encapsulates stdin
  TLLStream* = object of RootObj
    kind*: TLLStreamKind # accessible for low-level access (lexbase uses this)
    f*: File
    s*: string
    rd*, wr*: int             # for string streams
    lineOffset*: int          # for fake stdin line numbers

  PLLStream* = ref TLLStream

proc llStreamOpen*(data: string): PLLStream =
  new(result)
  result.s = data
  result.kind = llsString

proc llStreamOpen*(f: File): PLLStream =
  new(result)
  result.f = f
  result.kind = llsFile

proc llStreamOpen*(filename: string, mode: FileMode): PLLStream =
  new(result)
  result.kind = llsFile
  if not open(result.f, filename, mode): result = nil

proc llStreamOpen*(): PLLStream =
  new(result)
  result.kind = llsNone

proc llStreamOpenStdIn*(): PLLStream =
  new(result)
  result.kind = llsStdIn
  result.s = ""
  result.lineOffset = -1

proc llStreamClose*(s: PLLStream) =
  case s.kind
  of llsNone, llsString, llsStdIn:
    discard
  of llsFile:
    close(s.f)

when not declared(readLineFromStdin):
  # fallback implementation:
  proc readLineFromStdin(prompt: string, line: var string): bool =
    stdout.write(prompt)
    result = readLine(stdin, line)
    if not result:
      stdout.write("\n")
      quit(0)

proc endsWith*(x: string, s: set[char]): bool =
  var i = x.len-1
  while i >= 0 and x[i] == ' ': dec(i)
  if i >= 0 and x[i] in s:
    result = true

const
  LineContinuationOprs = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^',
                          '|', '%', '&', '$', '@', '~', ','}
  AdditionalLineContinuationOprs = {'#', ':', '='}

proc endsWithOpr*(x: string): bool =
  # also used by the standard template filter:
  result = x.endsWith(LineContinuationOprs)

proc continueLine(line: string, inTripleString: bool): bool {.inline.} =
  result = inTripleString or
      line[0] == ' ' or
      line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs)

proc countTriples(s: string): int =
  var i = 0
  while i < s.len:
    if s[i] == '"' and s[i+1] == '"' and s[i+2] == '"':
      inc result
      inc i, 2
    inc i

proc llReadFromStdin(s: PLLStream, buf: pointer, bufLen: int): int =
  s.s = ""
  s.rd = 0
  var line = newStringOfCap(120)
  var triples = 0
  while readLineFromStdin(if s.s.len == 0: ">>> " else: "... ", line):
    add(s.s, line)
    add(s.s, "\n")
    inc triples, countTriples(line)
    if not continueLine(line, (triples and 1) == 1): break
  inc(s.lineOffset)
  result = min(bufLen, len(s.s) - s.rd)
  if result > 0:
    copyMem(buf, addr(s.s[s.rd]), result)
    inc(s.rd, result)

proc llStreamRead*(s: PLLStream, buf: pointer, bufLen: int): int =
  case s.kind
  of llsNone:
    result = 0
  of llsString:
    result = min(bufLen, len(s.s) - s.rd)
    if result > 0:
      copyMem(buf, addr(s.s[0 + s.rd]), result)
      inc(s.rd, result)
  of llsFile:
    result = readBuffer(s.f, buf, bufLen)
  of llsStdIn:
    result = llReadFromStdin(s, buf, bufLen)

proc llStreamReadLine*(s: PLLStream, line: var string): bool =
  setLen(line, 0)
  case s.kind
  of llsNone:
    result = true
  of llsString:
    while s.rd < len(s.s):
      case s.s[s.rd]
      of '\x0D':
        inc(s.rd)
        if s.s[s.rd] == '\x0A': inc(s.rd)
        break
      of '\x0A':
        inc(s.rd)
        break
      else:
        add(line, s.s[s.rd])
        inc(s.rd)
    result = line.len > 0 or s.rd < len(s.s)
  of llsFile:
    result = readLine(s.f, line)
  of llsStdIn:
    result = readLine(stdin, line)

proc llStreamWrite*(s: PLLStream, data: string) =
  case s.kind
  of llsNone, llsStdIn:
    discard
  of llsString:
    add(s.s, data)
    inc(s.wr, len(data))
  of llsFile:
    write(s.f, data)

proc llStreamWriteln*(s: PLLStream, data: string) =
  llStreamWrite(s, data)
  llStreamWrite(s, "\n")

proc llStreamWrite*(s: PLLStream, data: char) =
  var c: char
  case s.kind
  of llsNone, llsStdIn:
    discard
  of llsString:
    add(s.s, data)
    inc(s.wr)
  of llsFile:
    c = data
    discard writeBuffer(s.f, addr(c), sizeof(c))

proc llStreamWrite*(s: PLLStream, buf: pointer, buflen: int) =
  case s.kind
  of llsNone, llsStdIn:
    discard
  of llsString:
    if buflen > 0:
      setLen(s.s, len(s.s) + buflen)
      copyMem(addr(s.s[0 + s.wr]), buf, buflen)
      inc(s.wr, buflen)
  of llsFile:
    discard writeBuffer(s.f, buf, buflen)

proc llStreamReadAll*(s: PLLStream): string =
  const
    bufSize = 2048
  case s.kind
  of llsNone, llsStdIn:
    result = ""
  of llsString:
    if s.rd == 0: result = s.s
    else: result = substr(s.s, s.rd)
    s.rd = len(s.s)
  of llsFile:
    result = newString(bufSize)
    var bytes = readBuffer(s.f, addr(result[0]), bufSize)
    var i = bytes
    while bytes == bufSize:
      setLen(result, i + bufSize)
      bytes = readBuffer(s.f, addr(result[i + 0]), bufSize)
      inc(i, bytes)
    setLen(result, i)