lmjtfy.git / packages / rules / src / tests.rs
tests.rsannotatedtests.rssource201 lines · 7.9 KB · raw
1use super::*;
2
3fn known(facts: &[(Fact, Value)]) -> Known {
4    let mut known = Known::default();
5    for (fact, value) in facts {
6        known.learn(*fact, *value);
7    }
8    known
9}
10
11const NOUL: Fact = Fact::Reads(Kind::Noul);
12const CHOICE: Fact = Fact::Reads(Kind::Choice);
13const SCORE: Fact = Fact::Reads(Kind::Score);
14const CHANCE: Fact = Fact::Reads(Kind::Chance);
15
16/// What Jev's one request teaches, with `reads` the readings that hold.
17fn facts(answerable: bool, several: bool, reads: &[Fact], scale: bool, fit: bool) -> Known {
18    let mut known = known(&[
19        (Fact::Answerable, Value::Bool(answerable)),
20        (Fact::Several, Value::Bool(several)),
21        (Fact::Scale, Value::Bool(scale)),
22        (Fact::Yes, Value::Given),
23        (Fact::Degree, Value::Given),
24        (Fact::Chance, Value::Given),
25        (Fact::Fit, Value::Bool(fit)),
26    ]);
27    for kind in [NOUL, CHOICE, SCORE, CHANCE] {
28        known.learn(kind, Value::Bool(reads.contains(&kind)));
29    }
30    known
31}
32
33#[test]
34fn with_nothing_known_every_jev_fact_is_asked_for_at_once() {
35    let next = Network::lmjtfy().next(&Known::default());
36    assert_eq!(next, Next::Ask(vec![Fact::Answerable, Fact::Several, NOUL, CHOICE, SCORE, CHANCE, Fact::Scale, Fact::Yes, Fact::Degree, Fact::Chance, Fact::Fit]));
37}
38
39#[test]
40fn what_jev_cannot_judge_ends_there() {
41    let all = facts(false, true, &[CHOICE], false, true);
42    assert_eq!(Network::lmjtfy().next(&all), Next::End(End::NotAQuestion));
43}
44
45#[test]
46fn one_yes_or_no_question_is_answered_without_the_llm() {
47    let network = Network::lmjtfy();
48    let all = facts(true, false, &[NOUL], false, true);
49    assert_eq!(network.next(&all), Next::Done);
50    assert_eq!(network.shows(&all), [Fact::Yes]);
51    assert!(network.wants(&all).is_empty());
52}
53
54#[test]
55fn a_how_much_question_is_graded_unless_it_needs_its_own_scale() {
56    let network = Network::lmjtfy();
57    let general = facts(true, false, &[SCORE], false, true);
58    assert_eq!(network.next(&general), Next::Done);
59    assert_eq!(network.shows(&general), [Fact::Degree]);
60
61    let bespoke = facts(true, false, &[SCORE], true, true);
62    assert_eq!(network.next(&bespoke), Next::Do(Effect::Draft(Want::Scale)));
63    assert!(network.shows(&bespoke).is_empty());
64    assert_eq!(network.wants(&bespoke), [Want::Scale]);
65}
66
67#[test]
68fn a_how_likely_question_is_answered_with_jevs_own_probability_and_no_llm() {
69    let network = Network::lmjtfy();
70    let odds = facts(true, false, &[CHANCE], true, true);
71    assert_eq!(network.next(&odds), Next::Done);
72    assert_eq!(network.shows(&odds), [Fact::Chance]);
73    assert!(network.wants(&odds).is_empty());
74}
75
76#[test]
77fn a_pick_or_several_questions_go_to_the_llm() {
78    let network = Network::lmjtfy();
79    assert_eq!(network.next(&facts(true, false, &[CHOICE], false, true)), Next::Do(Effect::Draft(Want::Options)));
80    for reads in [NOUL, CHOICE, SCORE, CHANCE] {
81        let several = facts(true, true, &[reads], false, true);
82        assert_eq!(network.next(&several), Next::Do(Effect::Draft(Want::Split)), "{reads:?}");
83        // Nothing is answered directly when it is several questions.
84        assert!(network.shows(&several).is_empty());
85    }
86}
87
88#[test]
89fn when_jev_is_split_both_readings_are_answered() {
90    let network = Network::lmjtfy();
91    // A yes-or-no and a pick: the yes-or-no is in hand, the pick is drafted.
92    let split = facts(true, false, &[NOUL, CHOICE], false, true);
93    assert_eq!(network.shows(&split), [Fact::Yes]);
94    assert_eq!(network.next(&split), Next::Do(Effect::Draft(Want::Options)));
95    // A yes-or-no and a how-much: both are in hand, and no LLM is asked.
96    let free = facts(true, false, &[NOUL, SCORE], false, true);
97    assert_eq!(network.shows(&free), [Fact::Yes, Fact::Degree]);
98    assert_eq!(network.next(&free), Next::Done);
99    // A pick and a how-much that needs its scale: one call writes both.
100    let both = facts(true, false, &[CHOICE, SCORE], true, true);
101    assert_eq!(network.wants(&both), [Want::Options, Want::Scale]);
102}
103
104#[test]
105fn drafts_are_judged_and_then_the_query_is_done() {
106    let network = Network::lmjtfy();
107    let mut all = facts(true, false, &[CHOICE], false, true);
108    all.learn(Fact::Drafted, YES);
109    assert_eq!(network.next(&all), Next::Do(Effect::Judge));
110    all.learn(Fact::Judged, YES);
111    assert_eq!(network.next(&all), Next::Done);
112    assert_eq!(network.shows(&all), [Fact::Judged]);
113    all.learn(Fact::Judged, NO);
114    assert_eq!(network.next(&all), Next::Done);
115    assert!(network.shows(&all).is_empty());
116    all.learn(Fact::Drafted, NO);
117    assert_eq!(network.next(&all), Next::Done);
118}
119
120#[test]
121fn every_way_the_jev_facts_can_fall_ends_or_does_something() {
122    let network = Network::lmjtfy();
123    let readings: [&[Fact]; 9] = [
124        &[NOUL],
125        &[CHOICE],
126        &[SCORE],
127        &[CHANCE],
128        &[NOUL, CHOICE],
129        &[NOUL, SCORE],
130        &[CHOICE, SCORE],
131        &[NOUL, CHANCE],
132        &[SCORE, CHANCE],
133    ];
134    for answerable in [true, false] {
135        for several in [true, false] {
136            for reads in readings {
137                for scale in [true, false] {
138                    let all = facts(answerable, several, reads, scale, true);
139                    let next = network.next(&all);
140                    assert!(!matches!(next, Next::Ask(_)), "{all:?}");
141                    // An answerable question is either answered already or on its way to the LLM.
142                    if answerable {
143                        assert!(matches!(next, Next::Do(_)) || !network.shows(&all).is_empty(), "{all:?}");
144                    }
145                }
146            }
147        }
148    }
149}
150
151#[test]
152fn only_a_fit_question_jev_could_answer_is_listed() {
153    let network = Network::lmjtfy();
154    assert!(network.notes(&known(&[(Fact::Answerable, YES), (Fact::Fit, YES)]), Note::List));
155    assert!(!network.notes(&known(&[(Fact::Answerable, YES), (Fact::Fit, NO)]), Note::List));
156    assert!(!network.notes(&known(&[(Fact::Answerable, NO), (Fact::Fit, YES)]), Note::List));
157    assert!(!network.notes(&known(&[(Fact::Answerable, YES)]), Note::List));
158}
159
160#[test]
161fn a_note_never_decides_a_step() {
162    let network = Network::lmjtfy();
163    let all = facts(true, false, &[NOUL, CHOICE], false, true);
164    let (by, next) = network.decide(&all);
165    assert_eq!(next, Next::Do(Effect::Draft(Want::Options)));
166    assert_eq!(network.terminals[by.unwrap()].name, "draft options");
167    assert_eq!(network.decide(&Known::default()).0, None);
168}
169
170#[test]
171fn rules_share_the_tests_they_have_in_common() {
172    let network = Network::lmjtfy();
173    let tests: usize = RULES.iter().map(|rule| rule.when.len()).sum();
174    assert_eq!(tests, 27);
175    // "one answerable question" is five rules' first two tests, and two joins.
176    let one = network.joins.iter().filter(|join| join.depth == 2 && network.alphas[join.alpha] == ONE[1]).count();
177    assert_eq!(one, 1);
178    assert_eq!(network.terminals.len(), RULES.len());
179    assert!(network.joins.len() < tests);
180}
181
182#[test]
183fn a_failed_test_fails_everything_after_it_and_nothing_before() {
184    let network = Network::lmjtfy();
185    let some = known(&[(Fact::Answerable, YES), (Fact::Several, NO), (NOUL, NO)]);
186    let direct = network.terminals.iter().find(|terminal| terminal.name == "answer yes or no").unwrap();
187    assert_eq!(network.join(direct.join, &some), State::Fails);
188    let options = network.terminals.iter().find(|terminal| terminal.name == "draft options").unwrap();
189    assert_eq!(network.join(options.join, &some), State::Waits);
190    let one = network.joins[options.join].left.unwrap();
191    assert_eq!(network.join(one, &some), State::Holds);
192}
193
194#[test]
195fn only_what_a_holding_rule_stands_on_is_used() {
196    let network = Network::lmjtfy();
197    let all = facts(true, false, &[CHOICE], false, false);
198    let (_, alphas) = network.used(&all);
199    let used: Vec<Test> = network.alphas.iter().zip(&alphas).filter(|(_, used)| **used).map(|(test, _)| *test).collect();
200    assert_eq!(used, [Test::Is(Fact::Answerable, YES), Test::Is(Fact::Several, NO), Test::Is(CHOICE, YES)]);
201}