diff --git a/.gitlab/ci/jobs/packaging/opam_package.yml b/.gitlab/ci/jobs/packaging/opam_package.yml index f18421168fb0501172f838b28efb0b51726ba8b8..7c4053d2bc4721afb7a2842292994f035e28f663 100644 --- a/.gitlab/ci/jobs/packaging/opam_package.yml +++ b/.gitlab/ci/jobs/packaging/opam_package.yml @@ -611,6 +611,8 @@ opam:tezos-crypto-dal: # Ignoring unreleased package tezos-dac-node-lib. +# Ignoring unreleased package tezos-dac-node-lib-test. + # Ignoring unreleased package tezos-dal-016-PtMumbai. # Ignoring unreleased package tezos-dal-alpha. diff --git a/dune-project b/dune-project index 86074daf942db83ea1446cf622e671a1deb3858f..4565a0c8033c3381dea5d50c973df21951ecfe72 100644 --- a/dune-project +++ b/dune-project @@ -79,6 +79,7 @@ (package (name tezos-crypto-dal)) (package (name tezos-dac-alpha)) (package (name tezos-dac-node-lib)(allow_empty)) +(package (name tezos-dac-node-lib-test)(allow_empty)) (package (name tezos-dal-016-PtMumbai)) (package (name tezos-dal-alpha)) (package (name tezos-dal-node-lib)(allow_empty)) diff --git a/manifest/main.ml b/manifest/main.ml index ba3ce039ae854fb80f0d97dd34a5da67265d4beb..96c122bf344e33c45589d413182de2f7304bc3ea 100644 --- a/manifest/main.ml +++ b/manifest/main.ml @@ -3293,6 +3293,23 @@ let octez_dac_node_lib = octez_stdlib_unix |> open_; ] +let _octez_dac_node_lib_tests = + test + "main" + ~path:"src/lib_dac_node/test" + ~opam:"tezos-dac-node-lib-test" + ~synopsis:"Test for dac node lib" + ~deps: + [ + octez_stdlib |> open_; + octez_stdlib_unix |> open_; + octez_base |> open_ |> open_ ~m:"TzPervasives"; + octez_test_helpers |> open_; + octez_base_test_helpers |> open_; + octez_dac_node_lib |> open_; + alcotest_lwt; + ] + let octez_node_config = public_lib "octez-node-config" diff --git a/opam/tezos-dac-node-lib-test.opam b/opam/tezos-dac-node-lib-test.opam new file mode 100644 index 0000000000000000000000000000000000000000..f21b671a59dda4ffc68ec874dc9cfd9416647ffb --- /dev/null +++ b/opam/tezos-dac-node-lib-test.opam @@ -0,0 +1,26 @@ +# This file was automatically generated, do not edit. +# Edit file manifest/main.ml instead. +opam-version: "2.0" +maintainer: "contact@tezos.com" +authors: ["Tezos devteam"] +homepage: "https://www.tezos.com/" +bug-reports: "https://gitlab.com/tezos/tezos/issues" +dev-repo: "git+https://gitlab.com/tezos/tezos.git" +license: "MIT" +depends: [ + "dune" { >= "3.0" } + "ocaml" { >= "4.14" } + "tezos-stdlib" {with-test} + "tezos-stdlib-unix" {with-test} + "tezos-base" {with-test} + "tezos-test-helpers" {with-test} + "tezos-base-test-helpers" {with-test} + "tezos-dac-node-lib" {with-test} + "alcotest-lwt" { with-test & >= "1.5.0" } +] +build: [ + ["rm" "-r" "vendors"] + ["dune" "build" "-p" name "-j" jobs] + ["dune" "runtest" "-p" name "-j" jobs] {with-test} +] +synopsis: "Test for dac node lib" diff --git a/src/bin_dac_node/data_streamer.ml b/src/lib_dac_node/data_streamer.ml similarity index 78% rename from src/bin_dac_node/data_streamer.ml rename to src/lib_dac_node/data_streamer.ml index 6427267958181b7bec772f382737d199c726490a..d84209b2cea7349693a1a08b823e17ad39625477 100644 --- a/src/bin_dac_node/data_streamer.ml +++ b/src/lib_dac_node/data_streamer.ml @@ -2,6 +2,7 @@ (* *) (* Open Source License *) (* Copyright (c) 2023 TriliTech, *) +(* Copyright (c) 2023 Marigold *) (* *) (* Permission is hereby granted, free of charge, to any person obtaining a *) (* copy of this software and associated documentation files (the "Software"),*) @@ -23,21 +24,12 @@ (* *) (*****************************************************************************) -(** FIXME: https://gitlab.com/tezos/tezos/-/issues/4740 - Implement a useful Root_hash_streamer -*) -module Root_hash_streamer = struct - type t = unit +type 'a t = 'a Lwt_watcher.input - type configuration = unit +let init () = Lwt_watcher.create_input () - let init (_configuration : configuration) = () +let publish streamer hash = + Lwt_result_syntax.return @@ Lwt_watcher.notify streamer hash - let publish (_streamer : t) (_hash : Dac_plugin.Dac_hash.t) = - Lwt_result_syntax.return_unit - - let make_subscription (_streamer : t) : - (Dac_plugin.Dac_hash.t Lwt_stream.t * Lwt_watcher.stopper) tzresult Lwt.t - = - Lwt_result_syntax.return @@ Lwt_watcher.create_fake_stream () -end +let handle_subscribe streamer = + Lwt_result_syntax.return @@ Lwt_watcher.create_stream streamer diff --git a/src/lib_dac_node/data_streamer.mli b/src/lib_dac_node/data_streamer.mli new file mode 100644 index 0000000000000000000000000000000000000000..361511e473766bf2afeaf7f389b2deff0b43dc33 --- /dev/null +++ b/src/lib_dac_node/data_streamer.mli @@ -0,0 +1,51 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 TriliTech, *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** [Data_streamer] is an in-memory data structure for handling pub-sub + mechanism of streaming data from publishers to subscribers. +*) + +(* TODO https://gitlab.com/tezos/tezos/-/issues/4848 + To make [Data_streamer] interface implementation agnostic we should + replace [Lwt_watcher.stopper] with an abstract [stopper] type, + and add [unsubscribe] method that uses it. +*) + +(** ['a t] represents an instance of [Data_streamer], where ['a] + is the type of the data that we stream. *) +type 'a t + +(** Initializes an instance of [Data_streamer.t]. *) +val init : unit -> 'a t + +(** [publish streamer data] publishes [data] to all attached + subscribers of the [streamer]. *) +val publish : 'a t -> 'a -> unit tzresult Lwt.t + +(** [handle_subscribe streamer] returns a new stream of data for the + subscriber to consume. An [Lwt_watcher.stopper] function is also returned + for the subscriber to close the stream. *) +val handle_subscribe : + 'a t -> ('a Lwt_stream.t * Lwt_watcher.stopper) tzresult Lwt.t diff --git a/src/lib_dac_node/test/dune b/src/lib_dac_node/test/dune new file mode 100644 index 0000000000000000000000000000000000000000..1205238ef9a21a3ccafb614ae265c03a33e1ba13 --- /dev/null +++ b/src/lib_dac_node/test/dune @@ -0,0 +1,27 @@ +; This file was automatically generated, do not edit. +; Edit file manifest/main.ml instead. + +(executable + (name main) + (libraries + tezos-stdlib + tezos-stdlib-unix + tezos-base + tezos-test-helpers + tezos-base-test-helpers + tezos_dac_node_lib + alcotest-lwt) + (flags + (:standard) + -open Tezos_stdlib + -open Tezos_stdlib_unix + -open Tezos_base + -open Tezos_base.TzPervasives + -open Tezos_test_helpers + -open Tezos_base_test_helpers + -open Tezos_dac_node_lib)) + +(rule + (alias runtest) + (package tezos-dac-node-lib-test) + (action (run %{dep:./main.exe}))) diff --git a/src/bin_dac_node/data_streamer.mli b/src/lib_dac_node/test/main.ml similarity index 66% rename from src/bin_dac_node/data_streamer.mli rename to src/lib_dac_node/test/main.ml index 76f920a159a315b5516fbe867a5d5e417cc8833c..51faa35f86c2783039788828a12fcdfc5b276b5d 100644 --- a/src/bin_dac_node/data_streamer.mli +++ b/src/lib_dac_node/test/main.ml @@ -1,7 +1,7 @@ (*****************************************************************************) (* *) (* Open Source License *) -(* Copyright (c) 2023 TriliTech, *) +(* Copyright (c) 2023 Marigold *) (* *) (* Permission is hereby granted, free of charge, to any person obtaining a *) (* copy of this software and associated documentation files (the "Software"),*) @@ -23,29 +23,29 @@ (* *) (*****************************************************************************) -(** [Root_hash_streamer] manages the pub-sub mechanism for streaming root - page hashes from publishers to subscribers. Root hash refers to the - root hash of the DAC payload Merkle tree. -*) -module Root_hash_streamer : sig - type t +module Unit_test : sig + (** + * Example: [spec "Data_streamer.ml" Test_data_streamer.tests] + * Unit tests needs tag in log (like "[UNIT] some test description here...") + * This function handles such meta data *) + val spec : + string -> + unit Alcotest_lwt.test_case list -> + string * unit Alcotest_lwt.test_case list - (* Streamer configuration. *) - type configuration + (** Tests with description string without [Unit] are skipped *) + val _skip : + string -> + unit Alcotest_lwt.test_case list -> + string * unit Alcotest_lwt.test_case list +end = struct + let spec unit_name test_cases = ("[Unit] " ^ unit_name, test_cases) - (** Initializes a [Root_hash_streamer.t] *) - val init : configuration -> t - - (** [publish streamer root_hash] publishes a [root_hash] to all attached - subscribers in [streamer]. - *) - val publish : t -> Dac_plugin.Dac_hash.t -> unit tzresult Lwt.t - - (** [make_subscription streamer] returns a new stream of hashes for the subscriber to - consume. An [Lwt_watcher.stopper] function is also returned for the - subscriber to close the stream. - *) - val make_subscription : - t -> - (Dac_plugin.Dac_hash.t Lwt_stream.t * Lwt_watcher.stopper) tzresult Lwt.t + let _skip unit_name test_cases = ("[SKIPPED] " ^ unit_name, test_cases) end + +let () = + Alcotest_lwt.run + "protocol > unit" + [Unit_test.spec "Data_streamer.ml" Test_data_streamer.tests] + |> Lwt_main.run diff --git a/src/lib_dac_node/test/test_data_streamer.ml b/src/lib_dac_node/test/test_data_streamer.ml new file mode 100644 index 0000000000000000000000000000000000000000..e518c7b3fee63b072fb1fe20976d90c1206ffecf --- /dev/null +++ b/src/lib_dac_node/test/test_data_streamer.ml @@ -0,0 +1,116 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Marigold *) +(* *) +(* 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: Lib_dac_node Data_streamer + Invocation: dune exec src/lib_dac_node/test/main.exe \ + -- test "^\[Unit\] Data_streamer.ml$" + Subject: Tests for the data streamer component. +*) + +let dac_hash_1 = "hash_1" + +let dac_hash_2 = "hash_2" + +let assert_equal_string expected actual = + Assert.equal + ~loc:__LOC__ + ~eq:String.equal + ~pp:Format.pp_print_string + expected + actual + +let test_simple_pub_sub () = + let open Lwt_result_syntax in + let open Data_streamer in + let streamer = init () in + let* stream, stopper = handle_subscribe streamer in + let* () = publish streamer dac_hash_1 in + let*! next = Lwt_stream.next stream in + let () = Lwt_watcher.shutdown stopper in + let*! is_empty = Lwt_stream.is_empty stream in + let () = Assert.assert_true "Expected empty stream." is_empty in + return @@ assert_equal_string dac_hash_1 next + +let test_subscription_time () = + (* 1. subscriber_1 subscribes to the streamer component. + 2. [dac_hash_1] is published via the streamer component. + 3. subscriber_2 subscribes to the streamer component. + 4. [dac_hash_2] is published via streamer component. + + As such we expect: + - subscriber_1 first element inside the stream is [dac_hash_1] + and second one is [dac_hash_2]. + - subscriber_2 first element inside the stream is [dac_hash_2]. + + Note that subscriber_2 does not receive [dac_hash_1] as it was published + before his/her subscription to the streaming component. + *) + let open Lwt_result_syntax in + let open Data_streamer in + let streamer = init () in + let* stream_1, _stopper_1 = handle_subscribe streamer in + let* () = publish streamer dac_hash_1 in + let* stream_2, _stopper_2 = handle_subscribe streamer in + let* () = publish streamer dac_hash_2 in + let*! subscriber_1_first = Lwt_stream.next stream_1 in + let () = assert_equal_string dac_hash_1 subscriber_1_first in + let*! subscriber_1_second = Lwt_stream.next stream_1 in + let () = assert_equal_string dac_hash_2 subscriber_1_second in + let*! subscriber_2_first = Lwt_stream.next stream_2 in + return @@ assert_equal_string dac_hash_2 subscriber_2_first + +let test_closing_subscriber_stream () = + (* 1. subscriber_1 subscribes to the streamer component. + 2. subscriber_2 subscribes to the streamer component. + 3. subscriber_1's [stream_1] is closed. + 4. [dac_hash_1] is published via streamer component. + + As such we expect: + - subscriber_1's [stream_1] is empty. + - subscriber_2 first element inside the stream is [dac_hash_1]. + *) + let open Lwt_result_syntax in + let open Data_streamer in + let streamer = init () in + let* stream_1, stopper_1 = handle_subscribe streamer in + let* stream_2, _stopper_2 = handle_subscribe streamer in + let () = Lwt_watcher.shutdown stopper_1 in + let* () = publish streamer dac_hash_1 in + let*! is_empty = Lwt_stream.is_empty stream_1 in + let () = Assert.assert_true "[stream_1]: expected empty stream." is_empty in + let*! subscriber_2_first = Lwt_stream.next stream_2 in + return @@ assert_equal_string dac_hash_1 subscriber_2_first + +let tests = + [ + Tztest.tztest "Simple pub sub" `Quick test_simple_pub_sub; + Tztest.tztest "Test order of subscription" `Quick test_subscription_time; + Tztest.tztest + "Test closing subscriber stream" + `Quick + test_closing_subscriber_stream; + ]