Path: blob/trunk/third_party/closure/goog/labs/testing/json_fuzzing.js
2868 views
// Copyright 2015 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview a fuzzing JSON generator.16*17* This class generates a random JSON-compatible array object under the18* following rules, (n) n being the relative weight of enum/discrete values19* of a stochastic variable:20* 1. Total number of elements for the generated JSON array: [1, 10)21* 2. Each element: with message (1), array (1)22* 3. Each message: number of fields: [0, 5); field type: with23* message (5), string (1), number (1), boolean (1), array (1), null (1)24* 4. Message may be nested, and will be terminated randomly with25* a max depth equal to 526* 5. Each array: length [0, 5), and may be nested too27*/2829goog.provide('goog.labs.testing.JsonFuzzing');3031goog.require('goog.string');32goog.require('goog.testing.PseudoRandom');33343536/**37* The JSON fuzzing generator.38*39* @param {!goog.labs.testing.JsonFuzzing.Options=} opt_options Configuration40* for the fuzzing json generator.41* @param {number=} opt_seed The seed for the random generator.42* @constructor43* @struct44*/45goog.labs.testing.JsonFuzzing = function(opt_options, opt_seed) {46/**47* The config options.48* @private {!goog.labs.testing.JsonFuzzing.Options}49*/50this.options_ =51opt_options || {jsonSize: 10, numFields: 5, arraySize: 5, maxDepth: 5};5253/**54* The random generator55* @private {!goog.testing.PseudoRandom}56*/57this.random_ = new goog.testing.PseudoRandom(opt_seed);5859/**60* The depth limit, which defaults to 5.61* @private {number}62*/63this.maxDepth_ = this.options_.maxDepth;64};656667/**68* Configuration spec.69*70* jsonSize: default to [1, 10) for the entire JSON object (array)71* numFields: default to [0, 5)72* arraySize: default to [0, 5) for the length of nested arrays73* maxDepth: default to 574*75* @typedef {{76* jsonSize: number,77* numFields: number,78* arraySize: number,79* maxDepth: number80* }}81*/82goog.labs.testing.JsonFuzzing.Options;838485/**86* Gets a fuzzily-generated JSON object (an array).87*88* TODO(user): whitespaces89*90* @return {!Array} A new JSON compliant array object.91*/92goog.labs.testing.JsonFuzzing.prototype.newArray = function() {93var result = [];94var depth = 0;9596var maxSize = this.options_.jsonSize;9798var size = this.nextInt(1, maxSize);99for (var i = 0; i < size; i++) {100result.push(this.nextElm_(depth));101}102103return result;104};105106107/**108* Gets a new integer.109*110* @param {number} min Inclusive111* @param {number} max Exclusive112* @return {number} A random integer113*/114goog.labs.testing.JsonFuzzing.prototype.nextInt = function(min, max) {115var random = this.random_.random();116117return Math.floor(random * (max - min)) + min;118};119120121/**122* Gets a new element type, randomly.123*124* @return {number} 0 for message and 1 for array.125* @private126*/127goog.labs.testing.JsonFuzzing.prototype.nextElmType_ = function() {128var random = this.random_.random();129130if (random < 0.5) {131return 0;132} else {133return 1;134}135};136137138/**139* Enum type for the field type (of a message).140* @enum {number}141* @private142*/143goog.labs.testing.JsonFuzzing.FieldType_ = {144/**145* Message field.146*/147MESSAGE: 0,148149/**150* Array field.151*/152ARRAY: 1,153154/**155* String field.156*/157STRING: 2,158159/**160* Numeric field.161*/162NUMBER: 3,163164/**165* Boolean field.166*/167BOOLEAN: 4,168169/**170* Null field.171*/172NULL: 5173};174175176/**177* Get a new field type, randomly.178*179* @return {!goog.labs.testing.JsonFuzzing.FieldType_} the field type.180* @private181*/182goog.labs.testing.JsonFuzzing.prototype.nextFieldType_ = function() {183var FieldType = goog.labs.testing.JsonFuzzing.FieldType_;184185var random = this.random_.random();186187if (random < 0.5) {188return FieldType.MESSAGE;189} else if (random < 0.6) {190return FieldType.ARRAY;191} else if (random < 0.7) {192return FieldType.STRING;193} else if (random < 0.8) {194return FieldType.NUMBER;195} else if (random < 0.9) {196return FieldType.BOOLEAN;197} else {198return FieldType.NULL;199}200};201202203/**204* Gets a new element.205*206* @param {number} depth The depth207* @return {!Object} a random element, msg or array208* @private209*/210goog.labs.testing.JsonFuzzing.prototype.nextElm_ = function(depth) {211switch (this.nextElmType_()) {212case 0:213return this.nextMessage_(depth);214case 1:215return this.nextArray_(depth);216default:217throw Error('invalid elm type encounted.');218}219};220221222/**223* Gets a new message.224*225* @param {number} depth The depth226* @return {!Object} a random message.227* @private228*/229goog.labs.testing.JsonFuzzing.prototype.nextMessage_ = function(depth) {230if (depth > this.maxDepth_) {231return {};232}233234var numFields = this.options_.numFields;235236var random_num = this.nextInt(0, numFields);237var result = {};238239// TODO(user): unicode and random keys240for (var i = 0; i < random_num; i++) {241switch (this.nextFieldType_()) {242case 0:243result['f' + i] = this.nextMessage_(depth++);244continue;245case 1:246result['f' + i] = this.nextArray_(depth++);247continue;248case 2:249result['f' + i] = goog.string.getRandomString();250continue;251case 3:252result['f' + i] = this.nextNumber_();253continue;254case 4:255result['f' + i] = this.nextBoolean_();256continue;257case 5:258result['f' + i] = null;259continue;260default:261throw Error('invalid field type encounted.');262}263}264265return result;266};267268269/**270* Gets a new array.271*272* @param {number} depth The depth273* @return {!Array} a random array.274* @private275*/276goog.labs.testing.JsonFuzzing.prototype.nextArray_ = function(depth) {277if (depth > this.maxDepth_) {278return [];279}280281var size = this.options_.arraySize;282283var random_size = this.nextInt(0, size);284var result = [];285286// mixed content287for (var i = 0; i < random_size; i++) {288switch (this.nextFieldType_()) {289case 0:290result.push(this.nextMessage_(depth++));291continue;292case 1:293result.push(this.nextArray_(depth++));294continue;295case 2:296result.push(goog.string.getRandomString());297continue;298case 3:299result.push(this.nextNumber_());300continue;301case 4:302result.push(this.nextBoolean_());303continue;304case 5:305result.push(null);306continue;307default:308throw Error('invalid field type encounted.');309}310}311312return result;313};314315316/**317* Gets a new boolean.318*319* @return {boolean} a random boolean.320* @private321*/322goog.labs.testing.JsonFuzzing.prototype.nextBoolean_ = function() {323var random = this.random_.random();324325return random < 0.5;326};327328329/**330* Gets a new number.331*332* @return {number} a random number..333* @private334*/335goog.labs.testing.JsonFuzzing.prototype.nextNumber_ = function() {336var result = this.random_.random();337338var random = this.random_.random();339if (random < 0.5) {340result *= 1000;341}342343random = this.random_.random();344if (random < 0.5) {345result = Math.floor(result);346}347348random = this.random_.random();349if (random < 0.5) {350result *= -1;351}352353// TODO(user); more random numbers354355return result;356};357358359