test-rope.ss
(module test-rope mzscheme
  (require (planet "test.ss" ("schematics" "schemeunit.plt" 2 8))
           (planet "text-ui.ss" ("schematics" "schemeunit.plt" 2 8))
           (planet "util.ss" ("schematics" "schemeunit.plt" 2 8))
           (planet "comprehensions.ss" ("dyoo" "srfi-alias.plt" 1))
           (lib "etc.ss")
           (lib "list.ss")
           
           "rope.ss")
  
  (require/expose "rope.ss" (make-rope:concat))
  (define (++ x y)
    (make-rope:concat x y (+ (rope-length x) (rope-length y))))
  
  
  (define (make-long-degenerate-rope)
    (define my-rope (string->rope ""))
    (do-ec (:range i 5000)
           (set! my-rope (++ my-rope
                             (string->rope (format "hello~a" i)))))
    my-rope)
  
  
  (define (read-file-as-rope filename)
    (call-with-input-file filename
      (lambda (ip)
        (let loop ([char (read-char ip)]
                   [a-rope (string->rope "")])
          (cond [(eof-object? char)
                 a-rope]
                [else
                 (loop (read-char ip)
                       (rope-append a-rope
                                    (string->rope (string char))))])))))
  
  (define sr string->rope)
  
  (provide rope-tests)
  (define rope-tests
    (test-suite
     "rope.ss"
     (test-case
      "rope?"
      (check-false (rope? "is this a rope?"))
      (check-true (rope? (string->rope "is this a rope?")))
      (check-true (rope? (rope-append (string->rope "hello ")
                                      (string->rope "world")))))
     
     (test-case
      "rope-append"
      (check-equal? (rope->string (rope-append (sr "") (sr "abcd")))
                    (rope->string (rope-append (sr "abcd") (sr "")))))
     
     (test-case
      "subrope checking bounds"
      (local ((define myrope (make-long-degenerate-rope)))
        (check-equal? (rope->string
                       (subrope myrope 0 18))
                      "hello0hello1hello2")
        (check-equal? (rope->string
                       (subrope myrope 1 18))
                      "ello0hello1hello2")
        (check-equal? (rope->string
                       (subrope myrope 3 18))
                      "lo0hello1hello2")
        (check-equal? (rope->string
                       (subrope myrope 6 18))
                      "hello1hello2")
        (check-equal? (rope->string
                       (subrope myrope 6 15))
                      "hello1hel")
        (check-equal? (rope->string
                       (subrope myrope 17 30))
                      "2hello3hello4")
        (check-equal? (rope->string
                       (subrope myrope 17 31))
                      "2hello3hello4h")))
     
     (test-case
      "balance"
      (parameterize ([current-optimize-flat-ropes #f])
        (check-equal? "abcdef"
                      (rope->string
                       (rope-balance (++ (sr "a")
                                         (++ (sr "bc")
                                             (++ (sr "d")
                                                 (sr "ef")))))))
        (check-equal? (rope-depth
                       (rope-balance (++ (sr "a")
                                         (++ (sr "bc")
                                             (++ (sr "d")
                                                 (sr "ef"))))))
                      2)))
     
     (test-case
      "rope-fold/leaves"
      (parameterize ([current-optimize-flat-ropes #f])
        (check-equal? (rope-fold/leaves (lambda (a-str acc)
                                          (cons a-str acc))
                                        '()
                                        (++ (sr "hello") (sr "world")))
                      (list "world" "hello"))))
     
     
     (test-case
      "rope-fold"
      (parameterize ([current-optimize-flat-ropes #f])
        (check-equal? (rope-fold (lambda (a-str acc)
                                   (cons a-str acc))
                                 '()
                                 (++ (sr "hello") (sr "world")))
                      (reverse
                       (list #\h #\e #\l #\l #\o
                             #\w #\o #\r #\l #\d)))))
     
     
     (test-case
      "open-input-rope"
      (parameterize ([current-optimize-flat-ropes #f])
        (check-equal?
         (regexp-match
          "abracadabra"
          (open-input-rope
           (++ (sr "a") (++ (sr "braca")
                            (++ (++ (sr "da") (sr "br")) (sr "a"))))))
         '(#"abracadabra"))))
     
     
     (test-case
      "rope-depth and balancing"
      (parameterize ([current-optimize-flat-ropes #f])
        (check-equal? (rope-depth (rope-balance (++ (sr "h0")
                                                    (sr "h1"))))
                      1)
        (check-equal? (rope-depth (rope-balance (++ (sr "h0")
                                                    (++ (sr "h1")
                                                        (sr "h2")))))
                      2)
        (check-equal?
         (rope-depth (rope-balance (++ (sr "h0")
                                       (++ (sr "h1")
                                           (++ (sr "h2") (sr "h3"))))))
         2)
        (check-equal? (rope-depth
                       (rope-balance
                        (++ (sr "h0") (++ (sr "h1")
                                          (++ (sr "h2")
                                              (++ (sr "h3")
                                                  (sr "h4")))))))
                      3)
        (check-equal?
         (rope-depth
          (rope-balance
           (++ (sr "h0")
               (++ (sr "h1")
                   (++ (sr "h2")
                       (++ (sr "h3") (++ (sr "h4") (sr "h5"))))))))
         3)))
     
     
     (test-case
      "rope-ref"
      (parameterize ([current-optimize-flat-ropes #f])
        (local ((define word-rope (++
                                   (++ (++ (sr "super")
                                           (sr "cali"))
                                       (++ (sr "fragil")
                                           (sr "istic")))
                                   (++ (sr "expiali")
                                       (sr "docious"))))
                (define word-string "supercalifragilisticexpialidocious"))
          (for-each (lambda (i ch)
                      (check-equal? (rope-ref word-rope i) ch))
                    (build-list (string-length word-string) (lambda (i) i))
                    (string->list word-string)))))
     
     (test-case
      "rope-ref and specials"
      (local ((define b (box "I am a box")))
        (check-eq? b (rope-ref (special->rope b) 0))
        (check-eq?
         b
         (rope-ref (rope-append (string->rope "rope ")
                                (special->rope b)) 5))))
     
     (test-case
      "rope-for-each"
      (parameterize ([current-optimize-flat-ropes #f])
        (local ((define seen-chars '()))
          (rope-for-each
           (lambda (ch/special)
             (set! seen-chars (cons ch/special seen-chars)))
           (++ (string->rope "lambda-the-")
               (string->rope "ultimate")))
          (check-equal?
           seen-chars
           (reverse (string->list "lambda-the-ultimate"))))))
     
     
     (test-case
      "rope-fold and specials"
      (local ((define mybox (box " "))
              (define rope-with-specials
                (rope-append (string->rope "hello")
                             (rope-append (special->rope mybox)
                                          (string->rope "world")))))
        (check-equal?
         (reverse (rope-fold cons '() rope-with-specials))
         (list #\h #\e #\l #\l #\o mybox #\w #\o #\r #\l #\d))))
     
     (test-case
      "rope-length"
      (local ((define a-rope
                (rope-append 
                 (string->rope 
                  "hello, this is a test of the emergency broadcast")
                 (string->rope "system; this is only a test"))))
        (check-equal? (rope-length a-rope) 75)))
     
     
     (test-case
      "rope-has-special?"
      (local ((define a-rope (rope-append
                              (string->rope "x")
                              (rope-append
                               (special->rope (box "I am a special"))
                               (string->rope "y")))))
        (check-true (rope-has-special? a-rope))
        (check-false (rope-has-special? (subrope a-rope 0 1)))
        (check-true (rope-has-special? (subrope a-rope 1)))
        (check-true (rope-has-special? (subrope a-rope 1 2)))
        (check-false (rope-has-special? (subrope a-rope 2)))))
     
     (test-case
      "ports and specials"
      (local ((define a-special 42)
              (define a-rope (rope-append
                              (string->rope "x")
                              (rope-append
                               (special->rope a-special)
                               (string->rope "y"))))
              (define inp (open-input-rope a-rope)))
        (check-equal? (read-byte-or-special inp)
                      (char->integer #\x))
        (check-eq? (read-byte-or-special inp)
                   a-special)
        (check-equal? (read-byte-or-special inp)
                      (char->integer #\y))
        (check-true (eof-object? (read-byte-or-special inp)))))
     
     
     (test-case
      "rope-node-count"
      (check-equal? (rope-node-count (string->rope "x")) 1)
      (check-equal? (rope-node-count (special->rope (box "x"))) 1)
      (check-equal? (rope-node-count
                     (rope-append (string->rope "x")
                                  (special->rope (box "x"))))
                    3))))
  
  
  (test/text-ui rope-tests))