succubus 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  _A random generator based on a generalised Backus-Naur Form grammar_
2
2
 
3
- [![Build Status](https://travis-ci.org/asilano/succubus.png?branch=master)](https://travis-ci.org/asilano/succubus)
3
+ [![Build Status](https://travis-ci.org/asilano/succubus.png?branch=master)](https://travis-ci.org/asilano/succubus)[![Coverage Status](https://coveralls.io/repos/asilano/succubus/badge.png?branch=master)](https://coveralls.io/r/asilano/succubus)
4
4
 
5
5
  **Succubus** is a generator which takes stochastic paths through a
6
6
  generalised Backus-Naur Form grammar to produce random text. For instance, the following:
data/Rakefile CHANGED
@@ -3,7 +3,9 @@ require 'bundler/gem_tasks'
3
3
 
4
4
  Rake::TestTask.new do |t|
5
5
  t.test_files = FileList['test/**/*_test.rb', 'spec/**/*_spec.rb']
6
- t.warning = true
6
+
7
+ # Disable warnings - coveralls spews out a bunch
8
+ # t.warning = true
7
9
  end
8
10
 
9
11
  task :default => :test
@@ -1,77 +1,43 @@
1
+ require 'succubus/grammar'
2
+ require 'succubus/generator'
3
+ require 'succubus/result'
4
+
5
+ # @author Chris Howlett
1
6
  module Succubus
7
+ # Raised by {Grammar#initialize Grammar.new} if the grammar defined in the
8
+ # supplied block cannot be parsed.
9
+ #
10
+ # @!attribute [r] errors
11
+ # @return [Array<String>] the errors encountered during parsing
2
12
  class ParseError < RuntimeError
3
- attr_accessor :errors
4
- end
5
-
6
- class ExecuteError < RuntimeError
7
- attr_accessor :errors, :partial
8
- end
9
-
10
- class Grammar
11
- def initialize(&block)
12
- @rules = {}
13
- @errors = []
14
- define_singleton_method(:create, block)
15
- create
16
- class << self ; undef_method :create ; end
17
-
18
- unless @errors.empty?
19
- pe = ParseError.new("Errors found parsing")
20
- pe.errors = @errors
21
- raise pe
22
- end
23
- end
13
+ attr_reader :errors
24
14
 
25
- def add_rule(name, *choices, &block)
26
- name = name.to_sym
27
- @errors << "Duplicate rule definition: #{name}" if @rules.include? name
28
- @rules[name] = choices
29
- end
30
-
31
- def execute(start)
32
- gen = Generator.new(@rules)
33
- gen.run(start)
34
-
35
- unless gen.errors.empty?
36
- ee = ExecuteError.new("Errors found executing")
37
- ee.errors = gen.errors
38
- ee.partial = gen.result
39
- raise ee
40
- end
41
-
42
- gen.result
15
+ # @private
16
+ def set_errors(errors)
17
+ @errors = errors
43
18
  end
44
19
  end
45
-
46
- class Generator
47
- attr_reader :result
48
- attr_reader :errors
49
-
50
- def initialize(rules)
51
- @result = ""
52
- @rules = rules
53
- @errors = []
54
- end
55
-
56
- def run(rule)
57
- @result = invoke(rule)
20
+
21
+ # Raised by {Grammar#execute} if the {Generator} encountered any errors
22
+ # while executing the grammar.
23
+ #
24
+ # @!attribute [r] errors
25
+ # @return [Array<String>] the errors encountered during execution
26
+ # @!attribute [r] partial
27
+ # @return [Result] as much of the result as the {Generator} was able
28
+ # to produce. Typically, this will be a string with missing rules
29
+ # surrounded by +!!missing!!+. For instance, +"My !!pet!! is nice"+
30
+ class ExecuteError < RuntimeError
31
+ attr_reader :errors, :partial
32
+
33
+ # @private
34
+ def set_errors(errors)
35
+ @errors = errors
58
36
  end
59
-
60
- def invoke(rule)
61
- unless @rules.include? rule
62
- @errors << "No such rule: #{rule}"
63
- return "!!#{rule}!!"
64
- end
65
-
66
- local_res = ""
67
- @rules[rule].sample.scan(/(.*?(?<!\\)(?:\\\\)*)(<.*?>|$)/) do |match|
68
- local_res << match[0]
69
- unless match[1].empty?
70
- local_res << invoke(match[1].match(/<(.*?)>/)[1].to_sym)
71
- end
72
- end
73
-
74
- return local_res
37
+
38
+ # @private
39
+ def set_partial(partial)
40
+ @partial = partial
75
41
  end
76
42
  end
77
- end
43
+ end
@@ -0,0 +1,82 @@
1
+ module Succubus
2
+ # Class which handles running through a {Grammar} and producing a
3
+ # {Result} string.
4
+ #
5
+ # @private
6
+ class Generator
7
+
8
+ # Run through a given {Grammar}, starting at a given rule. Optionally
9
+ # use a given random seed.
10
+ #
11
+ # @param [Grammar] grammar the {Grammar} to generate a string from
12
+ # @param [Symbol] rule the name of the rule to start generation from
13
+ # @param [Integer] seed optional random seed. Defaults to +nil+, which will
14
+ # make the {Generator} use a random seed
15
+ def self.run(grammar, rule, seed=nil)
16
+ new(grammar, seed).run(rule)
17
+ end
18
+
19
+ # Create a new {Generator}, ready to run through the supplied {Grammar},
20
+ # optionally with the given random seed.
21
+ #
22
+ # @param [Grammar] grammar the {Grammar} to generate a string from
23
+ # @param [Integer] seed optional random seed. Defaults to +nil+, which will
24
+ # make the {Generator} use a random seed
25
+ def initialize(grammar, seed=nil)
26
+ @rules = grammar.rules
27
+
28
+ @seed = seed
29
+ unless @seed
30
+ srand()
31
+ @seed = rand(0xffffffff)
32
+ end
33
+ srand(@seed)
34
+
35
+ @errors = []
36
+ end
37
+
38
+ # Produce a random {Result} string from the Generator's {Grammar}
39
+ #
40
+ # @param [Symbol] rule the name of the rule to start generation from
41
+ # @return [Result] the {Result} string produced
42
+ def run(rule)
43
+ @result = Result.new(@seed, invoke(rule))
44
+ @result.set_errors(@errors)
45
+
46
+ return @result
47
+ end
48
+
49
+ private
50
+ # Execute a single rule all the way down, returning the +String+ randomly
51
+ # produced from it
52
+ #
53
+ # @param [Symbol] rule the name of the rule to execute
54
+ # @return [String] the string produced
55
+ def invoke(rule)
56
+ # Check the rule is defined; error if so
57
+ unless @rules.include? rule
58
+ @errors << "No such rule: #{rule}"
59
+ return "!!#{rule}!!"
60
+ end
61
+
62
+ local_res = ""
63
+
64
+ # Pick an option. We can't use Array#sample, as its implementation differs
65
+ # between Ruby versions, so random seeds aren't portable.
66
+ choices = @rules[rule]
67
+ choice = choices[rand(choices.length)]
68
+
69
+ # Scan it for all instances of (unescaped) <rules>
70
+ # Each match will be an array containing the portion of string before
71
+ # the <rule>; and the <rule> itself (which may be empty on the last match)
72
+ choice.scan(/(.*?(?<!\\)(?:\\\\)*)(<.*?>|$)/) do |match|
73
+ local_res << match[0]
74
+ unless match[1].empty?
75
+ local_res << invoke(match[1].match(/<(.*?)>/)[1].to_sym)
76
+ end
77
+ end
78
+
79
+ return local_res
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,79 @@
1
+ module Succubus
2
+ # Class which contains the rules describing a grammar from
3
+ # which to generate random strings.
4
+ #
5
+ # {Grammar} provides the vast majority of the user interface
6
+ # to Succubus.
7
+ #
8
+ # @!attribute [r] last_seed
9
+ # @return [Integer] the random seed used by the last call to {#execute}
10
+ # @!attribute [r] rules
11
+ # @return [Hash<Symbol => String>] the rules describing this grammar
12
+ class Grammar
13
+ attr_reader :last_seed
14
+ attr_reader :rules
15
+
16
+ # Create a new {Grammar} object. {#initialize Grammar.new} should be passed
17
+ # a block defining the rules for the grammar. +self+ in the block will be
18
+ # the newly-created {Grammar} instance
19
+ #
20
+ # @yield Block defining the grammar. +self+ is the newly-created {Grammar},
21
+ # so bare calls to {#add_rule} work as expected.
22
+ # @raise [ParseError] if errors were encountered parsing the grammar in the
23
+ # supplied block
24
+ def initialize(&block)
25
+ @rules = {}
26
+ @errors = []
27
+ define_singleton_method(:create, block)
28
+ create
29
+ class << self ; undef_method :create ; end
30
+
31
+ unless @errors.empty?
32
+ pe = ParseError.new("Errors found parsing")
33
+ pe.set_errors(@errors)
34
+ raise pe
35
+ end
36
+ end
37
+
38
+ # Define a new rule into the {Grammar}.
39
+ #
40
+ # {#add_rule} is usually called from the block parameter of {#initialize Grammar.new};
41
+ # however, it can be called on an existing {Grammar} object. Errors encountered by
42
+ # {#add_rule} will only be reported when called via {#initialize Grammar.new}.
43
+ #
44
+ # @param [#to_sym] name name of the rule
45
+ # @param [Array<String>] choices possible replacements for the rule. When +<name>+ is
46
+ # encountered during execution of the {Grammar}, +<name>+ will be replaced by one of
47
+ # the strings in +choices+ chosen uniformly at random.
48
+ def add_rule(name, *choices)
49
+ name = name.to_sym
50
+ @errors << "Duplicate rule definition: #{name}" if @rules.include? name
51
+ @rules[name] = choices
52
+ end
53
+
54
+ # Execute the grammar, producing a random {Result} string.
55
+ #
56
+ # The rule named +start+ is examined, and one of that rule's choices chosen. Then, for each
57
+ # rule reference tag +<name>+ in that choice, the tag is recursively replaced with one of
58
+ # the choices for the rule named +name+.
59
+ #
60
+ # @param [#to_sym] start name of the rule to start execution at
61
+ # @param [Integer] seed optional random seed. Defaults to +nil+, which will
62
+ # cause a random seed to be used
63
+ #
64
+ # @raise [ExecuteError] if errors were encountered while executing the grammar
65
+ def execute(start, seed=nil)
66
+ result = Generator.run(self, start.to_sym, seed)
67
+
68
+ unless result.errors.empty?
69
+ ee = ExecuteError.new("Errors found executing")
70
+ ee.set_errors(result.errors)
71
+ ee.set_partial(result)
72
+ raise ee
73
+ end
74
+
75
+ @last_seed = result.random_seed
76
+ result
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,39 @@
1
+ module Succubus
2
+ # Class containing the result of executing a {Grammar}.
3
+ #
4
+ # Result is a subclass of String, so it can be used exactly as if it were a String.
5
+ # Its main purpose is to provide access to metadata about the execution that
6
+ # produced it - namely the +random_seed+ used, and any +errors+ produced.
7
+ #
8
+ # @!attribute [r] random_seed
9
+ # @return [Integer] The random seed used to generate this Result.
10
+ # {#random_seed} can be passed to {Grammar#execute} to exactly repeat the process
11
+ # that generated this {Result} (for possible debugging purposes, or reporting issues)
12
+ # @!attribute [r] errors
13
+ # @return [Array<String>] Any errors that occurred during the generation
14
+ # of this {Result}.
15
+ class Result < String
16
+ attr_reader :random_seed, :errors
17
+
18
+ # Create a new Result
19
+ #
20
+ # @private
21
+ #
22
+ # @param [Integer] seed The random seed used to generate this result
23
+ # @param [Array] args Passed through to +super+
24
+ # @param [Proc] block Passed through to +super+
25
+ def initialize(seed, *args, &block)
26
+ @random_seed = seed
27
+ super(*args, &block)
28
+ end
29
+
30
+ # Set the errors encountered creating this Result
31
+ #
32
+ # @private
33
+ #
34
+ # @param [Array<String>] errors the errors encountered
35
+ def set_errors(errors)
36
+ @errors = errors
37
+ end
38
+ end
39
+ end
@@ -1,3 +1,4 @@
1
1
  module Succubus
2
- VERSION = "0.0.0"
2
+ # The gem version
3
+ VERSION = "0.1.0"
3
4
  end
@@ -1,5 +1,4 @@
1
- require 'minitest/autorun'
2
- require File.dirname(__FILE__) + '/../test/support/fixed_random'
1
+ require File.dirname(__FILE__) + '/spec_helper'
3
2
  require 'succubus'
4
3
 
5
4
  describe Succubus::Grammar do
@@ -50,13 +49,27 @@ describe Succubus::Grammar do
50
49
  end
51
50
 
52
51
  it "should produce a known string given pre-chosen randomness" do
53
- expect_sample ["I have a <colour> <pet>", "<pet>s look best in <colour>"], "<pet>s look best in <colour>"
54
- expect_sample ["cat", "dog"], "cat"
55
- expect_sample ["black", "white"], "black"
52
+ # From ["I have a <colour> <pet>", "<pet>s look best in <colour>"], choose "<pet>s look best in <colour>"
53
+ expect_random 2, 1
54
+ # From ["cat", "dog"], choose "cat"
55
+ expect_random 2, 0
56
+ # From ["black", "white"], choose "black"
57
+ expect_random 2, 0
56
58
 
57
59
  @grammar.execute(:base).must_equal "cats look best in black"
58
60
 
59
- samples_must_be_used
61
+ queue_must_be_used
62
+ end
63
+
64
+ it "should always produce the same string given a known seed" do
65
+ result = @grammar.execute(:base)
66
+ result.random_seed.wont_be_nil
67
+
68
+ seed = result.random_seed
69
+ 100.times { @grammar.execute(:base, seed).must_equal result }
70
+
71
+ # We know from manual runs that the seed 42 produces "I have a white cat"
72
+ 100.times { @grammar.execute(:base, 42).must_equal "I have a white cat" }
60
73
  end
61
74
  end
62
75
 
@@ -72,7 +85,7 @@ describe Succubus::Grammar do
72
85
  end
73
86
 
74
87
  ex = bad_parse.must_raise Succubus::ParseError
75
- ex.errors.must_have_same_elements_as ["Duplicate rule definition: colour", "Duplicate rule definition: base"]
88
+ ex.errors.must_equal_contents ["Duplicate rule definition: colour", "Duplicate rule definition: base"]
76
89
  end
77
90
 
78
91
  it "should fail to execute when rules are missing" do
@@ -81,10 +94,11 @@ describe Succubus::Grammar do
81
94
  add_rule :colour, "black", "white"
82
95
  end
83
96
 
84
- expect_sample %w<black white>, "black"
97
+ # From ["black", "white"], choose "black"
98
+ expect_random 2, 0
85
99
 
86
100
  ex = proc {grammar.execute(:base)}.must_raise Succubus::ExecuteError
87
- ex.errors.must_have_same_elements_as ["No such rule: size", "No such rule: pet"]
101
+ ex.errors.must_equal_contents ["No such rule: size", "No such rule: pet"]
88
102
  ex.partial.must_equal "I have a !!size!! black !!pet!!"
89
103
  end
90
104
  end
@@ -0,0 +1,27 @@
1
+ require 'coveralls'
2
+ Coveralls.wear! do
3
+ add_filter '/test/'
4
+ add_filter '/spec/'
5
+ add_filter '/examples/'
6
+ end
7
+ require 'minitest/autorun'
8
+ require 'minitest/great_expectations'
9
+
10
+ if false
11
+ require 'turn'
12
+ Turn.config do |c|
13
+ # use one of output formats:
14
+ # :outline - turn's original case/test outline mode [default]
15
+ # :progress - indicates progress with progress bar
16
+ # :dotted - test/unit's traditional dot-progress mode
17
+ # :pretty - new pretty reporter
18
+ # :marshal - dump output as YAML (normal run mode only)
19
+ # :cue - interactive testing
20
+ c.format = :outline
21
+ # turn on invoke/execute tracing, enable full backtrace
22
+ c.trace = true
23
+ # use humanized test names (works only with :outline format)
24
+ c.natural = true
25
+ end
26
+ end
27
+ require File.dirname(__FILE__) + '/../test/support/fixed_random'
@@ -1,40 +1,39 @@
1
- require File.dirname(__FILE__) + '/subset_asserts'
2
-
3
1
  module MiniTest::Assertions
4
- @@queued_samples = []
2
+ @@queued_random = []
5
3
 
6
- def self.queued_samples; @@queued_samples; end
7
- def self.next_sample; @@queued_samples.shift; end
4
+ def self.queued_random; @@queued_random; end
5
+ def self.next_random; @@queued_random.shift; end
8
6
 
9
- def expect_sample(options, *choice)
10
- assert_subset choice, options, "Can't queue #{options.inspect}.sample => #{choice}; choice not in options"
11
- @@queued_samples << {:options => options, :choice => choice}
7
+ def expect_random(max, choice)
8
+ assert_includes (0...max), choice, "Can't queue rand(#{max.inspect}) => #{choice}; choice not in [0-1)"
9
+ unless max.nil?
10
+ assert_kind_of Integer, choice, "Can't queue rand(#{max}) => #{choice}; choice not an integer"
11
+ end
12
+ @@queued_random << {:max => max, :choice => choice}
12
13
  end
13
14
 
14
- def samples_must_be_used
15
- assert_empty @@queued_samples, "Expected to have used all queued samples: #{@@queued_samples.length} left"
15
+ def queue_must_be_used
16
+ assert_empty @@queued_random, "Expected to have used all queued random choices: #{@@queued_random.length} left"
16
17
  end
17
18
  end
18
19
 
19
- unless Array.method_defined? :sample_with_predefined_values
20
- class Array
21
- def sample_with_predefined_values(n = nil, &block)
22
- queued = MiniTest::Assertions.queued_samples
23
- if !queued.empty? && queued[0][:options] == self && queued[0][:choice].length == (n || 1)
24
- expected = MiniTest::Assertions.next_sample
25
- ret = expected[:choice]
26
- ret = ret[0] if n.nil?
27
- return ret
20
+ unless Kernel.method_defined? :rand_with_predefined_values
21
+ module Kernel
22
+ def rand_with_predefined_values(max=nil, &block)
23
+ queued = MiniTest::Assertions.queued_random
24
+ if !queued.empty? && queued[0][:max] == max
25
+ expected = MiniTest::Assertions.next_random
26
+ return expected[:choice]
28
27
  else
29
- if n
30
- sample_without_predefined_values(n, &block)
28
+ if max
29
+ rand_without_predefined_values(max, &block)
31
30
  else
32
- sample_without_predefined_values(&block)
31
+ rand_without_predefined_values(&block)
33
32
  end
34
33
  end
35
34
  end
36
- alias_method :sample_without_predefined_values, :sample
37
- alias_method :sample, :sample_with_predefined_values
35
+ alias_method :rand_without_predefined_values, :rand
36
+ alias_method :rand, :rand_with_predefined_values
38
37
 
39
38
  end
40
39
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: succubus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-11 00:00:00.000000000 Z
12
+ date: 2013-04-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: !ruby/object:Gem::Requirement
16
+ requirement: &20243712 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,15 +21,32 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
24
+ version_requirements: *20243712
25
+ - !ruby/object:Gem::Dependency
26
+ name: minitest
27
+ requirement: &20243460 !ruby/object:Gem::Requirement
25
28
  none: false
26
29
  requirements:
27
30
  - - ! '>='
28
31
  - !ruby/object:Gem::Version
29
32
  version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *20243460
30
36
  - !ruby/object:Gem::Dependency
31
- name: minitest
32
- requirement: !ruby/object:Gem::Requirement
37
+ name: minitest-great_expectations
38
+ requirement: &20243208 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *20243208
47
+ - !ruby/object:Gem::Dependency
48
+ name: turn
49
+ requirement: &20242956 !ruby/object:Gem::Requirement
33
50
  none: false
34
51
  requirements:
35
52
  - - ! '>='
@@ -37,12 +54,18 @@ dependencies:
37
54
  version: '0'
38
55
  type: :development
39
56
  prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
57
+ version_requirements: *20242956
58
+ - !ruby/object:Gem::Dependency
59
+ name: coveralls
60
+ requirement: &20242704 !ruby/object:Gem::Requirement
41
61
  none: false
42
62
  requirements:
43
63
  - - ! '>='
44
64
  - !ruby/object:Gem::Version
45
65
  version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *20242704
46
69
  description: ! " Succubus is a generator which takes stochastic paths through a
47
70
  \n generalised Backus-Naur Form grammar to produce random text.\n\n See the
48
71
  examples for details.\n"
@@ -51,13 +74,16 @@ executables: []
51
74
  extensions: []
52
75
  extra_rdoc_files: []
53
76
  files:
77
+ - lib/succubus/generator.rb
78
+ - lib/succubus/grammar.rb
79
+ - lib/succubus/result.rb
54
80
  - lib/succubus/version.rb
55
81
  - lib/succubus.rb
56
82
  - Rakefile
57
83
  - README.md
58
84
  - test/support/fixed_random.rb
59
- - test/support/subset_asserts.rb
60
85
  - spec/grammar_spec.rb
86
+ - spec/spec_helper.rb
61
87
  homepage: https://github.com/asilano/succubus
62
88
  licenses: []
63
89
  post_install_message:
@@ -78,11 +104,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
104
  version: '0'
79
105
  requirements: []
80
106
  rubyforge_project:
81
- rubygems_version: 1.8.24
107
+ rubygems_version: 1.8.16
82
108
  signing_key:
83
109
  specification_version: 3
84
110
  summary: A random generator based on a generalised Backus-Naur Form grammar
85
111
  test_files:
86
112
  - test/support/fixed_random.rb
87
- - test/support/subset_asserts.rb
88
113
  - spec/grammar_spec.rb
114
+ - spec/spec_helper.rb
@@ -1,49 +0,0 @@
1
- module MiniTest::Assertions
2
- def assert_subset(subset, superset, msg = nil)
3
- sub = subset.to_a.dup
4
- supe = superset.to_a.dup
5
- msg ||= "#{sub.inspect} is not a subset of #{supe.inspect}"
6
- failed = false
7
- sub.each do |elem|
8
- if supe.index(elem)
9
- supe.delete_at(supe.index(elem) || li.length)
10
- else
11
- failed = true
12
- break
13
- end
14
- end
15
-
16
- assert(!failed, msg)
17
- end
18
-
19
- def assert_disjoint(left, right, msg = nil)
20
- left_a = left.to_a.dup
21
- right_a = right.to_a.dup
22
- msg ||= "#{left_a.inspect} and #{right_a.inspect} are not disjoint"
23
- failed = false
24
- left_a.each do |elem|
25
- if right_a.index(elem)
26
- failed = true
27
- break
28
- end
29
- end
30
-
31
- assert(!failed, msg)
32
- end
33
-
34
- def assert_same_elements(a1, a2, msg = nil)
35
- [:select, :inject, :size].each do |m|
36
- [a1, a2].each {|a| assert_respond_to(a, m, "Are you sure that #{a.inspect} is an array? It doesn't respond to #{m}.") }
37
- end
38
-
39
- assert a1h = a1.inject({}) { |h,e| h[e] = a1.select { |i| i == e }.size; h }
40
- assert a2h = a2.inject({}) { |h,e| h[e] = a2.select { |i| i == e }.size; h }
41
-
42
- assert_equal(a1h, a2h, msg)
43
- end
44
- end
45
-
46
- module MiniTest::Expectations
47
- infect_an_assertion :assert_subset, :must_be_subset_of
48
- infect_an_assertion :assert_same_elements, :must_have_same_elements_as
49
- end