oktest 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|