oktest 1.0.2 → 1.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.
- checksums.yaml +5 -5
- data/README.md +244 -13
- data/Rakefile.rb +1 -1
- data/lib/oktest.rb +379 -13
- data/oktest.gemspec +2 -2
- data/test/assertion_test.rb +70 -5
- data/test/filter_test.rb +2 -2
- data/test/fixture_test.rb +1 -1
- data/test/generator_test.rb +1 -1
- data/test/helper_test.rb +1 -1
- data/test/initialize.rb +8 -1
- data/test/mainapp_test.rb +31 -5
- data/test/matcher_test.rb +424 -0
- data/test/misc_test.rb +1 -1
- data/test/node_test.rb +1 -1
- data/test/reporter_test.rb +34 -6
- data/test/runner_test.rb +90 -4
- data/test/util_test.rb +1 -1
- data/test/visitor_test.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 240a20810f76b97272c48007e0a8a3193f0217c0aa31d7e9ccf363e05aa577aa
|
4
|
+
data.tar.gz: 035ac9fb077d8f5e67355c9d6a177e67684e11e46db351edc71992f280d4ab08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 333dea58ea3e17034bda9706a426fc63e089fda74ea2b2626915b635b3cdf5e72cce076823b22acb2d7390a189408adf0286eac89650a80939d057f51ba2ff09
|
7
|
+
data.tar.gz: 4136c2c8613ca829d0d72deb8c857995a482e19b0b9c7c282e9a45b3948b9ac3dced2ba665544a98c62d79260e3bbf2da231f577c4221fd23bd81087dea168cf
|
data/README.md
CHANGED
@@ -4,12 +4,13 @@
|
|
4
4
|
|
5
5
|
Oktest.rb is a new-style testing library for Ruby.
|
6
6
|
|
7
|
-
* `ok {actual} == expected` style assertion.
|
8
|
-
*
|
7
|
+
* `ok {actual} == expected` style [assertion](#assertions).
|
8
|
+
* [Fixture injection](#fixture-injection) inspired by dependency injection.
|
9
9
|
* Structured test specifications like RSpec.
|
10
|
-
*
|
10
|
+
* [JSON Matcher](#json-matcher) similar to JSON Schema.
|
11
|
+
* [Filtering](#tag-and-filtering) testcases by pattern or tags.
|
11
12
|
* Blue/red color instead of green/red for accesability.
|
12
|
-
* Small code size (
|
13
|
+
* Small code size (less than 3000 lines) and good performance.
|
13
14
|
|
14
15
|
```ruby
|
15
16
|
### Oktest ### Test::Unit
|
@@ -82,6 +83,11 @@ Oktest.rb requires Ruby 2.3 or later.
|
|
82
83
|
* <a href="#dummy_attrs"><code>dummy_attrs()</code></a>
|
83
84
|
* <a href="#dummy_ivars"><code>dummy_ivars()</code></a>
|
84
85
|
* <a href="#recorder"><code>recorder()</code></a>
|
86
|
+
* <a href="#json-matcher">JSON Matcher</a>
|
87
|
+
* <a href="#simple-example">Simple Example</a>
|
88
|
+
* <a href="#nested-example">Nested Example</a>
|
89
|
+
* <a href="#complex-example">Complex Example</a>
|
90
|
+
* <a href="#helper-methods-for-json-matcher">Helper Methods for JSON Matcher</a>
|
85
91
|
* <a href="#tips">Tips</a>
|
86
92
|
* <a href="#ok--in-minitest"><code>ok {}</code> in MiniTest</a>
|
87
93
|
* <a href="#testing-rack-application">Testing Rack Application</a>
|
@@ -165,6 +171,7 @@ Result:
|
|
165
171
|
|
166
172
|
```terminal
|
167
173
|
$ oktest test/example01_test.rb # or: ruby test/example01_test.rb
|
174
|
+
## test/example01_test.rb
|
168
175
|
* Hello
|
169
176
|
* #hello()
|
170
177
|
- [pass] returns greeting message.
|
@@ -206,6 +213,7 @@ Result:
|
|
206
213
|
|
207
214
|
```terminal
|
208
215
|
$ oktest test/example02_test.rb # or: ruby test/example02_test.rb
|
216
|
+
## test/example02_test.rb
|
209
217
|
* other examples
|
210
218
|
- [Fail] example of assertion failure
|
211
219
|
- [ERROR] example of something error
|
@@ -260,6 +268,7 @@ Result:
|
|
260
268
|
|
261
269
|
```terminal
|
262
270
|
$ oktest test/example03_test.rb # or: ruby test/example03_test.rb
|
271
|
+
## oktest test/example03_test.rb
|
263
272
|
* other examples
|
264
273
|
- [Skip] example of skip (reason: requires Ruby3)
|
265
274
|
- [TODO] example of todo
|
@@ -280,6 +289,7 @@ Verbose mode (default):
|
|
280
289
|
|
281
290
|
```terminal
|
282
291
|
$ oktest test/example01_test.rb -s verbose # or -sv
|
292
|
+
## test/example01_test.rb
|
283
293
|
* Hello
|
284
294
|
* #hello()
|
285
295
|
- [pass] returns greeting message.
|
@@ -291,6 +301,16 @@ Simple mode:
|
|
291
301
|
|
292
302
|
```terminal
|
293
303
|
$ oktest test/example01_test.rb -s simple # or -ss
|
304
|
+
## test/example01_test.rb
|
305
|
+
* Hello:
|
306
|
+
* #hello(): ..
|
307
|
+
## total:2 (pass:2, fail:0, error:0, skip:0, todo:0) in 0.000s
|
308
|
+
```
|
309
|
+
|
310
|
+
Compact mode:
|
311
|
+
|
312
|
+
```terminal
|
313
|
+
$ oktest test/example01_test.rb -s compact # or -sc
|
294
314
|
test/example01_test.rb: ..
|
295
315
|
## total:2 (pass:2, fail:0, error:0, skip:0, todo:0) in 0.000s
|
296
316
|
```
|
@@ -298,7 +318,7 @@ test/example01_test.rb: ..
|
|
298
318
|
Plain mode:
|
299
319
|
|
300
320
|
```terminal
|
301
|
-
$ oktest test/example01_test.rb -s
|
321
|
+
$ oktest test/example01_test.rb -s plain # or -sp
|
302
322
|
..
|
303
323
|
## total:2 (pass:2, fail:0, error:0, skip:0, todo:0) in 0.000s
|
304
324
|
```
|
@@ -324,7 +344,7 @@ How to run test scripts under `test` directory:
|
|
324
344
|
$ ls test/
|
325
345
|
example01_test.rb example02_test.rb example03_test.rb
|
326
346
|
|
327
|
-
$ oktest -s
|
347
|
+
$ oktest -s compact test # or: ruby -r oktest -e 'Oktest.main' -- test -s compact
|
328
348
|
test/example01_test.rb: ..
|
329
349
|
test/example02_test.rb: fE
|
330
350
|
----------------------------------------------------------------------
|
@@ -457,6 +477,7 @@ Result:
|
|
457
477
|
|
458
478
|
```terminal
|
459
479
|
$ ruby test/example05_test.rb
|
480
|
+
## test/example05_test.rb
|
460
481
|
* Integer
|
461
482
|
* #abs()
|
462
483
|
- When value is negative...
|
@@ -1022,8 +1043,8 @@ Oktest.scope do
|
|
1022
1043
|
after { puts "===== Inner: after =====" } # !!!!!
|
1023
1044
|
|
1024
1045
|
spec "example" do
|
1025
|
-
|
1026
|
-
|
1046
|
+
ok {1+1} == 2
|
1047
|
+
end
|
1027
1048
|
|
1028
1049
|
end
|
1029
1050
|
|
@@ -1589,6 +1610,216 @@ end
|
|
1589
1610
|
|
1590
1611
|
|
1591
1612
|
|
1613
|
+
## JSON Matcher
|
1614
|
+
|
1615
|
+
Oktest.rb provides easy way to assert JSON data.
|
1616
|
+
This is very convenient feature, but don't abuse it.
|
1617
|
+
|
1618
|
+
|
1619
|
+
### Simple Example
|
1620
|
+
|
1621
|
+
<!--
|
1622
|
+
test/example41_test.rb:
|
1623
|
+
-->
|
1624
|
+
```ruby
|
1625
|
+
require 'oktest'
|
1626
|
+
require 'set' # !!!!!
|
1627
|
+
|
1628
|
+
Oktest.scope do
|
1629
|
+
topic 'JSON Example' do
|
1630
|
+
|
1631
|
+
spec "simple example" do
|
1632
|
+
actual = {
|
1633
|
+
"name": "Alice",
|
1634
|
+
"id": 1001,
|
1635
|
+
"age": 18,
|
1636
|
+
"email": "alice@example.com",
|
1637
|
+
"gender": "F",
|
1638
|
+
"deleted": false,
|
1639
|
+
"tags": ["aaa", "bbb", "ccc"],
|
1640
|
+
#"twitter": "@alice",
|
1641
|
+
}
|
1642
|
+
## assertion
|
1643
|
+
ok {JSON(actual)} === { # requires `JSON()` and `===`
|
1644
|
+
"name": "Alice", # scalar value
|
1645
|
+
"id": 1000..9999, # range object
|
1646
|
+
"age": Integer, # class object
|
1647
|
+
"email": /^\w+@example\.com$/, # regexp
|
1648
|
+
"gender": Set.new(["M", "F"]), # Set object ("M" or "F")
|
1649
|
+
"deleted": Set.new([true, false]), # boolean (true or false)
|
1650
|
+
"tags": [/^\w+$/].each, # Enumerator object (!= Array obj)
|
1651
|
+
"twitter?": /^@\w+$/, # key 'xxx?' means optional value
|
1652
|
+
}
|
1653
|
+
end
|
1654
|
+
|
1655
|
+
end
|
1656
|
+
end
|
1657
|
+
```
|
1658
|
+
|
1659
|
+
(Note: Ruby 2.4 or older doesn't have `Set#===()`, so above code will occur error
|
1660
|
+
in Ruby 2.4 or older. Please add the folllowing hack in your test script.)
|
1661
|
+
|
1662
|
+
```ruby
|
1663
|
+
require 'set'
|
1664
|
+
unless Set.instance_methods(false).include?(:===) # for Ruby 2.4 or older
|
1665
|
+
class Set; alias === include?; end
|
1666
|
+
end
|
1667
|
+
```
|
1668
|
+
|
1669
|
+
Notice that `Enumerator` has different meaning from `Array` in JSON matcher.
|
1670
|
+
|
1671
|
+
```ruby
|
1672
|
+
actual = {"tags": ["foo", "bar", "baz"]}
|
1673
|
+
|
1674
|
+
## Array
|
1675
|
+
ok {JSON(actual)} == {"tags": ["foo", "bar", "baz"]}
|
1676
|
+
|
1677
|
+
## Enumerator
|
1678
|
+
ok {JSON(actual)} == {"tags": [/^\w+$/].each}
|
1679
|
+
```
|
1680
|
+
|
1681
|
+
|
1682
|
+
### Nested Example
|
1683
|
+
|
1684
|
+
<!--
|
1685
|
+
test/example42_test.rb:
|
1686
|
+
-->
|
1687
|
+
```ruby
|
1688
|
+
require 'oktest'
|
1689
|
+
require 'set' # !!!!!
|
1690
|
+
|
1691
|
+
Oktest.scope do
|
1692
|
+
topic 'JSON Example' do
|
1693
|
+
|
1694
|
+
spec "nested example" do
|
1695
|
+
actual = {
|
1696
|
+
"teams": [
|
1697
|
+
{
|
1698
|
+
"team": "Section 9",
|
1699
|
+
"members": [
|
1700
|
+
{"id": 2500, "name": "Aramaki", "gender": "M"},
|
1701
|
+
{"id": 2501, "name": "Motoko" , "gender": "F"},
|
1702
|
+
{"id": 2502, "name": "Batou" , "gender": "M"},
|
1703
|
+
],
|
1704
|
+
"leader": "Aramaki",
|
1705
|
+
},
|
1706
|
+
{
|
1707
|
+
"team": "SOS Brigade",
|
1708
|
+
"members": [
|
1709
|
+
{"id": 1001, "name": "Haruhi", "gender": "F"},
|
1710
|
+
{"id": 1002, "name": "Mikuru", "gender": "F"},
|
1711
|
+
{"id": 1003, "name": "Yuki" , "gender": "F"},
|
1712
|
+
{"id": 1004, "name": "Itsuki", "gender": "M"},
|
1713
|
+
{"id": 1005, "name": "Kyon" , "gender": "M"},
|
1714
|
+
],
|
1715
|
+
},
|
1716
|
+
],
|
1717
|
+
}
|
1718
|
+
## assertion
|
1719
|
+
ok {JSON(actual)} === { # requires `JSON()` and `===`
|
1720
|
+
"teams": [
|
1721
|
+
{
|
1722
|
+
"team": String,
|
1723
|
+
"members": [
|
1724
|
+
{"id": 1000..9999, "name": String, "gender": Set.new(["M", "F"])}
|
1725
|
+
].each, # Enumerator object (!= Array obj)
|
1726
|
+
"leader?": String, # key 'xxx?' means optional value
|
1727
|
+
}
|
1728
|
+
].each, # Enumerator object (!= Array obj)
|
1729
|
+
}
|
1730
|
+
end
|
1731
|
+
|
1732
|
+
end
|
1733
|
+
end
|
1734
|
+
```
|
1735
|
+
|
1736
|
+
|
1737
|
+
### Complex Example
|
1738
|
+
|
1739
|
+
* `OR(x, y, z)` matches to `x`, `y`, or `z`.
|
1740
|
+
* `AND(x, y, z)` matches to `x`, `y`, and `z`.
|
1741
|
+
* Key `"*"` matches to any key of hash object.
|
1742
|
+
* `Any()` matches to anything.
|
1743
|
+
|
1744
|
+
<!--
|
1745
|
+
test/example43_test.rb:
|
1746
|
+
-->
|
1747
|
+
```ruby
|
1748
|
+
require 'oktest'
|
1749
|
+
require 'set'
|
1750
|
+
|
1751
|
+
Oktest.scope do
|
1752
|
+
topic 'JSON Example' do
|
1753
|
+
|
1754
|
+
spec "OR() example" do
|
1755
|
+
ok {JSON({"val": "123"})} === {"val": OR(String, Integer)} # OR()
|
1756
|
+
ok {JSON({"val": 123 })} === {"val": OR(String, Integer)} # OR()
|
1757
|
+
end
|
1758
|
+
|
1759
|
+
spec "AND() example" do
|
1760
|
+
ok {JSON({"val": "123"})} === {"val": AND(String, /^\d+$/)} # AND()
|
1761
|
+
ok {JSON({"val": 123 })} === {"val": AND(Integer, 1..1000)} # AND()
|
1762
|
+
end
|
1763
|
+
|
1764
|
+
spec "`*` and `ANY` example" do
|
1765
|
+
ok {JSON({"name": "Bob", "age": 20})} === {"*": Any()} # '*' and Any()
|
1766
|
+
end
|
1767
|
+
|
1768
|
+
spec "complex exapmle" do
|
1769
|
+
actual = {
|
1770
|
+
"item": "awesome item",
|
1771
|
+
"colors": ["red", "#cceeff", "green", "#fff"],
|
1772
|
+
"memo": "this is awesome.",
|
1773
|
+
"url": "https://example.com/awesome",
|
1774
|
+
}
|
1775
|
+
## assertion
|
1776
|
+
color_names = ["red", "blue", "green", "white", "black"]
|
1777
|
+
color_pat = /^\#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/
|
1778
|
+
ok {JSON(actual)} === {
|
1779
|
+
"colors": [
|
1780
|
+
AND(String, OR(Set.new(color_names), color_pat)), # AND() and OR()
|
1781
|
+
].each,
|
1782
|
+
"*": Any(), # match to any key (`"*"`) and value (`ANY`)
|
1783
|
+
}
|
1784
|
+
end
|
1785
|
+
|
1786
|
+
end
|
1787
|
+
end
|
1788
|
+
```
|
1789
|
+
|
1790
|
+
(Note: `/^\d+$/` implies String value, and `1..100` implies Integer value.)
|
1791
|
+
|
1792
|
+
```ruby
|
1793
|
+
## No need to write:
|
1794
|
+
## ok {JSON{...}} === {"val": AND(String, /^\d+$/)}
|
1795
|
+
## ok {JSON{...}} === {"val": AND(Integer, 1..100)}
|
1796
|
+
ok {JSON({"val": "A"})} === {"val": /^\d+$/} # implies String value
|
1797
|
+
ok {JSON({"val": 99 })} === {"val": 1..100} # implies Integer value
|
1798
|
+
```
|
1799
|
+
|
1800
|
+
|
1801
|
+
### Helper Methods for JSON Matcher
|
1802
|
+
|
1803
|
+
Oktest.rb provides some helper methods and objects:
|
1804
|
+
|
1805
|
+
* `Enum(x, y, z)` is almost same as `Set.new([x, y, z])`.
|
1806
|
+
* `Bool()` is same as `Enum(true, false)`.
|
1807
|
+
* `Length(3)` matches to length 3, and `Length(1..3)` matches to length 1..3.
|
1808
|
+
|
1809
|
+
<!--
|
1810
|
+
test/example44_test.rb:
|
1811
|
+
-->
|
1812
|
+
```ruby
|
1813
|
+
actual = {"gender": "M", "deleted": false, "code": "ABCD1234"}
|
1814
|
+
ok {JSON(actual)} == {
|
1815
|
+
"gender": Enum("M", "F"), # same as Set.new(["M", "F"])
|
1816
|
+
"deleted": Bool(), # same as Enum(true, false)
|
1817
|
+
"code": Length(6..10), # code length should be 6..10
|
1818
|
+
}
|
1819
|
+
```
|
1820
|
+
|
1821
|
+
|
1822
|
+
|
1592
1823
|
## Tips
|
1593
1824
|
|
1594
1825
|
|
@@ -1597,7 +1828,7 @@ end
|
|
1597
1828
|
If you want to use `ok {actual} == expected` style assertion in MiniTest,
|
1598
1829
|
install `minitest-ok` gem instead of `otest` gem.
|
1599
1830
|
|
1600
|
-
test/
|
1831
|
+
test/example51_test.rb:
|
1601
1832
|
|
1602
1833
|
```ruby
|
1603
1834
|
require 'minitest/spec'
|
@@ -1620,7 +1851,7 @@ See [minitest-ok README](https://github.com/kwatch/minitest-ok) for details.
|
|
1620
1851
|
|
1621
1852
|
`rack-test_app` gem will help you to test Rack application very well.
|
1622
1853
|
|
1623
|
-
test/
|
1854
|
+
test/example52_test.rb:
|
1624
1855
|
|
1625
1856
|
```ruby
|
1626
1857
|
require 'rack'
|
@@ -1693,7 +1924,7 @@ end
|
|
1693
1924
|
|
1694
1925
|
Oktest.rb provides `Traverser` class which implements Visitor pattern.
|
1695
1926
|
|
1696
|
-
test/
|
1927
|
+
test/example54_test.rb:
|
1697
1928
|
|
1698
1929
|
```ruby
|
1699
1930
|
require 'oktest'
|
@@ -1750,8 +1981,8 @@ MyTraverser.new.start()
|
|
1750
1981
|
Result:
|
1751
1982
|
|
1752
1983
|
```terminal
|
1753
|
-
$ ruby test/
|
1754
|
-
# scope: test/
|
1984
|
+
$ ruby test/example54_test.rb
|
1985
|
+
# scope: test/example54_test.rb
|
1755
1986
|
+ topic: Example Topic
|
1756
1987
|
- spec: sample #1
|
1757
1988
|
- spec: sample #2
|
data/Rakefile.rb
CHANGED
data/lib/oktest.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
|
3
3
|
###
|
4
|
-
### $Release: 1.0
|
4
|
+
### $Release: 1.1.0 $
|
5
5
|
### $Copyright: copyright(c) 2011-2021 kuwata-lab.com all rights reserved $
|
6
6
|
### $License: MIT License $
|
7
7
|
###
|
8
8
|
|
9
|
+
require 'set'
|
10
|
+
|
9
11
|
|
10
12
|
module Oktest
|
11
13
|
|
12
14
|
|
13
|
-
VERSION = '$Release: 1.0
|
15
|
+
VERSION = '$Release: 1.1.0 $'.split()[1]
|
14
16
|
|
15
17
|
|
16
18
|
class OktestError < StandardError
|
@@ -114,6 +116,10 @@ module Oktest
|
|
114
116
|
|
115
117
|
def ===(expected)
|
116
118
|
__done()
|
119
|
+
#; [!mjh4d] raises error when combination of 'not_ok()' and matcher object.
|
120
|
+
if @bool == false && @actual.is_a?(Matcher)
|
121
|
+
raise OktestError, "negative `===` is not available with matcher object."
|
122
|
+
end
|
117
123
|
#; [!42f6a] raises assertion error when failed.
|
118
124
|
#; [!vhvyu] is avaialbe with NOT.
|
119
125
|
__assert(@bool == (@actual === expected)) {
|
@@ -264,10 +270,10 @@ module Oktest
|
|
264
270
|
|
265
271
|
def raise!(errcls=nil, errmsg=nil, &b)
|
266
272
|
#; [!8k6ee] compares error class by '.is_a?' instead of '=='.
|
267
|
-
return raise?(errcls, errmsg,
|
273
|
+
return raise?(errcls, errmsg, _subclass: true, &b)
|
268
274
|
end
|
269
275
|
|
270
|
-
def raise?(errcls=nil, errmsg=nil,
|
276
|
+
def raise?(errcls=nil, errmsg=nil, _subclass: false, &b)
|
271
277
|
__done()
|
272
278
|
#; [!2rnni] 1st argument can be error message string or rexp.
|
273
279
|
if errmsg.nil? && ! errcls.nil? && ! (errcls.is_a?(Class) && errcls <= Exception)
|
@@ -289,8 +295,8 @@ module Oktest
|
|
289
295
|
#; [!lq6jv] compares error class with '==' operator, not '.is_a?'.
|
290
296
|
elsif exc.class == errcls # not `exc.is_a?(errcls)`
|
291
297
|
nil
|
292
|
-
#; [!hwg0z] compares error class with '.is_a?' if '
|
293
|
-
elsif
|
298
|
+
#; [!hwg0z] compares error class with '.is_a?' if '_subclass: true' specified.
|
299
|
+
elsif _subclass && exc.class < errcls
|
294
300
|
nil
|
295
301
|
#; [!4n3ed] reraises if exception is not matched to specified error class.
|
296
302
|
else
|
@@ -328,8 +334,8 @@ module Oktest
|
|
328
334
|
#; [!smprc] compares error class with '==' operator, not '.is_a?'.
|
329
335
|
elsif exc.class == errcls # not `exc.is_a?(errcls)`
|
330
336
|
__assert(false) { "#{errcls.inspect} should not be raised but got #{exc.inspect}." }
|
331
|
-
#; [!34nd8] compares error class with '.is_a?' if '
|
332
|
-
elsif
|
337
|
+
#; [!34nd8] compares error class with '.is_a?' if '_subclass: true' specified.
|
338
|
+
elsif _subclass && exc.class < errcls
|
333
339
|
__assert(false) { "#{errcls.inspect} should not be raised but got #{exc.inspect}." }
|
334
340
|
#; [!shxne] reraises exception if different from specified error class.
|
335
341
|
else
|
@@ -548,6 +554,246 @@ module Oktest
|
|
548
554
|
end
|
549
555
|
|
550
556
|
|
557
|
+
class Matcher
|
558
|
+
|
559
|
+
def initialize(actual)
|
560
|
+
@actual = actual
|
561
|
+
end
|
562
|
+
|
563
|
+
def ===(expected)
|
564
|
+
#; [!spybn] raises NotImplementedError.
|
565
|
+
raise NotImplementedError.new("#{self.class.name}#===(): not implemented yet.")
|
566
|
+
end
|
567
|
+
|
568
|
+
def ==(expected)
|
569
|
+
#; [!ymt1b] raises OktestError.
|
570
|
+
raise OktestError, "JSON(): use `===` instead of `==`."
|
571
|
+
end
|
572
|
+
|
573
|
+
def fail(errmsg)
|
574
|
+
#; [!8qpsd] raises assertion error.
|
575
|
+
raise Oktest::FAIL_EXCEPTION, errmsg
|
576
|
+
end
|
577
|
+
|
578
|
+
end
|
579
|
+
|
580
|
+
|
581
|
+
class JsonMatcher < Matcher
|
582
|
+
|
583
|
+
def ===(expected)
|
584
|
+
#; [!4uf1o] raises assertion error when JSON not matched.
|
585
|
+
_compare([], @actual, expected)
|
586
|
+
#; [!0g0u4] returns true when JSON matched.
|
587
|
+
return true
|
588
|
+
end
|
589
|
+
|
590
|
+
private
|
591
|
+
|
592
|
+
def _compare?(path, a, e)
|
593
|
+
#; [!nkvqo] returns true when nothing raised.
|
594
|
+
#; [!57m2j] returns false when assertion error raised.
|
595
|
+
_compare(path, a, e)
|
596
|
+
return true
|
597
|
+
rescue FAIL_EXCEPTION
|
598
|
+
return false
|
599
|
+
end
|
600
|
+
|
601
|
+
def _compare(path, a, e)
|
602
|
+
if a.is_a?(Hash) && e.is_a?(Hash)
|
603
|
+
_compare_hash(path, a, e)
|
604
|
+
elsif a.is_a?(Array) && e.is_a?(Array)
|
605
|
+
_compare_array(path, a, e)
|
606
|
+
elsif e.is_a?(Enumerator)
|
607
|
+
_compare_enumerator(path, a, e)
|
608
|
+
elsif e.is_a?(OR)
|
609
|
+
_compare_or(path, a, e)
|
610
|
+
elsif e.is_a?(AND)
|
611
|
+
_compare_and(path, a, e)
|
612
|
+
else
|
613
|
+
_compare_value(path, a, e)
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
def _compare_value(path, a, e)
|
618
|
+
#; [!1ukbv] scalar value matches to integer, string, bool, and so son.
|
619
|
+
#; [!8o55d] class object matches to instance object.
|
620
|
+
#; [!s625d] regexp object matches to string value.
|
621
|
+
#; [!aqkk0] range object matches to scalar value.
|
622
|
+
#; [!a7bfs] Set object matches to enum value.
|
623
|
+
e === a or fail <<"END"
|
624
|
+
$<JSON>#{_path(path)}: $<expected> === $<actual> : failed.
|
625
|
+
$<actual>: #{a.inspect}
|
626
|
+
$<expected>: #{e.inspect}
|
627
|
+
END
|
628
|
+
#; [!4ymj2] fails when actual value is not matched to item class of range object.
|
629
|
+
if e.is_a?(Range)
|
630
|
+
expected_class = (e.begin || e.end).class
|
631
|
+
a.is_a?(expected_class) or fail <<"END"
|
632
|
+
$<JSON>#{_path(path)}: expected #{expected_class.name} value, but got #{a.class.name} value.
|
633
|
+
$<actual>: #{a.inspect}
|
634
|
+
$<expected>: #{e.inspect}
|
635
|
+
END
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
def _compare_array(path, a, e)
|
640
|
+
#; [!bz74w] fails when array lengths are different.
|
641
|
+
a.length == e.length or fail <<"END"
|
642
|
+
$<JSON>#{_path(path)}: $<actual>.length == $<expected>.length : failed.
|
643
|
+
$<actual>.length: #{a.length}
|
644
|
+
$<expected>.length: #{e.length}
|
645
|
+
$<actual>: #{a.inspect}
|
646
|
+
$<expected>: #{e.inspect}
|
647
|
+
END
|
648
|
+
#; [!lh6d6] compares array items recursively.
|
649
|
+
path.push(nil)
|
650
|
+
i = -1
|
651
|
+
a.zip(e) do |a2, e2|
|
652
|
+
path[-1] = (i += 1)
|
653
|
+
_compare(path, a2, e2)
|
654
|
+
end
|
655
|
+
path.pop()
|
656
|
+
end
|
657
|
+
|
658
|
+
def _compare_hash(path, a, e)
|
659
|
+
#; [!rkv0z] compares two hashes with converting keys into string.
|
660
|
+
a2 = {}; a.each {|k, v| a2[k.to_s] = v }
|
661
|
+
e2 = {}; e.each {|k, v| e2[k.to_s] = v }
|
662
|
+
#; [!fmxyg] compares hash objects recursively.
|
663
|
+
path.push(nil)
|
664
|
+
a2.each_key do |k|
|
665
|
+
path[-1] = k
|
666
|
+
if e2.key?(k)
|
667
|
+
_compare(path, a2[k], e2[k])
|
668
|
+
#; [!jbyv6] key 'aaa?' represents optional key.
|
669
|
+
elsif e2.key?("#{k}?")
|
670
|
+
_compare(path, a2[k], e2["#{k}?"]) unless a2[k].nil?
|
671
|
+
#; [!uc4ag] key '*' matches to any key name.
|
672
|
+
elsif e2.key?("*")
|
673
|
+
_compare(path, a2[k], e2["*"])
|
674
|
+
#; [!mpbvu] fails when unexpected key exists in actual hash.
|
675
|
+
else
|
676
|
+
fail <<"END"
|
677
|
+
$<JSON>#{_path(path)}: unexpected key.
|
678
|
+
$<actual>: #{a2[k].inspect}
|
679
|
+
END
|
680
|
+
end
|
681
|
+
end
|
682
|
+
path.pop()
|
683
|
+
#; [!4oasq] fails when expected key not exist in actual hash.
|
684
|
+
(e2.keys - a2.keys).each do |k|
|
685
|
+
k =~ /\?\z/ || k == "*" or fail <<"END"
|
686
|
+
$<JSON>#{_path(path)}: key \"#{k}\" expected but not found.
|
687
|
+
$<actual>.keys: #{a2.keys.sort.inspect[1...-1]}
|
688
|
+
$<expected>.keys: #{e2.keys.sort.inspect[1...-1]}
|
689
|
+
END
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
def _compare_enumerator(path, a, e)
|
694
|
+
#; [!ljrmc] fails when expected is an Enumerator object and actual is not an array.
|
695
|
+
e2 = e.first
|
696
|
+
a.is_a?(Array) or fail <<"END"
|
697
|
+
$<JSON>#{_path(path)}: Array value expected but got #{a.class.name} value.
|
698
|
+
$<actual>: #{a.inspect}
|
699
|
+
$<expected>: [#{e2.inspect}].each
|
700
|
+
END
|
701
|
+
#; [!sh5cg] Enumerator object matches to repeat of rule.
|
702
|
+
path.push(nil)
|
703
|
+
a.each_with_index do |a2, i|
|
704
|
+
path[-1] = i
|
705
|
+
_compare(path, a2, e2)
|
706
|
+
end
|
707
|
+
path.pop()
|
708
|
+
end
|
709
|
+
|
710
|
+
def _compare_or(path, a, e)
|
711
|
+
#; [!eqr3b] `OR()` matches to any of arguments.
|
712
|
+
#; [!5ybfg] `OR()` can contain `AND()`.
|
713
|
+
passed = e.items.any? {|e2| _compare?(path, a, e2) }
|
714
|
+
passed or fail <<"END"
|
715
|
+
$<JSON>#{_path(path)}: $<expected> === $<actual> : failed.
|
716
|
+
$<actual>: #{a.inspect}
|
717
|
+
$<expected>: OR(#{e.items.collect(&:inspect).join(', ')})
|
718
|
+
END
|
719
|
+
end
|
720
|
+
|
721
|
+
def _compare_and(path, a, e)
|
722
|
+
#; [!4hk96] `AND()` matches to all of arguments.
|
723
|
+
#; [!scx22] `AND()` can contain `OR()`.
|
724
|
+
failed = e.items.find {|e2| ! _compare?(path, a, e2) }
|
725
|
+
! failed or fail <<"END"
|
726
|
+
$<JSON>#{_path(path)}: $<expected> === $<actual> : failed.
|
727
|
+
$<actual>: #{a.inspect}
|
728
|
+
$<expected>: AND(#{failed.inspect})
|
729
|
+
END
|
730
|
+
end
|
731
|
+
|
732
|
+
def _path(path)
|
733
|
+
#return path.collect {|x| "/#{x}" }.join()
|
734
|
+
return path.collect {|x| "[#{x.inspect}]" }.join()
|
735
|
+
end
|
736
|
+
|
737
|
+
protected
|
738
|
+
|
739
|
+
class OR
|
740
|
+
def initialize(*items)
|
741
|
+
@items = items
|
742
|
+
end
|
743
|
+
attr_reader :items
|
744
|
+
def inspect()
|
745
|
+
#; [!2mu33] returns 'OR(...)' string.
|
746
|
+
return "OR(#{@items.collect(&:inspect).join(', ')})"
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
class AND
|
751
|
+
def initialize(*items)
|
752
|
+
@items = items
|
753
|
+
end
|
754
|
+
attr_reader :items
|
755
|
+
def inspect()
|
756
|
+
#; [!w43ag] returns 'AND(...)' string.
|
757
|
+
return "AND(#{@items.collect(&:inspect).join(', ')})"
|
758
|
+
end
|
759
|
+
end
|
760
|
+
|
761
|
+
class Enum < Set
|
762
|
+
alias === include? # Ruby 2.4 or older doesn't have 'Set#==='.
|
763
|
+
def inspect()
|
764
|
+
#; [!fam11] returns 'Enum(...)' string.
|
765
|
+
return "Enum(#{self.collect(&:inspect).join(', ')})"
|
766
|
+
end
|
767
|
+
end
|
768
|
+
|
769
|
+
class Length
|
770
|
+
def initialize(expected)
|
771
|
+
@expected = expected
|
772
|
+
end
|
773
|
+
def ===(actual)
|
774
|
+
#; [!03ozi] compares length of actual value with expected value.
|
775
|
+
return @expected === actual.length
|
776
|
+
end
|
777
|
+
def inspect()
|
778
|
+
#; [!nwv3e] returns 'Length(n)' string.
|
779
|
+
return "Length(#{@expected.inspect})"
|
780
|
+
end
|
781
|
+
end
|
782
|
+
|
783
|
+
class Any
|
784
|
+
def ===(actual)
|
785
|
+
#; [!mzion] returns true in any case.
|
786
|
+
true
|
787
|
+
end
|
788
|
+
def inspect()
|
789
|
+
#; [!6f0yv] returns 'Any()' string.
|
790
|
+
return "Any()"
|
791
|
+
end
|
792
|
+
end
|
793
|
+
|
794
|
+
end
|
795
|
+
|
796
|
+
|
551
797
|
class Context
|
552
798
|
## * Context class is separated from ScopeNode, TopicNode, and SpecLeaf.
|
553
799
|
## * `topic()` and `spec()` creates subclass of Context class,
|
@@ -1116,6 +1362,41 @@ module Oktest
|
|
1116
1362
|
return Benry::Recorder.new
|
1117
1363
|
end
|
1118
1364
|
|
1365
|
+
def JSON(actual)
|
1366
|
+
#; [!n0k03] creates JsonMatcher object.
|
1367
|
+
return JsonMatcher.new(actual)
|
1368
|
+
end
|
1369
|
+
|
1370
|
+
def Enum(*values)
|
1371
|
+
#; [!fbfr0] creates Enum object which is a subclass of Set.
|
1372
|
+
return JsonMatcher::Enum.new(values)
|
1373
|
+
end
|
1374
|
+
|
1375
|
+
def Bool()
|
1376
|
+
#; [!vub5j] creates a set of true and false.
|
1377
|
+
return Enum(true, false)
|
1378
|
+
end
|
1379
|
+
|
1380
|
+
def OR(*args)
|
1381
|
+
#; [!9e8im] creates `OR` object.
|
1382
|
+
return JsonMatcher::OR.new(*args)
|
1383
|
+
end
|
1384
|
+
|
1385
|
+
def AND(*args)
|
1386
|
+
#; [!38jln] creates `AND` object.
|
1387
|
+
return JsonMatcher::AND.new(*args)
|
1388
|
+
end
|
1389
|
+
|
1390
|
+
def Length(n)
|
1391
|
+
#; [!qqas3] creates Length object.
|
1392
|
+
return JsonMatcher::Length.new(n)
|
1393
|
+
end
|
1394
|
+
|
1395
|
+
def Any()
|
1396
|
+
#; [!dlo1o] creates an 'Any' object.
|
1397
|
+
return JsonMatcher::Any.new
|
1398
|
+
end
|
1399
|
+
|
1119
1400
|
end
|
1120
1401
|
|
1121
1402
|
|
@@ -1215,11 +1496,25 @@ module Oktest
|
|
1215
1496
|
@reporter.exit_all(self)
|
1216
1497
|
end
|
1217
1498
|
|
1499
|
+
def _spec_first(node)
|
1500
|
+
a1, a2 = node.each_child.partition {|c|
|
1501
|
+
c.is_a?(SpecLeaf) || (c.is_a?(TopicNode) && c._prefix != '*')
|
1502
|
+
}
|
1503
|
+
return a1+a2
|
1504
|
+
end
|
1505
|
+
private :_spec_first
|
1506
|
+
|
1218
1507
|
def visit_scope(scope, depth, parent)
|
1219
1508
|
@reporter.enter_scope(scope) unless scope.equal?(THE_GLOBAL_SCOPE)
|
1220
1509
|
#; [!5anr7] calls before_all and after_all blocks.
|
1221
1510
|
call_before_all_block(scope)
|
1222
|
-
|
1511
|
+
#; [!c5cw0] run specs and case_when in advance of specs and topics when SimpleReporter.
|
1512
|
+
if @reporter.order_policy() == :spec_first
|
1513
|
+
_spec_first(scope).each {|c| c.accept_visitor(self, depth+1, scope) }
|
1514
|
+
else
|
1515
|
+
scope.each_child {|c| c.accept_visitor(self, depth+1, scope) }
|
1516
|
+
end
|
1517
|
+
#
|
1223
1518
|
call_after_all_block(scope)
|
1224
1519
|
@reporter.exit_scope(scope) unless scope.equal?(THE_GLOBAL_SCOPE)
|
1225
1520
|
end
|
@@ -1228,7 +1523,13 @@ module Oktest
|
|
1228
1523
|
@reporter.enter_topic(topic, depth)
|
1229
1524
|
#; [!i3yfv] calls 'before_all' and 'after_all' blocks.
|
1230
1525
|
call_before_all_block(topic)
|
1231
|
-
|
1526
|
+
#; [!p3a5o] run specs and case_when in advance of specs and topics when SimpleReporter.
|
1527
|
+
if @reporter.order_policy() == :spec_first
|
1528
|
+
_spec_first(topic).each {|c| c.accept_visitor(self, depth+1, topic) }
|
1529
|
+
else
|
1530
|
+
topic.each_child {|c| c.accept_visitor(self, depth+1, topic) }
|
1531
|
+
end
|
1532
|
+
#
|
1232
1533
|
call_after_all_block(topic)
|
1233
1534
|
@reporter.exit_topic(topic, depth)
|
1234
1535
|
end
|
@@ -1429,6 +1730,7 @@ module Oktest
|
|
1429
1730
|
def exit_spec(spec, depth, status, error, parent); end
|
1430
1731
|
#
|
1431
1732
|
def counts; {}; end
|
1733
|
+
def order_policy(); nil; end # :spec_first or nil
|
1432
1734
|
|
1433
1735
|
end
|
1434
1736
|
|
@@ -1439,7 +1741,7 @@ module Oktest
|
|
1439
1741
|
CHARS = { :PASS=>'.', :FAIL=>'f', :ERROR=>'E', :SKIP=>'s', :TODO=>'t' }
|
1440
1742
|
|
1441
1743
|
|
1442
|
-
def initialize
|
1744
|
+
def initialize()
|
1443
1745
|
@exceptions = []
|
1444
1746
|
@counts = {}
|
1445
1747
|
end
|
@@ -1582,6 +1884,10 @@ module Oktest
|
|
1582
1884
|
|
1583
1885
|
LABELS = { :PASS=>'pass', :FAIL=>'Fail', :ERROR=>'ERROR', :SKIP=>'Skip', :TODO=>'TODO' }
|
1584
1886
|
|
1887
|
+
def enter_scope(scope)
|
1888
|
+
puts "## #{scope.filename}"
|
1889
|
+
end
|
1890
|
+
|
1585
1891
|
def enter_topic(topic, depth)
|
1586
1892
|
super
|
1587
1893
|
puts "#{' ' * (depth - 1)}#{topic._prefix} #{Color.topic(topic.target)}"
|
@@ -1615,6 +1921,64 @@ module Oktest
|
|
1615
1921
|
|
1616
1922
|
|
1617
1923
|
class SimpleReporter < BaseReporter
|
1924
|
+
#; [!jxa1b] reports topics and progress.
|
1925
|
+
|
1926
|
+
def initialize()
|
1927
|
+
super
|
1928
|
+
@_nl = true
|
1929
|
+
end
|
1930
|
+
|
1931
|
+
def order_policy()
|
1932
|
+
:spec_first
|
1933
|
+
end
|
1934
|
+
|
1935
|
+
def _nl()
|
1936
|
+
(puts(); @_nl = true) unless @_nl
|
1937
|
+
end
|
1938
|
+
private :_nl
|
1939
|
+
|
1940
|
+
def _nl_off()
|
1941
|
+
@_nl = false
|
1942
|
+
end
|
1943
|
+
private :_nl_off
|
1944
|
+
|
1945
|
+
def enter_scope(scope)
|
1946
|
+
_nl()
|
1947
|
+
puts "## #{scope.filename}"
|
1948
|
+
end
|
1949
|
+
|
1950
|
+
def exit_scope(scope)
|
1951
|
+
_nl()
|
1952
|
+
print_exceptions()
|
1953
|
+
end
|
1954
|
+
|
1955
|
+
def enter_topic(topic, depth)
|
1956
|
+
super
|
1957
|
+
return if topic._prefix == '-'
|
1958
|
+
_nl()
|
1959
|
+
print "#{' ' * (depth - 1)}#{topic._prefix} #{Color.topic(topic.target)}: "
|
1960
|
+
$stdout.flush()
|
1961
|
+
_nl_off()
|
1962
|
+
end
|
1963
|
+
|
1964
|
+
def exit_topic(topic, depth)
|
1965
|
+
super
|
1966
|
+
return if topic._prefix == '-'
|
1967
|
+
_nl()
|
1968
|
+
print_exceptions()
|
1969
|
+
end
|
1970
|
+
|
1971
|
+
def exit_spec(spec, depth, status, error, parent)
|
1972
|
+
super
|
1973
|
+
print Color.status(status, CHARS[status] || '?')
|
1974
|
+
$stdout.flush
|
1975
|
+
_nl_off()
|
1976
|
+
end
|
1977
|
+
|
1978
|
+
end
|
1979
|
+
|
1980
|
+
|
1981
|
+
class CompactReporter < BaseReporter
|
1618
1982
|
#; [!xfd5o] reports filename.
|
1619
1983
|
|
1620
1984
|
def enter_scope(scope)
|
@@ -1681,6 +2045,7 @@ module Oktest
|
|
1681
2045
|
REPORTER_CLASSES = {
|
1682
2046
|
'verbose' => VerboseReporter, 'v' => VerboseReporter,
|
1683
2047
|
'simple' => SimpleReporter, 's' => SimpleReporter,
|
2048
|
+
'compact' => CompactReporter, 'c' => CompactReporter,
|
1684
2049
|
'plain' => PlainReporter, 'p' => PlainReporter,
|
1685
2050
|
'quiet' => QuietReporter, 'q' => QuietReporter,
|
1686
2051
|
}
|
@@ -2185,7 +2550,8 @@ END
|
|
2185
2550
|
Config.auto_run = false
|
2186
2551
|
#; [!18qpe] runs test scripts.
|
2187
2552
|
#; [!0qd92] '-s verbose' or '-sv' option prints test results in verbose mode.
|
2188
|
-
#; [!
|
2553
|
+
#; [!zfdr5] '-s simple' or '-ss' option prints test results in simple mode.
|
2554
|
+
#; [!ef5v7] '-s compact' or '-sc' option prints test results in compact mode.
|
2189
2555
|
#; [!244te] '-s plain' or '-sp' option prints test results in plain mode.
|
2190
2556
|
#; [!ai61w] '-s quiet' or '-sq' option prints test results in quiet mode.
|
2191
2557
|
n_errors = Oktest.run(:style=>opts.style)
|
@@ -2246,7 +2612,7 @@ END
|
|
2246
2612
|
Usage: %{command} [<options>] [<file-or-directory>...]
|
2247
2613
|
-h, --help : show help
|
2248
2614
|
--version : print version
|
2249
|
-
-s <STYLE>
|
2615
|
+
-s <REPORT-STYLE> : verbose/simple/compact/plain/quiet, or v/s/c/p/q
|
2250
2616
|
-F <PATTERN> : filter topic or spec with pattern (see below)
|
2251
2617
|
--color[={on|off}] : enable/disable output coloring forcedly
|
2252
2618
|
-C, --create : print test code skeleton
|