[Bf-blender-cvs] SVN commit: /data/svn/bf-blender [21067] branches/soc-2009-jaguarandi: Initial code commit of test script
André Pinto
andresusanopinto at gmail.com
Sun Jun 21 22:01:47 CEST 2009
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. @@
More information about the Bf-blender-cvs
mailing list