#include "stdafx.h"
#include "Util.h"
#include "Compiler/Debug.h"

BEGIN_TEST(ActiveBasic, Reload) {
	// Check basic reloading of active functions:
	Package *reload = gEngine().package(S("tests.reload"));

	const char contents[] =
		"Nat activeBasic(Nat times) {\n"
		"  Nat sum = beforeItem();\n"
		"  for (Nat i = 0; i < times; i++) {\n"
		"    sum *= 10;\n"
		"    sum += nextItem();\n"
		"  }\n"
		"  sum += afterItem();\n"
		"  return sum;\n"
		"}\n"
		"\n"
		"Nat nextItem() {\n"
		"  yield();\n"
		"  5;\n"
		"}\n"
		"\n"
		"Nat beforeItem() { 5; }\n"
		"\n"
		"Nat afterItem() { 10000; }\n";

	// Note: This both verifies that the original code is run, but also that it is compiled before
	// we start. If it is not compiled, it invalidates our timing-based assumptions.
	CHECK_EQ(runFn<Nat>(S("tests.reload.activeBasic"), Nat(4)), 9);

	os::Future<Nat> result;
	spawnFn(result, S("tests.reload.activeBasic"), Nat(4));

	// Let the function run until its first 'yield':
	os::UThread::leave();

	// Replace everything:
	reloadFile(reload, S("active_basic.bs"), contents);

	// Now, let it run to completion and see what we get!
	// Note: Without replacing active functions, we should get 10017, with it we should get 2555.
	CHECK_EQ(result.result(), 15555);

} END_TEST


BEGIN_TEST(ActiveException, Reload) {
	// Check so that we can throw exceptions through an updated function:
	Package *reload = gEngine().package(S("tests.reload"));

	const char contents[] =
		"use core:debug;\n"
		"\n"
		"Int activeException() {\n"
		"  DbgVal val(10);\n"
		"  exceptionHelper();\n"
		"  val.v;\n"
		"}\n"
		"\n"
		"void exceptionHelper() {\n"
		"  yield();\n"
		"  throw NotSupported(\"test\");\n"
		"}\n";

	debug::DbgVal::clear();
	CHECK_ERROR(runFn<Int>(S("tests.reload.activeException")), NotSupported);
	CHECK(debug::DbgVal::clear());

	os::Future<Int> result;
	spawnFn(result, S("tests.reload.activeException"));

	os::UThread::leave();

	// Replace!
	reloadFile(reload, S("active_exception.bs"), contents);

	// Make sure that it still throws, that the exception is propagated properly, and that we can clean up as expected.
	CHECK_ERROR(result.result(), NotSupported);
	CHECK(debug::DbgVal::clear());

} END_TEST


BEGIN_TEST(ActiveNewVars, Reload) {
	// Check so that we can re-shuffle the stack frame of a function.
	Package *reload = gEngine().package(S("tests.reload"));

	const char contents[] =
		"use core:debug;\n"
		"\n"
		"Nat activeNewVars(Nat max) {\n"
		"  Nat result = 0;\n"
		"  Nat times = 0;\n"
		"  DbgVal val(0);\n"
		"  while (result < max) {\n"
		"    Nat v = newVarsNext();\n"
		"    result += v;\n"
		"    if (++times >= 4)\n"
		"      break;\n"
		"  }\n"
		"  result;\n"
		"}\n"
		"\n"
		"Nat newVarsNext() {\n"
		"  yield();\n"
		"  10;\n"
		"}\n";

	debug::DbgVal::clear();
	CHECK_EQ(runFn<Nat>(S("tests.reload.activeNewVars"), Nat(100)), 100);
	CHECK(debug::DbgVal::clear());

	os::Future<Nat> result;
	spawnFn(result, S("tests.reload.activeNewVars"), Nat(100));

	// Let it run for a bit, so that we see a difference from just starting the function from zero.
	for (Nat i = 0; i < 2; i++)
		os::UThread::leave();

	reloadFile(reload, S("active_newvars.bs"), contents);

	CHECK_EQ(result.result(), 50);
	CHECK(debug::DbgVal::clear());

	// Running it from scratch should give zero.
	CHECK_EQ(runFn<Nat>(S("tests.reload.activeNewVars"), Nat(100)), 40);
	CHECK(debug::DbgVal::clear());

} END_TEST
