json_fuzzing.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. // Copyright 2015 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview a fuzzing JSON generator.
  16. *
  17. * This class generates a random JSON-compatible array object under the
  18. * following rules, (n) n being the relative weight of enum/discrete values
  19. * 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: with
  23. * message (5), string (1), number (1), boolean (1), array (1), null (1)
  24. * 4. Message may be nested, and will be terminated randomly with
  25. * a max depth equal to 5
  26. * 5. Each array: length [0, 5), and may be nested too
  27. */
  28. goog.provide('goog.labs.testing.JsonFuzzing');
  29. goog.require('goog.string');
  30. goog.require('goog.testing.PseudoRandom');
  31. /**
  32. * The JSON fuzzing generator.
  33. *
  34. * @param {!goog.labs.testing.JsonFuzzing.Options=} opt_options Configuration
  35. * for the fuzzing json generator.
  36. * @param {number=} opt_seed The seed for the random generator.
  37. * @constructor
  38. * @struct
  39. */
  40. goog.labs.testing.JsonFuzzing = function(opt_options, opt_seed) {
  41. /**
  42. * The config options.
  43. * @private {!goog.labs.testing.JsonFuzzing.Options}
  44. */
  45. this.options_ =
  46. opt_options || {jsonSize: 10, numFields: 5, arraySize: 5, maxDepth: 5};
  47. /**
  48. * The random generator
  49. * @private {!goog.testing.PseudoRandom}
  50. */
  51. this.random_ = new goog.testing.PseudoRandom(opt_seed);
  52. /**
  53. * The depth limit, which defaults to 5.
  54. * @private {number}
  55. */
  56. this.maxDepth_ = this.options_.maxDepth;
  57. };
  58. /**
  59. * Configuration spec.
  60. *
  61. * jsonSize: default to [1, 10) for the entire JSON object (array)
  62. * numFields: default to [0, 5)
  63. * arraySize: default to [0, 5) for the length of nested arrays
  64. * maxDepth: default to 5
  65. *
  66. * @typedef {{
  67. * jsonSize: number,
  68. * numFields: number,
  69. * arraySize: number,
  70. * maxDepth: number
  71. * }}
  72. */
  73. goog.labs.testing.JsonFuzzing.Options;
  74. /**
  75. * Gets a fuzzily-generated JSON object (an array).
  76. *
  77. * TODO(user): whitespaces
  78. *
  79. * @return {!Array} A new JSON compliant array object.
  80. */
  81. goog.labs.testing.JsonFuzzing.prototype.newArray = function() {
  82. var result = [];
  83. var depth = 0;
  84. var maxSize = this.options_.jsonSize;
  85. var size = this.nextInt(1, maxSize);
  86. for (var i = 0; i < size; i++) {
  87. result.push(this.nextElm_(depth));
  88. }
  89. return result;
  90. };
  91. /**
  92. * Gets a new integer.
  93. *
  94. * @param {number} min Inclusive
  95. * @param {number} max Exclusive
  96. * @return {number} A random integer
  97. */
  98. goog.labs.testing.JsonFuzzing.prototype.nextInt = function(min, max) {
  99. var random = this.random_.random();
  100. return Math.floor(random * (max - min)) + min;
  101. };
  102. /**
  103. * Gets a new element type, randomly.
  104. *
  105. * @return {number} 0 for message and 1 for array.
  106. * @private
  107. */
  108. goog.labs.testing.JsonFuzzing.prototype.nextElmType_ = function() {
  109. var random = this.random_.random();
  110. if (random < 0.5) {
  111. return 0;
  112. } else {
  113. return 1;
  114. }
  115. };
  116. /**
  117. * Enum type for the field type (of a message).
  118. * @enum {number}
  119. * @private
  120. */
  121. goog.labs.testing.JsonFuzzing.FieldType_ = {
  122. /**
  123. * Message field.
  124. */
  125. MESSAGE: 0,
  126. /**
  127. * Array field.
  128. */
  129. ARRAY: 1,
  130. /**
  131. * String field.
  132. */
  133. STRING: 2,
  134. /**
  135. * Numeric field.
  136. */
  137. NUMBER: 3,
  138. /**
  139. * Boolean field.
  140. */
  141. BOOLEAN: 4,
  142. /**
  143. * Null field.
  144. */
  145. NULL: 5
  146. };
  147. /**
  148. * Get a new field type, randomly.
  149. *
  150. * @return {!goog.labs.testing.JsonFuzzing.FieldType_} the field type.
  151. * @private
  152. */
  153. goog.labs.testing.JsonFuzzing.prototype.nextFieldType_ = function() {
  154. var FieldType = goog.labs.testing.JsonFuzzing.FieldType_;
  155. var random = this.random_.random();
  156. if (random < 0.5) {
  157. return FieldType.MESSAGE;
  158. } else if (random < 0.6) {
  159. return FieldType.ARRAY;
  160. } else if (random < 0.7) {
  161. return FieldType.STRING;
  162. } else if (random < 0.8) {
  163. return FieldType.NUMBER;
  164. } else if (random < 0.9) {
  165. return FieldType.BOOLEAN;
  166. } else {
  167. return FieldType.NULL;
  168. }
  169. };
  170. /**
  171. * Gets a new element.
  172. *
  173. * @param {number} depth The depth
  174. * @return {!Object} a random element, msg or array
  175. * @private
  176. */
  177. goog.labs.testing.JsonFuzzing.prototype.nextElm_ = function(depth) {
  178. switch (this.nextElmType_()) {
  179. case 0:
  180. return this.nextMessage_(depth);
  181. case 1:
  182. return this.nextArray_(depth);
  183. default:
  184. throw Error('invalid elm type encounted.');
  185. }
  186. };
  187. /**
  188. * Gets a new message.
  189. *
  190. * @param {number} depth The depth
  191. * @return {!Object} a random message.
  192. * @private
  193. */
  194. goog.labs.testing.JsonFuzzing.prototype.nextMessage_ = function(depth) {
  195. if (depth > this.maxDepth_) {
  196. return {};
  197. }
  198. var numFields = this.options_.numFields;
  199. var random_num = this.nextInt(0, numFields);
  200. var result = {};
  201. // TODO(user): unicode and random keys
  202. for (var i = 0; i < random_num; i++) {
  203. switch (this.nextFieldType_()) {
  204. case 0:
  205. result['f' + i] = this.nextMessage_(depth++);
  206. continue;
  207. case 1:
  208. result['f' + i] = this.nextArray_(depth++);
  209. continue;
  210. case 2:
  211. result['f' + i] = goog.string.getRandomString();
  212. continue;
  213. case 3:
  214. result['f' + i] = this.nextNumber_();
  215. continue;
  216. case 4:
  217. result['f' + i] = this.nextBoolean_();
  218. continue;
  219. case 5:
  220. result['f' + i] = null;
  221. continue;
  222. default:
  223. throw Error('invalid field type encounted.');
  224. }
  225. }
  226. return result;
  227. };
  228. /**
  229. * Gets a new array.
  230. *
  231. * @param {number} depth The depth
  232. * @return {!Array} a random array.
  233. * @private
  234. */
  235. goog.labs.testing.JsonFuzzing.prototype.nextArray_ = function(depth) {
  236. if (depth > this.maxDepth_) {
  237. return [];
  238. }
  239. var size = this.options_.arraySize;
  240. var random_size = this.nextInt(0, size);
  241. var result = [];
  242. // mixed content
  243. for (var i = 0; i < random_size; i++) {
  244. switch (this.nextFieldType_()) {
  245. case 0:
  246. result.push(this.nextMessage_(depth++));
  247. continue;
  248. case 1:
  249. result.push(this.nextArray_(depth++));
  250. continue;
  251. case 2:
  252. result.push(goog.string.getRandomString());
  253. continue;
  254. case 3:
  255. result.push(this.nextNumber_());
  256. continue;
  257. case 4:
  258. result.push(this.nextBoolean_());
  259. continue;
  260. case 5:
  261. result.push(null);
  262. continue;
  263. default:
  264. throw Error('invalid field type encounted.');
  265. }
  266. }
  267. return result;
  268. };
  269. /**
  270. * Gets a new boolean.
  271. *
  272. * @return {boolean} a random boolean.
  273. * @private
  274. */
  275. goog.labs.testing.JsonFuzzing.prototype.nextBoolean_ = function() {
  276. var random = this.random_.random();
  277. return random < 0.5;
  278. };
  279. /**
  280. * Gets a new number.
  281. *
  282. * @return {number} a random number..
  283. * @private
  284. */
  285. goog.labs.testing.JsonFuzzing.prototype.nextNumber_ = function() {
  286. var result = this.random_.random();
  287. var random = this.random_.random();
  288. if (random < 0.5) {
  289. result *= 1000;
  290. }
  291. random = this.random_.random();
  292. if (random < 0.5) {
  293. result = Math.floor(result);
  294. }
  295. random = this.random_.random();
  296. if (random < 0.5) {
  297. result *= -1;
  298. }
  299. // TODO(user); more random numbers
  300. return result;
  301. };