Revision: 21067
          
http://projects.blender.org/plugins/scmsvn/viewcvs.php?view=rev&root=bf-blender&revision=21067
Author:   jaguarandi
Date:     2009-06-21 22:01:47 +0200 (Sun, 21 Jun 2009)

Log Message:
-----------
Initial code commit of test script

created as part of:
        "Raytrace optimization" a Google Summer of Code 2009 project by 
Andr?\195?\169 Susano Pinto

http://wiki.blender.org/index.php/User:Jaguarandi/SummerOfCode2009

Objectives of blender-test:
 * make it easier to run render tests
 * other people can run tests and send results, which can be used to compare
 * easy to add test cases
 * easy to add builds
 * run tests must be aware of the machine it runs on
 * be flexible as possible for future usage and extension

Characteristics:
 * Coded in python (default scripting language inside blender community)
 * Directory based database for easy managing
 * allow html reports/comparisons of scenes/build/machines

Added Paths:
-----------
    branches/soc-2009-jaguarandi/test/
    branches/soc-2009-jaguarandi/test/Test.py
    branches/soc-2009-jaguarandi/test/blendertest.py
    branches/soc-2009-jaguarandi/test/config.py
    branches/soc-2009-jaguarandi/test/html.py
    branches/soc-2009-jaguarandi/test/persistent.py
    branches/soc-2009-jaguarandi/test/util.py

Added: branches/soc-2009-jaguarandi/test/Test.py
===================================================================
--- branches/soc-2009-jaguarandi/test/Test.py                           (rev 0)
+++ branches/soc-2009-jaguarandi/test/Test.py   2009-06-21 20:01:47 UTC (rev 
21067)
@@ -0,0 +1,123 @@
+import os
+import string
+import config
+import time
+import subprocess
+import persistent
+
+class Case(dict):
+       def __init__(self, arg = {}):
+               dict.__init__(self, arg)
+               
+       def run(self, res):
+               print self["name"]+":","test not implemented",self["type"]
+               return TestRun.RESULT_NONE
+               
+       def run_cmd(self, cmd, test):
+               dt = time.time()
+               proc = subprocess.Popen(cmd,stdout=subprocess.PIPE, 
stderr=subprocess.PIPE)
+               out, err = proc.communicate()
+               dt = time.time()-dt
+
+               test["cmdline"] = cmd
+               test["time"] = dt
+               test["stderr"] = err
+               test["stdout"] = out
+               test["exit_status"] = proc.returncode
+               
+               return proc.returncode
+
+
+class Render(Case):
+       __name__ = "render"
+       
+       def __init__(self, arg = {}):
+               Case.__init__(self, arg)
+               
+       def run(self, test):
+               #prepare cmdline
+               filename = os.path.join(test.path, self["filename"])
+               cmd = [ test["build"]["path"], "-b", self["path"], "-o", 
filename, "-F", "PNG", "-x", "0", "-f", "%d"%self["frame"]]
+
+               if self.run_cmd(cmd, test) == 0:
+                       test["result"] = TestRun.RESULT_OK
+               else:
+                       test["result"] = TestRun.RESULT_FAIL
+
+
+################################
+class TestRun(dict):
+       RESULT_NONE = "none"
+       RESULT_OK       = "ok"
+       RESULT_FAIL     = "fail"
+
+       def __init__(self, machine, build, case):
+               self.path = os.path.join( config.run_path, machine["hash"], 
build["hash"], case["hash"] )
+               self.result_path = os.path.join( self.path, "result.xml" )
+               
+               if os.path.isfile(self.result_path):
+                       dict.__init__(self, 
persistent.load(file(self.result_path, "r")))
+                       if "result" not in self:
+                               self["result"] = TestRun.RESULT_NONE
+               else:
+                       self["machine"] = machine
+                       self["build"]   = build
+                       self["case"]    = case
+                       self["result"]  = TestRun.RESULT_NONE
+               
+       # Save results
+       def save_results(self):
+               dirname = os.path.dirname( self.result_path )
+               if not os.path.isdir(dirname):
+                       os.makedirs(dirname)
+               persistent.dump( dict(self), file(self.result_path, "w") )
+
+
+       def getResult(self):
+               return self["result"]
+       
+       # get a path to an image generated by the test run
+       def getImageResult(self):
+               img  = os.path.join(self.path,'1.png')
+               if os.path.isfile( img ):
+                       return img
+               return None
+
+
+       #Short run description:
+       # MM:SS.mmm <name> <result description>
+       def getResultDescription(self):
+               if self["result"] == TestRun.RESULT_NONE:
+                       return self["case"]["name"] + " (not available)"
+               res = ""
+
+               if "time" in self:
+                       time = self["time"]*1000
+                       res += "%02d:%02d.%03d "%(time/60000, (time/1000)%60, 
time%1000)
+               res += self["case"]["name"]
+               
+               if "returncode" in self and self["returncode"] != 0:
+                       res += " (non zero return)"
+               if "sdderr" in self and len(self["stderr"].splitlines()):
+                       res += " (%d stderr 
lines)"%(len(self["stderr"].splitlines()))
+               return res
+
+       
+       # TODO should be atomic (if it crashs while saving result it can create 
a wrong xml file)
+       def run(self, force = False):
+               try:    
+                       if self["result"] != TestRun.RESULT_NONE and force == 
False:
+                               return
+               
+                       if self["case"]["type"] == Render.__name__:
+                               Render(self["case"]).run(self)
+
+                       self.save_results()
+               except:
+                       #TODO print exception
+                       print "Failed to process test run:",self.result_path
+                       os._exit(-1)
+       
+##########################################################################################
+def get(machine,build,case):
+       return TestRun(machine,build,case)

Added: branches/soc-2009-jaguarandi/test/blendertest.py
===================================================================
--- branches/soc-2009-jaguarandi/test/blendertest.py                            
(rev 0)
+++ branches/soc-2009-jaguarandi/test/blendertest.py    2009-06-21 20:01:47 UTC 
(rev 21067)
@@ -0,0 +1,158 @@
+#!/usr/bin/python
+"""
+File structure:
+       case/<path>.blend
+       build/<path>.bin
+       
+       run/<machine-hash>/<build_hash>/<case_hash>/result.xml
+       run/<machine-hash>/<build_hash>/<case_hash>/1.png
+"""
+import sys
+import os
+import re
+import config
+
+import Test
+import persistent
+import html
+import util
+
+OK = "\033[92m"
+WARNING = "\033[93m"
+FAIL = "\033[91m"
+INFO = "\033[95m"
+ENDC = "\033[0m"
+
+### Build ###
+_valid_build = re.compile(r'.*\.bin$')
+def is_valid_build(arg):
+       return _valid_build.match( arg )
+       
+def load_build(path):
+       #TODO test the existence of a .xml file with information about this 
build
+       conf = dict(config.default_build_config)
+
+       conf["path"] = path
+       conf["name"] = os.path.splitext(os.path.basename(path))[0]
+       conf["hash"] = util.get_hash(open(path, 'rb'))
+               
+       return conf
+
+### Test Case ###      
+_valid_scene = re.compile(r'.*\.blend$')
+def is_valid_case(arg):
+       return _valid_scene.match( arg )
+
+def load_case(path):
+       #TODO test the existence of a .xml file with diferent configuration for 
this test case
+       conf = dict(config.default_case_config)
+       
+       conf["path"] = path
+       conf["name"] = os.path.splitext(os.path.basename(path))[0]
+       conf["hash"] = util.get_hash(open(path, 'rb'))
+               
+       return conf
+
+
+
+#############################################
+result_color = { "ok" : OK, "fail": FAIL, "none" : FAIL }
+
+def get_color(trun):
+       if "result" in trun and trun["result"] in result_color:
+               return result_color[trun["result"]]
+       return WARNING
+
+def update_test_runs( builds, cases ):
+       tot = len(cases)
+       for build in builds:
+               i = 1
+               print INFO+"=== "+build["path"]+" ==="+ENDC
+               for case in cases:
+                       res = Test.get( config.machine, build, case )
+                       res.run()
+                       print get_color(res)+"[%2d/%d] %s" % 
(i,tot,res.getResultDescription())+ENDC
+                       i += 1
+                               
+                               
+#############################################
+def gen_html( builds, cases ):
+       return html.generate( [config.machine], builds, cases)
+       
+       
+#############################################
+def do_help():
+       print """Usage:
+blender_test.py [action] [<paths for test cases or builds>]
+
+actions:
+       --update        runs tests cases between the given builds and cases
+                               test results are saved under:
+                                       
<config.run_path>/<machine-hash>/<build-hash>/<case-hash>/
+
+       --html          outputs html comparison table to standart output
+                               test results are loaded from <config.run_path>
+                               auxiliary html files are created under 
<config.html_path>
+       
+       if no action is given it will just print information on the cases
+       and builds found on the given paths
+
+paths:
+       All given paths will be recursively searched for files matching:
+               *.blend to represent test scenes
+               *.bin to represent builds
+       
+       if no test case is given it will scan directory "case"
+       if no build is given it will scan directory "build"
+"""
+
+
+builds = []
+cases = []
+
+def load_path(arg):
+       if os.path.isdir(arg):
+               for root, dirs, files in os.walk(arg):
+                        for file in files:
+                               load_path(os.path.join(root, file))
+       else:
+               if is_valid_case(arg):
+                       cases.append( load_case(arg) )          
+               elif is_valid_build(arg):
+                       builds.append( load_build(arg) )
+
+
+do_update = False
+do_html = False
+do_info = True
+
+if len(sys.argv) > 1:
+
+       for arg in sys.argv[1:]:
+               if arg == "--help":
+                       do_help()
+                       os._exit(-1)                    
+               if arg == "--update":
+                       do_update = True
+               if arg == "--html":
+                       do_info = False
+                       do_html = True
+               else:
+                       load_path(arg)
+                       
+if len(builds) == 0:
+       load_path( "build" )
+
+if len(cases) == 0:
+       load_path( "case" )
+
+if do_info:
+       print INFO," Machine   : ",config.machine,ENDC
+       print INFO," test-cases: ",len(cases),ENDC
+       print INFO," builds    : ",len(builds),ENDC
+
+if do_update:
+       update_test_runs( builds, cases )
+
+if do_html:
+       gen_html( builds, cases )               


Property changes on: branches/soc-2009-jaguarandi/test/blendertest.py
___________________________________________________________________
Name: svn:executable
   + *

Added: branches/soc-2009-jaguarandi/test/config.py
===================================================================
--- branches/soc-2009-jaguarandi/test/config.py                         (rev 0)
+++ branches/soc-2009-jaguarandi/test/config.py 2009-06-21 20:01:47 UTC (rev 
21067)
@@ -0,0 +1,21 @@
+import os
+
+run_path = "run"
+html_path = "html"
+
+default_case_config    = {
+               "type":                 "Render",
+               "filename":             "#.png",
+               "frame":                1,
+       }
+
+default_build_config = { }
+
+machine = {
+               "hostname":             os.uname()[1],
+#              "cpu":                  "Intel(R) Pentium(R) 4 CPU 2.80GHz",
+               "hash":                 os.uname()[1]
+       }
+
+print "config.py: please configure your machine description and comment this 
line!"
+

Added: branches/soc-2009-jaguarandi/test/html.py
===================================================================
--- branches/soc-2009-jaguarandi/test/html.py                           (rev 0)
+++ branches/soc-2009-jaguarandi/test/html.py   2009-06-21 20:01:47 UTC (rev 
21067)
@@ -0,0 +1,168 @@
+import os
+
+import config
+import Test
+import util
+
+       
+def dict2html(stream, dic):
+       print >>stream,'<dl>'
+       for key in dic:
+               print >>stream,'<dt>',key,'</dt><dd>'
+               if type(dic[key]) == type({}):
+                       dict2html(stream,dic[key])
+               else:
+                       print >>stream,dic[key]
+               print >>stream,'</dd>'
+       print >>stream,'</dl>'
+               
+
+def generate( machines, builds, cases ):
+       html_root = config.html_path
+
+       print """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";>
+<html xmlns="http://www.w3.org/1999/xhtml";>
+       <head>
+               <meta http-equiv="Content-Type" content="text/html; 
charset=utf-8" />
+               <title>Blender test compare</title>
+               <link href="style.css" rel="stylesheet" type="text/css" />
+       </head>
+       <body>
+"""
+       valid_configs = []
+       for machine in machines:
+               for build in builds:
+                       for case in cases:
+                               if Test.get(machine,build,case):
+                                       valid_configs.append( [machine,build] )
+                                       break
+                                       
+       def make_detail_page(run):
+               if run == None:
+                       return
+               
+               path = os.path.join( config.html_path, run["machine"]["hash"], 
run["build"]["hash"], run["case"]["hash"] )
+               if not os.path.isdir(path):
+                       os.makedirs( path )
+               stream = open( os.path.join(path, "index.html"), "w" )
+               print >>stream, """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";>
+<html xmlns="http://www.w3.org/1999/xhtml";>
+       <head>
+               <meta http-equiv="Content-Type" content="text/html; 
charset=utf-8" />

@@ Diff output truncated at 10240 characters. @@

_______________________________________________
Bf-blender-cvs mailing list
[email protected]
http://lists.blender.org/mailman/listinfo/bf-blender-cvs

Reply via email to