OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-cogutil / tests / util / WatermarkUTest.cxxtest

249 lines · cxxtest ref:
/** WatermarkUTest.cxxtest ---
 *
 * Tests for watermark functionality in concurrent containers
 *
 * Copyright (C) 2025 BrainyBlaze Dynamics, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License v3 as
 * published by the Free Software Foundation and including the exceptions
 * at http://opencog.org/wiki/Licenses
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, write to:
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <opencog/util/concurrent_queue.h>
#include <opencog/util/concurrent_stack.h>
#include <opencog/util/concurrent_set.h>
#include <opencog/util/Logger.h>
#include <thread>
#include <chrono>
#include <atomic>

using namespace opencog;
using namespace std;

class WatermarkUTest : public CxxTest::TestSuite
{
public:
	WatermarkUTest() {
		logger().set_print_to_stdout_flag(true);
		logger().set_level(Logger::DEBUG);
	}

	void test_queue_set_watermarks() {
		concurrent_queue<int> queue;
		queue.set_watermarks(100, 75);
		// Watermarks are set, no exception thrown
		TS_ASSERT(true);
	}

	void test_queue_is_full_empty() {
		concurrent_queue<int> queue;
		queue.set_watermarks(5, 3);
		TS_ASSERT(not queue.is_full());
	}

	void test_queue_is_full_at_watermark() {
		concurrent_queue<int> queue;
		queue.set_watermarks(5, 3);

		// Push items up to high watermark
		for (int i = 0; i < 5; i++) {
			queue.push(i);
		}

		TS_ASSERT(queue.is_full());
	}

	void test_queue_blocking_behavior() {
		concurrent_queue<int> queue;
		queue.set_watermarks(5, 3);

		// Fill to high watermark
		for (int i = 0; i < 5; i++) {
			queue.push(i);
		}

		atomic<bool> push_started(false);
		atomic<bool> push_completed(false);

		// Start thread that will block on push
		thread pusher([&]() {
			push_started = true;
			queue.push(99);  // This should block
			push_completed = true;
		});

		// Give pusher time to block
		this_thread::sleep_for(chrono::milliseconds(100));

		TS_ASSERT(push_started);
		TS_ASSERT(not push_completed);  // Should still be blocked
		TS_ASSERT(queue.is_full());

		// Pop items to below low watermark
		int value;
		queue.pop(value);
		queue.pop(value);
		queue.pop(value);

		// Wait for pusher to complete
		pusher.join();

		TS_ASSERT(push_completed);  // Should have unblocked
	}

	void test_stack_blocking_behavior() {
		concurrent_stack<int> stack;
		stack.set_watermarks(5, 3);

		// Fill to high watermark
		for (int i = 0; i < 5; i++) {
			stack.push(i);
		}

		atomic<bool> push_completed(false);

		// Start thread that will block on push
		thread pusher([&]() {
			stack.push(99);  // This should block
			push_completed = true;
		});

		// Give pusher time to block
		this_thread::sleep_for(chrono::milliseconds(100));

		TS_ASSERT(not push_completed);  // Should be blocked
		TS_ASSERT(stack.is_full());

		// Pop items to below low watermark
		int value;
		stack.pop(value);
		stack.pop(value);
		stack.pop(value);

		// Wait for pusher to complete
		pusher.join();

		TS_ASSERT(push_completed);  // Should have unblocked
	}

	void test_set_blocking_behavior() {
		concurrent_set<int> set;
		set.set_watermarks(5, 3);

		// Fill to high watermark with unique items
		for (int i = 0; i < 5; i++) {
			set.insert(i);
		}

		atomic<bool> insert_completed(false);

		// Start thread that will block on insert
		thread inserter([&]() {
			set.insert(99);  // This should block
			insert_completed = true;
		});

		// Give inserter time to block
		this_thread::sleep_for(chrono::milliseconds(100));

		TS_ASSERT(not insert_completed);  // Should be blocked
		TS_ASSERT(set.is_full());

		// Get items to below low watermark
		int value;
		set.get(value);
		set.get(value);
		set.get(value);

		// Wait for inserter to complete
		inserter.join();

		TS_ASSERT(insert_completed);  // Should have unblocked
	}

	void test_queue_multiple_blocked_pushers() {
		concurrent_queue<int> queue;
		queue.set_watermarks(3, 1);

		// Fill to high watermark
		for (int i = 0; i < 3; i++) {
			queue.push(i);
		}

		atomic<int> completed_pushers(0);

		// Start multiple threads that will block
		thread pusher1([&]() {
			queue.push(101);
			completed_pushers++;
		});

		thread pusher2([&]() {
			queue.push(102);
			completed_pushers++;
		});

		// Give pushers time to block
		this_thread::sleep_for(chrono::milliseconds(100));

		TS_ASSERT_EQUALS(completed_pushers.load(), 0);
		TS_ASSERT(queue.is_full());  // Should show full due to blocked pushers

		// Pop items to below low watermark
		int value;
		queue.pop(value);
		queue.pop(value);
		queue.pop(value);

		// Wait for pushers to complete
		pusher1.join();
		pusher2.join();

		TS_ASSERT_EQUALS(completed_pushers.load(), 2);
	}

	void test_queue_no_deadlock_on_cancel() {
		concurrent_queue<int> queue;
		queue.set_watermarks(3, 1);

		// Fill to high watermark
		for (int i = 0; i < 3; i++) {
			queue.push(i);
		}

		atomic<bool> caught_exception(false);

		// Start thread that will block
		thread pusher([&]() {
			try {
				queue.push(99);
			}
			catch (const concurrent_queue<int>::Canceled&) {
				caught_exception = true;
			}
		});

		// Give pusher time to block
		this_thread::sleep_for(chrono::milliseconds(100));

		// Cancel the queue
		queue.close();

		// Wait for pusher
		pusher.join();

		TS_ASSERT(caught_exception);
	}
};