Skip to content

Commit

Permalink
Merge pull request #584 from tardyp/nine
Browse files Browse the repository at this point in the history
More infra for js tests
  • Loading branch information
djmitche committed Nov 29, 2012
2 parents 5fb0652 + 4617944 commit 9c89bce
Show file tree
Hide file tree
Showing 13 changed files with 209 additions and 32 deletions.
14 changes: 6 additions & 8 deletions master/buildbot/data/testhooks.py
Expand Up @@ -14,6 +14,7 @@
# Copyright Buildbot Team Members

import re, inspect
from twisted.python import reflect
from twisted.internet import defer
from buildbot.data import base, exceptions

Expand All @@ -23,9 +24,7 @@
class TestHooksEndpoint(base.ControlParamsCheckMixin,base.Endpoint):
rootLinkName = 'testhooks'
pathPatterns = [ ( 'testhooks',) ]
action_specs = dict(enableFakeDb=dict(),
disableFakeDb=dict(),
playScenario=dict(scenario=dict(re=re.compile("[a-z\.]+"),
action_specs = dict(playScenario=dict(scenario=dict(re=re.compile("[a-z\.]+"),
type=str,
required=True)))
def safeControl(self, action, args, kwargs):
Expand All @@ -43,18 +42,17 @@ class TestHooksResourceType(base.ResourceType):
endpoints = [ TestHooksEndpoint]
@base.updateMethod
def playTestScenario(self, scenario):
if not self.isFakeDbEnabled():
raise exceptions.InvalidActionException("FakeDb disabled!")
scenario = scenario.split(".")
if len(scenario <3):
if len(scenario) <3:
raise exceptions.InvalidOptionException("invalid scenario path")
mod = ".".join(scenario[:-2])
sym = scenario[-2]
meth = scenario[-1]
if not sym in dir(mod):
module = reflect.namedModule(mod)
if not sym in dir(module):
raise exceptions.InvalidOptionException("no class %s in module %s"%(sym,
mod))
obj = getattr(mod, sym)
obj = getattr(module, sym)
if not(inspect.isclass(obj) and issubclass(obj, TestHooksScenario)):
raise exceptions.InvalidOptionException(
"class %s is not subclass of TestHooksScenario"%(meth,
Expand Down
2 changes: 1 addition & 1 deletion master/buildbot/scripts/uitestserver.py
Expand Up @@ -31,7 +31,7 @@ def _uitestserver(config):
reactor.stop()
raise defer.returnValue(1)
master = yield fakemaster.make_master_for_uitest(int(config['port']), public_html)
webbrowser.open(master.config.www['url']+"bb/tests/runner.html")
webbrowser.open(master.config.www['url']+"static/bb/tests/runner.html")

def uitestserver(config):
def async():
Expand Down
3 changes: 3 additions & 0 deletions master/buildbot/test/fake/fakemaster.py
Expand Up @@ -145,12 +145,15 @@ def make_master_for_uitest(port, public_html):
master = FakeMaster()
master.db = fakedb.FakeDBConnector(mock.Mock())
master.mq = mqconnector.MQConnector(master)
master.config.mq = dict(type='simple')
master.mq.setup()
class testHookedDataConnector(dataconnector.DataConnector):
submodules = dataconnector.DataConnector.submodules + ['buildbot.data.testhooks']

master.data = testHookedDataConnector(master)
master.config.www = dict(url=url, port=port, public_html=public_html)
master.www = service.WWWService(master)
master.data.updates.playTestScenario("buildbot.test.scenarios.base.BaseScenario.populateBaseDb")
yield master.www.startService()
yield master.www.reconfigService(master.config)
defer.returnValue(master)
Empty file.
43 changes: 43 additions & 0 deletions master/buildbot/test/scenarios/base.py
@@ -0,0 +1,43 @@
# This file is part of Buildbot. Buildbot 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, version 2.
#
# 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., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members

from buildbot.data import testhooks
from buildbot.test.fake import fakedb
import mock

class BaseScenario(testhooks.TestHooksScenario):
"""buildbot.test.unit.test_www.ui.BaseScenario"""
def populateBaseDb(self):
self.master.db.__init__(mock.Mock)
self.master.db.insertTestData([
fakedb.Master(id=13, name=u'inactivemaster', active=0,
last_active=0),
fakedb.Master(id=14, name=u'master', active=1,
last_active=0),
fakedb.Master(id=15, name=u'othermaster', active=1,
last_active=0),
])
self.master.db.insertTestData([
fakedb.Change(changeid=13, branch=u'trunk', revision=u'9283',
repository=u'svn://...', codebase=u'cbsvn',
project=u'world-domination'),
fakedb.Change(changeid=14, branch=u'devel', revision=u'9284',
repository=u'svn://...', codebase=u'cbsvn',
project=u'world-domination'),
])
def stopMaster(self):
print "stopping master 14"
return self.master.data.updates.masterStopped(name=u'master', masterid=14)

6 changes: 3 additions & 3 deletions master/buildbot/www/rest.py
Expand Up @@ -108,13 +108,13 @@ def updateError(msg, jsonrpccode,e=None):
def check(name, _types, _val=None):
if not name in data:
updateError("need '%s' to be present"%(name), JSONRPC_CODES["invalid_request"])
if not type(data[name]) in _types:
if _types and not type(data[name]) in _types:
updateError("need '%s' to be of type %s:%s"%(name, " or ".join(map(str,_types)), json.dumps(data[name])), JSONRPC_CODES["invalid_request"])
if _val != None and data[name] != _val:
updateError("need '%s' value to be '%s'"%(name, str(_val)), JSONRPC_CODES["invalid_request"])
check("jsonrpc", (str,unicode), "2.0")
check("method", (str,unicode))
check("id", (str,unicode))
check("id", None)
check("params", (dict,)) # params can be a list in jsonrpc, but we dont support it.
reply["id"] = data["id"]
return data["params"], data["method"]
Expand Down Expand Up @@ -177,7 +177,7 @@ def write_error_jsonrpc(msg, errcode=400, jsonrpccode=JSONRPC_CODES["internal_er
write_error(repr(e), errcode=500,jsonrpccode=JSONRPC_CODES["internal_error"])
log.err(e) # make sure we log unknown exception
return
if data is None:
if data is None and request.method=="GET":
write_error("no data")
return

Expand Down
29 changes: 23 additions & 6 deletions www/src/bb/jsonapi.js
@@ -1,10 +1,11 @@
/* utilities, and code related to direct access to the api
deferred based!
*/
define(["dojo/_base/declare", "dojo/_base/Deferred", "dojo/_base/xhr"],
function(declare, Deferred, xhr){
define(["dojo/_base/declare", "dojo/_base/Deferred", "dojo/request/xhr","dojo/json"],
function(declare, Deferred, xhr, json){
var api_url = dojo.baseUrl + "/../../../../api/";
return declare([], {
var jsonrpc_curid = 0;
return {
createAPIPath: function(a) {
var path=[];
for (var i = 0;i<a.length; i+=1) {
Expand All @@ -15,10 +16,26 @@ define(["dojo/_base/declare", "dojo/_base/Deferred", "dojo/_base/xhr"],
return path;
},
getApiV1: function() {
return xhr.get({handleAs:"json",url:api_url+"v1/"+this.createAPIPath(arguments)});
return xhr(api_url+"v1/"+this.createAPIPath(arguments),
{handleAs:"json"});
},
getApiV2: function() {
return xhr.get({handleAs:"json",url:api_url+"v2/"+this.createAPIPath(arguments)});
return xhr(api_url+"v2/"+this.createAPIPath(arguments),
{handleAs:"json"});
},
control: function(path, method, args) {
jsonrpc_curid+=1;
return xhr(api_url+"v2/"+this.createAPIPath(path),
{handleAs:"json",method:"POST",
headers: {
'Content-Type': 'application/json'
},
data:json.stringify({jsonrpc:"2.0",
method:method,
params:args,
id:jsonrpc_curid})
}
);
}
})(); // note the singleton..
}; // note the singleton..
});
6 changes: 5 additions & 1 deletion www/src/bb/router.js
Expand Up @@ -134,7 +134,11 @@ define(["dojo/_base/declare", "dojo/_base/connect","dojo/_base/array","dojo/dom"
w.placeAt(content);
window.bb.curWidget = w;
if(test) {
require(["doh/main",test],function(doh) {doh.run();});
if (window.doh) { /* doh already loaded! We need to cleanup the iframe */
location.reload();
return;
}
require(["doh/main","bb/tests/"+test],function(doh) {doh.run();});
}
});
});
Expand Down
2 changes: 2 additions & 0 deletions www/src/bb/tests/changes.js
Expand Up @@ -14,4 +14,6 @@
// Copyright Buildbot Team Members

define(["dojo/main", "doh/main", "bb/tests/utils"], function(dojo, doh, utils){
utils.registerBBTests(doh, "/changes", "changes",[
]);
});
12 changes: 8 additions & 4 deletions www/src/bb/tests/home.js
Expand Up @@ -14,9 +14,13 @@
// Copyright Buildbot Team Members

define(["dojo/main", "doh/main", "bb/tests/utils"], function(dojo, doh, utils){
if (utils.goToBuildbotHash(doh, "/", "bb/tests/home")) {
doh.register("bb/tests/home", [
function(t) { t.assertEqual(window.bb_router.base_url, utils.getBaseUrl());}
]);
utils.registerBBTests(doh, "/", "home",[
function resetDb(t) {
var d = new doh.Deferred();
dojo.when(utils.playTestScenario("buildbot.test.scenarios.base.BaseScenario.populateBaseDb"), function() {
d.callback(true);
});
return d;
}
]);
});
24 changes: 24 additions & 0 deletions www/src/bb/tests/masters.js
Expand Up @@ -14,4 +14,28 @@
// Copyright Buildbot Team Members

define(["dojo/main", "doh/main", "bb/tests/utils"], function(dojo, doh, utils){
utils.registerBBTests(doh, "/masters", "masters",[
function baseConfig(t) {
/* look at the dom and see if we have what we are supposed to */
utils.assertDomText(t,"inactivemaster", "#dgrid_0-row-13 td.field-name");
utils.assertDomText(t,"master", "#dgrid_0-row-14 td.field-name");
utils.assertDomText(t,"othermaster", "#dgrid_0-row-15 td.field-name");
utils.assertDomText(t,"Yes", "#dgrid_0-row-15 td.field-active");
utils.assertDomText(t,"Yes", "#dgrid_0-row-14 td.field-active");
utils.assertDomText(t,"No", "#dgrid_0-row-13 td.field-active");
/* make sure we at least have a link to the api on the master 14 */
var api = utils.getBaseUrl() + "api/v2/master/14";
utils.assertDomText(t,api, "#dgrid_0-row-14 a[href='"+api+"']");
},
function stopMaster(t) {
var query = "#dgrid_0-row-14 td.field-active";
return utils.when(
utils.playTestScenarioWaitForDomChange(
"buildbot.test.scenarios.base.BaseScenario.stopMaster",
query),
function() {
utils.assertDomText(t,"No", query);
});
}
]);
});
8 changes: 6 additions & 2 deletions www/src/bb/tests/runner.html
Expand Up @@ -23,21 +23,25 @@
.table tbody tr.failure td{
background-color: #f2dede;;
}
#testListContainer {
overflow: auto;

}
</style>
</head>
<body style="height: 100%;">
<div class="navbar navbar-inverse">
<div class="navbar-inner">
<div class="container-fluid">
<a class="brand" href="#all"><img src="../resources/favicon.ico"/> Buildbot</a>
<a class="brand" href="#all"><img src="../resources/img/favicon.ico"/> Buildbot</a>
<div class="nav-collapse pull-right">
<input type="checkbox" id="audio" name="audio"/>
<label for="audio">sounds?</label>
</div>
<div class="nav-collapse">
<ul class="nav" id="navlist">
<li class="active"><a href=""> Buildbot UI Test Suite </a></li>
<li><a href="../../ui/">Home</a></li>
<li><a href="../../../ui/">Home</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
Expand Down
92 changes: 85 additions & 7 deletions www/src/bb/tests/utils.js
Expand Up @@ -13,19 +13,42 @@
//
// Copyright Buildbot Team Members

define(["dojo/_base/declare", "bb/jsonapi"],function(declare,jsonapi) {
define(["dojo/_base/declare", "bb/jsonapi", "doh/main"],function(declare,jsonapi, doh) {
/* get the dojo script tag element */
var baseurl= dojo.query("script[data-dojo-config]")[0].src;
/* remove the 3 last path in the URL */
for (var i=0;i<3;i+=1) {
baseurl = baseurl.substr(0, baseurl.lastIndexOf("/"));
}
baseurl+="/";
return declare([],{
function testBrockenLink(t, tag) {
/* we need to use the special doh's deferred for this to work */
var d = new doh.Deferred();
var ctx = {errors : []};
var alltags = dojo.query(tag);
ctx.toLoad = alltags.length;
function didLoad(){
ctx.toLoad-=1;
if (ctx.toLoad===0) {
d.callback(true);
}
}
alltags.connect("onerror", function(){
ctx.errors.push(this.src+" is broken link");
didLoad();
});
alltags.connect("onload", didLoad);
alltags.forEach(function(x){x.src=x.src+"?reload";}); /* reload the images now we've setup the hooks */
d.addCallback(function(x) {
t.assertEqual(ctx.errors.length, 0, ctx.errors.join("\n"));
});
return d;
}
var utils = {
getBaseUrl : function() {
return baseurl;
},
goToBuildbotHash: function(doh, hash, test) {
registerBBTests: function(doh, hash, testname, tests) {
var topdog;
var _test;
try{
Expand All @@ -36,14 +59,69 @@ define(["dojo/_base/declare", "bb/jsonapi"],function(declare,jsonapi) {
}
if (topdog) {
if (hash.indexOf("?")>0) {
_test = "&test="+test;
_test = "&test="+testname;
}else{
_test = "?test="+test;
_test = "?test="+testname;
}
doh.register(test, require.toUrl(this.getBaseUrl()+"ui/#"+hash+_test));
doh.register(testname, require.toUrl(this.getBaseUrl()+"ui/#"+hash+_test));
} else {
doh.register("sanity", [
function baseurl(t) { t.assertEqual(window.bb_router.base_url, utils.getBaseUrl());},
function brockenimg(t) { return testBrockenLink(t,"img");}
]);
doh.register(testname, tests);
}
/* if we return true, the test is supposed to declare the real tests */
return !topdog;
},
assertDomText : function(t, expected, query) {
var e = dojo.query(query);
t.assertEqual(1, e.length, "query needs only one result: "+query);
t.assertEqual(expected, e[0].innerText," query's text is not as expected:"+query);
},
playTestScenario: function(scenario) {
var d = new doh.Deferred();
jsonapi.control(["testhooks"], "playScenario", { scenario:scenario}).then(function(res) {
d.callback(res);
});
return d;
},
/* play the scenario, and wait for any dom change triggered by tested code
on a given css query */
playTestScenarioWaitForDomChange: function(scenario, query) {
function get_query_html() {
var r ="";
dojo.query(query).forEach(function(e){r+= e.innerHTML;});
return r;
}
var orig_html = get_query_html();
var d = new doh.Deferred();
dojo.when(this.playTestScenario(scenario), function(res){
/* poll for dom change for 500 msecs,
doh times out at 1000msec
*/
var retries = 50;
var t = window.setInterval(function() {
retries -= 1;
if (retries <= 0 || get_query_html() !== orig_html) {
window.clearInterval(t);
d.callback(res);
}
}, 10);
});
return d;
},
when : function(d, f) {
if (d.addCallback) {
d.addCallback(f);
return d;
} else {
var _d = new doh.Deferred();
_d.addCallback(f);
_d.callback(d);
return _d;
}
}
})();/* note the singleton */
};
return utils;
});

0 comments on commit 9c89bce

Please sign in to comment.