Skip to content
This repository has been archived by the owner on Aug 24, 2021. It is now read-only.

Commit

Permalink
feat: Add character generator (#215)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kantis authored Oct 2, 2020
1 parent a8606a7 commit 2e8b7a6
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 16 deletions.
16 changes: 11 additions & 5 deletions docs/generators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,26 @@ Primitives
Generate booleans


Strings
Text
-------

``Generator.strings(minLength = 0, maxLength = 50, charset = StringCharsets.printable, exclude = emptySet())``
``Generator.characters(charset = CharSets.printable, exclude = emptySet())``
Generate strings. Use the parameter ``charset`` and ``exclude`` to customize the characters which can be used.

Generation include empty ("") and blank (" ") strings as samples.
Generation includes space (' ') as a sample.

.. note:: ``StringCharsets`` provide few common set of characters such as ``alpha``, ``alphaNumeric`` and others
.. note:: ``CharSets`` provide few common set of characters such as ``alpha``, ``alphaNumeric`` and others

It is there to help quickly configure the String generator.
It is there to help quickly configure the Character generator.

By default, it will generate any printable characters.

``Generator.strings(minLength = 0, maxLength = 50, charGenerator = Generator.characters())``
Generate strings. Use the parameter ``charGenerator`` to provide a character generator which is used to make the
string.

Generation includes empty ("") string as a sample.

Collections
-----------

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.github.jcornaz.kwik.generator.stdlib

import com.github.jcornaz.kwik.generator.api.Generator
import com.github.jcornaz.kwik.generator.api.withSamples
import kotlin.random.Random

fun Generator.Companion.characters(
charset: Set<Char> = CharSets.printable,
exclude: Set<Char> = emptySet()
): Generator<Char> {
val characters = (charset - exclude).toList()
require(characters.isNotEmpty())

val samples = mutableListOf<Char>()

if (' ' in characters)
samples += ' '

return Generator { rng: Random -> characters.random(rng) }.withSamples(samples)
}

/**
* Common set of character to be used with generators
*/
object CharSets {

/** All printable characters */
@Suppress("MagicNumber")
val printable: Set<Char> = (32..127).mapTo(HashSet()) { it.toChar() }

/** Numeric characters (0-9) */
val numeric: Set<Char> = ('0'..'9').toHashSet()

/** Lowercase alphabetic characters */
val alphaLowerCase: Set<Char> = ('a'..'z').toHashSet()

/** Uppercase alphabetic characters */
val alphaUpperCase: Set<Char> = ('A'..'Z').toHashSet()

/** Alphabetic characters (lower and upper cases)*/
val alpha: Set<Char> = alphaLowerCase + alphaUpperCase

/**
* Alphabetic and numeric characters
*
* Equivalent of [alpha] + [numeric]
*/
val alphaNum: Set<Char> = alpha + numeric
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,32 @@ import kotlin.random.Random
fun Generator.Companion.strings(
minLength: Int = 0,
maxLength: Int = maxOf(minLength, KWIK_DEFAULT_MAX_SIZE),
charset: Set<Char> = StringCharSets.printable,
charset: Set<Char> = CharSets.printable,
exclude: Set<Char> = emptySet()
) = strings(minLength, maxLength, characters(charset, exclude))

/**
* Returns a generator of String of length between [minLength] and [maxLength] (inclusive)
*
* @param charGenerator A generator of characters which will be used to construct the strings
*/
fun Generator.Companion.strings(
minLength: Int = 0,
maxLength: Int = maxOf(minLength, KWIK_DEFAULT_MAX_SIZE),
charGenerator: Generator<Char> = Generator.characters(),
): Generator<String> {
require(minLength >= 0) { "Invalid minLength: $minLength" }
require(maxLength >= minLength) { "Invalid maxLength: $minLength (minLength=$minLength)" }

val characters = (charset - exclude).toList()

val generator = Generator { rng: Random ->
String(CharArray(rng.nextInt(minLength, maxLength + 1)) { characters.random(rng) })
CharArray(rng.nextInt(minLength, maxLength + 1)) { charGenerator.generate(rng) }.concatToString()
}

val samples = mutableListOf<String>()

if (minLength == 0)
samples += ""

if (' ' in characters && minLength <= 1 && maxLength >= 1)
samples += " "

return generator.withSamples(samples)
}

Expand All @@ -47,9 +53,20 @@ fun Generator.Companion.strings(
*/
fun Generator.Companion.nonEmptyStrings(
maxLength: Int = KWIK_DEFAULT_MAX_SIZE,
charset: Set<Char> = StringCharSets.printable,
charset: Set<Char> = CharSets.printable,
exclude: Set<Char> = emptySet()
): Generator<String> = strings(1, maxLength, charset, exclude)
): Generator<String> = nonEmptyStrings(maxLength, characters(charset, exclude))

/**
* Returns a generator of String of length between 1 and [maxLength] (inclusive)
*
* @param charGenerator A generator of characters which will be used to construct the strings
*/
fun Generator.Companion.nonEmptyStrings(
maxLength: Int = KWIK_DEFAULT_MAX_SIZE,
charGenerator: Generator<Char> = Generator.characters(),
): Generator<String> = strings(1, maxLength, charGenerator)


/**
* Returns a generator of non-blank String of length between 1 and [maxLength] (inclusive)
Expand All @@ -59,13 +76,25 @@ fun Generator.Companion.nonEmptyStrings(
*/
fun Generator.Companion.nonBlankStrings(
maxLength: Int = KWIK_DEFAULT_MAX_SIZE,
charset: Set<Char> = StringCharSets.printable,
charset: Set<Char> = CharSets.printable,
exclude: Set<Char> = emptySet()
): Generator<String> = nonEmptyStrings(maxLength, charset, exclude).filterNot { it.isBlank() }
): Generator<String> = nonEmptyStrings(maxLength, characters(charset, exclude)).filterNot { it.isBlank() }

/**
* Returns a generator of non-blank String of length between 1 and [maxLength] (inclusive)
*
* @param charGenerator A generator of characters which will be used to construct the strings
*/
fun Generator.Companion.nonBlankStrings(
maxLength: Int = KWIK_DEFAULT_MAX_SIZE,
charGenerator: Generator<Char> = Generator.characters(),
): Generator<String> = nonEmptyStrings(maxLength, charGenerator).filterNot { it.isBlank() }


/**
* Common set of character to be used with string generators
*/
@Deprecated("Replaced by CharSets", replaceWith = ReplaceWith("CharSets"))
object StringCharSets {

/** All printable characters */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.github.jcornaz.kwik.generator.stdlib

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class CharSetTest {

@Test
fun provideNumericCharacters() {
assertEquals("0123456789".toSet(), CharSets.numeric)
}

@Test
fun provideLowerCaseAlphabeticCharacters() {
assertEquals("abcdefghijklmnopqrstuvwxyz".toSet(), CharSets.alphaLowerCase)
}

@Test
fun provideUpperCaseAlphabeticCharacters() {
assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ".toSet(), CharSets.alphaUpperCase)
}

@Test
fun alphaContainsAllLowerCaseCharacters() {
CharSets.alphaLowerCase.forEach {
assertTrue(it in CharSets.alpha)
}
}

@Test
fun alphaContainsAllUpperCaseCharacters() {
CharSets.alphaUpperCase.forEach {
assertTrue(it in CharSets.alpha)
}
}

@Test
fun alphaDoesNotContainsCharactersOtherThanLowerAndUpperCaseAlpha() {
(Char.MIN_VALUE..Char.MAX_VALUE).asSequence()
.filterNot { it in CharSets.alphaLowerCase }
.filterNot { it in CharSets.alphaUpperCase }
.forEach {
assertFalse(it in CharSets.alpha )
}
}

@Test
fun alphaNumContainsAllAlphaCharacters() {
CharSets.alpha.forEach {
assertTrue(it in CharSets.alphaNum)
}
}

@Test
fun alphaNumContainsAllNumericCharacters() {
CharSets.numeric.forEach {
assertTrue(it in CharSets.alphaNum)
}
}

@Test
fun alphaNulDoesNotContainsCharactersOtherThenAlphaAndNums() {
(Char.MIN_VALUE..Char.MAX_VALUE).asSequence()
.filterNot { it in CharSets.alpha }
.filterNot { it in CharSets.numeric }
.forEach {
assertFalse(it in CharSets.alphaNum )
}
}

@Test
fun printableCharacterIsSuperSetOfAlphaNum() {
CharSets.alphaNum.forEach {
assertTrue(it in CharSets.printable)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.github.jcornaz.kwik.generator.stdlib

import com.github.jcornaz.kwik.generator.api.Generator
import com.github.jcornaz.kwik.generator.api.randomSequence
import com.github.jcornaz.kwik.generator.test.AbstractGeneratorTest
import kotlin.test.Test
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue

class CharacterGeneratorTest : AbstractGeneratorTest() {

override val generator: Generator<Char> = Generator.characters()

@Test
fun generateSpace() {
assertTrue(generator.randomSequence(0).take(5).any { it == ' ' })
}

@Test
fun failIfEmptyCharSet() {
assertFailsWith<IllegalArgumentException> { Generator.characters(charset = emptySet()) }
}

@Test
fun failIfEntireCharsetExcluded() {
assertFailsWith<IllegalArgumentException> {
Generator.characters(
charset = CharSets.printable,
exclude = CharSets.printable
)
}
}

@Test
fun generateDifferentValues() {
val values = mutableSetOf<Char>()

Generator.characters(CharSets.alphaNum)
.randomSequence(0)
.take(200)
.forEach {
values += it
}

assertTrue { values.size > 10 }
}

@Test
fun dontGenerateExcludedChars() {
val values = Generator.characters(exclude = setOf('a', 'b', 'c'))
.randomSequence(0)
.take(100)

assertTrue(values.none { it == 'a' || it == 'b' || it == 'c' })
}
}

0 comments on commit 2e8b7a6

Please sign in to comment.