-
Notifications
You must be signed in to change notification settings - Fork 6
/
company-statistics-tests.el
330 lines (301 loc) · 14.4 KB
/
company-statistics-tests.el
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
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
;;; company-statistics-tests.el --- company-statistics tests -*- lexical-binding: t -*-
;; Copyright (C) 2014-2015 Free Software Foundation, Inc.
;; Author: Ingo Lohmar
;; This file is part of GNU Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; emacs -batch -L . -L ../company-mode/ -l ert -l company-statistics-tests.el -f ert-run-tests-batch-and-exit
;;; Code:
(require 'ert)
(require 'company-statistics)
(setq company-statistics-auto-restore nil
company-statistics-auto-save nil)
(company-statistics-mode)
;;; Core
(defun my/hash-compare (h1 h2 &optional pred)
"Check that hashes H1 and H2 use the same test, contain the same keys (as
per that test), and that their stored values agree (as per PRED, which
defaults to `equal')."
(let ((key-test (hash-table-test h1))
(pred (or pred 'equal)))
(and (eq key-test (hash-table-test h2))
(eq (hash-table-count h1) (hash-table-count h2))
(let ((keys nil))
(maphash (lambda (k v) (push k keys)) h1) ;get keys
(null ;expect no mismatch
(catch 'mismatch
(while keys ;if this finishes, it's nil
(let* ((k (car keys))
(v1 (gethash k h1))
(v2 (gethash k h2)))
(setq keys (cdr keys))
(unless (funcall pred v1 v2)
(throw 'mismatch k))))))))))
(defun my/vector-slice-compare (v1 i1 v2 i2 count &optional pred)
"Check that COUNT vector entries of V1 (starting at index I1) and
V2 (starting at index I2) satisfy the binary predicate PRED, default
`equal'. Wraps around if index exceeds corresponding vector length."
(let ((pred (or pred 'equal)))
(null
(let ((l1 (length v1))
(l2 (length v2)))
(catch 'mismatch
(dolist (i (number-sequence 0 (1- count)))
(unless (funcall pred
(aref v1 (mod (+ i1 i) l1))
(aref v2 (mod (+ i2 i) l2)))
(throw 'mismatch t))))))))
(defmacro cs-fixture (&rest body)
"Set up a completion history."
`(unwind-protect
;; some setup to get a completion history
(let ((company-statistics-size 5))
(company-statistics--init)
(let ((major-mode 'foo-mode)
(company-statistics--context
'((:keyword "if")
(:symbol "parent")
(:file "foo-file"))))
(company-statistics--finished "foo"))
(let ((major-mode 'foo-mode)
(company-statistics--context
'((:symbol "statistics")
(:file "bar-file"))))
(company-statistics--finished "bar"))
(let ((major-mode 'baz-mode)
(company-statistics--context
'((:keyword "unless")
(:symbol "company"))))
(company-statistics--finished "baz"))
(let ((major-mode 'baz-mode)
(company-statistics--context
'((:keyword "when")
(:file "quux-file"))))
(company-statistics--finished "quux"))
,@body)
;; tear down to clean slate
(company-statistics--init)))
(defmacro cs-persistence-fixture (&rest body)
"Check and prepare for persistence, clean up."
`(let ((company-statistics-file "./cs-test-tmp"))
(when (and (file-exists-p company-statistics-file)
(file-writable-p company-statistics-file))
(unwind-protect
(progn ,@body)
;; clean up file system
(when (file-exists-p company-statistics-file)
(delete-file company-statistics-file))))))
;; tests themselves
(ert-deftest c-s-history-resize ()
"Test history-resize for shrinking and enlarging."
(cs-fixture
;; resize several times
(let ((cs-scores (copy-tree company-statistics--scores))
(cs-history (copy-tree company-statistics--log 'vecp)))
(company-statistics--log-resize 'dummy 10)
;; scores unaffected?
(should (my/hash-compare company-statistics--scores cs-scores))
;; find all 4 old entries
(should (my/vector-slice-compare company-statistics--log
(- company-statistics--index 4)
cs-history 0
4))
;; index at "old-size"
(should (equal company-statistics--index 5))
(company-statistics--log-resize 'dummy 5)
(should (my/hash-compare company-statistics--scores cs-scores))
(should (my/vector-slice-compare company-statistics--log
(- company-statistics--index 4)
cs-history 0
4))
;; after shrink: index at 0
(should (equal company-statistics--index 0))
;; lose oldest entry "foo"
(company-statistics--log-resize 'dummy 3)
;; score should be removed
(should-not (gethash "foo" company-statistics--scores))
;; find *3* latest entries
(should (my/vector-slice-compare company-statistics--log
(- company-statistics--index 3)
cs-history 1
3))
(should (equal company-statistics--index 0)))))
(ert-deftest c-s-persistence ()
"Test that all statistics are properly saved and restored."
(cs-persistence-fixture
(cs-fixture
(let ((cs-scores (copy-sequence company-statistics--scores))
(cs-history (copy-sequence company-statistics--log))
(cs-index company-statistics--index))
(company-statistics--save)
(company-statistics--init) ;hence shallow copies suffice
(company-statistics--load)
;; (should (equal company-statistics--scores cs-scores))
(should (my/hash-compare company-statistics--scores cs-scores))
(should (equal company-statistics--log cs-history))
(should (equal company-statistics--index cs-index))))))
(ert-deftest c-s-score-change-light ()
"Test a few things about the default score updates."
(let ((major-mode 'foobar-mode))
(should (equal (company-statistics-score-change-light "dummy")
'((nil . 1) (foobar-mode . 1))))))
(ert-deftest c-s-score-calc-light ()
"Test score calculation default."
(cs-fixture
;; FIXME assumes that light context is a subset of the heavy context?
(let ((major-mode 'foo-mode))
(should (eq (company-statistics-score-calc-light "foo") 2))
(should (eq (company-statistics-score-calc-light "bar") 2))
(should (eq (company-statistics-score-calc-light "baz") 1))
(should (eq (company-statistics-score-calc-light "quux") 1)))
(let ((major-mode 'baz-mode))
(should (eq (company-statistics-score-calc-light "foo") 1))
(should (eq (company-statistics-score-calc-light "bar") 1))
(should (eq (company-statistics-score-calc-light "baz") 2))
(should (eq (company-statistics-score-calc-light "quux") 2)))))
(ert-deftest c-s-score-change-heavy ()
"Test a few things about the heavy score updates."
(let ((major-mode 'foobar-mode))
(should (equal (company-statistics-score-change-heavy "dummy")
'((nil . 1) (foobar-mode . 1))))
(let ((company-statistics--context
'((:keyword "kwd")
nil ;deliberately omit parent symbol
(:file "test-file.XYZ"))))
(should (equal (company-statistics-score-change-heavy "dummy")
'((nil . 1) (foobar-mode . 1)
((:keyword "kwd") . 1)
((:file "test-file.XYZ") . 1)))))))
(ert-deftest c-s-score-calc-heavy ()
"Test heavy score calculation."
(cs-fixture
(let ((major-mode 'foo-mode)
(company-statistics--context
'((:symbol "company")
(:file "foo-file"))))
(should (eq (company-statistics-score-calc-heavy "dummy") 0))
(should (eq (company-statistics-score-calc-heavy "foo") 3))
(should (eq (company-statistics-score-calc-heavy "bar") 2))
(should (eq (company-statistics-score-calc-heavy "baz") 2))
(should (eq (company-statistics-score-calc-heavy "quux") 1)))
(let ((major-mode 'foo-mode)
(company-statistics--context
'((:keyword "unless")
(:symbol "parent")
(:file "quux-file"))))
(should (eq (company-statistics-score-calc-heavy "dummy") 0))
(should (eq (company-statistics-score-calc-heavy "foo") 3))
(should (eq (company-statistics-score-calc-heavy "bar") 2))
(should (eq (company-statistics-score-calc-heavy "baz") 2))
(should (eq (company-statistics-score-calc-heavy "quux") 2)))
(let ((major-mode 'baz-mode)
(company-statistics--context
'((:keyword "when")
(:file "baz-file"))))
(should (eq (company-statistics-score-calc-heavy "dummy") 0))
(should (eq (company-statistics-score-calc-heavy "foo") 1))
(should (eq (company-statistics-score-calc-heavy "bar") 1))
(should (eq (company-statistics-score-calc-heavy "baz") 2))
(should (eq (company-statistics-score-calc-heavy "quux") 3)))
(let ((major-mode 'baz-mode)
(company-statistics--context
'((:keyword "if")
(:symbol "statistics")
(:file "quux-file"))))
(should (eq (company-statistics-score-calc-heavy "dummy") 0))
(should (eq (company-statistics-score-calc-heavy "foo") 2))
(should (eq (company-statistics-score-calc-heavy "bar") 2))
(should (eq (company-statistics-score-calc-heavy "baz") 2))
(should (eq (company-statistics-score-calc-heavy "quux") 3)))))
(ert-deftest c-s-alist-update ()
"Test central helper function for context/score alist update."
(let ((alist '((nil . 0) ("a" . 1) ("b" . 2) ("d" . some-symbol)))
(updates '(("a" . 1) ("c" . 3))))
(should (equal (company-statistics--alist-update alist updates '+)
'((nil . 0) ("a" . 2) ("b" . 2) ("d" . some-symbol) ("c" . 3)))))
;; filter only checks on merged, so nil entry remains, and symbol should not pose a problem:
(let ((alist '((nil . 0) ("a" . 1) ("b" . 2) ("d" . some-symbol)))
(updates '(("a" . 1) ("c" . 3))))
(should (equal (company-statistics--alist-update alist updates '+ 'zerop)
'((nil . 0) ("a" . 2) ("b" . 2) ("d" . some-symbol) ("c" . 3)))))
(let ((alist '((nil . 0) ("a" . 1) ("b" . 2) ("d" . some-symbol)))
(updates '(("a" . 1) ("c" . 3))))
(should (equal (company-statistics--alist-update alist updates '-)
'((nil . 0) ("a" . 0) ("b" . 2) ("d" . some-symbol) ("c" . 3)))))
(let ((alist '((nil . 0) ("a" . 1) ("b" . 2) ("d" . some-symbol)))
(updates '(("a" . 1) ("c" . 3))))
(should (equal (company-statistics--alist-update alist updates '- 'zerop)
'((nil . 0) ("b" . 2) ("d" . some-symbol) ("c" . 3))))))
(ert-deftest c-s-scores-add ()
"Test adding scores."
(cs-fixture
;; new entry
(company-statistics--scores-add "zufpah" '((nil . 27)))
(should (equal (gethash "zufpah" company-statistics--scores)
'((nil . 27))))
;; update existing entry
(company-statistics--scores-add "foo" '((nil . 2)))
(let ((h (gethash "foo" company-statistics--scores)))
(should (equal (assoc nil h) '(nil . 3)))
(should (equal (assoc 'foo-mode h) '(foo-mode . 1))))))
(ert-deftest c-s-history-revert ()
"Test reverting a score update stored in history."
;; deep copies throughout!
(cs-fixture
;; pointing to nil, should not change anything
(let ((cs-scores (copy-tree company-statistics--scores))
(cs-history (copy-tree company-statistics--log 'vecp))
(cs-index company-statistics--index))
(company-statistics--log-revert)
(should (my/hash-compare company-statistics--scores cs-scores))
(should (equal company-statistics--log cs-history))
(should (equal company-statistics--index cs-index))))
(cs-fixture
;; remove existing item 2: should vanish from scores
(let ((cs-scores (copy-tree company-statistics--scores))
(cs-history (copy-tree company-statistics--log 'vecp))
(cs-index company-statistics--index))
(company-statistics--log-revert 2)
(should-not (gethash "baz" company-statistics--scores))
(should (equal company-statistics--log cs-history))
(should (equal company-statistics--index cs-index))))
(cs-fixture
;; remove just inserted item 3 (scores should be same)
(let ((cs-scores (copy-tree company-statistics--scores))
(cs-history (copy-tree company-statistics--log 'vecp))
(cs-index company-statistics--index))
(let ((major-mode 'extra-mode))
(company-statistics--finished "foo")) ;adds to scores, history, index
(company-statistics--log-revert 4) ;reverts scores only, so...
(aset cs-history 4 '("foo" (nil . 1) (extra-mode . 1)))
(setq cs-index (mod (1+ cs-index) company-statistics-size))
(should (my/hash-compare company-statistics--scores cs-scores))
(should (equal company-statistics--log cs-history))
(should (equal company-statistics--index cs-index)))))
(ert-deftest c-s-history-store ()
"Test insert/overwrite of history item."
(cs-fixture
(let ((cs-history (copy-tree company-statistics--log 'vecp))
(cs-index company-statistics--index))
;; only changes history and index
(company-statistics--log-store "foo" '((nil . 27)))
(aset cs-history cs-index '("foo" (nil . 27)))
(setq cs-index 0) ;wraps around
(should (equal company-statistics--log cs-history))
(should (equal company-statistics--index cs-index))
;; now wrap around to overwrite an entry
(company-statistics--log-store "tagyok" '((bla . 42)))
(aset cs-history cs-index '("tagyok" (bla . 42)))
(setq cs-index 1)
(should (equal company-statistics--log cs-history))
(should (equal company-statistics--index cs-index)))))
;; test finished and sort functions? if the above is ok, they are trivial...