diff --git a/.gitignore b/.gitignore index ed690f89c..653bd410b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /wiki/backups/* +/tests/* *~ *.tar.* .sconf_temp diff --git a/SConstruct b/SConstruct index 0c2271198..4409b9a9b 100644 --- a/SConstruct +++ b/SConstruct @@ -32,6 +32,7 @@ OPTIONSCACHEFILE = 'optcache' CUSTOPTIONSFILE = 'custom-options' SRCDIR = 'src' BINDIR = 'src/bin' +TESTDIR = 'tests' VERSION = '3+alpha.01' #-----------------------------------Configuration @@ -57,7 +58,7 @@ def setupBasicEnvironment(): env.Replace( VERSION=VERSION , SRCDIR=SRCDIR , BINDIR=BINDIR - , CPPPATH=SRCDIR # used to find includes + , CPPPATH="#"+SRCDIR # used to find includes, "#" means always absolute to build-root ) appendCppDefine(env,'DEBUG','DEBUG') @@ -196,6 +197,7 @@ def defineBuildTargets(env, artifacts): # + srcSubtree(env,'lib') + env.Object('$SRCDIR/main.cpp') ) + testobj = srcSubtree(env,'test/*', isShared=False) plugobj = srcSubtree(env,'plugin', isShared=True) artifacts['cinelerra'] = env.Program('$BINDIR/cinelerra', cinobj) @@ -203,7 +205,7 @@ def defineBuildTargets(env, artifacts): # call subdir SConscript(s) for independent components SConscript(dirs=[SRCDIR+'/tool'], exports='env artifacts') - + SConscript(dirs=[TESTDIR], exports='env artifacts testobj') def defineInstallTargets(env, artifacts): diff --git a/admin/scons/Buildhelper.py b/admin/scons/Buildhelper.py index 99b84c997..85da4784e 100644 --- a/admin/scons/Buildhelper.py +++ b/admin/scons/Buildhelper.py @@ -23,6 +23,7 @@ import os import sys +import glob import fnmatch import re import tarfile @@ -62,16 +63,28 @@ def srcSubtree(env,tree,isShared=False, **args): SRCPATTERNS = ['*.c','*.cpp','*.cc'] -def scanSrcSubtree(root): - """ scan the given subtree for source filesnames +def scanSrcSubtree(roots): + """ first expand (possible) wildcards and filter out non-dirs. + Then scan the given subtree for source filesnames (python generator function) """ - for (dir,_,files) in os.walk(root): - if dir.startswith('./'): - dir = dir[2:] - for p in SRCPATTERNS: - for f in fnmatch.filter(files, p): - yield os.path.join(dir,f) + for root in globRootdirs(roots): + for (dir,_,files) in os.walk(root): + if dir.startswith('./'): + dir = dir[2:] + for p in SRCPATTERNS: + for f in fnmatch.filter(files, p): + yield os.path.join(dir,f) + + + +def globRootdirs(roots): + """ helper: expand shell wildcards and filter the resulting list, + so that it only contains existing directories + """ + filter = lambda f: os.path.isdir(f) and os.path.exists(f) + roots = glob.glob(roots) + return (dir for dir in roots if filter(dir) ) diff --git a/src/common/factory.hpp b/src/common/factory.hpp index b3f9faeb7..8664675c8 100644 --- a/src/common/factory.hpp +++ b/src/common/factory.hpp @@ -107,6 +107,19 @@ namespace cinelerra }; + /** another convienience instantiiation: auto_ptr-Factory, + * actually creating a subclass of the returned type + */ + template + class SubclassPtr : public Factory + { + typedef std::auto_ptr aP; + + public: + aP operator() (){ return aP (new TImpl ); }; + }; + + } // namespace factory diff --git a/src/test/common/factorytest.cpp b/src/test/common/factorytest.cpp new file mode 100644 index 000000000..61522b66f --- /dev/null +++ b/src/test/common/factorytest.cpp @@ -0,0 +1,54 @@ +/* + Factory(Test) - unittest for the object creating factory + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + + +#include "test/helper/run.hpp" + + +namespace cinelerra + { + namespace test + { + + /** + * Target object to be created by the Test-Factory + */ + class TargetObj + { + public: + }; + + class Factory_test : public Test + { + virtual void run(Arg arg) + { + } + }; + + /** Register this test class to be invoked in some test groups (suites) */ + Launch run_Factory_test("Factory_test","unit common"); + + + + } // namespace test + +} // namespace cinelerra diff --git a/src/test/common/helloworldtest.cpp b/src/test/common/helloworldtest.cpp new file mode 100644 index 000000000..877830989 --- /dev/null +++ b/src/test/common/helloworldtest.cpp @@ -0,0 +1,55 @@ +/* + HelloWorld(Test) - how to use this test framework... + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + + +#include +#include "test/helper/run.hpp" + + +namespace cinelerra + { + namespace test + { + + + class HelloWorld_test : public Test + { + virtual void run(Arg arg) + { + greeting(); + } + + void greeting() + { + std::cout << "This is how the world ends...\n\n"; + } + }; + + + + /** Register this test class to be invoked in some test groups (suites) */ + Launch run_HelloWorld_test("HelloWorld_test","unit common"); + + + } // namespace test + +} // namespace cinelerra diff --git a/src/test/helper/run.hpp b/src/test/helper/run.hpp new file mode 100644 index 000000000..4d5cfb0b3 --- /dev/null +++ b/src/test/helper/run.hpp @@ -0,0 +1,92 @@ +/* + RUN.hpp - helper class for grouping, registering and invoking testcases + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +#ifndef TESTHELPER_RUN_H +#define TESTHELPER_RUN_H + +#include +#include +#include "common/factory.hpp" +#include "test/helper/suite.hpp" + + +namespace test + { + + using std::string; + using std::auto_ptr; + using cinelerra::Factory; + using cinelerra::factory::SubclassPtr; + + typedef std::vector * Arg; + + + + /** + * Abstract Base Class for all testcases. + * Typically, such testcases are created by a Launcher. + */ + class Test + { + public: + virtual void run(Arg arg) = 0; + }; + + + + /** interface: generic testcase creating functor. */ + class Launcher + { + public: + virtual auto_ptr operator() () = 0; + }; + + + /** + * Helper class for running a collection of tests. + * Launch objects are functors, which create on + * invocation an instance of the Test class + * they were created with. Creating such a + * Test Launcher internally registers this + * testcase with the Testsuite-Class, + * optionally under several groups + * (=categories, suite selections). + * @note a subclass of Launcher + */ + template + class Launch : public Launcher + { + public: + Launch (string testID, string groups) { Suite::enroll (this,testID,groups); }; + virtual auto_ptr operator() () { return auto_ptr (new TEST ); }; + }; + +} // namespace test + +// make them global for convienience +using ::test::Arg; +using ::test::Test; +using ::test::Launch; + +#endif diff --git a/src/test/helper/suite.cpp b/src/test/helper/suite.cpp new file mode 100644 index 000000000..84fb7148c --- /dev/null +++ b/src/test/helper/suite.cpp @@ -0,0 +1,171 @@ +/* + Suite - helper class for running collections of tests + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + + +#include +#include +#include +#include +#include "test/helper/suite.hpp" +#include "test/helper/run.hpp" + +#include /////////////////////////TODO: Debug +#include + +namespace test + { + using std::map; + using std::vector; + using std::auto_ptr; + using std::tr1::shared_ptr; + + typedef map TestMap; + typedef shared_ptr PTestMap; + typedef map GroupMap; + + + /** helper to collect and manage the test cases. + * Every testcase class should create a Launch instance + * which causes a call to Suite::enroll(), so we can add a + * pointer to this Launcher into a map indexed by the + * provided testIDs and groupIDs. + * This enables us to build a Suite instance for any + * requested group and then instantiiate and invoke + * individual testcases accordingly. + */ + class Registry + { + auto_ptr groups; + public: + Registry() : groups(new GroupMap ) {}; + PTestMap& getGroup (string grpID) { return (*groups)[grpID]; }; + void add2group (Launcher* test, string testID, string groupID); + }; + + void Registry::add2group (Launcher* test, string testID, string groupID) + { + // TODO: ASSERT test!=null, testID.length > 0 ... + PTestMap& group = getGroup(groupID); + if (!group) + group.reset( new TestMap ); + (*group)[testID] = test; + } + + Registry testcases; + + + + + /** register the given test-launcher, so it can be later accessed + * either as a member of one of the specified groups, or direcly + * by its testID. Any test is automatically added to the groupID + * #ALLGROUP + * @param groups List of group-IDs selected by whitespace + */ + void Suite::enroll (Launcher* test, string testID, string groups) + { + // TODO learn to use NoBug for logging + std::cerr << "enroll( testID=" << testID << ")\n"; + // TODO: ASSERT test!=null, testID.length() > 0... + + std::istringstream ss(groups); + string group; + while (ss >> group ) + testcases.add2group(test, testID, group); + + // Magic: allways add any testcas to groupID="ALL" + testcases.add2group(test,testID, ALLGROUP); + } + + /** "magic" groupID containing all registered testcases */ + const string Suite::ALLGROUP = "ALL"; + + + + /** create a suite comprised of all the testcases + * previously @link #enroll() registered @endlink with this + * this group. + * @see #run() running tests in a Suite + */ + Suite::Suite(string groupID) + : groupID_(groupID) + { + std::cerr << "Suite( groupID="<< groupID << ")\n"; + if (!testcases.getGroup(groupID)) + throw "empty testsuite"; /////////// TODO Errorhandling! + } + + + /** run all testcases contained in this Suite. + * The first argument in the commandline, if present, will select + * one single testcase with a matching ID. + */ + void Suite::run (int argc, char* argv[]) + { + /////////////////////////////////////////////////////TODO:DEBUG + std::cerr << "Suite::run( (" << argc << "[" ; + for ( int i=0; i= 2) + { + if (Launcher* test = (*tests)[argv[1]]) + { + // first cmdline argument denotes a valid + // testcase registered in this group: + // go ahead and invoke just this test. + if (argc > 2) + { // pass additional cmdline as vector + vector arglist(argc-2); + for ( int i=2; irun(&arglist); + } + else + (*test)()->run(0); // without additional argumens + + return; + } + } + + // no test-ID was specified. + // instantiiate all tests cases and execute them. + for ( TestMap::iterator i=tests->begin(); i!=tests->end(); ++i ) + if (i->second) + { + std::cout << " ----------"<< i->first<< "----------\n"; + Launcher& test = *(i->second); + test()->run(0); // without cmdline arguments + } + + } + + + +} // namespace test diff --git a/src/test/helper/suite.hpp b/src/test/helper/suite.hpp new file mode 100644 index 000000000..6dfefe663 --- /dev/null +++ b/src/test/helper/suite.hpp @@ -0,0 +1,61 @@ +/* + SUITE.hpp - helper class for running collections of tests + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +#ifndef TESTHELPER_SUITE_H +#define TESTHELPER_SUITE_H + +#include +#include "common/factory.hpp" + + + +namespace test + { + using std::string; + + // Forward decls needed for run.hpp + class Test; + class Launcher; + + + + /** + * Helper class for running a collection of tests. + * + */ + class Suite + { + string groupID_; + + public: + Suite (string groupID); + void run (int argc, char* argv[]); + static void enroll (Launcher *test, string testID, string groups); + + static const string ALLGROUP; + }; + + + +} // namespace test +#endif diff --git a/src/test/mainsuite.cpp b/src/test/mainsuite.cpp new file mode 100644 index 000000000..729f55e6b --- /dev/null +++ b/src/test/mainsuite.cpp @@ -0,0 +1,36 @@ +/* + mainsuite.cpp - "the" cinelerra3 self test suite + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + + +#include "test/helper/suite.hpp" + + +/** run all tests or any single test specified in the first + * cmd line argument. + * Note: to ease debugging, we don't catch any exceptions. + */ +int main (int argc, char* argv[]) + { + test::Suite suite (test::Suite::ALLGROUP); + suite.run(argc,argv); + return 0; + } diff --git a/tests/SConscript b/tests/SConscript new file mode 100644 index 000000000..a5fe4a697 --- /dev/null +++ b/tests/SConscript @@ -0,0 +1,16 @@ +# -*- python -*- +## +## SConscript - SCons buildscript for the Testsuite (called by SConstruct) +## + +Import('env','artifacts','testobj') + +# build an application running the testsuite +artifacts['testsuite'] = env.Program('mainsuite',testobj + ['#$SRCDIR/test/mainsuite.cpp']) + +# TODO: we could apply much more "magic" here +# - build /every/ $TESTDIR/*.cpp into an application +# - link the testobj dynamically +# - install additionally scripts into tests-dir if desired + + diff --git a/tests/test.sh b/tests/test.sh new file mode 100755 index 000000000..30516cfde --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,154 @@ +#!/bin/bash +# +# tests/test.sh - run all defined automatic tests +# +# +# Copyright (C) CinelerraCV +# 2007, Christian Thaeter +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +################################################################### + + +# TESTMODE=FULL yet unimplemented +# run all tests, PLANNED which fail count as error +# +# TESTMODE=FAST +# run only tests which recently failed + +arg0="$0" +srcdir=$(dirname "$arg0") + +ulimit -S -t 1 -v 524288 +valgrind="" +if [ "$VALGRINDFLAGS" = 'DISABLE' ]; then + echo "valgrind explicit disabled" +else + if [ "$(which valgrind)" ]; then + valgrind="$(which valgrind) --suppressions=$srcdir/../valgrind.sup --leak-check=yes --show-reachable=yes -q $VALGRINDFLAGS" + ulimit -S -t 10 + else + echo "no valgrind found, go without it" + fi +fi + +echo +echo ================ $0 ================ + +TESTCNT=0 +SKIPCNT=0 +FAILCNT=0 + +if test -f ,testlog; then + mv ,testlog ,testlog.pre +else + touch ,testlog.pre +fi + +date >,testlog + +function TEST() +{ + name="$1" + shift + cat >,cmp + echo -n "" >,out + echo -n "TEST $name: " + echo -en "\nTEST $name: $* " >>,testlog + + case $TESTMODE in + *FAST*) + if grep "^TEST $name: .* FAILED" ,testlog.pre >&/dev/null; then + MSGOK=" (fixed)" + MSGFAIL=" (still broken)" + elif grep "^TEST $name: .* \\(SKIPPED (ok)\\|OK\\)" ,testlog.pre >&/dev/null; then + echo ".. SKIPPED (ok)" + echo ".. SKIPPED (ok)" >>,testlog + SKIPCNT=$(($SKIPCNT + 1)) + TESTCNT=$(($TESTCNT + 1)) + return + else + MSGOK=" (new)" + MSGFAIL=" (new)" + fi + ;; + *) + MSGOK="" + MSGFAIL="" + ;; + esac + + TESTCNT=$(($TESTCNT + 1)) + if $valgrind $TESTBIN "$@" 2>&1 | tee ,tmp | grep -v ': \(TRACE\|INFO\|NOTICE\|WARNING\|ERR\):' | cmp ,cmp - &>/dev/null; then + echo ".. OK$MSGOK" + echo ".. OK$MSGOK" >>,testlog + else + echo ".. FAILED$MSGFAIL"; + echo ".. FAILED$MSGFAIL" >>,testlog + grep -v ': \(TRACE\|INFO\|NOTICE\|WARNING\|ERR\):' <,tmp >,out + diff -ua ,cmp ,out >>,testlog + # grep 'DEBUG:\|==.*==' <,tmp >>,testlog + cat ,tmp >>,testlog + echo END >>,testlog + FAILCNT=$(($FAILCNT + 1)) + case $TESTMODE in + *FIRSTFAIL*) + break 2 + ;; + esac + fi +} + +function PLANNED() +{ + echo -n "PLANNED $1: " + echo -en "\nPLANNED $* " >>,testlog + echo ".. SKIPPED (planned)" + echo ".. SKIPPED (planned)" >>,testlog + SKIPCNT=$(($SKIPCNT + 1)) + TESTCNT=$(($TESTCNT + 1)) +} + +function RUNTESTS() +{ + for i in $srcdir/*.tests; do + source $i + done + echo + rm ,cmp ,out ,tmp + if [ $FAILCNT = 0 ]; then + echo " ... PASSED $(($TESTCNT - $SKIPCNT)) TESTS, $SKIPCNT SKIPPED" + #rm ,testlog + else + echo " ... SUCCEDED $(($TESTCNT - $FAILCNT - $SKIPCNT)) TESTS" + echo " ... FAILED $FAILCNT TESTS" + echo " ... SKIPPED $SKIPCNT TESTS" + echo " see ',testlog' for details" + exit 1 + fi +} + +function TESTING() +{ + echo + echo "$1" + TESTBIN=$2 +} + +RUNTESTS + +# arch-tag: f4d06a47-6e17-40de-bba8-17240ae3f435 + diff --git a/wiki/index.html b/wiki/index.html index 5f979a5b1..517fd7095 100755 --- a/wiki/index.html +++ b/wiki/index.html @@ -42,7 +42,7 @@ DAMAGE. -
My TiddlyWiki is loading ...

Requires Javascript.
+
Cinelerra TiddlyWiki is loading ...

Requires Javascript.
Cinelerra3 - Distributed Developer Wiki