test_message_cache.ml
(*****************************************************************************)
(* *)
(* Open Source License *)
(* Copyright (c) 2023 Marigold <contact@marigold.dev> *)
(* *)
(* Permission is hereby granted, free of charge, to any person obtaining a *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)
(* and/or sell copies of the Software, and to permit persons to whom the *)
(* Software is furnished to do so, subject to the following conditions: *)
(* *)
(* The above copyright notice and this permission notice shall be included *)
(* in all copies or substantial portions of the Software. *)
(* *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)
(* DEALINGS IN THE SOFTWARE. *)
(* *)
(*****************************************************************************)
(* Testing
-------
Component: Gossipsub/Message Cache
Invocation: dune exec test/test_gossipsub.exe -- --file test_message_cache.ml
Subject: Unit tests for message cache used in gossipsub
*)
open Test_gossipsub_shared
open Tezt
open Tezt_core.Base
module Peer = C.Subconfig.Peer
module Topic = C.Subconfig.Topic
module Message_id = C.Subconfig.Message_id
module Message = C.Subconfig.Message
(** [new_message ()] returns a fresh (message_id, message) pair. *)
let new_message : unit -> Message_id.t * Message.t =
let i = ref 0 in
fun () ->
let new_message = (!i, Format.sprintf "message%d" !i) in
incr i ;
new_message
let new_messages n = Array.init n (fun _ -> new_message ()) |> Array.to_list
(** [add_messages topic messages message_cache] adds [messages] to [message_cache]
under [topic]. *)
let add_messages topic messages message_cache =
List.fold_left
(fun m (message_id, message) ->
Message_cache.add_message message_id message topic m)
message_cache
messages
(** [check_messages_in_cache messages message_cache ~expect_included]
checks that [messages] are retrievable from [message_cache] if [expect_included = true].
If [expect_included = false], it checks that [messages] are not retrievable from [message_cache] *)
let check_messages_inclusion ~__LOC__ messages message_cache ~expect_included =
let random_peer = 99 in
List.iter
(fun (message_id, message) ->
match
Message_cache.get_message_for_peer random_peer message_id message_cache
with
| None ->
if expect_included then
Test.fail
~__LOC__
"Expected message %a to be in cache."
Message.pp
message
else (* Expected *)
()
| Some (_, returned_message, _) ->
if expect_included then
Check.(
(returned_message = message)
string
~error_msg:"Expected %R, got %L"
~__LOC__)
else
Test.fail
~__LOC__
"Expected message %a to not be in cache."
Message.pp
message)
messages
(** [check_gossip_ids topic message_cache ~expected_message_ids] checks that
[expected_message_ids] are in the gossiped message ids generated from [message_cache]. *)
let check_gossip_ids ~__LOC__ topic message_cache ~expected_message_ids =
let gossip_message_ids =
Message_cache.get_message_ids_to_gossip topic message_cache
in
Check.(
(gossip_message_ids = expected_message_ids)
(list int)
~error_msg:"Expected %R, got %L"
~__LOC__)
(* Tests the sliding window behavior of the message cache.
Ported from: https://github.com/libp2p/go-libp2p-pubsub/blob/56c0e6c5c9dfcc6cd3214e59ef19456dc0171574/mcache_test.go#L11
*)
let test_message_cache () =
Tezt_core.Test.register
~__FILE__
~title:"Gossipsub/Message Cache: Test message cache"
~tags:["gossipsub"; "cache"; "message_cache"]
@@ fun () ->
let m =
Message_cache.create ~gossip_slots:3 ~history_slots:5 ~seen_message_slots:99
in
let topic = "topic" in
let message_batches =
[|
new_messages 10;
new_messages 10;
new_messages 10;
new_messages 10;
new_messages 10;
new_messages 10;
|]
in
let ids_of_messages messages =
List.map (fun (message_id, _) -> message_id) messages
in
(* Add 10 messages. *)
let m = add_messages topic message_batches.(0) m in
(* Should gossip 10 messages (10 total). *)
check_gossip_ids
~__LOC__
topic
m
~expected_message_ids:(ids_of_messages message_batches.(0)) ;
(* 1st shift *)
let m = Message_cache.shift m in
(* Add another 10 messages (20 total). *)
let m = add_messages topic message_batches.(1) m in
(* Should gossip all 20 messages. *)
let expected_message_ids =
List.concat [message_batches.(0); message_batches.(1)] |> ids_of_messages
in
check_gossip_ids ~__LOC__ topic m ~expected_message_ids ;
(* 2nd shift *)
let m = Message_cache.shift m in
(* Add another 10 messages (30 total). *)
let m = add_messages topic message_batches.(2) m in
(* 3rd shift *)
let m = Message_cache.shift m in
(* Add another 10 messages (40 total). *)
let m = add_messages topic message_batches.(3) m in
(* 4th shift *)
let m = Message_cache.shift m in
(* Add another 10 messages (50 total). *)
let m = add_messages topic message_batches.(4) m in
(* 5th shift *)
let m = Message_cache.shift m in
(* Add another 10 messages (60 total). *)
let m = add_messages topic message_batches.(5) m in
(* Since the gossip window is [3], we should only gossip the last 30 messages. *)
let expected_message_ids =
List.concat [message_batches.(3); message_batches.(4); message_batches.(5)]
|> ids_of_messages
in
check_gossip_ids ~__LOC__ topic m ~expected_message_ids ;
(* Since the history window is [5], the first 10 messages should not be in the cache. *)
let first_10 = message_batches.(0) in
check_messages_inclusion ~__LOC__ first_10 m ~expect_included:false ;
(* The later 50 messages should be in the cache. *)
let later_50 =
List.concat
[
message_batches.(1);
message_batches.(2);
message_batches.(3);
message_batches.(4);
message_batches.(5);
]
in
check_messages_inclusion ~__LOC__ later_50 m ~expect_included:true ;
unit
let register () = test_message_cache ()