hobby-test 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: {}