AI
Animation
CGI
Compression
Console
Database
Debug
Dialects
Dialogs
Editor
Email
Encryption
Extension
External Library
File
File Handling
Files
Financial
FTP
Game
Games
Graphics
GUI
HTML
HTTP
Internet
LDC
Markup
Math
Module
Network
Networking
None
Other - Net
Parse
Patch
Printing
Protocol
Rebol
Scheme
Scientific
SDK
Security
Shell
Sound
SQL
TCP
Testing
Text
Text Processing
UI
User Interface
Util
Utility
VID
Visualization
Web
Win API
X-File
XML
REBOL [
    title: "Simple-Test"
    version: 0.4.1
    date: 12-Mar-2011
    author: Peter W A Wood
    file: %simple-test.r
    purpose: {A simple Rebol testing framework}
    library: [
      level: 'intermediate
      platform: 'all
      type: [package tool]
      domain: [test parse]
      license: 'mit
  ]
]

simple-test: make object! [
  
  ;; copy the built-in now function for use in case tests overwrite it
  test-now: :now
  
  ;; copy the built-in print function for use in case tests overwrite it
  test-print: :print
  
  ;; if the disarm function is not defined assume we are are running under R3
  ;;  define a disarm function
  if not word? 'disarm [
    disarm: func [value][:value]
  ]  
  
  ;; verbose flag to control amount of output
  verbose: false
  
  ;; overall counts
  final-tests: 0
	final-passed: 0
	final-failed: 0
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;; eval-case object  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  ;; Holds the parse rules for evaluate-case
  eval-case: make object! [
    
	  ;; local variables
	  assertion-no: 0
	  name: none
	  name-not-printed: true
	  result: none
	  result-type: none
	  run-time: none
	  timestamp: none
	  assertion-no: 0
	  actual: none
	  actual-result-type: none
	  expected: none
	  expected-result-type: none
	  tolerance: none
	  tolerance-result-type: none
	  any-failures: false
	  response: none
	  test-result: none
	  tr: none
	    
	  ;; "private" methods
	  assert-act-exp-action: func [
	    action [block!]
	    /local
	    rb                            ;; result block
	    res                           ;; test result
	    assertion                     ;; the asertion to be made
	  ][
	    inc-assertion-no
      get-actual-result
      get-expected-result
      assertion: copy [:actual :expected]
      insert assertion action
      either all [
        equal? :actual-result-type "normal"
        equal? :expected-result-type "normal"
        do assertion
      ][
        res: "passed"
      ][
        res: "failed"
      ]
      rb: reduce [
        'result :res
        'actual mold :actual
        'actual-restype :actual-result-type
        'expected mold :expected
        'expected-restype :expected-result-type
      ]   
      
      append test-result/assertions reduce [to-word join "a" assertion-no rb]
	  ]
	  
	  assert-result-type-action: func [
	    expected-result-type [string!]
	    /local
	    rb                            ;; result block
	    res                           ;; test result
	  ] [
	    inc-assertion-no
	    get-actual-result
	    	    
	    either equal? expected-result-type actual-result-type [
	      res: "passed"
	    ][
	      res: "failed"
	    ]
	    rb: reduce [
        'result :res
        'actual mold :actual
        'actual-restype :actual-result-type
      ]   
      
      append test-result/assertions reduce [to-word join "a" assertion-no rb]
	    
	  ]
	  
	  assert-equal-tolerance-action: func [
	    /local
	    rb                            ;; result block
	    res                           ;; test result
	  ][
	    inc-assertion-no
      get-actual-result
      get-expected-result
      get-tolerance-result
      
      either all [
        equal? :actual-result-type "normal"
        equal? :expected-result-type "normal"
        equal? :tolerance-result-type "normal"
        number? :actual
        number? :expected
        number? :tolerance
        tolerance >= abs (actual - expected)  
      ][
        res: "passed"
      ][
        res: "failed"
      ]
      rb: reduce [
        'result :res
        'actual mold :actual
        'actual-restype :actual-result-type
        'expected mold :expected
        'expected-restype :expected-result-type
        'tolerance mold :tolerance
        'tolerance-restype :tolerance-result-type
      ]   
      
      append test-result/assertions reduce [to-word join "a" assertion-no rb]
	  ]
	  
	  assert-result-type-action: func [
	    expected-result-type [string!]
	    /local
	    rb                            ;; result block
	    res                           ;; test result
	  ] [
	    inc-assertion-no
	    get-actual-result
	    	    
	    either equal? expected-result-type actual-result-type [
	      res: "passed"
	    ][
	      res: "failed"
	    ]
	    rb: reduce [
        'result :res
        'actual mold :actual
        'actual-restype :actual-result-type
      ]   
      
      append test-result/assertions reduce [to-word join "a" assertion-no rb]
	    
	  ]
	  
	  assert-not-error-action: func [
	    /local
	    rb                            ;; result block
	    res                           ;; test result
	  ] [
	    inc-assertion-no
	    get-actual-result
	    	    
	    either not equal? actual-result-type "error" [
	      res: "passed"
	    ][
	      res: "failed"
	    ]
	    rb: reduce [
        'result :res
        'actual mold :actual
        'actual-restype :actual-result-type
      ]   
      
      append test-result/assertions reduce [to-word join "a" assertion-no rb]
	    
	  ]
	  
	  assert-logic-action: func [
      /assert-false
	    /local
	    rb                            ;; result block
	    res                           ;; test result
	  ][
	    inc-assertion-no
	    get-actual-result
	    
	    either actual-result-type = "normal" [
	      either assert-false [
	        res: either actual =  false ["passed"] ["failed"]
	      ][
	        res: either actual = true  ["passed"] ["failed"]
	      ]
	    ][
	      res: "failed"
	    ]
	    
	    rb: reduce [
	      'result :res
	      'actual mold :actual
	      'actual-restype :actual-result-type
	    ]
	    
	    append test-result/assertions reduce [to-word join "a" assertion-no rb]
	  ]
	  
	  get-actual-result: does [
	    ;; get the actual result
      either all [
        unset! <> type? first actual-block 
        equal? 'do first actual-block
        equal? 1 length? actual-block
      ][
        actual: :tr
        actual-result-type: select test-result 'result-type
      ][
        response: evaluate :actual-block
        actual: select response 'result
        actual-result-type: :response/result-type
      ]
	  ]
	  
	  get-expected-result: does [
	    ;; evaluate the expected result
      response: evaluate :expected-block
      expected: select response 'result
      expected-result-type: :response/result-type
    ]
    
    get-tolerance-result: does [
	    ;; evaluate the tolerance result
      response: evaluate :tolerance-block
      tolerance: select response 'result
      tolerance-result-type: :response/result-type
    ]
    
    inc-assertion-no: does [
     assertion-no: add assertion-no 1 
    ]
    
	  init: does [
	    assertion-no: 0
	    name: none
	    actual: none
	    actual-result-type: none
	    expected: none
	    expected-result-type: none
	    tolerance: none
	    tolerance-result-type: none
	    test-result: copy [
	      status "normal"
	      case "not set"
	      timestamp "not set"
	      run-time "not set"
	      result "not set"
	      result-type "not set"
	      assertions "not set"
	    ]
	    test-result/assertions: copy []
	  ]
	  

    ;; object parse rules
    ;; name-rule - checks for properly formatted name
    name-rule: [
      'name string! 
    ]
    
    ;; setup-rule - evaluates any supplied setup code
    setup-rule: [
      'setup set setup [block!] (
        response: evaluate :setup
        if equal? :response/result-type "error" [
          test-result/status: "setup failure"
        ]
      )
    ]
    
    ;; teardown-rule - evaluates any supplied teardown code
    teardown-rule: [
      'teardown set teardown [block!] (
        response: evaluate :teardown
        if equal? :response/result-type "error" [
          either equal? test-result/status "setup failure" [
            test-result/status: "setup & teardown failure"
          ][
            test-result/status: "teardown failure"
          ]
        ]
      )
    ]
    
    ;; do-rule - evaluates the code being tested (the do block)
    do-rule: [
      'do set do-block [block!] (
        response: evaluate :do-block
        test-result/timestamp: mold :response/timestamp
        test-result/run-time: mold :response/run-time
        tr: select response 'result
        test-result/result: mold :tr
        test-result/result-type: :response/result-type
      )
    ]
    
    ;; assert-rule - evaluates an assertion supplied to check the test
    assert-rule: [
      assert-equal-rule
      |
      assert-equal-tolerance-rule
      |
      assert-error-rule
      |
      assert-false-rule
      |
      assert-not-equal-rule
      |
      assert-not-error-rule
      |
      assert-not-same-rule
      |
      assert-same-rule
      |
      assert-true-rule
      |
      assert-unset-rule
    ]
    
    ;; assert sub-rules
    assert-equal-rule: [
      'assert 'equal set actual-block block! set expected-block block! (
        assert-act-exp-action [equal?]
      )
    ]
    
    assert-equal-tolerance-rule: [
      'assert 'equal opt 'with 'tolerance 
      set actual-block block!
      set expected-block block!
      set tolerance-block block! (
        assert-equal-tolerance-action 
      )
    ]
    
    assert-error-rule: [
      'assert 'error set actual-block block! (
        assert-result-type-action "error"
      )
    ]
    
    assert-false-rule: [
      'assert 'false set actual-block block! (
        assert-logic-action/assert-false
      )
    ]
    
    assert-not-equal-rule: [
      'assert 'not 'equal set actual-block block! set expected-block block! (
        assert-act-exp-action [not equal?]
      )
    ]
    
    assert-not-error-rule: [
      'assert 'not 'error set actual-block block! (assert-not-error-action)
    ]
    
    assert-not-same-rule: [
      'assert 'not 'same set actual-block block! set expected-block block! (
        assert-act-exp-action [not same?]
      )
    ]
    
    assert-same-rule: [
      'assert 'same set actual-block block! set expected-block block! (
        assert-act-exp-action [same?]
      )
    ]
      
    assert-true-rule: [
      'assert 'true set actual-block block! (
        assert-logic-action
      )
    ]
    
    assert-unset-rule: [
      'assert 'unset set actual-block block! (
        assert-result-type-action "unset"
      )
    ]
    
    ; MAIN RULE
    rules: [
      name-rule 
      opt setup-rule
      do-rule
      some assert-rule
      opt teardown-rule
    ]
    
  ] ;; end eval-case object
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;; eval-set object  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  ;; Holds the parse rules for evaluate-set
  eval-set: make object! [
    
	  ;; local variables
	  name: none
	  setup-each: none
	  teardown-each: none
	  teardown-once: none
	  no-tests: 0
	  passes: 0
	  failures: 0
	  any-failures: false
	  
	  ;; "private" methods
	  init: does [
	    name: none
	    setup-each: none
	    teardown-each: none
	    teardown-once: none
	    no-tests: 0
	    passes: 0
	    failures: 0
	    simple-test/verbose: false
	    any-failures: false
	  ]
	  
	  perform-setup-each: does [
	    response: evaluate :setup-each
      if equal? :response/result-type "error" [
        test-print ["^-Setup each failed"]
      ]
	  ]
	  
	  perform-teardown-each: does [
	    response: evaluate :teardown-each
      if equal? :response/result-type "error" [
        test-print ["^-Teardown each failed"]
      ]
	  ]
	  
	  
	  print-type-value: func [act-exp [string!] type [string!] val [string!]][
	    switch  type [
	      "normal" [
	        test-print rejoin [
	          "^-" :act-exp " - type - " type? do val "^/^-" val
	        ]
	      ]
	      "error" [
	        test-print rejoin ["^-" :act-exp " - type - error!"]
	        test-print join "^-" val
	      ]
	      "unset" [
	        test-print  rejoin ["^-" :act-exp " - type - unset!"]
	      ]
	    ]
	  ]
	  
	  process-case-result: func [
	    cr [block!]
	  ][
	    if cr/status = "Invalid test case" [
	      test-print join "^/" [cr/status]
	      test-print rejoin ["^-" mold cr/case]
	      return none
	    ]
	    
	    ;; any failures ?
	    any-failures: false
	    foreach [a-no a-blk] cr/assertions [ 
	      if not equal? a-blk/result "passed" [any-failures: true]
	    ]
	    
	    either any-failures [
	      failures: add failures 1
	    ][
	      passes: add passes 1
	    ]
	    
	    ;; print test case name if required	    
	    if any [
	      any-failures
	      not equal? cr/status "normal"
	      simple-test/verbose
	    ][
	      test-print rejoin [
	        "^/Test - " cr/case/name 
	        either any-failures [" - *** failed ***"][" - passed"]
	      ]
	    ]
	    
	    if not equal? cr/status "normal" [test-print join "^-" cr/status]
	    
	    ;; print test case result if required
	    if any [
	      any-failures
	      simple-test/verbose
	    ][
	      test-print join "" [
	        "^-On " cr/timestamp "^/"
	        "^-Took " cr/run-time
	      ]
	    
	      foreach [a-no a-blk] cr/assertions [
	        test-print rejoin [
	          "^-Assertion " 
	          remove to-string a-no                  ;; strip off leading a
	          " " a-blk/result
	        ]
	        if not equal? a-blk/result "passed" [
	          print-type-value "actual" a-blk/actual-restype a-blk/actual
	          
	          if find a-blk 'expected [
	            print-type-value "expected" a-blk/expected-restype a-blk/expected
	          ]
	        ]	      
	      ]
	    ]
	  ]
	      
	  teardown-and-print: does [
	    if teardown-once [
	      response: evaluate teardown-once
        if equal? :response/result-type "error" [
          test-print ["^-Teardown once failed"]
        ]
      ]
	    test-print join "Totals^/" [
	      "^-Tests  = " no-tests #"^/"
	      "^-Passed = " passes #"^/"
	      "^-Failed = " failures
	    ]
	  ]
	  
    ;; object parse rules
    ;; name-rule - stores the test name 
    name-rule: [
      'set 'name set name string! (
        test-print join "Test Set " [name]
      )
    ]
    
    ;; setup-each-rule - stores the setup code
    setup-each-rule: [
      'setup 'each set setup-each block!
    ]
    
    ;; setup-once-rule - evaluates any supplied setup code
    setup-once-rule: [
      'setup 'once set setup block! (
        response: evaluate :setup
        if equal? :response/result-type "error" [
          test-print ["^-Setup once failed"]
        ]
      )
    ]
    
    ;; teardown-each-rule - stores the teardown code
    teardown-each-rule: [
      'teardown 'each set teardown-each block!
    ]
    
    ;; teardown-once-rule - stores any teardown code to run after test cases
    teardown-once-rule: [
      'teardown 'once set teardown-once block!
    ]
    
    ;; test-case rule - evaluates a test case
    test-case-rule: [
      'test 'case set test-case block! (
        no-tests: no-tests + 1
        if setup-each [
          perform-setup-each
        ]
        
        process-case-result evaluate-case :test-case
        
        if teardown-each [
          perform-teardown-each
        ]
      )
    ]
    
    ; MAIN RULE
    rules: [
      (init)
      opt ['verbose (simple-test/verbose: true)]
      name-rule 
      opt setup-once-rule
      opt setup-each-rule
      opt teardown-each-rule
      opt teardown-once-rule
      some test-case-rule
      end (teardown-and-print)
    ]
    
  ] ;; end eval-set object
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  
    
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;; evaluate function  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  evaluate: func [
    {
      Evaluates the supplied code and returns a rebol block 
      about the evaluation:
        [
          code-block - block! - the code block evaluated
          timestamp - date! -  the time of evaluation
          run-time - time! - the execution time of the evaluation
          result - any! - the result of the evaluation
                        - this will be an error object if an error occurred
                        - none if the result is unset
          result-type - "normal" - evaluation produced a result
                      - "error" - an error occurred during evalutaion
                      - "unset" - the evaluation returned unset
        ]
    }
    code-block [block!]       ; Format [code]
    /local
    timestamp                 ; The time of evaluation
    start                     ; The start time of evaluation
    end                       ; The end time of evaluation
    run-time                  ; The time taken to perform the evaluation
    result                    ; The result of the evaluation
    result-type               ; "normal", "error" or "unset"
    error                     ; set if error occured
  ][
    ;; initialisations
    timestamp: none
    start: none
    end: none
    run-time: none
    result: none
    result-type: copy "normal"
    error: none
    ;; evaluate the code
    timestamp: test-now
    start: test-now/precise
    if error? set/any 'result try code-block [
      ;; catch errors in the evaluation of the code block
      result: disarm result
      result-type: copy "error"
    ]
    end: test-now/precise
    if all [
      :result-type <> "error"
      error? set/any 'result try [:result]
    ][
      result: none
      result-type: copy "unset"
    ]

    run-time: difference end start
    
    ;; create and return the output
    reduce [
      'code-block :code-block 'timestamp :timestamp
      'run-time :run-time 'result :result 'result-type :result-type
    ]
  
  ] ;; end of evaluate function
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;; evaluate-case ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  evaluate-case: func [
    { 
      Evaluates a single test case presented in the following dialect:
            name "test identifer"
  	        opt setup [setup code]
            do [the code being tested - this will be timed]
            some assert-XXXXX [assertions to check the result]
            opt teardown [teardown code]
    }
	  the-test [block!]
  ][
    eval-case/init
    eval-case/test-result/case: copy/deep :the-test
    either parse :the-test :eval-case/rules [
      get in eval-case 'test-result
    ][
      reduce [
        'status "Invalid test case"
        'case :the-test
      ]
    ]
	  
  ] ;; end of evaluate-case
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;; evaluate-set function  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  evaluate-set: func [
  	{ Evaluates a set of tests }
	  test-set [block!]             ; Format: [command [attributes]]
  ][
    either parse test-set eval-set/rules [
      final-tests: add final-tests eval-set/no-tests
      final-passed: add final-passed eval-set/passes
      final-failed: add final-failed eval-set/failures
      reduce [
        'name eval-set/name
        'tests eval-set/no-tests
        'passed eval-set/passes
        'failed eval-set/failures
      ]
    ][
      test-print "Test halted - syntax error"
      false
    ]
    
    
  ] ;; end of evaluate-set
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  
  ;;;;;;;;;;;;;;;;;; init-final-totals function  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  init-final-totals: does [
    final-tests: 0
	  final-passed: 0
	  final-failed: 0
  ] ;; end of init-final-totals
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  ;;;;;;;;;;;;;;;;;; print-final-totals function  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  print-final-totals: does[
    test-print ""
    test-print join "Overall Tests " final-tests
	  test-print join "       Passed " final-passed
	  test-print join "       Failed " final-failed
  ] ;; end of print-final-totals
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;; run-tests function  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  run-tests: func [
  	{ Runs tests - either a set or suite of tests using recursion }
	  tests [file!]
  ][
    test-data: load tests
    either equal? 'suite first test-data [
      foreach suite-or-set second test-data [
        run-tests suite-or-set
      ]
    ][
      simple-test/evaluate-set test-data
    ] 
  ] ;; end of run-tests
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

] ;; end of test context!

run-test: func [
  { A wrapper for tests/run-tests in the global context }
  tests [file!]
][
  simple-test/init-final-totals
  simple-test/run-tests tests
  simple-test/print-final-totals
  exit
]


            
            
        
Copyright © 2018 Rebol Software Foundation