
/* Test creation and removal of wildcarded failure points while checking them
 * on a different thread.
 *
 * This test will have a thread enabling and disabling failure points like
 * point:number:1:*, point:number:2:*, ...
 *
 * Then it has two threads checking:
 * point:number:1:1, point:number:1:2, ...,
 * point:number:2:1, point:number:2:2, ...
 *
 * Please note this is a non-deterministic test. */

#include <assert.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <fiu-control.h>
#include <fiu.h>

/* Be careful with increasing these numbers, as the memory usage is directly
 * related to them. */
#define NHIGH 1000
#define NLOW 1000

/* Maximum number of a failure point's name */
#define MAX_FPNAME 32

/* How many seconds to run the test */
#define TEST_TIME 5

/* Signal for the threads to stop. */
bool stop_threads = false;

/* The name of each final point. */
char final_point_name[NHIGH][NLOW][MAX_FPNAME];

/* The name of each wildcarded point. */
char wildcard_point_name[NHIGH][MAX_FPNAME];

char *make_final_point_name(char *name, int high, int low)
{
	sprintf(name, "point/number/%d/%d", high, low);
	return name;
}

char *make_wildcard_point_name(char *name, int high)
{
	sprintf(name, "point/number/%d/*", high);
	return name;
}

/* Calls all the final points all the time. */
unsigned long long no_check_caller_count = 0;
void *no_check_caller(void *unused)
{
	int high, low;

	for (;;) {
		for (high = 0; high < NHIGH; high++) {
			for (low = 0; low < NLOW; low++) {
				fiu_fail(final_point_name[high][low]);
				no_check_caller_count++;
			}
			if (stop_threads)
				return NULL;
		}
	}

	return NULL;
}

bool rand_bool(void)
{
	return (rand() % 2) == 0;
}

/* Used too know if a point is enabled or not. */
bool enabled[NHIGH];
pthread_rwlock_t enabled_lock;

/* Calls all the *enabled* points all the time. */
unsigned long long checking_caller_count = 0;
void *checking_caller(void *unused)
{
	int high, low;
	int failed;

	for (;;) {
		for (high = 0; high < NHIGH; high++) {
			pthread_rwlock_rdlock(&enabled_lock);
			if (!enabled[high]) {
				pthread_rwlock_unlock(&enabled_lock);
				continue;
			}

			for (low = 0; low < NLOW; low++) {
				failed =
				    fiu_fail(final_point_name[high][low]) != 0;
				if (!failed) {
					printf("ERROR: %d:%d did not fail\n",
					       high, low);
					assert(false);
				}
				checking_caller_count++;
			}
			pthread_rwlock_unlock(&enabled_lock);

			if (stop_threads)
				return NULL;
		}
	}

	return NULL;
}

/* Enable and disable wildcarded points all the time. */
unsigned long long enabler_count = 0;
void *enabler(void *unused)
{
	int high;

	for (;;) {
		for (high = 0; high < NHIGH; high++) {
			/* 50% chance of flipping this point. */
			if (rand_bool())
				continue;

			pthread_rwlock_wrlock(&enabled_lock);
			if (enabled[high]) {
				assert(fiu_disable(wildcard_point_name[high]) ==
				       0);
				enabled[high] = false;
			} else {
				assert(fiu_enable(wildcard_point_name[high], 1,
				                  NULL, 0) == 0);
				enabled[high] = true;
			}
			pthread_rwlock_unlock(&enabled_lock);
			enabler_count++;

			if (stop_threads)
				return NULL;
		}
	}

	return NULL;
}

void disable_all()
{
	int high;

	pthread_rwlock_wrlock(&enabled_lock);
	for (high = 0; high < NHIGH; high++) {
		/* Note this could fail as we don't check if they're active or
		 * not, but here we don't care. */
		fiu_disable(wildcard_point_name[high]);
	}
	pthread_rwlock_unlock(&enabled_lock);
}

int main(void)
{
	int high, low;

	pthread_t t1, t2, t3;

	fiu_init(0);

	for (high = 0; high < NHIGH; high++) {
		make_wildcard_point_name(wildcard_point_name[high], high);

		for (low = 0; low < NLOW; low++) {
			make_final_point_name(final_point_name[high][low], high,
			                      low);
		}
	}

	pthread_rwlock_init(&enabled_lock, NULL);

	pthread_create(&t1, NULL, no_check_caller, NULL);
	pthread_create(&t2, NULL, checking_caller, NULL);
	pthread_create(&t3, NULL, enabler, NULL);

	sleep(TEST_TIME);

	stop_threads = 1;

	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	pthread_join(t3, NULL);

	disable_all();

	pthread_rwlock_destroy(&enabled_lock);

	printf("wildcard nc: %-8llu  c: %-8llu  e: %-8llu  t: %llu\n",
	       no_check_caller_count, checking_caller_count, enabler_count,
	       no_check_caller_count + checking_caller_count + enabler_count);

	return 0;
}
