/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 2013, Openismus GmbH * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * * Authors: Tristan Van Berkom */ #include "evolution-data-server-config.h" #ifdef HAVE_SYS_WAIT_H #include #endif #include #include #include #include #include "test-book-cache-utils.h" static const gchar *args_data_dir = NULL; void tcu_read_args (gint argc, gchar **argv) { gint ii; for (ii = 0; ii < argc; ii++) { if (g_strcmp0 (argv[ii], "--data-dir") == 0) { if (ii + 1 < argc) args_data_dir = argv[ii + 1]; break; } } } gchar * tcu_new_vcard_from_test_case (const gchar *case_name) { gchar *filename; gchar *case_filename; GFile * file; GError *error = NULL; gchar *vcard; case_filename = g_strdup_printf ("%s.vcf", case_name); /* In the case of installed tests, they run in ${pkglibexecdir}/installed-tests * and the vcards are installed in ${pkglibexecdir}/installed-tests/vcards */ if (g_getenv ("TEST_INSTALLED_SERVICES") != NULL) { filename = g_build_filename (INSTALLED_TEST_DIR, "vcards", case_filename, NULL); } else { if (!args_data_dir) { g_warning ("Data directory not set, pass it with `--data-dir PATH`"); exit(1); } filename = g_build_filename (args_data_dir, case_filename, NULL); } file = g_file_new_for_path (filename); if (!g_file_load_contents (file, NULL, &vcard, NULL, NULL, &error)) g_error ( "failed to read test contact file '%s': %s", filename, error->message); g_free (case_filename); g_free (filename); g_object_unref (file); return vcard; } EContact * tcu_new_contact_from_test_case (const gchar *case_name) { gchar *vcard; EContact *contact = NULL; vcard = tcu_new_vcard_from_test_case (case_name); if (vcard) contact = e_contact_new_from_vcard (vcard); g_free (vcard); if (!contact) g_error ( "failed to construct contact from test case '%s'", case_name); return contact; } void tcu_add_contact_from_test_case (TCUFixture *fixture, const gchar *case_name, EContact **ret_contact) { EContact *contact; GError *error = NULL; contact = tcu_new_contact_from_test_case (case_name); if (!e_book_cache_put_contact (fixture->book_cache, contact, case_name, 0, E_CACHE_IS_ONLINE, NULL, &error)) g_error ("Failed to add contact: %s", error->message); if (ret_contact) *ret_contact = g_object_ref (contact); g_clear_object (&contact); } static void delete_work_directory (const gchar *filename) { /* XXX Instead of complex error checking here, we should ideally use * a recursive GDir / g_unlink() function. * * We cannot use GFile and the recursive delete function without * corrupting our contained D-Bus environment with service files * from the OS. */ const gchar *argv[] = { "/bin/rm", "-rf", filename, NULL }; gboolean spawn_succeeded; gint exit_status; spawn_succeeded = g_spawn_sync ( NULL, (gchar **) argv, NULL, 0, NULL, NULL, NULL, NULL, &exit_status, NULL); g_assert_true (spawn_succeeded); #ifndef G_OS_WIN32 g_assert_true (WIFEXITED (exit_status)); g_assert_cmpint (WEXITSTATUS (exit_status), ==, 0); #else g_assert_cmpint (exit_status, ==, 0); #endif } ESourceBackendSummarySetup * tcu_setup_empty_book (void) { ESourceBackendSummarySetup *setup; ESource *scratch; GError *error = NULL; scratch = e_source_new_with_uid ("test-source", NULL, &error); if (!scratch) g_error ("Error creating scratch source: %s", error ? error->message : "Unknown error"); /* This is a bit of a cheat */ setup = g_object_new (E_TYPE_SOURCE_BACKEND_SUMMARY_SETUP, "source", scratch, NULL); e_source_backend_summary_setup_set_summary_fields ( setup, /* We don't use this field in our tests anyway */ E_CONTACT_FILE_AS, 0); g_object_unref (scratch); return setup; } static void e164_changed_cb (EBookCache *book_cache, EContact *contact, gboolean is_replace, gpointer user_data) { TCUFixture *fixture = user_data; if (is_replace) fixture->n_locale_changes++; else fixture->n_add_changes++; } void tcu_fixture_setup (TCUFixture *fixture, gconstpointer user_data) { TCUClosure *closure = (TCUClosure *) user_data; ESourceBackendSummarySetup *setup = NULL; gchar *filename, *directory; GError *error = NULL; /* Cleanup from last test */ directory = g_build_filename (g_get_tmp_dir (), "test-book-cache", NULL); delete_work_directory (directory); g_free (directory); filename = g_build_filename (g_get_tmp_dir (), "test-book-cache", "cache.db", NULL); if (closure->setup_summary) setup = closure->setup_summary (); fixture->book_cache = e_book_cache_new_full (filename, NULL, setup, NULL, &error); g_clear_object (&setup); if (!fixture->book_cache) g_error ("Failed to create the EBookCache: %s", error->message); g_free (filename); g_signal_connect (fixture->book_cache, "e164-changed", G_CALLBACK (e164_changed_cb), fixture); } void tcu_fixture_teardown (TCUFixture *fixture, gconstpointer user_data) { g_object_unref (fixture->book_cache); } void tcu_cursor_fixture_setup (TCUCursorFixture *fixture, gconstpointer user_data) { TCUFixture *base_fixture = (TCUFixture *) fixture; TCUCursorClosure *data = (TCUCursorClosure *) user_data; EContactField sort_fields[] = { E_CONTACT_FAMILY_NAME, E_CONTACT_GIVEN_NAME }; EBookCursorSortType sort_types[] = { data->sort_type, data->sort_type }; GSList *contacts = NULL; GSList *extra_list = NULL; GError *error = NULL; gint ii; gchar *sexp = NULL; tcu_fixture_setup (base_fixture, user_data); if (data->locale) tcu_cursor_fixture_set_locale (fixture, data->locale); else tcu_cursor_fixture_set_locale (fixture, "en_US.UTF-8"); for (ii = 0; ii < N_SORTED_CONTACTS; ii++) { gchar *case_name = g_strdup_printf ("sorted-%d", ii + 1); gchar *vcard; EContact *contact; vcard = tcu_new_vcard_from_test_case (case_name); contact = e_contact_new_from_vcard (vcard); contacts = g_slist_prepend (contacts, contact); extra_list = g_slist_prepend (extra_list, case_name); g_free (vcard); fixture->contacts[ii] = g_object_ref (contact); } if (!e_book_cache_put_contacts (base_fixture->book_cache, contacts, extra_list, NULL, E_CACHE_IS_ONLINE, NULL, &error)) { /* Dont complain here, we re-use the same addressbook for multiple tests * and we can't add the same contacts twice */ if (g_error_matches (error, E_CACHE_ERROR, E_CACHE_ERROR_CONSTRAINT)) g_clear_error (&error); else g_error ("Failed to add test contacts: %s", error->message); } g_slist_free_full (contacts, g_object_unref); g_slist_free_full (extra_list, g_free); /* Allow a surrounding fixture setup to add a query here */ if (fixture->query) { sexp = e_book_query_to_string (fixture->query); e_book_query_unref (fixture->query); fixture->query = NULL; } fixture->cursor = e_book_cache_cursor_new ( base_fixture->book_cache, sexp, sort_fields, sort_types, 2, &error); if (!fixture->cursor) g_error ("Failed to create cursor: %s\n", error->message); g_free (sexp); } void tcu_cursor_fixture_filtered_setup (TCUCursorFixture *fixture, gconstpointer user_data) { fixture->query = e_book_query_field_test (E_CONTACT_EMAIL, E_BOOK_QUERY_ENDS_WITH, ".com"); tcu_cursor_fixture_setup (fixture, user_data); } void tcu_cursor_fixture_teardown (TCUCursorFixture *fixture, gconstpointer user_data) { TCUFixture *base_fixture = (TCUFixture *) fixture; gint ii; for (ii = 0; ii < N_SORTED_CONTACTS; ii++) { if (fixture->contacts[ii]) g_object_unref (fixture->contacts[ii]); } e_book_cache_cursor_free (base_fixture->book_cache, fixture->cursor); tcu_fixture_teardown (base_fixture, user_data); } void tcu_cursor_fixture_set_locale (TCUCursorFixture *fixture, const gchar *locale) { TCUFixture *base_fixture = (TCUFixture *) fixture; GError *error = NULL; if (!e_book_cache_set_locale (base_fixture->book_cache, locale, NULL, &error)) g_error ("Failed to set locale: %s", error->message); } static gint find_contact_data (EBookCacheSearchData *data, const gchar *uid) { return g_strcmp0 (data->uid, uid); } void tcu_assert_contacts_order_slist (GSList *results, GSList *uids) { gint position = -1; GSList *link, *l; /* Assert that all passed UIDs are found in the * results, and that those UIDs are in the * specified order. */ for (l = uids; l; l = l->next) { const gchar *uid = l->data; gint new_position; link = g_slist_find_custom (results, uid, (GCompareFunc) find_contact_data); if (!link) g_error ("Specified uid '%s' was not found in results", uid); new_position = g_slist_position (results, link); g_assert_cmpint (new_position, >, position); position = new_position; } } void tcu_assert_contacts_order (GSList *results, const gchar *first_uid, ...) { GSList *uids = NULL; gchar *uid; va_list args; g_assert_true (first_uid); uids = g_slist_append (uids, (gpointer) first_uid); va_start (args, first_uid); uid = va_arg (args, gchar *); while (uid) { uids = g_slist_append (uids, uid); uid = va_arg (args, gchar *); } va_end (args); tcu_assert_contacts_order_slist (results, uids); g_slist_free (uids); } void tcu_print_results (const GSList *results) { const GSList *link; if (g_getenv ("TEST_DEBUG") == NULL) return; g_print ("\nPRINTING RESULTS:\n"); for (link = results; link; link = link->next) { EBookCacheSearchData *data = link->data; g_print ("\n%s\n", data->vcard); } g_print ("\nRESULT LIST_FINISHED\n"); } /******************************************** * Move By Test Helpers ********************************************/ #define DEBUG_FIXTURE 0 static TCUStepData * step_test_new_internal (const gchar *test_path, const gchar *locale, gboolean empty_book) { TCUStepData *data; data = g_slice_new0 (TCUStepData); data->parent.locale = g_strdup (locale); data->parent.sort_type = E_BOOK_CURSOR_SORT_ASCENDING; if (empty_book) data->parent.parent.setup_summary = tcu_setup_empty_book; data->path = g_strdup (test_path); return data; } static void step_test_free (TCUStepData *data) { GList *l; g_free (data->path); g_free ((gchar *) data->parent.locale); for (l = data->assertions; l; l = l->next) { TCUStepAssertion *assertion = l->data; g_free (assertion->locale); g_slice_free (TCUStepAssertion, assertion); } g_list_free (data->assertions); g_slice_free (TCUStepData, data); } TCUStepData * tcu_step_test_new (const gchar *test_prefix, const gchar *test_path, const gchar *locale, gboolean empty_book) { TCUStepData *data; gchar *path; path = g_strconcat (test_prefix, test_path, NULL); data = step_test_new_internal (path, locale, empty_book); g_free (path); return data; } TCUStepData * tcu_step_test_new_full (const gchar *test_prefix, const gchar *test_path, const gchar *locale, gboolean empty_book, EBookCursorSortType sort_type) { TCUStepData *data; gchar *path; path = g_strconcat (test_prefix, test_path, NULL); data = step_test_new_internal (path, locale, empty_book); data->parent.sort_type = sort_type; g_free (path); return data; } static void test_cursor_move_teardown (TCUCursorFixture *fixture, gconstpointer user_data) { TCUStepData *data = (TCUStepData *) user_data; tcu_cursor_fixture_teardown (fixture, user_data); step_test_free (data); } static void assert_step (TCUCursorFixture *fixture, TCUStepData *data, TCUStepAssertion *assertion, GSList *results, gint n_results, gboolean expect_results) { GSList *uids = NULL; gint ii, expected = 0; /* Count the number of really expected results */ for (ii = 0; ii < ABS (assertion->count); ii++) { gint index = assertion->expected[ii]; if (index < 0) break; expected++; } g_assert_cmpint (n_results, ==, expected); if (!expect_results) { g_assert_cmpint (g_slist_length (results), ==, 0); return; } /* Assert the exact amount of requested results */ g_assert_cmpint (g_slist_length (results), ==, expected); #if DEBUG_FIXTURE g_print ( "%s: Constructing expected result list for a fetch of %d: ", data->path, assertion->count); #endif for (ii = 0; ii < ABS (assertion->count); ii++) { gint index = assertion->expected[ii]; gchar *uid; if (index < 0) break; uid = (gchar *) e_contact_get_const (fixture->contacts[index], E_CONTACT_UID); uids = g_slist_append (uids, uid); #if DEBUG_FIXTURE g_print ("%s ", uid); #endif } #if DEBUG_FIXTURE g_print ("\n"); #endif tcu_assert_contacts_order_slist (results, uids); g_slist_free (uids); } static void test_step (TCUCursorFixture *fixture, gconstpointer user_data) { TCUFixture *base_fixture = (TCUFixture *) fixture; TCUStepData *data = (TCUStepData *) user_data; GSList *results = NULL; GError *error = NULL; gint n_results; EBookCacheCursorOrigin origin; GList *l; gboolean reset = TRUE; for (l = data->assertions; l; l = l->next) { TCUStepAssertion *assertion = l->data; if (assertion->locale) { gint n_locale_changes = base_fixture->n_locale_changes; if (!e_book_cache_set_locale (base_fixture->book_cache, assertion->locale, NULL, &error)) g_error ("Failed to set locale: %s", error->message); n_locale_changes = (base_fixture->n_locale_changes - n_locale_changes); /* Only check for contact changes is phone numbers are supported, * contact changes only happen because of e164 number interpretations. */ if (e_phone_number_is_supported () && assertion->count != -1 && assertion->count != n_locale_changes) g_error ("Expected %d e164 numbers to change, %d actually changed.", assertion->count, n_locale_changes); reset = TRUE; continue; } /* For the first call to e_book_cache_cursor_step(), * or the first reset after locale change, set the origin accordingly. */ if (reset) { if (assertion->count < 0) origin = E_BOOK_CACHE_CURSOR_ORIGIN_END; else origin = E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN; reset = FALSE; } else { origin = E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT; } /* Try only fetching the contacts but not moving the cursor */ n_results = e_book_cache_cursor_step ( base_fixture->book_cache, fixture->cursor, E_BOOK_CACHE_CURSOR_STEP_FETCH, origin, assertion->count, &results, NULL, &error); if (n_results < 0) g_error ("Error fetching cursor results: %s", error->message); tcu_print_results (results); assert_step (fixture, data, assertion, results, n_results, TRUE); g_slist_free_full (results, e_book_cache_search_data_free); results = NULL; /* Do it again, this time only moving the cursor */ n_results = e_book_cache_cursor_step ( base_fixture->book_cache, fixture->cursor, E_BOOK_CACHE_CURSOR_STEP_MOVE, origin, assertion->count, &results, NULL, &error); if (n_results < 0) g_error ("Error fetching cursor results: %s", error->message); tcu_print_results (results); assert_step (fixture, data, assertion, results, n_results, FALSE); g_slist_free_full (results, e_book_cache_search_data_free); results = NULL; } } static void step_test_add_assertion_va_list (TCUStepData *data, gint count, va_list args) { TCUStepAssertion *assertion = g_slice_new0 (TCUStepAssertion); gint expected, ii = 0; assertion->count = count; #if DEBUG_FIXTURE g_print ("Adding assertion to test %d: %s\n", ii + 1, data->path); g_print (" Test will move by %d and expect: ", count); #endif for (ii = 0; ii < ABS (count); ii++) { expected = va_arg (args, gint); #if DEBUG_FIXTURE g_print ("%d ", expected); #endif assertion->expected[ii] = expected - 1; } #if DEBUG_FIXTURE g_print ("\n"); #endif data->assertions = g_list_append (data->assertions, assertion); } /* A positive of negative 'count' value * followed by ABS (count) UID indexes. * * The indexes start at 1 so that they * are easier to match up with the chart * in data-test-utils.h */ void tcu_step_test_add_assertion (TCUStepData *data, gint count, ...) { va_list args; va_start (args, count); step_test_add_assertion_va_list (data, count, args); va_end (args); } void tcu_step_test_change_locale (TCUStepData *data, const gchar *locale, gint expected_changes) { TCUStepAssertion *assertion = g_slice_new0 (TCUStepAssertion); assertion->locale = g_strdup (locale); assertion->count = expected_changes; data->assertions = g_list_append (data->assertions, assertion); } void tcu_step_test_add (TCUStepData *data, gboolean filtered) { data->filtered = filtered; g_test_add ( data->path, TCUCursorFixture, data, filtered ? tcu_cursor_fixture_filtered_setup : tcu_cursor_fixture_setup, test_step, test_cursor_move_teardown); }