OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-cogutil / tests / util / sigslotUTest.cxxtest

258 lines · cxxtest ref:
/** sigslotUTest.cxxtest ---
 *
 * Copyright (C) 2025 OpenCog Foundation
 *
 * 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/sigslot.h>
#include <opencog/util/Logger.h>

#include <vector>
#include <string>

using namespace opencog;
using namespace std;

class sigslotUTest : public CxxTest::TestSuite
{
private:
    // Test counters for verifying signals were received
    int counter;
    int last_value;
    vector<int> received_values;
    string last_string;
    
    // Test class for member function connections
    class TestReceiver {
    public:
        int call_count;
        int last_x;
        vector<int> last_vec;
        
        TestReceiver() : call_count(0), last_x(0) {}
        
        void on_signal(int x, vector<int> y) {
            call_count++;
            last_x = x;
            last_vec = y;
        }
        
        void simple_handler(int x) {
            call_count++;
            last_x = x;
        }
        
        void no_args() {
            call_count++;
        }
    };

public:
    sigslotUTest() {
        logger().set_print_to_stdout_flag(true);
        logger().set_level(Logger::INFO);
    }

    void setUp() {
        counter = 0;
        last_value = 0;
        received_values.clear();
        last_string.clear();
    }

    // Test basic signal-slot with function
    void test_basic_connect() {
        SigSlot<int> sig;
        
        auto handler = [this](int x) {
            counter++;
            last_value = x;
        };
        
        int id = sig.connect(handler);
        TS_ASSERT(id > 0);
        
        sig.emit(42);
        TS_ASSERT_EQUALS(counter, 1);
        TS_ASSERT_EQUALS(last_value, 42);
        
        sig.emit(99);
        TS_ASSERT_EQUALS(counter, 2);
        TS_ASSERT_EQUALS(last_value, 99);
    }

    // Test multiple slots connected to same signal
    void test_multiple_connections() {
        SigSlot<int> sig;
        
        auto handler1 = [this](int x) {
            counter++;
            received_values.push_back(x);
        };
        
        auto handler2 = [this](int x) {
            counter++;
            last_value = x * 2;
        };
        
        sig.connect(handler1);
        sig.connect(handler2);
        
        sig.emit(10);
        TS_ASSERT_EQUALS(counter, 2);
        TS_ASSERT_EQUALS(received_values.size(), 1);
        TS_ASSERT_EQUALS(received_values[0], 10);
        TS_ASSERT_EQUALS(last_value, 20);
    }

    // Test disconnect
    void test_disconnect() {
        SigSlot<int> sig;
        
        auto handler = [this](int x) {
            counter++;
            last_value = x;
        };
        
        int id = sig.connect(handler);
        sig.emit(5);
        TS_ASSERT_EQUALS(counter, 1);
        
        sig.disconnect(id);
        sig.emit(10);
        TS_ASSERT_EQUALS(counter, 1); // Should still be 1
        TS_ASSERT_EQUALS(last_value, 5); // Should still be 5
    }

    // Test disconnect_all
    void test_disconnect_all() {
        SigSlot<int> sig;
        
        sig.connect([this](int x) { counter++; });
        sig.connect([this](int x) { counter++; });
        sig.connect([this](int x) { counter++; });
        
        sig.emit(1);
        TS_ASSERT_EQUALS(counter, 3);
        
        sig.disconnect_all();
        sig.emit(2);
        TS_ASSERT_EQUALS(counter, 3); // Should still be 3
    }

    // Test member function connection
    void test_member_function_connect() {
        TestReceiver receiver;
        SigSlot<int, vector<int>> sig;
        
        int id = sig.connect(&TestReceiver::on_signal, &receiver);
        TS_ASSERT(id > 0);
        
        vector<int> test_vec = {1, 2, 3};
        sig.emit(42, test_vec);
        
        TS_ASSERT_EQUALS(receiver.call_count, 1);
        TS_ASSERT_EQUALS(receiver.last_x, 42);
        TS_ASSERT_EQUALS(receiver.last_vec.size(), 3);
        TS_ASSERT_EQUALS(receiver.last_vec[0], 1);
        TS_ASSERT_EQUALS(receiver.last_vec[1], 2);
        TS_ASSERT_EQUALS(receiver.last_vec[2], 3);
    }

    // Test multiple types of arguments
    void test_multiple_arg_types() {
        SigSlot<int, string, float> sig;
        
        int call_count = 0;
        int recv_int = 0;
        string recv_str;
        float recv_float = 0.0f;
        
        sig.connect([&](int i, string s, float f) {
            call_count++;
            recv_int = i;
            recv_str = s;
            recv_float = f;
        });
        
        sig.emit(123, "hello", 3.14f);
        
        TS_ASSERT_EQUALS(call_count, 1);
        TS_ASSERT_EQUALS(recv_int, 123);
        TS_ASSERT_EQUALS(recv_str, "hello");
        TS_ASSERT_DELTA(recv_float, 3.14f, 0.001);
    }

    // Test no-argument signal
    void test_no_args() {
        TestReceiver receiver;
        SigSlot<> sig;
        
        sig.connect(&TestReceiver::no_args, &receiver);
        
        sig.emit();
        TS_ASSERT_EQUALS(receiver.call_count, 1);
        
        sig.emit();
        TS_ASSERT_EQUALS(receiver.call_count, 2);
    }

    // Test that disconnecting invalid ID doesn't crash
    void test_disconnect_invalid_id() {
        SigSlot<int> sig;
        
        // Should not crash or throw
        sig.disconnect(9999);
        sig.disconnect(-1);
        sig.disconnect(0);
        
        // Add a real connection and verify it still works
        auto handler = [this](int x) { counter++; };
        sig.connect(handler);
        sig.emit(1);
        TS_ASSERT_EQUALS(counter, 1);
    }

    // Test emit with no connections (should not crash)
    void test_emit_no_connections() {
        SigSlot<int> sig;
        sig.emit(42); // Should not crash
        
        SigSlot<int, string, vector<float>> complex_sig;
        complex_sig.emit(1, "test", {1.0f, 2.0f}); // Should not crash
    }

    // Test size() method
    void test_size() {
        SigSlot<int> sig;
        TS_ASSERT_EQUALS(sig.size(), 0);
        
        int id1 = sig.connect([](int x) {});
        TS_ASSERT_EQUALS(sig.size(), 1);
        
        int id2 = sig.connect([](int x) {});
        TS_ASSERT_EQUALS(sig.size(), 2);
        TS_ASSERT_DIFFERS(id1, id2);
        
        sig.disconnect(id1);
        TS_ASSERT_EQUALS(sig.size(), 1);
        
        sig.disconnect_all();
        TS_ASSERT_EQUALS(sig.size(), 0);
    }
};