summary refs log blame commit diff stats
path: root/lib/pure/unittest.nim
blob: d7abb8cfa671af5e2b40a79027b08e26a68262dc (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: expr): stmt =
    template rewrite(Exp, lineInfoLit: expr, expLit: string): stmt =
      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: expr): stmt =
        template rewrite(op, left, right, lineInfoLit: expr, opLit, leftLit, rightLit: string): stmt =
          block:
            var 
              lhs = left
              rhs = right

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

        result = getAst(rewrite(
          op[0], op[1], op[2],
          op.lineinfo,
          op.toStrLit,
          op[1].toStrLit,
          op[2].toStrLit))
        
      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): stmt =
    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")