Initial Commit

This commit is contained in:
Marc
2025-09-13 14:40:16 +02:00
commit ded01301c2
383 changed files with 71046 additions and 0 deletions

800
OU3/tabletest.c Normal file
View File

@@ -0,0 +1,800 @@
/*
* tabletest - test different implementation of a table.
*
* Should be compiled together with a table implementation that
* follows the interface in table.h of the code base for the
* Datastructures and Algorithms courses at the Department of
* Computing Science, Umea University.
*
* 20xx-xx-xx v1.0 Lena Kallin Westin <kallin@cs.umu.se>.
* 2017-03-xx v1.1 Adam Dahlstrom <dali@cs.umu.se>
* Modified to use dynamic memory.
* 2017-05-xx v1.2 Niclas Borlin <niclas@cs.umu.se>.
* Simplified printout and use. Test size can now be
* specified on the command line. Lookup (no key) moved
* before lookup to avoid cache-hit effects on timing of
* mtftable.
* 2017-05-xx v1.3 Niclas Borlin <niclas@cs.umu.se>.
* Added more fine-grained tests.
* 2018-02-08 v1.4 Niclas Borlin <niclas@cs.umu.se>.
* Modified to match 2018 implementation of the data
* structure code base.
* 2018-02-20 v1.5 Niclas Borlin <niclas@cs.umu.se>.
* Now completely destroys and rebuilds table between timed
* tests to reduce cache effects.
* 2018-04-12 v1.6 Niclas Borlin <niclas@cs.umu.se>.
* Modified program to accept two parameters; number of
* elements and number of lookups.
* 2018-05-02 v1.7 Niclas Borlin <niclas@cs.umu.se>.
* Bugfix in get_skewed_lookup_speed.
* 2019-02-12 v1.8 Reverted back to single-parameter version v1.6.
* 2019-04-17 v1.9 Added -m for machine-readable results.
* 2023-02-17 v1.10 Added printout of the codebase version.
*/
#define VERSION "v1.10"
#define VERSION_DATE "2023-02-17"
/*
* Correctness testing algorithm:
*
* 1. Tests if isempty returns true directly after a table is created
* 2. Tests if isempty returns false directly after a table is created
* and one element (key-value-pair) is inserted to it.
* 3. Tests a table by creating it and inserting one key-value-pair. After
* that it is checked that the returned values from a lookup are the ones
* expected. First by looking up a non-existent key, then by looking up
* an existent key.
* 4. Tests a table by creating it and inserting three key-value-pairs with
* unique keys. After that, a lookup for all three keys are tested and
* it is checked that the returned values are the ones expected.
* 5. Tests a table by creating it and inserting three key-value-pairs with
* the same keys. After that, a lookup for the key is tested and it is
* checked that the returned value is the last one inserted to the table.
* 6. Tests a table by creating it and inserting one key-value-pair. After
* that the element is removed and it is checked that the table is empty.
* 7. Tests a table by creating it and inserting three key-value-pairs. After
* that the elements is removed one at a time and it is checked that the
* table is empty after the third element is removed.
* 8. Tests a table by creating it and inserting a single key-value
* pair followed by three key-value-pairs with identical
* keys. After that, the first element is remove and it is verified
* that it is gone and that the other key returns the cocorrect
* value. The second key is removed and it is checked that the
* table is empty.
*
* There is also a module measuring time for insertions, lookups etc.
* */
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include "table.h"
// Maximum size of the table to generate
#define TABLESIZE 40000
#define SAMPLESIZE TABLESIZE*2
/**
* copy_string() - Create a dynamic copy of a string.
* @s: String to be copied.
*
* Allocates memory for a dynamic copy of s and copies the contents of
* s into the copy.
*
* Returns: Pointer to the copy of s.
*/
char *copy_string(const char *s)
{
int len=strlen(s);
/* Allocate memory for new string, with an extra char for \0 */
char *dest = malloc(sizeof(char)*(len+1));
/* Malloc failed, return NULL */
if (dest == NULL) {
return NULL;
}
/* Copy content to new memory */
strncpy(dest, s, len);
/* Strings should always be null terminated */
dest[len] = '\0';
return dest;
}
/**
* int_ptr_from_int() - Create a dynamic copy of an integer.
* @i: String to be copied.
*
* Allocates memory for a dynamic copy of i and copies the contents of
* i into the copy.
*
* Returns: Pointer to the copy of i.
*/
int *int_ptr_from_int(int i)
{
// Allocate memory for a dynamic copy of an integer
int *ip=malloc(sizeof(int));
// Copy the value
*ip=i;
return ip;
}
/**
* get_milliseconds() - Return the current time-of-day in milliseconds.
*
* Returns: The current time-of-day in milliseconds.
*/
unsigned long get_milliseconds()
{
struct timeval tv;
gettimeofday(&tv, 0);
return (unsigned long)(tv.tv_sec*1000 + tv.tv_usec/1000);
}
/**
* int_compare() - Compare to integers via pointers.
* @ip1, @ip2: Pointers to integers to be compared.
*
* Compares the integers that ip1 and ip2 points to.
*
* Returns: 0 if the integers are equal, negative if the first
* argument is smaller, positive if the first argument is larger.
*/
int int_compare(const void *ip1,const void *ip2)
{
const int *n1=ip1;
const int *n2=ip2;
return (*n1 - *n2);
}
/**
* string_compare() - Compare two strings.
* @ip1, @ip2: Pointers to strings to be compared.
*
* Compares the strings that ip1 and ip2 points to.
*
* Returns: 0 if the strings are equal, negative if the first
* argument is smaller, positive if the first argument is larger.
*/
int string_compare(const void *ip1,const void *ip2)
{
const char *s1=ip1;
const char *s2=ip2;
return strcmp(s1,s2);
}
/* Shuffles the numbers stored in seq
* seq - an array of randomnumbers to be shuffled
* n - the number of elements in seq to shuffle, i.e the indexes [0, n]
* will be shuffled. However, seq might be larger than n...
*/
void random_shuffle(int seq[], int n)
{
for(int i=0;i<n;i++) {
int switchPos=rand()%n;
if(i!=switchPos) {
int temp=seq[i];
seq[i]=seq[switchPos];
seq[switchPos]=temp;
}
}
}
/* Generate n unique random numbers. The numbers will be stored i seq
* seq - an array of randomnumbers created in the function
* n - the number of elements in seq
*/
void create_random_sample(int seq[], int n)
{
for(int i=0;i<n;i++) {
seq[i]=i;
}
random_shuffle(seq,n);
}
/* Fill a table with values.
* table - the table to fill
* keys - a list of keys to use
* values - a list of values to use
*/
void insert_values(table *t, int *keys, int *values, int n)
{
// Insert all items
for(int i=0;i<n;i++) {
table_insert(t, int_ptr_from_int(keys[i]),
int_ptr_from_int(values[i]));
}
}
/* Measures time taken to fill a table with values
* table - the table to fill
* keys - a list of keys to use
* values - a list of values to use
* n - the number of items to insert
* machine_table - true if we should output a machine-readable table
*/
void get_insert_speed(table *t, int *keys, int *values, int n,
bool machine_table)
{
unsigned long start;
unsigned long end;
// Insert all items
start = get_milliseconds();
insert_values(t,keys,values,n);
end = get_milliseconds();
if (machine_table) {
printf("%d, %d, %lu\n",1,n,end-start);
} else {
printf("Insert %5d items : ", n);
printf("%lu ms.\n",end-start);
}
}
/* Measures time taken to do n lookups of existing keys in a table
* t - the table to fill
* keys - a list of keys to use
* values - a list of values to use
* n - the number of lookups to perform
* machine_table - true if we should output a machine-readable table
*/
void get_random_existing_lookup_speed(table *t, int *keys, int n,
bool machine_table)
{
unsigned long start;
unsigned long end;
start = get_milliseconds();
for(int i=0;i<n;i++) {
// The existing keys in the table are stored in index
// [0, TABLESIZE-1] in the key-array
int pos = rand()%n;
table_lookup(t,&keys[pos]);
}
end = get_milliseconds();
if (machine_table) {
printf("%d, %d, %lu\n",4,n,end-start);
} else {
printf("%5d random lookups : ",n );
printf("%lu ms.\n", end-start);
}
}
/* Measures time taken to do n lookups of non-existing keys in a table
* t - the table to fill
* keys - a list of keys to use
* values - a list of values to use
* n - the number of lookups to perform
* machine_table - true if we should output a machine-readable table
*/
void get_random_non_existing_lookup_speed(table *t, int *keys, int n,
bool machine_table)
{
unsigned long start;
unsigned long end;
// We know the exisiting keys have indexes in [0, TABLESIZE-1]
// so if we try to lookup keys in the area [TABLESIZE,
// 2*TABLESIZE-1] they will not exist
start = get_milliseconds();
int startindex = n;
for(int i=0;i<n;i++){
table_lookup(t,&keys[startindex + (i%n)]);
}
end = get_milliseconds();
if (machine_table) {
printf("%d, %d, %lu\n",3,n,end-start);
} else {
printf("%5d lookups with non-existent keys : ", n);
printf("%lu ms.\n" ,end-start);
}
}
/* Measures time taken to do n lookups of existing keys in a table when the
* keys chosen are from only a part of all available keys
* t - the table to fill
* keys - a list of keys to use
* values - a list of values to use
* n - the number of lookups to perform
* machine_table - true if we should output a machine-readable table
*/
void get_skewed_lookup_speed(table *t, int *keys, int n, bool machine_table)
{
unsigned long start;
unsigned long end;
// Lookup skewed to a certain range (in this case the middle third
// of the keys used)
int startindex = n/3;
int stopindex = n*2/3;
int partition = stopindex - startindex + 1;
start = get_milliseconds();
for(int i=0;i<n;i++) {
int pos = rand()%partition + startindex;
table_lookup(t,&keys[pos]);
}
end = get_milliseconds();
if (machine_table) {
printf("%d, %d, %lu\n",5,n,end-start);
} else {
printf("%5d skewed lookups : " ,n);
printf("%lu ms.\n" ,end-start);
}
}
/* Measures time taken remove all keys from a table
* t - the table to fill
* keys - a list of keys to use
* values - a list of values to use
* n - number of items in the table
* machine_table - true if we should output a machine-readable table
*/
void get_remove_speed(table *t, int *keys, int n, bool machine_table)
{
unsigned long start;
unsigned long end;
// Remove all items, not in the same order as they were inserted
start = get_milliseconds();
random_shuffle(keys, n); // to get a new order of the keys
for(int i=0;i<n;i++) {
table_remove(t,&keys[i]);
}
end = get_milliseconds();
if (machine_table) {
printf("%d, %d, %lu\n",2,n,end-start);
} else {
printf("Remove all items : ");
printf("%lu ms.\n" ,end-start);
}
}
/* Tests if isempty returns true directly after a table is created.
*/
void test_isempty(){
table *t = table_empty(int_compare, NULL, NULL);
if (!table_is_empty(t)){
printf("An newly created empty table is said to be nonempty.\n");
exit(EXIT_FAILURE);
}
printf("Isempty returns true directly after a table is created. - OK\n");
table_kill(t);
}
/* Tests if isempty returns false directly after a table is created
* and one element (key-value-pair) is inserted to it.
*/
void test_insert_single_element(void)
{
table *t = table_empty(string_compare, free, free);
char *key = copy_string("key1");
char *value = copy_string("value1");
table_insert(t, key, value);
if (table_is_empty(t)){
printf("A table with one inserted element is seen as empty.\n");
exit(EXIT_FAILURE);
}
printf("Isempty false if one element is inserted to table. - OK\n");
table_kill(t);
}
/* Tests looking up the key key in a table table. Checks that lookup is
* returning the value value
* t - the table to do the lookup in
* key - the key to lookup
* value - the expected value
*/
void test_lookup_existing_key(table *t, char *key, char *value)
{
char *return_value = (char *)table_lookup(t, key);
if (return_value==NULL){
printf("Looked up an existing key, table claims it does "
"not exist.\n");
exit(EXIT_FAILURE);
}
if (strcmp(return_value, value)){
printf("Looked up a key but the value returned was wrong.");
printf(" Expected: %s but got %s.\n", value, return_value);
exit(EXIT_FAILURE);
}
}
/* Tests looking up the key key in a table table. Verifies that the
* key is not found.
*/
void test_lookup_missing_key(table *t, char *key)
{
char *return_value = (char *)table_lookup(t, key);
if (return_value!=NULL){
printf("Looked up an missing key %s, table claims it has "
"value %s.\n",key,return_value);
exit(EXIT_FAILURE);
}
}
/* Tests a table by creating it and inserting one key-value-pair. After
* that it is checked that the returned values from a lookup are the ones
* expected. First by looking up a non-existent key, then by looking up
* an existent key.
*
* It is assumed that test_insert_single_value has been run before calling
* this test, i.e. that it has been tested that inserting one element
* results in an nonempty table.
*/
void test_lookup_single_element()
{
table *t = table_empty(string_compare, free, free);
char *key1 = copy_string("key1");
char *value1 = copy_string("value1");
char *value2;
table_insert(t, key1, value1);
// Lookup a non-existent key
value2 = (char *)table_lookup(t, "key2");
if (value2!=NULL){
printf("Looked up non-existing key, table claims it does "
"exist.\n");
exit(EXIT_FAILURE);
}
printf("Test of looking up non-existing key in a table with one "
"element - OK\n");
// Lookup an existent key
test_lookup_existing_key(t, key1, value1);
printf("Looking up existing key in a table with one element - OK\n");
table_kill(t);
}
/* Tests a table by creating it and inserting three key-value-pairs with
* unique keys. After each step, all existing keys are looked up and
* it is checked that the returned values are the ones expected.
*
* It is assumed that test_insert_single_value and test_lookup_single_value
* has been run before calling this test, i.e. that it has been tested
* that inserting one element results in an nonempty table and that
* looking up an key-value-pairs is working with tablesize=1.
*/
void test_insert_lookup_different_keys()
{
table *t = table_empty(string_compare, free, free);
char *key1 = copy_string("key1");
char *key2 = copy_string("key2");
char *key3 = copy_string("key3");
char *value1 = copy_string("value1");
char *value2 = copy_string("value2");
char *value3 = copy_string("value3");
table_insert(t, key1, value1);
test_lookup_existing_key(t, "key1", "value1");
table_insert(t, key2, value2);
test_lookup_existing_key(t, "key1", "value1");
test_lookup_existing_key(t, "key2", "value2");
table_insert(t, key3, value3);
test_lookup_existing_key(t, "key1", "value1");
test_lookup_existing_key(t, "key2", "value2");
test_lookup_existing_key(t, "key3", "value3");
printf("Looking up three existing keys-value pairs in a table "
"with three elements - OK\n");
table_kill(t);
}
/* Tests a table by creating it and inserting three key-value-pairs with
* the same keys. After each insert, the new key is looked up and it is
* checked that the returned value is the last one inserted to the table.
*
* has been run before calling this test, i.e. that it has been tested
* It is assumed that test_insert_single_value and test_lookup_single_value
* that inserting one element results in an nonempty table and that
* looking up an key-value-pairs is working with tablesize=1.
*
*/
void test_insert_lookup_same_keys()
{
table *t = table_empty(string_compare, free, free);
/* Separate key to use on lookup, since it is not defined
* which duplicate key will be removed.
*/
char *key = "key1";
/* Different allocations for keys, but same content. Behaviour
* is undefined if the same pointer is used multiple times.
* Memhandler will break things in that case. */
char *key1 = copy_string(key);
char *key2 = copy_string(key);
char *key3 = copy_string(key);
char *value1 = copy_string("value1");
char *value2 = copy_string("value2");
char *value3 = copy_string("value3");
table_insert(t, key1, value1);
test_lookup_existing_key(t, key, value1);
table_insert(t, key2, value2);
test_lookup_existing_key(t, key, value2);
table_insert(t, key3, value3);
test_lookup_existing_key(t, key, value3);
printf("Looking up existing key and value after inserting the same "
"key three times with different values - OK\n");
table_kill(t);
}
/* Tests a table by creating it and inserting one key-value-pair. After
* that the element is removed and it is checked that the table is empty.
*
* It is assumed that test_insert_single_value has been run before calling
* this test, i.e. that it has been tested that inserting one element
* results in an nonempty table.
*/
void test_remove_single_element()
{
table *t = table_empty(string_compare, free, free);
char *key1 = copy_string("key1");
char *value1 = copy_string("value1");
table_insert(t, key1, value1);
table_remove(t, key1);
if (!table_is_empty(t)){
printf("Removing the last element from a table does not "
"result in an empty table.\n");
exit(EXIT_FAILURE);
}
printf("Inserting one element and removing it, checking that the "
"table gets empty - OK\n");
table_kill(t);
}
/* Tests a table by creating it and inserting three key-value-pairs.
* After that the elements is removed one at a time. After each
* removal, it is verified that each removed key is indeed missing
* and that each remaining key is still there. Finally, it is tested
* that the table is empty after the third element has been removed.
*
* It is assumed that test_insert_single_value and test_remove_single_value
* have been run before calling this test, i.e. that it has been tested
* that inserting one element results in an nonempty table and removing
* one element from a table with one element results in an empty table.
*/
void test_remove_elements_different_keys()
{
table *t = table_empty(string_compare, free, free);
char *key1 = copy_string("key1");
char *key2 = copy_string("key2");
char *key3 = copy_string("key3");
char *value1 = copy_string("value1");
char *value2 = copy_string("value2");
char *value3 = copy_string("value3");
table_insert(t, key1, value1);
table_insert(t, key2, value2);
table_insert(t, key3, value3);
table_remove(t, key1);
if (table_is_empty(t)){
printf("Should be two elements left in the table but it says "
"it is empty\n");
exit(EXIT_FAILURE);
}
test_lookup_missing_key(t, "key1");
test_lookup_existing_key(t, "key2", "value2");
test_lookup_existing_key(t, "key3", "value3");
table_remove(t, key2);
if (table_is_empty(t)){
printf("Should be one element left in the table but it "
"says it is empty\n");
exit(EXIT_FAILURE);
}
test_lookup_missing_key(t, "key1");
test_lookup_missing_key(t, "key2");
test_lookup_existing_key(t, "key3", "value3");
table_remove(t, key3);
if (!table_is_empty(t)){
printf("Removing the last element from a table does not "
"result in an empty table.\n");
exit(EXIT_FAILURE);
}
test_lookup_missing_key(t, "key1");
test_lookup_missing_key(t, "key2");
test_lookup_missing_key(t, "key3");
printf("Inserting three elements and removing them, should end with "
"empty table - OK\n");
table_kill(t);
}
/* Tests a table by creating it and inserting a single key-value
* follow by a set of three key-value-pairs with duplicate keys. The
* first key is removed, and it is tested that the key is gone and
* that the second key gives the right result. Then the second key is
* removed is it is checked that the table is empty.
*
* It is assumed that test_insert_single_value and test_remove_single_value
* have been run before calling this test, i.e. that it has been tested
* that inserting one element results in an nonempty table and removing
* one element from a table with one element results in an empty table.
*/
void test_remove_elements_same_keys()
{
table *t = table_empty(string_compare, free, free);
/* Separate key to use in remove, since it is not defined
* which duplicate key will be removed.
*/
char *key1 = "key1";
char *key2 = "key2";
/* Different allocations for keys, but same content. Behaviour
* is undefined if the same pointer is used multiple times.
* Memhandler will break things in that case. */
char *key11 = copy_string(key1);
char *key21 = copy_string(key2);
char *key22 = copy_string(key2);
char *key23 = copy_string(key2);
char *value11 = copy_string("value11");
char *value21 = copy_string("value21");
char *value22 = copy_string("value22");
char *value23 = copy_string("value23");
/* Insert triplicates and verify that each new value indeed
* becomes the new value. */
table_insert(t, key11, value11);
/* Insert another set of triplicates and verify that each new
* value indeed becomes the new value. */
table_insert(t, key21, value21);
table_insert(t, key22, value22);
table_insert(t, key23, value23);
/* Now remove the first element */
table_remove(t, key1);
test_lookup_missing_key(t, key1);
/* This will fail if remove permutes the order of the
duplicate key2 values */
test_lookup_existing_key(t, key2, "value23");
table_remove(t, key2);
test_lookup_missing_key(t, key2);
if (!table_is_empty(t)) {
printf("Removing the last element from a table does not "
"result in an empty table.\n");
exit(EXIT_FAILURE);
}
printf("Inserting three elements with the same key and removing "
"the key, should end with empty table - OK\n");
table_kill(t);
}
/* Tests a table by performing a set of tests. Program exits if any
* error is found.
*/
void correctness_test()
{
test_isempty();
test_insert_single_element();
test_lookup_single_element();
test_insert_lookup_different_keys();
test_insert_lookup_same_keys();
test_remove_single_element();
test_remove_elements_different_keys();
test_remove_elements_same_keys();
}
/* Tests the speed of a table using random numbers. First a number of
* elements are inserted. Second a random lookup among the elements are
* done followed by a skewed lookup (where a subset of the keys are
* looked up more frequently). Finally all elements are removed.
*/
void speed_test(int n,bool machine_table)
{
int randomsize = 2*n; // To make it easier testing
// non-existing keys later
int *keys = malloc(randomsize*sizeof(int));
int *values = malloc(randomsize*sizeof(int));
create_random_sample(keys, randomsize);
create_random_sample(values, n);
table *t = table_empty(int_compare, free, free);
get_insert_speed(t, keys, values, n, machine_table);
table_kill(t);
t = table_empty(int_compare, free, free);
insert_values(t,keys,values,n);
get_remove_speed(t, keys, n, machine_table);
table_kill(t);
t = table_empty(int_compare, free, free);
insert_values(t,keys,values,n);
get_random_non_existing_lookup_speed(t, keys, n, machine_table);
table_kill(t);
t = table_empty(int_compare, free, free);
insert_values(t,keys,values,n);
get_random_existing_lookup_speed(t, keys, n, machine_table);
table_kill(t);
t = table_empty(int_compare, free, free);
insert_values(t,keys,values,n);
get_skewed_lookup_speed(t, keys, n, machine_table);
table_kill(t);
free(keys);
free(values);
}
#define NAME "tabletest"
int main(int argc,char **argv)
{
bool do_test=true; // Should we run the testing code?
bool machine_table=false; // Should we output a machine-readable table?
int n=-1;
fprintf(stderr,NAME " " VERSION " (" VERSION_DATE ")\n");
fprintf(stderr, "Code base version %s.\n\n", CODE_BASE_VERSION);
for (int i=1; i<argc; i++) {
// Parse each argument
char *s=argv[i];
if (strlen(s)>0 && s[0]=='-') {
switch (s[1]) {
case 'n':
do_test=false;
break;
case 't':
machine_table=true;
break;
default:
fprintf(stderr,"%s: Bad switch: %s.\n",
argv[0],s);
exit(EXIT_FAILURE);
}
} else {
// Convert string to integer.
n=atoi(s);
break;
}
}
if (n<0) {
fprintf(stderr,"Usage:\n\t%s [-n] [-t] n\n"
"\twhere n is an integer from 1 to %d.\n\n"
"\tUse -n (no-test) to skip the testing.\n"
"\tUse -t (table) to output a machine-readable table with the timings.\n",
argv[0],TABLESIZE);
exit(EXIT_FAILURE);
}
if (n<1 || n>TABLESIZE) {
fprintf(stderr,"Error: supplied value of n (%d) is outside "
"allowed range 1-%d.\n",n,TABLESIZE);
exit(EXIT_FAILURE);
}
if (do_test) {
printf("Testing...\n");
correctness_test();
printf("All correctness tests succeeded!\n\n");
}
/*getchar();*/
speed_test(n,machine_table);
if (!machine_table) {
printf("Test completed.\n");
}
return 0;
}