summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Wall <richard@largo>2010-11-28 15:45:10 +0000
committerRichard Wall <richard@largo>2010-11-28 15:45:10 +0000
commit579605cb677a0345688c5421b0075b26111c4393 (patch)
treeb62bdc1fd6356239dad031450b6a373b85412d45
parent54b3c39b40077b045e7d10b78eaf85b11dad5a3a (diff)
parent02f4700f65df11c5ed286c716e9351daf547e853 (diff)
Merge lp:~richardw/jarmon/coveragev10.11
* Introduce unit tests for jarmon.downloadBinary, jarmon.RRDFile and jarmon.RrdQuery. Unit tests are based on YUI test. * Various API changes to facilitate testing * Various non-api library changes to fix bugs revealed by the unit tests. * Coverage is measured using jscoverage which reports 50% coverage of jarmon.js * Command line tool to generate unit test RRD data * Documentation describing how to run the tests and measure code coverage
-rw-r--r--docs/examples/index.html1
-rw-r--r--docs/index.html39
-rw-r--r--jarmon/jarmon.js88
-rw-r--r--jarmon/jarmon.test.js312
-rw-r--r--jarmonbuild/commands.py112
-rw-r--r--test.html24
-rw-r--r--testfile.binbin0 -> 1 bytes
7 files changed, 529 insertions, 47 deletions
diff --git a/docs/examples/index.html b/docs/examples/index.html
index 9d12389..3debd18 100644
--- a/docs/examples/index.html
+++ b/docs/examples/index.html
@@ -208,6 +208,7 @@
</div>
<div class="chart-container">
<h2 class="title"></h2>
+ <div class="error"></div>
<div class="chart"></div>
<div class="graph-legend"></div>
</div>
diff --git a/docs/index.html b/docs/index.html
index 1d57d49..b5ea14a 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -10,6 +10,7 @@
<li><a href="#browser-compatibility">Browser Compatibility</a></li>
<li><a href="#api-documentation">API documentation</a></li>
<li><a href="#release-process">Release process and tools</a></li>
+ <li><a href="#automated-tests">Running automated tests</a></li>
</ol>
<h2 id="browser-compatibility">Browser Compatibility</h2>
<p>Jarmon depends upon the <a href="http://code.google.com/p/flot/">Flot
@@ -46,7 +47,7 @@
<dd>This is the default IE compatibility workaround. Jarmon bundles the
<a href="http://code.google.com/p/explorercanvas/">excanvas library</a>
which translates canvas api calls into VML, but this is much slower
- than the native canvas support in other browsers. In the absense of
+ than the native canvas support in other browsers. In the absence of
other workarounds, Jarmon will transparently use the excanvas library.
<dt>Chrome Frame
@@ -92,3 +93,39 @@
out the source code, then issue the
<code>./bin/build -V 10.8 release</code> command from within the source
tree.</p>
+
+ <h2 id="automated-tests">Running automated tests</h2>
+ <p>Jarmon includes unit tests which are used to exercise the library
+ code, to check for compatibility with various platforms and web client
+ software and to check for regressions as the library evolves.</p>
+ <p>The unit tests are designed to be run inside a web browser using the
+ <a href="http://developer.yahoo.com/yui/yuitest/">YUI test harness</a>.</p>
+ <ol>
+ <li>Generate test data by running the following command
+ <code>./bin/build testdata</code> from within the source tree.
+ <li>Start a web server to serve the test page. Eg
+ <code>$ nginx -p . -c docs/examples/nginx.conf.example</code></li>
+ <li>Use a web browser to load
+ <a href="http://localhost:8080/test.html">the test page</a>
+ (<a href="http://localhost:8080/test.html">
+ http://localhost:8080/test.html</a>)</li>
+ </ol>
+ <h3>Measure code coverage</h3>
+ <p>There are number of tools available to measure the
+ <a href="http://en.wikipedia.org/wiki/Code_coverage">code coverage</a> of
+ Javascript unit tests.</p>
+ <p>One such tool is <a href="http://siliconforks.com/jscoverage/">
+ JSCoverage</a>. It is very easy to install JSCoverage on a Debian / Ubuntu
+ system and use it to measure the coverage of the Jarmon unit tests.</p>
+ <ol>
+ <li><code>sudo aptitude install jscoverage</code> - install
+ jscoverage.</li>
+ <li><code>jscoverage-server</code> - run the JSCoverage web server from
+ the root of the Jarmon source tree.</li>
+ <li>Visit <a href="http://localhost:8080/jscoverage.html?test.html">
+ http://localhost:8080/jscoverage.html?test.html</a> to run the unit
+ tests through the JSCoverage server. The server will automatically
+ <a href="http://en.wikipedia.org/wiki/Instrumentation_%28computer_programming%29">
+ instrument</a> the Jarmon code and present a web interface which reports
+ how much of the code has been exercised.</li>
+ </ol>
diff --git a/jarmon/jarmon.js b/jarmon/jarmon.js
index 44c4dad..406c4d1 100644
--- a/jarmon/jarmon.js
+++ b/jarmon/jarmon.js
@@ -27,6 +27,7 @@ if(typeof jarmon == 'undefined') {
var jarmon = {};
}
+
jarmon.downloadBinary = function(url) {
/**
* Download a binary file asynchronously using the jQuery.ajax function
@@ -141,31 +142,36 @@ jarmon.RrdQuery = function(rrd, unit) {
this.unit = unit;
};
-jarmon.RrdQuery.prototype.getData = function(startTime, endTime, dsId, cfName) {
+jarmon.RrdQuery.prototype.getData = function(startTimeJs, endTimeJs, dsId, cfName) {
/**
* Generate a Flot compatible data object containing rows between start and
* end time. The rows are taken from the first RRA whose data spans the
* requested time range.
*
* @method getData
- * @param startTime {Number} start timestamp
- * @param endTime {Number} end timestamp
+ * @param startTimeJs {Number} start timestamp in microseconds
+ * @param endTimeJs {Number} end timestamp in microseconds
* @param dsId {Variant} identifier of the RRD datasource (string or number)
* @param cfName {String} The name of an RRD consolidation function (CF)
* eg AVERAGE, MIN, MAX
* @return {Object} A Flot compatible data series
* eg label: '', data: [], unit: ''
**/
- var startTimestamp = startTime/1000;
+ if (startTimeJs >= endTimeJs) {
+ throw RangeError(
+ ['starttime must be less than endtime. ',
+ 'starttime: ', startTimeJs,
+ 'endtime: ', endTimeJs].join(''));
+ }
+
+ var startTime = startTimeJs/1000;
var lastUpdated = this.rrd.getLastUpdate();
- var endTimestamp = lastUpdated;
- if(endTime) {
- endTimestamp = endTime/1000;
- // If end time stamp is beyond the range of this rrd then reset it
- if(lastUpdated < endTimestamp) {
- endTimestamp = lastUpdated;
- }
+
+ // default endTime to the last updated time (quantized to rrd step boundry)
+ var endTime = lastUpdated - lastUpdated%this.rrd.getMinStep();
+ if(endTimeJs) {
+ endTime = endTimeJs/1000;
}
if(dsId == null) {
@@ -177,7 +183,7 @@ jarmon.RrdQuery.prototype.getData = function(startTime, endTime, dsId, cfName) {
cfName = 'AVERAGE';
}
- var rra, step, rraRowCount, firstUpdated;
+ var rra, step, rraRowCount, lastRowTime, firstRowTime;
for(var i=0; i<this.rrd.getNrRRAs(); i++) {
// Look through all RRAs looking for the most suitable
@@ -191,29 +197,53 @@ jarmon.RrdQuery.prototype.getData = function(startTime, endTime, dsId, cfName) {
step = rra.getStep();
rraRowCount = rra.getNrRows();
- firstUpdated = lastUpdated - (rraRowCount - 1) * step;
+ lastRowTime = lastUpdated-lastUpdated%step;
+ firstRowTime = lastRowTime - rraRowCount * step;
+
// We assume that the RRAs are listed in ascending order of time range,
// therefore the first RRA which contains the range minimum should give
// the highest resolution data for this range.
- if(firstUpdated <= startTimestamp) {
+ if(firstRowTime <= startTime) {
break;
}
}
// If we got to the end of the loop without ever defining step, it means
// that the CF check never succeded.
if(!step) {
- throw new Error('Unrecognised consolidation function: ' + cfName);
+ throw TypeError('Unrecognised consolidation function: ' + cfName);
}
- var startRow = rraRowCount - parseInt((lastUpdated - Math.max(startTimestamp, firstUpdated))/step) - 1;
- var endRow = rraRowCount - parseInt((lastUpdated - endTimestamp)/step) - 1;
-
var flotData = [];
- var timestamp = firstUpdated + (startRow - 1) * step;
var dsIndex = ds.getIdx();
- for (var i=startRow; i<=endRow; i++) {
- flotData.push([timestamp*1000.0, rra.getEl(i, dsIndex)]);
- timestamp += step;
+
+ var startRowTime = Math.max(firstRowTime, startTime - startTime%step);
+ var endRowTime = Math.min(lastRowTime, endTime - endTime%step);
+ // If RRD exists, but hasn't been updated then the start time might end up
+ // being higher than the end time (which is capped at the last row time of
+ // the chosen RRA, so cap startTime at endTime...if you see what I mean)
+ startRowTime = Math.min(startRowTime, endRowTime);
+
+ /*
+ console.log('FRT: ', new Date(firstRowTime*1000));
+ console.log('LRT: ', new Date(lastRowTime*1000));
+ console.log('SRT: ', new Date(startRowTime*1000));
+ console.log('ERT: ', new Date(endRowTime*1000));
+ console.log('DIFF: ', (lastRowTime - startRowTime) / step);
+ console.log('ROWS: ', rraRowCount);
+ */
+
+ var startRowIndex = rraRowCount - (lastRowTime - startRowTime) / step;
+ var endRowIndex = rraRowCount - (lastRowTime - endRowTime) / step;
+
+ //console.log('SRI: ', startRowIndex);
+ //console.log('ERI: ', endRowIndex);
+
+ var val;
+ var timestamp = startRowTime;
+ for(var i=startRowIndex; i<endRowIndex; i++) {
+ val = rra.getEl(i, dsIndex)
+ flotData.push([timestamp*1000.0, val]);
+ timestamp += step
}
// Now get the date of the earliest record in entire rrd file, ie that of
@@ -240,7 +270,7 @@ jarmon.RrdQuery.prototype.getData = function(startTime, endTime, dsId, cfName) {
jarmon.RrdQueryRemote = function(url, unit, downloader) {
this.url = url;
this.unit = unit;
- this.downloader = downloader;
+ this.downloader = downloader || jarmon.downloadBinary;
this.lastUpdate = 0;
this._download = null;
};
@@ -498,6 +528,9 @@ jarmon.Chart.prototype.draw = function() {
return MochiKit.Async.gatherResults(results)
.addCallback(
function(self, data) {
+ // Clear any previous error messages.
+ self.template.find('.error').empty().hide();
+
var i, label, disabled = [];
unit = '';
for(i=0; i<data.length; i++) {
@@ -514,7 +547,7 @@ jarmon.Chart.prototype.draw = function() {
}
}
- $.plot(self.template.find('.chart'), data, self.options);
+ $.plot(self.template.find('.chart').empty().show(), data, self.options);
var yaxisUnitLabel = $('<div>').text(self.siPrefix + unit)
.css({width: '100px',
@@ -533,7 +566,7 @@ jarmon.Chart.prototype.draw = function() {
// table is useful as it generates an optimum label element
// width which we can copy to the new divs + a little extra
// to accomodate the color box
- var legend = self.template.find('.graph-legend');
+ var legend = self.template.find('.graph-legend').show();
legend.empty();
self.template.find('.legendLabel')
.each(function(i, el) {
@@ -562,7 +595,10 @@ jarmon.Chart.prototype.draw = function() {
}, this)
.addErrback(
function(self, failure) {
- self.template.text('error: ' + failure.message);
+ self.template.find('.chart').empty().hide();
+ self.template.find('.graph-legend').empty().hide();
+ self.template.find('.error').text('error: ' + failure.message);
+
}, this)
.addBoth(
function(self, res) {
diff --git a/jarmon/jarmon.test.js b/jarmon/jarmon.test.js
new file mode 100644
index 0000000..c838023
--- /dev/null
+++ b/jarmon/jarmon.test.js
@@ -0,0 +1,312 @@
+/* Copyright (c) 2010 Richard Wall <richard (at) the-moon.net>
+ * See LICENSE for details.
+ *
+ * Unit tests for Jarmon
+ **/
+
+YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
+ Y.Test.Runner.add(new Y.Test.Case({
+ name: "jarmon.downloadBinary",
+
+ test_urlNotFound: function () {
+ /**
+ * When url cannot be found, the deferred should errback with status
+ * 404.
+ **/
+ var d = new jarmon.downloadBinary('non-existent-file.html');
+ d.addBoth(
+ function(self, ret) {
+ self.resume(function() {
+ Y.Assert.isInstanceOf(Error, ret);
+ Y.Assert.areEqual(404, ret.message);
+ });
+ }, this);
+
+ this.wait();
+ },
+
+ test_urlFound: function () {
+ /**
+ * When url is found, the deferred should callback with an instance
+ * of javascriptrrd.BinaryFile
+ **/
+ var d = new jarmon.downloadBinary('testfile.bin');
+ d.addBoth(
+ function(self, ret) {
+ self.resume(function() {
+ Y.Assert.isInstanceOf(BinaryFile, ret);
+ Y.Assert.areEqual(String.fromCharCode(0), ret.getRawData());
+ });
+ }, this);
+
+ this.wait();
+ }
+
+ }));
+
+ var RRD_STEP = 10;
+ var RRD_DSNAME = 'speed';
+ var RRD_DSINDEX = 0;
+ var RRD_RRACOUNT = 2;
+ var RRD_RRAROWS = 12;
+ var RRD_STARTTIME = new Date('1 jan 1980 00:00:00').getTime();
+ var RRD_ENDTIME = new Date('1 jan 1980 00:02:00').getTime();
+
+ Y.Test.Runner.add(new Y.Test.Case({
+ name: "jarmon.RRDFile",
+
+ setUp: function() {
+ this.d = new jarmon.downloadBinary('build/test.rrd')
+ .addCallback(
+ function(self, binary) {
+ return new RRDFile(binary);
+ }, this)
+ .addErrback(
+ function(ret) {
+ console.log(ret);
+ });
+ },
+
+ test_getLastUpdate: function () {
+ /**
+ * The generated rrd file should have a lastupdate date of
+ * 1980-01-01 00:50:01
+ **/
+ this.d.addCallback(
+ function(self, rrd) {
+ self.resume(function() {
+ Y.Assert.areEqual(
+ RRD_ENDTIME/1000+1, rrd.getLastUpdate());
+ });
+ }, this);
+ this.wait();
+ },
+
+ test_getDSIndex: function () {
+ /**
+ * The generated rrd file should have a single DS whose name is
+ * 'speed'. A RangeError is thrown if the requested index or dsName
+ * doesnt exist.
+ **/
+ this.d.addCallback(
+ function(self, rrd) {
+ self.resume(function() {
+ Y.Assert.areEqual(RRD_DSNAME, rrd.getDS(0).getName());
+ Y.Assert.areEqual(
+ RRD_DSINDEX, rrd.getDS('speed').getIdx());
+ var error = null;
+ try {
+ rrd.getDS(RRD_DSINDEX+1);
+ } catch(e) {
+ error = e;
+ }
+ Y.assert(error instanceof RangeError);
+ });
+ }, this);
+ this.wait();
+ },
+
+ test_getNrRRAs: function () {
+ /**
+ * The generated rrd file should have a single RRA
+ **/
+ this.d.addCallback(
+ function(self, rrd) {
+ self.resume(function() {
+ Y.Assert.areEqual(RRD_RRACOUNT, rrd.getNrRRAs());
+ });
+ }, this);
+ this.wait();
+ },
+
+ test_getRRA: function () {
+ /**
+ * The generated rrd file should have a single RRA using AVERAGE
+ * consolidation, step=10, rows=6 and values 0-5
+ * rra.getEl throws a RangeError if asked for row which doesn't
+ * exist.
+ **/
+ this.d.addCallback(
+ function(self, rrd) {
+ self.resume(function() {
+ var rra = rrd.getRRA(0);
+ Y.Assert.areEqual('AVERAGE', rra.getCFName());
+ Y.Assert.areEqual(RRD_STEP, rra.getStep());
+ Y.Assert.areEqual(RRD_RRAROWS, rra.getNrRows());
+ for(var i=0; i<RRD_RRAROWS; i++) {
+ Y.Assert.areEqual(i, rra.getEl(i, RRD_DSINDEX));
+ }
+ var error = null
+ try {
+ rra.getEl(RRD_RRAROWS+1, 0);
+ } catch(e) {
+ error = e;
+ }
+ Y.assert(error instanceof RangeError);
+ });
+ }, this);
+ this.wait();
+ }
+ }));
+
+ Y.Test.Runner.add(new Y.Test.Case({
+ name: "jarmon.RrdQuery",
+
+ setUp: function() {
+ this.d = new jarmon.downloadBinary('build/test.rrd')
+ .addCallback(
+ function(self, binary) {
+ return new RRDFile(binary);
+ }, this)
+ .addErrback(
+ function(ret) {
+ console.log(ret);
+ });
+ },
+
+ test_getDataTimeRangeOverlapError: function () {
+ /**
+ * The starttime must be less than the endtime
+ **/
+ this.d.addCallback(
+ function(self, rrd) {
+ self.resume(function() {
+ var rq = new jarmon.RrdQuery(rrd, '');
+ var error = null;
+ try {
+ rq.getData(1, 0);
+ } catch(e) {
+ error = e;
+ }
+ Y.Assert.isInstanceOf(RangeError, error);
+ });
+ }, this);
+ this.wait();
+ },
+
+
+ test_getDataUnknownCfError: function () {
+ /**
+ * Error is raised if the rrd file doesn't contain an RRA with the
+ * requested consolidation function (CF)
+ **/
+ this.d.addCallback(
+ function(self, rrd) {
+ self.resume(function() {
+ var rq = new jarmon.RrdQuery(rrd, '');
+ var error = null;
+ try {
+ rq.getData(RRD_STARTTIME, RRD_ENDTIME, 0, 'FOO');
+ } catch(e) {
+ error = e;
+ }
+ Y.Assert.isInstanceOf(TypeError, error);
+ });
+ }, this);
+ this.wait();
+ },
+
+
+ test_getData: function () {
+ /**
+ * The generated rrd file should have values 0-9 at 300s intervals
+ * starting at 1980-01-01 00:00:00
+ * Result should include a data points with times > starttime and
+ * <= endTime
+ **/
+ this.d.addCallback(
+ function(self, rrd) {
+ self.resume(function() {
+ var rq = new jarmon.RrdQuery(rrd, '');
+
+ // We request data starting 1 STEP +1s after the RRD file
+ // first val and ending 1 STEP -1s before the RRD last val
+ // ie one step within the RRD file, but 1s away from the
+ // step boundary to test the quantisation of the
+ // requested time range.
+ var data = rq.getData(
+ RRD_STARTTIME + (RRD_STEP+1) * 1000,
+ RRD_ENDTIME - (RRD_STEP-1) * 1000);
+
+ // so we expect two less rows than the total rows in the
+ // file.
+ Y.Assert.areEqual(RRD_RRAROWS-2, data.data.length);
+
+ // The value of the first returned row should be the
+ // second value in the RRD file (starts at value 0)
+ Y.Assert.areEqual(1, data.data[0][1]);
+
+ // The value of the last returned row should be the
+ // 10 value in the RRD file (starts at value 0)
+ Y.Assert.areEqual(10, data.data[data.data.length-1][1]);
+
+ // The timestamp of the first returned row should be
+ // exactly one step after the start of the RRD file
+ Y.Assert.areEqual(
+ RRD_STARTTIME+RRD_STEP*1000, data.data[0][0]);
+
+ // RRD_ENDTIME is on a step boundary and is therfore
+ // actually the start time of a new row
+ // So when we ask for endTime = RRD_ENDTIME-STEP-1 we
+ // actually get data up to the 2nd to last RRD row.
+ Y.Assert.areEqual(
+ RRD_ENDTIME-RRD_STEP*1000*2,
+ data.data[data.data.length-1][0]);
+ });
+ }, this);
+ this.wait();
+ },
+
+ test_getDataUnknownValues: function () {
+ /**
+ * If the requested time range is outside the range of the RRD file
+ * we should not get any values back
+ **/
+ this.d.addCallback(
+ function(self, rrd) {
+ self.resume(function() {
+ var rq = new jarmon.RrdQuery(rrd, '');
+ var data = rq.getData(RRD_ENDTIME, RRD_ENDTIME+1000);
+ Y.Assert.areEqual(0, data.data.length);
+ });
+ }, this);
+ this.wait();
+ }
+
+ }));
+
+
+ Y.Test.Runner.add(new Y.Test.Case({
+ name: "jarmon.Chart",
+
+ test_draw: function () {
+ /**
+ * Test that a rendered chart has the correct dimensions, legend,
+ * axis, labels etc
+ **/
+ var $tpl = $('<div><div class="chart"></div></div>').appendTo($('body'));
+ var c = new jarmon.Chart($tpl, jarmon.Chart.BASE_OPTIONS);
+ //
+ c.options.xaxis.tzoffset = 0;
+ c.addData('speed', new jarmon.RrdQueryRemote('build/test.rrd', 'm/s'), true);
+ var d = c.setTimeRange(RRD_STARTTIME, RRD_ENDTIME);
+ d.addCallback(
+ function(self) {
+ self.resume(function() {
+ // TODO: write useful tests
+ });
+ }, this);
+ this.wait();
+ },
+ }));
+
+
+ //initialize the console
+ var yconsole = new Y.Console({
+ newestOnTop: false,
+ width:'600px',
+ height: '400px'
+ });
+ yconsole.render('#log');
+ Y.Test.Runner.run();
+});
diff --git a/jarmonbuild/commands.py b/jarmonbuild/commands.py
index 5071a46..9ababd9 100644
--- a/jarmonbuild/commands.py
+++ b/jarmonbuild/commands.py
@@ -8,7 +8,9 @@ import logging
import os
import shutil
import sys
+import time
+from datetime import datetime
from optparse import OptionParser
from subprocess import check_call, PIPE
from tempfile import gettempdir
@@ -34,13 +36,9 @@ class BuildError(Exception):
class BuildCommand(object):
- def __init__(self, buildversion, log=None):
- self.buildversion = buildversion
- if log is not None:
- self.log = log
- else:
- self.log = logging.getLogger(
- '%s.%s' % (__name__, self.__class__.__name__))
+ def __init__(self):
+ self.log = logging.getLogger(
+ '%s.%s' % (__name__, self.__class__.__name__))
self.workingbranch_dir = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..'))
@@ -60,12 +58,26 @@ class BuildApidocsCommand(BuildCommand):
Download YUI Doc and use it to generate apidocs for jarmon
"""
+ command_name = 'apidocs'
+
def main(self, argv):
"""
The main entry point for the build-apidocs command
@param argv: The list of arguments passed to the build-apidocs command
"""
+
+ parser = OptionParser(
+ usage='build [options] %s VERSION' % (self.command_name,))
+ parser.disable_interspersed_args()
+ options, args = parser.parse_args(argv)
+
+ if len(args) != 1:
+ parser.error('Wrong number of arguments. This command expects a '
+ 'version number only.')
+
+ buildversion = args[0]
+
tmpdir = gettempdir()
workingbranch_dir = self.workingbranch_dir
build_dir = self.build_dir
@@ -131,7 +143,7 @@ class BuildApidocsCommand(BuildCommand):
'--template=%s' % (
os.path.join(
workingbranch_dir, 'jarmonbuild', 'yuidoc_template'),),
- '--version=%s' % (self.buildversion,),
+ '--version=%s' % (buildversion,),
'--project=%s' % (JARMON_PROJECT_TITLE,),
'--projecturl=%s' % (JARMON_PROJECT_URL,)
), stdout=PIPE, stderr=PIPE,)
@@ -145,7 +157,21 @@ class BuildReleaseCommand(BuildCommand):
upload to Launchpad.
"""
+ command_name = 'release'
+
def main(self, argv):
+
+ parser = OptionParser(
+ usage='build [options] %s VERSION' % (self.command_name,))
+ parser.disable_interspersed_args()
+ options, args = parser.parse_args(argv)
+
+ if len(args) != 1:
+ parser.error('Wrong number of arguments. This command expects a '
+ 'version number only.')
+
+ buildversion = args[0]
+
workingbranch_dir = self.workingbranch_dir
build_dir = self.build_dir
@@ -167,11 +193,11 @@ class BuildReleaseCommand(BuildCommand):
self.log.debug('Generate apidocs')
- BuildApidocsCommand(buildversion=self.buildversion).main(argv)
+ BuildApidocsCommand().main([buildversion])
self.log.debug('Generate archive')
- archive_root = 'jarmon-%s' % (self.buildversion,)
+ archive_root = 'jarmon-%s' % (buildversion,)
prefix_len = len(build_dir) + 1
z = ZipFile('%s.zip' % (archive_root,), 'w', ZIP_DEFLATED)
try:
@@ -185,11 +211,54 @@ class BuildReleaseCommand(BuildCommand):
z.close()
-# The available sub commands
-build_commands = {
- 'apidocs': BuildApidocsCommand,
- 'release': BuildReleaseCommand,
-}
+class BuildTestDataCommand(BuildCommand):
+ """
+ Create data for use in unittests
+ """
+
+ command_name = 'testdata'
+
+ def main(self, argv):
+ """
+ Create an RRD file with values 0-9 entered at 1 second intervals from
+ 1980-01-01 00:00:00 (the first date that rrdtool allows)
+ """
+ from datetime import datetime
+ from pyrrd.rrd import DataSource, RRA, RRD
+ start = int(datetime(1980, 1, 1, 0, 0).strftime('%s'))
+ dss = []
+ rras = []
+ filename = os.path.join(self.build_dir, 'test.rrd')
+
+ rows = 12
+ step = 10
+
+ dss.append(DataSource(dsName='speed', dsType='GAUGE', heartbeat=2*step))
+ rras.append(RRA(cf='AVERAGE', xff=0.5, steps=1, rows=rows))
+ rras.append(RRA(cf='AVERAGE', xff=0.5, steps=12, rows=rows))
+ my_rrd = RRD(filename, ds=dss, rra=rras, start=start, step=step)
+ my_rrd.create()
+
+ for i, t in enumerate(range(start+step, start+step+(rows*step), step)):
+ self.log.debug('DATA: %s %s (%s)' % (t, i, datetime.fromtimestamp(t)))
+ my_rrd.bufferValue(t, i)
+
+ # Add further data 1 second later to demonstrate that the rrd
+ # lastupdatetime does not necessarily fall on a step boundary
+ t += 1
+ i += 1
+ self.log.debug('DATA: %s %s (%s)' % (t, i, datetime.fromtimestamp(t)))
+ my_rrd.bufferValue(t, i)
+
+ my_rrd.update()
+
+
+# The available subcommands
+SUBCOMMAND_HANDLERS = [
+ BuildApidocsCommand,
+ BuildReleaseCommand,
+ BuildTestDataCommand,
+]
def main(argv=sys.argv[1:]):
@@ -197,10 +266,13 @@ def main(argv=sys.argv[1:]):
The root build command which dispatches to various subcommands for eg
building apidocs and release zip files.
"""
- parser = OptionParser(usage='%prog [options] SUBCOMMAND [options]')
- parser.add_option(
- '-V', '--build-version', dest='buildversion', default='0',
- metavar='BUILDVERSION', help='Specify the build version')
+
+ build_commands = dict(
+ (handler.command_name, handler) for handler in SUBCOMMAND_HANDLERS)
+
+ parser = OptionParser(
+ usage='build [options] SUBCOMMAND [options]',
+ description='Available subcommands are: %r' % (build_commands.keys(),))
parser.add_option(
'-d', '--debug', action='store_true', default=False, dest='debug',
help='Print verbose debug log to stderr')
@@ -236,4 +308,4 @@ def main(argv=sys.argv[1:]):
log.setLevel(logging.DEBUG)
ch.setLevel(logging.DEBUG)
- command_factory(buildversion=options.buildversion).main(argv=args)
+ command_factory().main(argv=args)
diff --git a/test.html b/test.html
new file mode 100644
index 0000000..e8508f6
--- /dev/null
+++ b/test.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Jarmon Unit Test Runner</title>
+ <link rel="stylesheet" type="text/css"
+ href="http://developer.yahoo.com/yui/3/assets/yui.css"/>
+ <link rel="stylesheet" type="text/css"
+ href="http://yui.yahooapis.com/3.1.1/build/cssfonts/fonts-min.css"/>
+ <style type='text/css'>
+ .chart {
+ width: 500px;
+ height: 100px;
+ }
+ </style>
+ <script src="http://yui.yahooapis.com/3.1.1/build/yui/yui-min.js"></script>
+ <script src="docs/examples/assets/js/dependencies.js"></script>
+ <script src="jarmon/jarmon.js"></script>
+ <script src="jarmon/jarmon.test.js"></script>
+ </head>
+ <body class="yui3-skin-sam yui-skin-sam">
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testfile.bin b/testfile.bin
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/testfile.bin
Binary files differ