summaryrefslogtreecommitdiff
blob: e85cd61a8efead620bee2c9f34e7de169269fb31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
(use-modules (ice-9 textual-ports))
(use-modules (ice-9 popen))
(use-modules (gnucash utilities))
(use-modules (gnucash gnc-module))
(gnc:module-begin-syntax (gnc:module-load "gnucash/app-utils" 0))
(use-modules (gnucash engine test test-extras))
(use-modules (gnucash report standard-reports))
(use-modules (gnucash report business-reports))
(use-modules (gnucash report view-column))
(use-modules (gnucash report stylesheets))
(use-modules (gnucash report taxinvoice))
(use-modules (gnucash report report-system))
(use-modules (gnucash report report-system test test-extras))
(use-modules (srfi srfi-64))
(use-modules (srfi srfi-98))
(use-modules (gnucash engine test srfi64-extras))
(use-modules (sxml simple))
(use-modules (sxml xpath))

;; NOTE
;; ----
;; SIMPLE stress tests by default
;;
;; PAIRWISE COMBINATORICS are enabled by setting environment variable COMBINATORICS
;; to the fullpath for the compiled jenny from http://burtleburtle.net/bob/math/jenny.html
;;
;; e.g. COMBINATORICS=/home/user/jenny/jenny ninja check

(define optionslist '())

(define (generate-optionslist)
  (gnc:report-templates-for-each
   (lambda (report-id template)
     (let* ((options-generator (gnc:report-template-options-generator template))
            (name (gnc:report-template-name template))
            (options (options-generator)))
       (set! optionslist
         (cons (list (cons 'report-id report-id)
                     (cons 'report-name (gnc:report-template-name template))
                     (cons 'options (let ((report-options-tested '()))
                                      (gnc:options-for-each
                                       (lambda (option)
                                         (when (memq (gnc:option-type option)
                                                     '(multichoice boolean))
                                           (set! report-options-tested
                                             (cons (vector
                                                    (gnc:option-section option)
                                                    (gnc:option-name option)
                                                    (gnc:option-type option)
                                                    (case (gnc:option-type option)
                                                      ((multichoice) (map (lambda (d) (vector-ref d 0))
                                                                          (gnc:option-data option)))
                                                      ((boolean) (list #t #f))))
                                                   report-options-tested))))
                                       options)
                                      report-options-tested)))
               optionslist))))))

;; Explicitly set locale to make the report output predictable
(setlocale LC_ALL "C")

(define (run-test)
  (test-runner-factory gnc:test-runner)
  (test-begin "stress options")
  (generate-optionslist)
  (tests)
  (test-end "stress options"))

(define jennypath
  (get-environment-variable "COMBINATORICS"))

(define jenny-exists?
  ;; this is a simple test for presence of jenny - will check
  ;; COMBINATORICS env exists, and running it produces exit-code of
  ;; zero, and tests the first few letters of its output.
  (and (string? jennypath)
       (zero? (system jennypath))
       (string=? (string-take (get-string-all (open-input-pipe jennypath)) 6)
                 "jenny:")))

(define (set-option! options section name value)
  (let ((option (gnc:lookup-option options section name)))
    (if option
        (gnc:option-set-value option value))))

(define (mnemonic->commodity sym)
  (gnc-commodity-table-lookup
   (gnc-commodity-table-get-table (gnc-get-current-book))
   (gnc-commodity-get-namespace (gnc-default-report-currency))
   sym))

(define structure
  (list "Root" (list (cons 'type ACCT-TYPE-ASSET))
        (list "Asset"
              (list "Bank")
              (list "GBP Bank" (list (cons 'commodity (mnemonic->commodity "GBP"))))
              (list "Wallet"))
        (list "Income" (list (cons 'type ACCT-TYPE-INCOME)))
        (list "Income-GBP" (list (cons 'type ACCT-TYPE-INCOME)
                                 (cons 'commodity (mnemonic->commodity "GBP"))))
        (list "Expenses" (list (cons 'type ACCT-TYPE-EXPENSE)))
        (list "Liabilities" (list (cons 'type ACCT-TYPE-LIABILITY)))
        (list "Equity" (list (cons 'type ACCT-TYPE-EQUITY)))
        ))

(define (simple-stress-test report-name uuid report-options)
  (let ((options (gnc:make-report-options uuid)))
    (test-assert (format #f "basic test ~a" report-name)
      (gnc:options->render uuid options (string-append "stress-" report-name) "test"))
    (format #t "Testing SIMPLE combinations for:\n~a" report-name)
    (for-each
     (lambda (option)
       (format #t ",~a/~a"
               (vector-ref option 0)
               (vector-ref option 1)))
     report-options)
    (newline)
    (for-each
     (lambda (idx)
       (display report-name)
       (for-each
        (lambda (option)
          (let* ((section (vector-ref option 0))
                 (name (vector-ref option 1))
                 (value (list-ref (vector-ref option 3)
                                  (modulo idx (length (vector-ref option 3))))))
            (set-option! options section name value)
            (format #t ",~a"
                    (cond
                     ((boolean? value) (if value 't 'f))
                     (else value)))))
        report-options)
       (catch #t
         (lambda ()
           (gnc:options->render uuid options "stress-test" "test")
           (display "[pass]\n"))
         (lambda (k . args)
           (format #t "[fail]... error: (~s . ~s) options-list are:\n~a"
                   k args
                   (gnc:html-render-options-changed options #t))
           (test-assert "logging test failure as above..."
             #f))))
     (iota
      (apply max
             (map (lambda (opt) (length (vector-ref opt 3)))
                  report-options)))
     )))

(define (combinatorial-stress-test report-name uuid report-options)
  (let* ((options (gnc:make-report-options uuid))
         (render #f))
    (test-assert (format #f "basic test ~a" report-name)
      (set! render
        (gnc:options->render
         uuid options (string-append "stress-" report-name) "test")))
    (if render
        (begin
          (format #t "Testing n-tuple combinatorics for:\n~a" report-name)
          (for-each
           (lambda (option)
             (format #t ",~a/~a"
                     (vector-ref option 0)
                     (vector-ref option 1)))
           report-options)
          (newline)
          ;; generate combinatorics
          (let* ((option-lengths (map (lambda (report-option)
                                        (length (vector-ref report-option 3)))
                                      report-options))
                 (jennyargs (string-join (map number->string option-lengths) " "))
                 (n-tuple (min
                           ;; the following is the n-tuple
                           2
                           (length report-options)))
                 (cmdline (format #f "~a -n~a ~a"
                                  jennypath n-tuple jennyargs))
                 (jennyout (get-string-all (open-input-pipe cmdline)))
                 (test-cases (string-split jennyout #\newline)))
            (for-each
             (lambda (case)
               (unless (string-null? case)
                 (let* ((choices-str (string-filter char-alphabetic? case))
                        (choices-alpha (map char->integer (string->list choices-str)))
                        (choices (map (lambda (n)
                                        (- n (if (> n 96) 97 39))) ; a-z -> 0-25, and A-Z -> 26-51
                                      choices-alpha)))
                   (let loop ((option-idx (1- (length report-options)))
                              (option-summary '()))
                     (if (negative? option-idx)
                         (catch #t
                           (lambda ()
                             (gnc:options->render uuid options "stress-test" "test")
                             (format #t "[pass] ~a:~a \n"
                                     report-name
                                     (string-join option-summary ",")))
                           (lambda (k . args)
                             (format #t "[fail]... error (~s . ~s) options-list are:\n~a"
                                     k args
                                     (gnc:html-render-options-changed options #t))
                             (test-assert "logging test failure as above..."
                               #f)))
                         (let* ((option (list-ref report-options option-idx))
                                (section (vector-ref option 0))
                                (name (vector-ref option 1))
                                (value (list-ref (vector-ref option 3)
                                                 (list-ref choices option-idx))))
                           (set-option! options section name value)
                           (loop (1- option-idx)
                                 (cons (format #f "~a"
                                               (cond
                                                ((boolean? value) (if value 't 'f))
                                                (else value)))
                                       option-summary))))))))
             test-cases)))
        (display "...aborted due to basic test failure"))))

(define test
  ;; what strategy are we using here? simple stress test (ie tests as
  ;; many times as the maximum number of options) or combinatorial
  ;; tests (using jenny)
  (if jenny-exists?
      combinatorial-stress-test
      simple-stress-test))

(define (create-test-data)
  (let* ((env (create-test-env))
         (account-alist (env-create-account-structure-alist env structure))
         (bank (cdr (assoc "Bank" account-alist)))
         (gbp-bank (cdr (assoc "GBP Bank" account-alist)))
         (wallet (cdr (assoc "Wallet" account-alist)))
         (income (cdr (assoc "Income" account-alist)))
         (gbp-income (cdr (assoc "Income-GBP" account-alist)))
         (expense (cdr (assoc "Expenses" account-alist)))
         (liability (cdr (assoc "Liabilities" account-alist)))
         (equity (cdr (assoc "Equity" account-alist))))
    ;; populate datafile with old transactions
    (env-transfer env 01 01 1970 bank expense       5   #:description "desc-1" #:num "trn1" #:memo "memo-3")
    (env-transfer env 31 12 1969 income bank       10   #:description "desc-2" #:num "trn2" #:void-reason "void" #:notes "notes3")
    (env-transfer env 31 12 1969 income bank       29   #:description "desc-3" #:num "trn3"
                  #:reconcile (cons #\c (gnc-dmy2time64 01 03 1970)))
    (env-transfer env 01 02 1970 bank expense      15   #:description "desc-4" #:num "trn4" #:notes "notes2" #:memo "memo-1")
    (env-transfer env 10 01 1970 liability expense 10   #:description "desc-5" #:num "trn5" #:void-reason "any")
    (env-transfer env 10 01 1970 liability expense 11   #:description "desc-6" #:num "trn6" #:notes "notes1")
    (env-transfer env 10 02 1970 bank liability     8   #:description "desc-7" #:num "trn7" #:notes "notes1" #:memo "memo-2"
                  #:reconcile (cons #\y (gnc-dmy2time64 01 03 1970)))
    (let ((txn (xaccMallocTransaction (gnc-get-current-book)))
          (split-1 (xaccMallocSplit  (gnc-get-current-book)))
          (split-2 (xaccMallocSplit  (gnc-get-current-book)))
          (split-3 (xaccMallocSplit  (gnc-get-current-book))))
      (xaccTransBeginEdit txn)
      (xaccTransSetDescription txn "$100bank -> $80expenses + $20wallet")
      (xaccTransSetCurrency txn (xaccAccountGetCommodity bank))
      (xaccTransSetDate txn 14 02 1971)
      (xaccSplitSetParent split-1 txn)
      (xaccSplitSetParent split-2 txn)
      (xaccSplitSetParent split-3 txn)
      (xaccSplitSetAccount split-1 bank)
      (xaccSplitSetAccount split-2 expense)
      (xaccSplitSetAccount split-3 wallet)
      (xaccSplitSetValue split-1 -100)
      (xaccSplitSetValue split-2 80)
      (xaccSplitSetValue split-3 20)
      (xaccSplitSetAmount split-1 -100)
      (xaccSplitSetAmount split-2 80)
      (xaccSplitSetAmount split-3 20)
      (xaccTransSetNotes txn "multisplit")
      (xaccTransCommitEdit txn))
    (let ((closing-txn (env-transfer env 31 12 1977 expense equity 111 #:description "Closing")))
      (xaccTransSetIsClosingTxn closing-txn #t))
    (env-transfer-foreign env 15 01 2000 gbp-bank bank 10 14 #:description "GBP 10 to USD 14")
    (env-transfer-foreign env 15 02 2000 bank gbp-bank  9  6 #:description "USD 9 to GBP 6")
    (for-each (lambda (m)
                (env-transfer env 08 (1+ m) 1978 gbp-income gbp-bank 51 #:description "#51 income")
                (env-transfer env 03 (1+ m) 1978 income bank  103 #:description "$103 income")
                (env-transfer env 15 (1+ m) 1978 bank expense  22 #:description "$22 expense")
                (env-transfer env 09 (1+ m) 1978 income bank  109 #:description "$109 income"))
              (iota 12))
    (let ((mid (floor (/ (+ (gnc-accounting-period-fiscal-start)
                            (gnc-accounting-period-fiscal-end)) 2))))
      (env-create-transaction env mid bank income 200))))

(define (run-tests prefix)
  (for-each
   (lambda (option-set)
     (let ((report-name (assq-ref option-set 'report-name))
           (report-guid (assq-ref option-set 'report-id))
           (report-options (assq-ref option-set 'options)))
       (if (member report-name
                   ;; these reports seem to cause problems when running...
                   '(
                     ;; eguile-based reports
                     "Tax Invoice"
                     "Receipt"
                     "Australian Tax Invoice"
                     "Balance Sheet (eguile)"

                     ;; tax-schedule - locale-dependent?
                     "Tax Schedule Report/TXF Export"

                     ;; unusual reports
                     "Welcome to GnuCash"
                     "Hello, World"
                     "Multicolumn View"
                     "General Journal"
                     ))
           (format #t "\nSkipping ~a ~a...\n" report-name prefix)
           (begin
             (format #t "\nTesting ~a ~a...\n" report-name prefix)
             (test report-name report-guid report-options)))))
   optionslist))

(define (tests)
  (run-tests "with empty book")
  (create-test-data)
  (run-tests "on a populated book"))