Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
test_image_cache_unreadable.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Guillaume Stutin.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel 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
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
28#include "testdb.h"
29
30#include "caches/image_cache.h"
31#include "common/conf.h"
32#include "common/grouping.h"
33#include "common/image.h"
34#include "darktable.h"
35
36#include <glib/gstdio.h>
37#include <math.h>
38#include <stdlib.h> // calloc/free, used directly below
39
40#ifdef _WIN32
41#include "win/main_wrapper.h"
42#endif
43
44static gchar *_dir = NULL;
45static gchar *_library = NULL;
46static gchar *_rcfile = NULL;
47
48/* A library on disk, opened anew for each test: in memory, every connection has a database of
49 * its own and no other connection can lock it; and one successful read holds a read
50 * transaction that the lock cannot pass, so each test needs a connection that has read
51 * nothing yet. */
52static int _setup(void **state __attribute__((unused)))
53{
54 _dir = g_dir_make_tmp("ansel_test_image_cache_unreadable_XXXXXX", NULL);
55 if(IS_NULL_PTR(_dir)) return -1;
56 _library = g_build_filename(_dir, "library.db", NULL);
57 const dt_database_params_t params = { .alternative = _library,
58 .library = NULL,
59 .load_data = FALSE,
60 .has_gui = FALSE,
61 .verbose = FALSE };
63
64 /* Populating a cache entry reads conf (dt_image_film_roll_name()); and a sidecar write is a
65 * job, with no control to run it here. */
66 _rcfile = g_build_filename(_dir, "anselrc", NULL);
67 darktable.conf = (dt_conf_t *)calloc(1, sizeof(dt_conf_t));
69 dt_conf_set_string("write_sidecar_files", "FALSE");
71
73 return 0;
74}
75
76static void _remove_tree(const gchar *dir)
77{
78 GDir *d = g_dir_open(dir, 0, NULL);
79 if(IS_NULL_PTR(d)) return;
80 const char *name = NULL;
81 while((name = g_dir_read_name(d)))
82 {
83 gchar *path = g_build_filename(dir, name, NULL);
84 // the empty backup marker dt_database_open() leaves is read-only, which Windows will not delete
85 g_chmod(path, 0700);
86 if(g_file_test(path, G_FILE_TEST_IS_DIR))
87 _remove_tree(path);
88 else
89 g_remove(path);
90 dt_free(path);
91 }
92 g_dir_close(d);
93 g_rmdir(dir);
94}
95
96static int _teardown(void **state)
97{
100 free(darktable.conf);
101 darktable.conf = NULL;
106 dt_free(_dir);
107 return 0;
108}
109
113static void _make_unreadable(const int32_t imgid)
114{
115 sqlite3 *other = NULL;
116 assert_int_equal(sqlite3_open(_library, &other), SQLITE_OK);
117 assert_int_equal(sqlite3_exec(other, "BEGIN EXCLUSIVE", NULL, NULL, NULL), SQLITE_OK);
118 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
119 // released before asserting, so that a failure does not leave the file locked for the teardown
120 sqlite3_exec(other, "ROLLBACK", NULL, NULL, NULL);
121 sqlite3_close(other);
122
123 assert_null(img);
124 assert_null(dt_image_cache_get(imgid, 'r'));
125}
126
127static gboolean _group_is(const int32_t group_id, const int32_t *members, const int count)
128{
129 GList *got = dt_image_repository_get_group_members(group_id, -1);
130 gboolean same = (g_list_length(got) == (guint)count);
131 for(int k = 0; k < count; k++)
132 same = same && !IS_NULL_PTR(g_list_find(got, GINT_TO_POINTER(members[k])));
133 g_list_free(got);
134 return same;
135}
136
137/* Three images in a group led by `a`; `c`, the first in database order, cannot be read. */
138static void _group_of_three(const char *folder, int32_t *a, int32_t *b, int32_t *c)
139{
140 const int32_t film = testdb_make_film(folder);
141 assert_true(film > 0);
142 *c = testdb_make_image(film, "c.raw");
143 *a = testdb_make_image(film, "a.raw");
144 *b = testdb_make_image(film, "b.raw");
145 assert_true(*a > 0 && *b > 0 && *c > 0);
146 assert_true(dt_image_repository_set_group(*a, *a));
147 assert_true(dt_image_repository_set_group(*b, *a));
148 assert_true(dt_image_repository_set_group(*c, *a));
150}
151
152/* The member that cannot be read follows the new representative with the others: its row moves,
153 * and no group is left named after an image that is no longer in it. */
155{
156 int32_t a = 0;
157 int32_t b = 0;
158 int32_t c = 0;
159 _group_of_three("/testdb/representative", &a, &b, &c);
160
161 assert_int_equal(dt_grouping_change_representative(b), b);
162
163 assert_true(_group_is(b, (int32_t[]){ a, b, c }, 3));
164 assert_true(_group_is(a, NULL, 0));
165
166 // and an image that cannot be read is not made a representative
167 assert_int_equal(dt_grouping_change_representative(c), -1);
168 assert_true(_group_is(b, (int32_t[]){ a, b, c }, 3));
169}
170
171/* The representative leaves: the group takes the name of the first member that can be read, not
172 * of the first member, and the update of the rows still moves the unreadable one into it. */
174{
175 int32_t a = 0;
176 int32_t b = 0;
177 int32_t c = 0;
178 _group_of_three("/testdb/leaving", &a, &b, &c);
179
180 assert_int_equal(dt_grouping_remove_from_group(a), b);
181
182 assert_true(_group_is(b, (int32_t[]){ b, c }, 2));
183 assert_true(_group_is(a, (int32_t[]){ a }, 1));
184}
185
186/* The setters leave an image they cannot read as it is, and say nothing changed. */
188{
189 const int32_t film = testdb_make_film("/testdb/setters");
190 assert_true(film > 0);
191 const int32_t a = testdb_make_image(film, "a.raw");
192 const int32_t c = testdb_make_image(film, "c.raw");
193 assert_true(a > 0 && c > 0);
194 assert_true(dt_image_repository_set_group(a, a));
195 assert_true(dt_image_repository_set_group(c, c));
197
199 assert_true(_group_is(c, (int32_t[]){ c }, 1));
200
201 const dt_image_geoloc_t here = { 2.35, 48.85, 35.0 };
203 dt_image_geoloc_t got = { 0.0, 0.0, 0.0 };
204 dt_image_get_location(c, &got);
205 assert_true(isnan(got.longitude) && isnan(got.latitude) && isnan(got.elevation));
206
207 GList *one = g_list_prepend(NULL, GINT_TO_POINTER(c));
208 dt_image_set_datetime(one, "2026:10:28 12:00:00.000", FALSE);
209
212 assert_null(flagged);
213 g_list_free(one);
214}
215
216/* Moving a file whose duplicate cannot be read: the file moves, the readable row follows it, and
217 * the duplicate's row stays where it was -- with an alert, on stderr here, saying so. */
219{
220 gchar *src = g_build_filename(_dir, "src", NULL);
221 gchar *dst = g_build_filename(_dir, "dst", NULL);
222 assert_int_equal(g_mkdir(src, 0700), 0);
223 assert_int_equal(g_mkdir(dst, 0700), 0);
224 gchar *file = g_build_filename(src, "x.raw", NULL);
225 gchar *moved = g_build_filename(dst, "x.raw", NULL);
226 assert_true(g_file_set_contents(file, "raw", -1, NULL));
227
228 const int32_t from = testdb_make_film(src);
229 const int32_t to = testdb_make_film(dst);
230 assert_true(from > 0 && to > 0);
231 const int32_t a = testdb_make_image(from, "x.raw");
232 assert_true(a > 0);
233 assert_true(dt_image_repository_set_group(a, a));
234 const int32_t dup = dt_image_repository_duplicate(a, 1);
235 assert_true(dup > 0);
236 _make_unreadable(dup);
237
238 assert_int_equal(dt_image_move(a, to), 0);
239
240 assert_true(g_file_test(moved, G_FILE_TEST_EXISTS));
241 assert_int_equal(dt_image_repository_find_by_film_and_filename(to, "x.raw"), a);
242 assert_int_equal(dt_image_repository_find_by_film_and_filename(from, "x.raw"), dup);
243
244 dt_free(moved);
245 dt_free(file);
246 dt_free(dst);
247 dt_free(src);
248}
249
250int main(int argc, char *argv[])
251{
252 const struct CMUnitTest tests[] = {
255 _teardown),
256 cmocka_unit_test_setup_teardown(test_setters_leave_an_unreadable_image_alone, _setup, _teardown),
257 cmocka_unit_test_setup_teardown(test_rename_leaves_an_unreadable_duplicate_behind, _setup, _teardown),
258 };
259 return cmocka_run_group_tests(tests, NULL, NULL);
260}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_conf_cleanup(dt_conf_t *cf)
Free everything cf owns. No dt_conf_*() call is valid afterwards.
void dt_conf_init(dt_conf_t *cf, const char *filename, GSList *override_entries)
Populate cf from filename and from the generated defaults.
void dt_conf_set_string(const char *name, const char *val)
int32_t dt_image_move(const int32_t imgid, const int32_t filmid)
void dt_image_set_location(const int32_t imgid, const dt_image_geoloc_t *geoloc, const gboolean undo_on, const gboolean group_on)
void dt_image_get_location(const int32_t imgid, dt_image_geoloc_t *geoloc)
void dt_image_set_datetime(const GList *imgs, const char *datetime, const gboolean undo_on)
void dt_image_set_monochrome_flag(const int32_t imgid, gboolean monochrome)
void dt_image_xmp_mode_refresh_from_conf(void)
darktable_t darktable
Definition darktable.c:213
dt_database_open_result_t dt_database_open(const dt_database_params_t *params)
Definition database.c:3640
@ DT_DATABASE_OPEN_OK
Definition database.h:78
GtkWidget * folder
destination folder chooser
int dt_grouping_change_representative(const int32_t image_id)
Definition grouping.c:105
int dt_grouping_remove_from_group(const int32_t image_id)
Definition grouping.c:52
void dt_grouping_add_to_group(const int32_t group_id, const int32_t image_id)
Definition grouping.c:40
@ DT_IMAGE_MONOCHROME_PREVIEW
Definition image.h:158
void dt_image_cache_init(const gboolean verbose)
Initialise the cache.
dt_image_t * dt_image_cache_get(const int32_t imgid, char mode)
void dt_image_cache_cleanup(void)
GList * dt_image_repository_get_ids_with_flag_among(GList *imgids, const int flag)
Of the images in imgids, those whose flags carry flag, in row order.
gboolean dt_image_repository_set_group(const int32_t imgid, const int32_t group_id)
Set imgid's group_id.
int32_t dt_image_repository_duplicate(const int32_t imgid, const int32_t newversion)
Duplicate imgid as a new version, returning the new image id (-1 on failure).
int32_t dt_image_repository_find_by_film_and_filename(const int32_t film_id, const char *filename)
The image id for filename inside film roll film_id, or -1.
GList * dt_image_repository_get_group_members(const int32_t group_id, const int32_t exclude_imgid)
Image ids in group group_id.
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
const char * name
Definition pdf.h:90
int main()
Definition prova.c:47
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
const float uint32_t state[4]
struct dt_conf_t * conf
Definition darktable.h:184
The whole configuration state. One instance lives at darktable.conf.
double latitude
Definition image.h:341
double elevation
Definition image.h:341
double longitude
Definition image.h:341
static void _make_unreadable(const int32_t imgid)
static void test_setters_leave_an_unreadable_image_alone(void **state __attribute__((unused)))
static int _teardown(void **state)
static gchar * _rcfile
static void test_rename_leaves_an_unreadable_duplicate_behind(void **state __attribute__((unused)))
static void _remove_tree(const gchar *dir)
static int _setup(void **state __attribute__((unused)))
static void _group_of_three(const char *folder, int32_t *a, int32_t *b, int32_t *c)
static void test_change_representative_moves_an_unreadable_member(void **state __attribute__((unused)))
static void test_remove_from_group_names_the_group_after_a_readable_member(void **state __attribute__((unused)))
static gboolean _group_is(const int32_t group_id, const int32_t *members, const int count)
static gchar * _dir
static gchar * _library
Shared fixture for the src/database repository tests.
static int32_t testdb_make_film(const char *folder)
Definition testdb.h:85
static int testdb_teardown(void **state)
Definition testdb.h:66
static int32_t testdb_make_image(const int32_t film_id, const char *filename)
Definition testdb.h:92