aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-04-29 09:20:08 -0500
committerCraig Jennings <c@cjennings.net>2026-04-29 09:20:08 -0500
commitf707f95d80ace08db2b65a17f2e391a5edaa278e (patch)
treebf4cb48b3399cceaa8f2b345ebd00bcf7abdf7e6
parentbc8167388ce076c5b2a690c4d1e63c8dc82d6dfe (diff)
downloadorg-drill-f707f95d80ace08db2b65a17f2e391a5edaa278e.tar.gz
org-drill-f707f95d80ace08db2b65a17f2e391a5edaa278e.zip
refactor: extract shared scheduler test extractors
Three test files (SM2, SM5, and the upcoming Simple8) all extract the same fields from a scheduler result list. Pull the shared extractors into tests/testutil-scheduler.el so each algorithm's test file can use them. Position 2 holds an EF in SM2 and SM5 and an EASE in Simple8. Both names are exposed as aliases pointing at the same nth position so each call site reads accurately. SM2 and SM5 test files now require testutil-scheduler and call the shared helpers. 69 of 69 scheduler tests still green. Full unit suite at 180 of 180.
-rw-r--r--tests/test-org-drill-determine-next-interval-sm2.el148
-rw-r--r--tests/test-org-drill-determine-next-interval-sm5.el118
-rw-r--r--tests/testutil-scheduler.el47
3 files changed, 156 insertions, 157 deletions
diff --git a/tests/test-org-drill-determine-next-interval-sm2.el b/tests/test-org-drill-determine-next-interval-sm2.el
index a895ccd..622744d 100644
--- a/tests/test-org-drill-determine-next-interval-sm2.el
+++ b/tests/test-org-drill-determine-next-interval-sm2.el
@@ -23,6 +23,10 @@
(require 'assess)
(require 'org-drill)
+(add-to-list 'load-path
+ (file-name-directory (or load-file-name buffer-file-name)))
+(require 'testutil-scheduler)
+
;;; Test Data and Constants
;; Default SM2 parameters
@@ -32,41 +36,15 @@
(defconst test-sm2-min-ef 1.3
"Minimum easiness factor (SM2 floor).")
-;;; Helper Functions
-
-(defun test-sm2--extract-interval (result)
- "Extract interval from SM2 result list."
- (nth 0 result))
-
-(defun test-sm2--extract-repeats (result)
- "Extract repeats from SM2 result list."
- (nth 1 result))
-
-(defun test-sm2--extract-ef (result)
- "Extract easiness factor from SM2 result list."
- (nth 2 result))
-
-(defun test-sm2--extract-failures (result)
- "Extract failures from SM2 result list."
- (nth 3 result))
-
-(defun test-sm2--extract-meanq (result)
- "Extract mean quality from SM2 result list."
- (nth 4 result))
-
-(defun test-sm2--extract-total-repeats (result)
- "Extract total repeats from SM2 result list."
- (nth 5 result))
-
;;; Normal Cases - Successful Reviews
(ert-deftest test-org-drill-determine-next-interval-sm2-normal-first-review-quality-4 ()
"Test first successful review with quality 4.
First review (n=1) should return interval of 1 day."
(let* ((result (org-drill-determine-next-interval-sm2 0 1 nil 4 0 nil 0))
- (interval (test-sm2--extract-interval result))
- (repeats (test-sm2--extract-repeats result))
- (ef (test-sm2--extract-ef result)))
+ (interval (test-scheduler--extract-interval result))
+ (repeats (test-scheduler--extract-repeats result))
+ (ef (test-scheduler--extract-ef result)))
(should (= interval 1))
(should (= repeats 2))
(should ef))) ; EF should be calculated
@@ -76,8 +54,8 @@ First review (n=1) should return interval of 1 day."
Second review (n=2) should return interval of 6 days (no random noise)."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm2 1 2 2.5 4 0 nil 0))
- (interval (test-sm2--extract-interval result))
- (repeats (test-sm2--extract-repeats result)))
+ (interval (test-scheduler--extract-interval result))
+ (repeats (test-scheduler--extract-repeats result)))
(should (= interval 6))
(should (= repeats 3)))))
@@ -88,18 +66,18 @@ Third review (n=3) uses formula: last-interval * EF."
(let* ((ef 2.5)
(last-interval 6)
(result (org-drill-determine-next-interval-sm2 last-interval 3 ef 4 0 nil 0))
- (interval (test-sm2--extract-interval result))
- (new-ef (test-sm2--extract-ef result)))
+ (interval (test-scheduler--extract-interval result))
+ (new-ef (test-scheduler--extract-ef result)))
;; Interval should be approximately last-interval * new-ef
(should (> interval last-interval))
- (should (= (test-sm2--extract-repeats result) 4)))))
+ (should (= (test-scheduler--extract-repeats result) 4)))))
(ert-deftest test-org-drill-determine-next-interval-sm2-normal-quality-5-perfect-recall ()
"Test review with perfect recall (quality 5).
Quality 5 should increase EF and result in longer intervals."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm2 10 3 2.5 5 0 nil 0))
- (ef (test-sm2--extract-ef result)))
+ (ef (test-scheduler--extract-ef result)))
;; Quality 5 should maintain or increase EF
(should (>= ef 2.5)))))
@@ -108,7 +86,7 @@ Quality 5 should increase EF and result in longer intervals."
Quality 3 should maintain relatively stable EF."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm2 10 3 2.5 3 0 nil 0))
- (ef (test-sm2--extract-ef result)))
+ (ef (test-scheduler--extract-ef result)))
;; Quality 3 should result in moderate EF
(should (> ef test-sm2-min-ef))
(should (< ef 2.5))))) ; EF likely decreases slightly
@@ -119,10 +97,10 @@ Quality 3 should maintain relatively stable EF."
"Test failed review with quality 0.
Quality 0 (<= org-drill-failure-quality) should reset interval to -1."
(let* ((result (org-drill-determine-next-interval-sm2 10 3 2.5 0 0 nil 0))
- (interval (test-sm2--extract-interval result))
- (repeats (test-sm2--extract-repeats result))
- (ef (test-sm2--extract-ef result))
- (failures (test-sm2--extract-failures result)))
+ (interval (test-scheduler--extract-interval result))
+ (repeats (test-scheduler--extract-repeats result))
+ (ef (test-scheduler--extract-ef result))
+ (failures (test-scheduler--extract-failures result)))
(should (= interval -1))
(should (= repeats 1)) ; Reset to 1
(should (= ef 2.5)) ; EF unchanged on failure
@@ -132,9 +110,9 @@ Quality 0 (<= org-drill-failure-quality) should reset interval to -1."
"Test failed review with quality 1.
Quality 1 (<= org-drill-failure-quality) should reset interval."
(let* ((result (org-drill-determine-next-interval-sm2 15 5 2.6 1 2 3.5 10))
- (interval (test-sm2--extract-interval result))
- (ef (test-sm2--extract-ef result))
- (failures (test-sm2--extract-failures result)))
+ (interval (test-scheduler--extract-interval result))
+ (ef (test-scheduler--extract-ef result))
+ (failures (test-scheduler--extract-failures result)))
(should (= interval -1))
(should (= ef 2.6)) ; EF unchanged
(should (= failures 3)))) ; Previous failures + 1
@@ -143,7 +121,7 @@ Quality 1 (<= org-drill-failure-quality) should reset interval."
"Test failed review with quality 2.
Quality 2 (= org-drill-failure-quality default) should reset interval."
(let* ((result (org-drill-determine-next-interval-sm2 10 3 2.5 2 0 nil 0))
- (interval (test-sm2--extract-interval result)))
+ (interval (test-scheduler--extract-interval result)))
(should (= interval -1))))
;;; Boundary Cases - Default Values
@@ -151,15 +129,15 @@ Quality 2 (= org-drill-failure-quality default) should reset interval."
(ert-deftest test-org-drill-determine-next-interval-sm2-boundary-nil-ef-uses-default ()
"Test that nil EF defaults to 2.5."
(let* ((result (org-drill-determine-next-interval-sm2 0 1 nil 4 0 nil 0))
- (ef (test-sm2--extract-ef result)))
+ (ef (test-scheduler--extract-ef result)))
(should ef) ; EF should be set (modified from default 2.5)
(should (> ef 0))))
(ert-deftest test-org-drill-determine-next-interval-sm2-boundary-zero-n-becomes-one ()
"Test that n=0 is treated as n=1."
(let* ((result (org-drill-determine-next-interval-sm2 0 0 2.5 4 0 nil 0))
- (interval (test-sm2--extract-interval result))
- (repeats (test-sm2--extract-repeats result)))
+ (interval (test-scheduler--extract-interval result))
+ (repeats (test-scheduler--extract-repeats result)))
(should (= interval 1)) ; First review interval
(should (= repeats 2)))) ; n incremented from 1 to 2
@@ -167,7 +145,7 @@ Quality 2 (= org-drill-failure-quality default) should reset interval."
"Test that nil meanq initializes to current quality."
(let* ((quality 4)
(result (org-drill-determine-next-interval-sm2 0 1 2.5 quality 0 nil 0))
- (meanq (test-sm2--extract-meanq result)))
+ (meanq (test-scheduler--extract-meanq result)))
(should (= meanq quality))))
;;; Boundary Cases - Quality Extremes
@@ -176,13 +154,13 @@ Quality 2 (= org-drill-failure-quality default) should reset interval."
"Test maximum quality (5) - perfect recall."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm2 1 2 2.5 5 0 nil 0))
- (interval (test-sm2--extract-interval result)))
+ (interval (test-scheduler--extract-interval result)))
(should (= interval 6))))) ; Second review with quality 5
(ert-deftest test-org-drill-determine-next-interval-sm2-boundary-quality-0-minimum ()
"Test minimum quality (0) - complete failure."
(let* ((result (org-drill-determine-next-interval-sm2 10 3 2.5 0 0 nil 0))
- (interval (test-sm2--extract-interval result)))
+ (interval (test-scheduler--extract-interval result)))
(should (= interval -1)))) ; Failed review
;;; Boundary Cases - Repeat Count
@@ -190,7 +168,7 @@ Quality 2 (= org-drill-failure-quality default) should reset interval."
(ert-deftest test-org-drill-determine-next-interval-sm2-boundary-n-equals-1 ()
"Test first successful review (n=1) returns interval of 1."
(let* ((result (org-drill-determine-next-interval-sm2 0 1 2.5 4 0 nil 0))
- (interval (test-sm2--extract-interval result)))
+ (interval (test-scheduler--extract-interval result)))
(should (= interval 1))))
(ert-deftest test-org-drill-determine-next-interval-sm2-boundary-n-equals-2-no-noise ()
@@ -198,7 +176,7 @@ Quality 2 (= org-drill-failure-quality default) should reset interval."
Should return interval of 6 days regardless of quality (if passing)."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm2 1 2 2.5 4 0 nil 0))
- (interval (test-sm2--extract-interval result)))
+ (interval (test-scheduler--extract-interval result)))
(should (= interval 6)))))
(ert-deftest test-org-drill-determine-next-interval-sm2-boundary-n-equals-2-with-noise ()
@@ -208,9 +186,9 @@ Interval should vary by quality, with quality 5 having highest base interval (6)
(let* ((result-q5 (org-drill-determine-next-interval-sm2 1 2 2.5 5 0 nil 0))
(result-q4 (org-drill-determine-next-interval-sm2 1 2 2.5 4 0 nil 0))
(result-q3 (org-drill-determine-next-interval-sm2 1 2 2.5 3 0 nil 0))
- (interval-q5 (test-sm2--extract-interval result-q5))
- (interval-q4 (test-sm2--extract-interval result-q4))
- (interval-q3 (test-sm2--extract-interval result-q3)))
+ (interval-q5 (test-scheduler--extract-interval result-q5))
+ (interval-q4 (test-scheduler--extract-interval result-q4))
+ (interval-q3 (test-scheduler--extract-interval result-q3)))
;; Base intervals before noise are: Q5=6, Q4=4, Q3=3
;; After random noise, verify intervals are positive and roughly in expected ranges
(should (> interval-q5 0))
@@ -229,7 +207,7 @@ meanq = (quality + meanq * total-repeats) / (total-repeats + 1)"
(meanq 3.0)
(total-repeats 10)
(result (org-drill-determine-next-interval-sm2 10 3 2.5 quality 0 meanq total-repeats))
- (new-meanq (test-sm2--extract-meanq result))
+ (new-meanq (test-scheduler--extract-meanq result))
(expected-meanq (/ (+ quality (* meanq total-repeats 1.0))
(1+ total-repeats))))
(should (< (abs (- new-meanq expected-meanq)) 0.0001)))) ; Floating point comparison
@@ -240,7 +218,7 @@ meanq = (quality + meanq * total-repeats) / (total-repeats + 1)"
"Test that EF floor of 1.3 is applied to input EF when it's below 1.3.
If input EF < 1.3, org-drill-modify-e-factor returns 1.3 exactly."
(let* ((result (org-drill-determine-next-interval-sm2 10 3 1.0 4 0 nil 0))
- (ef (test-sm2--extract-ef result)))
+ (ef (test-scheduler--extract-ef result)))
;; Input EF was 1.0 < 1.3, so it should be raised to 1.3 first,
;; then modified based on quality 4 (which increases EF)
(should (>= ef 1.3))))
@@ -251,7 +229,7 @@ If input EF < 1.3, org-drill-modify-e-factor returns 1.3 exactly."
"Test that total-repeats is always incremented by 1."
(let* ((total-repeats 42)
(result (org-drill-determine-next-interval-sm2 10 3 2.5 4 0 3.5 total-repeats))
- (new-total (test-sm2--extract-total-repeats result)))
+ (new-total (test-scheduler--extract-total-repeats result)))
(should (= new-total (1+ total-repeats)))))
;;; Return Value Structure
@@ -280,7 +258,7 @@ Should return 7-element list: (INTERVAL REPEATS EF FAILURES MEAN TOTAL-REPEATS O
Quality 5 should result in EF >= initial EF."
(let* ((initial-ef 2.5)
(result (org-drill-determine-next-interval-sm2 10 3 initial-ef 5 0 nil 0))
- (new-ef (test-sm2--extract-ef result)))
+ (new-ef (test-scheduler--extract-ef result)))
(should (>= new-ef initial-ef))))
(ert-deftest test-org-drill-determine-next-interval-sm2-algorithm-ef-decreases-with-low-quality ()
@@ -288,7 +266,7 @@ Quality 5 should result in EF >= initial EF."
Quality 3 should result in EF < initial EF."
(let* ((initial-ef 2.5)
(result (org-drill-determine-next-interval-sm2 10 3 initial-ef 3 0 nil 0))
- (new-ef (test-sm2--extract-ef result)))
+ (new-ef (test-scheduler--extract-ef result)))
(should (< new-ef initial-ef))))
(ert-deftest test-org-drill-determine-next-interval-sm2-algorithm-interval-grows-exponentially ()
@@ -297,10 +275,10 @@ Third review interval should be significantly larger than second."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((ef 2.5)
(result-2nd (org-drill-determine-next-interval-sm2 1 2 ef 4 0 nil 0))
- (interval-2nd (test-sm2--extract-interval result-2nd))
- (ef-2nd (test-sm2--extract-ef result-2nd))
+ (interval-2nd (test-scheduler--extract-interval result-2nd))
+ (ef-2nd (test-scheduler--extract-ef result-2nd))
(result-3rd (org-drill-determine-next-interval-sm2 interval-2nd 3 ef-2nd 4 0 nil 1))
- (interval-3rd (test-sm2--extract-interval result-3rd)))
+ (interval-3rd (test-scheduler--extract-interval result-3rd)))
(should (> interval-3rd (* interval-2nd 1.5)))))) ; Significant growth
;;; Aggressive Boundary Cases
@@ -310,9 +288,9 @@ Third review interval should be significantly larger than second."
Algorithm should remain stable with extreme n values."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm2 365 1000 2.5 4 0 4.0 1000))
- (interval (test-sm2--extract-interval result))
- (repeats (test-sm2--extract-repeats result))
- (ef (test-sm2--extract-ef result)))
+ (interval (test-scheduler--extract-interval result))
+ (repeats (test-scheduler--extract-repeats result))
+ (ef (test-scheduler--extract-ef result)))
(should (numberp interval))
(should (> interval 0))
(should (= repeats 1001))
@@ -323,7 +301,7 @@ Algorithm should remain stable with extreme n values."
Algorithm should handle extreme intervals without overflow."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm2 3650 100 2.5 4 0 4.0 100))
- (interval (test-sm2--extract-interval result)))
+ (interval (test-scheduler--extract-interval result)))
(should (numberp interval))
(should (> interval 0)))))
@@ -332,7 +310,7 @@ Algorithm should handle extreme intervals without overflow."
Failure count should be tracked correctly even at extreme values."
(let* ((failures 100)
(result (org-drill-determine-next-interval-sm2 10 3 2.5 0 failures 3.0 150))
- (new-failures (test-sm2--extract-failures result)))
+ (new-failures (test-scheduler--extract-failures result)))
(should (= new-failures (1+ failures)))))
(ert-deftest test-org-drill-determine-next-interval-sm2-boundary-very-high-total-repeats ()
@@ -340,21 +318,21 @@ Failure count should be tracked correctly even at extreme values."
Total repeats should increment correctly even at extreme values."
(let* ((total-repeats 10000)
(result (org-drill-determine-next-interval-sm2 10 3 2.5 4 0 4.0 total-repeats))
- (new-total (test-sm2--extract-total-repeats result)))
+ (new-total (test-scheduler--extract-total-repeats result)))
(should (= new-total (1+ total-repeats)))))
(ert-deftest test-org-drill-determine-next-interval-sm2-boundary-ef-just-above-floor ()
"Test SM2 with EF just above minimum floor (1.31).
EF should be maintained or modified normally, not clamped."
(let* ((result (org-drill-determine-next-interval-sm2 10 3 1.31 4 0 4.0 10))
- (ef (test-sm2--extract-ef result)))
+ (ef (test-scheduler--extract-ef result)))
(should (>= ef test-sm2-min-ef))))
(ert-deftest test-org-drill-determine-next-interval-sm2-boundary-ef-at-floor ()
"Test SM2 with EF exactly at minimum floor (1.3).
EF at floor with quality 4 should increase slightly."
(let* ((result (org-drill-determine-next-interval-sm2 10 3 1.3 4 0 4.0 10))
- (ef (test-sm2--extract-ef result)))
+ (ef (test-scheduler--extract-ef result)))
(should (>= ef test-sm2-min-ef))))
(ert-deftest test-org-drill-determine-next-interval-sm2-boundary-perfect-recall-after-many-failures ()
@@ -363,8 +341,8 @@ Perfect recall should not reset failures, but should calculate normal interval."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((failures 50)
(result (org-drill-determine-next-interval-sm2 10 3 2.5 5 failures 3.0 100))
- (interval (test-sm2--extract-interval result))
- (new-failures (test-sm2--extract-failures result)))
+ (interval (test-scheduler--extract-interval result))
+ (new-failures (test-scheduler--extract-failures result)))
(should (> interval 0))
(should (= new-failures failures))))) ; Failures unchanged on success
@@ -372,9 +350,9 @@ Perfect recall should not reset failures, but should calculate normal interval."
"Test complete failure (quality 0) after long successful streak.
Failure should reset interval to -1 regardless of previous success."
(let* ((result (org-drill-determine-next-interval-sm2 365 100 2.8 0 0 4.5 100))
- (interval (test-sm2--extract-interval result))
- (repeats (test-sm2--extract-repeats result))
- (failures (test-sm2--extract-failures result)))
+ (interval (test-scheduler--extract-interval result))
+ (repeats (test-scheduler--extract-repeats result))
+ (failures (test-scheduler--extract-failures result)))
(should (= interval -1))
(should (= repeats 1)) ; Reset to 1
(should (= failures 1))))
@@ -383,7 +361,7 @@ Failure should reset interval to -1 regardless of previous success."
"Test SM2 with meanq at maximum (5.0).
High quality review with already-perfect meanq."
(let* ((result (org-drill-determine-next-interval-sm2 10 3 2.5 5 0 5.0 100))
- (meanq (test-sm2--extract-meanq result)))
+ (meanq (test-scheduler--extract-meanq result)))
(should (<= meanq 5.0))
(should (>= meanq 4.5)))) ; Should remain very high
@@ -391,7 +369,7 @@ High quality review with already-perfect meanq."
"Test SM2 with meanq at minimum (0.0).
Low quality history with poor current recall."
(let* ((result (org-drill-determine-next-interval-sm2 10 3 2.5 3 5 0.0 100))
- (meanq (test-sm2--extract-meanq result)))
+ (meanq (test-scheduler--extract-meanq result)))
(should (>= meanq 0.0))
(should (<= meanq 1.0)))) ; Should remain low with quality 3
@@ -401,7 +379,7 @@ Indicates item reviewed many times but poorly recalled."
(let* ((total-repeats 1000)
(meanq 2.0)
(result (org-drill-determine-next-interval-sm2 5 10 1.5 3 20 meanq total-repeats))
- (new-meanq (test-sm2--extract-meanq result)))
+ (new-meanq (test-scheduler--extract-meanq result)))
(should (numberp new-meanq))
;; meanq should remain low given poor history
(should (< new-meanq 3.0))))
@@ -410,7 +388,7 @@ Indicates item reviewed many times but poorly recalled."
"Test SM2 with zero last-interval.
Should handle as first review (n=1 case)."
(let* ((result (org-drill-determine-next-interval-sm2 0 1 2.5 4 0 4.0 0))
- (interval (test-sm2--extract-interval result)))
+ (interval (test-scheduler--extract-interval result)))
(should (= interval 1))))
(ert-deftest test-org-drill-determine-next-interval-sm2-boundary-float-vs-int-intervals ()
@@ -419,8 +397,8 @@ Algorithm should handle both correctly."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result-float (org-drill-determine-next-interval-sm2 10.5 3 2.5 4 0 4.0 10))
(result-int (org-drill-determine-next-interval-sm2 10 3 2.5 4 0 4.0 10))
- (interval-float (test-sm2--extract-interval result-float))
- (interval-int (test-sm2--extract-interval result-int)))
+ (interval-float (test-scheduler--extract-interval result-float))
+ (interval-int (test-scheduler--extract-interval result-int)))
(should (numberp interval-float))
(should (numberp interval-int))
;; Both should produce reasonable intervals
@@ -431,10 +409,10 @@ Algorithm should handle both correctly."
"Test SM2 behavior with alternating quality (5, 0, 5, 0 pattern).
Simulates inconsistent learning."
(let* ((result-1 (org-drill-determine-next-interval-sm2 1 2 2.5 5 0 5.0 1))
- (ef-1 (test-sm2--extract-ef result-1))
+ (ef-1 (test-scheduler--extract-ef result-1))
(result-2 (org-drill-determine-next-interval-sm2 6 3 ef-1 0 0 4.5 2))
- (interval-2 (test-sm2--extract-interval result-2))
- (failures-2 (test-sm2--extract-failures result-2)))
+ (interval-2 (test-scheduler--extract-interval result-2))
+ (failures-2 (test-scheduler--extract-failures result-2)))
;; After perfect then fail, should reset interval
(should (= interval-2 -1))
(should (= failures-2 1))))
diff --git a/tests/test-org-drill-determine-next-interval-sm5.el b/tests/test-org-drill-determine-next-interval-sm5.el
index dc64c7d..4e4cb7e 100644
--- a/tests/test-org-drill-determine-next-interval-sm5.el
+++ b/tests/test-org-drill-determine-next-interval-sm5.el
@@ -24,6 +24,10 @@
(require 'assess)
(require 'org-drill)
+(add-to-list 'load-path
+ (file-name-directory (or load-file-name buffer-file-name)))
+(require 'testutil-scheduler)
+
;;; Test Data and Constants
(defconst test-sm5-default-ef 2.5
@@ -33,45 +37,15 @@
"Minimum easiness factor.
The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
-;;; Helper Functions
-
-(defun test-sm5--extract-interval (result)
- "Extract interval from SM5 result list."
- (nth 0 result))
-
-(defun test-sm5--extract-repeats (result)
- "Extract repeats from SM5 result list."
- (nth 1 result))
-
-(defun test-sm5--extract-ef (result)
- "Extract easiness factor from SM5 result list."
- (nth 2 result))
-
-(defun test-sm5--extract-failures (result)
- "Extract failures from SM5 result list."
- (nth 3 result))
-
-(defun test-sm5--extract-meanq (result)
- "Extract mean quality from SM5 result list."
- (nth 4 result))
-
-(defun test-sm5--extract-total-repeats (result)
- "Extract total repeats from SM5 result list."
- (nth 5 result))
-
-(defun test-sm5--extract-of-matrix (result)
- "Extract optimal-factor matrix from SM5 result list."
- (nth 6 result))
-
;;; Normal Cases - Successful Reviews
(ert-deftest test-org-drill-determine-next-interval-sm5-normal-first-review-quality-4 ()
"Normal: first successful review with quality 4 returns positive interval and increments repeats."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm5 0 1 nil 4 0 nil 0 nil nil))
- (interval (test-sm5--extract-interval result))
- (repeats (test-sm5--extract-repeats result))
- (ef (test-sm5--extract-ef result)))
+ (interval (test-scheduler--extract-interval result))
+ (repeats (test-scheduler--extract-repeats result))
+ (ef (test-scheduler--extract-ef result)))
(should (> interval 0))
(should (= repeats 2))
(should ef))))
@@ -80,8 +54,8 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
"Normal: second successful review with quality 4 increments repeats to 3."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm5 1 2 2.5 4 0 nil 0 nil nil))
- (interval (test-sm5--extract-interval result))
- (repeats (test-sm5--extract-repeats result)))
+ (interval (test-scheduler--extract-interval result))
+ (repeats (test-scheduler--extract-repeats result)))
(should (> interval 0))
(should (= repeats 3)))))
@@ -89,8 +63,8 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
"Normal: third successful review uses OF-matrix lookup and returns positive interval."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm5 6 3 2.5 4 0 nil 0 nil nil))
- (interval (test-sm5--extract-interval result))
- (repeats (test-sm5--extract-repeats result)))
+ (interval (test-scheduler--extract-interval result))
+ (repeats (test-scheduler--extract-repeats result)))
(should (> interval 0))
(should (= repeats 4)))))
@@ -98,24 +72,24 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
"Normal: quality 5 maintains or increases EF on successful path."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm5 10 3 2.5 5 0 nil 0 nil nil))
- (ef (test-sm5--extract-ef result)))
+ (ef (test-scheduler--extract-ef result)))
(should (>= ef 2.5)))))
(ert-deftest test-org-drill-determine-next-interval-sm5-normal-quality-3-adequate-recall ()
"Normal: quality 3 decreases EF below initial but keeps it above the floor."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm5 10 3 2.5 3 0 nil 0 nil nil))
- (ef (test-sm5--extract-ef result)))
+ (ef (test-scheduler--extract-ef result)))
(should (> ef test-sm5-min-ef))
(should (< ef 2.5)))))
(ert-deftest test-org-drill-determine-next-interval-sm5-normal-failure-quality-0 ()
"Normal: failed review with quality 0 resets interval to -1, repeats to 1, increments failures."
(let* ((result (org-drill-determine-next-interval-sm5 10 3 2.5 0 0 nil 0 nil nil))
- (interval (test-sm5--extract-interval result))
- (repeats (test-sm5--extract-repeats result))
- (ef (test-sm5--extract-ef result))
- (failures (test-sm5--extract-failures result)))
+ (interval (test-scheduler--extract-interval result))
+ (repeats (test-scheduler--extract-repeats result))
+ (ef (test-scheduler--extract-ef result))
+ (failures (test-scheduler--extract-failures result)))
(should (= interval -1))
(should (= repeats 1))
(should (= ef 2.5))
@@ -124,9 +98,9 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
(ert-deftest test-org-drill-determine-next-interval-sm5-normal-failure-quality-1 ()
"Normal: failed review with quality 1 resets interval, preserves input EF, increments failures."
(let* ((result (org-drill-determine-next-interval-sm5 15 5 2.6 1 2 3.5 10 nil nil))
- (interval (test-sm5--extract-interval result))
- (ef (test-sm5--extract-ef result))
- (failures (test-sm5--extract-failures result)))
+ (interval (test-scheduler--extract-interval result))
+ (ef (test-scheduler--extract-ef result))
+ (failures (test-scheduler--extract-failures result)))
(should (= interval -1))
(should (= ef 2.6))
(should (= failures 3))))
@@ -134,14 +108,14 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
(ert-deftest test-org-drill-determine-next-interval-sm5-normal-failure-quality-2 ()
"Normal: quality 2 (= default org-drill-failure-quality) triggers failure path."
(let* ((result (org-drill-determine-next-interval-sm5 10 3 2.5 2 0 nil 0 nil nil))
- (interval (test-sm5--extract-interval result)))
+ (interval (test-scheduler--extract-interval result)))
(should (= interval -1))))
(ert-deftest test-org-drill-determine-next-interval-sm5-normal-failure-preserves-old-ef ()
"Normal: SM5 failure path returns the input EF (OLD-EF), not the modified one."
(let* ((input-ef 2.7)
(result (org-drill-determine-next-interval-sm5 10 3 input-ef 0 0 nil 0 nil nil))
- (returned-ef (test-sm5--extract-ef result)))
+ (returned-ef (test-scheduler--extract-ef result)))
;; modify-e-factor would have computed a different value, but the failure
;; branch returns old-ef, which is the input ef.
(should (= returned-ef input-ef))))
@@ -151,7 +125,7 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
(ert-deftest test-org-drill-determine-next-interval-sm5-boundary-nil-ef-uses-default ()
"Boundary: nil EF defaults to 2.5."
(let* ((result (org-drill-determine-next-interval-sm5 0 1 nil 4 0 nil 0 nil nil))
- (ef (test-sm5--extract-ef result)))
+ (ef (test-scheduler--extract-ef result)))
(should ef)
(should (> ef 0))))
@@ -159,27 +133,27 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
"Boundary: n=0 is treated as n=1, so returned repeats is 2."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm5 0 0 2.5 4 0 nil 0 nil nil))
- (repeats (test-sm5--extract-repeats result)))
+ (repeats (test-scheduler--extract-repeats result)))
(should (= repeats 2)))))
(ert-deftest test-org-drill-determine-next-interval-sm5-boundary-nil-meanq-uses-quality ()
"Boundary: nil meanq initializes to current quality."
(let* ((quality 4)
(result (org-drill-determine-next-interval-sm5 0 1 2.5 quality 0 nil 0 nil nil))
- (meanq (test-sm5--extract-meanq result)))
+ (meanq (test-scheduler--extract-meanq result)))
(should (= meanq quality))))
(ert-deftest test-org-drill-determine-next-interval-sm5-boundary-quality-5-maximum ()
"Boundary: maximum quality 5 is accepted and produces a positive interval."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm5 1 2 2.5 5 0 nil 0 nil nil))
- (interval (test-sm5--extract-interval result)))
+ (interval (test-scheduler--extract-interval result)))
(should (> interval 0)))))
(ert-deftest test-org-drill-determine-next-interval-sm5-boundary-quality-0-minimum ()
"Boundary: minimum quality 0 triggers the failure path with interval -1."
(let* ((result (org-drill-determine-next-interval-sm5 10 3 2.5 0 0 nil 0 nil nil))
- (interval (test-sm5--extract-interval result)))
+ (interval (test-scheduler--extract-interval result)))
(should (= interval -1))))
(ert-deftest test-org-drill-determine-next-interval-sm5-boundary-meanq-weighted-average ()
@@ -188,7 +162,7 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
(meanq 3.0)
(total-repeats 10)
(result (org-drill-determine-next-interval-sm5 10 3 2.5 quality 0 meanq total-repeats nil nil))
- (new-meanq (test-sm5--extract-meanq result))
+ (new-meanq (test-scheduler--extract-meanq result))
(expected (/ (+ quality (* meanq total-repeats 1.0))
(1+ total-repeats))))
(should (< (abs (- new-meanq expected)) 0.0001))))
@@ -197,14 +171,14 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
"Boundary: input EF below 1.3 is raised to 1.3 by org-drill-modify-e-factor."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm5 10 3 1.0 4 0 nil 0 nil nil))
- (ef (test-sm5--extract-ef result)))
+ (ef (test-scheduler--extract-ef result)))
(should (>= ef test-sm5-min-ef)))))
(ert-deftest test-org-drill-determine-next-interval-sm5-boundary-ef-at-floor ()
"Boundary: EF exactly at floor (1.3) does not crash and remains >= 1.3 with quality 4."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm5 10 3 1.3 4 0 4.0 10 nil nil))
- (ef (test-sm5--extract-ef result)))
+ (ef (test-scheduler--extract-ef result)))
(should (>= ef test-sm5-min-ef)))))
(ert-deftest test-org-drill-determine-next-interval-sm5-boundary-total-repeats-increments ()
@@ -212,13 +186,13 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
(let* ((total-repeats 42)
(result-success (org-drill-determine-next-interval-sm5 10 3 2.5 4 0 3.5 total-repeats nil nil))
(result-failure (org-drill-determine-next-interval-sm5 10 3 2.5 0 0 3.5 total-repeats nil nil)))
- (should (= (test-sm5--extract-total-repeats result-success) (1+ total-repeats)))
- (should (= (test-sm5--extract-total-repeats result-failure) (1+ total-repeats)))))
+ (should (= (test-scheduler--extract-total-repeats result-success) (1+ total-repeats)))
+ (should (= (test-scheduler--extract-total-repeats result-failure) (1+ total-repeats)))))
(ert-deftest test-org-drill-determine-next-interval-sm5-boundary-nil-of-matrix-uses-default ()
"Boundary: nil of-matrix defaults to org-drill-sm5-optimal-factor-matrix and returns a list."
(let* ((result (org-drill-determine-next-interval-sm5 0 1 2.5 4 0 nil 0 nil nil))
- (matrix (test-sm5--extract-of-matrix result)))
+ (matrix (test-scheduler--extract-of-matrix result)))
(should (listp matrix))))
(ert-deftest test-org-drill-determine-next-interval-sm5-boundary-input-matrix-not-mutated ()
@@ -235,7 +209,7 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
"Boundary: nil delta-days (the default) skips the early-review adjustment."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result (org-drill-determine-next-interval-sm5 6 3 2.5 4 0 nil 0 nil nil))
- (interval (test-sm5--extract-interval result)))
+ (interval (test-scheduler--extract-interval result)))
(should (numberp interval))
(should (> interval 0)))))
@@ -245,8 +219,8 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
(org-drill-adjust-intervals-for-early-and-late-repetitions-p t))
(let* ((result-late (org-drill-determine-next-interval-sm5 6 3 2.5 4 0 nil 0 nil 5))
(result-no-delta (org-drill-determine-next-interval-sm5 6 3 2.5 4 0 nil 0 nil nil))
- (interval-late (test-sm5--extract-interval result-late))
- (interval-no-delta (test-sm5--extract-interval result-no-delta)))
+ (interval-late (test-scheduler--extract-interval result-late))
+ (interval-no-delta (test-scheduler--extract-interval result-no-delta)))
(should (= interval-late interval-no-delta)))))
(ert-deftest test-org-drill-determine-next-interval-sm5-boundary-delta-days-negative-flag-disabled ()
@@ -255,8 +229,8 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
(org-drill-adjust-intervals-for-early-and-late-repetitions-p nil))
(let* ((result-no-adjust (org-drill-determine-next-interval-sm5 6 3 2.5 4 0 nil 0 nil -5))
(result-no-delta (org-drill-determine-next-interval-sm5 6 3 2.5 4 0 nil 0 nil nil))
- (interval-no-adjust (test-sm5--extract-interval result-no-adjust))
- (interval-no-delta (test-sm5--extract-interval result-no-delta)))
+ (interval-no-adjust (test-scheduler--extract-interval result-no-adjust))
+ (interval-no-delta (test-scheduler--extract-interval result-no-delta)))
(should (= interval-no-adjust interval-no-delta)))))
(ert-deftest test-org-drill-determine-next-interval-sm5-boundary-delta-days-negative-flag-enabled ()
@@ -264,7 +238,7 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
(let ((org-drill-add-random-noise-to-intervals-p nil)
(org-drill-adjust-intervals-for-early-and-late-repetitions-p t))
(let* ((result (org-drill-determine-next-interval-sm5 6 3 2.5 4 0 nil 0 nil -5))
- (interval (test-sm5--extract-interval result)))
+ (interval (test-scheduler--extract-interval result)))
(should (numberp interval))
(should (> interval 0)))))
@@ -304,32 +278,32 @@ The SM5 floor is shared with SM2 via `org-drill-modify-e-factor'.")
"Algorithm: quality 5 results in EF >= initial EF on the success path."
(let* ((initial-ef 2.5)
(result (org-drill-determine-next-interval-sm5 10 3 initial-ef 5 0 nil 0 nil nil))
- (new-ef (test-sm5--extract-ef result)))
+ (new-ef (test-scheduler--extract-ef result)))
(should (>= new-ef initial-ef))))
(ert-deftest test-org-drill-determine-next-interval-sm5-algorithm-ef-decreases-with-quality-3 ()
"Algorithm: quality 3 results in EF < initial EF on the success path."
(let* ((initial-ef 2.5)
(result (org-drill-determine-next-interval-sm5 10 3 initial-ef 3 0 nil 0 nil nil))
- (new-ef (test-sm5--extract-ef result)))
+ (new-ef (test-scheduler--extract-ef result)))
(should (< new-ef initial-ef))))
(ert-deftest test-org-drill-determine-next-interval-sm5-algorithm-interval-grows-over-successive-reviews ()
"Algorithm: successive successful quality-4 reviews produce strictly growing intervals."
(let ((org-drill-add-random-noise-to-intervals-p nil))
(let* ((result-2 (org-drill-determine-next-interval-sm5 1 2 2.5 4 0 nil 0 nil nil))
- (interval-2 (test-sm5--extract-interval result-2))
- (ef-2 (test-sm5--extract-ef result-2))
- (matrix-2 (test-sm5--extract-of-matrix result-2))
+ (interval-2 (test-scheduler--extract-interval result-2))
+ (ef-2 (test-scheduler--extract-ef result-2))
+ (matrix-2 (test-scheduler--extract-of-matrix result-2))
(result-3 (org-drill-determine-next-interval-sm5 interval-2 3 ef-2 4 0 nil 1 matrix-2 nil))
- (interval-3 (test-sm5--extract-interval result-3)))
+ (interval-3 (test-scheduler--extract-interval result-3)))
(should (> interval-3 interval-2)))))
(ert-deftest test-org-drill-determine-next-interval-sm5-algorithm-of-matrix-gains-entry ()
"Algorithm: of-matrix gains an entry for n after a successful call, preserving prior entries."
(let* ((input-matrix '((10 (1.0 . 1.0))))
(result (org-drill-determine-next-interval-sm5 0 1 2.5 4 0 nil 0 input-matrix nil))
- (output-matrix (test-sm5--extract-of-matrix result)))
+ (output-matrix (test-scheduler--extract-of-matrix result)))
(should (assoc 1 output-matrix)) ; new entry for n=1 was added
(should (assoc 10 output-matrix)))) ; unrelated entry for n=10 preserved
diff --git a/tests/testutil-scheduler.el b/tests/testutil-scheduler.el
new file mode 100644
index 0000000..72811e8
--- /dev/null
+++ b/tests/testutil-scheduler.el
@@ -0,0 +1,47 @@
+;;; testutil-scheduler.el --- Shared extractors for scheduler tests -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Common result-list element extractors used by the SM2, SM5, and Simple8
+;; scheduler test files. Each algorithm's result list shares the same layout
+;; for INTERVAL / REPEATS / FAILURES / MEAN / TOTAL-REPEATS. Position 2 holds
+;; either the EF (SM2, SM5) or the EASE (Simple8); both names are exposed as
+;; aliases pointing at the same `nth' position so each call site reads
+;; accurately.
+
+;;; Code:
+
+(defsubst test-scheduler--extract-interval (result)
+ "Extract the next-interval (position 0) from a scheduler RESULT list."
+ (nth 0 result))
+
+(defsubst test-scheduler--extract-repeats (result)
+ "Extract the repeats count (position 1) from a scheduler RESULT list."
+ (nth 1 result))
+
+(defsubst test-scheduler--extract-ef (result)
+ "Extract the easiness factor (position 2) from an SM2 or SM5 RESULT list."
+ (nth 2 result))
+
+(defsubst test-scheduler--extract-ease (result)
+ "Alias for `test-scheduler--extract-ef' (same `nth' position).
+Use this name in Simple8 tests where the field is called `ease' not `ef'."
+ (nth 2 result))
+
+(defsubst test-scheduler--extract-failures (result)
+ "Extract the failure count (position 3) from a scheduler RESULT list."
+ (nth 3 result))
+
+(defsubst test-scheduler--extract-meanq (result)
+ "Extract the mean quality (position 4) from a scheduler RESULT list."
+ (nth 4 result))
+
+(defsubst test-scheduler--extract-total-repeats (result)
+ "Extract the total repeats count (position 5) from a scheduler RESULT list."
+ (nth 5 result))
+
+(defsubst test-scheduler--extract-of-matrix (result)
+ "Extract the optimal-factor matrix (position 6) from an SM5 RESULT list."
+ (nth 6 result))
+
+(provide 'testutil-scheduler)
+;;; testutil-scheduler.el ends here