/ test / beamai_memory_forgetting_test.erl
beamai_memory_forgetting_test.erl
  1  %%%-------------------------------------------------------------------
  2  %%% @doc beamai_memory_forgetting 模块单元测试
  3  %%%
  4  %%% 测试覆盖:
  5  %%% - 基于重要性的遗忘策略
  6  %%% - LRU 遗忘策略
  7  %%% - 时间衰减遗忘策略
  8  %%% - FIFO 遗忘策略
  9  %%% - 混合遗忘策略
 10  %%% - 时间衰减计算
 11  %%% - 访问跟踪
 12  %%%
 13  %%% @end
 14  %%%-------------------------------------------------------------------
 15  -module(beamai_memory_forgetting_test).
 16  
 17  -include_lib("eunit/include/eunit.hrl").
 18  -include_lib("beamai_memory/include/beamai_episodic_memory.hrl").
 19  
 20  %%====================================================================
 21  %%% 测试函数
 22  %%====================================================================
 23  
 24  %%--------------------------------------------------------------------
 25  %% @doc 测试基于重要性的遗忘策略
 26  %%--------------------------------------------------------------------
 27  importance_forgetting_test() ->
 28      Now = erlang:system_time(millisecond),
 29  
 30      %% 创建测试记忆:不同重要性
 31      Memories = [
 32          #{id => <<"1">>, importance => 0.3, created_at => Now},
 33          #{id => <<"2">>, importance => 0.9, created_at => Now},
 34          #{id => <<"3">>, importance => 0.6, created_at => Now},
 35          #{id => <<"4">>, importance => 0.2, created_at => Now}
 36      ],
 37  
 38      %% 应用遗忘策略,保留前 2 个
 39      Filtered = beamai_memory_forgetting:forget(Memories, 2, importance),
 40  
 41      %% 验证:应该保留 importance=0.9 和 0.6 的记忆
 42      ?assertEqual(2, length(Filtered)),
 43      Ids = [maps:get(id, M) || M <- Filtered],
 44      ?assert(lists:member(<<"2">>, Ids)),  %% importance=0.9
 45      ?assert(lists:member(<<"3">>, Ids)),  %% importance=0.6
 46      ?assertNot(lists:member(<<"1">>, Ids)), %% importance=0.3
 47      ?assertNot(lists:member(<<"4">>, Ids)), %% importance=0.2
 48  
 49      ok.
 50  
 51  %%--------------------------------------------------------------------
 52  %% @doc 测试 LRU 遗忘策略
 53  %%--------------------------------------------------------------------
 54  lru_forgetting_test() ->
 55      Now = erlang:system_time(millisecond),
 56      OneHourAgo = Now - (60 * 60 * 1000),
 57      TwoHoursAgo = Now - (2 * 60 * 60 * 1000),
 58  
 59      %% 创建测试记忆:不同访问时间
 60      Memories = [
 61          #{id => <<"1">>, last_accessed_at => TwoHoursAgo, created_at => Now},
 62          #{id => <<"2">>, last_accessed_at => Now, created_at => Now},
 63          #{id => <<"3">>, last_accessed_at => OneHourAgo, created_at => Now}
 64      ],
 65  
 66      %% 应用 LRU 策略,保留前 2 个
 67      Filtered = beamai_memory_forgetting:forget(Memories, 2, lru),
 68  
 69      %% 验证:应该保留最近访问的记忆
 70      ?assertEqual(2, length(Filtered)),
 71      Ids = [maps:get(id, M) || M <- Filtered],
 72      ?assert(lists:member(<<"2">>, Ids)),  %% 最近访问
 73      ?assert(lists:member(<<"3">>, Ids)),  %% 1小时前访问
 74      ?assertNot(lists:member(<<"1">>, Ids)), %% 2小时前访问
 75  
 76      ok.
 77  
 78  %%--------------------------------------------------------------------
 79  %% @doc 测试时间衰减遗忘策略
 80  %%--------------------------------------------------------------------
 81  time_decay_forgetting_test() ->
 82      Now = erlang:system_time(millisecond),
 83      OneDayAgo = Now - (24 * 60 * 60 * 1000),
 84      TwoDaysAgo = Now - (2 * 24 * 60 * 60 * 1000),
 85  
 86      %% 创建测试记忆:不同创建时间
 87      Memories = [
 88          #{id => <<"1">>, created_at => TwoDaysAgo},
 89          #{id => <<"2">>, created_at => Now},
 90          #{id => <<"3">>, created_at => OneDayAgo}
 91      ],
 92  
 93      %% 应用时间衰减策略,保留前 2 个
 94      Filtered = beamai_memory_forgetting:forget(Memories, 2, time_decay),
 95  
 96      %% 验证:应该保留最新的记忆
 97      ?assertEqual(2, length(Filtered)),
 98      Ids = [maps:get(id, M) || M <- Filtered],
 99      ?assert(lists:member(<<"2">>, Ids)),  %% 最新
100      ?assert(lists:member(<<"3">>, Ids)),  %% 1天前
101      ?assertNot(lists:member(<<"1">>, Ids)), %% 2天前
102  
103      ok.
104  
105  %%--------------------------------------------------------------------
106  %% @doc 测试 FIFO 遗忘策略
107  %%--------------------------------------------------------------------
108  fifo_forgetting_test() ->
109      Now = erlang:system_time(millisecond),
110  
111      %% 创建测试记忆:不同创建时间
112      Memories = [
113          #{id => <<"1">>, created_at => Now - 3000},
114          #{id => <<"2">>, created_at => Now - 2000},
115          #{id => <<"3">>, created_at => Now - 1000},
116          #{id => <<"4">>, created_at => Now}
117      ],
118  
119      %% 应用 FIFO 策略,保留最后 2 个
120      Filtered = beamai_memory_forgetting:forget(Memories, 2, fifo),
121  
122      %% 验证:应该保留最后加入的记忆
123      ?assertEqual(2, length(Filtered)),
124      Ids = [maps:get(id, M) || M <- Filtered],
125      ?assert(lists:member(<<"3">>, Ids)),
126      ?assert(lists:member(<<"4">>, Ids)),
127      ?assertNot(lists:member(<<"1">>, Ids)),
128      ?assertNot(lists:member(<<"2">>, Ids)),
129  
130      ok.
131  
132  %%--------------------------------------------------------------------
133  %% @doc 测试混合遗忘策略
134  %%--------------------------------------------------------------------
135  hybrid_forgetting_test() ->
136      Now = erlang:system_time(millisecond),
137  
138      %% 创建测试记忆:综合考虑重要性和时间
139      Memories = [
140          #{id => <<"1">>, importance => 0.9, created_at => Now - (2 * 60 * 60 * 1000), last_accessed_at => Now - (1 * 60 * 60 * 1000)},
141          #{id => <<"2">>, importance => 0.5, created_at => Now, last_accessed_at => Now},
142          #{id => <<"3">>, importance => 0.7, created_at => Now, last_accessed_at => Now},
143          #{id => <<"4">>, importance => 0.2, created_at => Now, last_accessed_at => Now}
144      ],
145  
146      %% 应用混合策略,保留前 2 个
147      Filtered = beamai_memory_forgetting:forget(Memories, 2, hybrid),
148  
149      %% 验证:应该保留综合评分最高的记忆
150      ?assertEqual(2, length(Filtered)),
151      Ids = [maps:get(id, M) || M <- Filtered],
152      %% M3 (0.7 + 新) 和 M1 (0.9,虽然稍旧但重要性高) 应该被保留
153      %% 计算评分:
154      %% M1: 0.6*0.9 + 0.3*0.5 + 0.1*0.92 = 0.54 + 0.15 + 0.092 = 0.782
155      %% M3: 0.6*0.7 + 0.3*1 + 0.1*1 = 0.42 + 0.3 + 0.1 = 0.82
156      %% M2: 0.6*0.5 + 0.3*1 + 0.1*1 = 0.3 + 0.3 + 0.1 = 0.7
157      ?assert(lists:member(<<"3">>, Ids)),  %% 最高综合评分
158      ?assert(lists:member(<<"1">>, Ids)),  %% 次高综合评分
159  
160      ok.
161  
162  %%--------------------------------------------------------------------
163  %% @doc 测试时间衰减计算
164  %%--------------------------------------------------------------------
165  decay_importance_test() ->
166      %% 测试 map 类型记忆
167      Memory1 = #{importance => 0.9, content => <<"test">>},
168      Decayed1 = beamai_memory_forgetting:decay_importance(Memory1, 10),
169      NewImp1 = maps:get(importance, Decayed1),
170      ?assert(NewImp1 < 0.9),  %% 应该衰减
171      ?assert(NewImp1 > 0.0),   %% 但不会完全消失
172  
173      %% 测试 episode 记录
174      Episode = #episode{importance = 0.8},
175      DecayedEpisode = beamai_memory_forgetting:decay_importance(Episode, 10),
176      ?assert(DecayedEpisode#episode.importance < 0.8),
177  
178      %% 测试 event 记录
179      Event = #event{importance = 0.7},
180      DecayedEvent = beamai_memory_forgetting:decay_importance(Event, 10),
181      ?assert(DecayedEvent#event.importance < 0.7),
182  
183      %% 测试批量衰减
184      Memories = [
185          #{importance => 0.9},
186          #{importance => 0.5}
187      ],
188      DecayedMemories = beamai_memory_forgetting:batch_decay_importance(Memories, 10),
189      ?assertEqual(2, length(DecayedMemories)),
190      [Imp1, Imp2] = [maps:get(importance, M) || M <- DecayedMemories],
191      ?assert(Imp1 < 0.9),
192      ?assert(Imp2 < 0.5),
193  
194      ok.
195  
196  %%--------------------------------------------------------------------
197  %% @doc 测试访问跟踪
198  %%--------------------------------------------------------------------
199  access_tracking_test() ->
200      MemoryId = <<"test_memory">>,
201  
202      %% 测试1:首次访问
203      ok = beamai_memory_forgetting:record_access(MemoryId),
204      Info1 = beamai_memory_forgetting:get_access_info(MemoryId),
205      ?assertMatch(#{access_count := 1}, Info1),
206      ?assert(maps:get(last_accessed_at, Info1) > 0),
207  
208      %% 测试2:多次访问
209      timer:sleep(10),  %% 确保时间有差异
210      ok = beamai_memory_forgetting:record_access(MemoryId),
211      Info2 = beamai_memory_forgetting:get_access_info(MemoryId),
212      ?assertEqual(2, maps:get(access_count, Info2)),
213  
214      %% 测试3:自定义时间戳
215      CustomTime = 1234567890,
216      ok = beamai_memory_forgetting:record_access(MemoryId, CustomTime),
217      Info3 = beamai_memory_forgetting:get_access_info(MemoryId),
218      ?assertEqual(CustomTime, maps:get(last_accessed_at, Info3)),
219  
220      %% 测试4:不存在的记忆
221      ?assertEqual(undefined, beamai_memory_forgetting:get_access_info(<<"non_existent">>)),
222  
223      ok.
224  
225  %%--------------------------------------------------------------------
226  %% @doc 测试未超过容量限制时不删除
227  %%--------------------------------------------------------------------
228  no_forget_when_under_limit_test() ->
229      Memories = [
230          #{id => <<"1">>, importance => 0.3},
231          #{id => <<"2">>, importance => 0.5}
232      ],
233  
234      %% 容量为 5,当前只有 2 个记忆
235      Filtered = beamai_memory_forgetting:forget(Memories, 5, importance),
236  
237      %% 应该保留所有记忆
238      ?assertEqual(2, length(Filtered)),
239      ?assertEqual([<<"1">>, <<"2">>], [maps:get(id, M) || M <- Filtered]),
240  
241      ok.
242  
243  %%--------------------------------------------------------------------
244  %% @doc 测试批量遗忘
245  %%--------------------------------------------------------------------
246  batch_forget_test() ->
247      Now = erlang:system_time(millisecond),
248  
249      %% 创建多个记忆组
250      Groups = #{
251          <<"prefs">> => [
252              #{id => <<"p1">>, importance => 0.3, created_at => Now},
253              #{id => <<"p2">>, importance => 0.7, created_at => Now},
254              #{id => <<"p3">>, importance => 0.5, created_at => Now}
255          ],
256          <<"history">> => [
257              #{id => <<"h1">>, importance => 0.2, created_at => Now},
258              #{id => <<"h2">>, importance => 0.8, created_at => Now},
259              #{id => <<"h3">>, importance => 0.4, created_at => Now},
260              #{id => <<"h4">>, importance => 0.6, created_at => Now}
261          ]
262      },
263  
264      %% 每个组应用不同容量限制
265      MaxCounts = #{<<"prefs">> => 2, <<"history">> => 2},
266  
267      FilteredGroups = beamai_memory_forgetting:batch_forget(
268          Groups,
269          MaxCounts,
270          importance,
271          #{}
272      ),
273  
274      %% 验证 prefs 组
275      Prefs = maps:get(<<"prefs">>, FilteredGroups),
276      ?assertEqual(2, length(Prefs)),
277      PrefsIds = [maps:get(id, M) || M <- Prefs],
278      ?assert(lists:member(<<"p2">>, PrefsIds)),  %% 0.7
279      ?assert(lists:member(<<"p3">>, PrefsIds)),  %% 0.5
280  
281      %% 验证 history 组
282      History = maps:get(<<"history">>, FilteredGroups),
283      ?assertEqual(2, length(History)),
284      HistoryIds = [maps:get(id, M) || M <- History],
285      ?assert(lists:member(<<"h2">>, HistoryIds)), %% 0.8
286      ?assert(lists:member(<<"h4">>, HistoryIds)), %% 0.6
287  
288      ok.
289  
290  %%--------------------------------------------------------------------
291  %% @doc 测试 episode 和 event 记录的遗忘
292  %%--------------------------------------------------------------------
293  record_forgetting_test() ->
294      Now = erlang:system_time(millisecond),
295  
296      %% 创建 episode 记录
297      Episodes = [
298          #episode{id = <<"ep1">>, importance = 0.3, created_at = Now},
299          #episode{id = <<"ep2">>, importance = 0.8, created_at = Now},
300          #episode{id = <<"ep3">>, importance = 0.5, created_at = Now}
301      ],
302  
303      FilteredEpisodes = beamai_memory_forgetting:forget(Episodes, 2, importance),
304      ?assertEqual(2, length(FilteredEpisodes)),
305      ?assertEqual([<<"ep2">>, <<"ep3">>], [E#episode.id || E <- FilteredEpisodes]),
306  
307      %% 创建 event 记录
308      Events = [
309          #event{id = <<"ev1">>, importance = 0.2, timestamp = Now},
310          #event{id = <<"ev2">>, importance = 0.9, timestamp = Now},
311          #event{id = <<"ev3">>, importance = 0.6, timestamp = Now}
312      ],
313  
314      FilteredEvents = beamai_memory_forgetting:forget(Events, 2, importance),
315      ?assertEqual(2, length(FilteredEvents)),
316      ?assertEqual([<<"ev2">>, <<"ev3">>], [E#event.id || E <- FilteredEvents]),
317  
318      ok.
319  
320  %%--------------------------------------------------------------------
321  %% @doc 测试边界条件
322  %%--------------------------------------------------------------------
323  edge_cases_test() ->
324      %% 测试空列表
325      ?assertEqual([], beamai_memory_forgetting:forget([], 5, importance)),
326  
327      %% 测试单个记忆
328      Single = [#{id => <<"1">>, importance => 0.5}],
329      ?assertEqual(1, length(beamai_memory_forgetting:forget(Single, 5, importance))),
330  
331      %% 测试 MaxCount = 0
332      Multiple = [
333          #{id => <<"1">>, importance => 0.3},
334          #{id => <<"2">>, importance => 0.7}
335      ],
336      ?assertEqual(0, length(beamai_memory_forgetting:forget(Multiple, 0, importance))),
337  
338      %% 测试 MaxCount = 1
339      Filtered = beamai_memory_forgetting:forget(Multiple, 1, importance),
340      ?assertEqual(1, length(Filtered)),
341      ?assertEqual(<<"2">>, maps:get(id, hd(Filtered))),
342  
343      ok.