feat(bench press): allow multiple reporters, metrics and driver extensions.

This commit is contained in:
Tobias Bosch 2015-02-20 13:32:54 -08:00
parent 987a5fdf56
commit 1d4ffd986d
29 changed files with 559 additions and 63 deletions

View File

@ -14,5 +14,7 @@ export { IOsDriverExtension } from './src/webdriver/ios_driver_extension';
export { Runner } from './src/runner'; export { Runner } from './src/runner';
export { Options } from './src/sample_options'; export { Options } from './src/sample_options';
export { MeasureValues } from './src/measure_values'; export { MeasureValues } from './src/measure_values';
export { MultiMetric } from './src/metric/multi_metric';
export { MultiReporter } from './src/reporter/multi_reporter';
export { bind, Injector, OpaqueToken } from 'angular2/di'; export { bind, Injector, OpaqueToken } from 'angular2/di';

View File

@ -1,3 +1,4 @@
import { bind } from 'angular2/di';
import { import {
Promise, PromiseWrapper Promise, PromiseWrapper
} from 'angular2/src/facade/async'; } from 'angular2/src/facade/async';
@ -11,6 +12,14 @@ import { StringMap } from 'angular2/src/facade/collection';
*/ */
@ABSTRACT() @ABSTRACT()
export class Metric { export class Metric {
static bindTo(delegateToken) {
return [
bind(Metric).toFactory(
(delegate) => delegate, [delegateToken]
)
];
}
/** /**
* Starts measuring * Starts measuring
*/ */

View File

@ -0,0 +1,69 @@
import { bind, Injector, OpaqueToken } from 'angular2/di';
import { List, ListWrapper, StringMapWrapper, StringMap } from 'angular2/src/facade/collection';
import { Promise, PromiseWrapper } from 'angular2/src/facade/async';
import { Metric } from '../metric';
export class MultiMetric extends Metric {
static createBindings(childTokens) {
return [
bind(_CHILDREN).toAsyncFactory(
(injector) => PromiseWrapper.all(ListWrapper.map(childTokens, (token) => injector.asyncGet(token) )),
[Injector]
),
bind(MultiMetric).toFactory(
(children) => new MultiMetric(children),
[_CHILDREN]
)
];
}
_metrics:List;
constructor(metrics) {
super();
this._metrics = metrics;
}
/**
* Starts measuring
*/
beginMeasure():Promise {
return PromiseWrapper.all(ListWrapper.map(
this._metrics, (metric) => metric.beginMeasure()
));
}
/**
* Ends measuring and reports the data
* since the begin call.
* @param restart: Whether to restart right after this.
*/
endMeasure(restart:boolean):Promise<StringMap> {
return PromiseWrapper.all(ListWrapper.map(
this._metrics, (metric) => metric.endMeasure(restart)
)).then( (values) => {
return mergeStringMaps(values);
});
}
/**
* Describes the metrics provided by this metric implementation.
* (e.g. units, ...)
*/
describe():StringMap {
return mergeStringMaps(this._metrics.map( (metric) => metric.describe() ));
}
}
function mergeStringMaps(maps) {
var result = {};
ListWrapper.forEach(maps, (map) => {
StringMapWrapper.forEach(map, (value, prop) => {
result[prop] = value;
});
});
return result;
}
var _CHILDREN = new OpaqueToken('MultiMetric.children');

View File

@ -155,7 +155,7 @@ var _MAX_RETRY_COUNT = 20;
var _MARK_NAME_PREFIX = 'benchpress'; var _MARK_NAME_PREFIX = 'benchpress';
var _SET_TIMEOUT = new OpaqueToken('PerflogMetric.setTimeout'); var _SET_TIMEOUT = new OpaqueToken('PerflogMetric.setTimeout');
var _BINDINGS = [ var _BINDINGS = [
bind(Metric).toFactory( bind(PerflogMetric).toFactory(
(driverExtension, setTimeout) => new PerflogMetric(driverExtension, setTimeout), (driverExtension, setTimeout) => new PerflogMetric(driverExtension, setTimeout),
[WebDriverExtension, _SET_TIMEOUT] [WebDriverExtension, _SET_TIMEOUT]
), ),

View File

@ -1,3 +1,4 @@
import { bind } from 'angular2/di';
import { import {
Promise, PromiseWrapper Promise, PromiseWrapper
} from 'angular2/src/facade/async'; } from 'angular2/src/facade/async';
@ -12,6 +13,14 @@ import { MeasureValues } from './measure_values';
*/ */
@ABSTRACT() @ABSTRACT()
export class Reporter { export class Reporter {
static bindTo(delegateToken) {
return [
bind(Reporter).toFactory(
(delegate) => delegate, [delegateToken]
)
];
}
reportMeasureValues(values:MeasureValues):Promise { reportMeasureValues(values:MeasureValues):Promise {
throw new BaseException('NYI'); throw new BaseException('NYI');
} }

View File

@ -109,7 +109,7 @@ export class ConsoleReporter extends Reporter {
var _PRINT = new OpaqueToken('ConsoleReporter.print'); var _PRINT = new OpaqueToken('ConsoleReporter.print');
var _COLUMN_WIDTH = new OpaqueToken('ConsoleReporter.columnWidht'); var _COLUMN_WIDTH = new OpaqueToken('ConsoleReporter.columnWidht');
var _BINDINGS = [ var _BINDINGS = [
bind(Reporter).toFactory( bind(ConsoleReporter).toFactory(
(columnWidth, sampleDescription, print) => new ConsoleReporter(columnWidth, sampleDescription, print), (columnWidth, sampleDescription, print) => new ConsoleReporter(columnWidth, sampleDescription, print),
[_COLUMN_WIDTH, SampleDescription, _PRINT] [_COLUMN_WIDTH, SampleDescription, _PRINT]
), ),

View File

@ -0,0 +1,42 @@
import { bind, Injector, OpaqueToken } from 'angular2/di';
import { List, ListWrapper } from 'angular2/src/facade/collection';
import { Promise, PromiseWrapper } from 'angular2/src/facade/async';
import { MeasureValues } from '../measure_values';
import { Reporter } from '../reporter';
export class MultiReporter extends Reporter {
static createBindings(childTokens) {
return [
bind(_CHILDREN).toAsyncFactory(
(injector) => PromiseWrapper.all(ListWrapper.map(childTokens, (token) => injector.asyncGet(token) )),
[Injector]
),
bind(MultiReporter).toFactory(
(children) => new MultiReporter(children),
[_CHILDREN]
)
];
}
_reporters:List;
constructor(reporters) {
super();
this._reporters = reporters;
}
reportMeasureValues(values:MeasureValues):Promise {
return PromiseWrapper.all(ListWrapper.map(
this._reporters, (reporter) => reporter.reportMeasureValues(values)
));
}
reportSample(completeSample:List<MeasureValues>, validSample:List<MeasureValues>):Promise {
return PromiseWrapper.all(ListWrapper.map(
this._reporters, (reporter) => reporter.reportSample(completeSample, validSample)
));
}
}
var _CHILDREN = new OpaqueToken('MultiReporter.children');

View File

@ -5,11 +5,19 @@ import { Promise } from 'angular2/src/facade/async';
import { Sampler, SampleState } from './sampler'; import { Sampler, SampleState } from './sampler';
import { ConsoleReporter } from './reporter/console_reporter'; import { ConsoleReporter } from './reporter/console_reporter';
import { MultiReporter } from './reporter/multi_reporter';
import { RegressionSlopeValidator } from './validator/regression_slope_validator'; import { RegressionSlopeValidator } from './validator/regression_slope_validator';
import { SizeValidator } from './validator/size_validator';
import { Validator } from './validator';
import { PerflogMetric } from './metric/perflog_metric'; import { PerflogMetric } from './metric/perflog_metric';
import { MultiMetric } from './metric/multi_metric';
import { ChromeDriverExtension } from './webdriver/chrome_driver_extension'; import { ChromeDriverExtension } from './webdriver/chrome_driver_extension';
import { IOsDriverExtension } from './webdriver/ios_driver_extension';
import { WebDriverExtension } from './web_driver_extension';
import { SampleDescription } from './sample_description'; import { SampleDescription } from './sample_description';
import { WebDriverAdapter } from './web_driver_adapter';
import { Reporter } from './reporter';
import { Metric } from './metric';
import { Options } from './sample_options'; import { Options } from './sample_options';
/** /**
@ -48,7 +56,23 @@ var _DEFAULT_BINDINGS = [
Sampler.BINDINGS, Sampler.BINDINGS,
ConsoleReporter.BINDINGS, ConsoleReporter.BINDINGS,
RegressionSlopeValidator.BINDINGS, RegressionSlopeValidator.BINDINGS,
SizeValidator.BINDINGS,
ChromeDriverExtension.BINDINGS, ChromeDriverExtension.BINDINGS,
IOsDriverExtension.BINDINGS,
PerflogMetric.BINDINGS, PerflogMetric.BINDINGS,
SampleDescription.BINDINGS SampleDescription.BINDINGS,
MultiReporter.createBindings([ConsoleReporter]),
MultiMetric.createBindings([PerflogMetric]),
Reporter.bindTo(MultiReporter),
Validator.bindTo(RegressionSlopeValidator),
WebDriverExtension.bindTo([ChromeDriverExtension, IOsDriverExtension]),
Metric.bindTo(MultiMetric),
bind(Options.CAPABILITIES).toAsyncFactory(
(adapter) => adapter.capabilities(), [WebDriverAdapter]
),
bind(Options.USER_AGENT).toAsyncFactory(
(adapter) => adapter.executeScript('return window.navigator.userAgent;'), [WebDriverAdapter]
)
]; ];

View File

@ -27,15 +27,18 @@ export class SampleDescription {
var _BINDINGS = [ var _BINDINGS = [
bind(SampleDescription).toFactory( bind(SampleDescription).toFactory(
(metric, id, forceGc, validator, defaultDesc, userDesc) => new SampleDescription(id, (metric, id, forceGc, userAgent, validator, defaultDesc, userDesc) => new SampleDescription(id,
[ [
{'forceGc': forceGc}, {'forceGc': forceGc, 'userAgent': userAgent},
validator.describe(), validator.describe(),
defaultDesc, defaultDesc,
userDesc userDesc
], ],
metric.describe()), metric.describe()),
[Metric, Options.SAMPLE_ID, Options.FORCE_GC, Validator, Options.DEFAULT_DESCRIPTION, Options.SAMPLE_DESCRIPTION] [
Metric, Options.SAMPLE_ID, Options.FORCE_GC, Options.USER_AGENT,
Validator, Options.DEFAULT_DESCRIPTION, Options.SAMPLE_DESCRIPTION
]
), ),
bind(Options.DEFAULT_DESCRIPTION).toValue({}), bind(Options.DEFAULT_DESCRIPTION).toValue({}),
bind(Options.SAMPLE_DESCRIPTION).toValue({}) bind(Options.SAMPLE_DESCRIPTION).toValue({})

View File

@ -13,11 +13,17 @@ export class Options {
static get PREPARE() { return _PREPARE; } static get PREPARE() { return _PREPARE; }
// TODO(tbosch): use static initializer when our transpiler supports it // TODO(tbosch): use static initializer when our transpiler supports it
static get EXECUTE() { return _EXECUTE; } static get EXECUTE() { return _EXECUTE; }
// TODO(tbosch): use static initializer when our transpiler supports it
static get CAPABILITIES() { return _CAPABILITIES; }
// TODO(tbosch): use static initializer when our transpiler supports it
static get USER_AGENT() { return _USER_AGENT; }
} }
var _SAMPLE_ID = new OpaqueToken('SampleDescription.sampleId'); var _SAMPLE_ID = new OpaqueToken('Options.sampleId');
var _DEFAULT_DESCRIPTION = new OpaqueToken('SampleDescription.defaultDescription'); var _DEFAULT_DESCRIPTION = new OpaqueToken('Options.defaultDescription');
var _SAMPLE_DESCRIPTION = new OpaqueToken('SampleDescription.sampleDescription'); var _SAMPLE_DESCRIPTION = new OpaqueToken('Options.sampleDescription');
var _FORCE_GC = new OpaqueToken('Sampler.forceGc'); var _FORCE_GC = new OpaqueToken('Options.forceGc');
var _PREPARE = new OpaqueToken('Sampler.prepare'); var _PREPARE = new OpaqueToken('Options.prepare');
var _EXECUTE = new OpaqueToken('Sampler.execute'); var _EXECUTE = new OpaqueToken('Options.execute');
var _CAPABILITIES = new OpaqueToken('Options.capabilities');
var _USER_AGENT = new OpaqueToken('Options.userAgent');

View File

@ -1,3 +1,4 @@
import { bind } from 'angular2/di';
import { List, StringMap } from 'angular2/src/facade/collection'; import { List, StringMap } from 'angular2/src/facade/collection';
import { import {
ABSTRACT, BaseException ABSTRACT, BaseException
@ -12,6 +13,14 @@ import { MeasureValues } from './measure_values';
*/ */
@ABSTRACT() @ABSTRACT()
export class Validator { export class Validator {
static bindTo(delegateToken) {
return [
bind(Validator).toFactory(
(delegate) => delegate, [delegateToken]
)
];
}
/** /**
* Calculates a valid sample out of the complete sample * Calculates a valid sample out of the complete sample
*/ */

View File

@ -60,7 +60,7 @@ export class RegressionSlopeValidator extends Validator {
var _SAMPLE_SIZE = new OpaqueToken('RegressionSlopeValidator.sampleSize'); var _SAMPLE_SIZE = new OpaqueToken('RegressionSlopeValidator.sampleSize');
var _METRIC = new OpaqueToken('RegressionSlopeValidator.metric'); var _METRIC = new OpaqueToken('RegressionSlopeValidator.metric');
var _BINDINGS = [ var _BINDINGS = [
bind(Validator).toFactory( bind(RegressionSlopeValidator).toFactory(
(sampleSize, metric) => new RegressionSlopeValidator(sampleSize, metric), (sampleSize, metric) => new RegressionSlopeValidator(sampleSize, metric),
[_SAMPLE_SIZE, _METRIC] [_SAMPLE_SIZE, _METRIC]
), ),

View File

@ -38,7 +38,7 @@ export class SizeValidator extends Validator {
var _SAMPLE_SIZE = new OpaqueToken('SizeValidator.sampleSize'); var _SAMPLE_SIZE = new OpaqueToken('SizeValidator.sampleSize');
var _BINDINGS = [ var _BINDINGS = [
bind(Validator).toFactory( bind(SizeValidator).toFactory(
(size) => new SizeValidator(size), (size) => new SizeValidator(size),
[_SAMPLE_SIZE] [_SAMPLE_SIZE]
), ),

View File

@ -1,5 +1,7 @@
import { bind } from 'angular2/di';
import { Promise } from 'angular2/src/facade/async'; import { Promise } from 'angular2/src/facade/async';
import { BaseException, ABSTRACT } from 'angular2/src/facade/lang'; import { BaseException, ABSTRACT } from 'angular2/src/facade/lang';
import { List, Map } from 'angular2/src/facade/collection';
/** /**
* A WebDriverAdapter bridges API differences between different WebDriver clients, * A WebDriverAdapter bridges API differences between different WebDriver clients,
@ -8,16 +10,24 @@ import { BaseException, ABSTRACT } from 'angular2/src/facade/lang';
*/ */
@ABSTRACT() @ABSTRACT()
export class WebDriverAdapter { export class WebDriverAdapter {
static bindTo(delegateToken) {
return [
bind(WebDriverAdapter).toFactory(
(delegate) => delegate, [delegateToken]
)
];
}
waitFor(callback:Function):Promise { waitFor(callback:Function):Promise {
throw new BaseException('NYI'); throw new BaseException('NYI');
} }
executeScript(script:string):Promise { executeScript(script:string):Promise {
throw new BaseException('NYI'); throw new BaseException('NYI');
} }
capabilities():Promise { capabilities():Promise<Map> {
throw new BaseException('NYI'); throw new BaseException('NYI');
} }
logs(type:string):Promise { logs(type:string):Promise<List> {
throw new BaseException('NYI'); throw new BaseException('NYI');
} }
} }

View File

@ -1,6 +1,10 @@
import { BaseException, ABSTRACT } from 'angular2/src/facade/lang'; import { bind, Injector, OpaqueToken } from 'angular2/di';
import { Promise } from 'angular2/src/facade/async';
import { List } from 'angular2/src/facade/collection'; import { BaseException, ABSTRACT, isBlank } from 'angular2/src/facade/lang';
import { Promise, PromiseWrapper } from 'angular2/src/facade/async';
import { List, ListWrapper, StringMap } from 'angular2/src/facade/collection';
import { Options } from './sample_options';
/** /**
* A WebDriverExtension implements extended commands of the webdriver protocol * A WebDriverExtension implements extended commands of the webdriver protocol
@ -9,6 +13,30 @@ import { List } from 'angular2/src/facade/collection';
*/ */
@ABSTRACT() @ABSTRACT()
export class WebDriverExtension { export class WebDriverExtension {
static bindTo(childTokens) {
return [
bind(_CHILDREN).toAsyncFactory(
(injector) => PromiseWrapper.all(ListWrapper.map(childTokens, (token) => injector.asyncGet(token) )),
[Injector]
),
bind(WebDriverExtension).toFactory(
(children, capabilities) => {
var delegate;
ListWrapper.forEach(children, (extension) => {
if (extension.supports(capabilities)) {
delegate = extension;
}
});
if (isBlank(delegate)) {
throw new BaseException('Could not find a delegate for given capabilities!');
}
return delegate;
},
[_CHILDREN, Options.CAPABILITIES]
)
];
}
gc():Promise { gc():Promise {
throw new BaseException('NYI'); throw new BaseException('NYI');
} }
@ -35,4 +63,10 @@ export class WebDriverExtension {
readPerfLog():Promise<List> { readPerfLog():Promise<List> {
throw new BaseException('NYI'); throw new BaseException('NYI');
} }
supports(capabilities:StringMap):boolean {
return true;
}
} }
var _CHILDREN = new OpaqueToken('WebDriverExtension.children');

View File

@ -1,5 +1,5 @@
import { bind } from 'angular2/di'; import { bind } from 'angular2/di';
import { ListWrapper, StringMapWrapper } from 'angular2/src/facade/collection'; import { ListWrapper, StringMapWrapper, StringMap } from 'angular2/src/facade/collection';
import { import {
Json, isPresent, isBlank, RegExpWrapper, StringWrapper, BaseException, NumberWrapper Json, isPresent, isBlank, RegExpWrapper, StringWrapper, BaseException, NumberWrapper
} from 'angular2/src/facade/lang'; } from 'angular2/src/facade/lang';
@ -36,6 +36,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
return this._driver.executeScript(script); return this._driver.executeScript(script);
} }
// See [Chrome Trace Event Format](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit)
readPerfLog() { readPerfLog() {
// TODO(tbosch): Bug in ChromeDriver: Need to execute at least one command // TODO(tbosch): Bug in ChromeDriver: Need to execute at least one command
// so that the browser logs can be read out! // so that the browser logs can be read out!
@ -95,6 +96,10 @@ export class ChromeDriverExtension extends WebDriverExtension {
}); });
return normalizedEvents; return normalizedEvents;
} }
supports(capabilities:StringMap):boolean {
return StringWrapper.equals(capabilities['browserName'].toLowerCase(), 'chrome');
}
} }
function normalizeEvent(chromeEvent, data) { function normalizeEvent(chromeEvent, data) {
@ -120,7 +125,7 @@ function normalizeEvent(chromeEvent, data) {
} }
var _BINDINGS = [ var _BINDINGS = [
bind(WebDriverExtension).toFactory( bind(ChromeDriverExtension).toFactory(
(driver) => new ChromeDriverExtension(driver), (driver) => new ChromeDriverExtension(driver),
[WebDriverAdapter] [WebDriverAdapter]
) )

View File

@ -1,5 +1,5 @@
import { bind } from 'angular2/di'; import { bind } from 'angular2/di';
import { ListWrapper } from 'angular2/src/facade/collection'; import { ListWrapper, StringMap } from 'angular2/src/facade/collection';
import { import {
Json, isPresent, isBlank, RegExpWrapper, StringWrapper Json, isPresent, isBlank, RegExpWrapper, StringWrapper
} from 'angular2/src/facade/lang'; } from 'angular2/src/facade/lang';
@ -36,6 +36,7 @@ export class IOsDriverExtension extends WebDriverExtension {
return this._driver.executeScript(script); return this._driver.executeScript(script);
} }
// See https://github.com/WebKit/webkit/tree/master/Source/WebInspectorUI/Versions
readPerfLog() { readPerfLog() {
// TODO(tbosch): Bug in IOsDriver: Need to execute at least one command // TODO(tbosch): Bug in IOsDriver: Need to execute at least one command
// so that the browser logs can be read out! // so that the browser logs can be read out!
@ -97,6 +98,10 @@ export class IOsDriverExtension extends WebDriverExtension {
}); });
return events; return events;
} }
supports(capabilities:StringMap):boolean {
return StringWrapper.equals(capabilities['browserName'].toLowerCase(), 'safari');
}
} }
function createEvent(ph, name, time, args = null) { function createEvent(ph, name, time, args = null) {
@ -132,7 +137,7 @@ function createMarkEndEvent(name, time) {
} }
var _BINDINGS = [ var _BINDINGS = [
bind(WebDriverExtension).toFactory( bind(IOsDriverExtension).toFactory(
(driver) => new IOsDriverExtension(driver), (driver) => new IOsDriverExtension(driver),
[WebDriverAdapter] [WebDriverAdapter]
) )

View File

@ -17,7 +17,13 @@ export class SeleniumWebDriverAdapter extends WebDriverAdapter {
_convertPromise(thenable) { _convertPromise(thenable) {
var completer = PromiseWrapper.completer(); var completer = PromiseWrapper.completer();
thenable.then(completer.complete, completer.reject); thenable.then(
// selenium-webdriver uses an own Node.js context,
// so we need to convert data into objects of this context.
// (e.g. otherwise instanceof checks of rtts_assert would fail)
(data) => completer.complete(convertToLocalProcess(data)),
completer.reject
);
return completer.promise; return completer.promise;
} }
@ -30,7 +36,9 @@ export class SeleniumWebDriverAdapter extends WebDriverAdapter {
} }
capabilities():Promise { capabilities():Promise {
return this._convertPromise(this._driver.getCapabilities()); return this._convertPromise(
this._driver.getCapabilities().then( (capsObject) => capsObject.toJSON() )
);
} }
logs(type:string):Promise { logs(type:string):Promise {
@ -39,11 +47,15 @@ export class SeleniumWebDriverAdapter extends WebDriverAdapter {
return this._convertPromise(this._driver.schedule( return this._convertPromise(this._driver.schedule(
new webdriver.Command(webdriver.CommandName.GET_LOG). new webdriver.Command(webdriver.CommandName.GET_LOG).
setParameter('type', type), setParameter('type', type),
'WebDriver.manage().logs().get(' + type + ')').then( (logs) => { 'WebDriver.manage().logs().get(' + type + ')'));
// Need to convert the Array into an instance of an Array
// as selenium-webdriver uses an own Node.js context!
return [].slice.call(logs);
}));
} }
} }
function convertToLocalProcess(data) {
var serialized = JSON.stringify(data);
if (''+serialized === 'undefined') {
return undefined;
}
return JSON.parse(serialized);
}

View File

@ -0,0 +1,80 @@
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
import { List, ListWrapper, StringMap } from 'angular2/src/facade/collection';
import { PromiseWrapper, Promise } from 'angular2/src/facade/async';
import { Metric, MultiMetric, bind, Injector } from 'benchpress/benchpress';
export function main() {
function createMetric(ids) {
return new Injector([
ListWrapper.map(ids, (id) => bind(id).toValue(new MockMetric(id)) ),
MultiMetric.createBindings(ids)
]).asyncGet(MultiMetric);
}
describe('multi metric', () => {
it('should merge descriptions', (done) => {
createMetric(['m1', 'm2']).then( (m) => {
expect(m.describe()).toEqual({
'm1': 'describe', 'm2': 'describe'
});
done();
});
});
it('should merge all beginMeasure calls', (done) => {
createMetric(['m1', 'm2'])
.then( (m) => m.beginMeasure() )
.then( (values) => {
expect(values).toEqual([
'm1_beginMeasure', 'm2_beginMeasure'
]);
done();
});
});
[false, true].forEach( (restartFlag) => {
it(`should merge all endMeasure calls for restart=${restartFlag}`, (done) => {
createMetric(['m1', 'm2'])
.then( (m) => m.endMeasure(restartFlag) )
.then( (values) => {
expect(values).toEqual({
'm1': { 'restart': restartFlag },
'm2': { 'restart': restartFlag }
});
done();
});
});
});
});
}
class MockMetric extends Metric {
_id:string;
constructor(id) {
super();
this._id = id;
}
beginMeasure():Promise {
return PromiseWrapper.resolve(`${this._id}_beginMeasure`);
}
endMeasure(restart:boolean):Promise<StringMap> {
var result = {};
result[this._id] = {
'restart': restart
};
return PromiseWrapper.resolve(result);
}
describe():StringMap {
var result = {};
result[this._id] = 'describe';
return result;
}
}

View File

@ -20,7 +20,7 @@ export function main() {
fn(); fn();
}), }),
bind(WebDriverExtension).toValue(new MockDriverExtension(perfLogs, commandLog)) bind(WebDriverExtension).toValue(new MockDriverExtension(perfLogs, commandLog))
]).get(Metric); ]).get(PerflogMetric);
} }
describe('perflog metric', () => { describe('perflog metric', () => {

View File

@ -29,7 +29,7 @@ export function main() {
if (isPresent(columnWidth)) { if (isPresent(columnWidth)) {
ListWrapper.push(bindings, bind(ConsoleReporter.COLUMN_WIDTH).toValue(columnWidth)); ListWrapper.push(bindings, bind(ConsoleReporter.COLUMN_WIDTH).toValue(columnWidth));
} }
reporter = new Injector(bindings).get(Reporter); reporter = new Injector(bindings).get(ConsoleReporter);
} }
it('should print the sample id, description and table header', () => { it('should print the sample id, description and table header', () => {

View File

@ -0,0 +1,78 @@
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
import { List, ListWrapper, StringMap } from 'angular2/src/facade/collection';
import { PromiseWrapper, Promise } from 'angular2/src/facade/async';
import { DateWrapper } from 'angular2/src/facade/lang';
import { Reporter, MultiReporter, bind, Injector, MeasureValues } from 'benchpress/benchpress';
export function main() {
function createReporters(ids) {
return new Injector([
ListWrapper.map(ids, (id) => bind(id).toValue(new MockReporter(id)) ),
MultiReporter.createBindings(ids)
]).asyncGet(MultiReporter);
}
describe('multi reporter', () => {
it('should reportMeasureValues to all', (done) => {
var mv = new MeasureValues(0, DateWrapper.now(), {});
createReporters(['m1', 'm2'])
.then( (r) => r.reportMeasureValues(mv) )
.then( (values) => {
expect(values).toEqual([
{'id': 'm1', 'values': mv},
{'id': 'm2', 'values': mv}
]);
done();
});
});
it('should reportSample to call', (done) => {
var completeSample = [
new MeasureValues(0, DateWrapper.now(), {}),
new MeasureValues(1, DateWrapper.now(), {})
];
var validSample = [completeSample[1]];
createReporters(['m1', 'm2'])
.then( (r) => r.reportSample(completeSample, validSample) )
.then( (values) => {
expect(values).toEqual([
{'id': 'm1', 'completeSample': completeSample, 'validSample': validSample},
{'id': 'm2', 'completeSample': completeSample, 'validSample': validSample}
]);
done();
})
});
});
}
class MockReporter extends Reporter {
_id:string;
constructor(id) {
super();
this._id = id;
}
reportMeasureValues(values:MeasureValues):Promise {
return PromiseWrapper.resolve({
'id': this._id,
'values': values
});
}
reportSample(completeSample:List<MeasureValues>, validSample:List<MeasureValues>):Promise {
return PromiseWrapper.resolve({
'id': this._id,
'completeSample': completeSample,
'validSample': validSample
});
}
}

View File

@ -2,7 +2,7 @@ import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/te
import { import {
Runner, Sampler, SampleDescription, Runner, Sampler, SampleDescription,
Validator, bind, Injector, Metric, Validator, bind, Injector, Metric,
Options Options, WebDriverAdapter
} from 'benchpress/benchpress'; } from 'benchpress/benchpress';
import { isBlank } from 'angular2/src/facade/lang'; import { isBlank } from 'angular2/src/facade/lang';
import { Promise, PromiseWrapper } from 'angular2/src/facade/async'; import { Promise, PromiseWrapper } from 'angular2/src/facade/async';
@ -25,16 +25,19 @@ export function main() {
}, [Injector] }, [Injector]
), ),
bind(Metric).toFactory( () => new MockMetric(), []), bind(Metric).toFactory( () => new MockMetric(), []),
bind(Validator).toFactory( () => new MockValidator(), []) bind(Validator).toFactory( () => new MockValidator(), []),
bind(WebDriverAdapter).toFactory( () => new MockWebDriverAdapter(), [])
]); ]);
return runner; return runner;
} }
it('should set SampleDescription.id', (done) => { it('should set SampleDescription.id', (done) => {
createRunner().sample({id: 'someId'}).then( (_) => { createRunner().sample({id: 'someId'})
expect(injector.get(SampleDescription).id).toBe('someId'); .then( (_) => injector.asyncGet(SampleDescription) )
done(); .then( (desc) => {
}); expect(desc.id).toBe('someId');
done();
});
}); });
it('should merge SampleDescription.description', (done) => { it('should merge SampleDescription.description', (done) => {
@ -42,9 +45,12 @@ export function main() {
bind(Options.DEFAULT_DESCRIPTION).toValue({'a': 1}) bind(Options.DEFAULT_DESCRIPTION).toValue({'a': 1})
]).sample({id: 'someId', bindings: [ ]).sample({id: 'someId', bindings: [
bind(Options.SAMPLE_DESCRIPTION).toValue({'b': 2}) bind(Options.SAMPLE_DESCRIPTION).toValue({'b': 2})
]}).then( (_) => { ]}).then( (_) => injector.asyncGet(SampleDescription) )
expect(injector.get(SampleDescription).description).toEqual({ .then( (desc) => {
expect(desc.description).toEqual({
'forceGc': false, 'forceGc': false,
'userAgent': 'someUserAgent',
'a': 1, 'a': 1,
'b': 2, 'b': 2,
'v': 11 'v': 11
@ -54,8 +60,11 @@ export function main() {
}); });
it('should fill SampleDescription.metrics from the Metric', (done) => { it('should fill SampleDescription.metrics from the Metric', (done) => {
createRunner().sample({id: 'someId'}).then( (_) => { createRunner().sample({id: 'someId'})
expect(injector.get(SampleDescription).metrics).toEqual({ 'm1': 'some metric' }); .then( (_) => injector.asyncGet(SampleDescription) )
.then( (desc) => {
expect(desc.metrics).toEqual({ 'm1': 'some metric' });
done(); done();
}); });
}); });
@ -81,7 +90,9 @@ export function main() {
bind(Options.DEFAULT_DESCRIPTION).toValue({'a': 1}), bind(Options.DEFAULT_DESCRIPTION).toValue({'a': 1}),
]).sample({id: 'someId', bindings: [ ]).sample({id: 'someId', bindings: [
bind(Options.DEFAULT_DESCRIPTION).toValue({'a': 2}), bind(Options.DEFAULT_DESCRIPTION).toValue({'a': 2}),
]}).then( (_) => { ]}).then( (_) => injector.asyncGet(SampleDescription) )
.then( (desc) => {
expect(injector.get(SampleDescription).description['a']).toBe(2); expect(injector.get(SampleDescription).description['a']).toBe(2);
done(); done();
}); });
@ -91,6 +102,12 @@ export function main() {
}); });
} }
class MockWebDriverAdapter extends WebDriverAdapter {
executeScript(script):Promise {
return PromiseWrapper.resolve('someUserAgent');
}
}
class MockValidator extends Validator { class MockValidator extends Validator {
constructor() { constructor() {
super(); super();

View File

@ -15,7 +15,7 @@ export function main() {
RegressionSlopeValidator.BINDINGS, RegressionSlopeValidator.BINDINGS,
bind(RegressionSlopeValidator.METRIC).toValue(metric), bind(RegressionSlopeValidator.METRIC).toValue(metric),
bind(RegressionSlopeValidator.SAMPLE_SIZE).toValue(size) bind(RegressionSlopeValidator.SAMPLE_SIZE).toValue(size)
]).get(Validator); ]).get(RegressionSlopeValidator);
} }
it('should return sampleSize and metric as description', () => { it('should return sampleSize and metric as description', () => {

View File

@ -14,7 +14,7 @@ export function main() {
validator = new Injector([ validator = new Injector([
SizeValidator.BINDINGS, SizeValidator.BINDINGS,
bind(SizeValidator.SAMPLE_SIZE).toValue(size) bind(SizeValidator.SAMPLE_SIZE).toValue(size)
]).get(Validator); ]).get(SizeValidator);
} }
it('should return sampleSize as description', () => { it('should return sampleSize as description', () => {

View File

@ -0,0 +1,51 @@
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
import { StringMap, ListWrapper } from 'angular2/src/facade/collection';
import { isPresent, StringWrapper, isJsObject } from 'angular2/src/facade/lang';
import { WebDriverExtension, bind, Injector, Options } from 'benchpress/benchpress';
export function main() {
function createExtension(ids, caps) {
return new Injector([
ListWrapper.map(ids, (id) => bind(id).toValue(new MockExtension(id)) ),
bind(Options.CAPABILITIES).toValue(caps),
WebDriverExtension.bindTo(ids)
]).asyncGet(WebDriverExtension);
}
describe('WebDriverExtension.bindTo', () => {
it('should bind the extension that matches the capabilities', (done) => {
createExtension(['m1', 'm2', 'm3'], {'browser': 'm2'}).then( (m) => {
expect(m.id).toEqual('m2');
done();
});
});
// TODO(tbosch): In Dart, somehow we don't provide the error
// correctly in the promise result...
if (isJsObject({})) {
it('should throw if there is no match', (done) => {
createExtension(['m1'], {'browser': 'm2'}).then(null, (err) => {
expect(isPresent(err)).toBe(true);
done();
});
});
}
});
}
class MockExtension extends WebDriverExtension {
id:string;
constructor(id) {
super();
this.id = id;
}
supports(capabilities:StringMap):boolean {
return StringWrapper.equals(capabilities['browser'], this.id);
}
}

View File

@ -2,7 +2,7 @@ import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/te
import { ListWrapper } from 'angular2/src/facade/collection'; import { ListWrapper } from 'angular2/src/facade/collection';
import { PromiseWrapper } from 'angular2/src/facade/async'; import { PromiseWrapper } from 'angular2/src/facade/async';
import { Json, isBlank } from 'angular2/src/facade/lang'; import { Json, isBlank, isJsObject } from 'angular2/src/facade/lang';
import { import {
WebDriverExtension, ChromeDriverExtension, WebDriverExtension, ChromeDriverExtension,
@ -20,15 +20,15 @@ export function main() {
var chromeTimelineEvents = new TraceEventFactory('disabled-by-default-devtools.timeline', 'pid0'); var chromeTimelineEvents = new TraceEventFactory('disabled-by-default-devtools.timeline', 'pid0');
var normEvents = new TraceEventFactory('timeline', 'pid0'); var normEvents = new TraceEventFactory('timeline', 'pid0');
function createExtension(perfRecords = null) { function createExtension(perfRecords = null, messageMethod = 'Tracing.dataCollected') {
if (isBlank(perfRecords)) { if (isBlank(perfRecords)) {
perfRecords = []; perfRecords = [];
} }
log = []; log = [];
extension = new Injector([ extension = new Injector([
ChromeDriverExtension.BINDINGS, ChromeDriverExtension.BINDINGS,
bind(WebDriverAdapter).toValue(new MockDriverAdapter(log, perfRecords)) bind(WebDriverAdapter).toValue(new MockDriverAdapter(log, perfRecords, messageMethod))
]).get(WebDriverExtension); ]).get(ChromeDriverExtension);
return extension; return extension;
} }
@ -153,6 +153,31 @@ export function main() {
}); });
}); });
// TODO(tbosch): In Dart, somehow we don't provide the error
// correctly in the promise result...
if (isJsObject({})) {
it('should throw an error on buffer overflow', (done) => {
createExtension([
chromeTimelineEvents.start('FunctionCall', 1234),
], 'Tracing.bufferUsage').readPerfLog().then(null, (err) => {
expect( () => {
throw err;
}).toThrowError('The DevTools trace buffer filled during the test!');
done();
});
});
}
it('should match chrome browsers', () => {
expect(createExtension().supports({
'browserName': 'chrome'
})).toBe(true);
expect(createExtension().supports({
'browserName': 'Chrome'
})).toBe(true);
});
}); });
}); });
@ -161,10 +186,12 @@ export function main() {
class MockDriverAdapter extends WebDriverAdapter { class MockDriverAdapter extends WebDriverAdapter {
_log:List; _log:List;
_events:List; _events:List;
constructor(log, events) { _messageMethod:string;
constructor(log, events, messageMethod) {
super(); super();
this._log = log; this._log = log;
this._events = events; this._events = events;
this._messageMethod = messageMethod;
} }
executeScript(script) { executeScript(script) {
@ -175,11 +202,11 @@ class MockDriverAdapter extends WebDriverAdapter {
logs(type) { logs(type) {
ListWrapper.push(this._log, ['logs', type]); ListWrapper.push(this._log, ['logs', type]);
if (type === 'performance') { if (type === 'performance') {
return PromiseWrapper.resolve(this._events.map(function(event) { return PromiseWrapper.resolve(this._events.map( (event) => {
return { return {
'message': Json.stringify({ 'message': Json.stringify({
'message': { 'message': {
'method': 'Tracing.dataCollected', 'method': this._messageMethod,
'params': event 'params': event
} }
}) })

View File

@ -26,7 +26,7 @@ export function main() {
extension = new Injector([ extension = new Injector([
IOsDriverExtension.BINDINGS, IOsDriverExtension.BINDINGS,
bind(WebDriverAdapter).toValue(new MockDriverAdapter(log, perfRecords)) bind(WebDriverAdapter).toValue(new MockDriverAdapter(log, perfRecords))
]).get(WebDriverExtension); ]).get(IOsDriverExtension);
return extension; return extension;
} }
@ -155,6 +155,16 @@ export function main() {
}); });
}); });
it('should match safari browsers', () => {
expect(createExtension().supports({
'browserName': 'safari'
})).toBe(true);
expect(createExtension().supports({
'browserName': 'Safari'
})).toBe(true);
});
}); });
}); });

View File

@ -175,19 +175,13 @@ exports.createBenchpressRunner = function(options) {
benchpress.bind(benchpress.Options.DEFAULT_DESCRIPTION).toValue({ benchpress.bind(benchpress.Options.DEFAULT_DESCRIPTION).toValue({
'lang': options.lang, 'lang': options.lang,
'runId': runId 'runId': runId
}), })
// TODO(tbosch): Make the ChromeDriverExtension configurable based on the
// capabilities. Should support the case where we test against
// ios and chrome at the same time!
benchpress.bind(benchpress.WebDriverExtension).toFactory(function(adapter) {
return new benchpress.ChromeDriverExtension(adapter);
}, [benchpress.WebDriverAdapter])
]; ];
if (argv['benchmark']) { if (argv['benchmark']) {
bindings.push(benchpress.RegressionSlopeValidator.BINDINGS); bindings.push(benchpress.Validator.bindTo(benchpress.RegressionSlopeValidator));
bindings.push(benchpress.bind(benchpress.RegressionSlopeValidator.SAMPLE_SIZE).toValue(argv['sample-size'])); bindings.push(benchpress.bind(benchpress.RegressionSlopeValidator.SAMPLE_SIZE).toValue(argv['sample-size']));
} else { } else {
bindings.push(benchpress.SizeValidator.BINDINGS); bindings.push(benchpress.Validator.bindTo(benchpress.SizeValidator));
bindings.push(benchpress.bind(benchpress.SizeValidator.SAMPLE_SIZE).toValue(1)); bindings.push(benchpress.bind(benchpress.SizeValidator.SAMPLE_SIZE).toValue(1));
} }