LCOV - code coverage report
Current view: top level - libmogwai-schedule/tests - clock-dummy.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 100 125 80.0 %
Date: 2022-06-30 20:59:16 Functions: 18 22 81.8 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 43 74 58.1 %

           Branch data     Line data    Source code
       1                 :            : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
       2                 :            :  *
       3                 :            :  * Copyright © 2018 Endless Mobile, Inc.
       4                 :            :  *
       5                 :            :  * This library is free software; you can redistribute it and/or
       6                 :            :  * modify it under the terms of the GNU Lesser General Public
       7                 :            :  * License as published by the Free Software Foundation; either
       8                 :            :  * version 2.1 of the License, or (at your option) any later version.
       9                 :            :  *
      10                 :            :  * This library is distributed in the hope that it will be useful,
      11                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      12                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13                 :            :  * Lesser General Public License for more details.
      14                 :            :  *
      15                 :            :  * You should have received a copy of the GNU Lesser General Public
      16                 :            :  * License along with this library; if not, write to the Free Software
      17                 :            :  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
      18                 :            :  *
      19                 :            :  * Authors:
      20                 :            :  *  - Philip Withnall <withnall@endlessm.com>
      21                 :            :  */
      22                 :            : 
      23                 :            : #include "config.h"
      24                 :            : 
      25                 :            : #include <glib.h>
      26                 :            : #include <glib-object.h>
      27                 :            : #include <glib/gi18n-lib.h>
      28                 :            : #include <gio/gio.h>
      29                 :            : #include <libmogwai-schedule/clock.h>
      30                 :            : #include <libmogwai-schedule/tests/clock-dummy.h>
      31                 :            : 
      32                 :            : 
      33                 :            : static void mws_clock_dummy_clock_init (MwsClockInterface *iface);
      34                 :            : 
      35                 :            : static void mws_clock_dummy_finalize (GObject *obj);
      36                 :            : 
      37                 :            : static GDateTime *mws_clock_dummy_get_now_local (MwsClock       *clock);
      38                 :            : static guint      mws_clock_dummy_add_alarm     (MwsClock       *clock,
      39                 :            :                                                  GDateTime      *alarm_time,
      40                 :            :                                                  GSourceFunc     alarm_func,
      41                 :            :                                                  gpointer        user_data,
      42                 :            :                                                  GDestroyNotify  destroy_func);
      43                 :            : static void       mws_clock_dummy_remove_alarm  (MwsClock       *clock,
      44                 :            :                                                  guint           id);
      45                 :            : 
      46                 :            : typedef struct
      47                 :            : {
      48                 :            :   GDateTime *alarm_time;  /* (owned) */
      49                 :            :   GSourceFunc alarm_func;  /* (nullable) */
      50                 :            :   gpointer user_data;  /* (nullable) */
      51                 :            :   GDestroyNotify destroy_func;  /* (nullable) */
      52                 :            : } AlarmData;
      53                 :            : 
      54                 :            : static void
      55                 :         91 : alarm_data_free (AlarmData *data)
      56                 :            : {
      57                 :            :   /* FIXME: In order to be able to steal elements from the alarms array, we need
      58                 :            :    * to gracefully ignore NULL entries. */
      59         [ +  + ]:         91 :   if (data == NULL)
      60                 :         11 :     return;
      61                 :            : 
      62         [ +  - ]:         80 :   g_clear_pointer (&data->alarm_time, g_date_time_unref);
      63   [ -  +  -  - ]:         80 :   if (data->destroy_func != NULL && data->user_data != NULL)
      64                 :          0 :     data->destroy_func (data->user_data);
      65                 :         80 :   g_free (data);
      66                 :            : }
      67                 :            : 
      68         [ +  + ]:        246 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (AlarmData, alarm_data_free)
      69                 :            : 
      70                 :            : static gint
      71                 :        166 : alarm_data_compare (const AlarmData *a,
      72                 :            :                     const AlarmData *b)
      73                 :            : {
      74         [ +  + ]:        166 :   if (!g_date_time_equal (a->alarm_time, b->alarm_time))
      75                 :         21 :     return g_date_time_compare (a->alarm_time, b->alarm_time);
      76                 :            : 
      77                 :            :   /* Tie break using the pointer values, so the sort is stable. */
      78         [ -  + ]:        145 :   if (a == b)
      79                 :          0 :     return 0;
      80         [ +  + ]:        145 :   else if (a < b)
      81                 :         98 :     return -1;
      82                 :            :   else
      83                 :         47 :     return 1;
      84                 :            : }
      85                 :            : 
      86                 :            : static void
      87                 :         11 : alarm_data_invoke (const AlarmData *data)
      88                 :            : {
      89         [ +  - ]:         11 :   if (data->alarm_func != NULL)
      90                 :         11 :     data->alarm_func (data->user_data);
      91                 :         11 : }
      92                 :            : 
      93                 :            : /**
      94                 :            :  * MwsClockDummy:
      95                 :            :  *
      96                 :            :  * An implementation of the #MwsClock interface which is not tied to any
      97                 :            :  * real-world clock. Its time transitions are entirely programmatically driven
      98                 :            :  * by calling mws_clock_dummy_set_time() and mws_clock_dummy_set_time_zone(),
      99                 :            :  * which will typically be done by a test harness. Its internal clock will not
     100                 :            :  * progress automatically at all.
     101                 :            :  *
     102                 :            :  * This is intended for testing code which uses the #MwsClock interface.
     103                 :            :  *
     104                 :            :  * The time in the dummy clock may move forwards or backwards and its timezone
     105                 :            :  * can be changed (which results in the #MwsClock::offset-changed signal being
     106                 :            :  * emitted).
     107                 :            :  *
     108                 :            :  * The clock starts at 2000-01-01T00:00:00Z.
     109                 :            :  *
     110                 :            :  * Since: 0.1.0
     111                 :            :  */
     112                 :            : struct _MwsClockDummy
     113                 :            : {
     114                 :            :   GObject parent;
     115                 :            : 
     116                 :            :   /* Main context from construction time. */
     117                 :            :   GMainContext *context;  /* (owned) */
     118                 :            :   GSource *add_alarm_source;  /* (owned) (nullable) */
     119                 :            : 
     120                 :            :   /* The current time. This only changes when the test harness explicitly
     121                 :            :    * advances it. It’s always maintained in @tz. */
     122                 :            :   GDateTime *now;  /* (owned) */
     123                 :            :   GTimeZone *tz;  /* (owned) */
     124                 :            : 
     125                 :            :   /* Array of pending alarms, sorted by increasing date/time. */
     126                 :            :   GPtrArray *alarms;  /* (owned) (element-type AlarmData) */
     127                 :            : };
     128                 :            : 
     129   [ +  +  +  -  :        551 : G_DEFINE_TYPE_WITH_CODE (MwsClockDummy, mws_clock_dummy, G_TYPE_OBJECT,
                   +  + ]
     130                 :            :                          G_IMPLEMENT_INTERFACE (MWS_TYPE_CLOCK,
     131                 :            :                                                 mws_clock_dummy_clock_init))
     132                 :            : static void
     133                 :          2 : mws_clock_dummy_class_init (MwsClockDummyClass *klass)
     134                 :            : {
     135                 :          2 :   GObjectClass *object_class = G_OBJECT_CLASS (klass);
     136                 :            : 
     137                 :          2 :   object_class->finalize = mws_clock_dummy_finalize;
     138                 :          2 : }
     139                 :            : 
     140                 :            : static void
     141                 :          2 : mws_clock_dummy_clock_init (MwsClockInterface *iface)
     142                 :            : {
     143                 :          2 :   iface->get_now_local = mws_clock_dummy_get_now_local;
     144                 :          2 :   iface->add_alarm = mws_clock_dummy_add_alarm;
     145                 :          2 :   iface->remove_alarm = mws_clock_dummy_remove_alarm;
     146                 :          2 : }
     147                 :            : 
     148                 :            : static void
     149                 :         48 : mws_clock_dummy_init (MwsClockDummy *self)
     150                 :            : {
     151                 :            :   /* Start at the year 2000 UTC, so things are reproducible. We can’t start at
     152                 :            :    * the year 1, as then the initial timezone transition might not be possible. */
     153                 :         48 :   self->tz = g_time_zone_new_utc ();
     154                 :         48 :   self->now = g_date_time_new (self->tz, 2000, 1, 1, 0, 0, 0);
     155         [ -  + ]:         48 :   g_assert (self->now != NULL);
     156                 :            : 
     157                 :         48 :   self->context = g_main_context_ref_thread_default ();
     158                 :            : 
     159                 :         48 :   self->alarms = g_ptr_array_new_with_free_func ((GDestroyNotify) alarm_data_free);
     160                 :         48 : }
     161                 :            : 
     162                 :            : static void
     163                 :         47 : mws_clock_dummy_finalize (GObject *obj)
     164                 :            : {
     165                 :         47 :   MwsClockDummy *self = MWS_CLOCK_DUMMY (obj);
     166                 :            : 
     167         [ +  - ]:         47 :   g_clear_pointer (&self->alarms, g_ptr_array_unref);
     168         [ +  - ]:         47 :   g_clear_pointer (&self->tz, g_time_zone_unref);
     169         [ +  - ]:         47 :   g_clear_pointer (&self->now, g_date_time_unref);
     170                 :            : 
     171         [ +  + ]:         47 :   if (self->add_alarm_source != NULL)
     172                 :            :     {
     173                 :         17 :       g_source_destroy (self->add_alarm_source);
     174         [ +  - ]:         17 :       g_clear_pointer (&self->add_alarm_source, g_source_unref);
     175                 :            :     }
     176         [ +  - ]:         47 :   g_clear_pointer (&self->context, g_main_context_unref);
     177                 :            : 
     178                 :         47 :   G_OBJECT_CLASS (mws_clock_dummy_parent_class)->finalize (obj);
     179                 :         47 : }
     180                 :            : 
     181                 :            : static GDateTime *
     182                 :        168 : mws_clock_dummy_get_now_local (MwsClock *clock)
     183                 :            : {
     184                 :        168 :   MwsClockDummy *self = MWS_CLOCK_DUMMY (clock);
     185                 :        168 :   return g_date_time_ref (self->now);
     186                 :            : }
     187                 :            : 
     188                 :            : static gint
     189                 :        166 : alarm_data_compare_cb (gconstpointer a,
     190                 :            :                        gconstpointer b)
     191                 :            : {
     192                 :        166 :   return alarm_data_compare (*((const AlarmData **) a),
     193                 :            :                              *((const AlarmData **) b));
     194                 :            : }
     195                 :            : 
     196                 :            : static gboolean
     197                 :          0 : add_alarm_cb (gpointer user_data)
     198                 :            : {
     199                 :          0 :   MwsClockDummy *self = MWS_CLOCK_DUMMY (user_data);
     200                 :            : 
     201                 :          0 :   g_autoptr(GDateTime) now = mws_clock_dummy_get_now_local (MWS_CLOCK (self));
     202                 :          0 :   mws_clock_dummy_set_time (self, now);
     203         [ #  # ]:          0 :   g_clear_pointer (&self->add_alarm_source, g_source_unref);
     204                 :            : 
     205                 :          0 :   return G_SOURCE_REMOVE;
     206                 :            : }
     207                 :            : 
     208                 :            : static guint
     209                 :         80 : mws_clock_dummy_add_alarm (MwsClock       *clock,
     210                 :            :                            GDateTime      *alarm_time,
     211                 :            :                            GSourceFunc     alarm_func,
     212                 :            :                            gpointer        user_data,
     213                 :            :                            GDestroyNotify  destroy_func)
     214                 :            : {
     215                 :         80 :   MwsClockDummy *self = MWS_CLOCK_DUMMY (clock);
     216                 :            : 
     217                 :         80 :   g_autoptr(AlarmData) data = g_new0 (AlarmData, 1);
     218                 :         80 :   data->alarm_time = g_date_time_ref (alarm_time);
     219                 :         80 :   data->alarm_func = alarm_func;
     220                 :         80 :   data->user_data = user_data;
     221                 :         80 :   data->destroy_func = destroy_func;
     222                 :            : 
     223                 :         80 :   guint id = GPOINTER_TO_UINT (data);
     224                 :         80 :   g_ptr_array_add (self->alarms, g_steal_pointer (&data));
     225                 :         80 :   g_ptr_array_sort (self->alarms, alarm_data_compare_cb);
     226                 :            : 
     227                 :            :   /* Ensure the alarm is triggered if it’s in the past, but don’t trigger it
     228                 :            :    * immediately to avoid re-entrancy issues. */
     229         [ +  + ]:         80 :   if (self->add_alarm_source == NULL)
     230                 :            :     {
     231                 :         17 :       self->add_alarm_source = g_idle_source_new ();
     232                 :         17 :       g_source_set_callback (self->add_alarm_source, add_alarm_cb, self, NULL);
     233                 :         17 :       g_source_attach (self->add_alarm_source, self->context);
     234                 :            :     }
     235                 :            : 
     236                 :         80 :   return id;
     237                 :            : }
     238                 :            : 
     239                 :            : static void
     240                 :          0 : mws_clock_dummy_remove_alarm (MwsClock *clock,
     241                 :            :                               guint     id)
     242                 :            : {
     243                 :          0 :   MwsClockDummy *self = MWS_CLOCK_DUMMY (clock);
     244                 :          0 :   GSource *source = GUINT_TO_POINTER (id);
     245                 :            : 
     246                 :            :   /* The source will be destroyed by the free function set up on the array. */
     247         [ #  # ]:          0 :   g_return_if_fail (g_ptr_array_remove_fast (self->alarms, source));
     248                 :            : }
     249                 :            : 
     250                 :            : /**
     251                 :            :  * mws_clock_dummy_new:
     252                 :            :  *
     253                 :            :  * Create a #MwsClockDummy object which gets wall clock time from the dummy
     254                 :            :  * clock.
     255                 :            :  *
     256                 :            :  * Returns: (transfer full): a new #MwsClockDummy
     257                 :            :  * Since: 0.1.0
     258                 :            :  */
     259                 :            : MwsClockDummy *
     260                 :         48 : mws_clock_dummy_new (void)
     261                 :            : {
     262                 :         48 :   return g_object_new (MWS_TYPE_CLOCK_DUMMY, NULL);
     263                 :            : }
     264                 :            : 
     265                 :            : /**
     266                 :            :  * mws_clock_dummy_set_time:
     267                 :            :  * @self: a #MwsClockDummy
     268                 :            :  * @now: new time for the clock
     269                 :            :  *
     270                 :            :  * Set the clock to consider ‘now’ to be @now. This will result in any alarms
     271                 :            :  * whose trigger times are earlier than or equal to @now being triggered. Any
     272                 :            :  * new alarms added from alarm callbacks are also invoked if their trigger times
     273                 :            :  * meet the same criterion.
     274                 :            :  *
     275                 :            :  * The time zone for @now is not used to change the time zone for the clock;
     276                 :            :  * @now is converted to the current time zone in use by the clock. To change the
     277                 :            :  * clock’s time zone, call mws_clock_dummy_set_time_zone().
     278                 :            :  *
     279                 :            :  * Since: 0.1.0
     280                 :            :  */
     281                 :            : void
     282                 :         51 : mws_clock_dummy_set_time (MwsClockDummy *self,
     283                 :            :                           GDateTime     *now)
     284                 :            : {
     285         [ -  + ]:         51 :   g_return_if_fail (MWS_IS_CLOCK_DUMMY (self));
     286         [ -  + ]:         51 :   g_return_if_fail (now != NULL);
     287                 :            : 
     288                 :         51 :   g_autofree gchar *now_str = g_date_time_format (now, "%FT%T%:::z");
     289                 :         51 :   g_debug ("%s: Setting time to %s; %u alarms to check",
     290                 :            :            G_STRFUNC, now_str, self->alarms->len);
     291                 :            : 
     292                 :            :   /* Trigger all the alarms before the new @now (inclusive).
     293                 :            :    * Since other methods on #MwsClockDummy may be called by the user function
     294                 :            :    * in alarm_data_invoke(), which may modify the @self->alarms array, we need
     295                 :            :    * to be careful to re-read the array on each loop iteration, and to remove
     296                 :            :    * invoked alarms individually. */
     297         [ +  + ]:         62 :   while (self->alarms->len > 0)
     298                 :            :     {
     299         [ +  + ]:         43 :       g_autoptr(AlarmData) owned_data = NULL;
     300                 :         43 :       const AlarmData *data = g_ptr_array_index (self->alarms, 0);
     301                 :            : 
     302         [ +  + ]:         86 :       g_autofree gchar *alarm_time_str = g_date_time_format (data->alarm_time, "%FT%T%:::z");
     303                 :         43 :       g_debug ("%s: Comparing alarm %s to now %s",
     304                 :            :                G_STRFUNC, alarm_time_str, now_str);
     305                 :            : 
     306         [ +  + ]:         43 :       if (g_date_time_compare (data->alarm_time, now) > 0)
     307                 :         32 :         break;
     308                 :            : 
     309                 :            :       /* Steal the alarm from the array.
     310                 :            :        * FIXME: Use g_ptr_array_steal_index() when we can depend on a suitable GLib version. */
     311                 :         11 :       owned_data = g_steal_pointer (&self->alarms->pdata[0]);
     312                 :         11 :       g_ptr_array_remove_index (self->alarms, 0);
     313                 :            : 
     314                 :            :       /* Set the current time to what the alarm expects. */
     315                 :         11 :       g_date_time_unref (self->now);
     316                 :         11 :       self->now = g_date_time_to_timezone (owned_data->alarm_time, self->tz);
     317                 :            : 
     318                 :         11 :       alarm_data_invoke (owned_data);
     319                 :            :     }
     320                 :            : 
     321                 :            :   /* Convert the final time to our local timezone. */
     322                 :         51 :   g_date_time_unref (self->now);
     323                 :         51 :   self->now = g_date_time_to_timezone (now, self->tz);
     324                 :            : }
     325                 :            : 
     326                 :            : /**
     327                 :            :  * mws_clock_dummy_set_time_zone:
     328                 :            :  * @self: a #MwsClockDummy
     329                 :            :  * @tz: new time zone for the clock
     330                 :            :  *
     331                 :            :  * Set the clock’s time zone to @tz, and convert its current ‘now’ time to be
     332                 :            :  * the same instant as before, but in @tz.
     333                 :            :  *
     334                 :            :  * If the time zone has changed, this results in the #MwsClock::offset-changed
     335                 :            :  * signal being emitted.
     336                 :            :  *
     337                 :            :  * Since: 0.1.0
     338                 :            :  */
     339                 :            : void
     340                 :         51 : mws_clock_dummy_set_time_zone (MwsClockDummy *self,
     341                 :            :                                GTimeZone     *tz)
     342                 :            : {
     343         [ -  + ]:         81 :   g_return_if_fail (MWS_IS_CLOCK_DUMMY (self));
     344         [ -  + ]:         51 :   g_return_if_fail (tz != NULL);
     345                 :            : 
     346                 :            :   /* FIXME: Do we need a g_time_zone_equal() function to compare these? This
     347                 :            :    * is a terrible way of comparing them. */
     348         [ +  + ]:         51 :   if (tz == self->tz)
     349                 :         30 :     return;
     350                 :            : 
     351                 :         21 :   g_debug ("%s: Setting time zone to %s (%p)",
     352                 :            :            G_STRFUNC, g_time_zone_get_abbreviation (tz, 0), tz);
     353                 :            : 
     354         [ +  - ]:         42 :   g_autoptr(GDateTime) now_new_tz = g_date_time_to_timezone (self->now, tz);
     355         [ -  + ]:         21 :   g_return_if_fail (now_new_tz != NULL);
     356                 :         21 :   g_date_time_unref (self->now);
     357                 :         21 :   self->now = g_steal_pointer (&now_new_tz);
     358                 :            : 
     359                 :         21 :   g_time_zone_unref (self->tz);
     360                 :         21 :   self->tz = g_time_zone_ref (tz);
     361                 :            : 
     362                 :         21 :   g_signal_emit_by_name (self, "offset-changed");
     363                 :            : }
     364                 :            : 
     365                 :            : /**
     366                 :            :  * mws_clock_dummy_get_next_alarm_time:
     367                 :            :  * @self: a #MwsClockDummy
     368                 :            :  *
     369                 :            :  * Get the time when the next alarm will be triggered.
     370                 :            :  *
     371                 :            :  * Returns: time of the next alarm, or %NULL if there are currently no alarms
     372                 :            :  *    scheduled
     373                 :            :  * Since: 0.1.0
     374                 :            :  */
     375                 :            : GDateTime *
     376                 :          0 : mws_clock_dummy_get_next_alarm_time (MwsClockDummy *self)
     377                 :            : {
     378         [ #  # ]:          0 :   g_return_val_if_fail (MWS_IS_CLOCK_DUMMY (self), NULL);
     379                 :            : 
     380         [ #  # ]:          0 :   if (self->alarms->len == 0)
     381                 :          0 :     return NULL;
     382                 :            : 
     383                 :          0 :   const AlarmData *data = g_ptr_array_index (self->alarms, 0);
     384                 :          0 :   return data->alarm_time;
     385                 :            : }
     386                 :            : 
     387                 :            : /**
     388                 :            :  * mws_clock_dummy_next_alarm:
     389                 :            :  * @self: a #MwsClockDummy
     390                 :            :  *
     391                 :            :  * Advance the clock to the time of the next alarm (as determined by
     392                 :            :  * mws_clock_dummy_get_next_alarm_time()) using mws_clock_dummy_set_time().
     393                 :            :  *
     394                 :            :  * If there are no alarms scheduled, this function is a no-op and returns
     395                 :            :  * %FALSE.
     396                 :            :  *
     397                 :            :  * Returns: %TRUE if there was a next alarm, %FALSE otherwise
     398                 :            :  * Since: 0.1.0
     399                 :            :  */
     400                 :            : gboolean
     401                 :          0 : mws_clock_dummy_next_alarm (MwsClockDummy *self)
     402                 :            : {
     403         [ #  # ]:          0 :   g_return_val_if_fail (MWS_IS_CLOCK_DUMMY (self), FALSE);
     404                 :            : 
     405                 :          0 :   GDateTime *next_alarm_time = mws_clock_dummy_get_next_alarm_time (self);
     406         [ #  # ]:          0 :   if (next_alarm_time == NULL)
     407                 :          0 :     return FALSE;
     408                 :            : 
     409                 :          0 :   mws_clock_dummy_set_time (self, next_alarm_time);
     410                 :            : 
     411                 :          0 :   return TRUE;
     412                 :            : }

Generated by: LCOV version 1.16