123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- // Copyright 2015 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview a fuzzing JSON generator.
- *
- * This class generates a random JSON-compatible array object under the
- * following rules, (n) n being the relative weight of enum/discrete values
- * of a stochastic variable:
- * 1. Total number of elements for the generated JSON array: [1, 10)
- * 2. Each element: with message (1), array (1)
- * 3. Each message: number of fields: [0, 5); field type: with
- * message (5), string (1), number (1), boolean (1), array (1), null (1)
- * 4. Message may be nested, and will be terminated randomly with
- * a max depth equal to 5
- * 5. Each array: length [0, 5), and may be nested too
- */
- goog.provide('goog.labs.testing.JsonFuzzing');
- goog.require('goog.string');
- goog.require('goog.testing.PseudoRandom');
- /**
- * The JSON fuzzing generator.
- *
- * @param {!goog.labs.testing.JsonFuzzing.Options=} opt_options Configuration
- * for the fuzzing json generator.
- * @param {number=} opt_seed The seed for the random generator.
- * @constructor
- * @struct
- */
- goog.labs.testing.JsonFuzzing = function(opt_options, opt_seed) {
- /**
- * The config options.
- * @private {!goog.labs.testing.JsonFuzzing.Options}
- */
- this.options_ =
- opt_options || {jsonSize: 10, numFields: 5, arraySize: 5, maxDepth: 5};
- /**
- * The random generator
- * @private {!goog.testing.PseudoRandom}
- */
- this.random_ = new goog.testing.PseudoRandom(opt_seed);
- /**
- * The depth limit, which defaults to 5.
- * @private {number}
- */
- this.maxDepth_ = this.options_.maxDepth;
- };
- /**
- * Configuration spec.
- *
- * jsonSize: default to [1, 10) for the entire JSON object (array)
- * numFields: default to [0, 5)
- * arraySize: default to [0, 5) for the length of nested arrays
- * maxDepth: default to 5
- *
- * @typedef {{
- * jsonSize: number,
- * numFields: number,
- * arraySize: number,
- * maxDepth: number
- * }}
- */
- goog.labs.testing.JsonFuzzing.Options;
- /**
- * Gets a fuzzily-generated JSON object (an array).
- *
- * TODO(user): whitespaces
- *
- * @return {!Array} A new JSON compliant array object.
- */
- goog.labs.testing.JsonFuzzing.prototype.newArray = function() {
- var result = [];
- var depth = 0;
- var maxSize = this.options_.jsonSize;
- var size = this.nextInt(1, maxSize);
- for (var i = 0; i < size; i++) {
- result.push(this.nextElm_(depth));
- }
- return result;
- };
- /**
- * Gets a new integer.
- *
- * @param {number} min Inclusive
- * @param {number} max Exclusive
- * @return {number} A random integer
- */
- goog.labs.testing.JsonFuzzing.prototype.nextInt = function(min, max) {
- var random = this.random_.random();
- return Math.floor(random * (max - min)) + min;
- };
- /**
- * Gets a new element type, randomly.
- *
- * @return {number} 0 for message and 1 for array.
- * @private
- */
- goog.labs.testing.JsonFuzzing.prototype.nextElmType_ = function() {
- var random = this.random_.random();
- if (random < 0.5) {
- return 0;
- } else {
- return 1;
- }
- };
- /**
- * Enum type for the field type (of a message).
- * @enum {number}
- * @private
- */
- goog.labs.testing.JsonFuzzing.FieldType_ = {
- /**
- * Message field.
- */
- MESSAGE: 0,
- /**
- * Array field.
- */
- ARRAY: 1,
- /**
- * String field.
- */
- STRING: 2,
- /**
- * Numeric field.
- */
- NUMBER: 3,
- /**
- * Boolean field.
- */
- BOOLEAN: 4,
- /**
- * Null field.
- */
- NULL: 5
- };
- /**
- * Get a new field type, randomly.
- *
- * @return {!goog.labs.testing.JsonFuzzing.FieldType_} the field type.
- * @private
- */
- goog.labs.testing.JsonFuzzing.prototype.nextFieldType_ = function() {
- var FieldType = goog.labs.testing.JsonFuzzing.FieldType_;
- var random = this.random_.random();
- if (random < 0.5) {
- return FieldType.MESSAGE;
- } else if (random < 0.6) {
- return FieldType.ARRAY;
- } else if (random < 0.7) {
- return FieldType.STRING;
- } else if (random < 0.8) {
- return FieldType.NUMBER;
- } else if (random < 0.9) {
- return FieldType.BOOLEAN;
- } else {
- return FieldType.NULL;
- }
- };
- /**
- * Gets a new element.
- *
- * @param {number} depth The depth
- * @return {!Object} a random element, msg or array
- * @private
- */
- goog.labs.testing.JsonFuzzing.prototype.nextElm_ = function(depth) {
- switch (this.nextElmType_()) {
- case 0:
- return this.nextMessage_(depth);
- case 1:
- return this.nextArray_(depth);
- default:
- throw Error('invalid elm type encounted.');
- }
- };
- /**
- * Gets a new message.
- *
- * @param {number} depth The depth
- * @return {!Object} a random message.
- * @private
- */
- goog.labs.testing.JsonFuzzing.prototype.nextMessage_ = function(depth) {
- if (depth > this.maxDepth_) {
- return {};
- }
- var numFields = this.options_.numFields;
- var random_num = this.nextInt(0, numFields);
- var result = {};
- // TODO(user): unicode and random keys
- for (var i = 0; i < random_num; i++) {
- switch (this.nextFieldType_()) {
- case 0:
- result['f' + i] = this.nextMessage_(depth++);
- continue;
- case 1:
- result['f' + i] = this.nextArray_(depth++);
- continue;
- case 2:
- result['f' + i] = goog.string.getRandomString();
- continue;
- case 3:
- result['f' + i] = this.nextNumber_();
- continue;
- case 4:
- result['f' + i] = this.nextBoolean_();
- continue;
- case 5:
- result['f' + i] = null;
- continue;
- default:
- throw Error('invalid field type encounted.');
- }
- }
- return result;
- };
- /**
- * Gets a new array.
- *
- * @param {number} depth The depth
- * @return {!Array} a random array.
- * @private
- */
- goog.labs.testing.JsonFuzzing.prototype.nextArray_ = function(depth) {
- if (depth > this.maxDepth_) {
- return [];
- }
- var size = this.options_.arraySize;
- var random_size = this.nextInt(0, size);
- var result = [];
- // mixed content
- for (var i = 0; i < random_size; i++) {
- switch (this.nextFieldType_()) {
- case 0:
- result.push(this.nextMessage_(depth++));
- continue;
- case 1:
- result.push(this.nextArray_(depth++));
- continue;
- case 2:
- result.push(goog.string.getRandomString());
- continue;
- case 3:
- result.push(this.nextNumber_());
- continue;
- case 4:
- result.push(this.nextBoolean_());
- continue;
- case 5:
- result.push(null);
- continue;
- default:
- throw Error('invalid field type encounted.');
- }
- }
- return result;
- };
- /**
- * Gets a new boolean.
- *
- * @return {boolean} a random boolean.
- * @private
- */
- goog.labs.testing.JsonFuzzing.prototype.nextBoolean_ = function() {
- var random = this.random_.random();
- return random < 0.5;
- };
- /**
- * Gets a new number.
- *
- * @return {number} a random number..
- * @private
- */
- goog.labs.testing.JsonFuzzing.prototype.nextNumber_ = function() {
- var result = this.random_.random();
- var random = this.random_.random();
- if (random < 0.5) {
- result *= 1000;
- }
- random = this.random_.random();
- if (random < 0.5) {
- result = Math.floor(result);
- }
- random = this.random_.random();
- if (random < 0.5) {
- result *= -1;
- }
- // TODO(user); more random numbers
- return result;
- };
|