1Test::LectroTest::GenerUasteorr(C3o)ntributed Perl DocumTeenstta:t:iLoenctroTest::Generator(3)
2
3
4
6 Test::LectroTest::Generator - Random value generators and combinators
7
9 version 0.5001
10
12 use Test::LectroTest::Generator qw(:common :combinators);
13
14 my $int_gen = Int;
15 my $pct_gen = Int( range=>[0,100] );
16 my $flt_gen = Float( range=>[0,1] );
17 my $bln_gen = Bool;
18 my $chr_gen = Char( charset=>"a-z" );
19 my $str_gen = String( charset=>"A-Z0-9", length=>[3,] );
20 my $ary_gen = List( Int(sized=>0) );
21 my $hsh_gen = Hash( $str_gen, $pct_gen );
22 my $uni_gen = Unit( "e" ); # always returns "e"
23 my $elm_gen = Elements("e1", "e2", "e3", "e4");
24
25 for my $sizing_guidance (1..100) {
26 my $i = $int_gen->generate( $sizing_guidance );
27 print "$i ";
28 }
29 print "\n";
30
31 # generates single digits
32 my $digit_gen = Elements( 0..9 ); # or Int(range=>[0,9],sized=>0)
33
34 # generates SSNs like "910-77-2236"
35 my $ssn_gen = Paste( Paste( ($digit_gen) x 3 ),
36 Paste( ($digit_gen) x 2 ),
37 Paste( ($digit_gen) x 4 ),
38 glue => "-" );
39
40 # print 10 SSNs
41 print( map {$ssn_gen->generate($_)."\n"} 1..10 );
42
43 my $english_dist_vowel_gen =
44 Frequency( [8.167,Unit("a")], [12.702,Unit("e")],
45 [6.996,Unit("i")], [ 7.507,Unit("o")],
46 [2.758,Unit("u")] );
47 # Source: http://www.csm.astate.edu/~rossa/datasec/frequency.html
48
50 This module provides random value generators for common data types and
51 provides an interface and tools for creating your own generators. It
52 also provides generator combinators that can be used to create more-
53 complex generators by combining simple ones.
54
55 A generator is an object having a method "generate", which takes a
56 single argument, size and returns a new random value. The generated
57 value is always a scalar. Generators that produce data structures
58 return references to them.
59
60 Sizing guidance
61 The "generate" method interprets its size argument as guidance about
62 the complexity of the value it should create. Typically, smaller size
63 values result in smaller generated numbers and shorter generated
64 strings and lists. Some generators, for which sizing doesn't make
65 sense, ignore sizing guidance altogether; those that do use sizing
66 guidance can be told to ignore it via the sized modifier.
67
68 The purpose of sizing is to allow LectroTest to generate simple values
69 at first and then, as testing progresses, to slowly ramp up the
70 complexity. In this way, counterexamples for obvious problems will be
71 easier for you to understand.
72
73 Generators
74 The following functions create fully-formed generators, ready to use.
75 These functions are exported into your code's namespace if you ask for
76 ":generators" or ":all" when you "use" this module.
77
78 Each generator has a "generate" method that you can call to extract a
79 new, random value from the generator.
80
81 Int
82 my $gen = Int( range=>[0,9], sized=>0 );
83
84 Creates a generator for integer values, by default in the range
85 [-32768,32767], inclusive, but this can be changed via the optional
86 range modifier.
87
88 Int( range=>[low, high] )
89 Causes the generated values to be constrained to the range
90 [low, high], inclusive. By default, the range is [-32768,
91 32767].
92
93 Note: If your range is empty (i.e., low > high), LectroTest
94 will complain.
95
96 Note: If zero is not within the range you provide, sizing makes
97 no sense because the intersection of your range and the sizing
98 range can be empty, and thus you must turn off sizing with
99 "sized=>0". If you forget, LectroTest will complain.
100
101 Int( sized=>bool )
102 If true (the default), constrains the absolute value of the
103 generated integers to the sizing guidance provided to the
104 "generate" method. Otherwise, the generated values are
105 constrained only by the range.
106
107 Float
108 my $gen = Float( range=>[-2.0,2.0], sized=>1 );
109
110 Creates a generator for floating-point values, by default in the
111 range [-32768.0,32768.0), but this can be changed via the optional
112 range modifier. By default Float generators are sized.
113
114 Float( range=>[low, high] )
115 Causes the generated values to be constrained to the range
116 [low, high). By default, the range is [-32768.0,32768.0).
117 (Note that the high value itself can never be generated, but
118 values infinitesimally close to it can.)
119
120 Note: If your range is empty (i.e., low > high), LectroTest
121 will complain.
122
123 Note: If zero is not within the range you provide, sizing makes
124 no sense because the intersection of your range and the sizing
125 range can be empty, and thus you must turn off sizing with
126 "sized=>0". If you forget, LectroTest will complain.
127
128 Float( sized=>bool )
129 If true (the default), constrains the absolute value of the
130 generated values to the sizing guidance provided to the
131 "generate" method. Otherwise, the generated values are
132 constrained only by the range.
133
134 Bool
135 my $gen = Bool;
136
137 Creates a generator for boolean values: 0 for false, 1 for true.
138 The generator ignores sizing guidance.
139
140 Char
141 my $gen = Char( charset=>"A-Za-z0-9_" );
142
143 Creates a generator for characters. By default the characters are
144 in the ASCII range [0,127], inclusive, but this behavior can be
145 changed with the charset modifier:
146
147 Char( charset=>cset )
148 Characters will be drawn from the character set given by the
149 character-set specification cset. The syntax of cset is
150 similar the Perl "tr" built-in and is a string comprised of
151 characters and character ranges:
152
153 c Adds the character c to the set.
154
155 c-d Adds the characters in the range c through d (inclusive) to
156 the set. Note: If c is lexicographically greater than d,
157 the range is empty, and no characters will be added to the
158 set.
159
160 Examples:
161
162 charset=>"abcdwxyz"
163 The characters "a", "b", "c", "d", "w", "x", "y", and "z"
164 are in the set.
165
166 charset=>"a-dx-z"
167 Shorter version of the previous example.
168
169 charset=>"\x00-\x7f"
170 The ASCII character set.
171
172 charset=>"-_A-Za-z0-9"
173 The character set contains "-", "_", upper- and lower-case
174 ASCII letters, and the digits 0-9. Notice that the dash
175 must occur first so that it is not misinterpreted as
176 denoting a range of characters.
177
178 List(elemgen)
179 my $gen = List( Bool, length=>[1,10] );
180
181 Creates a generator for lists (which are returned as array refs).
182 The elements of the lists are generated by the generator given as
183 elemgen. The lengths of the generated lists are constrained by
184 sizing guidance at the time of generation. You can override the
185 default sizing behavior using the optional length modifier:
186
187 When the list generator calls the element generator, it divides the
188 sizing guidance by the length of the list. For example, if the
189 list being generated will have 7 elements, when the list generator
190 calls the element generator to generate each element, it will scale
191 the sizing guidance by 1/7. In this way the sizing guidance
192 provides a rough constraint on the total number of elements
193 produced, regardless of the depth of the list structure being
194 generated.
195
196 List( ..., length=>N )
197 Generated lists are exactly length N.
198
199 List( ..., length=>[M,] )
200 Generated lists are at least length M. (Maximum length is
201 constrained by sizing factor.)
202
203 List( ..., length=>[M,N] )
204 Generated lists are of length between M and N, inclusive.
205 Sizing guidance is ignored.
206
207 Advanced Note: If more than one elemgen is given, they will be used
208 in turn to create successive elements. In this case, the length of
209 the list will be multiplied by the number of generators given. For
210 example, providing two generators will create double-length lists.
211
212 Hash(keygen, valgen)
213 my $gen = Hash( String( charset=>"A-Z", length=>3 ),
214 Float( range=>[0.0, 100.0] );
215
216 Creates a generator for hashes (which are returned as hash refs).
217 The keys of the hash are generated by the generator given as
218 keygen, and the values are generated by the generator valgen.
219
220 The Hash generator takes an optional length modifier that specifies
221 the desired hash length (= number of keys):
222
223 Hash( ..., length=>length-spec )
224 Specifies the desired length of the generated hashes, using the
225 same length-spec syntax as for the List generator. Note that
226 the generated hashes may be smaller than expected because of
227 key collision.
228
229 String
230 my $gen = String( length=>[3,], charset=>"A-Z" );
231
232 Creates a generator for strings. By default the strings will be
233 drawn from the ASCII character set (0 through 127) and be of length
234 constrained by the sizing factor. Both defaults can be changed
235 using modifiers:
236
237 String( charset=>cset )
238 Characters will be drawn from the character set given by the
239 character-set specification cset. The syntax of cset is
240 similar the Perl "tr" operator and is a string comprised of
241 characters and character ranges. See Char for a full
242 description.
243
244 String( length=>length-spec )
245 Specifies the desired length of generated strings, using the
246 same length-spec syntax as for the List generator.
247
248 Elements(e1, e2, ...)
249 my $gen = Elements( "alpha", "beta", "gamma" );
250
251 Creates a generator that chooses among the given elements e1, e2,
252 ... with equal probability. Each call to the "generate" method
253 will return one of the element values. Sizing guidance has no
254 effect on this generator.
255
256 Note: This generator builder does not accept modifiers. If you
257 pass any, they will be interpreted as elements to be added to the
258 pool from which the generator randomly selects, which is probably
259 not what you want.
260
261 Unit(e)
262 my $gen = Unit( "alpha" );
263
264 Creates a generator that always returns the value e. Not too
265 useful on its own but can be handy as a building block for
266 combinators to chew on. Naturally, sizing guidance has no effect
267 on this generator.
268
269 Note: This generator builder does not accept modifiers.
270
271 Generator combinators
272 The following combinators allow you to build more complicated
273 generators from simpler ones. These combinators are exported into your
274 code's namespace if you ask for ":combinators" or ":all" when you "use"
275 this module.
276
277 Paste(gens..., glue=>str)
278 my $gen = Paste( (String(charset=>"0-9",length=>4)) x 4,
279 glue => " " );
280 # gens credit-card numbers like "4592 9459 9023 1369"
281
282 my $lgen = Paste( List( String(charset=>"0-9",length=>4)
283 , length=>4 ), glue => " " );
284 # another way of doing the same
285
286 Creates a combined generator that generates values by joining the
287 values generated by each of the supplied sub-generators gens.
288 (Generated list values will have their elements "flattened" into
289 the rest of the generated results before joining.) The resulting
290 string is returned.
291
292 The values are joined using the given glue string str. If no glue
293 modifier is provided, the default glue is the empty string.
294
295 The sizing guidance given to the combined generator will be passed
296 unchanged to each of the sub-generators.
297
298 OneOf(gens...)
299 my $gen = OneOf( Unit(0), List(Int,length=>3) );
300 # generates scalar 0 or a 3-element list of integers
301
302 Creates a combined generator that generates each value by selecting
303 at random (with equal probability) one of the sub-generators in
304 gens and using that generator to generate the output value.
305
306 The sizing guidance given to the combined generator will be passed
307 unchanged to the selected sub-generator.
308
309 Note: This combinator does not accept modifiers.
310
311 Frequency([freq1, gen1], [freq2, gen2], ...)
312 my $gen = Frequency( [50, Unit("common" )],
313 [35, Unit("less common")],
314 [15, Unit("uncommon" )] );
315 # generates one of "common", "less common", or
316 # "uncommon" with respective probabilities
317 # 50%, 35%, and 15%.
318
319 Creates a combined generator that generates each value by selecting
320 at random one of the generators gen1 or gen2 or ... and using that
321 generator to generate the output value. Each generator is selected
322 with probability proportional to its associated frequency. (If all
323 of the given frequencies are the same, the Frequency combinator
324 effectively becomes OneOf.) The frequencies can be any non-
325 negative numerical values you want and will be normalized to a
326 0-to-1 scale internally. At least one frequency must be greater
327 than zero.
328
329 The sizing guidance given to the combined generator will be passed
330 unchanged to the selected sub-generator.
331
332 Note: This combinator does not accept modifiers.
333
334 Each(gens...)
335 my $gen = Each( Unit(1), Unit("X") );
336 # always generates [ 1, "X" ]
337
338 Creates a generator that returns a list (array ref) whose
339 successive elements are the successive values generated by the
340 given generators gens.
341
342 The sizing guidance given to the combined generator will be passed
343 unchanged to each sub-generator.
344
345 Note: This combinator does not accept modifiers.
346
347 (Note for technical buffs: Each(...) is exactly equivalent to
348 "List(..., length=>1)").
349
350 Apply(fn, gens...)
351 my $gen = Apply( sub { $_[0] x $_[1] }
352 , Unit("X"), Unit(4) );
353 # always generates "XXXX"
354
355 Creates a generator that applies the given function fn to arguments
356 generated from each of the given sub-generators gens and returns
357 the resulting value. Each sub-generator contributes one value, and
358 the values are passed to fn as arguments in the same order as the
359 sub-generators were given to Apply.
360
361 The sizing guidance given to the combined generator will be passed
362 unchanged to each sub-generator.
363
364 Note: The function fn is always evaluated in scalar context. If
365 you need to generate an array, return it as an array reference.
366
367 Note: This combinator does not accept modifiers.
368
369 Map(fn, gens...)
370 my $gen = Map( sub { "X" x $_[0] }
371 , Unit(4), Unit(3), Unit(0) );
372 # always generates [ "XXXX", "XXX", "" ]
373
374 Creates a generator that applies the given function fn to the
375 values generated by the given generators gen one at a time and
376 returns a list (array ref) whose elements are each of the
377 successive results.
378
379 The sizing guidance given to the combined generator will be passed
380 unchanged to each sub-generator.
381
382 Note: The function fn is always evaluated in scalar context. If
383 you need to generate an array, return it as an array reference.
384
385 Note: This combinator does not accept modifiers.
386
387 Concat(gens...)
388 my $gen = Concat( List( Unit(1), length=>3 )
389 , List( Unit("x"), length=>1 ) );
390 # always generates [1, 1, 1, "x"]
391
392 Creates a generator that concatenates the values generated by each
393 of its sub-generators, resulting in a list (which is returned as a
394 array reference). The values returned by the sub-generators are
395 expected to be lists (array refs). If a sub-generator returns a
396 scalar value, it will be treated like a single-element list that
397 contains the value.
398
399 The sizing guidance given to the combined generator will be passed
400 unchanged to each sub-generator.
401
402 Note: If a sub-generator returns something other than a list or
403 scalar, you will get a run-time error.
404
405 Note: This combinator does not accept modifiers.
406
407 Flatten(gens...)
408 my $gen = Flatten( Unit( [[[[[[ 1 ]]]]]] ) );
409 # generates [1]
410
411 Flatten is just like Concat except that it recursively flattens any
412 sublists generated by the generators gen and then concatenates them
413 to generate a final a list of depth one, regardless of the depth of
414 any sublists.
415
416 The sizing guidance given to the combined generator will be passed
417 unchanged to each sub-generator.
418
419 Note: If a sub-generator returns something other than a list or
420 scalar, you will get a run-time error.
421
422 Note: This combinator does not accept modifiers.
423
424 ConcatMap(fn, gens)
425 sub take_odds { my $x = shift;
426 $x % 2 ? [$x] : [] }
427 my $gen = ConcatMap( \&take_odds
428 , Unit(1), Unit(2), Unit(3) );
429 # generates [1, 3]
430
431 Creates a generator that applies the function fn to each of the
432 values generated by the given generators gen in turn, and then
433 concatenates the results.
434
435 The sizing guidance given to the combined generator will be passed
436 unchanged to each sub-generator.
437
438 Note: The function fn is always evaluated in scalar context. If
439 you need to generate an array, return it as an array reference.
440
441 Note: If a sub-generator returns something other than a list or
442 scalar, you will get a run-time error.
443
444 Note: This combinator does not accept modifiers.
445
446 FlattenMap(fn, gens)
447 my $gen = FlattenMap( sub { [ ($_[0]) x 3 ] }
448 , Unit([1]), Unit([[2]]) );
449 # generates [1, 1, 1, 2, 2, 2]
450
451 Creates a generator that applies the function fn to each of the
452 values generated by the given generators gen in turn, and then
453 flattens and concatenates the results.
454
455 The sizing guidance given to the combined generator will be passed
456 unchanged to each sub-generator.
457
458 Note: The function fn is always evaluated in scalar context. If
459 you need to generate an array, return it as an array reference.
460
461 Note: If a sub-generator returns something other than a list or
462 scalar, you will get a run-time error.
463
464 Note: This combinator does not accept modifiers.
465
466 Sized(fn, gen)
467 my $gen = Sized { 2 * $_[0] } List(Int);
468 # ^ magnify sizing guidance by factor of two
469 my $gen2 = Sized { 10 } Int;
470 # ^ use constant guidance of 10
471
472 Creates a generator that adjusts sizing guidance by passing it
473 through the function fn. Then it calls the generator gen with the
474 adjusted guidance and returns the result.
475
476 Note: This combinator does not accept modifiers.
477
478 Rolling your own generators
479 You can create your own generators by creating any object that has a
480 "generate" method. Your method should accept as its first argument
481 sizing guidance size and, if it makes sense, adjust the complexity of
482 the values it generates accordingly.
483
484 The easiest way to create a generator is by using the magic function
485 "Gen". It promotes a block of code into a generator. For example,
486 here's a home-brew generator for times in ctime(3) format that is built
487 on top of an Int generator:
488
489 use Test::LectroTest::Generator qw( :common Gen );
490
491 my $time_gen = Int(range=>[0, 2_147_483_647], sized=>0);
492 my $ctime_gen = Gen {
493 scalar localtime $time_gen->generate( @_ );
494 };
495
496 print($ctime_gen->generate($_), "\n") for 1..5;
497 # Fri Jun 2 18:13:21 1978
498 # Thu Mar 28 00:55:51 1974
499 # Wed Mar 26 06:41:09 2025
500 # Sun Sep 11 15:39:44 2016
501 # Fri Dec 26 00:39:31 1975
502
503 Alternatively, we could build the generator using the Apply combinator:
504
505 my $ctime_gen2 = Apply { localtime $_[0] } $time_gen;
506
507 Note: "Gen" is not exported into your code's namespace by default. If
508 you want to use it, you must import it by name or import ":all" when
509 you use this module.
510
512 Here are some examples to consider.
513
514 Simple examples
515 use strict;
516 use Test::LectroTest::Generator qw(:common);
517
518 show("Ints (sized by default)", Int);
519
520 show("Floats (sized by default)", Float);
521
522 show("Percentages (unsized)",
523 Int( range=>[0,100], sized=>0 ));
524
525 show("Lists (sized by default) of Ints (unsized) in [0,10]",
526 List( Int( sized=>0, range=>[0,10] ) ));
527
528 show("Uppercase-alpha identifiers at least 3 chars long",
529 String( length=>[3,], charset=>"A-Z" ));
530
531
532 show("Hashes (sized by default) of form AAA=>Digit",
533 Hash( String( length=>3, charset=>"A-Z" ),
534 Int( sized=>0, range=>[0,9] ) ));
535
536 sub show {
537 print "\n", shift(), "\n";
538 my ($gen) = @_;
539 for (1..10) {
540 my $val = $gen->generate($_);
541 printf "Size %2d: ", $_;
542 if (ref $val eq "HASH") {
543 my @pairs = map {"$_=>$val->{$_}"} keys %$val;
544 print "{ @pairs }";
545 }
546 elsif (ref $val eq "ARRAY") {
547 print "[ @$val ]"
548 }
549 else {
550 print $val;
551 }
552 print "\n";
553 }
554 }
555
556 Advanced examples
557 For these examples we use "Data::Dumper" to inspect the data structures
558 we generate. Also, we import not only the common generator
559 constructors (like Int) but also the generic Gen constructor, which
560 lets us build generators out of blocks on the fly.
561
562 use Data::Dumper;
563 use Test::LectroTest::Generator qw(:common Gen);
564
565 First, here's a recipe for building a list of lists of integers:
566
567 my $loloi_gen = List( List( Int(sized=>0) ) );
568 print Dumper($loloi_gen->generate(10));
569
570 You may want to run the example several times to get a feel for the
571 distribution of the generated output.
572
573 Now, a more complicated example. Here we build sized trees of random
574 depth using a recursive set of generators.
575
576 my $tree_gen = do {
577 my $density = 0.5;
578 my $leaf_gen = Int( sized=>0 );
579 my $tree_helper = \1;
580 my $branch_gen = List( Gen { $$tree_helper->generate(@_) } );
581 $tree_helper = \Gen {
582 my ($size) = @_;
583 return rand($size) < $density
584 ? $leaf_gen->generate($size)
585 : $branch_gen->generate($size + 1);
586 };
587 $$tree_helper;
588 };
589
590 print Dumper($tree_gen->generate(30));
591
592 We define a tree as either a leaf or a branch, and we randomly decide
593 between the two at each node in the growing tree. Leaves are just
594 integers and become more likely when the sizing guidance diminishes
595 (which happens as we go deeper). The code uses $density as a control
596 knob for leaf density. (Try re-running the above code after changing
597 the value of $density. Try 0, 1, and 2.) Branches, on the other hand,
598 are lists of trees. Because branches generate trees, and trees
599 generate branches, we use a reference trick to set up the mutually
600 recursive relationship. This we encapsulate within a do block for
601 tidiness.
602
604 Test::LectroTest gives a quick overview of automatic, specification-
605 based testing with LectroTest.
606
608 Tom Moertel (tom@moertel.com)
609
611 The LectroTest project was inspired by Haskell's QuickCheck module by
612 Koen Claessen and John Hughes:
613 http://www.cs.chalmers.se/~rjmh/QuickCheck/.
614
616 Copyright (c) 2004-13 by Thomas G Moertel. All rights reserved.
617
618 This program is free software; you can redistribute it and/or modify it
619 under the same terms as Perl itself.
620
621
622
623perl v5.36.0 2023-01-20 Test::LectroTest::Generator(3)