justinf-unification_assertion 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +133 -0
- data/Rakefile +8 -0
- data/lib/unification_assertion.rb +145 -0
- data/lib/unification_assertion/version.rb +3 -0
- data/test/unification_assertion_test.rb +127 -0
- data/unification_assertion.gemspec +26 -0
- metadata +74 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# UnificationAssertion
|
2
|
+
|
3
|
+
UnificationAssertion provides powerful and simple way to compare two
|
4
|
+
structures which may be different partialy. The comparison is based on
|
5
|
+
unification algorithm, which is used in Prolog implementations and
|
6
|
+
type inference algorithms.
|
7
|
+
|
8
|
+
The assertion will be like the following:
|
9
|
+
|
10
|
+
assert_unifiable({ "timestamp" => :_,
|
11
|
+
"person" => {
|
12
|
+
"id" => :_,
|
13
|
+
"name" => "John",
|
14
|
+
"email" => "john@example.com",
|
15
|
+
"created_at" => :_a,
|
16
|
+
"updated_at" => :_a
|
17
|
+
}
|
18
|
+
}, JSON.parse(@response.body))
|
19
|
+
|
20
|
+
It compares two hash objects, but it does not care the exact value of
|
21
|
+
`"timestamp"`, `"id"`, `"created_at"`, and `"updated_at"`. The meta
|
22
|
+
variable `:_a` is not a black hole but test the equality between
|
23
|
+
`"created_at"` and `"updated_at"`.
|
24
|
+
|
25
|
+
## Introduction
|
26
|
+
|
27
|
+
I have been writing some tests like the following Rails functional
|
28
|
+
tests.
|
29
|
+
|
30
|
+
# Testing a web api which create a person data and send the created person object as JSON
|
31
|
+
post(:create_person, { :person => { :name => "John", :email => "john@example.com" } })
|
32
|
+
|
33
|
+
# Compare expected hash and actual result parsed by JSON parser
|
34
|
+
assert_equal({ "timestamp" => Time.now,
|
35
|
+
"person" => {
|
36
|
+
"id" => 13,
|
37
|
+
"name" => "John",
|
38
|
+
"email" => "john@example.com",
|
39
|
+
"created_at" => Time.now,
|
40
|
+
"updated_at" => Time.now
|
41
|
+
}
|
42
|
+
}, JSON.parse(@response.body))
|
43
|
+
|
44
|
+
You may point out some problems on the test.
|
45
|
+
|
46
|
+
* The ID of the result may be different from 13
|
47
|
+
* It is not sure to assume `result["timestamp"]` is equal to `Time.now`
|
48
|
+
* It is not sure to assume `result["person"]["created_at"]` is equal to `Time.now`
|
49
|
+
|
50
|
+
The root of the problems is that the comparison is too strict. The
|
51
|
+
properties I would like to test is only its `name` and `email`
|
52
|
+
fields. So I should test like the following:
|
53
|
+
|
54
|
+
assert_equal "John", result["person"]["name"]
|
55
|
+
assert_equal "john@example.com", result["person"]["email"]
|
56
|
+
|
57
|
+
It looks too complicated, and we need some way to compare structures.
|
58
|
+
This library, UnificationAssertion, provides the primitive for the
|
59
|
+
comparison called `assert_unifiable`.
|
60
|
+
|
61
|
+
assert_unifiable({ "timestamp" => :_,
|
62
|
+
"person" => {
|
63
|
+
"id" => :_,
|
64
|
+
"name" => "John",
|
65
|
+
"email" => "john@example.com",
|
66
|
+
"created_at" => :_a,
|
67
|
+
"updated_at" => :_a
|
68
|
+
}
|
69
|
+
}, JSON.parse(@response.body))
|
70
|
+
|
71
|
+
Symbols `:_a` for example, where its name starts with `_` is
|
72
|
+
interpreted as a meta variable. `assert_unifiable` does not care their
|
73
|
+
exact value is, but only the existence (can be `nil`) and equalities
|
74
|
+
for each occurance will be tested. The special symbol `:_` is a
|
75
|
+
wildcard. It can appear many times, but it will not be bound with any
|
76
|
+
value.
|
77
|
+
|
78
|
+
## Examples
|
79
|
+
|
80
|
+
assert_unifiable(:_a, 1) # pass, :_a will be 1
|
81
|
+
assert_unifiable([:_a, 1], [1, 1]) # pass, :_a will be 1
|
82
|
+
assert_unifiable([:_, :_], [1, 2]) # pass, :_ can not be bound with any value
|
83
|
+
assert_unifiable([:_a, :_a], [1, 2]) # fail, :_a can not be either 1 and 2
|
84
|
+
assert_unifiable([:_a], [1,2,3]) # fail, :_a can be a value but can not be a sequence
|
85
|
+
|
86
|
+
assert_unifiable({ :x => :_a }, { :x => 1 }) # pass, :_a will be 1
|
87
|
+
assert_unifiable({ :y => :_a }, { }) # fail, a key :y should be present
|
88
|
+
assert_unifiable({ :y => :_a }, { :y => nil }) # pass, :_a will be nil
|
89
|
+
assert_unifiable({ :_a => 1 }, { :x => 1 }) # fail, meta variable can not appear as a key
|
90
|
+
|
91
|
+
# assert_unifiable can receive a block, which will be yielded with the result of unification.
|
92
|
+
assert_unifiable([:_a, :_b], [1, 2]) do |unifier|
|
93
|
+
assert unifier[:_a] < unifier[:_b]
|
94
|
+
end
|
95
|
+
|
96
|
+
## Installation
|
97
|
+
|
98
|
+
Update your `Gemfile`.
|
99
|
+
|
100
|
+
gem "unification_assertion", :git => "git://github.com/soutaro/unification_assertion.git"
|
101
|
+
|
102
|
+
Write your test case.
|
103
|
+
|
104
|
+
require "minitest/autorun"
|
105
|
+
require "unification_assertion"
|
106
|
+
|
107
|
+
class GreatTest < MiniTest::Unit::TestCase
|
108
|
+
include UnificationAssertion
|
109
|
+
|
110
|
+
def test_something
|
111
|
+
assert_unifiable([:_a, :_b], [1, 2])
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
## Known Issues
|
116
|
+
|
117
|
+
### It skips occur check
|
118
|
+
|
119
|
+
The recursive pattern can not be processed well.
|
120
|
+
|
121
|
+
assert_unifiable(:_a, { :x => :_a })
|
122
|
+
|
123
|
+
Usual unification algorithm rejects such input by *occur check*. This
|
124
|
+
library is expected to be used for testing, so that I omit the
|
125
|
+
checking. (Who in the world will write such comparison?)
|
126
|
+
|
127
|
+
## Author
|
128
|
+
|
129
|
+
Written by Soutaro Matsumoto. (matsumoto at soutaro dot com)
|
130
|
+
|
131
|
+
Released under the MIT License: www.opensource.org/licenses/mit-license.php
|
132
|
+
|
133
|
+
github.com/soutaro/unification_assertion
|
data/Rakefile
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require "unification_assertion/version"
|
2
|
+
|
3
|
+
module UnificationAssertion
|
4
|
+
module_function
|
5
|
+
|
6
|
+
@@comparators = {}
|
7
|
+
|
8
|
+
@@comparators[Array] = lambda {|a, b, message, eqs, unifier, &block|
|
9
|
+
block.call(a.length, b.length, message + " (Array length mismatch)")
|
10
|
+
eqs.concat(a.zip(b))
|
11
|
+
}
|
12
|
+
|
13
|
+
@@comparators[Hash] = lambda {|a, b, message, eqs, unifier, &block|
|
14
|
+
block.call(a.keys.sort, b.keys.sort, message + " (Hash keys mismatch)")
|
15
|
+
eqs.concat(a.keys.map {|key| [a[key], b[key]] })
|
16
|
+
}
|
17
|
+
|
18
|
+
# Comparators are hash from a class to its comparator.
|
19
|
+
# It is used to check unifiability of two object of the given hash.
|
20
|
+
#
|
21
|
+
# There are two comparators defined by default; for |Array| and |Hash|.
|
22
|
+
#
|
23
|
+
# == Example ==
|
24
|
+
# UnificationAssertion.comparators[Array] = lambda do |a, b, message, eqs, unifier, &block|
|
25
|
+
# block.call(a.length, bl.ength, message + " (Array length mismatch)")
|
26
|
+
# eqs.concat(a.zip(b))
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# This is our comparator for |Array|. It first tests if the length of the two arrays are equal.
|
30
|
+
# And then, it pushes the equations of each components of the arrays.
|
31
|
+
# The components of the arrays will be tested unifiability later.
|
32
|
+
#
|
33
|
+
# == Comparator ==
|
34
|
+
#
|
35
|
+
# Comparator is a lambda which takes 5 arguments and block and checks the unifiability
|
36
|
+
# of the first two arguments.
|
37
|
+
#
|
38
|
+
# * |a|, |b| Objects they are compared.
|
39
|
+
# * |message| Message given to |unify|.
|
40
|
+
# * |eqs| Equations array. This is for output.
|
41
|
+
# * |unifier| Current unifier. This is just for reference.
|
42
|
+
# * |&block| Block given to |unify|, which can be used to check the equality of two values.
|
43
|
+
#
|
44
|
+
def comparators
|
45
|
+
@@comparators
|
46
|
+
end
|
47
|
+
|
48
|
+
# Run unification algorithm for given equations.
|
49
|
+
# If all equations in |eqs| are unifiable, |unify| returns (the most-general) unifier.
|
50
|
+
#
|
51
|
+
# Which identifies an symbol as a meta variable if the name matches with |options[:meta_pattern]|.
|
52
|
+
# The default of the meta variable pattern is |/^_/|.
|
53
|
+
# For example, |:_a| and |:_xyz| are meta variable, but |:a| and |:hello_world| are not.
|
54
|
+
#
|
55
|
+
# It also accepts wildcard variable. Which matches with any value, but does not introduce new equational constraints.
|
56
|
+
# The default wildcard is |:_|.
|
57
|
+
#
|
58
|
+
# |unify| takes a block to test equation of two values.
|
59
|
+
# The simplest form should be using |assert_equal|, however it can be customized as you like.
|
60
|
+
#
|
61
|
+
# == Example ==
|
62
|
+
# unify([[:_a, 1], [{ :x => :_b, :y => 1 }, { :x => 3, :y => 1 }]], "Example!!") do |x, y, message|
|
63
|
+
# assert_equal(x,y,message)
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# The |unify| call will return an hash |{ :_a => 1, :_b => 3 }|.
|
67
|
+
# The block will be used to test equality between 1 and 1 (it will pass.)
|
68
|
+
#
|
69
|
+
def unify(eqs, message = "", unifier = {}, options = {}, &block)
|
70
|
+
options = { :meta_pattern => /^_/, :wildcard => :_ }.merge!(options)
|
71
|
+
|
72
|
+
pattern = options[:meta_pattern]
|
73
|
+
wildcard = options[:wildcard]
|
74
|
+
|
75
|
+
while eq = eqs.shift
|
76
|
+
a,b = eq
|
77
|
+
case
|
78
|
+
when (Symbol === a and a.to_s =~ pattern)
|
79
|
+
unless a == wildcard
|
80
|
+
eqs = substitute({ a => b }, eqs)
|
81
|
+
unifier = substitute({ a => b }, unifier).merge!(a => b)
|
82
|
+
end
|
83
|
+
when (Symbol === b and b.to_s =~ pattern)
|
84
|
+
unless b == wildcard
|
85
|
+
eqs = substitute({ b => a }, eqs)
|
86
|
+
unifier = substitute({ b => a }, unifier).merge!(b => a)
|
87
|
+
end
|
88
|
+
when (a.class == b.class and @@comparators[a.class])
|
89
|
+
@@comparators[a.class].call(a, b, message, eqs, unifier, &block)
|
90
|
+
else
|
91
|
+
yield(a, b, message)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
unifier.inject({}) {|acc, (key, value)|
|
96
|
+
if key == value
|
97
|
+
acc
|
98
|
+
else
|
99
|
+
acc.merge!(key => value)
|
100
|
+
end
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
@@substituters = {}
|
105
|
+
@@substituters[Hash] = lambda {|unifier, hash|
|
106
|
+
hash.inject({}) {|acc, (key, val)|
|
107
|
+
if unifier[val]
|
108
|
+
acc.merge!(key => unifier[val])
|
109
|
+
else
|
110
|
+
acc.merge!(key => substitute(unifier, val))
|
111
|
+
end
|
112
|
+
}
|
113
|
+
}
|
114
|
+
@@substituters[Symbol] = lambda {|unifier, symbol| unifier[symbol] or symbol }
|
115
|
+
@@substituters[Array] = lambda {|unifier, array|
|
116
|
+
array.map {|x| substitute(unifier, x) }
|
117
|
+
}
|
118
|
+
|
119
|
+
def substitutions
|
120
|
+
@@substituters
|
121
|
+
end
|
122
|
+
|
123
|
+
def substitute(unifier, a)
|
124
|
+
subst = @@substituters[a.class]
|
125
|
+
if subst
|
126
|
+
subst.call(unifier, a)
|
127
|
+
else
|
128
|
+
a
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Run unification between |a| and |b|, and fails if they are not unifiable.
|
133
|
+
# |assert_unifiable| can have block, which yields the unifier for |a| and |b| if exists.
|
134
|
+
#
|
135
|
+
def assert_unifiable(a, b, message = "", options = {}, &block)
|
136
|
+
unifier = unify([[a, b]], message, {}, options) do |a, b, message|
|
137
|
+
assert_equal(a, b, message)
|
138
|
+
end
|
139
|
+
|
140
|
+
if block
|
141
|
+
yield(unifier)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "unification_assertion"
|
3
|
+
|
4
|
+
class UnificationAssertionTest < MiniTest::Unit::TestCase
|
5
|
+
include UnificationAssertion
|
6
|
+
|
7
|
+
def call_unify(a, b)
|
8
|
+
begin
|
9
|
+
unifier = unify([[a,b]]) do |a, b|
|
10
|
+
unless a == b
|
11
|
+
raise "Failure"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
return unifier
|
15
|
+
rescue
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_substitution
|
21
|
+
subst = { :a => 1 }
|
22
|
+
|
23
|
+
assert_equal 1, substitute(subst, :a)
|
24
|
+
assert_equal :b, substitute(subst, :b)
|
25
|
+
|
26
|
+
assert_equal "a", substitute(subst, "a")
|
27
|
+
|
28
|
+
assert_equal [], substitute(subst, [])
|
29
|
+
assert_equal [1], substitute(subst, [:a])
|
30
|
+
assert_equal [:b], substitute(subst, [:b])
|
31
|
+
|
32
|
+
assert_equal [[1]], substitute(subst, [[:a]])
|
33
|
+
assert_equal [[:b]], substitute(subst, [[:b]])
|
34
|
+
|
35
|
+
assert_equal({}, substitute(subst, {}))
|
36
|
+
assert_equal({ x: 1 }, substitute(subst, { x: :a }))
|
37
|
+
assert_equal({ x: :b}, substitute(subst, { x: :b }))
|
38
|
+
|
39
|
+
assert_equal({ x: { y: 1 } }, substitute(subst, { x: { y: :a } }))
|
40
|
+
assert_equal({ x: { y: :b } }, substitute(subst, { x: { y: :b } }))
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_unify
|
44
|
+
assert_nil call_unify(:a, :b)
|
45
|
+
|
46
|
+
assert_equal({}, call_unify(:a, :a))
|
47
|
+
|
48
|
+
assert_equal({ :_a => 1 }, call_unify(:_a, 1))
|
49
|
+
assert_equal({ :_b => :a }, call_unify(:a, :_b))
|
50
|
+
|
51
|
+
assert_equal({ :_a => [1,:_b,3] }, call_unify([1,:_b,3], :_a))
|
52
|
+
assert_equal({}, call_unify([1,2,3], [1,2,3]))
|
53
|
+
assert_nil call_unify([1,2,3], [1,3])
|
54
|
+
assert_equal({ :_b => 2 }, call_unify([1,2,3], [1,:_b, 3]))
|
55
|
+
assert_nil call_unify([1,2,3], [:_a, 2, :_a])
|
56
|
+
|
57
|
+
assert_equal({}, call_unify({ a:1, b:2 }, { b:2, a:1 }))
|
58
|
+
assert_equal({ :_a => 2 }, call_unify({ a:1, b: :_a }, { b:2, a:1 }))
|
59
|
+
assert_equal({ :_b => :_a }, call_unify({ a: :_b, b: :_a }, { b: :_b, a: :_a }))
|
60
|
+
assert_equal({ :_a => 1 }, call_unify([1, { :x => :_a }], [:_a, { :x => 1 }]))
|
61
|
+
assert_nil call_unify([1, { :x => :_a }], [:_a, { :x => 2 }])
|
62
|
+
assert_equal({ :_a => 1, :_b => 1}, call_unify([1, :_b], [:_a, :_a]))
|
63
|
+
|
64
|
+
assert_equal({}, call_unify([:_, :_], [1,2]))
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_assertion
|
68
|
+
assert_unifiable(:_a, [1,2,3])
|
69
|
+
|
70
|
+
assert_unifiable(:_a, 1) do |unifier|
|
71
|
+
assert_equal 1, unifier[:_a]
|
72
|
+
end
|
73
|
+
|
74
|
+
# :_ is wildcard
|
75
|
+
assert_unifiable([:_, :_, :_], [1,2,3])
|
76
|
+
|
77
|
+
assert_unifiable({ :created_at => :_a,
|
78
|
+
:updated_at => :_b },
|
79
|
+
{ :created_at => Time.now,
|
80
|
+
:updated_at => Time.now + 1 }) do |unifier|
|
81
|
+
assert unifier[:_a] <= unifier[:_b]
|
82
|
+
end
|
83
|
+
|
84
|
+
# Meta variable pattern can be changed (ML type variable style)
|
85
|
+
assert_unifiable([:"'a", :"'b", :"'_"],
|
86
|
+
[1, 2, 3],
|
87
|
+
"Test message",
|
88
|
+
:meta_pattern => /^'/,
|
89
|
+
:wildcard => :"'_")
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_assertion_failure
|
93
|
+
# 1 and 3 is incompatible
|
94
|
+
assert_raises(MiniTest::Assertion) do
|
95
|
+
assert_unifiable([:_a, :_a], [1, 3])
|
96
|
+
end
|
97
|
+
|
98
|
+
# Time.now and Time.now+1 is incompatible
|
99
|
+
assert_raises(MiniTest::Assertion) do
|
100
|
+
assert_unifiable({ :created_at => :_a,
|
101
|
+
:updated_at => :_a },
|
102
|
+
{ :created_at => Time.now,
|
103
|
+
:updated_at => Time.now + 1 })
|
104
|
+
end
|
105
|
+
|
106
|
+
# There is no ``row'' variable
|
107
|
+
assert_raises(MiniTest::Assertion) do
|
108
|
+
assert_unifiable([:_a, :_b], [1,2,3])
|
109
|
+
end
|
110
|
+
|
111
|
+
# There is no ``row'' variable
|
112
|
+
assert_raises(MiniTest::Assertion) do
|
113
|
+
assert_unifiable({ :_a => 3 }, { :x => :_b })
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_occur_check_skipped
|
118
|
+
# This should be nil because of (absent) occur check will fail.
|
119
|
+
# :_a \in FV( { :x => :_a } ) => cyclic!!
|
120
|
+
#
|
121
|
+
# However, I have not implement it.
|
122
|
+
# This unification implementation is only for testing.
|
123
|
+
# Who on the earth write this kind of test? (meaningless and it will result different from what the programmer expects)
|
124
|
+
#
|
125
|
+
assert_equal({ :_a => { :x => :_a }}, call_unify(:_a, { :x => :_a }))
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "unification_assertion/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "justinf-unification_assertion"
|
7
|
+
s.version = UnificationAssertion::VERSION
|
8
|
+
s.authors = ["Soutaro Matsumoto"]
|
9
|
+
s.email = ["matsumoto@soutaro.com"]
|
10
|
+
s.homepage = "https://github.com/soutaro/unification_assertion"
|
11
|
+
s.summary = "Assertion to test unifiability of two structures"
|
12
|
+
s.description = "UnificationAssertion defines +assert_unifiable+ assertion to test if given two values are unifiable.
|
13
|
+
I only made this gem because Soutaro hadn't pushed it to rubygems yet.
|
14
|
+
"
|
15
|
+
|
16
|
+
s.rubyforge_project = "unification_assertion"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
# specify any dependencies here; for example:
|
24
|
+
# s.add_development_dependency "rspec"
|
25
|
+
# s.add_runtime_dependency "rest-client"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: justinf-unification_assertion
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Soutaro Matsumoto
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-02-29 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: "UnificationAssertion defines +assert_unifiable+ assertion to test if given two values are unifiable.\n\
|
22
|
+
I only made this gem because Soutaro hadn't pushed it to rubygems yet.\n "
|
23
|
+
email:
|
24
|
+
- matsumoto@soutaro.com
|
25
|
+
executables: []
|
26
|
+
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- README.md
|
35
|
+
- Rakefile
|
36
|
+
- lib/unification_assertion.rb
|
37
|
+
- lib/unification_assertion/version.rb
|
38
|
+
- test/unification_assertion_test.rb
|
39
|
+
- unification_assertion.gemspec
|
40
|
+
homepage: https://github.com/soutaro/unification_assertion
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
hash: 3
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project: unification_assertion
|
69
|
+
rubygems_version: 1.8.15
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Assertion to test unifiability of two structures
|
73
|
+
test_files: []
|
74
|
+
|