1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
# encoding: utf-8
# This file is part of ruby-flores.
# Copyright (C) 2015 Jordan Sissel
#
require "flores/namespace"
autoload :Socket, "socket"
# A collection of methods intended for use in randomized testing.
module Flores::Random
# A selection of UTF-8 characters
#
# I'd love to generate this, but I don't yet know enough about how unicode
# blocks are allocated to do that. For now, hardcode a set of possible
# characters.
CHARACTERS = [
# Basic Latin
*(32..126).map(&:chr).map { |c| c.force_encoding(Encoding.default_external) },
# hand-selected CJK Unified Ideographs Extension A
"㐤", "㐨", "㐻", "㑐",
# hand-selected Hebrew
"א", "ב", "ג", "ד", "ה",
# hand-selected Cyrillic
"Є", "Б", "Р", "н", "я"
]
# Generates text with random characters of a given length (or within a length range)
#
# * The length can be a number or a range `x..y`. If a range, it must be ascending (x < y)
# * Negative lengths are not permitted and will raise an ArgumentError
#
# @param length [Integer or Range] the length of text to generate
# @return [String] the generated text
def self.text(length)
return text_range(length) if length.is_a?(Range)
raise ArgumentError, "A negative length is not permitted, I received #{length}" if length < 0
length.times.collect { character }.join
end # def text
# Generate text with random characters of a length within the given range.
#
# @param range [Range] the range of length to generate, inclusive
# @return [String] the generated text
def self.text_range(range)
raise ArgumentError, "Requires ascending range, you gave #{range}." if range.end < range.begin
raise ArgumentError, "A negative range values are not permitted, I received range #{range}" if range.begin < 0
text(integer(range))
end
# Generates a random character (A string of length 1)
#
# @return [String]
def self.character
return CHARACTERS[integer(0...CHARACTERS.length)]
end # def character
# Return a random integer value within a given range.
#
# @param range [Range]
def self.integer(range)
raise ArgumentError, "Range not given, got #{range.class}: #{range.inspect}" if !range.is_a?(Range)
rand(range)
end # def integer
# Return a random number within a given range.
#
# @param range [Range]
def self.number(range)
raise ArgumentError, "Range not given, got #{range.class}: #{range.inspect}" if !range.is_a?(Range)
# Ruby 1.9.3 and below do not have Enumerable#size, so we have to compute the size of the range
# ourselves.
rand * (range.end - range.begin) + range.begin
end # def number
# Run a block a random number of times.
#
# @param range [Fixnum of Range] same meaning as #integer(range)
def self.iterations(range, &block)
range = 0..range if range.is_a?(Numeric)
if block_given?
integer(range).times(&block)
nil
else
integer(range).times
end
end # def iterations
# Return a random element from an array
def self.item(array)
array[integer(0...array.size)]
end
# Return a random IPv4 address as a string
def self.ipv4
# TODO(sissel): Support CIDR range restriction?
# TODO(sissel): Support netmask restriction?
[integer(0..IPV4_MAX)].pack("N").unpack("C4").join(".")
end
# Return a random IPv6 address as a string
#
# The address may be in abbreviated form (ABCD::01EF):w
def self.ipv6
# TODO(sissel): Support CIDR range restriction?
# TODO(sissel): Support netmask restriction?
length = integer(2..8)
if length == 8
# Full address; nothing to abbreviate
ipv6_pack(length)
else
abbreviation = ipv6_abbreviation(length)
if length == 2
first = 1
second = 1
else
first = integer(2...length)
second = length - first
end
ipv6_pack(first) + abbreviation + ipv6_pack(second)
end
end
# Get a TCP socket bound and listening on a random port.
#
# You are responsible for closing the socket.
#
# Returns [socket, address, port]
def self.tcp_listener(host = "127.0.0.1")
socket_listener(Socket::SOCK_STREAM, host)
end
# Get a UDP socket bound and listening on a random port.
#
# You are responsible for closing the socket.
#
# Returns [socket, address, port]
def self.udp_listener(host = "127.0.0.1")
socket_listener(Socket::SOCK_DGRAM, host)
end
private
IPV4_MAX = 1 << 32
IPV6_SEGMENT = 1 << 16
def self.ipv6_pack(length)
length.times.collect { integer(0...IPV6_SEGMENT).to_s(16) }.join(":")
end
def self.ipv6_abbreviation(length)
abbreviate = (integer(0..1) == 0)
if abbreviate
"::"
else
":" + (8 - length).times.collect { "0" }.join(":") + ":"
end
end
LISTEN_BACKLOG = 5
def self.socket_listener(type, host)
socket = server_socket_class.new(Socket::AF_INET, type)
socket.bind(Socket.pack_sockaddr_in(0, host))
if type == Socket::SOCK_STREAM || type == Socket::SOCK_SEQPACKET
socket.listen(LISTEN_BACKLOG)
end
port = socket.local_address.ip_port
address = socket.local_address.ip_address
[socket, address, port]
end
def self.server_socket_class
if RUBY_ENGINE == 'jruby'
# https://github.com/jruby/jruby/wiki/ServerSocket
ServerSocket
else
Socket
end
end
end # module Flores::Random
|