<commit_before>/**
 *Licensed to the Apache Software Foundation (ASF) under one
 *or more contributor license agreements.  See the NOTICE file
 *distributed with this work for additional information
 *regarding copyright ownership.  The ASF licenses this file
 *to you under the Apache License, Version 2.0 (the
 *"License"); you may not use this file except in compliance
 *with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *Unless required by applicable law or agreed to in writing,
 *software distributed under the License is distributed on an
 *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 *specific language governing permissions and limitations
 *under the License.
 */
#include <thread>
#include <chrono>
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <zconf.h>
#include <string.h>
#include <map>
#include <future>

#include "celix_api.h"
#include "celix_framework_factory.h"
#include "celix_service_factory.h"
#include "service_tracker_private.h"


#include <CppUTest/TestHarness.h>
#include <CppUTest/CommandLineTestRunner.h>



TEST_GROUP(CelixBundleContextServicesTests) {
    framework_t* fw = nullptr;
    bundle_context_t *ctx = nullptr;
    properties_t *properties = nullptr;

    void setup() {
        properties = properties_create();
        properties_set(properties, "LOGHELPER_ENABLE_STDOUT_FALLBACK", "true");
        properties_set(properties, "org.osgi.framework.storage.clean", "onFirstInit");
        properties_set(properties, "org.osgi.framework.storage", ".cacheBundleContextTestFramework");

        fw = celix_frameworkFactory_createFramework(properties);
        ctx = framework_getContext(fw);
    }

    void teardown() {
        celix_frameworkFactory_destroyFramework(fw);
    }
};

TEST(CelixBundleContextServicesTests, registerService) {
    struct calc {
        int (*calc)(int);
    };

    const char *calcName = "calc";
    calc svc;
    svc.calc = [](int n) -> int {
        return n * 42;
    };

    long svcId = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId >= 0);
    celix_bundleContext_unregisterService(ctx, svcId);
};

TEST(CelixBundleContextServicesTests, incorrectUnregisterCalls) {
    celix_bundleContext_unregisterService(ctx, 1);
    celix_bundleContext_unregisterService(ctx, 2);
    celix_bundleContext_unregisterService(ctx, -1);
    celix_bundleContext_unregisterService(ctx, -2);
};

TEST(CelixBundleContextServicesTests, registerMultipleAndUseServices) {
    struct calc {
        int (*calc)(int);
    };

    const char *calcName = "calc";
    struct calc svc;
    svc.calc = [](int n) -> int {
        return n * 42;
    };

    long svcId1 = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId1 >= 0);

    long svcId2 = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId2 >= 0);

    long svcId3 = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId3 >= 0);

    auto use = [](void *handle, void *svc) {
        CHECK(svc != nullptr);
        int *total =  static_cast<int*>(handle);
        struct calc *calc = static_cast<struct calc*>(svc);
        int tmp = calc->calc(1);
        *total += tmp;
    };

    int total = 0;
    celix_bundleContext_useServices(ctx, "calc", &total, use);
    CHECK_EQUAL(42 * 3, total);


    celix_bundleContext_unregisterService(ctx, svcId3);
    total = 0;
    celix_bundleContext_useServices(ctx, "calc", &total, use);
    CHECK_EQUAL(42 * 2, total);

    total = 0;
    bool called = celix_bundleContext_useService(ctx, "calc", &total, use);
    CHECK(called);
    CHECK_EQUAL(42, total);

    celix_bundleContext_unregisterService(ctx, svcId1);
    celix_bundleContext_unregisterService(ctx, svcId2);
};

TEST(CelixBundleContextServicesTests, registerAndUseService) {
    struct calc {
        int (*calc)(int);
    };

    const char *calcName = "calc";
    struct calc svc;
    svc.calc = [](int n) -> int {
        return n * 42;
    };

    long svcId = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId >= 0);

    int result = 0;
    bool called = celix_bundleContext_useServiceWithId(ctx, svcId, calcName, &result, [](void *handle, void *svc) {
        CHECK(svc != nullptr);
        int *result =  static_cast<int*>(handle);
        struct calc *calc = static_cast<struct calc*>(svc);
        int tmp = calc->calc(2);
        *result = tmp;
    });
    CHECK(called);
    CHECK_EQUAL(84, result);

    result = 0;
    long nonExistingSvcId = 101;
    called = celix_bundleContext_useServiceWithId(ctx, nonExistingSvcId, calcName, &result, [](void *handle, void *svc) {
        int *result =  static_cast<int*>(handle);
        struct calc *calc = static_cast<struct calc*>(svc);
        int tmp = calc->calc(2);
        *result = tmp;
    });
    CHECK(!called);
    CHECK_EQUAL(0, result); //e.g. not called

    celix_bundleContext_unregisterService(ctx, svcId);
};

TEST(CelixBundleContextServicesTests, registerAndUseServiceWithTimeout) {
    struct calc {
        int (*calc)(int);
    };

    const char *calcName = "calc";
    struct calc svc;
    svc.calc = [](int n) -> int {
        return n * 42;
    };

    celix_service_use_options_t opts{};
    opts.filter.serviceName = "calc";

    bool called = celix_bundleContext_useServiceWithOptions(ctx, &opts);
    CHECK(!called); //service not avail.

    std::future<bool> result{std::async([&]{
        opts.waitTimeoutInSeconds = 5.0;
        bool calledAsync = celix_bundleContext_useServiceWithOptions(ctx, &opts);
        return calledAsync;
    })};

    long svcId = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId >= 0);


    CHECK(result.get()); //should return true after waiting for the registered service.


    celix_bundleContext_unregisterService(ctx, svcId);
}

TEST(CelixBundleContextServicesTests, registerAndUseWithForcedRaceCondition) {
    struct calc {
        int (*calc)(int);
    };

    const char *calcName = "calc";
    struct calc svc;
    svc.calc = [](int n) -> int {
        return n * 42;
    };

    long svcId = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId >= 0);

    struct sync {
        std::mutex mutex{};
        std::condition_variable sync{};
        bool inUseCall{false};
        bool readyToExitUseCall{false};
        bool unregister{false};
        int result{0};
    };
    struct sync callInfo{};<commit_msg>Adds a test to check if a timed delay use service call will timeout.<commit_after>/**
 *Licensed to the Apache Software Foundation (ASF) under one
 *or more contributor license agreements.  See the NOTICE file
 *distributed with this work for additional information
 *regarding copyright ownership.  The ASF licenses this file
 *to you under the Apache License, Version 2.0 (the
 *"License"); you may not use this file except in compliance
 *with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *Unless required by applicable law or agreed to in writing,
 *software distributed under the License is distributed on an
 *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 *specific language governing permissions and limitations
 *under the License.
 */
#include <thread>
#include <chrono>
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <zconf.h>
#include <string.h>
#include <map>
#include <future>

#include "celix_api.h"
#include "celix_framework_factory.h"
#include "celix_service_factory.h"
#include "service_tracker_private.h"


#include <CppUTest/TestHarness.h>
#include <CppUTest/CommandLineTestRunner.h>



TEST_GROUP(CelixBundleContextServicesTests) {
    framework_t* fw = nullptr;
    bundle_context_t *ctx = nullptr;
    properties_t *properties = nullptr;

    void setup() {
        properties = properties_create();
        properties_set(properties, "LOGHELPER_ENABLE_STDOUT_FALLBACK", "true");
        properties_set(properties, "org.osgi.framework.storage.clean", "onFirstInit");
        properties_set(properties, "org.osgi.framework.storage", ".cacheBundleContextTestFramework");

        fw = celix_frameworkFactory_createFramework(properties);
        ctx = framework_getContext(fw);
    }

    void teardown() {
        celix_frameworkFactory_destroyFramework(fw);
    }
};

TEST(CelixBundleContextServicesTests, registerService) {
    struct calc {
        int (*calc)(int);
    };

    const char *calcName = "calc";
    calc svc;
    svc.calc = [](int n) -> int {
        return n * 42;
    };

    long svcId = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId >= 0);
    celix_bundleContext_unregisterService(ctx, svcId);
};

TEST(CelixBundleContextServicesTests, incorrectUnregisterCalls) {
    celix_bundleContext_unregisterService(ctx, 1);
    celix_bundleContext_unregisterService(ctx, 2);
    celix_bundleContext_unregisterService(ctx, -1);
    celix_bundleContext_unregisterService(ctx, -2);
};

TEST(CelixBundleContextServicesTests, registerMultipleAndUseServices) {
    struct calc {
        int (*calc)(int);
    };

    const char *calcName = "calc";
    struct calc svc;
    svc.calc = [](int n) -> int {
        return n * 42;
    };

    long svcId1 = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId1 >= 0);

    long svcId2 = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId2 >= 0);

    long svcId3 = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId3 >= 0);

    auto use = [](void *handle, void *svc) {
        CHECK(svc != nullptr);
        int *total =  static_cast<int*>(handle);
        struct calc *calc = static_cast<struct calc*>(svc);
        int tmp = calc->calc(1);
        *total += tmp;
    };

    int total = 0;
    celix_bundleContext_useServices(ctx, "calc", &total, use);
    CHECK_EQUAL(42 * 3, total);


    celix_bundleContext_unregisterService(ctx, svcId3);
    total = 0;
    celix_bundleContext_useServices(ctx, "calc", &total, use);
    CHECK_EQUAL(42 * 2, total);

    total = 0;
    bool called = celix_bundleContext_useService(ctx, "calc", &total, use);
    CHECK(called);
    CHECK_EQUAL(42, total);

    celix_bundleContext_unregisterService(ctx, svcId1);
    celix_bundleContext_unregisterService(ctx, svcId2);
};

TEST(CelixBundleContextServicesTests, registerAndUseService) {
    struct calc {
        int (*calc)(int);
    };

    const char *calcName = "calc";
    struct calc svc;
    svc.calc = [](int n) -> int {
        return n * 42;
    };

    long svcId = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId >= 0);

    int result = 0;
    bool called = celix_bundleContext_useServiceWithId(ctx, svcId, calcName, &result, [](void *handle, void *svc) {
        CHECK(svc != nullptr);
        int *result =  static_cast<int*>(handle);
        struct calc *calc = static_cast<struct calc*>(svc);
        int tmp = calc->calc(2);
        *result = tmp;
    });
    CHECK(called);
    CHECK_EQUAL(84, result);

    result = 0;
    long nonExistingSvcId = 101;
    called = celix_bundleContext_useServiceWithId(ctx, nonExistingSvcId, calcName, &result, [](void *handle, void *svc) {
        int *result =  static_cast<int*>(handle);
        struct calc *calc = static_cast<struct calc*>(svc);
        int tmp = calc->calc(2);
        *result = tmp;
    });
    CHECK(!called);
    CHECK_EQUAL(0, result); //e.g. not called

    celix_bundleContext_unregisterService(ctx, svcId);
};

TEST(CelixBundleContextServicesTests, registerAndUseServiceWithTimeout) {
    struct calc {
        int (*calc)(int);
    };

    const char *calcName = "calc";
    struct calc svc;
    svc.calc = [](int n) -> int {
        return n * 42;
    };

    celix_service_use_options_t opts{};
    opts.filter.serviceName = "calc";

    bool called = celix_bundleContext_useServiceWithOptions(ctx, &opts);
    CHECK(!called); //service not avail.

    std::future<bool> result{std::async([&]{
        opts.waitTimeoutInSeconds = 5.0;
        printf("Trying to call calc with timeout of %f\n", opts.waitTimeoutInSeconds);
        bool calledAsync = celix_bundleContext_useServiceWithOptions(ctx, &opts);
        printf("returned from use service with timeout. calc called %s.\n", calledAsync ? "true" : "false");
        return calledAsync;
    })};
    long svcId = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId >= 0);


    CHECK(result.get()); //should return true after waiting for the registered service.


    celix_bundleContext_unregisterService(ctx, svcId);


    //check if timeout is triggered
    std::future<bool> result2{std::async([&]{
        opts.waitTimeoutInSeconds = 0.5;
        printf("Trying to call calc with timeout of %f\n", opts.waitTimeoutInSeconds);
        bool calledAsync = celix_bundleContext_useServiceWithOptions(ctx, &opts);
        printf("returned from use service with timeout. calc called %s.\n", calledAsync ? "true" : "false");
        return calledAsync;
    })};
    CHECK(!result2.get());
}

TEST(CelixBundleContextServicesTests, registerAndUseWithForcedRaceCondition) {
    struct calc {
        int (*calc)(int);
    };

    const char *calcName = "calc";
    struct calc svc;
    svc.calc = [](int n) -> int {
        return n * 42;
    };

    long svcId = celix_bundleContext_registerService(ctx, &svc, calcName, nullptr);
    CHECK(svcId >= 0);

    struct sync {
        std::mutex mutex{};
        std::condition_variable sync{};
        bool inUseCall{false};
        bool readyToExitUseCall{false};
        bool unregister{false};
        int result{0};
    };
    struct sync callInfo{};
{'fraction_non_alphanumeric': 0.11768488745980707, 'fraction_numerical': 0.00871739907109682, 'mean_word_length': 3.5235940530058176, 'pattern_counts': {'":': 0, '<': 40, '<?xml version=': 0, '>': 70, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3429296', 'n_tokens_mistral': 4467, 'n_tokens_neox': 4325, 'n_words': 1098}