/* * Copyright © 2025 Khalid Abu Shawarib * * SPDX-License-Identifier: GPL-3.0-or-later * * Authors: Khalid Abu Shawarib */ #include "test-utilities.h" #include #include #include #include #include #include typedef struct { GdkPixbuf *pixbuf; gboolean done; GError *error; } ThumbnailCallbackData; static void thumbnail_data_free (ThumbnailCallbackData *data) { g_clear_error (&data->error); g_clear_object (&data->pixbuf); } G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (ThumbnailCallbackData, thumbnail_data_free) static void thumbnailing_done_cb (GObject *source_object, GAsyncResult *res, gpointer data) { g_autoptr (GError) error = NULL; ThumbnailCallbackData *cb_data = data; cb_data->pixbuf = nautilus_create_thumbnail_finish (res, &error); cb_data->error = g_steal_pointer (&error); cb_data->done = TRUE; } static gboolean file_has_valid_thumbnail_path (GFile *file, const gchar *thumbnail_path) { g_autoptr (GError) error = NULL; g_autoptr (GFileInfo) info = g_file_query_info (file, G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID "," G_FILE_ATTRIBUTE_THUMBNAILING_FAILED, G_FILE_QUERY_INFO_NONE, NULL, &error); g_assert_no_error (error); if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID) && g_strcmp0 (g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH), thumbnail_path) == 0 && !g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)) { return TRUE; } return FALSE; } static guint64 get_file_mime_type_and_mtime (GFile *file, gchar **mime_type) { g_autoptr (GFileInfo) info = g_file_query_info (file, "standard::*,time::*", G_FILE_QUERY_INFO_NONE, NULL, NULL); if (mime_type != NULL) { g_clear_pointer (mime_type, g_free); *mime_type = g_strdup (g_file_info_get_content_type (info)); } return g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); } static void test_thumbnail_test_queue (void) { GStrvBuilder *strv_builder = g_strv_builder_new (); g_auto (GStrv) files_hier = NULL; const guint64 old_mtime = 1; g_autolist (GFile) image_locations = NULL; guint n_images = MAX (g_get_num_processors (), 4); g_autoptr (GPtrArray) cancellables_array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (uint i = 0; i < 2 * n_images; i++) { g_strv_builder_take (strv_builder, g_strdup_printf ("image_%u.png", i + 1)); } files_hier = g_strv_builder_unref_to_strv (strv_builder); image_locations = file_hierarchy_get_files_list (files_hier, "", FALSE); for (GList *l = image_locations; l != NULL; l = l->next) { make_image_file_with_mtime (l->data, old_mtime, FALSE); } /* Add to thumbnailing queue */ for (GList *l = image_locations; l != NULL; l = l->next) { GFile *image_location = l->data; g_autofree gchar *uri = g_file_get_uri (image_location); g_autofree gchar *mime_type = NULL; guint64 mtime = get_file_mime_type_and_mtime (image_location, &mime_type); GCancellable *cancellable = g_cancellable_new (); nautilus_create_thumbnail_async (uri, mime_type, mtime, cancellable, NULL, NULL); g_ptr_array_add (cancellables_array, cancellable); } /* Cancel all then enqeue again */ g_ptr_array_foreach (cancellables_array, (GFunc) g_cancellable_cancel, NULL); for (GList *l = image_locations; l != NULL; l = l->next) { GFile *image_location = l->data; g_autofree gchar *uri = g_file_get_uri (image_location); g_autofree gchar *mime_type = NULL; guint64 mtime = get_file_mime_type_and_mtime (image_location, &mime_type); nautilus_create_thumbnail_async (uri, mime_type, mtime, NULL, NULL, NULL); } for (GList *l = image_locations; l != NULL; l = l->next) { GFile *image_location = l->data; g_autofree gchar *image_uri = g_file_get_uri (image_location); g_autofree gchar *thumbnail_path = nautilus_thumbnail_get_path_for_uri (image_uri); g_autoptr (GFile) thumbnail_location = g_file_new_for_path (thumbnail_path); ITER_CONTEXT_WHILE (!file_has_valid_thumbnail_path (image_location, thumbnail_path)); g_assert_true (file_has_valid_thumbnail_path (image_location, thumbnail_path)); g_assert_true (g_file_query_exists (thumbnail_location, NULL)); g_file_delete (thumbnail_location, NULL, NULL); } test_clear_tmp_dir (); } static void test_thumbnail_image (void) { g_autoptr (GFile) image_location = g_file_new_build_filename (test_get_tmp_dir (), "Image.png", NULL); g_autofree gchar *uri = g_file_get_uri (image_location); make_image_file (image_location, FALSE); g_autofree gchar *mime_type = NULL; guint64 mtime = get_file_mime_type_and_mtime (image_location, &mime_type); g_assert_true (nautilus_thumbnail_is_mimetype_limited_by_size (mime_type)); g_autofree gchar *thumbnail_path = nautilus_thumbnail_get_path_for_uri (uri); g_autoptr (GFile) thumbnail_location = g_file_new_for_path (thumbnail_path); g_auto (ThumbnailCallbackData) thumbnailing_data = { NULL, FALSE, NULL }; nautilus_create_thumbnail_async (uri, mime_type, mtime, NULL, thumbnailing_done_cb, &thumbnailing_data); ITER_CONTEXT_WHILE (!thumbnailing_data.done); g_assert_true (g_file_query_exists (thumbnail_location, NULL)); g_assert_nonnull (thumbnailing_data.pixbuf); g_assert_no_error (thumbnailing_data.error); g_assert_true (file_has_valid_thumbnail_path (image_location, thumbnail_path)); g_assert_cmpint (gdk_pixbuf_get_height (thumbnailing_data.pixbuf), ==, DEFAULT_IMAGE_SIZE); g_assert_cmpint (gdk_pixbuf_get_width (thumbnailing_data.pixbuf), ==, DEFAULT_IMAGE_SIZE); g_assert_true (g_file_delete (thumbnail_location, NULL, NULL)); test_clear_tmp_dir (); } static void test_thumbnail_old_time (void) { const guint64 old_mtime = 1; g_autoptr (GFile) image_location = g_file_new_build_filename (test_get_tmp_dir (), "Image.png", NULL); g_autofree gchar *uri = g_file_get_uri (image_location); make_image_file_with_mtime (image_location, old_mtime, FALSE); g_autofree gchar *mime_type = NULL; guint64 mtime = get_file_mime_type_and_mtime (image_location, &mime_type); g_assert_true (nautilus_thumbnail_is_mimetype_limited_by_size (mime_type)); g_assert_cmpint (mtime, ==, old_mtime); g_autofree gchar *thumbnail_path = nautilus_thumbnail_get_path_for_uri (uri); g_autoptr (GFile) thumbnail_location = g_file_new_for_path (thumbnail_path); g_auto (ThumbnailCallbackData) thumbnailing_data = { NULL, FALSE, NULL }; nautilus_create_thumbnail_async (uri, mime_type, mtime, NULL, thumbnailing_done_cb, &thumbnailing_data); ITER_CONTEXT_WHILE (!thumbnailing_data.done); g_assert_true (g_file_query_exists (thumbnail_location, NULL)); g_assert_nonnull (thumbnailing_data.pixbuf); g_assert_no_error (thumbnailing_data.error); g_assert_true (file_has_valid_thumbnail_path (image_location, thumbnail_path)); g_assert_cmpint (gdk_pixbuf_get_height (thumbnailing_data.pixbuf), ==, DEFAULT_IMAGE_SIZE); g_assert_cmpint (gdk_pixbuf_get_width (thumbnailing_data.pixbuf), ==, DEFAULT_IMAGE_SIZE); g_assert_true (g_file_delete (thumbnail_location, NULL, NULL)); test_clear_tmp_dir (); } static void test_thumbnail_image_no_mtime (void) { g_autoptr (GFile) image_location = g_file_new_build_filename (test_get_tmp_dir (), "Image.png", NULL); const guint64 mtime = 1; g_autofree gchar *uri = g_file_get_uri (image_location); g_autofree gchar *mime_type = NULL; make_image_file_with_mtime (image_location, mtime, FALSE); get_file_mime_type_and_mtime (image_location, &mime_type); g_assert_true (nautilus_thumbnail_is_mimetype_limited_by_size (mime_type)); g_autofree gchar *thumbnail_path = nautilus_thumbnail_get_path_for_uri (uri); g_autoptr (GFile) thumbnail_location = g_file_new_for_path (thumbnail_path); g_auto (ThumbnailCallbackData) thumbnailing_data = { NULL, FALSE, NULL }; nautilus_create_thumbnail_async (uri, mime_type, 0, NULL, thumbnailing_done_cb, &thumbnailing_data); ITER_CONTEXT_WHILE (!thumbnailing_data.done); g_assert_true (g_file_query_exists (thumbnail_location, NULL)); g_assert_nonnull (thumbnailing_data.pixbuf); g_assert_no_error (thumbnailing_data.error); g_assert_true (file_has_valid_thumbnail_path (image_location, thumbnail_path)); g_assert_cmpint (gdk_pixbuf_get_height (thumbnailing_data.pixbuf), ==, DEFAULT_IMAGE_SIZE); g_assert_cmpint (gdk_pixbuf_get_width (thumbnailing_data.pixbuf), ==, DEFAULT_IMAGE_SIZE); g_assert_true (g_file_delete (thumbnail_location, NULL, NULL)); test_clear_tmp_dir (); } static void test_thumbnail_cancel (void) { g_autoptr (GFile) image_location = g_file_new_build_filename (test_get_tmp_dir (), "Image.png", NULL); guint64 mtime = 1; g_autofree gchar *uri = g_file_get_uri (image_location); make_image_file_with_mtime (image_location, mtime, FALSE); g_autofree gchar *mime_type = NULL; mtime = get_file_mime_type_and_mtime (image_location, &mime_type); g_assert_true (nautilus_thumbnail_is_mimetype_limited_by_size (mime_type)); g_autofree gchar *thumbnail_path = nautilus_thumbnail_get_path_for_uri (uri); g_autoptr (GFile) thumbnail_location = g_file_new_for_path (thumbnail_path); g_autoptr (GCancellable) cancellable = g_cancellable_new (); g_auto (ThumbnailCallbackData) thumbnailing_data = { NULL, FALSE, NULL }; nautilus_create_thumbnail_async (uri, mime_type, mtime, cancellable, thumbnailing_done_cb, &thumbnailing_data); g_usleep (50 * 1000); g_cancellable_cancel (cancellable); ITER_CONTEXT_WHILE (!thumbnailing_data.done); g_assert_nonnull (thumbnailing_data.error); g_assert_error (thumbnailing_data.error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_assert_null (thumbnailing_data.pixbuf); test_clear_tmp_dir (); } static void test_thumbnail_overwrite (void) { g_autoptr (GFile) image_location = g_file_new_build_filename (test_get_tmp_dir (), "Image.png", NULL); g_autofree gchar *uri = g_file_get_uri (image_location); make_image_file (image_location, FALSE); g_autofree gchar *mime_type = NULL; guint64 mtime = get_file_mime_type_and_mtime (image_location, &mime_type); g_assert_true (nautilus_thumbnail_is_mimetype_limited_by_size (mime_type)); g_autofree gchar *thumbnail_path = nautilus_thumbnail_get_path_for_uri (uri); g_autoptr (GFile) thumbnail_location = g_file_new_for_path (thumbnail_path); g_auto (ThumbnailCallbackData) thumbnailing_data1 = { NULL, FALSE, NULL }; g_auto (ThumbnailCallbackData) thumbnailing_data2 = { NULL, FALSE, NULL }; nautilus_create_thumbnail_async (uri, mime_type, mtime, NULL, thumbnailing_done_cb, &thumbnailing_data1); /* Overwrite the image file while thumbnailing is in progress */ g_usleep (50 * 1000); make_image_file (image_location, FALSE); mtime = get_file_mime_type_and_mtime (image_location, &mime_type); nautilus_create_thumbnail_async (uri, mime_type, mtime, NULL, thumbnailing_done_cb, &thumbnailing_data2); ITER_CONTEXT_WHILE (!thumbnailing_data1.done); ITER_CONTEXT_WHILE (!thumbnailing_data2.done); g_assert_true (g_file_query_exists (thumbnail_location, NULL)); g_assert_true (file_has_valid_thumbnail_path (image_location, thumbnail_path)); g_assert_nonnull (thumbnailing_data1.pixbuf); g_assert_no_error (thumbnailing_data1.error); g_assert_nonnull (thumbnailing_data2.pixbuf); g_assert_no_error (thumbnailing_data2.error); g_assert_cmpint (gdk_pixbuf_get_height (thumbnailing_data1.pixbuf), ==, DEFAULT_IMAGE_SIZE); g_assert_cmpint (gdk_pixbuf_get_width (thumbnailing_data1.pixbuf), ==, DEFAULT_IMAGE_SIZE); g_assert_cmpint (gdk_pixbuf_get_height (thumbnailing_data2.pixbuf), ==, DEFAULT_IMAGE_SIZE); g_assert_cmpint (gdk_pixbuf_get_width (thumbnailing_data2.pixbuf), ==, DEFAULT_IMAGE_SIZE); g_assert_true (g_file_delete (thumbnail_location, NULL, NULL)); test_clear_tmp_dir (); } static void test_thumbnail_rethumbnail_failed_on_mtime_change (void) { g_autoptr (GFile) image_location = g_file_new_build_filename (test_get_tmp_dir (), "Image.png", NULL); g_autofree gchar *uri = g_file_get_uri (image_location); g_autofree gchar *thumbnail_path = nautilus_thumbnail_get_path_for_uri (uri); g_autoptr (GFile) thumbnail_location = g_file_new_for_path (thumbnail_path); g_autoptr (GError) error = NULL; g_auto (ThumbnailCallbackData) thumbnailing_data = { NULL, FALSE, NULL }; g_file_set_contents (g_file_peek_path (image_location), "not a valid image", -1, &error); g_assert_no_error (error); /* Produce a failed thumbnail */ nautilus_create_thumbnail_async (uri, "image/png", 0, NULL, thumbnailing_done_cb, &thumbnailing_data); ITER_CONTEXT_WHILE (!thumbnailing_data.done); g_assert_false (g_file_query_exists (thumbnail_location, NULL)); g_assert_null (thumbnailing_data.pixbuf); g_assert_nonnull (thumbnailing_data.error); thumbnail_data_free (&thumbnailing_data); /* Replace the corrupt image with a valid one, updating mtime */ make_image_file (image_location, FALSE); g_autofree gchar *mime_type = NULL; guint64 new_mtime = get_file_mime_type_and_mtime (image_location, &mime_type); /* Attempt thumbnailing again */ thumbnailing_data = (ThumbnailCallbackData) { NULL, FALSE, NULL }; nautilus_create_thumbnail_async (uri, mime_type, new_mtime, NULL, thumbnailing_done_cb, &thumbnailing_data); ITER_CONTEXT_WHILE (!thumbnailing_data.done); g_assert_true (g_file_query_exists (thumbnail_location, NULL)); g_assert_nonnull (thumbnailing_data.pixbuf); g_assert_no_error (thumbnailing_data.error); test_clear_tmp_dir (); } static GFile * make_text_file (void) { const gchar *text = "Hello, world!"; g_autoptr (GFile) text_location = g_file_new_build_filename (test_get_tmp_dir (), "Document.txt", NULL); g_autoptr (GError) error = NULL; g_autoptr (GFileOutputStream) stream = g_file_create (text_location, G_FILE_CREATE_NONE, NULL, &error); g_assert_no_error (error); g_output_stream_write_all (G_OUTPUT_STREAM (stream), text, strlen (text), NULL, NULL, &error); g_assert_no_error (error); g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, &error); g_assert_no_error (error); /* Speed up thumbnailing */ g_file_set_attribute_uint64 (text_location, G_FILE_ATTRIBUTE_TIME_MODIFIED, 1, G_FILE_QUERY_INFO_NONE, NULL, NULL); return g_steal_pointer (&text_location); } static void test_thumbnail_text (void) { g_autoptr (GFile) text_location = make_text_file (); g_autofree gchar *uri = g_file_get_uri (text_location); g_autofree gchar *mime_type = NULL; guint64 mtime = get_file_mime_type_and_mtime (text_location, &mime_type); g_assert_false (nautilus_thumbnail_is_mimetype_limited_by_size (mime_type)); if (nautilus_can_thumbnail (uri, mime_type, mtime)) { g_test_skip ("System has a thumbnailer for text files, but this test is meant to test" "a file that doesn't have one."); test_clear_tmp_dir (); return; } g_autofree gchar *thumbnail_path = nautilus_thumbnail_get_path_for_uri (uri); g_autoptr (GFile) thumbnail_location = g_file_new_for_path (thumbnail_path); g_auto (ThumbnailCallbackData) thumbnailing_data = { NULL, FALSE, NULL }; nautilus_create_thumbnail_async (uri, mime_type, mtime, NULL, thumbnailing_done_cb, &thumbnailing_data); ITER_CONTEXT_WHILE (!thumbnailing_data.done); g_assert_false (g_file_query_exists (thumbnail_location, NULL)); g_assert_null (thumbnailing_data.pixbuf); g_assert_nonnull (thumbnailing_data.error); g_assert_false (file_has_valid_thumbnail_path (text_location, thumbnail_path)); test_clear_tmp_dir (); } int main (int argc, char *argv[]) { nautilus_ensure_extension_points (); gtk_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); g_test_set_nonfatal_assertions (); if (nautilus_application_is_sandboxed ()) { g_message ("Cannot run thumbnails tests inside a sandbox."); return 77; } if (!nautilus_can_thumbnail ("file:///tmp/nautilus_tests/image.png", "image/png", 1)) { g_message ("System has no thumbnailer for PNGs, but most tests are meant to test " "thumbnailing images."); return 77; } /* HACK: Pretend to be a snap to disable sandboxing in libgnome-desktop */ g_setenv ("SNAP_NAME", "1", TRUE); g_test_add_func ("/thumbnail/invalid/text", test_thumbnail_text); g_test_add_func ("/thumbnail/single/image", test_thumbnail_image); g_test_add_func ("/thumbnail/single/old-time", test_thumbnail_old_time); g_test_add_func ("/thumbnail/single/no-mtime", test_thumbnail_image_no_mtime); g_test_add_func ("/thumbnail/single/cancel", test_thumbnail_cancel); g_test_add_func ("/thumbnail/single/overwrite", test_thumbnail_overwrite); g_test_add_func ("/thumbnail/single/rethumbnail/failed-on-mtime-change", test_thumbnail_rethumbnail_failed_on_mtime_change); g_test_add_func ("/thumbnail/queue/deprioritize", test_thumbnail_test_queue); return g_test_run (); }