blob: c5e4545fd05c0b29cd9e888f4ba121ff1d79362c [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <CUnit/CUnit.h>
#include <guacamole/fifo.h>
#include <guacamole/timestamp.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
/**
* The maximum number of milliseconds to wait for a test event to be added to a
* fifo.
*/
#define TEST_TIMEOUT 250
/**
* The maximum number of items permitted in test_fifo.
*/
#define TEST_FIFO_MAX_ITEMS 4
/**
* The rough amount of time to wait between fifo reads within the test thread,
* in milliseconds. A random delay between 0ms and this value will be added
* before each read. This is done to verify that the fifo behaves correctly
* for cases where the sending thread is producing data much faster than it's
* being read, slower than it's read, etc.
*/
#define TEST_READ_INTERVAL 10
/**
* Zero-terminated set of arbitrarily-chosen values that will be provided as
* the test_value of a sequence of test_events.
*/
unsigned int TEST_VALUES[] = {
32, 32, 226, 136, 167, 44, 44, 44,
226, 136, 167, 32, 32, 32, 32, 32,
65, 112, 97, 119, 99, 104, 101, 10,
32, 40, 226, 128, 162, 32, 226, 169,
138, 32, 226, 128, 162, 41, 32, 32,
71, 117, 97, 99, 97, 109, 101, 111,
119, 108, 101, 33, 10, /* END */ 0
};
/**
* Test event for an event fifo. This particular event contains a single
* integer for verifying that events are received in the order expected, and a
* chunk of arbitrary padding to ensure the base fifo is capable of supporting
* events of arbitrary size.
*/
typedef union test_event {
/**
* Arbitrary integer test value. This value is primarily intended to allow
* unit tests to verify the order of received events matches the order they
* were sent.
*/
unsigned int test_value;
/**
* Arbitrary padding. This member is entirely ignored and is used only to
* increase the storage size of this event. A wonky prime value is used
* here to help ensure the tests inherently verify that the base fifo
* implementation does not somehow depend on power-of-two alignment.
*/
char padding[73];
} test_event;
/**
* Test event fifo that extends the guac_fifo base. This event
* fifo differs from the base only in that it specifically stores test_events
* alongside an array of expected event values.
*/
typedef struct test_fifo {
/**
* The base fifo implementation.
*/
guac_fifo base;
/**
* Storage for all event items in this fifo.
*/
test_event items[TEST_FIFO_MAX_ITEMS];
/**
* A zero-terminated array of all integer values expected to be received as
* test events, in the order that they are expected to be received.
*/
unsigned int* expected_values;
} test_fifo;
/**
* Initializes the given test_fifo, assigning the given set of expected
* values for later reference by unit tests. The pointer to the expected values
* MUST remain valid until the text_fifo is destroyed.
*
* @param fifo
* The test_fifo to initialize.
*
* @param expected_values
* The zero-terminated set of expected values to be associated with the
* given test_fifo.
*/
void test_fifo_init(test_fifo* fifo, unsigned int* expected_values) {
guac_fifo_init((guac_fifo*) fifo, &fifo->items,
TEST_FIFO_MAX_ITEMS, sizeof(test_event));
fifo->expected_values = expected_values;
}
/**
* Destroys the given test_fifo, releasing any associated resources. It
* is safe to clean up the set of expected values originally provided to
* test_fifo_init() after this function has been invoked.
*
* @param fifo
* The test_fifo to destroy.
*/
void test_fifo_destroy(test_fifo* fifo) {
guac_fifo_destroy((guac_fifo*) fifo);
}
/**
* Thread that continuously reads events from the given test_fifo,
* verifying that each expected value is read in the correct order.
*
* @param data
* The test_fifo to read from.
*
* @return
* Always NULL.
*/
static void* queue_read_thread(void* data) {
test_fifo* fifo = (test_fifo*) data;
test_event event;
/* Continuously read values until zero (end of expected values) is reached */
for (unsigned int* current_expected_value = fifo->expected_values;
/* Exit condition checked in body of loop*/; current_expected_value++) {
/* Induce random delays in reading to simulate real-world conditions
* that may cause the fifo to fill */
guac_timestamp_msleep(rand() % TEST_READ_INTERVAL);
int retval = guac_fifo_timed_dequeue(
(guac_fifo*) fifo, &event, TEST_TIMEOUT);
/* A value of zero marks the end of the set of expected values, so the
* fifo SHOULD fail to read at this point */
if (*current_expected_value == 0) {
printf(" | END\n");
CU_ASSERT_FALSE(retval);
break;
}
/* For all other cases, the fifo should succeed in reading the next
* event, and the value of that event should match the current value
* from the set of expected values */
else {
printf(" | %i\n", event.test_value);
CU_ASSERT_TRUE(retval);
CU_ASSERT_EQUAL(event.test_value, *current_expected_value);
}
/* Do not continue waiting for more events if the fifo is timing out
* incorrectly */
if (!retval)
break;
}
return NULL;
}
/**
* Generic base test that sends all values in TEST_VALUES at the given
* interval. Values are read by a separate thread that instead reads at
* TEST_READ_INTERVAL, allowing the send/receive rates to differ. Timing
* between each send/receive attempt is varied randomly but is always bounded
* by the relevant interval.
*
* @param send_interval
* The rough number of milliseconds to wait between sending each event. The
* true number of milliseconds that elapse between each subsequent send
* attempt is varied randomly, with this provided value functioning as an
* upper bound.
*/
static void verify_send_receive(int send_interval) {
test_fifo fifo;
/* Create a test fifo that verifies each value within TEST_VALUES is
* received in order */
test_fifo_init(&fifo, TEST_VALUES);
/* Both this function and the thread it spawns will log sent/received event
* values to STDOUT for sake of debugging and verification */
printf("Sent | Received\n"
"---- | --------\n");
/* Spawn thread that can independently wait for events to be flagged */
pthread_t test_thread;
CU_ASSERT_FALSE_FATAL(pthread_create(&test_thread, NULL, queue_read_thread, &fifo));
/* Send all test values in order */
for (unsigned int* current = TEST_VALUES; *current != 0; current++) {
/* Pull next test value from TEST_VALUES array */
test_event event = {
.test_value = *current
};
/* Induce random delays in reading to simulate real-world conditions
* that may cause the fifo to fill */
if (send_interval)
guac_timestamp_msleep(rand() % send_interval);
printf("%4i |\n", event.test_value);
guac_fifo_enqueue((guac_fifo*) &fifo, &event);
}
/* All test values have now been sent */
printf(" END |\n");
/* Wait for thread to finish waiting for events */
CU_ASSERT_FALSE(pthread_join(test_thread, NULL));
test_fifo_destroy(&fifo);
}
/**
* Verify that the base fifo implementation functions correctly when events
* are sent slower than they are read.
*/
void test_fifo__slow_add(void) {
/* Add context for subsequent logging of sent/received values to STDOUT */
printf("-------- %s() --------\n", __func__);
/* Send at half the speed of the reading thread */
verify_send_receive(TEST_READ_INTERVAL * 2);
}
/**
* Verify that the base fifo implementation functions correctly when events
* are sent faster than they are read.
*/
void test_fifo__fast_add(void) {
/* Add context for subsequent logging of sent/received values to STDOUT */
printf("-------- %s() --------\n", __func__);
/* Send as quickly as possible (much faster than reading thread) */
verify_send_receive(0);
}
/**
* Verify that the base fifo implementation functions correctly when events
* are sent at roughly the same speed as the reading thread.
*/
void test_fifo__interleaved(void) {
/* Add context for subsequent logging of sent/received values to STDOUT */
printf("-------- %s() --------\n", __func__);
/* Send at roughly same speed as reading thread */
verify_send_receive(TEST_READ_INTERVAL);
}