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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 166b6d9643f2120ba363ed8b12a3c9a15557dcb7
4
- data.tar.gz: a1982ba0b5dfb002f03f35651dce123fa0233f15
3
+ metadata.gz: 6630ab85ce09d1fbd6661bd8ec6201e66a73c197
4
+ data.tar.gz: 7547c087095c379e16aafe99a62fb72dddedfba6
5
5
  SHA512:
6
- metadata.gz: dbe0f5a1e37ce0e1caa14d74c834e196fc9aa115dee2defe79c4a0f1d3b566e9504a2cee03f2251d578f625ac6fa87b0cfe893981913e29b41eba87d2b102fe8
7
- data.tar.gz: 769282da8aa96c7bc4efeb913a866830074552f7f87a179ebe349c29f45f5e33b53016f49e1353a504dbe3eb1fc99950be97ec4a0b9b85eedf8a217d9053a429
6
+ metadata.gz: 4f2d0e876e7b6e10990418aa0318aeca79189b30f9953eb24c50fe2d212110a0c4603e5d00a5d061bb9a01f3f18219c11a07369dbbd91c6fcbb7149c3b745fa7
7
+ data.tar.gz: 6f4c9b2756eb6fd613ed6ef211a208f5ef78eb74aa81312f81da11e0d5715dc8b86da986ab0aa2cd745e3a2d4ecc6bc1f27c14d2eff4bdc92a86083d9502d141
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'hobby'
4
+ gem 'hobby-json'
5
+ gem 'rspec'
6
+ gem 'minitest'
7
+ gem 'minitest-power_assert'
8
+ gem 'pry'
9
+ gem 'awesome_print'
10
+ gem 'puma'
11
+ gem 'mutant'
12
+ gem 'mutant-rspec'
13
+ gem 'tra'
14
+
15
+ gemspec
data/debug.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'puma'
2
+ require_relative 'spec/apps/main'
3
+
4
+ server = Puma::Server.new MainApp.new
5
+ server.add_unix_listener 'debug.socket'
6
+ server.run
7
+ sleep
@@ -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
@@ -0,0 +1,14 @@
1
+ - request:
2
+ verb: get
3
+ path: /
4
+
5
+ response:
6
+ status: 200
7
+ body: root-only app
8
+
9
+ - request:
10
+ verb: get
11
+ path: /non-existent
12
+
13
+ response:
14
+ status: 404
@@ -0,0 +1,11 @@
1
+ request:
2
+ verb: POST
3
+ data:
4
+ text: text
5
+ username: username
6
+
7
+ response:
8
+ code: 201
9
+ body:
10
+ id: event-endpoint
11
+ text: txet
@@ -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
@@ -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,12 @@
1
+ - get:
2
+ path: /
3
+
4
+ response:
5
+ status: 200
6
+ body: root-only app
7
+
8
+ - get:
9
+ path: /non-existent
10
+
11
+ response:
12
+ status: 200
@@ -0,0 +1,12 @@
1
+ - get:
2
+ path: /
3
+
4
+ response:
5
+ status: 200
6
+ body: root-only app
7
+
8
+ - get:
9
+ path: /non-existent
10
+
11
+ response:
12
+ status: 404
@@ -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,11 @@
1
+ - post:
2
+ path: /counter
3
+
4
+ - post:
5
+ path: /counter
6
+
7
+ - get:
8
+ path: /counter
9
+
10
+ response:
11
+ body: "2"
@@ -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
@@ -0,0 +1,8 @@
1
+ - get:
2
+ path: /query
3
+ query:
4
+ array: [1, 2]
5
+
6
+ response:
7
+ status: 200
8
+ body: String
data/useful_links ADDED
@@ -0,0 +1,3 @@
1
+ https://github.com/excon/excon
2
+ http://blog.paulrugelhiatt.com/ruby/testing/rspec/2014/11/28/improving-rspec-test-clarity-with-yaml-expectations.html
3
+ http://rspec-api.github.io/
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.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: {}