Skip to content

Commit

Permalink
Merge pull request #10 from ohbarye/char-and-string-arbitrary
Browse files Browse the repository at this point in the history
Char and string arbitrary
  • Loading branch information
ohbarye authored Apr 2, 2024
2 parents 2cbad83 + be72a1b commit b8a06f8
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .standard.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# For available configuration options, see:
# https://github.com/standardrb/standard
ruby_version: 3.3
ruby_version: 3.1
40 changes: 40 additions & 0 deletions lib/pbt/arbitrary/arbitrary_methods.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# frozen_string_literal: true

require "pbt/arbitrary/constant"
require "pbt/arbitrary/array_arbitrary"
require "pbt/arbitrary/char_arbitrary"
require "pbt/arbitrary/integer_arbitrary"
require "pbt/arbitrary/tuple_arbitrary"
require "pbt/arbitrary/fixed_hash_arbitrary"
require "pbt/arbitrary/choose_arbitrary"
require "pbt/arbitrary/one_of_arbitrary"
require "pbt/arbitrary/string_arbitrary"

module Pbt
module Arbitrary
Expand Down Expand Up @@ -50,6 +53,43 @@ def choose(range)
def one_of(*choices)
OneOfArbitrary.new(choices)
end

# Generates a single unicode character (including printable and non-printable).
def char
CharArbitrary.new
end

def alphanumeric_char
one_of(*ALPHANUMERIC_CHARS)
end

def alphanumeric_string(**kwargs)
StringArbitrary.new(array(alphanumeric_char, **kwargs))
end

def ascii_char
one_of(*ASCII_CHARS)
end

def ascii_string(**kwargs)
StringArbitrary.new(array(ascii_char, **kwargs))
end

def printable_ascii_char
one_of(*PRINTABLE_ASCII_CHARS)
end

def printable_ascii_string(**kwargs)
StringArbitrary.new(array(printable_ascii_char, **kwargs))
end

def printable_char
one_of(*PRINTABLE_CHARS)
end

def printable_string(**kwargs)
StringArbitrary.new(array(printable_char, **kwargs))
end
end
end
end
36 changes: 36 additions & 0 deletions lib/pbt/arbitrary/char_arbitrary.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module Pbt
module Arbitrary
class CharArbitrary
def initialize
@arb = ChooseArbitrary.new(0..0x10FFFF)
end

# @return [String]
def generate(rng)
map(@arb.generate(rng))
end

# Shrinks towards characters with lower codepoints, e.g. ASCII
# @return [Enumerator]
def shrink(current)
Enumerator.new do |y|
@arb.shrink(unmap(current)).each do |v|
y << map(v)
end
end
end

private

def map(v)
[v].pack("U")
end

def unmap(v)
v.unpack1("U")
end
end
end
end
15 changes: 15 additions & 0 deletions lib/pbt/arbitrary/constant.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module Pbt
module Arbitrary
ALPHANUMERIC_CHARS = [*("a".."z"), *("A".."Z"), *("0".."9")].freeze
PRINTABLE_ASCII_CHARS = [*(" ".."~")].freeze
ASCII_CHARS = [*PRINTABLE_ASCII_CHARS, "\n", "\r", "\t", "\v", "\b", "\f", "\e", "\d", "\a"].freeze
PRINTABLE_CHARS = [
*ASCII_CHARS,
*("\u{A0}".."\u{D7FF}"),
*("\u{E000}".."\u{FFFD}"),
*("\u{10000}".."\u{10FFFF}")
].freeze
end
end
36 changes: 36 additions & 0 deletions lib/pbt/arbitrary/string_arbitrary.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module Pbt
module Arbitrary
class StringArbitrary
# @param arb [ArrayArbitrary]
def initialize(arb)
@arb = arb
end

# @return [Array]
def generate(rng)
map(@arb.generate(rng))
end

# @return [Enumerator]
def shrink(current)
Enumerator.new do |y|
@arb.shrink(unmap(current)).each do |v|
y.yield map(v)
end
end
end

private

def map(v)
v.join
end

def unmap(v)
v.chars
end
end
end
end
139 changes: 139 additions & 0 deletions spec/pbt/arbitrary/arbitrary_methods_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# frozen_string_literal: true

RSpec.describe Pbt::Arbitrary::ArbitraryMethods do
describe ".alphanumeric_char" do
it "generates a character" do
val = Pbt.alphanumeric_char.generate(Random.new)
expect(val).to be_a(String)
expect(val.size).to eq(1)
end

it "is alphanumeric char" do
val = Pbt.alphanumeric_char.generate(Random.new)
expect(Pbt::Arbitrary::ALPHANUMERIC_CHARS).to include(val)
end
end

describe ".alphanumeric_string" do
it "generates a string" do
arb = Pbt.alphanumeric_string(min: 1, max: 5)
aggregate_failures do
100.times do
val = arb.generate(Random.new)
expect(val).to be_a(String)
expect(val.size).to be >= 1
expect(val.size).to be <= 5
end
end
end

it "is alphanumeric string" do
val = Pbt.alphanumeric_string.generate(Random.new)
val.chars.each do |char|
expect(Pbt::Arbitrary::ALPHANUMERIC_CHARS).to include(char)
end
end
end

describe ".ascii_char" do
it "generates a character" do
val = Pbt.ascii_char.generate(Random.new)
expect(val).to be_a(String)
expect(val.size).to eq(1)
end

it "is printable ascii char" do
val = Pbt.ascii_char.generate(Random.new)
expect(Pbt::Arbitrary::ASCII_CHARS).to include(val)
end
end

describe ".ascii_string" do
it "generates a string" do
arb = Pbt.ascii_string(min: 1, max: 5)
aggregate_failures do
100.times do
val = arb.generate(Random.new)
expect(val).to be_a(String)
expect(val.size).to be >= 1
expect(val.size).to be <= 5
end
end
end

it "is ascii string" do
val = Pbt.ascii_string.generate(Random.new)
val.chars.each do |char|
expect(Pbt::Arbitrary::ASCII_CHARS).to include(char)
end
end
end

describe ".printable_ascii_char" do
it "generates a character" do
val = Pbt.printable_ascii_char.generate(Random.new)
expect(val).to be_a(String)
expect(val.size).to eq(1)
end

it "is printable ascii char" do
val = Pbt.printable_ascii_char.generate(Random.new)
expect(Pbt::Arbitrary::PRINTABLE_ASCII_CHARS).to include(val)
end
end

describe ".printable_ascii_string" do
it "generates a string" do
arb = Pbt.printable_ascii_string(min: 1, max: 5)
aggregate_failures do
100.times do
val = arb.generate(Random.new)
expect(val).to be_a(String)
expect(val.size).to be >= 1
expect(val.size).to be <= 5
end
end
end

it "is printable_ascii string" do
val = Pbt.printable_ascii_string.generate(Random.new)
val.chars.each do |char|
expect(Pbt::Arbitrary::PRINTABLE_ASCII_CHARS).to include(char)
end
end
end

describe ".printable_char" do
it "generates a character" do
val = Pbt.printable_char.generate(Random.new)
expect(val).to be_a(String)
expect(val.size).to eq(1)
end

it "is printable char" do
val = Pbt.printable_char.generate(Random.new)
expect(Pbt::Arbitrary::PRINTABLE_CHARS).to include(val)
end
end

describe ".printable_string" do
it "generates a string" do
arb = Pbt.printable_string(min: 1, max: 5)
aggregate_failures do
100.times do
val = arb.generate(Random.new)
expect(val).to be_a(String)
expect(val.size).to be >= 1
expect(val.size).to be <= 5
end
end
end

it "is printable string" do
val = Pbt.printable_string.generate(Random.new)
val.chars.each do |char|
expect(Pbt::Arbitrary::PRINTABLE_CHARS).to include(char)
end
end
end
end
24 changes: 24 additions & 0 deletions spec/pbt/arbitrary/char_arbitrary_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

RSpec.describe Pbt::Arbitrary::CharArbitrary do
describe "#generate" do
it "generates an character" do
val = Pbt::Arbitrary::CharArbitrary.new.generate(Random.new)
expect(val).to be_a(String)
expect(val.size).to eq(1)
end
end

describe "#shrink" do
it "returns an Enumerator" do
arb = Pbt::Arbitrary::CharArbitrary.new
val = arb.generate(Random.new)
expect(arb.shrink(val)).to be_a(Enumerator)
end

it "returns an Enumerator that iterates characters shrinking towards lower codepoint" do
arb = Pbt::Arbitrary::CharArbitrary.new
expect(arb.shrink("z").to_a).to eq ["=", "\u001F", "\u0010", "\b", "\u0004", "\u0002", "\u0001", "\u0000"]
end
end
end

0 comments on commit b8a06f8

Please sign in to comment.