(defpackage #:organic-cafe/tests (:use #:cl) (:export #:run-tests)) (in-package #:organic-cafe/tests) (defun menu-path (filename) (asdf:system-relative-pathname "organic-cafe" (format nil "menus/~A" filename))) (defmacro check (form) `(unless ,form (error "Check failed: ~S" ',form))) (defun selections (candidate meal label) (remove-if-not (lambda (selection) (and (eql meal (organic-cafe::selection-meal selection)) (eql label (organic-cafe::selection-label selection)))) (organic-cafe::candidate-selections candidate))) (defun check-within-budget (plans budget-cents) (check (every (lambda (candidate) (<= (organic-cafe::candidate-total candidate) budget-cents)) plans))) (defun test-peoples-organic () (let* ((menu (organic-cafe:load-menu (menu-path "peoples-organic.sexp"))) (candidates ;; test a smaller budget to check filter (organic-cafe:feasible-candidates menu :full-day 85)) (plans (organic-cafe:generate-plans menu :full-day 85 3 :state (sb-ext:seed-random-state 1337)))) (check (= 107168 (length candidates))) (check-within-budget candidates 8500) (check (= 3 (length plans))) (check-within-budget plans 8500) (length candidates))) (defun test-townhall () (let* ((menu (organic-cafe:load-menu (menu-path "townhall-shortnorth.sexp"))) (plans (organic-cafe:generate-plans menu :full-day 70 3 :state (sb-ext:seed-random-state 67))) (lazy-plan (first (organic-cafe:generate-plans menu :lazy-day 30 1 :state (sb-ext:seed-random-state 69)))) (branch-samples (organic-cafe:generate-plans menu :full-day 100 20 :state (sb-ext:seed-random-state 69) :diversity-pool-size 1))) (check (= 3 (length plans))) (check-within-budget plans 7000) (dolist (candidate plans) (check (= 2 (length (selections candidate :lunch :side)))) (check (= 2 (length (selections candidate :lunch :garnish)))) (check (= 2 (length (remove-duplicates (mapcar (lambda (selection) (organic-cafe::item-name (organic-cafe::selection-item selection))) (selections candidate :lunch :side)) :test #'string=)))) (let ((shared (selections candidate :dinner :shared-plate)) (bowl (selections candidate :dinner :protein))) (check (not (and shared bowl))) (check (or shared bowl)) (check shared))) (check (<= (organic-cafe::candidate-total lazy-plan) 3000)) (check (= 4 (length (organic-cafe::candidate-selections lazy-plan)))) (dolist (candidate branch-samples) (let ((shared (selections candidate :dinner :shared-plate)) (bowl (selections candidate :dinner :protein))) (check (not (and shared bowl))) (check (or shared bowl)) (when bowl (check (= 2 (length (selections candidate :dinner :side)))) (check (= 2 (length (selections candidate :dinner :garnish))))))) ;; At a roomy budget, both weighted dinner branches are reachable. (check (some (lambda (candidate) (selections candidate :dinner :shared-plate)) branch-samples)) (check (some (lambda (candidate) (selections candidate :dinner :protein)) branch-samples)) t)) (defun test-cli-lazy-day-budget () (let* ((peoples-menu (menu-path "peoples-organic.sexp")) (output (with-output-to-string (stream) (let ((*standard-output* stream)) (organic-cafe:run-cli (list "--days" "1" "--add-lazy-day" "30" "--seed" "1337") :default-menu peoples-menu))))) (check (search "Day 1" output)) (check (search "Lazy Day (outbound)" output)) (check (not (search "Lazy Day 2" output))) (check (handler-case (let ((*standard-output* (make-broadcast-stream))) (organic-cafe:run-cli (list "--days" "1" "--add-lazy-day" "1" "--seed" "1337") :default-menu peoples-menu) nil) (error (condition) (search "LAZY-DAY" (string-upcase (princ-to-string condition)))))) (check (handler-case (progn (organic-cafe:run-cli (list "--plan" "lazy-day") :default-menu peoples-menu) nil) (error (condition) (search "--add-lazy-day" (princ-to-string condition))))) t)) (defun run-tests () (let ((peoples-count (test-peoples-organic))) (test-townhall) (test-cli-lazy-day-budget) (format t "~&All organic-cafe tests passed (~:D enumerated People's days; TownHall DP sampling covered).~%" peoples-count) t))