Skip to content

Commit

Permalink
Merge pull request #331 from lf-lang/rti-unit-tests
Browse files Browse the repository at this point in the history
Unit tests for the RTI and fix of bug revealed by test
  • Loading branch information
byeonggiljun authored Feb 12, 2024
2 parents 26eb346 + a4389bc commit 2ad20f7
Show file tree
Hide file tree
Showing 4 changed files with 318 additions and 3 deletions.
10 changes: 8 additions & 2 deletions core/federated/RTI/rti_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,14 @@ static void _update_min_delays_upstream(
// but for most programs, the gain might be negligible since there are relatively few
// upstream nodes.
for (int i = 0; i < intermediate->num_upstream; i++) {
// Add connection delay to path delay so far.
tag_t path_delay = lf_delay_tag(delay_from_intermediate_so_far, intermediate->upstream_delay[i]);
// Add connection delay to path delay so far. Because tag addition is not commutative,
// the calculation order should be carefully handled. Specifically, we should calculate
// intermediate->upstream_delay[i] + delay_from_intermediate_so_far,
// NOT delay_from_intermediate_so_far + intermediate->upstream_delay[i].
// Before calculating path delay, convert intermediate->upstream_delay[i] to a tag
// cause there is no function that adds a tag to an interval.
tag_t connection_delay = lf_delay_tag(ZERO_TAG, intermediate->upstream_delay[i]);
tag_t path_delay = lf_tag_add(connection_delay, delay_from_intermediate_so_far);
// If the path delay is less than the so-far recorded path delay from upstream, update upstream.
if (lf_tag_compare(path_delay, path_delays[intermediate->upstream[i]]) < 0) {
if (path_delays[intermediate->upstream[i]].time == FOREVER) {
Expand Down
2 changes: 1 addition & 1 deletion lingua-franca-ref.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
clock-realtime
master
257 changes: 257 additions & 0 deletions test/RTI/rti_common_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
#if defined STANDALONE_RTI
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "rti_remote.h"
#include "rti_common.h"
#include "tag.h"

// The RTI under test.
static rti_common_t test_rti;

/******************************************Start of Utility Functions******************************************************/

/**
* Free dynamically allocated memory on the scheduling node.
* @param node The node to be freed
*/
void delete_scheduling_node(scheduling_node_t* node) {
if (node->upstream != NULL) {
free(node->upstream);
}
if (node->upstream_delay != NULL) {
free(node->upstream_delay);
}
if (node->downstream != NULL) {
free(node->downstream);
}
invalidate_min_delays_upstream(node);
}

/**
* Set the parameters of scheduling node to construct the test case.
* Before calling this function, reset_common_RTI should be called to
* reset every scheduling nodes.
* @param id The ID of the scheduling node.
* @param num_upstream The number of upstreams of the scheduling node.
* @param num_downstream The number of downstreams of the scheduling node.
* @param upstream The array of IDs from upstream nodes.
* @param upstream_delay The array of delays from upstream nodes.
* @param downstream The array of IDs from downstream nodes.
*/
void set_scheduling_node(
int id,
int num_upstream,
int num_downstream,
int* upstream,
interval_t* upstream_delay,
int* downstream) {
// Save the number of upstream and downstream nodes.
test_rti.scheduling_nodes[id]->num_upstream = num_upstream;
test_rti.scheduling_nodes[id]->num_downstream = num_downstream;

// If there is any upstream nodes, store IDs and delays from the upstream nodes into the structure.
if (test_rti.scheduling_nodes[id]->num_upstream > 0) {
test_rti.scheduling_nodes[id]->upstream = (int*) calloc(test_rti.scheduling_nodes[id]->num_upstream, sizeof(int));
test_rti.scheduling_nodes[id]->upstream_delay = (interval_t*) calloc(test_rti.scheduling_nodes[id]->num_upstream, sizeof(interval_t));
for (int i = 0; i < test_rti.scheduling_nodes[id]->num_upstream; i++) {
test_rti.scheduling_nodes[id]->upstream[i] = upstream[i];
test_rti.scheduling_nodes[id]->upstream_delay[i] = upstream_delay[i];
}
}
// If there is any downstream nodes, store IDs of the downstream nodes into the structure.
if (test_rti.scheduling_nodes[id]->num_downstream > 0) {
test_rti.scheduling_nodes[id]->downstream = (int*) calloc(test_rti.scheduling_nodes[id]->num_downstream, sizeof(int));
for (int i = 0; i < test_rti.scheduling_nodes[id]->num_downstream; i++) {
test_rti.scheduling_nodes[id]->downstream[i] = downstream[i];
}
}
}

/**
* Reset the RTI to re-construct the structure of nodes.
* This includes freeing every scheduling node and the array of nodes.
*/
void reset_common_RTI() {
// For every scheduling nodes, delete them and free themselves, too.
for (uint16_t i = 0; i < test_rti.number_of_scheduling_nodes; i++) {
delete_scheduling_node(test_rti.scheduling_nodes[i]);
free(test_rti.scheduling_nodes[i]);
}
// Free the array of scheduling nodes either. This will be re-created
// in set_common_RTI().
if (test_rti.scheduling_nodes != NULL) {
free(test_rti.scheduling_nodes);
}
}

/**
* Set the number of nodes and create an array for scheduling nodes.
* This includes resetting the previous RTI.
* @param num_nodes The number of scheduling nodes.
*/
void set_common_RTI(uint16_t num_nodes) {
reset_common_RTI();

test_rti.number_of_scheduling_nodes = num_nodes;

// Allocate memory for the scheduling nodes
test_rti.scheduling_nodes = (scheduling_node_t**)calloc(test_rti.number_of_scheduling_nodes, sizeof(scheduling_node_t*));
for (uint16_t i = 0; i < test_rti.number_of_scheduling_nodes; i++) {
scheduling_node_t *scheduling_node = (scheduling_node_t *) malloc(sizeof(scheduling_node_t));
initialize_scheduling_node(scheduling_node, i);
test_rti.scheduling_nodes[i] = scheduling_node;
}
}

/**
* Set the state of every scheduling node. The state can be NOT_CONNECTED, GRANTED,
* or PENDING.
* @param state The state that every scheduling node will have.
*/
void set_state_of_nodes(scheduling_node_state_t state) {
for (uint16_t i = 0; i < test_rti.number_of_scheduling_nodes; i++) {
test_rti.scheduling_nodes[i]->state = state;
}
}
/******************************************End of Utility Functions******************************************************/

void valid_cache() {
set_common_RTI(2);

// Construct the structure illustrated below.
// node[0] --> node[1]
set_scheduling_node(0, 0, 1, NULL, NULL, (int[]) {1});
set_scheduling_node(1, 1, 0, (int[]) {0}, (interval_t[]) {NEVER}, NULL);

set_state_of_nodes(GRANTED);

// If min_delays is not null (the cached data is valid), nothing should be changed.
test_rti.scheduling_nodes[1]->num_min_delays = 1;
test_rti.scheduling_nodes[1]->min_delays = (minimum_delay_t*)calloc(1, sizeof(minimum_delay_t));
update_min_delays_upstream(test_rti.scheduling_nodes[1]);
assert(test_rti.scheduling_nodes[1]->num_min_delays == 1);
}

void not_connected() {
set_common_RTI(2);

// Construct the structure illustrated below.
// node[0] --> node[1]
set_scheduling_node(0, 0, 1, NULL, NULL, (int[]) {1});
set_scheduling_node(1, 1, 0, (int[]) {0}, (interval_t[]) {NEVER}, NULL);

set_state_of_nodes(NOT_CONNECTED);

// If the nodes are not connected, num_min_delays should not be changed.
update_min_delays_upstream(test_rti.scheduling_nodes[1]);
assert(test_rti.scheduling_nodes[1]->num_min_delays == 0);
}

static void two_nodes_no_delay() {
set_common_RTI(2);

// Construct the structure illustrated below.
// node[0] --> node[1]
set_scheduling_node(0, 0, 1, NULL, NULL, (int[]) {1});
set_scheduling_node(1, 1, 0, (int[]) {0}, (interval_t[]) {NEVER}, NULL);

set_state_of_nodes(GRANTED);

update_min_delays_upstream(test_rti.scheduling_nodes[0]);
assert(test_rti.scheduling_nodes[1]->num_min_delays == 0); // node[0] has no upstream nodes.

update_min_delays_upstream(test_rti.scheduling_nodes[1]);
assert(test_rti.scheduling_nodes[1]->num_min_delays == 1); // node[1] has one upstream nodes.
assert(test_rti.scheduling_nodes[1]->min_delays[0].id == 0); // node[1]'s upstream node is node[0].
// The min_delay between them is node[0] and node[1] which means no delay.
assert(lf_tag_compare(test_rti.scheduling_nodes[1]->min_delays[0].min_delay, ZERO_TAG) == 0);
}

static void two_nodes_zero_delay() {
set_common_RTI(2);

// Construct the structure illustrated below.
// node[0] --/0/--> node[1]
set_scheduling_node(0, 0, 1, NULL, NULL, (int[]) {1});
set_scheduling_node(1, 1, 0, (int[]) {0}, (interval_t[]) {0}, NULL);

set_state_of_nodes(GRANTED);

update_min_delays_upstream(test_rti.scheduling_nodes[0]);
assert(test_rti.scheduling_nodes[1]->num_min_delays == 0); // node[0] has no upstream nodes.

update_min_delays_upstream(test_rti.scheduling_nodes[1]);
assert(test_rti.scheduling_nodes[1]->num_min_delays == 1); // node[1] has one upstream nodes.
assert(test_rti.scheduling_nodes[1]->min_delays[0].id == 0); // node[1]'s upstream node is node[0].
// The min_delay between node[0] and node[1] is (0, 1) which means zero delay.
assert(lf_tag_compare(test_rti.scheduling_nodes[1]->min_delays[0].min_delay, (tag_t) {.time = 0, .microstep = 1}) == 0);
}

static void two_nodes_normal_delay() {
set_common_RTI(2);

// Construct the structure illustrated below.
// node[0] --/1 nsec/--> node[1]
set_scheduling_node(0, 0, 1, NULL, NULL, (int[]) {1});
set_scheduling_node(1, 1, 0, (int[]) {0}, (interval_t[]) {NSEC(1)}, NULL);

set_state_of_nodes(GRANTED);

update_min_delays_upstream(test_rti.scheduling_nodes[0]);
assert(test_rti.scheduling_nodes[1]->num_min_delays == 0); // node[0] has no upstream nodes.

update_min_delays_upstream(test_rti.scheduling_nodes[1]);
assert(test_rti.scheduling_nodes[1]->num_min_delays == 1); // node[1] has one upstream nodes.
assert(test_rti.scheduling_nodes[1]->min_delays[0].id == 0); // node[1]'s upstream node is node[0].
// The min_delay between node[0] and node[1] is (1 nsec, 0).
assert(lf_tag_compare(test_rti.scheduling_nodes[1]->min_delays[0].min_delay, (tag_t) {.time = NSEC(1), .microstep = 0}) == 0);
}

static void multiple_nodes() {
set_common_RTI(4);

// Construct the structure illustrated below.
// node[0] --/1 nsec/--> node[1] --/0/--> node[2] --/2 nsec/--> node[3]
set_scheduling_node(0, 0, 1, NULL, NULL, (int[]) {1});
set_scheduling_node(1, 1, 1, (int[]) {0}, (interval_t[]) {NSEC(1)}, (int[]) {2});
set_scheduling_node(2, 1, 1, (int[]) {1}, (interval_t[]) {0}, (int[]) {3});
set_scheduling_node(3, 1, 0, (int[]) {2}, (interval_t[]) {NSEC(2)}, NULL);

set_state_of_nodes(GRANTED);

update_min_delays_upstream(test_rti.scheduling_nodes[2]);
assert(test_rti.scheduling_nodes[2]->num_min_delays == 2); // node[2] has two upstream nodes.
assert(test_rti.scheduling_nodes[2]->min_delays[1].id == 1); // node[2]'s first upstream node is node[1].
// The min_delay between node[1] and node[2] is (0, 1), which denotes zero delay.
assert(lf_tag_compare(test_rti.scheduling_nodes[2]->min_delays[1].min_delay, (tag_t) {0, 1}) == 0);
assert(test_rti.scheduling_nodes[2]->min_delays[0].id == 0); // node[2]'s second upstream node is node[0].
// The min_delay between node[0] and node[2] is (1 nsec, 1) = 1 nsec + zero delay.
assert(lf_tag_compare(test_rti.scheduling_nodes[2]->min_delays[0].min_delay, (tag_t) {NSEC(1), 1}) == 0);

update_min_delays_upstream(test_rti.scheduling_nodes[3]);
assert(test_rti.scheduling_nodes[3]->num_min_delays == 3); // node[3] has three upstream nodes.
assert(test_rti.scheduling_nodes[3]->min_delays[2].id == 2); // node[3]'s first upstream node is node [2].
// The min_delay between node[2] and node[3] is (2 nsec, 0).
assert(lf_tag_compare(test_rti.scheduling_nodes[3]->min_delays[2].min_delay, (tag_t) {NSEC(2), 0}) == 0);
assert(test_rti.scheduling_nodes[3]->min_delays[1].id == 1); // node[3]'s second upstream node is node [1].
// The min_delay between node[1] and node[3] is (2 nsec, 0) = zero_delay + 2 nsec.
assert(lf_tag_compare(test_rti.scheduling_nodes[3]->min_delays[1].min_delay, (tag_t) {NSEC(2), 0}) == 0);
assert(test_rti.scheduling_nodes[3]->min_delays[0].id == 0); // node[3]'s third upstream node is node [0].
// The min_delay between node[0] and node[3] is (3 nsec, 0) = 1 nsec + zero_delay + 2 nsec.
assert(lf_tag_compare(test_rti.scheduling_nodes[3]->min_delays[0].min_delay, (tag_t) {NSEC(3), 0}) == 0);
}

int main(int argc, char **argv) {
initialize_rti_common(&test_rti);

// Tests for the function update_min_delays_upstream()
valid_cache();
not_connected();
two_nodes_no_delay();
two_nodes_zero_delay();
two_nodes_normal_delay();
multiple_nodes();
}
#endif
52 changes: 52 additions & 0 deletions test/Tests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,55 @@ foreach(FILE ${TEST_FILES})
)
target_include_directories(${NAME} PRIVATE ${TEST_DIR})
endforeach(FILE ${TEST_FILES})

# Add the test for the RTI.
if (NOT DEFINED LF_SINGLE_THREADED)
# Check which system we are running on to select the correct platform support
# file and assign the file's path to LF_PLATFORM_FILE
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(LF_PLATFORM_FILE ${CoreLibPath}/platform/lf_linux_support.c)
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
set(LF_PLATFORM_FILE ${CoreLibPath}/platform/lf_macos_support.c)
else()
message(FATAL_ERROR "Your platform is not supported! RTI supports Linux and MacOS.")
endif()

set(IncludeDir include/core)

set(RTI_DIR ${CoreLibPath}/federated/RTI)
add_executable(
rti_common_test
${TEST_DIR}/RTI/rti_common_test.c
${RTI_DIR}/rti_common.c
${RTI_DIR}/rti_remote.c
${CoreLibPath}/trace.c
${LF_PLATFORM_FILE}
${CoreLibPath}/platform/lf_atomic_gcc_clang.c
${CoreLibPath}/platform/lf_unix_clock_support.c
${CoreLibPath}/utils/util.c
${CoreLibPath}/tag.c
${CoreLibPath}/clock.c
${CoreLibPath}/federated/network/net_util.c
${CoreLibPath}/utils/pqueue_base.c
${CoreLibPath}/utils/pqueue_tag.c
${CoreLibPath}/utils/pqueue.c
)
add_test(NAME rti_common_test COMMAND rti_common_test)
target_include_directories(rti_common_test PUBLIC ${RTI_DIR})
target_include_directories(rti_common_test PUBLIC ${IncludeDir})
target_include_directories(rti_common_test PUBLIC ${IncludeDir}/federated)
target_include_directories(rti_common_test PUBLIC ${IncludeDir}/modal_models)
target_include_directories(rti_common_test PUBLIC ${IncludeDir}/platform)
target_include_directories(rti_common_test PUBLIC ${IncludeDir}/utils)
# Set the STANDALONE_RTI flag to include the rti_remote and rti_common.
target_compile_definitions(rti_common_test PUBLIC STANDALONE_RTI=1)

# Set FEDERATED to get federated compilation support
target_compile_definitions(rti_common_test PUBLIC FEDERATED=1)

target_compile_definitions(rti_common_test PUBLIC PLATFORM_${CMAKE_SYSTEM_NAME})

# Find threads and link to it
find_package(Threads REQUIRED)
target_link_libraries(rti_common_test Threads::Threads)
endif()

0 comments on commit 2ad20f7

Please sign in to comment.