Skip to content
Snippets Groups Projects
proxy.h 14.11 KiB
// proxy.h
// NOTE: This is a generic file. Actual unit tests are located in
//       unit_tests.cpp.
// By Jack Toole for CS 225 spring 2011

#ifndef MONAD_PROXY_H
#define MONAD_PROXY_H

#include <math.h>

#include <iostream>
#include <functional>
#include <limits>
#include <map>
#include <string>
#include <vector>
#include <utility>

#include "pipestream.h"
#include "monad_shared.h"

#define NO_MP_PART -1
#include "_mp_part_number.h"
#define MP_PART(x) (MP_PART_NUMBER == (x) || MP_PART_NUMBER == NO_MP_PART)

namespace proxy
{
	using namespace std;
	using namespace monad_shared;

	class RunTests;
	typedef bool (*output_check)(const std::string &, const std::string &);

	extern std::vector<unit_test> * global_tests;
	typedef std::map<std::string, output_check, util::ci_less> output_check_map;
	extern output_check_map * global_output_checks;

	class add_unit_test
	{
		public:
		add_unit_test(const char * name, unit_test::function func,
		              int32_t points_in_part, int32_t points_in_total, long timeout,
		              bool is_valgrind);

		private:
		void lazy_init_global_tests();
		int32_t get_points(int32_t points_in_total, int32_t points_in_part);
	};

	class add_output_check
	{
		public:
		add_output_check(const char * name, output_check func);
	};

	enum mode_t
	{
		SINGLE_TEST,
		MP_PART_TESTS,
		ALL_TESTS
	};

	struct RunTimeEnvironment
	{
		public:
		//!!const int itimer_number0;
		//!!const int itimer_number1;
		const int timeout_signum0;
		const int timeout_signum1;
		const size_t max_output_length;
		const char * single_test_passed_string;
		std::vector<unit_test> * heap_tests;
		output_check_map * output_checks;
		int32_t cleanup_globals();

		RunTimeEnvironment(std::vector<unit_test> *& init_tests,
		                   output_check_map *& init_output_checks);
		
		bool is_timeout_signal(int8_t signal_number)
		{
			return signal_number == timeout_signum0 ||
			       signal_number == timeout_signum1;
		}

		private:
		RunTimeEnvironment(const RunTimeEnvironment & other);
		RunTimeEnvironment & operator=(RunTimeEnvironment & other);
	};

	class RunTests
	{
		private:
		RunTimeEnvironment & environment;
		mode_t mode;
		const char * test_arg;
		int8_t mp_part;

		public:
		RunTests(int argc, char ** argv, RunTimeEnvironment & env);
		int execute();
		private:
		void redirect_glibc_to_stderr();
		void process_args(int argc, char ** argv);

		protected:
		int32_t execute_by_mode();
		int32_t run_single_test(const char * testname);
		int32_t run_single_test(unit_test & curr_test);
		void    handle_single_test_output(const std::string & output);
		void    output_single_test_passfail(const unit_test & curr_test);
		
		int32_t run_all_tests();
		int32_t get_sum_points();
		int32_t get_max_testname_length();
		int32_t get_max_points_length();
		void output_detailed_info_if_any_failed(int32_t score);
		void output_detailed_tests_info(int32_t score);

		bool execute_test(unit_test & test, bool enable_valgrind_call);

		private:
		RunTests(const RunTests & other);
		RunTests & operator=(const RunTests & other);
	};

	template <typename F>
	bool fork_execute(F & executor);
	
	class test_execution
	{
		private:
		util::pipestream fmsg_pipe; // For error messages
		util::pipestream cout_pipe; // For stdout/stderr
		util::pipestream nums_pipe; // for numbers: time, valgrind
		unit_test & test;
		RunTimeEnvironment & environment;
		bool do_valgrind;

		public:
		test_execution(unit_test & _test, RunTimeEnvironment & env, bool enable_valgrind_call);
		void before();
		void parent();
		void child();
		void after_success(int8_t return_code);
		void after_failure(int8_t signal_number);
		
		private:
		void child_test();
		void child_valgrind();
		void after_test_success();
		void after_valgrind_success(int8_t return_code);
		void start_timeout();
		long end_timeout();
		static bool prof_timeout_enabled();
		
		private:
		test_execution(const test_execution & other);
		test_execution & operator=(const test_execution & other);
	};


	const char * get_valgrind_string(int32_t flags);
	int32_t get_valgrind_flags(bool test_failed);
	int32_t bitflags(unsigned long a,     unsigned long b = 0, unsigned long c = 0,
					 unsigned long d = 0, unsigned long e = 0);
	bool bitflag(int32_t flags, int32_t num);

} // namespace proxy

using std::cout;
using std::cerr;
using std::endl;

#define UNIT_TEST(func,pointsInPart,pointsInTotal,timeout)             \
	monad_shared::unit_test::return_type                               \
	func(monad_shared::unit_test & this_test);                         \
	proxy::add_unit_test                                               \
		func##_adder(#func, func, pointsInPart,                        \
		             pointsInTotal, timeout, false);                   \
	monad_shared::unit_test::return_type                               \
	func(monad_shared::unit_test & this_test)

#define VALGRIND_TEST(func,pointsInPart,pointsInTotal,timeout)         \
	monad_shared::unit_test::return_type                               \
	func(monad_shared::unit_test & this_test);                         \
	proxy::add_unit_test                                               \
		func##_adder(#func, func, pointsInPart,                        \
		             pointsInTotal, timeout, true);                    \
	monad_shared::unit_test::return_type                               \
	func(monad_shared::unit_test & this_test)

#define HELPER_TEST(func, ...)                                         \
	monad_shared::unit_test::return_type                               \
	func(monad_shared::unit_test & this_test, __VA_ARGS__)

#define CALL_HELPER(func, ...)                                         \
	do {                                                               \
		monad_shared::unit_test::return_type helperval =               \
			func(this_test, __VA_ARGS__);                              \
		if (helperval != monad_shared::unit_test::pass_string)         \
			FAIL(helperval);                                           \
	} while (0)

#define OUTPUT_CHECK(func)                                                              \
	bool output_check_##func(const std::string & output, const std::string & expected); \
	proxy::add_output_check                                                             \
		output_check_##func##_adder(#func, output_check_##func);                        \
	bool output_check_##func(const std::string & output, const std::string & expected)

#define STRINGIFY1(p)   #p
#define STR(p)          STRINGIFY1(p)

#define FAIL(error)     return std::string(__FILE__ ":" STR(__LINE__) ": ") + (error)

#define PASS            return monad_shared::unit_test::pass_string;

#define ASSERT(expr)    if (!(expr))  \
                            FAIL("Assertion (" #expr ") failed")

namespace proxy {
template <typename T>
inline std::string assert_equals_help(T expected, T actual, const char * expstr, const char * actstr)
{
	std::stringstream ss;
	if (actual != expected)
	{
		ss << "[" << actstr << " => " << actual << "] != [" << expstr << " => " << expected << "]";
		return ss.str();
	}
	return monad_shared::unit_test::pass_string;
}
}

#define ASSERT_EQUALS(expected, actual)                                                     \
	do {                                                                                    \
		string errormsg = proxy::assert_equals_help(expected, actual, #expected, #actual);  \
		if (errormsg != monad_shared::unit_test::pass_string)                               \
			FAIL(errormsg);                                                                 \
	} while (0)

#define ASSERT_OUTPUT(checkFunc, str)  \
	*this_test.checkstream << #checkFunc << str;

enum proxy_runtime_t
{
	CONSTANT_TIME = 0,
	LOGN_TIME,
	N_TIME,
	NLOGN_TIME,
//	NROOTN_TIME,
	N2_TIME,
	N3_TIME,
	INFINITE_TIME,
	TIME_COUNT
};

namespace proxy
{
	typedef double (*runtime_ratio_func)(size_t, size_t);
	extern runtime_ratio_func runtime_ratio[TIME_COUNT];
	extern const char * runtime_str[TIME_COUNT];

	struct TimeIterationsData
	{
		double timePerCall;
		size_t iterations;
		uint64_t totalTime;
	};

	template <typename Generator, typename Timer>                  TimeIterationsData timeIterationsImpl(Generator gen,            Timer timeFunctor, size_t input_size);
	template <typename Generator, typename Timer>                  TimeIterationsData timeIterations    (Generator gen,            Timer timeFunctor, size_t input_size);
	template <typename GenResult, typename GenArg, typename Timer> TimeIterationsData timeIterations    (GenResult (*gen)(GenArg), Timer timeFunctor, size_t input_size);
	
	template <typename Generator, typename Timer>
	bool assert_time_impl(Generator gen, Timer functor, proxy_runtime_t expectedTime, size_t size1 = 100, size_t size2 = 400);
}

#define ASSERT_TIME3(gen, functor, expectedTime)                                          \
	do {                                                                                  \
		if (proxy::assert_time_impl(gen, functor, expectedTime))                          \
			FAIL(string("Runtime was larger than ") + proxy::runtime_str[expectedTime]);  \
	} while(0)

#define ASSERT_TIME5(gen, functor, expectedTime, size1, size2)                            \
	do {                                                                                  \
		if (proxy::assert_time_impl(gen, functor, expectedTime, size1, size2))            \
			FAIL(string("Runtime was larger than ") + proxy::runtime_str[expectedTime]);  \
	} while(0)

// Crazy hack for overloading!
// Arg counting from:
// http://cplusplus.co.il/2010/07/17/variadic-macro-to-count-number-of-arguments/
// Overloading tips:
// http://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros
#define ASSERT_TIME_SIXTH_ARG(a, b, c, d, e, f, ...) f

#define ASSERT_TIME(...)  \
	ASSERT_TIME_SIXTH_ARG(__VA_ARGS__, ASSERT_TIME5, 0, ASSERT_TIME3, 0, 0) (__VA_ARGS__)

namespace proxy {

template <typename Generator, typename Timer>
TimeIterationsData timeIterations(Generator gen, Timer timeFunctor, size_t input_size)
{
	return timeIterationsImpl(
			bind1st(mem_fun(&Generator::operator()), &gen),
			timeFunctor,
			input_size);
}

template <typename GenResult, typename GenArg, typename Timer>
TimeIterationsData timeIterations(GenResult (*gen)(GenArg), Timer timeFunctor, size_t input_size)
{
	return timeIterationsImpl(ptr_fun(gen), timeFunctor, input_size);
}

template <typename Generator, typename Timer>
TimeIterationsData timeIterationsImpl(Generator gen, Timer timeFunctor, size_t input_size)
{
	const uint64_t min_time = 1000000; // in microseconds
	const size_t max_gen_iterations = 1000000;
	std::vector<typename Generator::result_type *> inputs;
	inputs.reserve(2000); // arbitrary, guess at how big it will be

	// Using pointers here allows us to avoid copying if the compiler supports copy elision
	// Since we're intentionally using large inputs, this could potentially have a significant effect on speed
	// We're also going to do something else weird here. Instead of generating a fixed number of inputs, we're
	// going to generate inputs for a fixed time.
	size_t max_iterations = 0;
	for (uint64_t genstart = util::process_clock(); max_iterations < max_gen_iterations && util::process_clock() - genstart < min_time; max_iterations++)
		inputs.push_back(new typename Generator::result_type(gen(input_size)));

	typename Generator::result_type warmup_temp = gen(1);
	timeFunctor(warmup_temp); // Warm up time functor (i.e. initialize statics)

	size_t succeeded_iterations;
	uint64_t starttime = util::process_clock();
	for (succeeded_iterations = 0; succeeded_iterations < max_iterations && util::process_clock() - starttime < min_time;)
		for (uint32_t i = 0; i < 10 && succeeded_iterations < max_iterations; i++, succeeded_iterations++)
			timeFunctor(*inputs[succeeded_iterations]);
	uint64_t endtime = util::process_clock();

	for (size_t i = 0; i < max_iterations; i++)
		delete inputs[i];

	TimeIterationsData result;
	result.timePerCall = static_cast<double>(endtime - starttime) / succeeded_iterations;
	result.iterations  = succeeded_iterations;
	result.totalTime   = endtime - starttime;
	return result;
}

inline void timeIterationsOutput(size_t size, const TimeIterationsData & data)
{
	std::cout << "Input size " << size << ": "
	          << data.iterations << " iterations in " << data.totalTime/1000 << " ms "
			  << "for an average of " << data.timePerCall << " us per call" << endl;
}

template <typename Generator, typename Timer>
bool assert_time_impl(Generator gen, Timer functor, proxy_runtime_t expectedTime, size_t size1, size_t size2)
{
	TimeIterationsData diff0 = timeIterations(gen, functor, 1);
	TimeIterationsData diff1 = timeIterations(gen, functor, size1);
	TimeIterationsData diff2 = timeIterations(gen, functor, size2);
	timeIterationsOutput(    1, diff0);
	timeIterationsOutput(size1, diff1);
	timeIterationsOutput(size2, diff2);
	double ratio = (diff2.timePerCall - diff0.timePerCall) / (diff1.timePerCall - diff0.timePerCall);
	
	double expected_ratio = proxy::runtime_ratio[expectedTime](size1, size2);
	double toohigh_ratio  = proxy::runtime_ratio[expectedTime + 1](size1, size2);
	double diffFromExpected = fabs(ratio - expected_ratio);
	double diffFromWrong    = fabs(ratio - toohigh_ratio);
	
	std::cout << "Actual ratio:     " << ratio << std::endl;
	std::cout << "Expected ratio:   " << expected_ratio << std::endl;
	std::cout << "Wrong/high ratio: " << toohigh_ratio  << std::endl;
	std::cout << "Diff from expected: " << diffFromExpected  << std::endl;
	std::cout << "Diff from wrong:    " << diffFromWrong  << std::endl;

#if 0 // This does not seem to be important. A sample of two iterations seems to work.
	const size_t min_iters = 100;
	if (diff0.iterations < min_iters || diff1.iterations < min_iters || diff2.iterations < min_iters)
	{
		std::cout << "Too few iterations: Code was too slow to be able to judge runtime accurately" << std::endl;
		return true;
	}
#endif

	return (diffFromWrong < diffFromExpected);
}

inline int32_t bitflags(unsigned long a, unsigned long b, unsigned long c,
                        unsigned long d, unsigned long e)
{
	return ((int)(a != 0))        | (((int)(b != 0)) << 1) |
           (((int)(c != 0)) << 2) | (((int)(d != 0)) << 3) |
           (((int)(e != 0)) << 4) ;
}

inline bool bitflag(int32_t flags, int32_t num)
{
	return (flags & (1 << num)) != 0;
}

}
#endif