blob: c84200d2c73d78e62b1f4fdeab8aaa09faff2d2c [file] [log] [blame]
= Automated Test Messaging Overview
An OTE Message is simply a logical encapsulation of a data buffer. These Message objects can be used to transmit
data over ethernet to a separate device or sent between services running within the same JVM. Wherever you
want to send a packet of information, you can use the OTE Messaging system to do so.
Messages are more than just a data buffer with a name. They can also define the structure of the data
by breaking up the buffer into logical Elements. These elements are objects within the message class and include
the type and location of the logical field. The elements themselves provide many useful APIs and are the best way
to read and write the data within a message.
All messages also have a header part. This header part contains additional information about the message that is
unique to the message type. For instance you may have a Publish/Subscribe message whose header contains the
destination ID, the message name, and the message body size. Each type of message has its own unique set of one or
more message headers depending on the application.
== Accessing Message Writers
To use a message in an OTE test, you must use the `IMessageManager` service to create a `IMessageRequestor` associated with the test
class itself. Normally, this API should be provided in the hierarchy of your test script to ease coding. Here is an example:
public MyTestScript(MessageSystemTestEnvironment testEnvironment, ITestEnvironmentCommandCallback callback) {
this.messageRequestor = testEnvironment.getMsgManager().createMessageRequestor(getClass().getName());
}
protected <CLASSTYPE extends Message> CLASSTYPE getMessageWriter(Class<CLASSTYPE> type) {
return messageRequestor.getMessageWriter(type);
}
With this implemented, you would then obtain your message object like this:
SOME_MESSAGE messageWriter = getMessageWriter(SOME_MESSAGE.class);
The message manager will handle registering your message with DDS and attaching it to an IO writer class to ensure it gets sent to
proper destination.
NOTE: As soon as you get the message writer, the environment will use the default message definition to begin sending the data. For
instance, if your message defines itself as scheduled by default with a certain rate then the environment will immediately start
sending at that rate. See the next section regarding controlling this behavior.
=== Controlling Message Transmissions
Each message will define its own default periodicity however the test can control that behavior. The following API are provided:
[options="header",cols="1,2,2"]
|===
|Method |Description |Example
//----------------------
|send() |Schedules the message to be sent as soon as possible.
The message type determines how fast that is. For instance an ethernet type may
be transmitted immediately where a Mux message may only be sent on the next frame. | messageWriter.send();
|changeRate(double) |changes the send rate to hz value provided. If the message is not currently
scheduled, nothing will be sent until schedule() is called. |messageWriter.changeRate(12.5);
|schedule() |Starts sending the message if it has a rate. If the message has no rate, nothing
will be sent until `changeRate` or `send` is called. |messageWriter.schedule();
|unschedule() |Stops the periodic sending of a message. Does not prevent transmissions via the
send() method. |messageWriter.unschedule();
|waitForValue(value, timeout) |Waits up to the allotted timeout for the value to hit. Returns the value
or the last value seen if timeout is reached. Useful for synchronizing
with a certain event before allowing the test to continue. |messageReader.SOME_INT_ELEMENT.waitForValue(10, 1000);
|===
=== Changing Element Values
Once you have a message writer object, you can start manipulating the data. The easiest way to do this is via the
elements within the message object. Here's an example:
messageWriter.SOME_INT_ELEMENT.set(this, 42); // can also use setNoLog if you don't want to log the call
The `set()` method is the primary way you will change a message's data buffer. The element determines how to convert the logical value argument into
the raw bit value and exactly where those bits will go in the overall message buffer. In this case there isn't much to convert as it is already
an integer however there are many element types available to fulfill your needs including enumerations, floats, fixed-point, boolean, String, etc.
It's important to note that these elements are strongly typed and will not allow you to set a logical value that doesn't match expected type. For instance you
couldn't pass in a String value to the set() above.
== Accessing Message Readers
Similar to message writers, message readers are obtained from the `IMessageRequestor.getMessageReader` method like this:
SOME_MESSAGE messageReader = messageRequestor.getMessageReader(type)
The message manager will then handle registering the reader with DDS and attach the appropriate IO reader to ensure the test object
gets filled in with the latest incoming transmissions.
=== Reading and Testing Element Values
When trying to read values from the unit under test (or any other test environment actor) the message elements provide the majority of the API you would
need to use. Here are some examples:
[options="header",cols="1,2,3"]
|===
|Method |Description |Example
//----------------------
|get() |pull the logical element value out of the latest buffer |int latestValue = messageReader.SOME_INT_ELEMENT.get();
|check(logger, value) |log a test point if the current value matches the 2nd argument |messageReader.SOME_INT_ELEMENT.check(this, 10);
|check(logger, value, timeout) |Log a test point if the current value matches the 2nd argument
at any point in the next timeout ms |messageReader.SOME_INT_ELEMENT.check(this, 10, 1000);
|waitForValue(value, timeout) |Waits up to the alotted timeout for the value to hit. Returns
the value or the last value seen if timeout is reached. Useful
for synchronizing with a certain event before allowing the test
to continue. |messageReader.SOME_INT_ELEMENT.waitForValue(10, 1000);
|===
There are many other variants of these methods available (like checking ranges, pulses, etc) to fulfill your element testing needs.
Much like the writer elements, the reader elements also deal primarily with logical values. Any value you use or check with the above
API will convert the raw bit patterns for that element into the appropriate logical type. This makes conceptual testing much
easier and keeps the test developer focused on the real purpose of a test and not on the underlying message structure.
== Message Mapping
One of the main benefits of using OTE tests are write once, run anywhere. This is true for
OTE messages as well. The intent is that any test that tries to manipulate a message must work
no matter where the test is run.
To map a message is to correlate two (or more) message classes together as associated types.
At runtime, the test environment will choose the correct signal to send based on rules you define.
For example, say you want to cause a light to blink in a cockpit by sending a "BLINK" signal and your
Unit Under Test (UUT) accepts communication over a 1553 Mux serial bus or via an internal Ethernet network.
You define two messages representing these two mediums for manipulating the cockpit light:
[options="header",cols="3,3,3,1,1,1,5"]
|===
|Message Name |Message Type |Element Name |Byte |MSB | LSB | Type
//-------------------------------------------------
|COCKPIT_ETH |Ethernet |WARNING_LIGHT |0 |1 | 2 |Enumeration (0 = OFF, 1 = ON, 2 = BLINK, 3 = SPARE)
|COCKPIT_MUX |1553 Mux |WARNING_LIGHT |2 |0 | 1 |Enumeration (0 = OFF, 1 = ON, 2 = BLINK, 3 = SPARE)
|===
In this case you could be running the test on a simulated environment at your desk where only Ethernet is
available or against some target flight computer in a hardware lab where both are present. Your test
philosophy is to always prefer the "most external" physical type available for the environment that the test is run.
Without mapping you would have to add logic to your test to use the Mux message object when running in the lab
or to use the Ethernet message when running at your desk.
With mapping you can, instead, create a logical message that conveys cockpit manipulation of any kind. You would then declare
that this message associates with the COCKPIT_ETH and the COCKPIT_MUX messages. Here is the important method within this
new COCKPIT_CONTROLS logical message:
@Override
public Map<DataType, Class<? extends Message>[]> getAssociatedMessages() {
Map<DataType, Class<? extends Message>[]> o = new LinkedHashMap<DataType, Class<? extends Message>[]>();
o.put(GenericOteIoType.Ethernet, new Class[]{COCKPIT_ETH.class});
o.put(GenericOteIoType.MUX, new Class[]{COCKPIT_MUX.class});
return o;
}
Your test would then only use this one COCKPIT_CONTROLS message to manipulate the light. At runtime, the messaging environment
will use the above method to determine mapping messages are available and will begin matching the elements of the source
message (COCKPIT_CONTROLS) to that of the most external associated message (ie. COCKPIT_MUX if in a lab). The test does
not have to care about the backing communication layer and instead focuses on the logical task of causing the light to blink at
the correct time.
NOTE: By default, elements only match if their names are exactly the same but this matching algorithm may be overridden to
fit any application needs.