LCOV - code coverage report
Current view: top level - libmogwai-schedule/tests - scheduler.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 520 527 98.7 %
Date: 2022-06-30 20:59:16 Functions: 18 18 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 218 340 64.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 <gio/gio.h>
      27                 :            : #include <libmogwai-schedule/scheduler.h>
      28                 :            : #include <libmogwai-schedule/tests/clock-dummy.h>
      29                 :            : #include <libmogwai-schedule/tests/connection-monitor-dummy.h>
      30                 :            : #include <libmogwai-schedule/tests/peer-manager-dummy.h>
      31                 :            : #include <libmogwai-schedule/tests/signal-logger.h>
      32                 :            : #include <libmogwai-tariff/tariff-builder.h>
      33                 :            : #include <libmogwai-tariff/tariff.h>
      34                 :            : #include <locale.h>
      35                 :            : 
      36                 :            : 
      37                 :            : /* Fixture which creates a #MwsScheduler, connects it to a mock peer manager,
      38                 :            :  * and logs all its signal emissions in @scheduled_signals.
      39                 :            :  *
      40                 :            :  * @scheduler_signals must be empty when the fixture is torn down. */
      41                 :            : typedef struct
      42                 :            : {
      43                 :            :   MwsConnectionMonitor *connection_monitor;  /* (owned) */
      44                 :            :   MwsPeerManager *peer_manager;  /* (owned) */
      45                 :            :   MwsClock *clock;  /* (owned) */
      46                 :            :   MwsScheduler *scheduler;  /* (owned) */
      47                 :            :   MwsSignalLogger *scheduler_signals;  /* (owned) */
      48                 :            : } Fixture;
      49                 :            : 
      50                 :            : typedef struct
      51                 :            : {
      52                 :            :   guint max_active_entries;  /* > 1 */
      53                 :            : } TestData;
      54                 :            : 
      55                 :            : static void
      56                 :         36 : setup (Fixture       *fixture,
      57                 :            :        gconstpointer  test_data)
      58                 :            : {
      59                 :         36 :   const TestData *data = test_data;
      60                 :            : 
      61         [ -  + ]:         36 :   g_assert (data->max_active_entries > 0);
      62                 :            : 
      63                 :         36 :   fixture->connection_monitor = MWS_CONNECTION_MONITOR (mws_connection_monitor_dummy_new ());
      64                 :         36 :   fixture->peer_manager = MWS_PEER_MANAGER (mws_peer_manager_dummy_new (FALSE));
      65                 :         36 :   fixture->clock = MWS_CLOCK (mws_clock_dummy_new ());
      66                 :            : 
      67                 :            :   /* Construct the scheduler manually so we can set max-active-entries. */
      68                 :         36 :   fixture->scheduler = g_object_new (MWS_TYPE_SCHEDULER,
      69                 :            :                                      "connection-monitor", fixture->connection_monitor,
      70                 :            :                                      "peer-manager", fixture->peer_manager,
      71                 :            :                                      "clock", fixture->clock,
      72                 :         36 :                                      "max-active-entries", data->max_active_entries,
      73                 :            :                                      NULL);
      74                 :         36 :   fixture->scheduler_signals = mws_signal_logger_new ();
      75                 :         36 :   mws_signal_logger_connect (fixture->scheduler_signals,
      76                 :         36 :                              fixture->scheduler, "notify::allow-downloads");
      77                 :         36 :   mws_signal_logger_connect (fixture->scheduler_signals,
      78                 :         36 :                              fixture->scheduler, "notify::entries");
      79                 :         36 :   mws_signal_logger_connect (fixture->scheduler_signals,
      80                 :         36 :                              fixture->scheduler, "entries-changed");
      81                 :         36 :   mws_signal_logger_connect (fixture->scheduler_signals,
      82                 :         36 :                              fixture->scheduler, "active-entries-changed");
      83                 :         36 : }
      84                 :            : 
      85                 :            : static void
      86                 :         36 : teardown (Fixture       *fixture,
      87                 :            :           gconstpointer  test_data)
      88                 :            : {
      89                 :            :   /* Clear the signal logger first so we don’t accidentally log signals from
      90                 :            :    * the other objects while we finalise them. */
      91         [ -  + ]:         36 :   mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
      92         [ +  - ]:         36 :   g_clear_pointer (&fixture->scheduler_signals, mws_signal_logger_free);
      93                 :            : 
      94         [ +  - ]:         36 :   g_clear_object (&fixture->scheduler);
      95         [ +  - ]:         36 :   g_clear_object (&fixture->clock);
      96         [ +  - ]:         36 :   g_clear_object (&fixture->peer_manager);
      97         [ +  - ]:         36 :   g_clear_object (&fixture->connection_monitor);
      98                 :         36 : }
      99                 :            : 
     100                 :            : /**
     101                 :            :  * assert_ptr_arrays_equal:
     102                 :            :  * @array1: (nullable): an array
     103                 :            :  * @array2: (nullable): another array
     104                 :            :  *
     105                 :            :  * Compare @array1 and @array2, checking that all their elements are pointerwise
     106                 :            :  * equal.
     107                 :            :  *
     108                 :            :  * Passing %NULL to either parameter is equivalent to passing an empty array.
     109                 :            :  *
     110                 :            :  * If the arrays do not match, an assertion will fail.
     111                 :            :  *
     112                 :            :  * Since: 0.1.0
     113                 :            :  */
     114                 :            : static void
     115                 :        474 : assert_ptr_arrays_equal (GPtrArray *array1,
     116                 :            :                          GPtrArray *array2)
     117                 :            : {
     118   [ +  +  +  + ]:        474 :   gboolean array1_is_empty = (array1 == NULL || array1->len == 0);
     119   [ +  +  -  + ]:        474 :   gboolean array2_is_empty = (array2 == NULL || array2->len == 0);
     120                 :            : 
     121         [ -  + ]:        474 :   g_assert_cmpuint (array1_is_empty, ==, array2_is_empty);
     122                 :            : 
     123   [ +  +  -  + ]:        474 :   if (array1_is_empty || array2_is_empty)
     124                 :        343 :     return;
     125                 :            : 
     126         [ -  + ]:        131 :   g_assert_cmpuint (array1->len, ==, array2->len);
     127                 :            : 
     128         [ +  + ]:        285 :   for (gsize i = 0; i < array1->len; i++)
     129         [ -  + ]:        154 :     g_assert_true (g_ptr_array_index (array1, i) == g_ptr_array_index (array2, i));
     130                 :            : }
     131                 :            : 
     132                 :            : /* Test that constructing a #MwsScheduler works. A basic smoketest. */
     133                 :            : static void
     134                 :          1 : test_scheduler_construction (void)
     135                 :            : {
     136                 :          1 :   g_autoptr(MwsConnectionMonitor) connection_monitor = NULL;
     137                 :          1 :   connection_monitor = MWS_CONNECTION_MONITOR (mws_connection_monitor_dummy_new ());
     138                 :            : 
     139                 :          1 :   g_autoptr(MwsPeerManager) peer_manager = NULL;
     140                 :          1 :   peer_manager = MWS_PEER_MANAGER (mws_peer_manager_dummy_new (FALSE));
     141                 :            : 
     142                 :          2 :   g_autoptr(MwsClock) clock = MWS_CLOCK (mws_clock_dummy_new ());
     143                 :            : 
     144                 :          2 :   g_autoptr(MwsScheduler) scheduler = mws_scheduler_new (connection_monitor,
     145                 :            :                                                          peer_manager,
     146                 :            :                                                          clock);
     147                 :            : 
     148                 :            :   /* Do something to avoid the compiler warning about unused variables. */
     149         [ -  + ]:          1 :   g_assert_true (mws_scheduler_get_peer_manager (scheduler) == peer_manager);
     150                 :          1 : }
     151                 :            : 
     152                 :            : /* Assert the signal emissions from #MwsScheduler are correct for a single call
     153                 :            :  * to mws_scheduler_update_entries().
     154                 :            :  *
     155                 :            :  * Two arrays of entries expected to be signalled as removed in a
     156                 :            :  * #MwsScheduler::active-entries-changed signal must be provided:
     157                 :            :  *  * @expected_changed_active_removed1 for the entries signalled before
     158                 :            :  *    #MwsScheduler::entries-changed is emitted
     159                 :            :  *  * expected_changed_active_removed2 for the entries signalled afterwards
     160                 :            :  */
     161                 :            : static void
     162                 :         79 : assert_entries_changed_signals (Fixture   *fixture,
     163                 :            :                                 GPtrArray *expected_changed_added,
     164                 :            :                                 GPtrArray *expected_changed_removed,
     165                 :            :                                 GPtrArray *expected_changed_active_added,
     166                 :            :                                 GPtrArray *expected_changed_active_removed1,
     167                 :            :                                 GPtrArray *expected_changed_active_removed2)
     168                 :            : {
     169                 :            :   /* Squash empty arrays. */
     170   [ +  +  -  + ]:         79 :   if (expected_changed_added != NULL && expected_changed_added->len == 0)
     171                 :          0 :     expected_changed_added = NULL;
     172   [ +  +  -  + ]:         79 :   if (expected_changed_removed != NULL && expected_changed_removed->len == 0)
     173                 :          0 :     expected_changed_removed = NULL;
     174   [ +  +  +  + ]:         79 :   if (expected_changed_active_added != NULL && expected_changed_active_added->len == 0)
     175                 :          2 :     expected_changed_active_added = NULL;
     176   [ +  +  -  + ]:         79 :   if (expected_changed_active_removed1 != NULL && expected_changed_active_removed1->len == 0)
     177                 :          0 :     expected_changed_active_removed1 = NULL;
     178   [ +  +  -  + ]:         79 :   if (expected_changed_active_removed2 != NULL && expected_changed_active_removed2->len == 0)
     179                 :          0 :     expected_changed_active_removed2 = NULL;
     180                 :            : 
     181                 :         79 :   g_autoptr(GPtrArray) changed_added = NULL;
     182                 :         79 :   g_autoptr(GPtrArray) changed_removed = NULL;
     183                 :         79 :   g_autoptr(GPtrArray) changed_active_added1 = NULL;
     184                 :         79 :   g_autoptr(GPtrArray) changed_active_removed1 = NULL;
     185                 :         79 :   g_autoptr(GPtrArray) changed_active_added2 = NULL;
     186                 :         79 :   g_autoptr(GPtrArray) changed_active_removed2 = NULL;
     187                 :            : 
     188                 :            :   /* We expect active-entries-changed to be emitted first for removed active
     189                 :            :    * entries (if there are any); then notify::entries and entries-changed; then
     190                 :            :    * active-entries-changed *again* for added active entries (if there are
     191                 :            :    * any). */
     192         [ +  + ]:         79 :   if (expected_changed_active_removed1 != NULL)
     193   [ +  -  +  -  :         62 :     mws_signal_logger_assert_emission_pop (fixture->scheduler_signals,
                   +  - ]
     194                 :            :                                            fixture->scheduler, "active-entries-changed",
     195                 :            :                                            &changed_active_added1, &changed_active_removed1);
     196   [ +  +  +  + ]:         79 :   if (expected_changed_added != NULL || expected_changed_removed != NULL)
     197                 :            :     {
     198   [ +  -  +  -  :        110 :       mws_signal_logger_assert_emission_pop (fixture->scheduler_signals,
                   +  - ]
     199                 :            :                                              fixture->scheduler, "notify::entries",
     200                 :            :                                              NULL);
     201   [ +  -  +  -  :        110 :       mws_signal_logger_assert_emission_pop (fixture->scheduler_signals,
                   +  - ]
     202                 :            :                                              fixture->scheduler, "entries-changed",
     203                 :            :                                              &changed_added, &changed_removed);
     204                 :            :     }
     205         [ +  + ]:         79 :   if (expected_changed_active_added != NULL)
     206   [ +  -  +  -  :         88 :     mws_signal_logger_assert_emission_pop (fixture->scheduler_signals,
                   +  - ]
     207                 :            :                                            fixture->scheduler, "active-entries-changed",
     208                 :            :                                            &changed_active_added2, &changed_active_removed2);
     209                 :            : 
     210         [ -  + ]:         79 :   mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
     211                 :            : 
     212                 :         79 :   assert_ptr_arrays_equal (changed_added, expected_changed_added);
     213                 :         79 :   assert_ptr_arrays_equal (changed_removed, expected_changed_removed);
     214                 :         79 :   assert_ptr_arrays_equal (changed_active_added1, NULL);
     215                 :         79 :   assert_ptr_arrays_equal (changed_active_removed1, expected_changed_active_removed1);
     216                 :         79 :   assert_ptr_arrays_equal (changed_active_added2, expected_changed_active_added);
     217                 :         79 :   assert_ptr_arrays_equal (changed_active_removed2, expected_changed_active_removed2);
     218                 :         79 : }
     219                 :            : 
     220                 :            : /* Test that entries are added to and removed from the scheduler correctly. */
     221                 :            : static void
     222                 :          1 : test_scheduler_entries (Fixture       *fixture,
     223                 :            :                         gconstpointer  test_data)
     224                 :            : {
     225                 :          1 :   g_autoptr(GError) local_error = NULL;
     226                 :            :   gboolean success;
     227                 :            :   GHashTable *entries;
     228                 :            : 
     229                 :            :   /* Check that it can be a no-op. */
     230                 :          1 :   success = mws_scheduler_update_entries (fixture->scheduler, NULL, NULL, &local_error);
     231         [ -  + ]:          1 :   g_assert_no_error (local_error);
     232         [ -  + ]:          1 :   g_assert_true (success);
     233                 :            : 
     234                 :          1 :   entries = mws_scheduler_get_entries (fixture->scheduler);
     235         [ -  + ]:          1 :   g_assert_cmpuint (g_hash_table_size (entries), ==, 0);
     236                 :            : 
     237         [ -  + ]:          1 :   mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
     238                 :            : 
     239                 :            :   /* Add an entry. */
     240                 :          2 :   g_autoptr(GPtrArray) added1 = g_ptr_array_new_with_free_func (g_object_unref);
     241                 :          1 :   g_ptr_array_add (added1, mws_schedule_entry_new (":owner.1"));
     242                 :            : 
     243                 :          1 :   success = mws_scheduler_update_entries (fixture->scheduler, added1, NULL, &local_error);
     244         [ -  + ]:          1 :   g_assert_no_error (local_error);
     245         [ -  + ]:          1 :   g_assert_true (success);
     246                 :            : 
     247                 :          1 :   entries = mws_scheduler_get_entries (fixture->scheduler);
     248         [ -  + ]:          1 :   g_assert_cmpuint (g_hash_table_size (entries), ==, 1);
     249                 :          1 :   assert_entries_changed_signals (fixture, added1, NULL, added1, NULL, NULL);
     250                 :            : 
     251                 :          1 :   MwsScheduleEntry *entry = mws_scheduler_get_entry (fixture->scheduler, "0");
     252         [ -  + ]:          1 :   g_assert_nonnull (entry);
     253         [ -  + ]:          1 :   g_assert_true (MWS_IS_SCHEDULE_ENTRY (entry));
     254         [ -  + ]:          1 :   g_assert_true (mws_scheduler_is_entry_active (fixture->scheduler, entry));
     255                 :            : 
     256                 :            :   /* Remove an entry. */
     257                 :          2 :   g_autoptr(GPtrArray) removed1 = g_ptr_array_new_with_free_func (NULL);
     258                 :          1 :   g_ptr_array_add (removed1, mws_schedule_entry_get_id (entry));
     259                 :          2 :   g_autoptr(GPtrArray) expected_removed1 = g_ptr_array_new_with_free_func (NULL);
     260                 :          1 :   g_ptr_array_add (expected_removed1, entry);
     261                 :            : 
     262                 :          1 :   success = mws_scheduler_update_entries (fixture->scheduler, NULL, removed1, &local_error);
     263         [ -  + ]:          1 :   g_assert_no_error (local_error);
     264         [ -  + ]:          1 :   g_assert_true (success);
     265                 :            : 
     266                 :          1 :   entries = mws_scheduler_get_entries (fixture->scheduler);
     267         [ -  + ]:          1 :   g_assert_cmpuint (g_hash_table_size (entries), ==, 0);
     268                 :          1 :   assert_entries_changed_signals (fixture, NULL, expected_removed1, NULL, expected_removed1, NULL);
     269                 :            : 
     270                 :            :   /* Remove a non-existent entry. */
     271                 :          2 :   g_autoptr(GPtrArray) removed2 = g_ptr_array_new_with_free_func (NULL);
     272                 :          1 :   g_ptr_array_add (removed2, "nope");
     273                 :            : 
     274                 :          1 :   success = mws_scheduler_update_entries (fixture->scheduler, NULL, removed2, &local_error);
     275         [ -  + ]:          1 :   g_assert_no_error (local_error);
     276         [ -  + ]:          1 :   g_assert_true (success);
     277                 :            : 
     278                 :          1 :   entries = mws_scheduler_get_entries (fixture->scheduler);
     279         [ -  + ]:          1 :   g_assert_cmpuint (g_hash_table_size (entries), ==, 0);
     280         [ -  + ]:          1 :   mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
     281                 :            : 
     282                 :            :   /* Add several entries. */
     283                 :          2 :   g_autoptr(GPtrArray) added2 = g_ptr_array_new_with_free_func (g_object_unref);
     284                 :          1 :   g_ptr_array_add (added2, mws_schedule_entry_new (":owner.1"));
     285                 :          1 :   g_ptr_array_add (added2, mws_schedule_entry_new (":owner.1"));
     286                 :          1 :   g_ptr_array_add (added2, mws_schedule_entry_new (":owner.2"));
     287                 :            : 
     288                 :          2 :   g_autoptr(GPtrArray) added_active2 = g_ptr_array_new_with_free_func (NULL);
     289                 :          1 :   g_ptr_array_add (added_active2, added2->pdata[0]);
     290                 :            : 
     291                 :          1 :   success = mws_scheduler_update_entries (fixture->scheduler, added2, NULL, &local_error);
     292         [ -  + ]:          1 :   g_assert_no_error (local_error);
     293         [ -  + ]:          1 :   g_assert_true (success);
     294                 :            : 
     295                 :          1 :   entries = mws_scheduler_get_entries (fixture->scheduler);
     296         [ -  + ]:          1 :   g_assert_cmpuint (g_hash_table_size (entries), ==, 3);
     297                 :          1 :   assert_entries_changed_signals (fixture, added2, NULL, added_active2, NULL, NULL);
     298                 :            : 
     299                 :            :   /* Add duplicate entry. */
     300                 :          2 :   g_autoptr(GPtrArray) added3 = g_ptr_array_new_with_free_func (NULL);
     301                 :          1 :   g_ptr_array_add (added3, added2->pdata[0]);
     302                 :            : 
     303                 :          1 :   success = mws_scheduler_update_entries (fixture->scheduler, added3, NULL, &local_error);
     304         [ -  + ]:          1 :   g_assert_no_error (local_error);
     305         [ -  + ]:          1 :   g_assert_true (success);
     306                 :            : 
     307                 :          1 :   entries = mws_scheduler_get_entries (fixture->scheduler);
     308         [ -  + ]:          1 :   g_assert_cmpuint (g_hash_table_size (entries), ==, 3);
     309         [ -  + ]:          1 :   mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
     310                 :            : 
     311                 :            :   /* No-op when non-empty. */
     312                 :          1 :   success = mws_scheduler_update_entries (fixture->scheduler, NULL, NULL, &local_error);
     313         [ -  + ]:          1 :   g_assert_no_error (local_error);
     314         [ -  + ]:          1 :   g_assert_true (success);
     315                 :            : 
     316                 :          1 :   entries = mws_scheduler_get_entries (fixture->scheduler);
     317         [ -  + ]:          1 :   g_assert_cmpuint (g_hash_table_size (entries), ==, 3);
     318         [ -  + ]:          1 :   mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
     319                 :            : 
     320                 :            :   /* Remove several entries. */
     321                 :          2 :   g_autoptr(GPtrArray) removed3 = g_ptr_array_new_with_free_func (NULL);
     322                 :          2 :   g_autoptr(GPtrArray) expected_removed3 = g_ptr_array_new_with_free_func (NULL);
     323                 :            : 
     324                 :            :   GHashTableIter iter;
     325                 :            :   gpointer key, value;
     326                 :          1 :   g_hash_table_iter_init (&iter, entries);
     327         [ +  + ]:          4 :   while (g_hash_table_iter_next (&iter, &key, &value))
     328                 :            :     {
     329                 :          3 :       g_ptr_array_add (removed3, key);
     330                 :          3 :       g_ptr_array_add (expected_removed3, value);
     331                 :            :     }
     332                 :            : 
     333                 :          1 :   success = mws_scheduler_update_entries (fixture->scheduler, NULL, removed3, &local_error);
     334         [ -  + ]:          1 :   g_assert_no_error (local_error);
     335         [ -  + ]:          1 :   g_assert_true (success);
     336                 :            : 
     337                 :          1 :   entries = mws_scheduler_get_entries (fixture->scheduler);
     338         [ -  + ]:          1 :   g_assert_cmpuint (g_hash_table_size (entries), ==, 0);
     339                 :          1 :   assert_entries_changed_signals (fixture, NULL, expected_removed3, NULL, added_active2, NULL);
     340                 :          1 : }
     341                 :            : 
     342                 :            : /* Test that entries can be removed by owner from a scheduler correctly. */
     343                 :            : static void
     344                 :          1 : test_scheduler_entries_remove_for_owner (Fixture       *fixture,
     345                 :            :                                          gconstpointer  test_data)
     346                 :            : {
     347                 :          1 :   g_autoptr(GError) local_error = NULL;
     348                 :            :   gboolean success;
     349                 :            :   GHashTable *entries;
     350                 :            : 
     351                 :            :   /* Add several entries. */
     352                 :          2 :   g_autoptr(GPtrArray) added1 = g_ptr_array_new_with_free_func (g_object_unref);
     353                 :          1 :   g_ptr_array_add (added1, mws_schedule_entry_new (":owner.1"));
     354                 :          1 :   g_ptr_array_add (added1, mws_schedule_entry_new (":owner.1"));
     355                 :          1 :   g_ptr_array_add (added1, mws_schedule_entry_new (":owner.2"));
     356                 :            : 
     357                 :          2 :   g_autoptr(GPtrArray) added_active1 = g_ptr_array_new_with_free_func (NULL);
     358                 :          1 :   g_ptr_array_add (added_active1, added1->pdata[0]);
     359                 :            : 
     360                 :          1 :   success = mws_scheduler_update_entries (fixture->scheduler, added1, NULL, &local_error);
     361         [ -  + ]:          1 :   g_assert_no_error (local_error);
     362         [ -  + ]:          1 :   g_assert_true (success);
     363                 :            : 
     364                 :          1 :   entries = mws_scheduler_get_entries (fixture->scheduler);
     365         [ -  + ]:          1 :   g_assert_cmpuint (g_hash_table_size (entries), ==, 3);
     366                 :          1 :   assert_entries_changed_signals (fixture, added1, NULL, added_active1, NULL, NULL);
     367                 :            : 
     368                 :            :   /* Remove all entries from one owner, including the active entry. */
     369                 :          1 :   success = mws_scheduler_remove_entries_for_owner (fixture->scheduler, ":owner.1", &local_error);
     370         [ -  + ]:          1 :   g_assert_no_error (local_error);
     371         [ -  + ]:          1 :   g_assert_true (success);
     372                 :            : 
     373                 :          2 :   g_autoptr(GPtrArray) removed1 = g_ptr_array_new_with_free_func (NULL);
     374                 :          1 :   g_ptr_array_add (removed1, added1->pdata[0]);
     375                 :          1 :   g_ptr_array_add (removed1, added1->pdata[1]);
     376                 :            : 
     377                 :          2 :   g_autoptr(GPtrArray) removed_active1 = g_ptr_array_new_with_free_func (NULL);
     378                 :          1 :   g_ptr_array_add (removed_active1, added1->pdata[0]);
     379                 :            : 
     380                 :          2 :   g_autoptr(GPtrArray) added_active2 = g_ptr_array_new_with_free_func (NULL);
     381                 :          1 :   g_ptr_array_add (added_active2, added1->pdata[2]);
     382                 :            : 
     383                 :          1 :   entries = mws_scheduler_get_entries (fixture->scheduler);
     384         [ -  + ]:          1 :   g_assert_cmpuint (g_hash_table_size (entries), ==, 1);
     385                 :          1 :   assert_entries_changed_signals (fixture, NULL, removed1, added_active2, added_active1, NULL);
     386                 :            : 
     387                 :            :   /* Remove entries from a non-existent owner. */
     388                 :          1 :   success = mws_scheduler_remove_entries_for_owner (fixture->scheduler, ":owner.100", &local_error);
     389         [ -  + ]:          1 :   g_assert_no_error (local_error);
     390         [ -  + ]:          1 :   g_assert_true (success);
     391                 :            : 
     392                 :          1 :   entries = mws_scheduler_get_entries (fixture->scheduler);
     393         [ -  + ]:          1 :   g_assert_cmpuint (g_hash_table_size (entries), ==, 1);
     394         [ -  + ]:          1 :   mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
     395                 :            : 
     396                 :            :   /* Remove the remaining entries. */
     397                 :          1 :   success = mws_scheduler_remove_entries_for_owner (fixture->scheduler, ":owner.2", &local_error);
     398         [ -  + ]:          1 :   g_assert_no_error (local_error);
     399         [ -  + ]:          1 :   g_assert_true (success);
     400                 :            : 
     401                 :          2 :   g_autoptr(GPtrArray) removed2 = g_ptr_array_new_with_free_func (NULL);
     402                 :          1 :   g_ptr_array_add (removed2, added1->pdata[2]);
     403                 :            : 
     404                 :          1 :   entries = mws_scheduler_get_entries (fixture->scheduler);
     405         [ -  + ]:          1 :   g_assert_cmpuint (g_hash_table_size (entries), ==, 0);
     406                 :          1 :   assert_entries_changed_signals (fixture, NULL, removed2, NULL, added_active2, NULL);
     407                 :          1 : }
     408                 :            : 
     409                 :            : /* Test that getting the properties from a #MwsScheduler works. */
     410                 :            : static void
     411                 :          1 : test_scheduler_properties (Fixture       *fixture,
     412                 :            :                            gconstpointer  test_data)
     413                 :            : {
     414                 :          1 :   g_autoptr(GHashTable) entries = NULL;
     415                 :            :   guint max_entries;
     416                 :          1 :   g_autoptr(MwsConnectionMonitor) connection_monitor = NULL;
     417                 :            :   guint max_active_entries;
     418                 :          1 :   g_autoptr(MwsPeerManager) peer_manager = NULL;
     419                 :            :   gboolean allow_downloads;
     420                 :          1 :   g_autoptr(MwsClock) clock = NULL;
     421                 :            : 
     422                 :          1 :   g_object_get (fixture->scheduler,
     423                 :            :                 "entries", &entries,
     424                 :            :                 "max-entries", &max_entries,
     425                 :            :                 "connection-monitor", &connection_monitor,
     426                 :            :                 "max-active-entries", &max_active_entries,
     427                 :            :                 "peer-manager", &peer_manager,
     428                 :            :                 "allow-downloads", &allow_downloads,
     429                 :            :                 "clock", &clock,
     430                 :            :                 NULL);
     431                 :            : 
     432         [ -  + ]:          1 :   g_assert_nonnull (entries);
     433         [ -  + ]:          1 :   g_assert_cmpuint (max_entries, >, 0);
     434         [ -  + ]:          1 :   g_assert_nonnull (connection_monitor);
     435         [ -  + ]:          1 :   g_assert_cmpuint (max_active_entries, >, 0);
     436         [ -  + ]:          1 :   g_assert_nonnull (peer_manager);
     437         [ -  + ]:          1 :   g_assert_nonnull (clock);
     438                 :          1 : }
     439                 :            : 
     440                 :            : /* Convenience method to create a new schedule entry and set its priority. */
     441                 :            : static MwsScheduleEntry *
     442                 :         19 : schedule_entry_new_with_priority (const gchar *owner,
     443                 :            :                                   guint32      priority)
     444                 :            : {
     445                 :         38 :   g_autoptr(MwsScheduleEntry) entry = mws_schedule_entry_new (owner);
     446                 :         19 :   mws_schedule_entry_set_priority (entry, priority);
     447                 :         19 :   return g_steal_pointer (&entry);
     448                 :            : }
     449                 :            : 
     450                 :            : /* Assert that when all the entries in @expected_scheduling_order are added to
     451                 :            :  * the scheduler, they are scheduled in the given order. This is checked by
     452                 :            :  * adding them all, checking which one entry is active, removing it, then
     453                 :            :  * checking which entry is made active next, etc. This checks all signal
     454                 :            :  * emissions. It requires the scheduler to be empty beforehand, and to have a
     455                 :            :  * max-active-entries limit of 1. */
     456                 :            : static void
     457                 :          2 : assert_scheduling_order (Fixture                 *fixture,
     458                 :            :                          const MwsScheduleEntry **expected_scheduling_order,
     459                 :            :                          gsize                    n_entries)
     460                 :            : {
     461                 :          2 :   g_autoptr(GError) local_error = NULL;
     462                 :            : 
     463         [ -  + ]:          2 :   g_assert (n_entries > 0);
     464         [ -  + ]:          2 :   g_assert (g_hash_table_size (mws_scheduler_get_entries (fixture->scheduler)) == 0);
     465                 :            :   guint max_active_entries;
     466                 :          2 :   g_object_get (fixture->scheduler, "max-active-entries", &max_active_entries, NULL);
     467         [ -  + ]:          2 :   g_assert (max_active_entries == 1);
     468                 :            : 
     469                 :            :   /* Add all the entries to the scheduler. Add them in the reverse of the
     470                 :            :    * expected scheduling order, just in case the scheduler is being really dumb. */
     471                 :          4 :   g_autoptr(GPtrArray) added = g_ptr_array_new_with_free_func (NULL);
     472         [ +  + ]:         14 :   for (gsize i = 0; i < n_entries; i++)
     473                 :         12 :     g_ptr_array_add (added, expected_scheduling_order[n_entries - 1 - i]);
     474                 :            : 
     475                 :          4 :   g_autoptr(GPtrArray) expected_active = g_ptr_array_new_with_free_func (NULL);
     476                 :          2 :   g_ptr_array_add (expected_active, expected_scheduling_order[0]);
     477                 :            : 
     478                 :          2 :   mws_scheduler_update_entries (fixture->scheduler, added, NULL, &local_error);
     479         [ -  + ]:          2 :   g_assert_no_error (local_error);
     480                 :          2 :   assert_entries_changed_signals (fixture, added, NULL, expected_active, NULL, NULL);
     481                 :            : 
     482                 :            :   /* Remove each active entry to work out what is going to be scheduled next. */
     483         [ +  + ]:         14 :   for (gsize i = 0; i < n_entries; i++)
     484                 :            :     {
     485                 :         24 :       g_autoptr(GPtrArray) removed = g_ptr_array_new_with_free_func (NULL);
     486                 :         12 :       g_ptr_array_add (removed, mws_schedule_entry_get_id (expected_scheduling_order[i]));
     487                 :            : 
     488                 :         24 :       g_autoptr(GPtrArray) expected_removed = g_ptr_array_new_with_free_func (NULL);
     489                 :         12 :       g_ptr_array_add (expected_removed, expected_scheduling_order[i]);
     490                 :            : 
     491                 :         24 :       g_autoptr(GPtrArray) old_expected_active = g_steal_pointer (&expected_active);
     492                 :         12 :       expected_active = g_ptr_array_new_with_free_func (NULL);
     493         [ +  + ]:         12 :       if (i + 1 < n_entries)
     494                 :         10 :         g_ptr_array_add (expected_active, expected_scheduling_order[i + 1]);
     495                 :            : 
     496                 :         12 :       mws_scheduler_update_entries (fixture->scheduler, NULL, removed, &local_error);
     497         [ -  + ]:         12 :       g_assert_no_error (local_error);
     498                 :         12 :       assert_entries_changed_signals (fixture,
     499                 :            :                                       NULL, expected_removed,
     500                 :            :                                       expected_active, old_expected_active, NULL);
     501                 :            :     }
     502                 :          2 : }
     503                 :            : 
     504                 :            : /* Test that schedule entries are correctly ordered by their priority within a
     505                 :            :  * single peer. */
     506                 :            : static void
     507                 :          1 : test_scheduler_scheduling_entry_priorities (Fixture       *fixture,
     508                 :            :                                             gconstpointer  test_data)
     509                 :            : {
     510                 :            :   /* Add several entries. @entry4 and @entry5 have the same priority, but ties
     511                 :            :    * are broken using the entry ID, so @entry5 loses as its ID is slightly
     512                 :            :    * higher. */
     513                 :          2 :   g_autoptr(MwsScheduleEntry) entry1 = schedule_entry_new_with_priority (":owner.1", 5);
     514                 :          2 :   g_autoptr(MwsScheduleEntry) entry2 = schedule_entry_new_with_priority (":owner.1", 10);
     515                 :          2 :   g_autoptr(MwsScheduleEntry) entry3 = schedule_entry_new_with_priority (":owner.1", 15);
     516                 :          2 :   g_autoptr(MwsScheduleEntry) entry4 = schedule_entry_new_with_priority (":owner.1", 16);
     517                 :          2 :   g_autoptr(MwsScheduleEntry) entry5 = schedule_entry_new_with_priority (":owner.1", 16);
     518                 :            : 
     519                 :            :   /* We expect @entry4 to be scheduled first, then @entry5, etc. */
     520                 :          1 :   const MwsScheduleEntry *expected_scheduling_order[] = { entry4, entry5, entry3, entry2, entry1 };
     521                 :            : 
     522                 :          1 :   assert_scheduling_order (fixture, expected_scheduling_order,
     523                 :            :                            G_N_ELEMENTS (expected_scheduling_order));
     524                 :          1 : }
     525                 :            : 
     526                 :            : /* Test that schedule entries are correctly ordered by their priority between
     527                 :            :  * multiple peers. */
     528                 :            : static void
     529                 :          1 : test_scheduler_scheduling_peer_priorities (Fixture       *fixture,
     530                 :            :                                            gconstpointer  test_data)
     531                 :            : {
     532                 :            :   /* Add several entries. */
     533                 :          2 :   g_autoptr(MwsScheduleEntry) entry1 = schedule_entry_new_with_priority (":eos.updater", 5);
     534                 :          2 :   g_autoptr(MwsScheduleEntry) entry2 = schedule_entry_new_with_priority (":gnome.software", 10);
     535                 :          2 :   g_autoptr(MwsScheduleEntry) entry3 = schedule_entry_new_with_priority (":eos.updater", 15);
     536                 :          2 :   g_autoptr(MwsScheduleEntry) entry4 = schedule_entry_new_with_priority (":random.program.1", 12);
     537                 :          2 :   g_autoptr(MwsScheduleEntry) entry5 = schedule_entry_new_with_priority (":random.program.1", 100);
     538                 :          2 :   g_autoptr(MwsScheduleEntry) entry6 = schedule_entry_new_with_priority (":random.program.2", 2);
     539                 :          2 :   g_autoptr(MwsScheduleEntry) entry7 = schedule_entry_new_with_priority (":unknown.peer", 110);
     540                 :            : 
     541                 :            :   /* Set up the peer credentials. */
     542                 :          1 :   mws_peer_manager_dummy_set_peer_credentials (MWS_PEER_MANAGER_DUMMY (fixture->peer_manager),
     543                 :            :                                                ":eos.updater", "/usr/libexec/eos-updater");
     544                 :          1 :   mws_peer_manager_dummy_set_peer_credentials (MWS_PEER_MANAGER_DUMMY (fixture->peer_manager),
     545                 :            :                                                ":gnome.software", "/usr/bin/gnome-software");
     546                 :          1 :   mws_peer_manager_dummy_set_peer_credentials (MWS_PEER_MANAGER_DUMMY (fixture->peer_manager),
     547                 :            :                                                ":random.program.1", "/some/random/path1");
     548                 :          1 :   mws_peer_manager_dummy_set_peer_credentials (MWS_PEER_MANAGER_DUMMY (fixture->peer_manager),
     549                 :            :                                                ":random.program.2", "/some/random/path2");
     550                 :            : 
     551                 :            :   /* We expect @entry3 to be scheduled first, then @entry2, etc. Note that since
     552                 :            :    * the priority order of :random.program.1 and :random.program.2 is undefined
     553                 :            :    * (depends on the hash of their unique names, so may change between GLib
     554                 :            :    * versions), [entry6] and [entry5, entry4] may change places at some point
     555                 :            :    * in the future. */
     556                 :          1 :   const MwsScheduleEntry *expected_scheduling_order[] =
     557                 :            :       { entry3, entry2, entry1, entry6, entry5, entry4, entry7 };
     558                 :            : 
     559                 :          1 :   assert_scheduling_order (fixture, expected_scheduling_order,
     560                 :            :                            G_N_ELEMENTS (expected_scheduling_order));
     561                 :          1 : }
     562                 :            : 
     563                 :            : /* Test that the size of the set of active entries is limited by
     564                 :            :  * #MwsScheduler:max-active-entries, and that only elements in that set are
     565                 :            :  * mentioned in #MwsScheduler::active-entries-changed signals.
     566                 :            :  *
     567                 :            :  * Schedule two entries, then add two more with priorities such that one of them
     568                 :            :  * will be scheduled in place of one of the original entries. Remove one of the
     569                 :            :  * scheduled entries so one of the unscheduled entries is scheduled; then remove
     570                 :            :  * the remaining unscheduled entry to check that it’s not mentioned in
     571                 :            :  * ::active-entries-changed signals. */
     572                 :            : static void
     573                 :          1 : test_scheduler_scheduling_max_active_entries (Fixture       *fixture,
     574                 :            :                                               gconstpointer  test_data)
     575                 :            : {
     576                 :          1 :   const TestData *data = test_data;
     577         [ -  + ]:          1 :   g_assert (data->max_active_entries == 2);
     578                 :            : 
     579                 :          1 :   g_autoptr(GError) local_error = NULL;
     580                 :            : 
     581                 :            :   /* Add several entries. */
     582                 :          2 :   g_autoptr(MwsScheduleEntry) entry1 = schedule_entry_new_with_priority (":owner.1", 5);
     583                 :          2 :   g_autoptr(MwsScheduleEntry) entry2 = schedule_entry_new_with_priority (":owner.1", 10);
     584                 :            : 
     585                 :            :   /* Set up the peer credentials. */
     586                 :          1 :   mws_peer_manager_dummy_set_peer_credentials (MWS_PEER_MANAGER_DUMMY (fixture->peer_manager),
     587                 :            :                                                ":owner.1", "/some/owner");
     588                 :            : 
     589                 :            :   /* Add all the entries to the scheduler. */
     590                 :          2 :   g_autoptr(GPtrArray) added1 = g_ptr_array_new_with_free_func (NULL);
     591                 :          1 :   g_ptr_array_add (added1, entry1);
     592                 :          1 :   g_ptr_array_add (added1, entry2);
     593                 :            : 
     594                 :          2 :   g_autoptr(GPtrArray) expected_active1 = g_ptr_array_new_with_free_func (NULL);
     595                 :          1 :   g_ptr_array_add (expected_active1, entry2);
     596                 :          1 :   g_ptr_array_add (expected_active1, entry1);
     597                 :            : 
     598                 :          1 :   mws_scheduler_update_entries (fixture->scheduler, added1, NULL, &local_error);
     599         [ -  + ]:          1 :   g_assert_no_error (local_error);
     600                 :          1 :   assert_entries_changed_signals (fixture, added1, NULL, expected_active1, NULL, NULL);
     601                 :            : 
     602                 :            :   /* Add some more entries, one of which at a higher priority so that one of the
     603                 :            :    * old ones is descheduled. */
     604                 :          2 :   g_autoptr(MwsScheduleEntry) entry3 = schedule_entry_new_with_priority (":owner.1", 15);
     605                 :          2 :   g_autoptr(MwsScheduleEntry) entry4 = schedule_entry_new_with_priority (":owner.1", 7);
     606                 :            : 
     607                 :          2 :   g_autoptr(GPtrArray) added2 = g_ptr_array_new_with_free_func (NULL);
     608                 :          1 :   g_ptr_array_add (added2, entry3);
     609                 :          1 :   g_ptr_array_add (added2, entry4);
     610                 :            : 
     611                 :          2 :   g_autoptr(GPtrArray) expected_active2 = g_ptr_array_new_with_free_func (NULL);
     612                 :          1 :   g_ptr_array_add (expected_active2, entry3);
     613                 :            : 
     614                 :          2 :   g_autoptr(GPtrArray) expected_inactive1 = g_ptr_array_new_with_free_func (NULL);
     615                 :          1 :   g_ptr_array_add (expected_inactive1, entry1);
     616                 :            : 
     617                 :          1 :   mws_scheduler_update_entries (fixture->scheduler, added2, NULL, &local_error);
     618         [ -  + ]:          1 :   g_assert_no_error (local_error);
     619                 :          1 :   assert_entries_changed_signals (fixture, added2, NULL, expected_active2, NULL, expected_inactive1);
     620                 :            : 
     621                 :            :   /* Remove one of the high priority entries so another should be scheduled in
     622                 :            :    * its place. */
     623                 :          2 :   g_autoptr(GPtrArray) removed_ids1 = g_ptr_array_new_with_free_func (NULL);
     624                 :          1 :   g_ptr_array_add (removed_ids1, mws_schedule_entry_get_id (entry3));
     625                 :          2 :   g_autoptr(GPtrArray) removed1 = g_ptr_array_new_with_free_func (NULL);
     626                 :          1 :   g_ptr_array_add (removed1, entry3);
     627                 :            : 
     628                 :          2 :   g_autoptr(GPtrArray) expected_active3 = g_ptr_array_new_with_free_func (NULL);
     629                 :          1 :   g_ptr_array_add (expected_active3, entry4);
     630                 :            : 
     631                 :          2 :   g_autoptr(GPtrArray) expected_inactive2 = g_ptr_array_new_with_free_func (NULL);
     632                 :          1 :   g_ptr_array_add (expected_inactive2, entry3);
     633                 :            : 
     634                 :          1 :   mws_scheduler_update_entries (fixture->scheduler, NULL, removed_ids1, &local_error);
     635         [ -  + ]:          1 :   g_assert_no_error (local_error);
     636                 :          1 :   assert_entries_changed_signals (fixture, NULL, removed1, expected_active3, expected_inactive2, NULL);
     637                 :            : 
     638                 :            :   /* Now remove an inactive entry and check it’s not signalled. */
     639                 :          2 :   g_autoptr(GPtrArray) removed_ids2 = g_ptr_array_new_with_free_func (NULL);
     640                 :          1 :   g_ptr_array_add (removed_ids2, mws_schedule_entry_get_id (entry1));
     641                 :          2 :   g_autoptr(GPtrArray) removed2 = g_ptr_array_new_with_free_func (NULL);
     642                 :          1 :   g_ptr_array_add (removed2, entry1);
     643                 :            : 
     644                 :          1 :   mws_scheduler_update_entries (fixture->scheduler, NULL, removed_ids2, &local_error);
     645         [ -  + ]:          1 :   g_assert_no_error (local_error);
     646                 :          1 :   assert_entries_changed_signals (fixture, NULL, removed2, NULL, NULL, NULL);
     647                 :          1 : }
     648                 :            : 
     649                 :            : /* Test that the schedule entries for a given peer are automatically removed if
     650                 :            :  * that peer vanishes, whether they are active or not. */
     651                 :            : static void
     652                 :          1 : test_scheduler_scheduling_peer_vanished (Fixture       *fixture,
     653                 :            :                                          gconstpointer  test_data)
     654                 :            : {
     655                 :          1 :   g_autoptr(GError) local_error = NULL;
     656                 :            : 
     657                 :            :   /* Add several entries. */
     658                 :          2 :   g_autoptr(MwsScheduleEntry) entry1 = schedule_entry_new_with_priority (":owner.1", 5);
     659                 :          2 :   g_autoptr(MwsScheduleEntry) entry2 = schedule_entry_new_with_priority (":owner.1", 10);
     660                 :          2 :   g_autoptr(MwsScheduleEntry) entry3 = schedule_entry_new_with_priority (":owner.2", 2);
     661                 :            : 
     662                 :            :   /* Set up the peer credentials. Make sure :owner.1 is scheduled first, by
     663                 :            :    * making it gnome-software. */
     664                 :          1 :   mws_peer_manager_dummy_set_peer_credentials (MWS_PEER_MANAGER_DUMMY (fixture->peer_manager),
     665                 :            :                                                ":owner.1", "/usr/bin/gnome-software");
     666                 :          1 :   mws_peer_manager_dummy_set_peer_credentials (MWS_PEER_MANAGER_DUMMY (fixture->peer_manager),
     667                 :            :                                                ":owner.2", "/some/other/path");
     668                 :            : 
     669                 :            :   /* Add all the entries to the scheduler. */
     670                 :          2 :   g_autoptr(GPtrArray) added = g_ptr_array_new_with_free_func (NULL);
     671                 :          1 :   g_ptr_array_add (added, entry1);
     672                 :          1 :   g_ptr_array_add (added, entry2);
     673                 :          1 :   g_ptr_array_add (added, entry3);
     674                 :            : 
     675                 :          2 :   g_autoptr(GPtrArray) expected_active1 = g_ptr_array_new_with_free_func (NULL);
     676                 :          1 :   g_ptr_array_add (expected_active1, entry2);
     677                 :            : 
     678                 :          1 :   mws_scheduler_update_entries (fixture->scheduler, added, NULL, &local_error);
     679         [ -  + ]:          1 :   g_assert_no_error (local_error);
     680                 :          1 :   assert_entries_changed_signals (fixture, added, NULL, expected_active1, NULL, NULL);
     681                 :            : 
     682                 :            :   /* Make `:owner.1` vanish, and check that both its entries (but not the third
     683                 :            :    * entry) disappear. */
     684                 :          1 :   mws_peer_manager_dummy_remove_peer (MWS_PEER_MANAGER_DUMMY (fixture->peer_manager),
     685                 :            :                                       ":owner.1");
     686                 :            : 
     687                 :          2 :   g_autoptr(GPtrArray) expected_removed = g_ptr_array_new_with_free_func (NULL);
     688                 :          1 :   g_ptr_array_add (expected_removed, entry2);
     689                 :          1 :   g_ptr_array_add (expected_removed, entry1);
     690                 :            : 
     691                 :          2 :   g_autoptr(GPtrArray) expected_active2 = g_ptr_array_new_with_free_func (NULL);
     692                 :          1 :   g_ptr_array_add (expected_active2, entry3);
     693                 :            : 
     694                 :          1 :   assert_entries_changed_signals (fixture, NULL, expected_removed,
     695                 :            :                                   expected_active2, expected_active1, NULL);
     696                 :            : 
     697                 :            :   /* Now make `:owner.2` vanish, to test what happens when there will be no new
     698                 :            :    * active entry to take the place of the one being removed. */
     699                 :          1 :   mws_peer_manager_dummy_remove_peer (MWS_PEER_MANAGER_DUMMY (fixture->peer_manager),
     700                 :            :                                       ":owner.2");
     701                 :            : 
     702                 :          1 :   assert_entries_changed_signals (fixture, NULL, expected_active2, NULL, expected_active2, NULL);
     703                 :          1 : }
     704                 :            : 
     705                 :            : /* Test the transitions between different network connection states, checking
     706                 :            :  * whether they cause a single entry to be scheduled/unscheduled
     707                 :            :  * appropriately. Each test vector provides two states of a set of network
     708                 :            :  * connections. The test starts in state 1, checks whether the entry is
     709                 :            :  * scheduled appropriately, transitions to state 2, checks again, and then
     710                 :            :  * transitions back to state 1 to test the reverse transition (and checks
     711                 :            :  * again). */
     712                 :            : static void
     713                 :          1 : test_scheduler_scheduling_metered_connection (Fixture       *fixture,
     714                 :            :                                               gconstpointer  test_data)
     715                 :            : {
     716                 :            :   /* Various pre-defined connection details, which can be assigned for use below. */
     717                 :          1 :   const MwsConnectionDetails connection_metered =
     718                 :            :     {
     719                 :            :       .metered = MWS_METERED_YES,
     720                 :            :       .allow_downloads_when_metered = FALSE,
     721                 :            :       .allow_downloads = TRUE,
     722                 :            :       .tariff = NULL,
     723                 :            :     };
     724                 :          1 :   const MwsConnectionDetails connection_maybe_metered =
     725                 :            :     {
     726                 :            :       .metered = MWS_METERED_GUESS_YES,
     727                 :            :       .allow_downloads_when_metered = FALSE,
     728                 :            :       .allow_downloads = TRUE,
     729                 :            :       .tariff = NULL,
     730                 :            :     };
     731                 :          1 :   const MwsConnectionDetails connection_unmetered =
     732                 :            :     {
     733                 :            :       .metered = MWS_METERED_NO,
     734                 :            :       .allow_downloads_when_metered = FALSE,
     735                 :            :       .allow_downloads = TRUE,
     736                 :            :       .tariff = NULL,
     737                 :            :     };
     738                 :          1 :   const MwsConnectionDetails connection_metered_allow_downloads =
     739                 :            :     {
     740                 :            :       .metered = MWS_METERED_YES,
     741                 :            :       .allow_downloads_when_metered = TRUE,
     742                 :            :       .allow_downloads = TRUE,
     743                 :            :       .tariff = NULL,
     744                 :            :     };
     745                 :          1 :   const MwsConnectionDetails connection_metered_no_downloads =
     746                 :            :     {
     747                 :            :       .metered = MWS_METERED_YES,
     748                 :            :       .allow_downloads_when_metered = FALSE,
     749                 :            :       .allow_downloads = FALSE,
     750                 :            :       .tariff = NULL,
     751                 :            :     };
     752                 :          1 :   const MwsConnectionDetails connection_unmetered_no_downloads =
     753                 :            :     {
     754                 :            :       .metered = MWS_METERED_NO,
     755                 :            :       .allow_downloads_when_metered = FALSE,
     756                 :            :       .allow_downloads = FALSE,
     757                 :            :       .tariff = NULL,
     758                 :            :     };
     759                 :            : 
     760                 :          1 :   g_autoptr(GError) local_error = NULL;
     761                 :            :   struct
     762                 :            :     {
     763                 :            :       /* We use a fixed limit of 3 connections in these tests, because we should
     764                 :            :        * be able to simulate any condition we care about using 3. Typically,
     765                 :            :        * systems will have only 1 active connection (maybe 2 if they have wired
     766                 :            :        * and Wi-Fi enabled at the same time). */
     767                 :            :       const MwsConnectionDetails *state1_connections[3];
     768                 :            :       gboolean state1_expected_allow_downloads;
     769                 :            :       gboolean state1_expected_active;
     770                 :            :       const MwsConnectionDetails *state2_connections[3];
     771                 :            :       gboolean state2_expected_allow_downloads;
     772                 :            :       gboolean state2_expected_active;
     773                 :            :     }
     774                 :          1 :   transitions[] =
     775                 :            :     {
     776                 :            :       /* Transition from definitely metered to definitely unmetered. */
     777                 :            :       { { &connection_metered, }, TRUE, FALSE,
     778                 :            :         { &connection_unmetered, }, TRUE, TRUE },
     779                 :            :       /* Transition from maybe metered to definitely unmetered. */
     780                 :            :       { { &connection_maybe_metered, }, TRUE, FALSE,
     781                 :            :         { &connection_unmetered, }, TRUE, TRUE },
     782                 :            :       /* Transition from maybe metered to definitely unmetered. */
     783                 :            :       { { &connection_maybe_metered, }, TRUE, FALSE,
     784                 :            :         { &connection_unmetered, }, TRUE, TRUE },
     785                 :            :       /* Transition from definitely metered to definitely unmetered, but with
     786                 :            :        * downloads disabled. */
     787                 :            :       { { &connection_metered_no_downloads, }, FALSE, FALSE,
     788                 :            :         { &connection_unmetered_no_downloads, }, FALSE, FALSE },
     789                 :            :       /* Transition from unmetered to metered (but with downloads allowed). */
     790                 :            :       { { &connection_unmetered, }, TRUE, TRUE,
     791                 :            :         { &connection_metered_allow_downloads, }, TRUE, TRUE },
     792                 :            :       /* Transition from metered to metered (but with downloads allowed). */
     793                 :            :       { { &connection_metered, }, TRUE, FALSE,
     794                 :            :         { &connection_metered_allow_downloads, }, TRUE, TRUE },
     795                 :            :       /* Transition from two definitely metered connections to having both of
     796                 :            :        * them definitely unmetered. */
     797                 :            :       { { &connection_metered, &connection_metered }, TRUE, FALSE,
     798                 :            :         { &connection_unmetered, &connection_unmetered, }, TRUE, TRUE },
     799                 :            :       /* Transition from two definitely metered connections to having one of
     800                 :            :        * them definitely unmetered. */
     801                 :            :       { { &connection_metered, &connection_metered }, TRUE, FALSE,
     802                 :            :         { &connection_metered, &connection_unmetered, }, TRUE, FALSE },
     803                 :            :       /* Transition from a metered and an unmetered connection to having one of
     804                 :            :        * them allow downloads. */
     805                 :            :       { { &connection_unmetered, &connection_metered }, TRUE, FALSE,
     806                 :            :         { &connection_unmetered, &connection_metered_allow_downloads, }, TRUE, TRUE },
     807                 :            :       /* Transition from a selection of connections to various unmetered ones
     808                 :            :        * with downloads disallowed. allow-downloads must become false. */
     809                 :            :       { { &connection_unmetered, &connection_metered, &connection_unmetered }, TRUE, FALSE,
     810                 :            :         { &connection_unmetered_no_downloads, &connection_unmetered_no_downloads, &connection_unmetered_no_downloads }, FALSE, FALSE },
     811                 :            :     };
     812                 :            : 
     813         [ +  + ]:         11 :   for (gsize i = 0; i < G_N_ELEMENTS (transitions); i++)
     814                 :            :     {
     815                 :            :       /* We only care about transitions within a given connection in this test,
     816                 :            :        * so the set of connections available in states 1 and 2 must be the same */
     817         [ +  + ]:         40 :       for (gsize j = 0; j < G_N_ELEMENTS (transitions[i].state1_connections); j++)
     818         [ -  + ]:         30 :         g_assert ((transitions[i].state1_connections[j] == NULL) ==
     819                 :            :                   (transitions[i].state2_connections[j] == NULL));
     820                 :            : 
     821                 :         10 :       g_test_message ("Transition test %" G_GSIZE_FORMAT " of %" G_GSIZE_FORMAT,
     822                 :            :                       i + 1, G_N_ELEMENTS (transitions));
     823                 :            : 
     824                 :         10 :       gboolean initial_allow_downloads = mws_scheduler_get_allow_downloads (fixture->scheduler);
     825                 :            : 
     826                 :            :       /* Set up the connections in state 1. */
     827                 :         10 :       g_autoptr(GHashTable) state1_connections =
     828                 :         10 :           g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
     829                 :            : 
     830         [ +  + ]:         40 :       for (gsize j = 0; j < G_N_ELEMENTS (transitions[i].state1_connections); j++)
     831                 :            :         {
     832         [ +  + ]:         30 :           if (transitions[i].state1_connections[j] != NULL)
     833                 :            :             {
     834                 :         30 :               g_autofree gchar *id = g_strdup_printf ("connection%" G_GSIZE_FORMAT, j);
     835                 :         15 :               g_hash_table_insert (state1_connections, g_steal_pointer (&id),
     836                 :         15 :                                    transitions[i].state1_connections[j]);
     837                 :            :             }
     838                 :            :         }
     839                 :            : 
     840                 :         10 :       mws_connection_monitor_dummy_update_connections (MWS_CONNECTION_MONITOR_DUMMY (fixture->connection_monitor),
     841                 :            :                                                        state1_connections, NULL);
     842                 :            : 
     843         [ +  + ]:         10 :       if (transitions[i].state1_expected_allow_downloads != initial_allow_downloads)
     844   [ +  -  +  -  :          2 :         mws_signal_logger_assert_emission_pop (fixture->scheduler_signals,
                   +  - ]
     845                 :            :                                                fixture->scheduler,
     846                 :            :                                                "notify::allow-downloads", NULL);
     847         [ -  + ]:         10 :       mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
     848                 :            : 
     849                 :            :       /* Add a single entry to the scheduler. */
     850                 :         20 :       g_autoptr(MwsScheduleEntry) entry = mws_schedule_entry_new (":owner.1");
     851                 :            : 
     852                 :         20 :       g_autoptr(GPtrArray) entry_array = g_ptr_array_new_with_free_func (NULL);
     853                 :         10 :       g_ptr_array_add (entry_array, entry);
     854                 :            : 
     855                 :         10 :       mws_scheduler_update_entries (fixture->scheduler, entry_array, NULL, &local_error);
     856         [ -  + ]:         10 :       g_assert_no_error (local_error);
     857                 :         10 :       assert_entries_changed_signals (fixture, entry_array, NULL,
     858         [ +  + ]:         10 :                                       (transitions[i].state1_expected_active ? entry_array : NULL),
     859                 :            :                                       NULL, NULL);
     860                 :            : 
     861                 :            :       /* Change to the next connection state. */
     862         [ +  + ]:         40 :       for (gsize j = 0; j < G_N_ELEMENTS (transitions[i].state2_connections); j++)
     863                 :            :         {
     864         [ +  + ]:         30 :           if (transitions[i].state2_connections[j] != NULL)
     865                 :            :             {
     866                 :         30 :               g_autofree gchar *id = g_strdup_printf ("connection%" G_GSIZE_FORMAT, j);
     867                 :         15 :               mws_connection_monitor_dummy_update_connection (MWS_CONNECTION_MONITOR_DUMMY (fixture->connection_monitor),
     868                 :            :                                                               id, transitions[i].state2_connections[j]);
     869                 :            :             }
     870                 :            :         }
     871                 :            : 
     872         [ +  + ]:         10 :       if (transitions[i].state2_expected_allow_downloads != transitions[i].state1_expected_allow_downloads)
     873   [ +  -  +  -  :          2 :         mws_signal_logger_assert_emission_pop (fixture->scheduler_signals,
                   +  - ]
     874                 :            :                                                fixture->scheduler,
     875                 :            :                                                "notify::allow-downloads", NULL);
     876                 :            : 
     877         [ +  + ]:         10 :       if (transitions[i].state1_expected_active == transitions[i].state2_expected_active)
     878         [ -  + ]:          4 :         mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
     879         [ -  + ]:          6 :       else if (transitions[i].state1_expected_active)
     880                 :          0 :         assert_entries_changed_signals (fixture, NULL, NULL, NULL, entry_array, NULL);
     881                 :            :       else
     882                 :          6 :         assert_entries_changed_signals (fixture, NULL, NULL, entry_array, NULL, NULL);
     883                 :            : 
     884                 :            :       /* Change back to the previous connection state. The entry’s scheduled
     885                 :            :        * state should always be the same as before. */
     886         [ +  + ]:         40 :       for (gsize j = 0; j < G_N_ELEMENTS (transitions[i].state1_connections); j++)
     887                 :            :         {
     888         [ +  + ]:         30 :           if (transitions[i].state1_connections[j] != NULL)
     889                 :            :             {
     890                 :         30 :               g_autofree gchar *id = g_strdup_printf ("connection%" G_GSIZE_FORMAT, j);
     891                 :         15 :               mws_connection_monitor_dummy_update_connection (MWS_CONNECTION_MONITOR_DUMMY (fixture->connection_monitor),
     892                 :            :                                                               id, transitions[i].state1_connections[j]);
     893                 :            :             }
     894                 :            :         }
     895                 :            : 
     896         [ +  + ]:         10 :       if (transitions[i].state1_expected_allow_downloads != transitions[i].state2_expected_allow_downloads)
     897   [ +  -  +  -  :          2 :         mws_signal_logger_assert_emission_pop (fixture->scheduler_signals,
                   +  - ]
     898                 :            :                                                fixture->scheduler,
     899                 :            :                                                "notify::allow-downloads", NULL);
     900                 :            : 
     901         [ +  + ]:         10 :       if (transitions[i].state1_expected_active == transitions[i].state2_expected_active)
     902         [ -  + ]:          4 :         mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
     903         [ +  - ]:          6 :       else if (transitions[i].state2_expected_active)
     904                 :          6 :         assert_entries_changed_signals (fixture, NULL, NULL, NULL, entry_array, NULL);
     905                 :            :       else
     906                 :          0 :         assert_entries_changed_signals (fixture, NULL, NULL, entry_array, NULL, NULL);
     907                 :            : 
     908                 :            :       /* Clean up. */
     909                 :         10 :       teardown (fixture, test_data);
     910                 :         10 :       setup (fixture, test_data);
     911                 :            :     }
     912                 :          1 : }
     913                 :            : 
     914                 :            : static GTimeZone *
     915                 :         34 : time_zone_new (const gchar *tz_str)
     916                 :            : {
     917         [ +  + ]:         34 :   if (tz_str != NULL)
     918                 :            :     {
     919                 :            : #if GLIB_CHECK_VERSION(2, 68, 0)
     920                 :         14 :       g_autoptr(GTimeZone) tz = g_time_zone_new_identifier (tz_str);
     921         [ -  + ]:         14 :       g_assert_nonnull (tz);
     922                 :         14 :       return g_steal_pointer (&tz);
     923                 :            : #else
     924                 :            :       return g_time_zone_new (tz_str);
     925                 :            : #endif
     926                 :            :     }
     927                 :            :   else
     928                 :            :     {
     929                 :         20 :       return g_time_zone_new_utc ();
     930                 :            :     }
     931                 :            : }
     932                 :            : 
     933                 :            : /* Test the transitions between different tariffs, checking whether they cause a
     934                 :            :  * single entry to be scheduled/unscheduled appropriately. Each test vector
     935                 :            :  * provides two states of a set of tariffs, and two times. Either the set of
     936                 :            :  * tariffs can vary, or the time can vary in a single vector — not both. The
     937                 :            :  * test starts in state 1 at time 1, checks whether the entry is scheduled
     938                 :            :  * appropriately, transitions to state 2 at time 2, checks again, and then
     939                 :            :  * transitions back to state 1 at time 1 to test the reverse transition (and
     940                 :            :  * checks again). */
     941                 :            : static void
     942                 :          1 : test_scheduler_scheduling_tariff (Fixture       *fixture,
     943                 :            :                                   gconstpointer  test_data)
     944                 :            : {
     945                 :            :   /* A tariff which is normally unmetered, but has a period from 01:00–02:00
     946                 :            :    * each day which has zero capacity limit. */
     947                 :          2 :   g_autoptr(GPtrArray) tariff1_periods = g_ptr_array_new_with_free_func (NULL);
     948                 :            : 
     949                 :          2 :   g_autoptr(GDateTime) tariff1_period1_start = g_date_time_new_utc (2018, 1, 1, 0, 0, 0);
     950                 :          2 :   g_autoptr(GDateTime) tariff1_period1_end = g_date_time_new_utc (2018, 1, 2, 0, 0, 0);
     951                 :          2 :   g_autoptr(MwtPeriod) tariff1_period1 = mwt_period_new (tariff1_period1_start,
     952                 :            :                                                          tariff1_period1_end,
     953                 :            :                                                          MWT_PERIOD_REPEAT_DAY,
     954                 :            :                                                          1,
     955                 :            :                                                          "capacity-limit", G_MAXUINT64,
     956                 :            :                                                          NULL);
     957                 :          1 :   g_ptr_array_add (tariff1_periods, tariff1_period1);
     958                 :            : 
     959                 :          2 :   g_autoptr(GDateTime) tariff1_period2_start = g_date_time_new_utc (2018, 1, 1, 1, 0, 0);
     960                 :          2 :   g_autoptr(GDateTime) tariff1_period2_end = g_date_time_new_utc (2018, 1, 1, 2, 0, 0);
     961                 :          2 :   g_autoptr(MwtPeriod) tariff1_period2 = mwt_period_new (tariff1_period2_start,
     962                 :            :                                                          tariff1_period2_end,
     963                 :            :                                                          MWT_PERIOD_REPEAT_DAY,
     964                 :            :                                                          1,
     965                 :            :                                                          "capacity-limit", G_GUINT64_CONSTANT (0),
     966                 :            :                                                          NULL);
     967                 :          1 :   g_ptr_array_add (tariff1_periods, tariff1_period2);
     968                 :            : 
     969                 :          2 :   g_autoptr(MwtTariff) tariff1 = mwt_tariff_new ("tariff1", tariff1_periods);
     970                 :            : 
     971                 :            :   /* A tariff which is always unmetered (infinite capacity limit). */
     972                 :          2 :   g_autoptr(GPtrArray) tariff_unmetered_periods = g_ptr_array_new_with_free_func (NULL);
     973                 :            : 
     974                 :          2 :   g_autoptr(GDateTime) tariff_unmetered_period1_start = g_date_time_new_utc (2018, 1, 1, 0, 0, 0);
     975                 :          2 :   g_autoptr(GDateTime) tariff_unmetered_period1_end = g_date_time_new_utc (2018, 1, 2, 0, 0, 0);
     976                 :          2 :   g_autoptr(MwtPeriod) tariff_unmetered_period1 = mwt_period_new (tariff_unmetered_period1_start,
     977                 :            :                                                                   tariff_unmetered_period1_end,
     978                 :            :                                                                   MWT_PERIOD_REPEAT_DAY,
     979                 :            :                                                                   1,
     980                 :            :                                                                   "capacity-limit", G_MAXUINT64,
     981                 :            :                                                                   NULL);
     982                 :          1 :   g_ptr_array_add (tariff_unmetered_periods, tariff_unmetered_period1);
     983                 :            : 
     984                 :          2 :   g_autoptr(MwtTariff) tariff_unmetered = mwt_tariff_new ("tariff_unmetered",
     985                 :            :                                                           tariff_unmetered_periods);
     986                 :            : 
     987                 :            :   /* A tariff which is normally metered, but has a period from 01:30–02:00
     988                 :            :    * each day which has unlimited capacity. */
     989                 :          2 :   g_autoptr(GPtrArray) tariff2_periods = g_ptr_array_new_with_free_func (NULL);
     990                 :            : 
     991                 :          2 :   g_autoptr(GDateTime) tariff2_period1_start = g_date_time_new_utc (2018, 1, 1, 0, 0, 0);
     992                 :          2 :   g_autoptr(GDateTime) tariff2_period1_end = g_date_time_new_utc (2018, 1, 2, 0, 0, 0);
     993                 :          2 :   g_autoptr(MwtPeriod) tariff2_period1 = mwt_period_new (tariff2_period1_start,
     994                 :            :                                                          tariff2_period1_end,
     995                 :            :                                                          MWT_PERIOD_REPEAT_DAY,
     996                 :            :                                                          1,
     997                 :            :                                                          "capacity-limit", G_GUINT64_CONSTANT (0),
     998                 :            :                                                          NULL);
     999                 :          1 :   g_ptr_array_add (tariff2_periods, tariff2_period1);
    1000                 :            : 
    1001                 :          2 :   g_autoptr(GDateTime) tariff2_period2_start = g_date_time_new_utc (2018, 1, 1, 1, 30, 0);
    1002                 :          2 :   g_autoptr(GDateTime) tariff2_period2_end = g_date_time_new_utc (2018, 1, 1, 2, 30, 0);
    1003                 :          2 :   g_autoptr(MwtPeriod) tariff2_period2 = mwt_period_new (tariff2_period2_start,
    1004                 :            :                                                          tariff2_period2_end,
    1005                 :            :                                                          MWT_PERIOD_REPEAT_DAY,
    1006                 :            :                                                          1,
    1007                 :            :                                                          "capacity-limit", G_MAXUINT64,
    1008                 :            :                                                          NULL);
    1009                 :          1 :   g_ptr_array_add (tariff2_periods, tariff2_period2);
    1010                 :            : 
    1011                 :          2 :   g_autoptr(MwtTariff) tariff2 = mwt_tariff_new ("tariff2", tariff2_periods);
    1012                 :            : 
    1013                 :            :   /* A tariff has a single period and no recurrence. */
    1014                 :          2 :   g_autoptr(GPtrArray) tariff3_periods = g_ptr_array_new_with_free_func (NULL);
    1015                 :            : 
    1016                 :          2 :   g_autoptr(GDateTime) tariff3_period1_start = g_date_time_new_utc (2018, 1, 1, 0, 0, 0);
    1017                 :          2 :   g_autoptr(GDateTime) tariff3_period1_end = g_date_time_new_utc (2018, 1, 2, 0, 0, 0);
    1018                 :          2 :   g_autoptr(MwtPeriod) tariff3_period1 = mwt_period_new (tariff3_period1_start,
    1019                 :            :                                                          tariff3_period1_end,
    1020                 :            :                                                          MWT_PERIOD_REPEAT_NONE,
    1021                 :            :                                                          0,
    1022                 :            :                                                          "capacity-limit", G_GUINT64_CONSTANT (0),
    1023                 :            :                                                          NULL);
    1024                 :          1 :   g_ptr_array_add (tariff3_periods, tariff3_period1);
    1025                 :            : 
    1026                 :          2 :   g_autoptr(MwtTariff) tariff3 = mwt_tariff_new ("tariff3", tariff3_periods);
    1027                 :            : 
    1028                 :          1 :   g_autoptr(GError) local_error = NULL;
    1029                 :            :   struct
    1030                 :            :     {
    1031                 :            :       /* We use a fixed limit of 3 tariffs in these tests, because we should
    1032                 :            :        * be able to simulate any condition we care about using 3. Typically,
    1033                 :            :        * systems will have only 1 active connection (maybe 2 if they have wired
    1034                 :            :        * and Wi-Fi enabled at the same time). Each tariff listed here will
    1035                 :            :        * result in a separate network connection being configured.
    1036                 :            :        *
    1037                 :            :        * The dates/times below are given in UTC, and then converted to the
    1038                 :            :        * timezone given as @state1_tz or @state2_tz. This means they represent
    1039                 :            :        * the same instant as the UTC time, and hence @state1_expected_active
    1040                 :            :        * and @state2_expected_active are independent of @state1_tz and
    1041                 :            :        * @state2_tz. */
    1042                 :            :       const MwtTariff *state1_tariffs[3];
    1043                 :            :       gint state1_year;
    1044                 :            :       gint state1_month;
    1045                 :            :       gint state1_day;
    1046                 :            :       gint state1_hour;
    1047                 :            :       gint state1_minute;
    1048                 :            :       gdouble state1_seconds;
    1049                 :            :       const gchar *state1_tz;
    1050                 :            :       gboolean state1_expected_active;
    1051                 :            :       const MwtTariff *state2_tariffs[3];
    1052                 :            :       gint state2_year;
    1053                 :            :       gint state2_month;
    1054                 :            :       gint state2_day;
    1055                 :            :       gint state2_hour;
    1056                 :            :       gint state2_minute;
    1057                 :            :       gdouble state2_seconds;
    1058                 :            :       const gchar *state2_tz;
    1059                 :            :       gboolean state2_expected_active;
    1060                 :            :     }
    1061                 :          1 :   transitions[] =
    1062                 :            :     {
    1063                 :            :       /* Transition from an unmetered period to a metered period in the same
    1064                 :            :        * tariff. */
    1065                 :            :       { { tariff1, }, 2018, 2, 3, 17, 0, 0, NULL, TRUE,
    1066                 :            :         { tariff1, }, 2018, 2, 4, 1, 30, 0, NULL, FALSE },
    1067                 :            :       /* Transition within an unmetered period, and within a metered period. */
    1068                 :            :       { { tariff1, }, 2018, 2, 3, 17, 0, 0, NULL, TRUE,
    1069                 :            :         { tariff1, }, 2018, 2, 3, 17, 30, 0, NULL, TRUE },
    1070                 :            :       { { tariff1, }, 2018, 2, 4, 1, 15, 0, NULL, FALSE,
    1071                 :            :         { tariff1, }, 2018, 2, 4, 1, 30, 0, NULL, FALSE },
    1072                 :            :       /* Transition between an unmetered tariff and a normal tariff, for a time
    1073                 :            :        * which is and is not in the tariff metered period. */
    1074                 :            :       { { tariff_unmetered, }, 2018, 2, 3, 17, 0, 0, NULL, TRUE,
    1075                 :            :         { tariff1, }, 2018, 2, 3, 17, 0, 0, NULL, TRUE },
    1076                 :            :       { { tariff_unmetered, }, 2018, 2, 4, 1, 30, 0, NULL, TRUE,
    1077                 :            :         { tariff1, }, 2018, 2, 4, 1, 30, 0, NULL, FALSE },
    1078                 :            :       /* Transition from an unmetered period and a metered period in one
    1079                 :            :        * tariff, to a metered period in one and an unmetered period in another.
    1080                 :            :        * Since the scheduler requires *all* connections to be safe in order to
    1081                 :            :        * start a download, this leads to very limited active downloads. */
    1082                 :            :       { { tariff1, tariff2, }, 2018, 2, 4, 0, 30, 0, NULL, FALSE,
    1083                 :            :         { tariff1, tariff2, }, 2018, 2, 4, 1, 15, 0, NULL, FALSE },
    1084                 :            :       { { tariff1, tariff2, }, 2018, 2, 4, 1, 45, 0, NULL, FALSE,
    1085                 :            :         { tariff1, tariff2, }, 2018, 2, 4, 1, 59, 0, NULL, FALSE },
    1086                 :            :       { { tariff1, tariff2, }, 2018, 2, 4, 2, 0, 0, NULL, TRUE,
    1087                 :            :         { tariff1, tariff2, }, 2018, 2, 4, 2, 15, 0, NULL, TRUE },
    1088                 :            :       /* Timezone change in metered and unmetered periods. The underlying UTC
    1089                 :            :        * time may change at the same time, to either give a timezone change only
    1090                 :            :        * (where the underlying UTC time is the same) or a timezone and time
    1091                 :            :        * change such that the resolved wall clock time stays the same */
    1092                 :            :       /* Change timezone only: */
    1093                 :            :       { { tariff1, }, 2018, 2, 3, 15, 30, 0, "Europe/London" /* UTC+0 */, TRUE,
    1094                 :            :         { tariff1, }, 2018, 2, 3, 15, 30, 0, "Australia/Brisbane" /* UTC+10 */, TRUE },
    1095                 :            :       /* Keep wall clock time unchanged: */
    1096                 :            :       { { tariff1, }, 2018, 2, 3, 1, 30, 0, "Australia/Brisbane" /* UTC+10 */, FALSE,
    1097                 :            :         { tariff1, }, 2018, 2, 3, 11, 30, 0, "Europe/London" /* UTC+0 */, TRUE },
    1098                 :            : 
    1099                 :            :       { { tariff1, }, 2018, 2, 3, 16, 30, 0, "Europe/London" /* UTC+0 */, TRUE,
    1100                 :            :         { tariff1, }, 2018, 2, 3, 16, 30, 0, "Europe/Berlin" /* UTC+1 */, TRUE },
    1101                 :            :       { { tariff1, }, 2018, 2, 3, 15, 30, 0, "Europe/Berlin" /* UTC+1 */, TRUE,
    1102                 :            :         { tariff1, }, 2018, 2, 3, 16, 30, 0, "Europe/London" /* UTC+0 */, TRUE },
    1103                 :            : 
    1104                 :            :       { { tariff1, }, 2018, 2, 3, 1, 30, 0, "Europe/London" /* UTC+0 */, FALSE,
    1105                 :            :         { tariff1, }, 2018, 2, 3, 1, 30, 0, "Australia/Brisbane" /* UTC+10 */, FALSE },
    1106                 :            :       { { tariff1, }, 2018, 2, 2, 15, 30, 0, "Australia/Brisbane" /* UTC+10 */, TRUE,
    1107                 :            :         { tariff1, }, 2018, 2, 3, 1, 30, 0, "Europe/London" /* UTC+0 */, FALSE },
    1108                 :            : 
    1109                 :            :       { { tariff1, }, 2018, 2, 3, 1, 30, 0, "Europe/London", FALSE,
    1110                 :            :         { tariff1, }, 2018, 2, 3, 1, 30, 0, "Europe/Isle_of_Man", FALSE },
    1111                 :            :       /* Test different times in (and out of) a tariff which doesn’t repeat. */
    1112                 :            :       { { tariff3, }, 2017, 12, 30, 0, 0, 0, NULL, TRUE,
    1113                 :            :         { tariff3, }, 2018, 1, 1, 1, 30, 0, NULL, FALSE },
    1114                 :            :       { { tariff3, }, 2018, 1, 1, 0, 0, 0, NULL, FALSE,
    1115                 :            :         { tariff3, }, 2018, 1, 2, 0, 0, 0, NULL, TRUE },
    1116                 :            :     };
    1117                 :            : 
    1118         [ +  + ]:         18 :   for (gsize i = 0; i < G_N_ELEMENTS (transitions); i++)
    1119                 :            :     {
    1120                 :            :       /* Set up the dates/times. The date/time figures in the test vector are
    1121                 :            :        * given in UTC and converted to the stated timezone, so they represent
    1122                 :            :        * the same instant as the UTC time, but with a likely different wall
    1123                 :            :        * clock time. */
    1124                 :         34 :       g_autoptr(GTimeZone) state1_tz = time_zone_new (transitions[i].state1_tz);
    1125                 :         34 :       g_autoptr(GDateTime) state1_time_utc = g_date_time_new_utc (transitions[i].state1_year,
    1126                 :            :                                                                   transitions[i].state1_month,
    1127                 :            :                                                                   transitions[i].state1_day,
    1128                 :            :                                                                   transitions[i].state1_hour,
    1129                 :            :                                                                   transitions[i].state1_minute,
    1130                 :            :                                                                   transitions[i].state1_seconds);
    1131                 :         34 :       g_autoptr(GDateTime) state1_time = g_date_time_to_timezone (state1_time_utc,
    1132                 :            :                                                                   state1_tz);
    1133                 :            : 
    1134                 :         34 :       g_autoptr(GTimeZone) state2_tz = time_zone_new (transitions[i].state2_tz);
    1135                 :         34 :       g_autoptr(GDateTime) state2_time_utc = g_date_time_new_utc (transitions[i].state2_year,
    1136                 :            :                                                                   transitions[i].state2_month,
    1137                 :            :                                                                   transitions[i].state2_day,
    1138                 :            :                                                                   transitions[i].state2_hour,
    1139                 :            :                                                                   transitions[i].state2_minute,
    1140                 :            :                                                                   transitions[i].state2_seconds);
    1141                 :         34 :       g_autoptr(GDateTime) state2_time = g_date_time_to_timezone (state2_time_utc,
    1142                 :            :                                                                   state2_tz);
    1143                 :            : 
    1144                 :            :       /* We only care about transitions within a given connection in this test,
    1145                 :            :        * so the set of connections available in states 1 and 2 must be the same */
    1146         [ +  + ]:         68 :       for (gsize j = 0; j < G_N_ELEMENTS (transitions[i].state1_tariffs); j++)
    1147         [ -  + ]:         51 :         g_assert ((transitions[i].state1_tariffs[j] == NULL) ==
    1148                 :            :                   (transitions[i].state2_tariffs[j] == NULL));
    1149                 :            :       /* Similarly, we can vary either the wall clock time, or the connection
    1150                 :            :        * tariff, between states 1 and 2 — but varying both would give too many
    1151                 :            :        * axes of freedom in the test. Ensure only one varies. */
    1152                 :         17 :       gboolean all_tariffs_equal = TRUE;
    1153         [ +  + ]:         68 :       for (gsize j = 0; j < G_N_ELEMENTS (transitions[i].state1_tariffs); j++)
    1154                 :         51 :         all_tariffs_equal =
    1155         [ +  + ]:         98 :             all_tariffs_equal &&
    1156         [ +  + ]:         47 :             transitions[i].state1_tariffs[j] == transitions[i].state2_tariffs[j];
    1157   [ +  +  -  + ]:         17 :       g_assert (all_tariffs_equal ||
    1158                 :            :                 g_date_time_equal (state1_time, state2_time));
    1159                 :            :       /* We want a monotonic wall clock. */
    1160         [ -  + ]:         17 :       g_assert_cmpint (g_date_time_compare (state1_time, state2_time), <=, 0);
    1161                 :            : 
    1162                 :         17 :       g_test_message ("Transition test %" G_GSIZE_FORMAT " of %" G_GSIZE_FORMAT,
    1163                 :            :                       i + 1, G_N_ELEMENTS (transitions));
    1164                 :            : 
    1165                 :         17 :       gboolean initial_allow_downloads = mws_scheduler_get_allow_downloads (fixture->scheduler);
    1166                 :            : 
    1167                 :            :       /* Set the time in state 1. */
    1168                 :         17 :       mws_clock_dummy_set_time_zone (MWS_CLOCK_DUMMY (fixture->clock), state1_tz);
    1169                 :         17 :       mws_clock_dummy_set_time (MWS_CLOCK_DUMMY (fixture->clock), state1_time);
    1170                 :            : 
    1171                 :            :       /* Set up the connections in state 1. */
    1172                 :         17 :       g_autoptr(GHashTable) state1_connections =
    1173                 :         17 :           g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
    1174                 :            : 
    1175         [ +  + ]:         68 :       for (gsize j = 0; j < G_N_ELEMENTS (transitions[i].state1_tariffs); j++)
    1176                 :            :         {
    1177         [ +  + ]:         51 :           if (transitions[i].state1_tariffs[j] != NULL)
    1178                 :            :             {
    1179                 :         40 :               g_autofree gchar *id = g_strdup_printf ("connection%" G_GSIZE_FORMAT, j);
    1180                 :            : 
    1181                 :         40 :               g_autofree MwsConnectionDetails *connection = g_new0 (MwsConnectionDetails, 1);
    1182                 :         20 :               connection->metered = MWS_METERED_NO;
    1183                 :         20 :               connection->allow_downloads_when_metered = FALSE;
    1184                 :         20 :               connection->allow_downloads = TRUE;
    1185                 :         20 :               connection->tariff = transitions[i].state1_tariffs[j];
    1186                 :            : 
    1187                 :         20 :               g_hash_table_insert (state1_connections, g_steal_pointer (&id),
    1188                 :            :                                    g_steal_pointer (&connection));
    1189                 :            :             }
    1190                 :            :         }
    1191                 :            : 
    1192                 :         17 :       mws_connection_monitor_dummy_update_connections (MWS_CONNECTION_MONITOR_DUMMY (fixture->connection_monitor),
    1193                 :            :                                                        state1_connections, NULL);
    1194                 :            : 
    1195                 :            :       /* Due to the test setup, we always expect allow-downloads=true. */
    1196         [ -  + ]:         17 :       if (!initial_allow_downloads)
    1197   [ #  #  #  #  :          0 :         mws_signal_logger_assert_emission_pop (fixture->scheduler_signals,
                   #  # ]
    1198                 :            :                                                fixture->scheduler,
    1199                 :            :                                                "notify::allow-downloads", NULL);
    1200         [ -  + ]:         17 :       mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
    1201                 :            : 
    1202                 :            :       /* Add a single entry to the scheduler. */
    1203                 :         34 :       g_autoptr(MwsScheduleEntry) entry = mws_schedule_entry_new (":owner.1");
    1204                 :            : 
    1205                 :         34 :       g_autoptr(GPtrArray) entry_array = g_ptr_array_new_with_free_func (NULL);
    1206                 :         17 :       g_ptr_array_add (entry_array, entry);
    1207                 :            : 
    1208                 :         17 :       mws_scheduler_update_entries (fixture->scheduler, entry_array, NULL, &local_error);
    1209         [ -  + ]:         17 :       g_assert_no_error (local_error);
    1210                 :         17 :       assert_entries_changed_signals (fixture, entry_array, NULL,
    1211         [ +  + ]:         17 :                                       (transitions[i].state1_expected_active ? entry_array : NULL),
    1212                 :            :                                       NULL, NULL);
    1213                 :            : 
    1214                 :            :       /* Change to the next time. */
    1215                 :         17 :       mws_clock_dummy_set_time_zone (MWS_CLOCK_DUMMY (fixture->clock), state2_tz);
    1216                 :         17 :       mws_clock_dummy_set_time (MWS_CLOCK_DUMMY (fixture->clock), state2_time);
    1217                 :            : 
    1218                 :            :       /* Change to the next connection state. */
    1219         [ +  + ]:         68 :       for (gsize j = 0; j < G_N_ELEMENTS (transitions[i].state2_tariffs); j++)
    1220                 :            :         {
    1221         [ +  + ]:         51 :           if (transitions[i].state2_tariffs[j] != NULL)
    1222                 :            :             {
    1223                 :         40 :               g_autofree gchar *id = g_strdup_printf ("connection%" G_GSIZE_FORMAT, j);
    1224                 :            : 
    1225                 :         40 :               g_autofree MwsConnectionDetails *connection = g_new0 (MwsConnectionDetails, 1);
    1226                 :         20 :               connection->metered = MWS_METERED_NO;
    1227                 :         20 :               connection->allow_downloads_when_metered = FALSE;
    1228                 :         20 :               connection->allow_downloads = TRUE;
    1229                 :         20 :               connection->tariff = transitions[i].state2_tariffs[j];
    1230                 :            : 
    1231                 :         20 :               mws_connection_monitor_dummy_update_connection (MWS_CONNECTION_MONITOR_DUMMY (fixture->connection_monitor),
    1232                 :            :                                                               id, connection);
    1233                 :            :             }
    1234                 :            :         }
    1235                 :            : 
    1236         [ +  + ]:         17 :       if (transitions[i].state1_expected_active == transitions[i].state2_expected_active)
    1237         [ -  + ]:         11 :         mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
    1238         [ +  + ]:          6 :       else if (transitions[i].state1_expected_active)
    1239                 :          4 :         assert_entries_changed_signals (fixture, NULL, NULL, NULL, entry_array, NULL);
    1240                 :            :       else
    1241                 :          2 :         assert_entries_changed_signals (fixture, NULL, NULL, entry_array, NULL, NULL);
    1242                 :            : 
    1243                 :            :       /* Change back to the previous time. This might not be very realistic,
    1244                 :            :        * but the scheduler should deal with it. */
    1245                 :         17 :       mws_clock_dummy_set_time_zone (MWS_CLOCK_DUMMY (fixture->clock), state1_tz);
    1246                 :         17 :       mws_clock_dummy_set_time (MWS_CLOCK_DUMMY (fixture->clock), state1_time);
    1247                 :            : 
    1248                 :            :       /* Change back to the previous connection state. The entry’s scheduled
    1249                 :            :        * state should always be the same as before. */
    1250         [ +  + ]:         68 :       for (gsize j = 0; j < G_N_ELEMENTS (transitions[i].state1_tariffs); j++)
    1251                 :            :         {
    1252         [ +  + ]:         51 :           if (transitions[i].state1_tariffs[j] != NULL)
    1253                 :            :             {
    1254                 :         40 :               g_autofree gchar *id = g_strdup_printf ("connection%" G_GSIZE_FORMAT, j);
    1255                 :            : 
    1256                 :         40 :               g_autofree MwsConnectionDetails *connection = g_new0 (MwsConnectionDetails, 1);
    1257                 :         20 :               connection->metered = MWS_METERED_NO;
    1258                 :         20 :               connection->allow_downloads_when_metered = FALSE;
    1259                 :         20 :               connection->allow_downloads = TRUE;
    1260                 :         20 :               connection->tariff = transitions[i].state1_tariffs[j];
    1261                 :            : 
    1262                 :         20 :               mws_connection_monitor_dummy_update_connection (MWS_CONNECTION_MONITOR_DUMMY (fixture->connection_monitor),
    1263                 :            :                                                               id, connection);
    1264                 :            :             }
    1265                 :            :         }
    1266                 :            : 
    1267         [ +  + ]:         17 :       if (transitions[i].state1_expected_active == transitions[i].state2_expected_active)
    1268         [ -  + ]:         11 :         mws_signal_logger_assert_no_emissions (fixture->scheduler_signals);
    1269         [ +  + ]:          6 :       else if (transitions[i].state2_expected_active)
    1270                 :          2 :         assert_entries_changed_signals (fixture, NULL, NULL, NULL, entry_array, NULL);
    1271                 :            :       else
    1272                 :          4 :         assert_entries_changed_signals (fixture, NULL, NULL, entry_array, NULL, NULL);
    1273                 :            : 
    1274                 :            :       /* Clean up. */
    1275                 :         17 :       teardown (fixture, test_data);
    1276                 :         17 :       setup (fixture, test_data);
    1277                 :            :     }
    1278                 :          1 : }
    1279                 :            : 
    1280                 :            : int
    1281                 :          1 : main (int    argc,
    1282                 :            :       char **argv)
    1283                 :            : {
    1284                 :          1 :   setlocale (LC_ALL, "");
    1285                 :          1 :   g_test_init (&argc, &argv, NULL);
    1286                 :            : 
    1287                 :          1 :   const TestData standard_data =
    1288                 :            :     {
    1289                 :            :       .max_active_entries = 1,
    1290                 :            :     };
    1291                 :          1 :   const TestData max_active_entries_data =
    1292                 :            :     {
    1293                 :            :       .max_active_entries = 2,
    1294                 :            :     };
    1295                 :            : 
    1296                 :          1 :   g_test_add_func ("/scheduler/construction", test_scheduler_construction);
    1297                 :          1 :   g_test_add ("/scheduler/entries", Fixture, &standard_data, setup,
    1298                 :            :               test_scheduler_entries, teardown);
    1299                 :          1 :   g_test_add ("/scheduler/entries/remove-for-owner", Fixture,
    1300                 :            :               &standard_data, setup,
    1301                 :            :               test_scheduler_entries_remove_for_owner, teardown);
    1302                 :          1 :   g_test_add ("/scheduler/properties", Fixture, &standard_data, setup,
    1303                 :            :               test_scheduler_properties, teardown);
    1304                 :          1 :   g_test_add ("/scheduler/scheduling/entry-priorities", Fixture,
    1305                 :            :               &standard_data, setup,
    1306                 :            :               test_scheduler_scheduling_entry_priorities, teardown);
    1307                 :          1 :   g_test_add ("/scheduler/scheduling/peer-priorities", Fixture,
    1308                 :            :               &standard_data, setup,
    1309                 :            :               test_scheduler_scheduling_peer_priorities, teardown);
    1310                 :          1 :   g_test_add ("/scheduler/scheduling/max-active-entries", Fixture,
    1311                 :            :               &max_active_entries_data, setup,
    1312                 :            :               test_scheduler_scheduling_max_active_entries, teardown);
    1313                 :          1 :   g_test_add ("/scheduler/scheduling/peer-vanished", Fixture,
    1314                 :            :               &standard_data, setup,
    1315                 :            :               test_scheduler_scheduling_peer_vanished, teardown);
    1316                 :          1 :   g_test_add ("/scheduler/scheduling/metered-connection", Fixture,
    1317                 :            :               &standard_data, setup,
    1318                 :            :               test_scheduler_scheduling_metered_connection, teardown);
    1319                 :          1 :   g_test_add ("/scheduler/scheduling/tariff", Fixture,
    1320                 :            :               &standard_data, setup,
    1321                 :            :               test_scheduler_scheduling_tariff, teardown);
    1322                 :            : 
    1323                 :          1 :   return g_test_run ();
    1324                 :            : }

Generated by: LCOV version 1.16