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