diff --git a/src/components/product_copier/.all_path b/src/components/product_copier/.all_path new file mode 100644 index 00000000..e69de29b diff --git a/src/components/product_copier/component-product_copier-implementation.adb b/src/components/product_copier/component-product_copier-implementation.adb new file mode 100644 index 00000000..1daa59d8 --- /dev/null +++ b/src/components/product_copier/component-product_copier-implementation.adb @@ -0,0 +1,100 @@ +-------------------------------------------------------------------------------- +-- Product_Copier Component Implementation Body +-------------------------------------------------------------------------------- + +with Data_Product_Types; use Data_Product_Types; +with Data_Product_Enums; + +package body Component.Product_Copier.Implementation is + + -------------------------------------------------- + -- Subprogram for implementation init method: + -------------------------------------------------- + -- At initialization, this component requires a list of source/destination pairs + -- of data products to copy. + -- + -- Init Parameters: + -- Products_To_Copy : Product_Mapping_Array_Access - The list of mappings to be + -- copied by this component every tick. Raises an error on Init if the list is + -- null, as well as if two mappings share a destination. + -- You must pass a reference (using 'Access) to this function, and as a + -- consequence you can't declare your array of mappings within, say, a function's + -- declarative region, since it must not be garbage collected. Either declare it + -- in a package or use a dynamic allocation. + -- Send_Event_On_Source_Id_Out_Of_Range : Boolean - When the status of a fetch is + -- of Id_Out_Of_Range, specifies whether an error event should be sent. This could + -- indicate misconfiguration, so sending error events is the default. + -- Send_Event_On_Source_Not_Available : Boolean - When the status of a fetch is of + -- Not_Available, specifies whether an error event should be sent. This might + -- simply indicate that the product is not yet ready to be fetched, in which case + -- this is expected behavior. Accordingly, not sending error events is the + -- default. + -- + overriding procedure Init (Self : in out Instance; Products_To_Copy : in Product_Mapping_Array_Access; Send_Event_On_Source_Id_Out_Of_Range : in Boolean := True; Send_Event_On_Source_Not_Available : in Boolean := False) is + begin + -- make sure no two destinations have the same ID, otherwise raise an error + for I in Products_To_Copy'Range loop + for J in I + 1 .. Products_To_Copy'Last loop + pragma Assert ( + Products_To_Copy (I).Destination_Id /= Products_To_Copy (J).Destination_Id + ); + end loop; + end loop; + + -- copy configuration to component record + Self.Send_Event_On_Source_Id_Out_Of_Range := Send_Event_On_Source_Id_Out_Of_Range; + Self.Send_Event_On_Source_Not_Available := Send_Event_On_Source_Not_Available; + pragma Assert (Products_To_Copy /= null); + Self.Mappings := Products_To_Copy; + end Init; + + --------------------------------------- + -- Invokee connector primitives: + --------------------------------------- + -- Triggers copying of data products (through request and send connectors). + overriding procedure Tick_T_Recv_Sync (Self : in out Instance; Arg : in Tick.T) is + begin + for Mapping of Self.Mappings.all loop + declare + use Data_Product_Enums; + use Data_Product_Enums.Fetch_Status; -- required for `=` operator + -- fetch source + Dp_Return : constant Data_Product_Return.T := + Self.Data_Product_Fetch_T_Request ((Id => Mapping.Source_Id)); + begin + case Dp_Return.The_Status is + -- send error events if applicable + when Fetch_Status.Not_Available => + if Self.Send_Event_On_Source_Not_Available then + Self.Event_T_Send_If_Connected ( + Self.Events.Source_Not_Available ( + Self.Sys_Time_T_Get, + ( + Tick => Arg.Count, + Mapping => Mapping + ) + ) + ); + end if; + when Fetch_Status.Id_Out_Of_Range => + if Self.Send_Event_On_Source_Id_Out_Of_Range then + Self.Event_T_Send_If_Connected ( + Self.Events.Source_Id_Out_Of_Range ( + Self.Sys_Time_T_Get, + ( + Tick => Arg.Count, + Mapping => Mapping + ) + ) + ); + end if; + + -- send to dest + when Fetch_Status.Success => + Self.Data_Product_T_Send (Dp_Return.The_Data_Product); + end case; + end; + end loop; + end Tick_T_Recv_Sync; + +end Component.Product_Copier.Implementation; diff --git a/src/components/product_copier/component-product_copier-implementation.ads b/src/components/product_copier/component-product_copier-implementation.ads new file mode 100644 index 00000000..76988201 --- /dev/null +++ b/src/components/product_copier/component-product_copier-implementation.ads @@ -0,0 +1,82 @@ +-------------------------------------------------------------------------------- +-- Product_Copier Component Implementation Spec +-------------------------------------------------------------------------------- + +-- Includes: +with Tick; + +-- Given two locations and a list of source/destination IDs, fetches Data_Product +-- entries from one location and sends/copies them to another upon receiving a +-- Tick. +-- The use case in mind was for the two locations to be databases (e.g. +-- Product_Database instances), and for this component to take snapshots of a +-- source database at a fixed interval. The idea is that if the values stored in +-- source database is constantly in flux, then the destination database could +-- provide a stable view of the source -- within a tick, the values in the +-- destination database will not change between reads. +package Component.Product_Copier.Implementation is + + -- The component class instance record: + type Instance is new Product_Copier.Base_Instance with private; + + -------------------------------------------------- + -- Subprogram for implementation init method: + -------------------------------------------------- + -- At initialization, this component requires a list of source/destination pairs + -- of data products to copy. + -- + -- Init Parameters: + -- Products_To_Copy : Product_Mapping_Array_Access - The list of mappings to be + -- copied by this component every tick. Raises an error on Init if the list is + -- null, as well as if two mappings share a destination. + -- You must pass a reference (using 'Access) to this function, and as a + -- consequence you can't declare your array of mappings within, say, a function's + -- declarative region, since it must not be garbage collected. Either declare it + -- in a package or use a dynamic allocation. + -- Send_Event_On_Source_Id_Out_Of_Range : Boolean - When the status of a fetch is + -- of Id_Out_Of_Range, specifies whether an error event should be sent. This could + -- indicate misconfiguration, so sending error events is the default. + -- Send_Event_On_Source_Not_Available : Boolean - When the status of a fetch is of + -- Not_Available, specifies whether an error event should be sent. This might + -- simply indicate that the product is not yet ready to be fetched, in which case + -- this is expected behavior. Accordingly, not sending error events is the + -- default. + -- + overriding procedure Init (Self : in out Instance; Products_To_Copy : in Product_Mapping_Array_Access; Send_Event_On_Source_Id_Out_Of_Range : in Boolean := True; Send_Event_On_Source_Not_Available : in Boolean := False); + +private + + -- The component class instance record: + type Instance is new Product_Copier.Base_Instance with record + Send_Event_On_Source_Id_Out_Of_Range : Boolean := True; + Send_Event_On_Source_Not_Available : Boolean := False; + Mappings : Product_Mapping_Array_Access := null; + end record; + + --------------------------------------- + -- Set Up Procedure + --------------------------------------- + -- Null method which can be implemented to provide some component + -- set up code. This method is generally called by the assembly + -- main.adb after all component initialization and tasks have been started. + -- Some activities need to only be run once at startup, but cannot be run + -- safely until everything is up and running, ie. command registration, initial + -- data product updates. This procedure should be implemented to do these things + -- if necessary. + overriding procedure Set_Up (Self : in out Instance) is null; + + --------------------------------------- + -- Invokee connector primitives: + --------------------------------------- + -- Triggers copying of data products (through request and send connectors). + overriding procedure Tick_T_Recv_Sync (Self : in out Instance; Arg : in Tick.T); + + --------------------------------------- + -- Invoker connector primitives: + --------------------------------------- + -- This procedure is called when a Data_Product_T_Send message is dropped due to a full queue. + overriding procedure Data_Product_T_Send_Dropped (Self : in out Instance; Arg : in Data_Product.T) is null; + -- This procedure is called when a Event_T_Send message is dropped due to a full queue. + overriding procedure Event_T_Send_Dropped (Self : in out Instance; Arg : in Event.T) is null; + +end Component.Product_Copier.Implementation; diff --git a/src/components/product_copier/doc/product_copier.pdf b/src/components/product_copier/doc/product_copier.pdf new file mode 100644 index 00000000..3335504a Binary files /dev/null and b/src/components/product_copier/doc/product_copier.pdf differ diff --git a/src/components/product_copier/doc/product_copier.tex b/src/components/product_copier/doc/product_copier.tex new file mode 100644 index 00000000..73aa0828 --- /dev/null +++ b/src/components/product_copier/doc/product_copier.tex @@ -0,0 +1,83 @@ +\input{common_packages.tex} + +\begin{document} + +\title{\textbf{Product Copier} \\ +\large\textit{Component Design Document}} +\date{} +\maketitle + +\section{Description} +\input{build/tex/product_copier_description.tex} + +\section{Requirements} +\input{build/tex/product_copier_requirements.tex} + +\section{Design} + +\subsection{At a Glance} +\input{build/tex/product_copier_stats.tex} + +\subsection{Diagram} +\begin{figure}[H] + \includegraphics[width=1.0\textwidth,center]{../build/eps/product_copier.eps} + \caption{Product Copier component diagram.} +\end{figure} + +\subsection{Connectors} +\input{build/tex/product_copier_connectors.tex} + +\subsection{Interrupts} + +\input{build/tex/product_copier_interrupts.tex} + +\subsection{Initialization} +\input{build/tex/product_copier_init.tex} + +\subsection{Commands} + +\input{build/tex/product_copier_commands.tex} + +\subsection{Parameters} + +\input{build/tex/product_copier_parameters.tex} + +\subsection{Events} + +\input{build/tex/product_copier_events.tex} + +\subsection{Data Products} + +\input{build/tex/product_copier_data_products.tex} + +\subsection{Data Dependencies} + +\input{build/tex/product_copier_data_dependencies.tex} + +\subsection{Packets} + +\input{build/tex/product_copier_packets.tex} + +\subsection{Faults} + +\input{build/tex/product_copier_faults.tex} + +\section{Unit Tests} + +\input{build/tex/product_copier_unit_test.tex} + +\section{Appendix} + +\subsection{Preamble} + +\input{build/tex/product_copier_preamble.tex} + +\subsection{Packed Types} + +\input{build/tex/product_copier_types.tex} + +\subsection{Enumerations} + +\input{build/tex/product_copier_enums.tex} + +\end{document} diff --git a/src/components/product_copier/product_copier.component.yaml b/src/components/product_copier/product_copier.component.yaml new file mode 100644 index 00000000..a1e5fa1e --- /dev/null +++ b/src/components/product_copier/product_copier.component.yaml @@ -0,0 +1,45 @@ +--- +description: | + Given two locations and a list of source/destination IDs, fetches Data_Product entries from one location and sends/copies them to another upon receiving a Tick. + + A typical use case is for the two locations to be databases (e.g. Product_Database instances), and for this component to take snapshots of products in a source database at a fixed interval. While values stored in the source database may constantly be in flux, the destination database could provide a stable view of the source -- within a tick, the values in the destination database will not change between reads. +execution: passive +with: + - Product_Mapping +preamble: | + type Product_Mapping_Array is array (Natural range <>) of Product_Mapping.T; + type Product_Mapping_Array_Access is access all Product_Mapping_Array; +init: + description: | + At initialization, this component requires a list of source/destination pairs of data products to copy. + parameters: + - name: Products_To_Copy + type: Product_Mapping_Array_Access + description: The list of mappings to be copied by this component every tick. Raises an error on Init if the list is null, as well as if two mappings share a destination. + - name: Send_Event_On_Source_Id_Out_Of_Range + type: Boolean + default: "True" + description: When the status of a fetch is of Id_Out_Of_Range, specifies whether an error event should be sent. This could indicate misconfiguration, so sending error events is the default. + - name: Send_Event_On_Source_Not_Available + type: Boolean + default: "False" + description: When the status of a fetch is of Not_Available, specifies whether an error event should be sent. This might simply indicate that the product is not yet ready to be fetched, in which case this is expected behavior. Accordingly, not sending error events is the default. +connectors: + # invokee + - type: Tick.T + kind: recv_sync + description: Triggers copying of data products (through request and send connectors). + # invokers + - type: Data_Product.T + kind: send + description: The destination for fetched data products to be sent to. + - type: Data_Product_Fetch.T + return_type: Data_Product_Return.T + kind: request + description: Where data products are copied from. + - type: Event.T + kind: send + description: Any produced events are sent out this connector. + - return_type: Sys_Time.T + kind: get + description: Time stamps for events are fetched via this connector. diff --git a/src/components/product_copier/product_copier.events.yaml b/src/components/product_copier/product_copier.events.yaml new file mode 100644 index 00000000..f9158a04 --- /dev/null +++ b/src/components/product_copier/product_copier.events.yaml @@ -0,0 +1,8 @@ +--- +events: + - name: Source_Not_Available + description: A data product fetch resulted in an a Not_Available status, and was not sent to the destination. + param_type: Product_Copier_Error_Info.T + - name: Source_Id_Out_Of_Range + description: A data product fetch resulted in an an Id_Out_Of_Range status, and was not sent to the destination. + param_type: Product_Copier_Error_Info.T diff --git a/src/components/product_copier/product_copier.requirements.yaml b/src/components/product_copier/product_copier.requirements.yaml new file mode 100644 index 00000000..0f1c051d --- /dev/null +++ b/src/components/product_copier/product_copier.requirements.yaml @@ -0,0 +1,7 @@ +--- +description: The requirements of the component. +requirements: + - text: The component shall copy data products from one location to another, every time it is sent a Tick, given a list of source ID/destination ID mappings. + - text: The component shall fail at initialization when two mappings share the same destination ID. + description: When two mappings share the same destination, it's unclear which one should "win" and write to the destination first. If the user requires some kind of overwriting behavior, they can make this explicit by instantiating another of this component. (Assuming the send connector is connected to some kind of database write operation.) + - text: If fetching from the source results in the data product not being available or the requested ID is out of range, no data product will be copied, and execution will continue. It will raise an error event if configured to do so. diff --git a/src/components/product_copier/product_copier_error_info.record.yaml b/src/components/product_copier/product_copier_error_info.record.yaml new file mode 100644 index 00000000..7639c591 --- /dev/null +++ b/src/components/product_copier/product_copier_error_info.record.yaml @@ -0,0 +1,9 @@ +--- +description: To be sent in error events, specifying which source, destination, and tick caused the error. +fields: + - name: Tick + description: The count of the tick that caused the error. + type: Interfaces.Unsigned_32 # these fields copy the format of tick.record.yaml + format: U32 + - name: Mapping + type: Product_Mapping.T diff --git a/src/components/product_copier/product_mapping.record.yaml b/src/components/product_copier/product_mapping.record.yaml new file mode 100644 index 00000000..7e921535 --- /dev/null +++ b/src/components/product_copier/product_mapping.record.yaml @@ -0,0 +1,10 @@ +--- +description: A source/destination ID pair that specifies where data products should be copied to/from +fields: + # chose U16 to match Id in Data_Product_Header + - name: Source_Id + type: Data_Product_Types.Data_Product_Id + format: U16 + - name: Destination_Id + type: Data_Product_Types.Data_Product_Id + format: U16 diff --git a/src/components/product_copier/tests/component-product_copier-implementation-tester.adb b/src/components/product_copier/tests/component-product_copier-implementation-tester.adb new file mode 100644 index 00000000..d4f06440 --- /dev/null +++ b/src/components/product_copier/tests/component-product_copier-implementation-tester.adb @@ -0,0 +1,170 @@ +-------------------------------------------------------------------------------- +-- Product_Copier Component Tester Body +-------------------------------------------------------------------------------- + +with Data_Product_Enums; +with Data_Product_Types; +with Packed_U32; +with Packed_U16; + +package body Component.Product_Copier.Implementation.Tester is + + --------------------------------------- + -- Initialize heap variables: + --------------------------------------- + procedure Init_Base (Self : in out Instance) is + begin + -- Initialize tester heap: + -- Connector histories: + Self.Data_Product_T_Recv_Sync_History.Init (Depth => 100); + Self.Data_Product_Fetch_T_Service_History.Init (Depth => 100); + Self.Event_T_Recv_Sync_History.Init (Depth => 100); + Self.Sys_Time_T_Return_History.Init (Depth => 100); + -- Event histories: + Self.Source_Not_Available_History.Init (Depth => 100); + Self.Source_Id_Out_Of_Range_History.Init (Depth => 100); + end Init_Base; + + procedure Final_Base (Self : in out Instance) is + begin + -- Destroy tester heap: + -- Connector histories: + Self.Data_Product_T_Recv_Sync_History.Destroy; + Self.Data_Product_Fetch_T_Service_History.Destroy; + Self.Event_T_Recv_Sync_History.Destroy; + Self.Sys_Time_T_Return_History.Destroy; + -- Event histories: + Self.Source_Not_Available_History.Destroy; + Self.Source_Id_Out_Of_Range_History.Destroy; + end Final_Base; + + --------------------------------------- + -- Test initialization functions: + --------------------------------------- + procedure Connect (Self : in out Instance) is + begin + Self.Component_Instance.Attach_Data_Product_T_Send (To_Component => Self'Unchecked_Access, Hook => Self.Data_Product_T_Recv_Sync_Access); + Self.Component_Instance.Attach_Data_Product_Fetch_T_Request (To_Component => Self'Unchecked_Access, Hook => Self.Data_Product_Fetch_T_Service_Access); + Self.Component_Instance.Attach_Event_T_Send (To_Component => Self'Unchecked_Access, Hook => Self.Event_T_Recv_Sync_Access); + Self.Component_Instance.Attach_Sys_Time_T_Get (To_Component => Self'Unchecked_Access, Hook => Self.Sys_Time_T_Return_Access); + Self.Attach_Tick_T_Send (To_Component => Self.Component_Instance'Unchecked_Access, Hook => Self.Component_Instance.Tick_T_Recv_Sync_Access); + end Connect; + + --------------------------------------- + -- Invokee connector primitives: + --------------------------------------- + overriding procedure Data_Product_T_Recv_Sync (Self : in out Instance; Arg : in Data_Product.T) is + begin + -- Push the argument onto the test history for looking at later: + Self.Data_Product_T_Recv_Sync_History.Push (Arg); + end Data_Product_T_Recv_Sync; + + overriding function Data_Product_Fetch_T_Service (Self : in out Instance; Arg : in Data_Product_Fetch.T) return Data_Product_Return.T is + use Data_Product_Enums; + use Data_Product_Types; + Dp : Data_Product.T; + DP_Return : Data_Product_Return.T; + begin + -- Push the argument onto the test history for looking at later: + Self.Data_Product_Fetch_T_Service_History.Push (Arg); + + -- simulate a database, with different indices having different behavior + case Arg.Id is + when 1 => + -- always succeeds + Dp.Header.Id := 1 + 10 * Data_Product_Id (Self.Case_1_Ctr); + Dp.Header.Buffer_Length := Packed_U32.Serialization.Byte_Array'Length; + Dp.Buffer (Dp.Buffer'First .. Dp.Buffer'First + Dp.Header.Buffer_Length - 1) := Packed_U32.Serialization.To_Byte_Array ((Value => 23)); + + DP_Return.The_Status := Fetch_Status.Success; + DP_Return.The_Data_Product := Dp; + + Self.Case_1_Ctr := @ + 1; + when 2 => + -- always fails + DP_Return.The_Status := Fetch_Status.Not_Available; + when 3 => + -- fails first 2 attempts, then succeeds + if Self.Case_3_Ctr < 2 then + DP_Return.The_Status := Fetch_Status.Not_Available; + else + Dp.Header.Id := 3 + 10 * Data_Product_Id (Self.Case_3_Ctr); + Dp.Header.Buffer_Length := Tick.Serialization.Byte_Array'Length; + Dp.Buffer (Dp.Buffer'First .. Dp.Buffer'First + Dp.Header.Buffer_Length - 1) := Tick.Serialization.To_Byte_Array ((Dp.Header.Time, 14)); + + DP_Return.The_Status := Fetch_Status.Success; + DP_Return.The_Data_Product := Dp; + end if; + Self.Case_3_Ctr := @ + 1; + when 4 => + -- succeeds first 2 attempts, then returns same DP repeatedly + -- always has success status + if Self.Case_4_Ctr < 2 then + Self.Case_4_Ctr := @ + 1; + end if; + + Dp.Header.Id := 4 + 10 * Data_Product_Id (Self.Case_4_Ctr); + Dp.Header.Buffer_Length := Packed_U16.Serialization.Byte_Array'Length; + Dp.Buffer (Dp.Buffer'First .. Dp.Buffer'First + Dp.Header.Buffer_Length - 1) := Packed_U16.Serialization.To_Byte_Array ((Value => 33)); + + DP_Return.The_Status := Fetch_Status.Success; + DP_Return.The_Data_Product := Dp; + when 5 => + -- alternates success and failure + if Self.Case_5_Ctr rem 2 = 0 then + Dp.Header.Id := 5 + 10 * Data_Product_Id (Self.Case_5_Ctr); + Dp.Header.Buffer_Length := Packed_U16.Serialization.Byte_Array'Length; + Dp.Buffer (Dp.Buffer'First .. Dp.Buffer'First + Dp.Header.Buffer_Length - 1) := Packed_U16.Serialization.To_Byte_Array ((Value => 0)); + + DP_Return.The_Status := Fetch_Status.Success; + DP_Return.The_Data_Product := Dp; + else + DP_Return.The_Status := Fetch_Status.Not_Available; + end if; + Self.Case_5_Ctr := @ + 1; + -- adding a 6th branch will not break any tests + when others => + -- pass any other id to get this status + DP_Return.The_Status := Fetch_Status.Id_Out_Of_Range; + end case; + + return DP_Return; + end Data_Product_Fetch_T_Service; + + overriding procedure Event_T_Recv_Sync (Self : in out Instance; Arg : in Event.T) is + begin + -- Push the argument onto the test history for looking at later: + Self.Event_T_Recv_Sync_History.Push (Arg); + -- Dispatch the event to the correct handler: + Self.Dispatch_Event (Arg); + end Event_T_Recv_Sync; + + overriding function Sys_Time_T_Return (Self : in out Instance) return Sys_Time.T is + -- Return the system time: + To_Return : constant Sys_Time.T := Self.System_Time; + begin + -- Push the argument onto the test history for looking at later: + Self.Sys_Time_T_Return_History.Push (To_Return); + return To_Return; + end Sys_Time_T_Return; + + ----------------------------------------------- + -- Event handler primitive: + ----------------------------------------------- + -- A data product fetch resulted in an a Not_Available status, and was not read + -- from the source. + overriding procedure Source_Not_Available (Self : in out Instance; Arg : in Product_Copier_Error_Info.T) is + begin + -- Push the argument onto the test history for looking at later: + Self.Source_Not_Available_History.Push (Arg); + end Source_Not_Available; + + -- A data product fetch resulted in an an Id_Out_Of_Range status, and was not read + -- from the source. + overriding procedure Source_Id_Out_Of_Range (Self : in out Instance; Arg : in Product_Copier_Error_Info.T) is + begin + -- Push the argument onto the test history for looking at later: + Self.Source_Id_Out_Of_Range_History.Push (Arg); + end Source_Id_Out_Of_Range; + +end Component.Product_Copier.Implementation.Tester; diff --git a/src/components/product_copier/tests/component-product_copier-implementation-tester.ads b/src/components/product_copier/tests/component-product_copier-implementation-tester.ads new file mode 100644 index 00000000..b5db167e --- /dev/null +++ b/src/components/product_copier/tests/component-product_copier-implementation-tester.ads @@ -0,0 +1,84 @@ +-------------------------------------------------------------------------------- +-- Product_Copier Component Tester Spec +-------------------------------------------------------------------------------- + +-- Includes: +with Component.Product_Copier_Reciprocal; +with Printable_History; +with Data_Product.Representation; +with Data_Product_Return.Representation; +with Data_Product_Fetch.Representation; +with Event.Representation; +with Sys_Time.Representation; +with Event; +with Product_Copier_Error_Info.Representation; + +-- Given two locations (e.g. a database) and a list of source/destination IDs +-- ("mappings"), fetches Data_Product entries from one location and copies/sends +-- them to another upon receiving a Tick. One potential use case is to take +-- snapshots of a database at fixed intervals, in order to provide a stable view +-- of it to other components despite a high volume of writes. +package Component.Product_Copier.Implementation.Tester is + + use Component.Product_Copier_Reciprocal; + -- Invoker connector history packages: + package Data_Product_T_Recv_Sync_History_Package is new Printable_History (Data_Product.T, Data_Product.Representation.Image); + package Data_Product_Fetch_T_Service_History_Package is new Printable_History (Data_Product_Fetch.T, Data_Product_Fetch.Representation.Image); + package Data_Product_Fetch_T_Service_Return_History_Package is new Printable_History (Data_Product_Return.T, Data_Product_Return.Representation.Image); + package Event_T_Recv_Sync_History_Package is new Printable_History (Event.T, Event.Representation.Image); + package Sys_Time_T_Return_History_Package is new Printable_History (Sys_Time.T, Sys_Time.Representation.Image); + + -- Event history packages: + package Source_Not_Available_History_Package is new Printable_History (Product_Copier_Error_Info.T, Product_Copier_Error_Info.Representation.Image); + package Source_Id_Out_Of_Range_History_Package is new Printable_History (Product_Copier_Error_Info.T, Product_Copier_Error_Info.Representation.Image); + + -- Component class instance: + type Instance is new Component.Product_Copier_Reciprocal.Base_Instance with record + -- The component instance under test: + Component_Instance : aliased Component.Product_Copier.Implementation.Instance; + -- Connector histories: + Data_Product_T_Recv_Sync_History : Data_Product_T_Recv_Sync_History_Package.Instance; + Data_Product_Fetch_T_Service_History : Data_Product_Fetch_T_Service_History_Package.Instance; + Event_T_Recv_Sync_History : Event_T_Recv_Sync_History_Package.Instance; + Sys_Time_T_Return_History : Sys_Time_T_Return_History_Package.Instance; + -- Event histories: + Source_Not_Available_History : Source_Not_Available_History_Package.Instance; + Source_Id_Out_Of_Range_History : Source_Id_Out_Of_Range_History_Package.Instance; + Case_1_Ctr : Integer := 0; + Case_2_Ctr : Integer := 0; + Case_3_Ctr : Integer := 0; + Case_4_Ctr : Integer := 0; + Case_5_Ctr : Integer := 0; + end record; + type Instance_Access is access all Instance; + + --------------------------------------- + -- Initialize component heap variables: + --------------------------------------- + procedure Init_Base (Self : in out Instance); + procedure Final_Base (Self : in out Instance); + + --------------------------------------- + -- Test initialization functions: + --------------------------------------- + procedure Connect (Self : in out Instance); + + --------------------------------------- + -- Invokee connector primitives: + --------------------------------------- + overriding procedure Data_Product_T_Recv_Sync (Self : in out Instance; Arg : in Data_Product.T); + overriding function Data_Product_Fetch_T_Service (Self : in out Instance; Arg : in Data_Product_Fetch.T) return Data_Product_Return.T; + overriding procedure Event_T_Recv_Sync (Self : in out Instance; Arg : in Event.T); + overriding function Sys_Time_T_Return (Self : in out Instance) return Sys_Time.T; + + ----------------------------------------------- + -- Event handler primitive: + ----------------------------------------------- + -- A data product fetch resulted in an a Not_Available status, and was not read + -- from the source. + overriding procedure Source_Not_Available (Self : in out Instance; Arg : in Product_Copier_Error_Info.T); + -- A data product fetch resulted in an an Id_Out_Of_Range status, and was not read + -- from the source. + overriding procedure Source_Id_Out_Of_Range (Self : in out Instance; Arg : in Product_Copier_Error_Info.T); + +end Component.Product_Copier.Implementation.Tester; diff --git a/src/components/product_copier/tests/env.py b/src/components/product_copier/tests/env.py new file mode 100644 index 00000000..8d5248e0 --- /dev/null +++ b/src/components/product_copier/tests/env.py @@ -0,0 +1 @@ +from environments import test # noqa: F401 diff --git a/src/components/product_copier/tests/product_copier.tests.yaml b/src/components/product_copier/tests/product_copier.tests.yaml new file mode 100644 index 00000000..220cf2bf --- /dev/null +++ b/src/components/product_copier/tests/product_copier.tests.yaml @@ -0,0 +1,11 @@ +--- +description: This is a set of unit tests for the Simple_Package package. +tests: + - name: Test_Dest_Conflict + description: Tests whether two conflicting destinations raise an error. + - name: Test_Nominal_Tick + description: Tests the fetch and send operations caused by a tick. + - name: Test_Fetch_Fail_Behavior + description: Tests that no data products are sent to the destination when a fetch fails. + - name: Test_Fetch_Fail_Event + description: Makes sure an event is raised when a fetch operation fails and the corresponding init flag is set. diff --git a/src/components/product_copier/tests/product_copier_tests-implementation.adb b/src/components/product_copier/tests/product_copier_tests-implementation.adb new file mode 100644 index 00000000..8b26b446 --- /dev/null +++ b/src/components/product_copier/tests/product_copier_tests-implementation.adb @@ -0,0 +1,360 @@ +-------------------------------------------------------------------------------- +-- Product_Copier Tests Body +-------------------------------------------------------------------------------- + +with AUnit.Assertions; use AUnit.Assertions; +with Basic_Assertions; use Basic_Assertions; +with Component.Product_Copier; +with Ada.Assertions; +with Interfaces; + +package body Product_Copier_Tests.Implementation is + + ------------------------------------------------------------------------- + -- Fixtures: + ------------------------------------------------------------------------- + use Component.Product_Copier; + + Init_Products : aliased Product_Mapping_Array := [ + (Source_Id => 1, Destination_Id => 1), + (Source_Id => 2, Destination_Id => 2), + (Source_Id => 3, Destination_Id => 3), + (Source_Id => 4, Destination_Id => 4), + (Source_Id => 5, Destination_Id => 5), + (Source_Id => 100, Destination_Id => 6) + ]; + + overriding procedure Set_Up_Test (Self : in out Instance) is + begin + -- Allocate heap memory to component: + Self.Tester.Init_Base; + + -- Make necessary connections between tester and component: + Self.Tester.Connect; + + -- TODO Call component init here. + Self.Tester.Component_Instance.Init (Products_To_Copy => Init_Products'Access); + + -- Call the component set up method that the assembly would normally call. + Self.Tester.Component_Instance.Set_Up; + + -- TODO Insert custom set up code here. + null; + end Set_Up_Test; + + overriding procedure Tear_Down_Test (Self : in out Instance) is + begin + -- TODO Insert custom cleanup code here. + null; + -- Free component heap: + Self.Tester.Final_Base; + end Tear_Down_Test; + + ------------------------------------------------------------------------- + -- Tests: + ------------------------------------------------------------------------- + + No_Conflict_Products_1 : aliased Product_Mapping_Array := []; + No_Conflict_Products_2 : aliased Product_Mapping_Array := [ + (Source_Id => 1, Destination_Id => 1) + ]; + No_Conflict_Products_3 : aliased Product_Mapping_Array := [ + (Source_Id => 2, Destination_Id => 0), + (Source_Id => 0, Destination_Id => 1), + (Source_Id => 0, Destination_Id => 2), + (Source_Id => 3, Destination_Id => 3) + ]; + No_Conflict_Products_4 : aliased Product_Mapping_Array := [ + (Source_Id => 0, Destination_Id => 0), + (Source_Id => 0, Destination_Id => 1), + (Source_Id => 0, Destination_Id => 2), + (Source_Id => 0, Destination_Id => 3), + (Source_Id => 0, Destination_Id => 4) + ]; + + Conflict_Products_1 : aliased Product_Mapping_Array := [ + (Source_Id => 0, Destination_Id => 0), + (Source_Id => 0, Destination_Id => 1), + (Source_Id => 0, Destination_Id => 2), + (Source_Id => 0, Destination_Id => 0) + ]; + Conflict_Products_2 : aliased Product_Mapping_Array := [ + (Source_Id => 1, Destination_Id => 1), + (Source_Id => 3, Destination_Id => 1) + ]; + + -- Tests whether two conflicting destinations raise an error. + overriding procedure Test_Dest_Conflict (Self : in out Instance) is + begin + -- TODO is Init'ing many times / after Set_Up_Test okay? + + -- these should not raise an assertion error + Self.Tester.Component_Instance.Init (Products_To_Copy => No_Conflict_Products_1'Access); + Self.Tester.Component_Instance.Init (Products_To_Copy => No_Conflict_Products_2'Access); + Self.Tester.Component_Instance.Init (Products_To_Copy => No_Conflict_Products_3'Access); + Self.Tester.Component_Instance.Init (Products_To_Copy => No_Conflict_Products_4'Access); + + -- these should raise an assertion error + declare -- TODO named declare? + begin + Self.Tester.Component_Instance.Init (Products_To_Copy => Conflict_Products_1'Access); + -- this should be unreachable + Assert (False, "Conflicting destinations, but did not raise error"); + exception + -- this is what gets raised by `pragma Assert (...)` + when Ada.Assertions.Assertion_Error => + null; + end; + + declare + begin + Self.Tester.Component_Instance.Init (Products_To_Copy => Conflict_Products_2'Access); + -- this should be unreachable + Assert (False, "Conflicting destinations, but did not raise error"); + exception + when Ada.Assertions.Assertion_Error => + null; + end; + end Test_Dest_Conflict; + + Non_Error_Products : aliased Product_Mapping_Array := [(Source_Id => 1, Destination_Id => 1)]; + + -- Tests the fetch and send operations caused by a tick. + overriding procedure Test_Nominal_Tick (Self : in out Instance) is + -- TODO declarations + use Interfaces; + begin + -- see the reciprocal component + -- fetching source id 1 always results in success + Self.Tester.Component_Instance.Init (Products_To_Copy => Non_Error_Products'Access); + + Self.Tester.Tick_T_Send ((Time => (0, 0), Count => 0)); + + -- data_product_fetch_t_request should have been invoked + Natural_Assert.Eq (Self.Tester.Data_Product_Fetch_T_Service_History.Get_Count, 1); + + -- data_product_t_send should have been invoked + Natural_Assert.Eq (Self.Tester.Data_Product_T_Recv_Sync_History.Get_Count, 1); + + -- no events should have been sent + Natural_Assert.Eq (Self.Tester.Source_Not_Available_History.Get_Count, 0); + Natural_Assert.Eq (Self.Tester.Source_Id_Out_Of_Range_History.Get_Count, 0); + + -- send 5 more ticks + for I in 1 .. 5 loop + Self.Tester.Tick_T_Send ((Time => (Unsigned_32 (I), 0), Count => Unsigned_32 (I))); + end loop; + + -- same checks, plus 5 + Natural_Assert.Eq (Self.Tester.Data_Product_Fetch_T_Service_History.Get_Count, 6); + Natural_Assert.Eq (Self.Tester.Data_Product_T_Recv_Sync_History.Get_Count, 6); + Natural_Assert.Eq (Self.Tester.Source_Not_Available_History.Get_Count, 0); + Natural_Assert.Eq (Self.Tester.Source_Id_Out_Of_Range_History.Get_Count, 0); + + -- check contents of successful copies + declare + Previous_Id : Integer := -1; + Int_Id : Integer; + begin + for I in 1 .. Self.Tester.Data_Product_T_Recv_Sync_History.Get_Count loop + Int_Id := Integer ( + Self.Tester.Data_Product_T_Recv_Sync_History.Get (Natural (I)).Header.Id + ); + Assert ( + Int_Id mod 10 = 1, + "Tick copied the incorrect Data_Product at position " & I'Image & ". (Possibly an error in the reciprocal component.)" + ); + -- we care about the order across multiple ticks + Assert ( + Previous_Id < Int_Id, + "Tick copied incorrect data products -- IDs should be in ascending order per the reciprocal tester component." + ); + Previous_Id := Int_Id; + end loop; + end; + + end Test_Nominal_Tick; + + -- do not change numbers/order; see body of Test_Fetch_Fail_Behavior + -- TODO make more descriptive names, i.e. One_Failing_Product + Fetch_Fail_Products_1 : aliased Product_Mapping_Array := [ + (Source_Id => 2, Destination_Id => 2) + ]; + Fetch_Fail_Products_2 : aliased Product_Mapping_Array := [ + (Source_Id => 5, Destination_Id => 5), + (Source_Id => 1, Destination_Id => 1), + (Source_Id => 4, Destination_Id => 4), + (Source_Id => 2, Destination_Id => 2), + (Source_Id => 3, Destination_Id => 3) + ]; + + -- Tests that no data products are sent to the destination when a fetch fails. + overriding procedure Test_Fetch_Fail_Behavior (Self : in out Instance) is + begin + -- see reciprocal component for which values of Source_Id cause errors + Self.Tester.Component_Instance.Init (Products_To_Copy => Fetch_Fail_Products_1'Access); + + -- send tick with bad Source_Id + Self.Tester.Tick_T_Send ((Time => (0, 0), Count => 0)); + -- fetch, but no send + Natural_Assert.Eq (Self.Tester.Data_Product_Fetch_T_Service_History.Get_Count, 1); + Natural_Assert.Eq (Self.Tester.Data_Product_T_Recv_Sync_History.Get_Count, 0); + + Self.Tester.Tick_T_Send ((Time => (1, 0), Count => 1)); + Self.Tester.Tick_T_Send ((Time => (2, 0), Count => 2)); + Self.Tester.Tick_T_Send ((Time => (3, 0), Count => 3)); + Self.Tester.Tick_T_Send ((Time => (4, 0), Count => 4)); + + Natural_Assert.Eq (Self.Tester.Data_Product_Fetch_T_Service_History.Get_Count, 5); + Natural_Assert.Eq (Self.Tester.Data_Product_T_Recv_Sync_History.Get_Count, 0); + + -- should work with variety of ids + -- note new init + Self.Tester.Component_Instance.Init (Products_To_Copy => Fetch_Fail_Products_2'Access); + Self.Tester.Data_Product_T_Recv_Sync_History.Clear; + + Self.Tester.Tick_T_Send ((Time => (5, 0), Count => 5)); -- S,F,F,S,S + + Natural_Assert.Eq (Self.Tester.Data_Product_Fetch_T_Service_History.Get_Count, 10); + Natural_Assert.Eq (Self.Tester.Data_Product_T_Recv_Sync_History.Get_Count, 3); + + Self.Tester.Tick_T_Send ((Time => (6, 0), Count => 6)); -- S,F,F,S,F + + Natural_Assert.Eq (Self.Tester.Data_Product_Fetch_T_Service_History.Get_Count, 15); + Natural_Assert.Eq (Self.Tester.Data_Product_T_Recv_Sync_History.Get_Count, 5); + + Self.Tester.Tick_T_Send ((Time => (7, 0), Count => 7)); -- S,F,S,S,S + + Natural_Assert.Eq (Self.Tester.Data_Product_Fetch_T_Service_History.Get_Count, 20); + Natural_Assert.Eq (Self.Tester.Data_Product_T_Recv_Sync_History.Get_Count, 9); + + Self.Tester.Tick_T_Send ((Time => (8, 0), Count => 8)); -- S,F,S,S,F + + Natural_Assert.Eq (Self.Tester.Data_Product_Fetch_T_Service_History.Get_Count, 25); + Natural_Assert.Eq (Self.Tester.Data_Product_T_Recv_Sync_History.Get_Count, 12); + + -- test that the 12 products are: 5 1 4 1 4 5 1 4 3 1 4 3 + -- TODO do we care about the order within ticks? + Check_Product_Ids : declare + Counts : array (1 .. 5) of Natural := (others => 0); + Id : Natural; + Mod_Id : Natural; + begin + for I in 1 .. Self.Tester.Data_Product_T_Recv_Sync_History.Get_Count loop + Id := Natural ( + Self.Tester.Data_Product_T_Recv_Sync_History.Get (Natural (I)).Header.Id + ); + Mod_Id := Id mod 10; + -- Ada.Text_IO.Put_Line (Mod_Id'Image); + -- TODO convert Count_X's to array, match Mod_Id with 1-5 and increment counts + case Mod_Id is + when Counts'Range => + Counts (Mod_Id) := @ + 1; + when others => + Assert (False, "Unexpected ID found"); + end case; + end loop; + Natural_Assert.Eq (Counts (1), 4); + Natural_Assert.Eq (Counts (2), 0); + Natural_Assert.Eq (Counts (3), 2); + Natural_Assert.Eq (Counts (4), 4); + Natural_Assert.Eq (Counts (5), 2); + end Check_Product_Ids; + + end Test_Fetch_Fail_Behavior; + + -- these three values of Source_Id will result in success, not available, and ID out of range respectively + Every_Error_Products : aliased Product_Mapping_Array := [ + (Source_Id => 1, Destination_Id => 1), + (Source_Id => 2, Destination_Id => 2), + (Source_Id => 99, Destination_Id => 3) + ]; + Out_Of_Range_Products : aliased Product_Mapping_Array := [ + (Source_Id => 1, Destination_Id => 1), + (Source_Id => 99, Destination_Id => 3) + ]; + Not_Available_Products : aliased Product_Mapping_Array := [ + (Source_Id => 1, Destination_Id => 1), + (Source_Id => 2, Destination_Id => 2) + ]; + Okay_Products : aliased Product_Mapping_Array := [ + (Source_Id => 1, Destination_Id => 1) + ]; + -- Makes sure an event is raised when a fetch operation fails and the + -- corresponding init flag is set. + overriding procedure Test_Fetch_Fail_Event (Self : in out Instance) is + begin + -- all events turned off + Self.Tester.Component_Instance.Init ( + Products_To_Copy => Every_Error_Products'Access, + Send_Event_On_Source_Id_Out_Of_Range => False, + Send_Event_On_Source_Not_Available => False + ); + Self.Tester.Tick_T_Send ((Time => (0, 0), Count => 0)); + Natural_Assert.Eq (Self.Tester.Event_T_Recv_Sync_History.Get_Count, 0); + + -- relevant events turned off + Self.Tester.Component_Instance.Init ( + Products_To_Copy => Not_Available_Products'Access, + Send_Event_On_Source_Id_Out_Of_Range => True, + Send_Event_On_Source_Not_Available => False + ); + Self.Tester.Tick_T_Send ((Time => (0, 0), Count => 0)); + Natural_Assert.Eq (Self.Tester.Event_T_Recv_Sync_History.Get_Count, 0); + + Self.Tester.Component_Instance.Init ( + Products_To_Copy => Out_Of_Range_Products'Access, + Send_Event_On_Source_Id_Out_Of_Range => False, + Send_Event_On_Source_Not_Available => True + ); + Self.Tester.Tick_T_Send ((Time => (0, 0), Count => 0)); + Natural_Assert.Eq (Self.Tester.Event_T_Recv_Sync_History.Get_Count, 0); + + -- events turned on, but no event should raise + Self.Tester.Component_Instance.Init ( + Products_To_Copy => Okay_Products'Access, + Send_Event_On_Source_Id_Out_Of_Range => True, + Send_Event_On_Source_Not_Available => True + ); + Self.Tester.Tick_T_Send ((Time => (0, 0), Count => 0)); + Natural_Assert.Eq (Self.Tester.Event_T_Recv_Sync_History.Get_Count, 0); + + -- now check correct events are raised + + -- check not_available events + Self.Tester.Component_Instance.Init ( + Products_To_Copy => Not_Available_Products'Access, + Send_Event_On_Source_Id_Out_Of_Range => True, + Send_Event_On_Source_Not_Available => True + ); + Self.Tester.Tick_T_Send ((Time => (0, 0), Count => 0)); + Self.Tester.Tick_T_Send ((Time => (1, 0), Count => 1)); + Self.Tester.Tick_T_Send ((Time => (2, 0), Count => 2)); + + Natural_Assert.Eq (Self.Tester.Source_Not_Available_History.Get_Count, 3); + Natural_Assert.Eq (Self.Tester.Source_Id_Out_Of_Range_History.Get_Count, 0); + + -- TODO also check specific contents of event? + + -- clear history + Self.Tester.Source_Not_Available_History.Clear; + + -- check id_out_of_range events + Self.Tester.Component_Instance.Init ( + Products_To_Copy => Out_Of_Range_Products'Access, + Send_Event_On_Source_Id_Out_Of_Range => True, + Send_Event_On_Source_Not_Available => True + ); + Self.Tester.Tick_T_Send ((Time => (0, 0), Count => 0)); + Self.Tester.Tick_T_Send ((Time => (1, 0), Count => 1)); + Self.Tester.Tick_T_Send ((Time => (2, 0), Count => 2)); + + Natural_Assert.Eq (Self.Tester.Source_Not_Available_History.Get_Count, 0); + Natural_Assert.Eq (Self.Tester.Source_Id_Out_Of_Range_History.Get_Count, 3); + + -- clear + Self.Tester.Source_Id_Out_Of_Range_History.Clear; + + end Test_Fetch_Fail_Event; + +end Product_Copier_Tests.Implementation; diff --git a/src/components/product_copier/tests/product_copier_tests-implementation.ads b/src/components/product_copier/tests/product_copier_tests-implementation.ads new file mode 100644 index 00000000..dabbc2bc --- /dev/null +++ b/src/components/product_copier/tests/product_copier_tests-implementation.ads @@ -0,0 +1,31 @@ +-------------------------------------------------------------------------------- +-- Product_Copier Tests Spec +-------------------------------------------------------------------------------- + +-- This is a set of unit tests for the Simple_Package package. +package Product_Copier_Tests.Implementation is + + -- Test data and state: + type Instance is new Product_Copier_Tests.Base_Instance with private; + type Class_Access is access all Instance'Class; + +private + -- Fixture procedures: + overriding procedure Set_Up_Test (Self : in out Instance); + overriding procedure Tear_Down_Test (Self : in out Instance); + + -- Tests whether two conflicting destinations raise an error. + overriding procedure Test_Dest_Conflict (Self : in out Instance); + -- Tests the fetch and send operations caused by a tick. + overriding procedure Test_Nominal_Tick (Self : in out Instance); + -- Tests that no data products are sent to the destination when a fetch fails. + overriding procedure Test_Fetch_Fail_Behavior (Self : in out Instance); + -- Makes sure an event is raised when a fetch operation fails and the + -- corresponding init flag is set. + overriding procedure Test_Fetch_Fail_Event (Self : in out Instance); + + -- Test data and state: + type Instance is new Product_Copier_Tests.Base_Instance with record + null; + end record; +end Product_Copier_Tests.Implementation; diff --git a/src/components/product_copier/tests/test.adb b/src/components/product_copier/tests/test.adb new file mode 100644 index 00000000..bf8fc1c8 --- /dev/null +++ b/src/components/product_copier/tests/test.adb @@ -0,0 +1,23 @@ +-------------------------------------------------------------------------------- +-- Product_Copier Tests +-------------------------------------------------------------------------------- + +with AUnit.Reporter.Text; +with AUnit.Run; +with Product_Copier_Tests.Implementation.Suite; +-- Make sure any terminating tasks are handled and an appropriate +-- error message is printed. +with Unit_Test_Termination_Handler; +pragma Unreferenced (Unit_Test_Termination_Handler); + +procedure Test is + -- Create runner for test suite: + procedure Runner is new AUnit.Run.Test_Runner (Product_Copier_Tests.Implementation.Suite.Get); + -- Use the text reporter: + Reporter : AUnit.Reporter.Text.Text_Reporter; +begin + -- Add color output to test run: + AUnit.Reporter.Text.Set_Use_ANSI_Colors (Reporter, True); + -- Run tests: + Runner (Reporter); +end Test;