hobby-test 0.0.0 → 0.0.1
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 +4 -4
- data/.gitignore +1 -0
- data/Gemfile +15 -0
- data/debug.rb +7 -0
- data/hobby-test.gemspec +10 -0
- data/lib/hobby/test/exchange/assert.rb +62 -0
- data/lib/hobby/test/exchange/request.rb +16 -0
- data/lib/hobby/test/exchange.rb +31 -0
- data/lib/hobby/test/report.rb +17 -0
- data/lib/hobby/test.rb +28 -0
- data/spec/apps/main.rb +69 -0
- data/spec/auto_spec.rb +20 -0
- data/spec/helper.rb +30 -0
- data/spec/http/basic.yml +14 -0
- data/spec/http/event.yml +11 -0
- data/spec/http/reverse.yml +24 -0
- data/spec/run_mutant.rb +70 -0
- data/spec/setup/mutant.rb +24 -0
- data/spec/setup/power_assert.rb +19 -0
- data/spec/tcp_spec.rb +28 -0
- data/spec/yml/failing/0.yml +12 -0
- data/spec/yml/passing/0.yml +12 -0
- data/spec/yml/passing/chain_assertions.yml +36 -0
- data/spec/yml/passing/counter.yml +11 -0
- data/spec/yml/passing/echo.yml +23 -0
- data/spec/yml/passing/query.yml +8 -0
- data/useful_links +3 -0
- metadata +28 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6630ab85ce09d1fbd6661bd8ec6201e66a73c197
|
4
|
+
data.tar.gz: 7547c087095c379e16aafe99a62fb72dddedfba6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f2d0e876e7b6e10990418aa0318aeca79189b30f9953eb24c50fe2d212110a0c4603e5d00a5d061bb9a01f3f18219c11a07369dbbd91c6fcbb7149c3b745fa7
|
7
|
+
data.tar.gz: 6f4c9b2756eb6fd613ed6ef211a208f5ef78eb74aa81312f81da11e0d5715dc8b86da986ab0aa2cd745e3a2d4ecc6bc1f27c14d2eff4bdc92a86083d9502d141
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gemfile.lock
|
data/Gemfile
ADDED
data/debug.rb
ADDED
data/hobby-test.gemspec
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Gem::Specification.new do |g|
|
2
|
+
g.name = 'hobby-test'
|
3
|
+
g.files = `git ls-files`.split($/)
|
4
|
+
g.version = '0.0.1'
|
5
|
+
g.summary = 'A way to test HTTP exchanges via YAML specifications'
|
6
|
+
g.authors = ['Anatoly Chernow']
|
7
|
+
|
8
|
+
g.add_dependency 'excon'
|
9
|
+
g.add_dependency 'to_proc'
|
10
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class Hobby::Test::Exchange
|
2
|
+
module Assert
|
3
|
+
def self.[] pair
|
4
|
+
key, delimiter, chain = pair[0].partition /\.|\[/
|
5
|
+
chain.prepend (delimiter == '[' ? 'self[' : 'self.') unless chain.empty?
|
6
|
+
const_get(key.capitalize).new key, chain, pair[1]
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize key, chain, value
|
10
|
+
@key, @chain, @specified_value = key, chain, value
|
11
|
+
end
|
12
|
+
|
13
|
+
def ok?
|
14
|
+
@ok
|
15
|
+
end
|
16
|
+
|
17
|
+
def [] response
|
18
|
+
dup.assert response
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :actual_value, :specified_value, :chain, :key
|
22
|
+
|
23
|
+
class Status
|
24
|
+
include Assert
|
25
|
+
|
26
|
+
def assert response
|
27
|
+
@actual_value = response.public_send key
|
28
|
+
@ok = actual_value == specified_value
|
29
|
+
self
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Body
|
34
|
+
include Assert
|
35
|
+
|
36
|
+
def assert response
|
37
|
+
@actual_value = response.public_send key
|
38
|
+
@actual_value = begin
|
39
|
+
JSON.parse actual_value
|
40
|
+
rescue JSON::ParserError
|
41
|
+
actual_value
|
42
|
+
end
|
43
|
+
|
44
|
+
@ok = if chain.empty?
|
45
|
+
actual_value == specified_value
|
46
|
+
else
|
47
|
+
compare_chain
|
48
|
+
end
|
49
|
+
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def compare_chain
|
54
|
+
if chain.end_with? '>', '=', '<'
|
55
|
+
actual_value.instance_eval "#{chain}(#{specified_value})"
|
56
|
+
else
|
57
|
+
(actual_value.instance_eval chain) == specified_value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Hobby::Test::Exchange
|
2
|
+
class Request < OpenStruct
|
3
|
+
VERBS = %w[delete get head options patch post put]
|
4
|
+
def initialize array
|
5
|
+
@verb = array[0]
|
6
|
+
super array[1]
|
7
|
+
end
|
8
|
+
attr_reader :verb
|
9
|
+
|
10
|
+
def to_hash
|
11
|
+
hash = to_h
|
12
|
+
hash[:body] = hash[:body].to_json
|
13
|
+
hash
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Hobby
|
2
|
+
class Test
|
3
|
+
class Exchange
|
4
|
+
def initialize hash
|
5
|
+
@request = Request.new hash.find { |key, _|
|
6
|
+
Request::VERBS.include? key
|
7
|
+
}
|
8
|
+
@asserts = (hash['response']&.map &Assert) || []
|
9
|
+
end
|
10
|
+
attr_reader :request, :asserts
|
11
|
+
|
12
|
+
def [] connection
|
13
|
+
dup.run_against connection
|
14
|
+
end
|
15
|
+
|
16
|
+
def ok?
|
17
|
+
asserts.all? &:ok?
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
def run_against connection
|
22
|
+
response = connection.public_send request.verb, **request
|
23
|
+
@asserts = asserts.map &[response]
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'hobby/test/exchange/request'
|
31
|
+
require 'hobby/test/exchange/assert'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Hobby
|
2
|
+
class Test
|
3
|
+
class Report
|
4
|
+
def initialize exchanges
|
5
|
+
@exchanges = exchanges
|
6
|
+
end
|
7
|
+
|
8
|
+
def ok?
|
9
|
+
@exchanges.all? &:ok?
|
10
|
+
end
|
11
|
+
|
12
|
+
include Enumerable
|
13
|
+
extend Forwardable
|
14
|
+
delegate [:each, :[], :size] => :@exchanges
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/hobby/test.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'to_proc/all'
|
2
|
+
require 'excon'
|
3
|
+
|
4
|
+
require 'yaml'
|
5
|
+
require 'json'
|
6
|
+
require 'forwardable'
|
7
|
+
require 'ostruct'
|
8
|
+
|
9
|
+
require 'hobby/test/exchange'
|
10
|
+
require 'hobby/test/report'
|
11
|
+
|
12
|
+
module Hobby
|
13
|
+
class Test
|
14
|
+
def initialize file
|
15
|
+
@exchanges = (YAML.load_file file).map &Exchange
|
16
|
+
end
|
17
|
+
|
18
|
+
def [] address
|
19
|
+
connection = if address.start_with? 'http'
|
20
|
+
Excon.new address
|
21
|
+
else
|
22
|
+
Excon.new 'unix:///', socket: address
|
23
|
+
end
|
24
|
+
|
25
|
+
Report.new @exchanges.map &[connection]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/spec/apps/main.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'hobby'
|
2
|
+
require 'hobby/json'
|
3
|
+
|
4
|
+
class MainApp
|
5
|
+
include Hobby::App
|
6
|
+
get { 'root-only app' }
|
7
|
+
|
8
|
+
class Counter
|
9
|
+
include Hobby::App
|
10
|
+
@@counter = 0
|
11
|
+
get { @@counter }
|
12
|
+
post { @@counter += 1 }
|
13
|
+
end
|
14
|
+
map('/counter') { run Counter.new }
|
15
|
+
|
16
|
+
class Echo
|
17
|
+
include Hobby::App
|
18
|
+
include Hobby::JSON
|
19
|
+
|
20
|
+
get { json }
|
21
|
+
end
|
22
|
+
map('/echo') { run Echo.new }
|
23
|
+
get('/echo-with-query') { request.params.to_json }
|
24
|
+
|
25
|
+
class Query
|
26
|
+
include Hobby::App
|
27
|
+
get { request.params['array'].class }
|
28
|
+
end
|
29
|
+
map('/query') { run Query.new }
|
30
|
+
|
31
|
+
class HashApp
|
32
|
+
include Hobby::App
|
33
|
+
include Hobby::JSON
|
34
|
+
get do
|
35
|
+
{
|
36
|
+
'a' => 1,
|
37
|
+
'b' => 2,
|
38
|
+
'c' => 3
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
map('/hash') { run HashApp.new }
|
43
|
+
|
44
|
+
class ArrayApp
|
45
|
+
include Hobby::App
|
46
|
+
include Hobby::JSON
|
47
|
+
get do
|
48
|
+
[
|
49
|
+
{
|
50
|
+
'first' => {
|
51
|
+
'a' => 0, 'b' => 1
|
52
|
+
},
|
53
|
+
'second' => {
|
54
|
+
'c' => 2, 'd' => 3
|
55
|
+
}
|
56
|
+
},
|
57
|
+
{
|
58
|
+
'first' => {
|
59
|
+
'a' => 5, 'b' => 6
|
60
|
+
},
|
61
|
+
'second' => {
|
62
|
+
'c' => 2, 'd' => 3
|
63
|
+
}
|
64
|
+
}
|
65
|
+
]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
map('/array') { run ArrayApp.new }
|
69
|
+
end
|
data/spec/auto_spec.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe 'passing and failing YAML specifications' do
|
4
|
+
Dir["spec/yml/**/*.yml"].each do |path|
|
5
|
+
name = path.split('/').last
|
6
|
+
test = Hobby::Test.new path
|
7
|
+
|
8
|
+
if path.include? 'passing'
|
9
|
+
it "passing #{name}" do
|
10
|
+
report = test[@socket]
|
11
|
+
assert { report.ok? }
|
12
|
+
end
|
13
|
+
else
|
14
|
+
it "failing #{name}" do
|
15
|
+
report = test[@socket]
|
16
|
+
assert { not report.ok? }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'setup/power_assert'
|
2
|
+
require_relative 'setup/mutant'
|
3
|
+
require_relative 'apps/main'
|
4
|
+
|
5
|
+
require 'hobby/test'
|
6
|
+
require 'puma'
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.expect_with :rspec, :minitest
|
10
|
+
|
11
|
+
config.before :each do |example|
|
12
|
+
@socket = "MainApp.for.#{example}.socket"
|
13
|
+
@pid = fork do
|
14
|
+
server = Puma::Server.new MainApp.new
|
15
|
+
server.add_unix_listener @socket
|
16
|
+
server.run
|
17
|
+
sleep
|
18
|
+
end
|
19
|
+
|
20
|
+
sleep 0.01 until File.exist? @socket
|
21
|
+
end
|
22
|
+
|
23
|
+
config.after :each do
|
24
|
+
`kill #{@pid}`
|
25
|
+
end
|
26
|
+
|
27
|
+
config.after :suite do
|
28
|
+
`rm *.socket` unless Dir['*.socket'].empty?
|
29
|
+
end
|
30
|
+
end
|
data/spec/http/basic.yml
ADDED
data/spec/http/event.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
- default:
|
2
|
+
verb: post
|
3
|
+
path: /event
|
4
|
+
format: json
|
5
|
+
|
6
|
+
- request:
|
7
|
+
body:
|
8
|
+
text: text
|
9
|
+
|
10
|
+
response:
|
11
|
+
status: 201
|
12
|
+
body:
|
13
|
+
name: ReverseApp
|
14
|
+
text: txet
|
15
|
+
|
16
|
+
- request:
|
17
|
+
body:
|
18
|
+
text: reverse
|
19
|
+
|
20
|
+
response:
|
21
|
+
status: 201
|
22
|
+
body:
|
23
|
+
name: ReverseApp
|
24
|
+
text: something entirely else
|
data/spec/run_mutant.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# A workaround for
|
2
|
+
# https://github.com/mbj/mutant/#the-crash--stuck-problem-mri
|
3
|
+
#
|
4
|
+
# For Hobby::Test::Report
|
5
|
+
# https://gist.github.com/ch1c0t/c5e0a8de8de14d10bec49839fb6e2fee
|
6
|
+
# this mutation
|
7
|
+
# https://gist.github.com/ch1c0t/c1626fe232032489fad93dab8d3a10c0
|
8
|
+
# causes the infinite running.
|
9
|
+
#
|
10
|
+
# This workaround "solves" it by introducing a timeout enforced
|
11
|
+
# from the parent process.
|
12
|
+
#
|
13
|
+
# With this workaround, Mutant ends its run successfully, but lefts out
|
14
|
+
# a process which keeps running in the background. That process, apparently,
|
15
|
+
# is forked from the process with the offending mutation.
|
16
|
+
# TODO: find out why it happens; create a workaround for the workaround
|
17
|
+
# (because killing that process manually every time is too much hassle).
|
18
|
+
require 'mutant'
|
19
|
+
|
20
|
+
Runner = Mutant::Runner
|
21
|
+
Bootstrap = Mutant::Env::Bootstrap
|
22
|
+
Integration = Mutant::Integration
|
23
|
+
|
24
|
+
require 'tra/run'
|
25
|
+
module MyIsolation
|
26
|
+
def self.call &block
|
27
|
+
pid = fork do
|
28
|
+
Process.setproctitle Process.pid.to_s
|
29
|
+
Process.ppid.put block.call
|
30
|
+
end
|
31
|
+
Timeout.timeout(3) { Tra.next }
|
32
|
+
rescue
|
33
|
+
Process.kill :KILL, pid
|
34
|
+
fail Mutant::Isolation::Error
|
35
|
+
ensure
|
36
|
+
Process.waitpid pid
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class MyIntegration
|
41
|
+
def initialize _config
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup
|
45
|
+
self
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
DEFAULT = Mutant::Config::DEFAULT
|
50
|
+
config = DEFAULT.with \
|
51
|
+
matcher: DEFAULT.matcher.add(:match_expressions, DEFAULT.expression_parser.(ARGV[0])),
|
52
|
+
integration: Integration.setup(Kernel, 'rspec'),
|
53
|
+
isolation: MyIsolation
|
54
|
+
|
55
|
+
#Runner.call Bootstrap.call config
|
56
|
+
|
57
|
+
env = Bootstrap.call config
|
58
|
+
puts "#{env.mutations.size} mutations are to be checked."
|
59
|
+
result_mutations = env.mutations.map.with_index 1 do |mutation, index|
|
60
|
+
puts index
|
61
|
+
env.kill mutation
|
62
|
+
end
|
63
|
+
|
64
|
+
failed_mutations = result_mutations.reject &:success?
|
65
|
+
if failed_mutations.empty?
|
66
|
+
puts 'Covered.'
|
67
|
+
else
|
68
|
+
require 'pry'
|
69
|
+
failed_mutations.__binding__.pry
|
70
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
if defined? Mutant::CLI
|
2
|
+
module Mutant
|
3
|
+
class Selector::Expression
|
4
|
+
def call _subject
|
5
|
+
integration.all_tests
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'timeout'
|
10
|
+
class Isolation::Fork
|
11
|
+
def parent reader, writer, &block
|
12
|
+
pid = process.fork do
|
13
|
+
child reader, writer, &block
|
14
|
+
end
|
15
|
+
|
16
|
+
writer.close
|
17
|
+
Timeout::timeout(3) { marshal.load reader }
|
18
|
+
ensure
|
19
|
+
process.kill :KILL, pid
|
20
|
+
process.waitpid pid
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'minitest'
|
2
|
+
require 'minitest-power_assert'
|
3
|
+
|
4
|
+
assert = if ENV['PRY']
|
5
|
+
require 'pry'
|
6
|
+
require 'awesome_print'
|
7
|
+
|
8
|
+
Module.new do
|
9
|
+
def assert &block
|
10
|
+
PowerAssert.start Proc.new, assertion_method: __method__ do |pa|
|
11
|
+
block.binding.pry unless pa.yield
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
else
|
16
|
+
Minitest::PowerAssert::Assertions
|
17
|
+
end
|
18
|
+
|
19
|
+
Minitest::Assertions.prepend assert
|
data/spec/tcp_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
describe Hobby::Test do
|
2
|
+
before do
|
3
|
+
@tcp_pid = fork do
|
4
|
+
server = Puma::Server.new MainApp.new
|
5
|
+
server.add_tcp_listener 'localhost', 8080
|
6
|
+
server.run
|
7
|
+
sleep
|
8
|
+
end
|
9
|
+
|
10
|
+
begin
|
11
|
+
Excon.get 'http://localhost:8080'
|
12
|
+
rescue
|
13
|
+
sleep 0.01
|
14
|
+
retry
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
after do
|
20
|
+
`kill #{@tcp_pid}`
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'in case of success' do
|
24
|
+
test = described_class.new 'spec/yml/passing/0.yml'
|
25
|
+
report = test['http://localhost:8080']
|
26
|
+
assert { report.ok? }
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
- get:
|
2
|
+
path: /hash
|
3
|
+
|
4
|
+
response:
|
5
|
+
body:
|
6
|
+
a: 1
|
7
|
+
b: 2
|
8
|
+
c: 3
|
9
|
+
body.>:
|
10
|
+
a: 1
|
11
|
+
body.size: 3
|
12
|
+
body['b']: 2
|
13
|
+
|
14
|
+
- get:
|
15
|
+
path: /array
|
16
|
+
|
17
|
+
response:
|
18
|
+
body:
|
19
|
+
- first:
|
20
|
+
a: 0
|
21
|
+
b: 1
|
22
|
+
second:
|
23
|
+
c: 2
|
24
|
+
d: 3
|
25
|
+
- first:
|
26
|
+
a: 5
|
27
|
+
b: 6
|
28
|
+
second:
|
29
|
+
c: 2
|
30
|
+
d: 3
|
31
|
+
body.size: 2
|
32
|
+
body[1]['first']:
|
33
|
+
a: 5
|
34
|
+
b: 6
|
35
|
+
body[1]['first'].>:
|
36
|
+
a: 5
|
@@ -0,0 +1,23 @@
|
|
1
|
+
- get:
|
2
|
+
path: /echo-with-query
|
3
|
+
query:
|
4
|
+
first: one
|
5
|
+
second: two
|
6
|
+
|
7
|
+
response:
|
8
|
+
status: 200
|
9
|
+
body:
|
10
|
+
first: one
|
11
|
+
second: two
|
12
|
+
|
13
|
+
- get:
|
14
|
+
path: /echo
|
15
|
+
body:
|
16
|
+
first: one
|
17
|
+
second: two
|
18
|
+
|
19
|
+
response:
|
20
|
+
status: 200
|
21
|
+
body:
|
22
|
+
first: one
|
23
|
+
second: two
|
data/useful_links
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hobby-test
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anatoly Chernow
|
@@ -43,7 +43,33 @@ email:
|
|
43
43
|
executables: []
|
44
44
|
extensions: []
|
45
45
|
extra_rdoc_files: []
|
46
|
-
files:
|
46
|
+
files:
|
47
|
+
- ".gitignore"
|
48
|
+
- Gemfile
|
49
|
+
- debug.rb
|
50
|
+
- hobby-test.gemspec
|
51
|
+
- lib/hobby/test.rb
|
52
|
+
- lib/hobby/test/exchange.rb
|
53
|
+
- lib/hobby/test/exchange/assert.rb
|
54
|
+
- lib/hobby/test/exchange/request.rb
|
55
|
+
- lib/hobby/test/report.rb
|
56
|
+
- spec/apps/main.rb
|
57
|
+
- spec/auto_spec.rb
|
58
|
+
- spec/helper.rb
|
59
|
+
- spec/http/basic.yml
|
60
|
+
- spec/http/event.yml
|
61
|
+
- spec/http/reverse.yml
|
62
|
+
- spec/run_mutant.rb
|
63
|
+
- spec/setup/mutant.rb
|
64
|
+
- spec/setup/power_assert.rb
|
65
|
+
- spec/tcp_spec.rb
|
66
|
+
- spec/yml/failing/0.yml
|
67
|
+
- spec/yml/passing/0.yml
|
68
|
+
- spec/yml/passing/chain_assertions.yml
|
69
|
+
- spec/yml/passing/counter.yml
|
70
|
+
- spec/yml/passing/echo.yml
|
71
|
+
- spec/yml/passing/query.yml
|
72
|
+
- useful_links
|
47
73
|
homepage:
|
48
74
|
licenses: []
|
49
75
|
metadata: {}
|