Integrated unit testing framework with scons on Linux.
authorArystanbek Dyussenov <arystan.d@gmail.com>
Tue, 7 Jul 2009 08:38:18 +0000 (08:38 +0000)
committerArystanbek Dyussenov <arystan.d@gmail.com>
Tue, 7 Jul 2009 08:38:18 +0000 (08:38 +0000)
I needed this to make sure that BKE_copy_images works properly,
probably will be useful in future.

Using Check framework (http://check.sourceforge.net/doc/check.html/index.html).

WITH_BF_UNIT_TEST option builds 'alltest' program under [BUILDDIR]/bin,
which, when executed, runs unit tests, currently only 1.

Example output:
----------------------------------------------------------------------
Running suite(s): Image
0%: Checks: 1, Failures: 1, Errors: 0
tests/alltest.c:74:F:Core:test_copy_images:0: Expected //bar/image.png to be translated to /tmp/bar/image.png, got /tmp/bar/image.pn.
----------------------------------------------------------------------

Spent lots of time (a couple of days actually :) to figure out how to
link the test program with Blender libraries. As it turned out there
are circular dependencies among Blender libraries. GCC by default
doesn't expect circular dependencies - dependant libs should precede
libs they depend on.

The magical --start-group linker option helped to solve this
(http://stephane.carrez.free.fr/doc/ld_2.html#IDX122).

Also:

- added bpy.util module. bpy.sys.* functions will move here later
- added bpy.util.copy_images that uses BKE_copy_images
- export_obj.py uses bpy.util.copy_images

SConstruct
release/io/export_obj.py
source/blender/blenkernel/BKE_image.h
source/blender/blenkernel/SConscript
source/blender/blenkernel/intern/image.c
source/blender/python/intern/bpy_interface.c
source/blender/python/intern/bpy_util.c
source/blender/python/intern/bpy_util.h
tools/btools.py

index b85bc799ea5a11729018ddc08c15c7224140486b..2d1daba98b61406db646da80790b93cf706482e8 100644 (file)
@@ -407,7 +407,16 @@ if env['WITH_BF_PLAYER']:
 
 if 'blender' in B.targets or not env['WITH_BF_NOBLENDER']:
        #env.BlenderProg(B.root_build_dir, "blender", dobj , [], mainlist + thestatlibs + thesyslibs, [B.root_build_dir+'/lib'] + thelibincs, 'blender')
-       env.BlenderProg(B.root_build_dir, "blender", dobj + mainlist, [], thestatlibs + thesyslibs, [B.root_build_dir+'/lib'] + thelibincs, 'blender')
+       blen = env.BlenderProg(B.root_build_dir, "blender", dobj + mainlist, [], thestatlibs + thesyslibs, [B.root_build_dir+'/lib'] + thelibincs, 'blender')
+
+       build_data = {"lib": thestatlibs + thesyslibs, "libpath": thelibincs, "blen": blen}
+
+       Export('env')
+       Export('build_data')
+
+       BuildDir(B.root_build_dir+'/tests', 'tests', duplicate=0)
+       SConscript(B.root_build_dir+'/tests/SConscript')
+
 if env['WITH_BF_PLAYER']:
        playerlist = B.create_blender_liblist(env, 'player')
        env.BlenderProg(B.root_build_dir, "blenderplayer", dobj + playerlist, [], thestatlibs + thesyslibs, [B.root_build_dir+'/lib'] + thelibincs, 'blenderplayer')
index 1ee685a52a3dae89b8e2a697801bcfa66215adf1..ee045053dd3e7f001bfa83b1ffcafe6be8aa2624 100644 (file)
@@ -178,19 +178,22 @@ def copy_images(dest_dir):
                                                        pass
        
        # Now copy images
-       copyCount = 0
+#      copyCount = 0
        
-       for bImage in uniqueImages.values():
-               image_path = bpy.sys.expandpath(bImage.filename)
-               if bpy.sys.exists(image_path):
-                       # Make a name for the target path.
-                       dest_image_path = dest_dir + image_path.split('\\')[-1].split('/')[-1]
-                       if not bpy.sys.exists(dest_image_path): # Image isnt alredy there
-                               print('\tCopying "%s" > "%s"' % (image_path, dest_image_path))
-                               copy_file(image_path, dest_image_path)
-                               copyCount+=1
+#      for bImage in uniqueImages.values():
+#              image_path = bpy.sys.expandpath(bImage.filename)
+#              if bpy.sys.exists(image_path):
+#                      # Make a name for the target path.
+#                      dest_image_path = dest_dir + image_path.split('\\')[-1].split('/')[-1]
+#                      if not bpy.sys.exists(dest_image_path): # Image isnt alredy there
+#                              print('\tCopying "%s" > "%s"' % (image_path, dest_image_path))
+#                              copy_file(image_path, dest_image_path)
+#                              copyCount+=1
+
+       paths= bpy.util.copy_images(uniqueImages.values(), dest_dir)
 
        print('\tCopied %d images' % copyCount)
+#      print('\tCopied %d images' % copyCount)
 
 # XXX not converted
 def test_nurbs_compat(ob):
index 816baa204677f15cde78d7aebfcbacd723e8aaaf..f3f5266189a9e2e028fc1782a58106192e4e1555 100644 (file)
@@ -40,6 +40,7 @@ struct ImBuf;
 struct Tex;
 struct anim;
 struct Scene;
+struct ListBase;
 
 /* call from library */
 void   free_image(struct Image *me);
@@ -154,6 +155,11 @@ struct Image *BKE_image_copy(struct Image *ima);
 /* merge source into dest, and free source */
 void BKE_image_merge(struct Image *dest, struct Image *source);
 
+/* ********************************** FOR EXPORTERS *********************** */
+
+/* copy images into dest_dir */
+void BKE_copy_images(struct ListBase *images, char *dest_dir, struct ListBase *filenames);
+
 #ifdef __cplusplus
 }
 #endif
index dbc990d06137ff52d3613dfd678b076876898f2d..9aaea7e8aff6cac1718d1675ed380323c902ffff 100644 (file)
@@ -57,6 +57,9 @@ if env['BF_NO_ELBEEM']:
 
 if env['WITH_BF_LCMS']:
        defs.append('WITH_LCMS')
+
+if env['WITH_BF_UNIT_TEST']:
+       defs.append('WITH_UNIT_TEST')
        
 if env['OURPLATFORM'] in ('win32-vc', 'win32-mingw', 'linuxcross', 'win64-vc'):
     incs += ' ' + env['BF_PTHREADS_INC']
index ef0984bf93dbb3242eff27ee5bfa6c7248e7657c..039c2d07bce9d53f97479dfa1d5ab4f93de86c82 100644 (file)
@@ -2109,3 +2109,120 @@ void BKE_image_user_calc_imanr(ImageUser *iuser, int cfra, int fieldnr)
        }
 }
 
+/*
+  Copy list of images to dest_dir.
+
+  paths is optional, if given, image paths for each image will be written in it.
+  If an image file doesn't exist, NULL is added in paths.
+
+  Logic:
+
+  For each image if it's "below" current .blend file directory,
+  rebuild the same dir structure in dest_dir.
+
+  For example //textures/foo/bar.png becomes
+  [dest_dir]/textures/foo/bar.png.
+
+  If an image is not "below" current .blend file directory, disregard
+  it's path and copy it in the same directory where 3D file goes.
+
+  For example //../foo/bar.png becomes [dest_dir]/bar.png.
+
+  This logic will help ensure that all image paths are relative and
+  that a user gets his images in one place. It'll also provide
+  consistent behaviour across exporters.
+*/
+void BKE_copy_images(ListBase *images, char *dest_dir, ListBase *paths)
+{
+       char path[FILE_MAX];
+       char dir[FILE_MAX];
+       char base[FILE_MAX];
+       char blend_dir[FILE_MAX];       /* directory, where current .blend file resides */
+       char dest_path[FILE_MAX];
+       int len;
+       Image *im;
+       LinkData *link;
+
+       if (paths) {
+               memset(paths, 0, sizeof(*paths));
+       }
+
+       BLI_split_dirfile_basic(G.sce, blend_dir, NULL);
+       
+       link= images->first;
+
+       while (link) {
+               im= link->data;
+
+               BLI_strncpy(path, im->name, sizeof(path));
+
+               /* expand "//" in filename and get absolute path */
+               BLI_convertstringcode(path, G.sce);
+
+               /* in unit tests, we don't want to modify the filesystem */
+#ifndef WITH_UNIT_TEST
+               /* proceed only if image file exists */
+               if (!BLI_exists(path)) {
+
+                       if (paths) {
+                               LinkData *ld = MEM_callocN(sizeof(LinkData), "PathLinkData");
+                               ld->data= NULL;
+                               BLI_addtail(paths, ld);
+                       }
+
+                       continue;
+               }
+#endif
+
+               /* get the directory part */
+               BLI_split_dirfile_basic(path, dir, base);
+
+               len= strlen(blend_dir);
+
+               /* if image is "below" current .blend file directory */
+               if (!strncmp(path, blend_dir, len)) {
+
+                       /* if image is _in_ current .blend file directory */
+                       if (!strcmp(dir, blend_dir)) {
+                               /* copy to dest_dir */
+                               BLI_join_dirfile(dest_path, dest_dir, base);
+                       }
+                       /* "below" */
+                       else {
+                               char rel[FILE_MAX];
+
+                               /* rel = image_path_dir - blend_dir */
+                               BLI_strncpy(rel, dir + len, sizeof(rel));
+                               
+                               BLI_join_dirfile(dest_path, dest_dir, rel);
+
+#ifndef WITH_UNIT_TEST
+                               /* build identical directory structure under dest_dir */
+                               BLI_make_existing_file(dest_path);
+#endif
+
+                               BLI_join_dirfile(dest_path, dest_path, base);
+                       }
+                       
+               }
+               /* image is out of current directory */
+               else {
+                       /* copy to dest_dir */
+                       BLI_join_dirfile(dest_path, dest_dir, base);
+               }
+
+#ifndef WITH_UNIT_TEST
+               BLI_copy_fileops(path, dest_path);
+#endif
+
+               if (paths) {
+                       LinkData *ld = MEM_callocN(sizeof(LinkData), "PathLinkData");
+                       len= strlen(dest_path) + 1;
+                       ld->data= MEM_callocN(len, "PathLinkData");
+                       BLI_strncpy(ld->data, dest_path, len);
+                       BLI_addtail(paths, ld);
+               }
+
+               link= link->next;
+       }
+}
index 96ef796839b7a899a124961212ef0b8664e4d7b7..c895a41637db1b62bf10b3969ed6f81ce617b4c4 100644 (file)
@@ -92,6 +92,9 @@ void BPY_update_modules( void )
        PyObject *mod= PyImport_ImportModuleLevel("bpy", NULL, NULL, NULL, 0);
        PyModule_AddObject( mod, "data", BPY_rna_module() );
        PyModule_AddObject( mod, "types", BPY_rna_types() );
+       PyModule_AddObject( mod, "util", BPY_util_module() );
+
+       /* XXX this will move to bpy.util */
        PyModule_AddObject( mod, "sys", BPY_sys_module() );
 }
 
index bce73b903c02a9c4726bdb1568445da8d1ff775a..5f235b913370ac01e2cef3e69cc365a4b4998f92 100644 (file)
 #include "DNA_listBase.h"
 #include "RNA_access.h"
 #include "bpy_util.h"
-#include "BLI_dynstr.h"
+#include "bpy_rna.h"
+
 #include "MEM_guardedalloc.h"
-#include "BKE_report.h"
 
+#include "BLI_dynstr.h"
+#include "BLI_listbase.h"
 
+#include "BKE_report.h"
+#include "BKE_image.h"
 #include "BKE_context.h"
+
 bContext*      __py_context = NULL;
 bContext*      BPy_GetContext(void) { return __py_context; };
 void           BPy_SetContext(bContext *C) { __py_context= C; };
@@ -464,3 +469,127 @@ int BPy_errors_to_report(ReportList *reports)
        Py_DECREF(pystring);
        return 1;
 }
+
+
+/* bpy.util module */
+static PyObject *bpy_util_copy_images(PyObject *self, PyObject *args);
+
+struct PyMethodDef bpy_util_methods[] = {
+       {"copy_images", bpy_util_copy_images, METH_VARARGS, NULL},
+       {NULL, NULL, 0, NULL}
+};
+
+#if PY_VERSION_HEX >= 0x03000000
+static struct PyModuleDef bpy_util_module = {
+       PyModuleDef_HEAD_INIT,
+       "bpyutil",
+       NULL,
+       -1,
+       bpy_util_methods,
+       NULL, NULL, NULL, NULL
+};
+#endif
+
+PyObject *BPY_util_module( void )
+{
+       PyObject *submodule, *dict;
+
+#if PY_VERSION_HEX >= 0x03000000
+       submodule= PyModule_Create(&bpy_util_module);
+#else /* Py2.x */
+       submodule= Py_InitModule3("bpyutil", bpy_util_methods, NULL);
+#endif
+
+       dict = PyModule_GetDict(submodule);
+       
+       return submodule;
+}
+
+/*
+  copy_images(images, dest_dir)
+  return filenames
+*/
+static PyObject *bpy_util_copy_images(PyObject *self, PyObject *args)
+{
+       const char *dest_dir;
+       ListBase *images;
+       ListBase *paths;
+       LinkData *link;
+       PyObject *seq;
+       PyObject *ret;
+       PyObject *item;
+       int i;
+       int len;
+
+       /* check args/types */
+       if (!PyArg_ParseTuple(args, "Os", &seq, &dest_dir)) {
+               PyErr_SetString(PyExc_TypeError, "Invalid arguments.");
+               return NULL;
+       }
+
+       /* expecting a sequence of Image objects */
+       if (!PySequence_Check(seq)) {
+               PyErr_SetString(PyExc_TypeError, "Expected a sequence of images.");
+               return NULL;
+       }
+
+       /* create image list */
+       len= PySequence_Size(seq);
+
+       if (!len) {
+               PyErr_SetString(PyExc_TypeError, "At least one image should be specified.");
+               return NULL;
+       }
+
+       /* make sure all sequence items are Image */
+       for(i= 0; i < len; i++) {
+               item= PySequence_GetItem(seq, i);
+
+               if (!BPy_StructRNA_Check(item) || ((BPy_StructRNA*)item)->ptr.type != &RNA_Image) {
+                       PyErr_SetString(PyExc_TypeError, "Expected a sequence of Image objects.");
+                       return NULL;
+               }
+       }
+
+       images= MEM_callocN(sizeof(*images), "ListBase of images");
+
+       for(i= 0; i < len; i++) {
+               BPy_StructRNA* srna;
+
+               item= PySequence_GetItem(seq, i);
+               srna= (BPy_StructRNA*)item;
+
+               link= MEM_callocN(sizeof(LinkData), "LinkData image");
+               link->data= srna->ptr.data;
+               BLI_addtail(images, link);
+
+               Py_DECREF(item);
+       }
+
+       paths= MEM_callocN(sizeof(*paths), "ListBase of image paths");
+
+       /* call BKE_copy_images */
+       BKE_copy_images(images, dest_dir, paths);
+
+       /* convert filenames */
+       ret= PyList_New(0);
+       len= BLI_countlist(paths);
+
+       for(link= paths->first, i= 0; link; link++, i++) {
+               if (link->data) {
+                       item= PyUnicode_FromString(link->data);
+                       PyList_Append(ret, item);
+                       Py_DECREF(item);
+               }
+               else {
+                       PyList_Append(ret, Py_None);
+               }
+       }
+
+       /* free memory */
+       BLI_freelistN(images);
+       BLI_freelistN(paths);
+
+       /* return filenames */
+       return ret;
+}
index 6429af67eb0d46ae66d18cd50fbf85e271861d86..89d27ba8325305aabe7376a428613eca211e2926 100644 (file)
@@ -81,4 +81,6 @@ int BPy_errors_to_report(struct ReportList *reports);
 struct bContext *BPy_GetContext(void);
 void BPy_SetContext(struct bContext *C);
 
+PyObject *BPY_util_module(void); 
+
 #endif
index 9603022deaa554f36a1d758f11fcbf560efd0e1a..f4d79b1bb246824f78d7d49afaa4163602015969 100755 (executable)
@@ -65,6 +65,8 @@ def validate_arguments(args, bc):
                        'WITH_BF_DOCS',
                        'BF_NUMJOBS',
                        'BF_MSVS',
+
+                       'WITH_BF_UNIT_TEST'
                        ]
        
        # Have options here that scons expects to be lists
@@ -356,7 +358,9 @@ def read_opts(cfg, args):
                
                ('BF_CONFIG', 'SCons python config file used to set default options', 'user_config.py'),
                ('BF_NUMJOBS', 'Number of build processes to spawn', '1'),
-               ('BF_MSVS', 'Generate MSVS project files and solution', False)
+               ('BF_MSVS', 'Generate MSVS project files and solution', False),
+
+               (BoolVariable('WITH_BF_UNIT_TEST', 'Build unit tests', False))
 
        ) # end of opts.AddOptions()