clientExample: proper clean-up in case of subsession errors 95/277895/21
authorAdam Michalski <a.michalski2@partner.samsung.com>
Thu, 14 Jul 2022 14:59:05 +0000 (16:59 +0200)
committerAdam Michalski <a.michalski2@partner.samsung.com>
Thu, 21 Jul 2022 15:02:48 +0000 (17:02 +0200)
Change-Id: I57682b4e7b4649af55435c70186a0a97956e10bf

clientExample/app/main.cpp

index 69c1cb9..97b9428 100644 (file)
@@ -2,6 +2,7 @@
 #include <iostream>
 #include <gio/gio.h>
 #include <cassert>
+#include <memory>
 #include <stdlib.h>
 #include "sessiond.h"
 
@@ -70,9 +71,17 @@ gboolean callback_pending(gpointer data)
        if (is_pending == TRUE)
                return TRUE;
 
-       // N.B. There are `noOfUsers` add and remove operations. Each add/remove
-       // increments the value of the `callback_pending_reference` variable.
-       if ((unsigned)ctrl_value >= usernames.size() * 2) {
+       /*
+        * N.B. There are `usernames.size()` add, remove and switch operations.
+        * Each add/remove/switch user increments the value of the
+        * `callback_pending_reference` variable. The last `+3`
+        * is
+        * for the three extra switches:
+        * - setting user to the initial one before starting the switch user test
+        * - setting user to the initial one before removing users
+        * - setting user to the beginning one at the end of tests.
+        */
+       if ((unsigned)ctrl_value >= usernames.size() * 3 + 3) {
                g_main_loop_quit(loop);
                g_main_loop_unref(loop);
        }
@@ -125,6 +134,8 @@ void test_reply_switchuser_callback(int result, void *cb_data)
 {
        g_mutex_lock(&mutex);
 
+       g_atomic_int_inc(&callback_pending_reference);
+
        test_user_data *user_data = (test_user_data *)cb_data;
        user_data->callback_result = result;
 
@@ -212,6 +223,86 @@ void wait_for_all_pending_callbacks(GMainLoop *loop, GThread *loop_thread)
        g_thread_join(loop_thread);
 }
 
+int get_user_list(subsession_user_t **userlist = NULL)
+{
+       int count;
+
+       if (int r = subsession_get_user_list(SESSION_UID, userlist, &count); r != SUBSESSION_ERROR_NONE) {
+               printf("Error getting initial user list: %d\n", r);
+               return -1;
+       }
+
+       return count;
+}
+
+bool remove_all_test_users(bool ignore_errors = false)
+{
+       bool all_removed = true;
+
+       for (auto user : usernames) {
+               test_user_data test_remove_ud;
+               test_remove_ud.callback_result = -1;
+               int remove_user_res = subsession_remove_user(SESSION_UID, user,
+                       test_reply_removeuser_callback, (void *)&test_remove_ud);
+               if (remove_user_res != SUBSESSION_ERROR_NONE && !ignore_errors) {
+                       printf("removing user %s failed code: %d\n", user, remove_user_res);
+                       all_removed = false;
+               }
+       }
+
+       return all_removed;
+}
+
+bool check_user_name_collisions(subsession_user_t *userlist, int number_of_users)
+{
+       for (int i = 0; i < number_of_users; ++i)
+               for (auto user : usernames)
+                       if (strncmp(userlist[i], user, SUBSESSION_USER_MAXLEN) == 0)
+                               return true;
+
+       return false;
+}
+
+void final_cleanup()
+{
+       g_mutex_clear(&mutex);
+       g_cond_clear(&cond);
+}
+
+bool switch_to_first_chosen_user(subsession_user_t first_user)
+{
+       test_user_data test_switch_ud;
+       test_switch_ud.callback_result = CALLBACK_DATA_MAGIC;
+
+       int switch_user_res = subsession_switch_user(SESSION_UID, first_user,
+               test_reply_switchuser_callback, (void *)&test_switch_ud);
+       if (switch_user_res != 0) {
+               printf("Error subsession_switch_user res is%d\n", switch_user_res);
+               return false;
+       }
+
+       return true;
+}
+
+void cleanup_after_failure(subsession_user_t first_user)
+{
+       switch_to_first_chosen_user(first_user);
+       remove_all_test_users(true);
+       final_cleanup();
+}
+
+template <typename F> struct generic_destructor {
+       F f;
+       bool finalize;
+
+       generic_destructor(F _f) : f(std::move(_f)), finalize(true) {}
+       ~generic_destructor() { if (finalize) f(); };
+};
+
+struct subsession_user_deleter {
+       void operator() (subsession_user_t *p) { free(p); }
+};
+
 int main(int argc, char *argv[])
 {
        g_cond_init(&cond);
@@ -221,8 +312,39 @@ int main(int argc, char *argv[])
        GThread *loop_thread = g_thread_new("mainloop thread", (GThreadFunc) g_main_loop_run, loop);
 
        ///===================================///
-       printf("Test program start\nCreating test users...");
+       printf("Test program start\nGetting initial user list... ");
+
+       /*
+        * First get the initial user list. We're doing this for two reasons:
+        * 1) to ensure proper calculations taking into account previously registered users
+        * 2) to ensure no name collisions are taking place
+        *
+        */
+       subsession_user_t *initial_userlist_raw;
+       int initial_number_of_users = get_user_list(&initial_userlist_raw);
+       if (initial_number_of_users == -1)
+               return EXIT_FAILURE;
+       std::unique_ptr<subsession_user_t[], subsession_user_deleter> initial_userlist { initial_userlist_raw };
+
+       if (check_user_name_collisions(initial_userlist.get(), initial_number_of_users)) {
+               printf("User name collisions detected. Test interrupted. Consider manual clean-up before re-running.\n");
+               // N.B. Since we've detected name collisions, we deliberately choose not to remove any users.
+               final_cleanup();
+               return EXIT_FAILURE;
+       }
+       green_print("done");
 
+       subsession_user_t first_user;
+       int r = subsession_get_current_user(SESSION_UID, first_user);
+       if (r != SUBSESSION_ERROR_NONE) {
+               printf("Error: subsession_get_current_user result is %d\n", r);
+               final_cleanup();
+               return EXIT_FAILURE;
+       }
+
+       generic_destructor failure_finalizer ([&]() { cleanup_after_failure(first_user); } );
+
+       printf("Creating test users... ");
        for (size_t i = 0; i < usernames.size(); ++i)
        {
                g_mutex_lock(&mutex);
@@ -245,32 +367,44 @@ int main(int argc, char *argv[])
        }
        green_print("done");
 
-       int registered_users;
-       subsession_user_t *userlist;
-       ///===================================///
-       int r = subsession_get_user_list(SESSION_UID, &userlist, &registered_users);
-       if (r != SUBSESSION_ERROR_NONE) {
-               printf("Error getting user list: %d\n", r);
+       printf("Setting user to the initial one... ");
+       if (!switch_user_test(SUBSESSION_INITIAL_SID)) {
+               printf("Error setting user to starting\n");
                return EXIT_FAILURE;
        }
+       green_print("done");
+
+       subsession_user_t *userlist_raw;
+       int registered_users = get_user_list(&userlist_raw);
+       if (registered_users == -1)
+               return EXIT_FAILURE;
+       std::unique_ptr<subsession_user_t[], subsession_user_deleter> userlist { userlist_raw };
 
-       printf("No of users [%zu]...", usernames.size());
-       if (usernames.size() == (unsigned) (registered_users-1))
+       printf("No of test users [%zu]... ", usernames.size());
+       if (usernames.size() + initial_number_of_users == (unsigned) (registered_users))
                green_print("ok");
        else
        {
-               printf("Failed to register some users (%zu expected, got %d)\n", usernames.size(), registered_users);
+               printf("Failed to register some users: %zu expected (%d initial + %zu ours), got %d"
+                       , usernames.size() + initial_number_of_users
+                       , initial_number_of_users
+                       , usernames.size()
+                       , registered_users
+               );
                return EXIT_FAILURE;
+
        }
 
        /* NB: the order of users as returned by get_user_list is unspecified
         * and does not need to match the one in the original array above. */
-       for (int i = 0; i < registered_users; ++i)
-               printf("%s\n", userlist[i]);
+       for (int i = 0; i < registered_users; ++i) {
+               for (auto user : usernames)
+                       if (strncmp(userlist[i], user, SUBSESSION_USER_MAXLEN) == 0)
+                               printf("%s\n", userlist[i]);
+       }
 
        ///===================================///
-       printf("Switching users test...");
-
+       printf("Switching users test... ");
        callbackCount = 0;
 
        test_user_data_cb_t data;
@@ -282,11 +416,11 @@ int main(int argc, char *argv[])
        }
 
        if (!switch_to_each_user_to_generate_events()
-       ||  !check_callbacks_called((registered_users-1)))
+       ||  !check_callbacks_called(usernames.size()))
                return EXIT_FAILURE;
 
        ///===================================///
-       printf("Subsession unregister event callback test...");
+       printf("Subsession unregister event callback test... ");
 
        callbackCount = 0;
        r = subsession_unregister_event_callback(SESSION_UID, SUBSESSION_SWITCH_USER_COMPLETION);
@@ -300,28 +434,25 @@ int main(int argc, char *argv[])
                return EXIT_FAILURE;
 
        ///======================================///
-       printf("Removing users...");
+       printf("Removing users... ");
        if (!switch_user_test(SUBSESSION_INITIAL_SID)) {
                printf("Error setting user to starting\n");
                return EXIT_FAILURE;
        }
-       for (auto user : usernames) {
-               test_user_data test_remove_ud;
-               test_remove_ud.callback_result = -1;
-               int remove_user_res = subsession_remove_user(SESSION_UID, user,
-                       test_reply_removeuser_callback, (void *)&test_remove_ud);
-               if (remove_user_res != SUBSESSION_ERROR_NONE)
-               {
-                       printf("removing user %s failed code: %d\n", user, remove_user_res);
-                       return EXIT_FAILURE;
-               }
-       }
+
+       failure_finalizer.finalize = false;
+
+       if (!remove_all_test_users())
+               return EXIT_FAILURE;
+
        green_print("done");
 
+       if (!switch_to_first_chosen_user(first_user))
+               return EXIT_FAILURE;
+
        wait_for_all_pending_callbacks(loop, loop_thread);
 
-       g_mutex_clear(&mutex);
-       g_cond_clear(&cond);
+       final_cleanup();
 
        printf("Test program end\n");
        return EXIT_SUCCESS;