#lang racket

#|

This test checks:

  - if opening frames/tabs leaks,

  - if DrRacket writes to the filesystem during startup or
    while opening tabs/frames

  - if there are any adjacent separators in the menus

  - if there are any duplicate shortcuts in the menus

  - that the logger GUI is not completely broken (checks
    that it opens, that it catches a GC message, and that
    it closes)

|#

(require "private/drracket-test-util.rkt"
         drracket/private/local-member-names
         racket/gui/base
         framework
         string-constants)

(define (main)
  (parameterize ([current-security-guard
                  (make-security-guard
                   (current-security-guard)
                   (λ (who pth what)
                     (when (member 'write what)
                       (error who "Writing to the file system is not allowed"))
                     (when (member 'delete what)
                       (error who "Deleting files is not allowed")))
                   void
                   void)])
    (fire-up-drracket-and-run-tests 
     #:prefs '([plt:framework-pref:drracket:online-compilation-default-on #f]) 
     (λ ()
       (define drr (wait-for-drracket-frame))
       (check-log-panel drr)
       (check-menus drr)
       
       (try-to-find-leak "online compilation disabled:" void)
       
       (preferences:set 'drracket:online-compilation-default-on #t)
       
       (try-to-find-leak "online compilation enabled:" wait-for-online-compilation-to-finish)))))

(define (wait-for-online-compilation-to-finish frame) 
  (let loop ([i 0])
    (define current-colors (send frame get-online-expansion-colors))
    (unless (equal? current-colors '("forestgreen"))
      (sleep 1)
      (loop (+ i 1)))))

(define (try-to-find-leak online-compilation-string extra-waiting)
  (define drs-frame1 (wait-for-drracket-frame))
  (sync (system-idle-evt))

  (for ([tries (in-range 3)])
    (test:menu-select "File" "New Tab")
    (sync (system-idle-evt))
    
    (define drs-tabb (make-weak-box (send drs-frame1 get-current-tab)))
    (define tab-nsb (make-weak-box (send (send (send drs-frame1 get-current-tab) get-ints) get-user-namespace)))
    
    (test:menu-select "File" (if (eq? (system-type) 'unix) "Close" "Close Tab"))
    (sync (system-idle-evt))
    
    (test:menu-select "File" "New")
    (sync (system-idle-evt))
    
    (define drs-frame2b (make-weak-box (wait-for-new-frame drs-frame1)))
    (define frame2-nsb (make-weak-box (send (send (send (weak-box-value drs-frame2b) get-current-tab) get-ints) get-user-namespace)))
    
    (queue-callback/res
     (λ () (send (send (send (weak-box-value drs-frame2b) get-current-tab) get-defs) load-file
                 (collection-file-path "rep.rkt" "drracket" "private"))))
    (sleep 2)
    (extra-waiting (weak-box-value drs-frame2b))
    (sync (system-idle-evt))
    
    (test:menu-select "File" (if (eq? (system-type) 'unix) "Close" "Close Window"))
    (sync (system-idle-evt))
    
    (let loop ([n 30])
      (cond
        [(zero? n) 
         (when (weak-box-value drs-tabb)
           (eprintf "~a tab leak!\n" online-compilation-string))
         (when (weak-box-value drs-frame2b)
           (eprintf "~a frame leak!\n" online-compilation-string))
         (when (weak-box-value tab-nsb)
           (eprintf "~a tab namespace leak!\n" online-compilation-string))
         (when (weak-box-value frame2-nsb)
           (eprintf "~a frame namespace leak!\n" online-compilation-string))]
        [else
         (collect-garbage) (sync (system-idle-evt))
         (when (ormap weak-box-value 
                      (list drs-tabb
                            tab-nsb
                            drs-frame2b
                            frame2-nsb))
           (loop (- n 1)))]))))
  
(define (check-menus frame)
  
  (define shortcuts (make-hash))
  
  (define (process-container container)
    (define sub-items (send container get-items))
    (unless (null? sub-items)
      (record-shortcut (car sub-items))
      (when (is-a? (car sub-items) menu-item-container<%>)
        (process-container (car sub-items)))
      (define printed? #f)
      (for ([prev-item (in-list sub-items)]
            [item (in-list (cdr sub-items))])
        (record-shortcut item)
        (when (and (is-a? prev-item separator-menu-item%)
                   (is-a? item separator-menu-item%)
                   (not printed?))
          (set! printed? #t)
          (eprintf "found two adjacent separator items in: ~s:\n" (get-names container))
          (for ([item (in-list sub-items)])
            (eprintf "  ~a\n" (get-lab item)))
          (eprintf "\n"))
        (when (is-a? item menu-item-container<%>)
          (process-container item)))))
  
  (define (record-shortcut item)
    (when (is-a? item selectable-menu-item<%>)
      (when (send item get-shortcut)
        (define k (append (sort (send item get-shortcut-prefix)
                                string<=?
                                #:key symbol->string)
                          (list (send item get-shortcut))))
        (hash-set! shortcuts 
                   k
                   (cons (send item get-label)
                         (hash-ref shortcuts k '()))))))
  
  (define (get-lab item)
    (cond
      [(is-a? item labelled-menu-item<%>)
       (send item get-label)]
      [(is-a? item separator-menu-item%)
       "---------"]
      [else #f]))
  
  (define (get-names item)
    (let loop ([item item])
      (cond
        [(is-a? item menu-item<%>)
         (cons (get-lab item)
               (loop (send item get-parent)))]
        [else
         '()])))
  
  (define (check-shortcuts)
    (for ([(k v) (in-hash shortcuts)])
      (unless (= 1 (length v))
        (eprintf "found multiple menu items with the shortcut ~s: ~s\n"
                 k v))))
  
  (process-container (send frame get-menu-bar))
  (check-shortcuts))

(define (check-log-panel drr)
  
  (define (find-log-messages-message)
    (let loop ([p drr])
      (cond
        [(is-a? p area-container<%>)
         (for/or ([c (in-list (send p get-children))])
           (loop c))]
        [(is-a? p message%)
         (and (equal? (send p get-label) (string-constant log-messages))
              p)]
        [else #f])))
  
  (test:menu-select "View" "Show Log")
  
  ;; wait for the log window to show up.
  (poll-until
   (λ ()
     (queue-callback/res find-log-messages-message)))
  
  (collect-garbage)
  
  (define logger-string
    (poll-until
     (λ ()
       (define str
         (queue-callback/res
          (λ ()
            (define msg (find-log-messages-message))
            (define msg-parent-parent (send (send msg get-parent) get-parent))
            (let loop ([p msg-parent-parent])
              (cond
                [(is-a? p area-container<%>)
                 (for/or ([c (in-list (send p get-children))])
                   (loop c))]
                [(is-a? p editor-canvas%)
                 (send (send p get-editor) get-text)]
                [else #f])))))
       (and str
            (not (equal? str ""))
            str))))
  
  (unless (regexp-match #rx"GC: [0-9]+:MAJ" logger-string)
    (error 'check-log-panel "didn't find GC log message: ~s" logger-string))
  
  (test:menu-select "View" "Hide Log")
  
  (poll-until
   (λ ()
     (not (queue-callback/res find-log-messages-message)))))

(main)
