1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 2007-2014 Kouhei Sutou <kou@clear-code.com>
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser 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 * 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
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif /* HAVE_CONFIG_H */
23
24 #include <stdlib.h>
25 #include <string.h>
26 #include <glib.h>
27 #include <glib-compatible/glib-compatible.h>
28
29 #include "cut-repository.h"
30 #include "cut-loader.h"
31 #include "cut-utils.h"
32
33 #define CUT_REPOSITORY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CUT_TYPE_REPOSITORY, CutRepositoryPrivate))
34
35 typedef struct _CutRepositoryPrivate CutRepositoryPrivate;
36 struct _CutRepositoryPrivate
37 {
38 gchar *directory;
39 GList *exclude_files_regexs;
40 GList *exclude_dirs_regexs;
41 GList *loader_customizers;
42 GList *loaders;
43
44 gint deep;
45 CutLoader *test_suite_loader;
46
47 gboolean keep_opening_modules;
48 gboolean enable_convenience_attribute_definition;
49 };
50
51 enum
52 {
53 PROP_0,
54 PROP_DIRECTORY
55 };
56
57 G_DEFINE_TYPE (CutRepository, cut_repository, G_TYPE_OBJECT)
58
59 static void dispose (GObject *object);
60 static void set_property (GObject *object,
61 guint prop_id,
62 const GValue *value,
63 GParamSpec *pspec);
64 static void get_property (GObject *object,
65 guint prop_id,
66 GValue *value,
67 GParamSpec *pspec);
68
69 static void
cut_repository_class_init(CutRepositoryClass * klass)70 cut_repository_class_init (CutRepositoryClass *klass)
71 {
72 GObjectClass *gobject_class;
73 GParamSpec *spec;
74
75 gobject_class = G_OBJECT_CLASS(klass);
76
77 gobject_class->dispose = dispose;
78 gobject_class->set_property = set_property;
79 gobject_class->get_property = get_property;
80
81 spec = g_param_spec_string("directory",
82 "Directory name",
83 "The directory name in which stores shared object",
84 NULL,
85 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
86 g_object_class_install_property(gobject_class, PROP_DIRECTORY, spec);
87
88 g_type_class_add_private(gobject_class, sizeof(CutRepositoryPrivate));
89 }
90
91 static void
cut_repository_init(CutRepository * repository)92 cut_repository_init (CutRepository *repository)
93 {
94 CutRepositoryPrivate *priv = CUT_REPOSITORY_GET_PRIVATE(repository);
95
96 priv->directory = NULL;
97 priv->loaders = NULL;
98 priv->exclude_files_regexs = NULL;
99 priv->exclude_dirs_regexs = NULL;
100 priv->loader_customizers = NULL;
101 priv->deep = 0;
102 priv->test_suite_loader = NULL;
103 priv->keep_opening_modules = FALSE;
104 priv->enable_convenience_attribute_definition = FALSE;
105 }
106
107 static void
free_regexs(GList * regexs)108 free_regexs (GList *regexs)
109 {
110 g_list_foreach(regexs, (GFunc)g_regex_unref, NULL);
111 g_list_free(regexs);
112 }
113
114 static void
dispose(GObject * object)115 dispose (GObject *object)
116 {
117 CutRepositoryPrivate *priv = CUT_REPOSITORY_GET_PRIVATE(object);
118
119 if (priv->directory) {
120 g_free(priv->directory);
121 priv->directory = NULL;
122 }
123
124 if (priv->loaders) {
125 g_list_foreach(priv->loaders, (GFunc)g_object_unref, NULL);
126 g_list_free(priv->loaders);
127 priv->loaders = NULL;
128 }
129
130 if (priv->exclude_files_regexs) {
131 free_regexs(priv->exclude_files_regexs);
132 priv->exclude_files_regexs = NULL;
133 }
134
135 if (priv->exclude_dirs_regexs) {
136 free_regexs(priv->exclude_dirs_regexs);
137 priv->exclude_dirs_regexs = NULL;
138 }
139
140 if (priv->loader_customizers) {
141 g_list_foreach(priv->loader_customizers, (GFunc)g_object_unref, NULL);
142 g_list_free(priv->loader_customizers);
143 priv->loader_customizers = NULL;
144 }
145
146 if (priv->test_suite_loader) {
147 g_object_unref(priv->test_suite_loader);
148 priv->test_suite_loader = NULL;
149 }
150
151 G_OBJECT_CLASS(cut_repository_parent_class)->dispose(object);
152 }
153
154 static void
set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)155 set_property (GObject *object,
156 guint prop_id,
157 const GValue *value,
158 GParamSpec *pspec)
159 {
160 CutRepositoryPrivate *priv = CUT_REPOSITORY_GET_PRIVATE(object);
161
162 switch (prop_id) {
163 case PROP_DIRECTORY:
164 priv->directory = g_value_dup_string(value);
165 break;
166 default:
167 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
168 break;
169 }
170 }
171
172 static void
get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)173 get_property (GObject *object,
174 guint prop_id,
175 GValue *value,
176 GParamSpec *pspec)
177 {
178 CutRepositoryPrivate *priv = CUT_REPOSITORY_GET_PRIVATE(object);
179
180 switch (prop_id) {
181 case PROP_DIRECTORY:
182 g_value_set_string(value, priv->directory);
183 break;
184 default:
185 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
186 break;
187 }
188 }
189
190 CutRepository *
cut_repository_new(const gchar * directory)191 cut_repository_new (const gchar *directory)
192 {
193 return g_object_new(CUT_TYPE_REPOSITORY,
194 "directory", directory,
195 NULL);
196 }
197
198 gboolean
cut_repository_get_keep_opening_modules(CutRepository * repository)199 cut_repository_get_keep_opening_modules (CutRepository *repository)
200 {
201 return CUT_REPOSITORY_GET_PRIVATE(repository)->keep_opening_modules;
202 }
203
204 void
cut_repository_set_keep_opening_modules(CutRepository * repository,gboolean keep_opening)205 cut_repository_set_keep_opening_modules (CutRepository *repository,
206 gboolean keep_opening)
207 {
208 CUT_REPOSITORY_GET_PRIVATE(repository)->keep_opening_modules = keep_opening;
209 }
210
211 gboolean
cut_repository_get_enable_convenience_attribute_definition(CutRepository * repository)212 cut_repository_get_enable_convenience_attribute_definition (CutRepository *repository)
213 {
214 return CUT_REPOSITORY_GET_PRIVATE(repository)->enable_convenience_attribute_definition;
215 }
216
217 void
cut_repository_set_enable_convenience_attribute_definition(CutRepository * repository,gboolean enable_convenience_attribute_definition)218 cut_repository_set_enable_convenience_attribute_definition (CutRepository *repository,
219 gboolean enable_convenience_attribute_definition)
220 {
221 CUT_REPOSITORY_GET_PRIVATE(repository)->enable_convenience_attribute_definition = enable_convenience_attribute_definition;
222 }
223
224 static gboolean
is_test_suite_so_path_name(const gchar * path_name)225 is_test_suite_so_path_name (const gchar *path_name)
226 {
227 gboolean is_test_suite_so = FALSE;
228 gchar *base_name;
229
230 base_name = g_path_get_basename(path_name);
231 if (g_str_has_prefix(base_name, "suite_") ||
232 g_str_has_prefix(base_name, "suite-"))
233 is_test_suite_so = TRUE;
234 g_free(base_name);
235 return is_test_suite_so;
236 }
237
238 static void
update_test_suite_loader(CutRepositoryPrivate * priv,CutLoader * loader,gint deep)239 update_test_suite_loader (CutRepositoryPrivate *priv, CutLoader *loader,
240 gint deep)
241 {
242 if (!priv->test_suite_loader) {
243 priv->deep = deep;
244 priv->test_suite_loader = g_object_ref(loader);
245 }
246
247 if (priv->deep > deep) {
248 priv->deep = deep;
249 g_object_unref(priv->test_suite_loader);
250 priv->test_suite_loader = g_object_ref(loader);
251 }
252 }
253
254 static inline gboolean
is_ignore_directory(const gchar * dir_name)255 is_ignore_directory (const gchar *dir_name)
256 {
257 return g_str_equal(dir_name, ".svn") ||
258 g_str_equal(dir_name, ".git") ||
259 g_str_equal(dir_name, "CVS") ||
260 g_str_has_suffix(dir_name, ".dSYM");
261 }
262
263 static inline gchar *
compute_relative_path(GArray * paths)264 compute_relative_path (GArray *paths)
265 {
266 gchar *last_component = NULL;
267 gchar *relative_path;
268 gboolean is_ignore_library_directory = FALSE;
269
270 if (paths->len > 0) {
271 last_component = g_array_index(paths, gchar *, paths->len - 1);
272 if (g_str_equal(last_component, ".libs") ||
273 g_str_equal(last_component, "_libs"))
274 is_ignore_library_directory = TRUE;
275 }
276
277 if (is_ignore_library_directory)
278 g_array_index(paths, gchar *, paths->len - 1) = NULL;
279 relative_path = g_build_filenamev((gchar **)(paths->data));
280 if (is_ignore_library_directory)
281 g_array_index(paths, gchar *, paths->len - 1) = last_component;
282
283 return relative_path;
284 }
285
286 static void
cut_repository_collect_loader(CutRepository * repository,const gchar * dir_name,GArray * paths)287 cut_repository_collect_loader (CutRepository *repository, const gchar *dir_name,
288 GArray *paths)
289 {
290 GDir *dir;
291 const gchar *entry;
292 CutRepositoryPrivate *priv = CUT_REPOSITORY_GET_PRIVATE(repository);
293
294 if (is_ignore_directory(dir_name))
295 return;
296
297 dir = g_dir_open(dir_name, 0, NULL);
298 if (!dir)
299 return;
300
301 while ((entry = g_dir_read_name(dir))) {
302 gchar *path_name;
303
304 path_name = g_build_filename(dir_name, entry, NULL);
305 if (g_file_test(path_name, G_FILE_TEST_IS_DIR)) {
306 if (cut_utils_filter_match(priv->exclude_dirs_regexs, entry)) {
307 g_free(path_name);
308 continue;
309 }
310 g_array_append_val(paths, entry);
311 cut_repository_collect_loader(repository, path_name, paths);
312 g_array_remove_index(paths, paths->len - 1);
313 } else {
314 CutLoader *loader;
315 gchar *relative_path;
316 GList *node;
317
318 if (cut_utils_filter_match(priv->exclude_files_regexs, entry) ||
319 !g_str_has_suffix(entry, "."G_MODULE_SUFFIX)) {
320 g_free(path_name);
321 continue;
322 }
323
324 loader = cut_loader_new(path_name);
325 relative_path = compute_relative_path(paths);
326 cut_loader_set_base_directory(loader, relative_path);
327 cut_loader_set_keep_opening(loader, priv->keep_opening_modules);
328 cut_loader_set_enable_convenience_attribute_definition(
329 loader, priv->enable_convenience_attribute_definition);
330 for (node = priv->loader_customizers; node; node = g_list_next(node)) {
331 CutLoaderCustomizer *customizer = node->data;
332 cut_loader_customizer_customize(customizer, loader);
333 }
334 if (is_test_suite_so_path_name(path_name)) {
335 update_test_suite_loader(priv, loader, paths->len);
336 } else {
337 priv->loaders = g_list_prepend(priv->loaders, loader);
338 }
339 g_free(relative_path);
340 }
341 g_free(path_name);
342 }
343 g_dir_close(dir);
344 }
345
346 CutTestSuite *
cut_repository_create_test_suite(CutRepository * repository)347 cut_repository_create_test_suite (CutRepository *repository)
348 {
349 CutTestSuite *suite = NULL;
350 CutRepositoryPrivate *priv = CUT_REPOSITORY_GET_PRIVATE(repository);
351 GList *list;
352
353 if (!priv->directory)
354 return NULL;
355
356 if (!priv->loaders) {
357 GArray *paths;
358
359 priv->deep = 0;
360 paths = g_array_new(TRUE, TRUE, sizeof(gchar *));
361 cut_repository_collect_loader(repository, priv->directory, paths);
362 g_array_free(paths, TRUE);
363 }
364
365 if (priv->test_suite_loader)
366 suite = cut_loader_load_test_suite(priv->test_suite_loader);
367 if (!suite)
368 suite = cut_test_suite_new_empty();
369
370 for (list = priv->loaders; list; list = g_list_next(list)) {
371 CutLoader *loader = CUT_LOADER(list->data);
372 GList *test_cases, *node;
373
374 test_cases = cut_loader_load_test_cases(loader);
375 for (node = test_cases; node; node = g_list_next(node)) {
376 CutTestCase *test_case = node->data;
377
378 cut_test_suite_add_test_case(suite, test_case);
379 g_object_unref(test_case);
380 }
381 g_list_free(test_cases);
382 }
383 return suite;
384 }
385
386 void
cut_repository_set_exclude_files(CutRepository * repository,const gchar ** files)387 cut_repository_set_exclude_files (CutRepository *repository, const gchar **files)
388 {
389 CutRepositoryPrivate *priv = CUT_REPOSITORY_GET_PRIVATE(repository);
390
391 if (priv->exclude_files_regexs)
392 free_regexs(priv->exclude_files_regexs);
393
394 if (files)
395 priv->exclude_files_regexs = cut_utils_filter_to_regexs(files);
396 }
397
398 void
cut_repository_set_exclude_directories(CutRepository * repository,const gchar ** dirs)399 cut_repository_set_exclude_directories (CutRepository *repository, const gchar **dirs)
400 {
401 CutRepositoryPrivate *priv = CUT_REPOSITORY_GET_PRIVATE(repository);
402
403 if (priv->exclude_dirs_regexs)
404 free_regexs(priv->exclude_dirs_regexs);
405
406 if (dirs)
407 priv->exclude_dirs_regexs = cut_utils_filter_to_regexs(dirs);
408 }
409
410 void
cut_repository_add_loader_customizer(CutRepository * repository,CutLoaderCustomizer * customizer)411 cut_repository_add_loader_customizer (CutRepository *repository,
412 CutLoaderCustomizer *customizer)
413 {
414 CutRepositoryPrivate *priv = CUT_REPOSITORY_GET_PRIVATE(repository);
415
416 g_object_ref(customizer);
417 priv->loader_customizers =
418 g_list_append(priv->loader_customizers, customizer);
419 }
420
421 /*
422 vi:ts=4:nowrap:ai:expandtab:sw=4
423 */
424