Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
test_import_jobs.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Paolo SANTUCCI.
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
11#include "common/utility.h"
12#include "common/variables.h"
13#include "common/conf.h"
14#include "common/image.h"
15#include "control/control.h"
17#include "darktable.h"
19#include "system/macros.h"
20#include "system/mem_alloc.h"
21
22#include <setjmp.h>
23#include <stdarg.h>
24#include <stddef.h>
25#include <stdint.h>
26#include <stdlib.h>
27#include <fcntl.h>
28#include <cmocka.h>
29#include <glib/gstdio.h>
30
31#ifdef _WIN32
32#include <io.h>
33#define test_dup _dup
34#define test_dup2 _dup2
35#define test_close _close
36#else
37#include <unistd.h>
38#define test_dup dup
39#define test_dup2 dup2
40#define test_close close
41#endif
42
43extern int _import_copy_file(const char *filename, const char *destination, dt_control_import_t *data,
44 gchar *img_path_to_db, size_t pathname_len, GList **discarded);
45
46typedef struct extension_case_t
47{
48 const char *path;
49 const char *extension;
50 const char *variables;
51 const char *replacement;
53
55 { "capture.raw", "raw", "capture|capture|raw", "capture.jpg" },
56 { ".profile.jpg", "jpg", ".profile|.profile|jpg", ".profile.jpg" },
57 { "/a.b/capture", NULL, "capture|capture|", NULL },
58 { "C:\\a.b\\capture", NULL, "capture|capture|", NULL },
59 { ".profile", NULL, ".profile|.profile|", NULL },
60 { "capture.", NULL, "capture.|capture.|", NULL },
61 { "capture", NULL, "capture|capture|", NULL },
62 { "", NULL, "||", NULL },
63 { "/", NULL, "||", NULL },
64 { "\\", NULL, "||", NULL },
65};
66
67static char *_rcfile = NULL;
69static char *_integration_cache = NULL;
70static char *_integration_tmp = NULL;
72
81
82static void _import_completed(const char *source, gboolean success, gpointer user_data)
83{
84 import_completion_t *completion = user_data;
85 g_hash_table_insert(completion->sources, g_strdup(source), GINT_TO_POINTER(success));
86 completion->completed++;
87 completion->succeeded += success;
88}
89
90static void _import_job_disposed(gpointer user_data)
91{
92 import_completion_t *completion = user_data;
93 completion->job_disposed = TRUE;
94}
95
96static void _completion_init(import_completion_t *completion, const int expected)
97{
98 completion->expected = expected;
100}
101
103{
104 g_hash_table_destroy(completion->sources);
105}
106
107static void _remove_tree(const char *path)
108{
109 GDir *directory = g_dir_open(path, 0, NULL);
111 {
112 g_remove(path);
113 return;
114 }
115
116 const char *name = NULL;
117 while((name = g_dir_read_name(directory)))
118 {
119 char *child = g_build_filename(path, name, NULL);
120 _remove_tree(child);
121 dt_free(child);
122 }
124 g_rmdir(path);
125}
126
127static char *_make_jpeg(const char *directory, const char *name, const uint8_t value)
128{
129 char *path = g_build_filename(directory, name, NULL);
130 const uint8_t pixels[] = { value, value, value, 255, value, value, value, 255,
131 value, value, value, 255, value, value, value, 255 };
132 assert_int_equal(dt_imageio_jpeg_write(path, pixels, 2, 2, 90, NULL, 0), 0);
133 return path;
134}
135
136static void _assert_file_bytes(const char *path, const gchar *expected, const gsize expected_size)
137{
138 gchar *actual = NULL;
139 gsize actual_size = 0;
144}
145
146static void _assert_different_bytes(const gchar *first, const gsize first_size, const gchar *second,
147 const gsize second_size)
148{
149 assert_true(first_size != second_size || memcmp(first, second, first_size) != 0);
150}
151
152static void _assert_import(import_completion_t *completion, const int expected_successes)
153{
154 assert_int_equal(completion->completed, completion->expected);
156 assert_int_equal(g_hash_table_size(completion->sources), completion->expected);
157 assert_true(completion->job_disposed);
158}
159
160static dt_control_import_t _copy_import(GList *sources, const int elements, const char *destination,
161 const char *pattern, const dt_import_onconflict_t policy,
162 import_completion_t *completion)
163{
164 dt_control_import_t data = { .imgs = sources, .elements = elements };
166 data.copy = TRUE;
167 data.on_conflict = policy;
168 data.base_folder = g_strdup(destination);
172 data.callback_data = completion;
174 return data;
175}
176
178{
179 char *root = g_dir_make_tmp("ansel-import-siblings-XXXXXX", NULL);
180 char *source = g_build_filename(root, "source", NULL);
181 char *destination = g_build_filename(root, "destination", NULL);
182 assert_int_equal(g_mkdir(source, 0700), 0);
183 char *jpg = _make_jpeg(source, "capture.jpg", 20);
184 char *jpeg = _make_jpeg(source, "capture.jpeg", 21);
185 gchar *jpg_bytes = NULL;
186 gchar *jpeg_bytes = NULL;
187 gsize jpg_size = 0;
188 gsize jpeg_size = 0;
194
195 import_completion_t completion = { 0 };
196 _completion_init(&completion, 2);
198 assert_int_equal(dt_control_import(_copy_import(sources, 2, destination,
199 "$(FILE.NAME)_$(FILE.EXTENSION)_$(SEQUENCE).jpg",
200 DT_IMPORT_ONCONFLICT_SKIP, &completion)), 0);
201 _assert_import(&completion, 2);
202 char *first = g_build_filename(destination, "capture_jpg_0001.jpg", NULL);
203 char *second = g_build_filename(destination, "capture_jpeg_0001.jpg", NULL);
208 dt_free(first);
209 dt_free(second);
210 _completion_cleanup(&completion);
211 _remove_tree(root);
212 dt_free(destination);
213 dt_free(source);
214 dt_free(root);
215}
216
218{
219 char *root = g_dir_make_tmp("ansel-import-in-place-XXXXXX", NULL);
220 assert_non_null(root);
221 char *source = _make_jpeg(root, "capture.jpg", 25);
222 char *source_path = g_strdup(source);
223 char *unused_destination = g_build_filename(root, "unused", NULL);
224 import_completion_t completion = { 0 };
225 _completion_init(&completion, 1);
227 "fixed.jpg", DT_IMPORT_ONCONFLICT_SKIP, &completion);
228 data.copy = FALSE;
229 data.delete_source = TRUE;
231 _assert_import(&completion, 1);
235 _completion_cleanup(&completion);
236 _remove_tree(root);
239 dt_free(root);
240}
241
243{
244 char *root = g_dir_make_tmp("ansel-import-delete-XXXXXX", NULL);
245 assert_non_null(root);
246 char *destination = g_build_filename(root, "destination", NULL);
247 char *source = _make_jpeg(root, "first.jpg", 35);
248 char *source_path = g_strdup(source);
249 gchar *bytes = NULL;
250 gsize size = 0;
252 import_completion_t completion = { 0 };
253 _completion_init(&completion, 1);
254 dt_control_import_t data = _copy_import(g_list_append(NULL, source), 1, destination,
255 "fixed.jpg", DT_IMPORT_ONCONFLICT_SKIP, &completion);
256 data.delete_source = TRUE;
258 _assert_import(&completion, 1);
260 char *copied = g_build_filename(destination, "fixed.jpg", NULL);
262 _completion_cleanup(&completion);
264
265 source = _make_jpeg(root, "second.jpg", 45);
266 source_path = g_strdup(source);
267 completion = (import_completion_t){ 0 };
268 _completion_init(&completion, 1);
269 dt_control_import_t skipped = _copy_import(g_list_append(NULL, source), 1, destination,
270 "fixed.jpg", DT_IMPORT_ONCONFLICT_SKIP, &completion);
271 skipped.delete_source = TRUE;
273 _assert_import(&completion, 1);
276 _completion_cleanup(&completion);
279 dt_free(bytes);
280 _remove_tree(root);
281 dt_free(destination);
282 dt_free(root);
283}
284
286{
287 char *directory = g_dir_make_tmp("ansel-stream-compare-XXXXXX", NULL);
289 char *first_path = g_build_filename(directory, "first", NULL);
290 char *second_path = g_build_filename(directory, "second", NULL);
291 const size_t sizes[] = { 0, 1, 65535, 65536, 65537, 131073 };
292 unsigned char *bytes = g_malloc0(sizes[G_N_ELEMENTS(sizes) - 1]);
293 gboolean streams_opened = TRUE;
294 size_t i = 0;
296 {
299 FILE *first = g_fopen(first_path, "rb");
300 FILE *second = g_fopen(second_path, "r+b");
301 streams_opened = !IS_NULL_PTR(first) && !IS_NULL_PTR(second);
302 if(!streams_opened)
303 {
304 if(!IS_NULL_PTR(first)) fclose(first);
305 if(!IS_NULL_PTR(second)) fclose(second);
306 goto cleanup;
307 }
308 const int equal = dt_util_streams_equal(first, second);
309 int mismatch = 0;
310 if(sizes[i] > 0)
311 {
312 assert_int_equal(fseek(second, -1, SEEK_END), 0);
313 assert_int_equal(fputc(1, second), 1);
314 rewind(first);
315 rewind(second);
316 mismatch = dt_util_streams_equal(first, second);
317 assert_int_equal(fseek(second, -1, SEEK_END), 0);
318 assert_int_equal(fputc(0, second), 0);
319 }
320 assert_int_equal(fseek(second, 0, SEEK_END), 0);
321 assert_int_equal(fputc(2, second), 2);
322 rewind(first);
323 rewind(second);
324 const int longer = dt_util_streams_equal(first, second);
325 fclose(second);
326 second = g_fopen(second_path, "wb");
327 streams_opened = !IS_NULL_PTR(second);
328 int read_error = 0;
330 {
331 rewind(first);
332 read_error = dt_util_streams_equal(first, second);
333 fclose(second);
334 }
335 fclose(first);
340 i++;
341 }
342cleanup:
351}
352
354{
355 char *root = g_dir_make_tmp("ansel-import-fallback-XXXXXX", NULL);
356 char *source = g_build_filename(root, "source", NULL);
357 char *destination = g_build_filename(root, "destination", NULL);
358 assert_int_equal(g_mkdir(source, 0700), 0);
359 char *jpg = _make_jpeg(source, "capture.jpg", 30);
360 char *jpeg = _make_jpeg(source, "capture.jpeg", 31);
361 gchar *first_bytes = NULL;
362 gchar *fallback_bytes = NULL;
363 gsize first_size = 0;
368
369 import_completion_t completion = { 0 };
370 _completion_init(&completion, 2);
372 assert_int_equal(dt_control_import(_copy_import(sources, 2, destination, "$(FILE.NAME)_$(SEQUENCE).jpg",
373 DT_IMPORT_ONCONFLICT_SKIP, &completion)), 0);
374 _assert_import(&completion, 2);
375 char *first = g_build_filename(destination, "capture_0001.jpg", NULL);
376 char *fallback = g_build_filename(destination, "capture_0002.jpg", NULL);
385 dt_free(first);
387 _completion_cleanup(&completion);
388 _remove_tree(root);
389 dt_free(destination);
390 dt_free(source);
391 dt_free(root);
392}
393
395{
398 for(size_t policy = 0; policy < G_N_ELEMENTS(policies); policy++)
399 {
400 char *root = g_dir_make_tmp("ansel-import-fixed-XXXXXX", NULL);
401 char *source = g_build_filename(root, "source", NULL);
402 char *destination = g_build_filename(root, "destination", NULL);
403 assert_int_equal(g_mkdir(source, 0700), 0);
404 char *jpg = _make_jpeg(source, "capture.jpg", 40);
405 char *jpeg = _make_jpeg(source, "capture.jpeg", 41);
406 gchar *first_bytes = NULL;
407 gchar *second_bytes = NULL;
408 gsize first_size = 0;
409 gsize second_size = 0;
413 import_completion_t completion = { 0 };
414 _completion_init(&completion, 2);
416 assert_int_equal(dt_control_import(_copy_import(sources, 2, destination, "fixed.jpg", policies[policy],
417 &completion)), 0);
418 _assert_import(&completion, 2);
419 char *fixed = g_build_filename(destination, "fixed.jpg", NULL);
420 char *unique = g_build_filename(destination, "fixed_01.jpg", NULL);
427 {
430 }
434 dt_free(fixed);
435 _completion_cleanup(&completion);
436 _remove_tree(root);
437 dt_free(destination);
438 dt_free(source);
439 dt_free(root);
440 }
441}
442
444{
447 for(size_t policy = 0; policy < G_N_ELEMENTS(policies); policy++)
448 {
449 char *root = g_dir_make_tmp("ansel-import-unrelated-XXXXXX", NULL);
450 char *source = g_build_filename(root, "source", NULL);
451 char *destination = g_build_filename(root, "destination", NULL);
452 assert_int_equal(g_mkdir(source, 0700), 0);
453 char *first_source = _make_jpeg(source, "first.jpg", 50);
454 char *second_source = _make_jpeg(source, "second.jpg", 60);
455 gchar *first_bytes = NULL;
456 gchar *second_bytes = NULL;
457 gsize first_size = 0;
458 gsize second_size = 0;
462 import_completion_t completion = { 0 };
463 _completion_init(&completion, 2);
465 assert_int_equal(dt_control_import(_copy_import(sources, 2, destination, "fixed.jpg", policies[policy],
466 &completion)), 0);
467 _assert_import(&completion, 2);
468 char *fixed = g_build_filename(destination, "fixed.jpg", NULL);
469 char *fallback = g_build_filename(destination, "fixed_2.jpg", NULL);
470 char *unique = g_build_filename(destination, "fixed_01.jpg", NULL);
476 {
479 }
484 dt_free(fixed);
485 _completion_cleanup(&completion);
486 _remove_tree(root);
487 dt_free(destination);
488 dt_free(source);
489 dt_free(root);
490 }
491}
492
494{
495 char *root = g_dir_make_tmp("ansel-import-failures-XXXXXX", NULL);
496 char *source = g_build_filename(root, "source", NULL);
497 char *destination = g_build_filename(root, "destination", NULL);
498 assert_int_equal(g_mkdir(source, 0700), 0);
499 char *missing = g_build_filename(source, "missing.jpg", NULL);
500 char *invalid = g_build_filename(source, "invalid.dt", NULL);
501 char *valid = _make_jpeg(source, "valid.jpg", 70);
502 char *missing_key = g_strdup(missing);
504 char *valid_key = g_strdup(valid);
505 assert_true(g_file_set_contents(invalid, "not a jpeg", -1, NULL));
506 import_completion_t completion = { 0 };
507 _completion_init(&completion, 3);
508 GList *sources = g_list_append(g_list_append(g_list_append(NULL, missing), invalid), valid);
509 assert_int_equal(dt_control_import(_copy_import(sources, 3, destination, "$(FILE.NAME)_$(SEQUENCE).$(FILE.EXTENSION)",
510 DT_IMPORT_ONCONFLICT_SKIP, &completion)), 0);
511 _assert_import(&completion, 1);
515 char *missing_destination = g_build_filename(destination, "missing_0001.jpg", NULL);
516 char *invalid_destination = g_build_filename(destination, "invalid_0002.dt", NULL);
517 char *valid_destination = g_build_filename(destination, "valid_0003.jpg", NULL);
529 _completion_cleanup(&completion);
530 _remove_tree(root);
531 dt_free(destination);
532 dt_free(source);
533 dt_free(root);
534}
535
537{
538 char *root = g_dir_make_tmp("ansel-import-copy-reservation-XXXXXX", NULL);
539 char *source = g_build_filename(root, "source", NULL);
540 char *destination = g_build_filename(root, "destination", NULL);
541 assert_int_equal(g_mkdir(source, 0700), 0);
542 char *missing = g_build_filename(source, "capture.jpg", NULL);
543 char *sibling = _make_jpeg(source, "capture.jpeg", 80);
544 char *missing_key = g_strdup(missing);
546 import_completion_t completion = { 0 };
547 _completion_init(&completion, 2);
548 GList *sources = g_list_append(g_list_append(NULL, missing), sibling);
549 assert_int_equal(dt_control_import(_copy_import(sources, 2, destination, "$(FILE.NAME)_$(SEQUENCE).jpg",
550 DT_IMPORT_ONCONFLICT_SKIP, &completion)), 0);
551 _assert_import(&completion, 1);
554 char *failed = g_build_filename(destination, "capture_0001.jpg", NULL);
555 char *fallback = g_build_filename(destination, "capture_0002.jpg", NULL);
560 dt_free(failed);
563 _completion_cleanup(&completion);
564 _remove_tree(root);
565 dt_free(destination);
566 dt_free(source);
567 dt_free(root);
568}
569
571{
572 char *root = g_dir_make_tmp("ansel-import-db-reservation-XXXXXX", NULL);
573 char *source = g_build_filename(root, "source", NULL);
574 char *destination = g_build_filename(root, "destination", NULL);
575 assert_int_equal(g_mkdir(source, 0700), 0);
576 char *first = _make_jpeg(source, "capture.jpg", 90);
577 char *sibling = _make_jpeg(source, "capture.jpeg", 91);
578 gchar *first_bytes = NULL;
579 gchar *sibling_bytes = NULL;
580 gsize first_size = 0;
587 char *first_key = g_strdup(first);
589 import_completion_t completion = { 0 };
590 _completion_init(&completion, 2);
591 GList *sources = g_list_append(g_list_append(NULL, first), sibling);
592 assert_int_equal(dt_control_import(_copy_import(sources, 2, destination, "$(FILE.NAME)_$(SEQUENCE).dt",
593 DT_IMPORT_ONCONFLICT_SKIP, &completion)), 0);
594 _assert_import(&completion, 0);
597 char *first_destination = g_build_filename(destination, "capture_0001.dt", NULL);
598 char *fallback = g_build_filename(destination, "capture_0002.dt", NULL);
607 _completion_cleanup(&completion);
608 _remove_tree(root);
609 dt_free(destination);
610 dt_free(source);
611 dt_free(root);
612}
613
615{
618 for(size_t policy = 0; policy < G_N_ELEMENTS(policies); policy++)
619 {
620 char *root = g_dir_make_tmp("ansel-import-reserved-fallback-XXXXXX", NULL);
621 char *source = g_build_filename(root, "source", NULL);
622 char *destination = g_build_filename(root, "destination", NULL);
623 assert_int_equal(g_mkdir(source, 0700), 0);
624 char *capture = _make_jpeg(source, "capture.jpg", 100);
625 char *fallback_owner = _make_jpeg(source, "capture1.jpg", 101);
628 gchar *capture_bytes = NULL;
629 gchar *owner_bytes = NULL;
630 gchar *sibling_bytes = NULL;
632 gsize owner_size = 0;
637 for(int sequence = 3; sequence < 12; sequence++)
638 {
639 char *name = g_strdup_printf("other%d.jpg", sequence);
640 sources = g_list_append(sources, _make_jpeg(source, name, (uint8_t)(100 + sequence)));
641 dt_free(name);
642 }
643 char *sibling = _make_jpeg(source, "capture.jpeg", 120);
648 sources = g_list_append(sources, sibling);
649 import_completion_t completion = { 0 };
650 _completion_init(&completion, 12);
651 assert_int_equal(dt_control_import(_copy_import(sources, 12, destination, "$(FILE.NAME)$(SEQUENCE1).jpg",
652 policies[policy], &completion)), 0);
653 _assert_import(&completion, 12);
657 char *first_destination = g_build_filename(destination, "capture1.jpg", NULL);
658 char *reserved_fallback = g_build_filename(destination, "capture12.jpg", NULL);
659 char *third_retry = g_build_filename(destination, "capture13.jpg", NULL);
660 char *unique = g_build_filename(destination, "capture12_01.jpg", NULL);
680 _completion_cleanup(&completion);
681 _remove_tree(root);
682 dt_free(destination);
683 dt_free(source);
684 dt_free(root);
685 }
686}
687
688static char *_prepare_test_datadir(const char *tmp_dir)
689{
690 char *datadir = g_build_filename(tmp_dir, "data", NULL);
691 char *rawspeed_dir = g_build_filename(datadir, "rawspeed", NULL);
692 char *rawspeed_xml = g_build_filename(rawspeed_dir, "cameras.xml", NULL);
693 char *source_xml = g_build_filename(ANSEL_TEST_SOURCE_DIR, "src", "external", "rawspeed", "data", "cameras.xml", NULL);
694 assert_int_equal(g_mkdir(datadir, 0700), 0);
704 return datadir;
705}
706
707static int _setup(void **state G_GNUC_UNUSED)
708{
709 _rcfile = g_build_filename(g_get_tmp_dir(), "ansel-test-import-jobs.rc", NULL);
711 darktable.conf = calloc(1, sizeof(dt_conf_t));
713 return 0;
714}
715
716static int _teardown(void **state G_GNUC_UNUSED)
717{
719 free(darktable.conf);
723 _rcfile = NULL;
724 return 0;
725}
726
728{
731
732 for(size_t i = 0; i < G_N_ELEMENTS(_extension_cases); i++)
733 {
734 const extension_case_t *test_case = &_extension_cases[i];
735 char *path = g_strdup(test_case->path);
736 const char *extension = dt_util_path_get_extension(path);
737
738 if(test_case->extension)
739 assert_ptr_equal(extension, path + strlen(path) - strlen(test_case->extension));
740 else
742 assert_string_equal(path, test_case->path);
743
744 params->filename = path;
745 char source[] = "$(FILE.NAME)|$(IMAGE.BASENAME)|$(FILE.EXTENSION)";
746 char *variables = dt_variables_expand(params, source, FALSE);
747 assert_string_equal(variables, test_case->variables);
748 assert_string_equal(path, test_case->path);
749 dt_free(variables);
750
751 if(test_case->replacement)
752 {
753 char *replacement = dt_copy_filename_extension(path, "capture.jpg");
754 assert_string_equal(replacement, test_case->replacement);
755 assert_true(dt_has_same_path_basename(path, replacement));
756 assert_string_equal(path, test_case->path);
757 dt_free(replacement);
758 }
759 else
760 {
761 assert_null(dt_copy_filename_extension(path, "capture.jpg"));
762 assert_false(dt_has_same_path_basename(path, "capture.jpg"));
763 }
764
765 dt_free(path);
766 }
767
769}
770
772{
773 assert_false(dt_has_same_path_basename("capture.raw", "other.jpg"));
774 assert_false(dt_has_same_path_basename("/a.b/capture.raw", "/a/capture.jpg"));
777
780 assert_null(dt_copy_filename_extension("capture.raw", ".profile"));
781 assert_null(dt_copy_filename_extension("capture.raw", "capture."));
782}
783
785{
786 dt_control_import_t data = { 0 };
787 dt_image_t image;
788 char *directory = g_dir_make_tmp("ansel-test-pattern-expansion-XXXXXX", NULL);
790 data.base_folder = directory;
792 dt_image_init(&image);
793
794 assert_null(dt_build_filename_from_pattern("source.raw", 1, &image, &data));
796
797 data.target_file_pattern = "target.raw";
798 data.target_subfolder_pattern = "";
799 data.base_folder = NULL;
800 assert_null(dt_build_filename_from_pattern("source.raw", 1, &image, &data));
802
806}
807
840
848
850 { "blocker", "Unable to create the target folder", "Not allowed to write", TRUE },
851 { "read-only", "Not allowed to write", "Unable to create the target folder", FALSE },
852};
853
873
876{
878 int result = 0;
879 if(fixture->stdout_fd >= 0) test_close(fixture->stdout_fd);
880 if(fixture->stderr_fd >= 0) test_close(fixture->stderr_fd);
881 if(fixture->diagnostic_fd >= 0) test_close(fixture->diagnostic_fd);
883 dt_free(fixture->output);
884 dt_free(fixture->destination);
885 if(!IS_NULL_PTR(fixture->diagnostics)) g_remove(fixture->diagnostics);
886 dt_free(fixture->diagnostics);
887 dt_free(fixture->data.target_dir);
888 if(fixture->mutex_initialized) dt_pthread_mutex_destroy(&fixture->control.log_mutex);
889 darktable.control = fixture->saved_control;
890 if(fixture->target_created)
891 {
892 if(fixture->test_case->target_is_file)
893 result = g_remove(fixture->target);
894 else
895 {
896 result = g_chmod(fixture->target, 0700);
897 if(g_rmdir(fixture->target) != 0) result = -1;
898 }
899 }
900 if(!IS_NULL_PTR(fixture->directory) && g_rmdir(fixture->directory) != 0) result = -1;
901 dt_free(fixture->target);
902 dt_free(fixture->directory);
903 return result;
904}
905
908{
910 const directory_failure_case_t *test_case = fixture->test_case;
911 fixture->stdout_fd = -1;
912 fixture->stderr_fd = -1;
913 fixture->diagnostic_fd = -1;
914 fixture->saved_control = darktable.control;
915 fixture->directory = g_dir_make_tmp("ansel-test-import-copy-XXXXXX", NULL);
916 if(IS_NULL_PTR(fixture->directory)) goto failed;
917 fixture->target = g_build_filename(fixture->directory, test_case->target_name, NULL);
918 if(test_case->target_is_file)
919 fixture->target_created = g_file_set_contents(fixture->target, "x", 1, NULL);
920 else
921 fixture->target_created = g_mkdir(fixture->target, 0700) == 0;
922 if(!fixture->target_created) goto failed;
923 if(!test_case->target_is_file)
924 {
925 if(g_chmod(fixture->target, 0500) != 0) goto failed;
926 fixture->writable_after_chmod = dt_util_test_writable_dir(fixture->target);
927 }
928 if(dt_pthread_mutex_init(&fixture->control.log_mutex, NULL) != 0) goto failed;
929 fixture->mutex_initialized = TRUE;
930 darktable.control = &fixture->control;
931 fixture->data.target_dir = test_case->target_is_file
932 ? g_build_filename(fixture->target, "missing", NULL) : g_strdup(fixture->target);
933 fixture->destination = g_build_filename(fixture->data.target_dir, "target.raw", NULL);
934 fixture->diagnostics = g_build_filename(fixture->directory, "diagnostics", NULL);
935 fixture->diagnostic_fd = g_open(fixture->diagnostics, O_CREAT | O_EXCL | O_RDWR, 0600);
936 if(fixture->diagnostic_fd < 0) goto failed;
937 fixture->stdout_fd = test_dup(fileno(stdout));
938 if(fixture->stdout_fd < 0) goto failed;
939 fixture->stderr_fd = test_dup(fileno(stderr));
940 if(fixture->stderr_fd < 0) goto failed;
941 return 0;
942
943failed:
945 return -1;
946}
947
949{
951 const directory_failure_case_t *test_case = fixture->test_case;
952 if(fixture->writable_after_chmod) skip();
953 gchar image_path[DT_PATH_MAX] = { 0 };
954 int import_result = 0;
955 int stderr_redirect_result = -1;
956 fflush(stdout);
957 fflush(stderr);
958 const int stdout_redirect_result = test_dup2(fixture->diagnostic_fd, fileno(stdout));
962 import_result = _import_copy_file("source.raw", fixture->destination, &fixture->data,
963 image_path, sizeof(image_path), &fixture->discarded);
964
965restore:
966 fflush(stdout);
967 fflush(stderr);
968 const int stdout_restore_result = test_dup2(fixture->stdout_fd, fileno(stdout));
969 const int stderr_restore_result = test_dup2(fixture->stderr_fd, fileno(stderr));
975 assert_true(g_file_get_contents(fixture->diagnostics, &fixture->output, NULL, NULL));
977 assert_null(strstr(fixture->output, test_case->unexpected_diagnostic));
978 assert_null(strstr(fixture->output, "Unable to copy the file"));
979 assert_string_equal(image_path, "");
980 assert_null(fixture->discarded);
981 if(test_case->target_is_file)
982 {
983 assert_int_equal(fixture->control.log_pos, 1);
984 assert_non_null(strstr(fixture->control.log_message[0], "Impossible to create directory"));
985 }
986}
987
988int main(void)
989{
992 { .test_case = &_directory_failure_cases[1] },
993 };
994 const struct CMUnitTest tests[] = {
1004 };
1006 if(unit_result) return unit_result;
1007
1008 _integration_config = g_strdup_printf("%s/ansel-test-import-config-XXXXXX", g_get_tmp_dir());
1009 _integration_cache = g_strdup_printf("%s/ansel-test-import-cache-XXXXXX", g_get_tmp_dir());
1010 _integration_tmp = g_strdup_printf("%s/ansel-test-import-tmp-XXXXXX", g_get_tmp_dir());
1015 char *noiseprofiles = g_build_filename(ANSEL_TEST_SOURCE_DIR, "data", "noiseprofiles.json", NULL);
1016 char *argv[] = { "ansel-test-import-jobs", "--library", ":memory:", "--datadir", _integration_datadir,
1017 "--noiseprofiles", noiseprofiles, "--moduledir", ANSEL_TEST_BINARY_DIR "/src",
1018 "--configdir", _integration_config, "--cachedir", _integration_cache, "--tmpdir", _integration_tmp,
1019 "--disable-opencl", "--conf", "write_sidecar_files=FALSE", "-t", "1", NULL };
1020 assert_int_equal(dt_init(G_N_ELEMENTS(argv) - 1, argv, FALSE, FALSE), 0);
1023
1024 const struct CMUnitTest integration_tests[] = {
1035 };
1038 dt_cleanup();
1046 return integration_result;
1047}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
const char * extension(dt_imageio_module_data_t *data)
Definition avif.c:651
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:170
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.
int32_t dt_image_import(const int32_t film_id, const char *filename, gboolean raise_signals)
dt_image_flags_t dt_image_flags_from_extension(const char *extension)
void dt_image_init(dt_image_t *img)
int32_t dt_image_get_id_full_path(const gchar *filename)
void dt_cleanup()
Definition darktable.c:1864
darktable_t darktable
Definition darktable.c:213
int dt_init(int argc, char *argv[], const gboolean init_gui, const gboolean load_data)
Definition darktable.c:885
gchar * directory
documentation root given after –doc, or NULL
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Initialise a mutex. With mutexattr NULL – which is how 54 of the 56 call sites in this tree spell it ...
Definition dtpthread.h:104
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:132
#define UNKNOWN_IMAGE
Definition image.h:78
@ DT_IMAGE_RAW
Definition image.h:124
@ DT_IMAGE_LDR
Definition image.h:122
int dt_imageio_jpeg_write(const char *filename, const uint8_t *in, const int width, const int height, const int quality, const void *exif, int exif_len)
gchar * dt_build_filename_from_pattern(const char *const filename, const int index, dt_image_t *img, dt_control_import_t *data)
Build a full path for a given image file, given a pattern.
Definition import_jobs.c:79
int dt_control_import(dt_control_import_t data)
Process a list of images to import with or without copying the files on an arbitrary hard-drive.
dt_import_onconflict_t
Behaviour when a copy-on-import destination path already exists.
Definition import_jobs.h:36
@ DT_IMPORT_ONCONFLICT_UNIQUE
Definition import_jobs.h:39
@ DT_IMPORT_ONCONFLICT_SKIP
Definition import_jobs.h:37
@ DT_IMPORT_ONCONFLICT_OVERWRITE
Definition import_jobs.h:38
TIFF sidecar I/O API for drawlayer layers.
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
#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
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
size_t size
Definition mipmap_cache.c:3
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
const char * name
Definition pdf.h:90
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
char * dt_variables_expand(dt_variables_params_t *params, gchar *source, gboolean iterate)
void dt_variables_params_destroy(dt_variables_params_t *params)
void dt_variables_params_init(dt_variables_params_t **params)
const float uint32_t state[4]
struct dt_conf_t * conf
Definition darktable.h:184
struct dt_control_t * control
Definition darktable.h:188
const directory_failure_case_t * test_case
The whole configuration state. One instance lives at darktable.conf.
Definition common/conf.h:99
dt_import_onconflict_t on_conflict
Definition import_jobs.h:51
GDateTime * datetime
Definition import_jobs.h:45
GDestroyNotify callback_data_free
Definition import_jobs.h:89
void(* file_imported)(const char *source, gboolean success, gpointer user_data)
Optional per-file completion method.
Definition import_jobs.h:87
char * target_subfolder_pattern
Definition import_jobs.h:65
dt_pthread_mutex_t mutex
Definition control.h:268
struct dt_control_t::@8 progress_system
const char * variables
const char * replacement
const char * extension
static void _assert_file_bytes(const char *path, const gchar *expected, const gsize expected_size)
static void _test_copy_directory_failure_reports_only_its_cause(void **state)
static char * _integration_datadir
static void _test_pattern_expansion_rejects_missing_patterns(void **state G_GNUC_UNUSED)
static void _assert_different_bytes(const gchar *first, const gsize first_size, const gchar *second, const gsize second_size)
static dt_control_import_t _copy_import(GList *sources, const int elements, const char *destination, const char *pattern, const dt_import_onconflict_t policy, import_completion_t *completion)
static void _test_extension_contract(void **state G_GNUC_UNUSED)
static void _test_public_copy_failure_reservation_forces_sibling_fallback(void **state G_GNUC_UNUSED)
static void _test_stream_comparison_boundaries(void **state G_GNUC_UNUSED)
static void _import_job_disposed(gpointer user_data)
#define test_dup
static char * _integration_cache
static void _test_public_siblings_keep_sequence_for_distinct_destinations(void **state G_GNUC_UNUSED)
static void _test_image_import_extension_boundaries(void **state G_GNUC_UNUSED)
static char * _make_jpeg(const char *directory, const char *name, const uint8_t value)
static void _test_public_reserved_fallback_reaches_policy_without_third_expansion(void **state G_GNUC_UNUSED)
static void _test_public_fixed_destination_follows_all_policies(void **state G_GNUC_UNUSED)
static void _test_extension_utility_rejections(void **state G_GNUC_UNUSED)
static void _import_completed(const char *source, gboolean success, gpointer user_data)
static char * _prepare_test_datadir(const char *tmp_dir)
static void _test_public_unrelated_collision_has_no_sibling_fallback(void **state G_GNUC_UNUSED)
static int _setup(void **state G_GNUC_UNUSED)
static int _teardown(void **state G_GNUC_UNUSED)
#define test_close
static const extension_case_t _extension_cases[]
static void _assert_import(import_completion_t *completion, const int expected_successes)
static void _test_public_sibling_collision_reexpands_once(void **state G_GNUC_UNUSED)
static void _completion_cleanup(import_completion_t *completion)
static void _completion_init(import_completion_t *completion, const int expected)
static int _directory_failure_setup(void **state)
Prepare both diagnostic cases without redirecting process-wide output.
static char * _integration_config
int main(void)
#define test_dup2
static int _directory_failure_teardown(void **state)
Release fixture resources after setup failure, assertion failure, or skip.
static char * _rcfile
int _import_copy_file(const char *filename, const char *destination, dt_control_import_t *data, gchar *img_path_to_db, size_t pathname_len, GList **discarded)
copy a file to a destination path after checking if everything is allright.
static void _test_public_delete_source_requires_successful_copy(void **state G_GNUC_UNUSED)
static void _remove_tree(const char *path)
static void _test_public_in_place_import_preserves_source(void **state G_GNUC_UNUSED)
static char * _integration_tmp
static void _test_public_failures_consume_sequences_and_complete_once(void **state G_GNUC_UNUSED)
static void _test_public_database_failure_reservation_forces_sibling_fallback(void **state G_GNUC_UNUSED)
static const directory_failure_case_t _directory_failure_cases[]
#define ANSEL_TEST_SOURCE_DIR
#define ANSEL_TEST_BINARY_DIR
gboolean dt_has_same_path_basename(const char *filename1, const char *filename2)
Test whether two paths are byte-identical before their normal extensions.
Definition utility.c:1049
int dt_util_streams_equal(FILE *first, FILE *second)
Compare the remaining bytes of two borrowed binary streams.
Definition utility.c:94
gboolean dt_util_test_writable_dir(const char *path)
Definition utility.c:371
const char * dt_util_path_get_extension(const char *path)
Locate a normal filename extension in a path basename.
Definition utility.c:1033
char * dt_copy_filename_extension(const char *filename1, const char *filename2)
Replace filename1's normal extension with filename2's normal extension.
Definition utility.c:1062