socky-server 0.4.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.
- data/CHANGELOG.textile +95 -0
- data/README.md +30 -0
- data/Rakefile +31 -0
- data/VERSION +1 -0
- data/bin/socky +5 -0
- data/lib/em-websocket_hacks.rb +15 -0
- data/lib/socky.rb +75 -0
- data/lib/socky/connection.rb +137 -0
- data/lib/socky/connection/authentication.rb +99 -0
- data/lib/socky/connection/finders.rb +67 -0
- data/lib/socky/message.rb +85 -0
- data/lib/socky/misc.rb +74 -0
- data/lib/socky/net_request.rb +27 -0
- data/lib/socky/options.rb +39 -0
- data/lib/socky/options/config.rb +79 -0
- data/lib/socky/options/parser.rb +93 -0
- data/lib/socky/runner.rb +95 -0
- data/spec/em-websocket_spec.rb +36 -0
- data/spec/files/default.yml +18 -0
- data/spec/files/invalid.yml +1 -0
- data/spec/socky/connection/authentication_spec.rb +183 -0
- data/spec/socky/connection/finders_spec.rb +188 -0
- data/spec/socky/connection_spec.rb +151 -0
- data/spec/socky/message_spec.rb +102 -0
- data/spec/socky/misc_spec.rb +74 -0
- data/spec/socky/net_request_spec.rb +42 -0
- data/spec/socky/options/config_spec.rb +72 -0
- data/spec/socky/options/parser_spec.rb +76 -0
- data/spec/socky/options_spec.rb +60 -0
- data/spec/socky/runner_spec.rb +88 -0
- data/spec/socky_spec.rb +89 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/support/stallion.rb +96 -0
- metadata +198 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Socky::Options do
|
4
|
+
|
5
|
+
context "class" do
|
6
|
+
before(:all) do
|
7
|
+
@default_options = Socky.options
|
8
|
+
end
|
9
|
+
after(:each) do
|
10
|
+
Socky.options = @default_options
|
11
|
+
end
|
12
|
+
|
13
|
+
context "#prepare" do
|
14
|
+
before(:each) do
|
15
|
+
Socky::Options::Parser.stub!(:parse).and_return({})
|
16
|
+
Socky::Options::Config.stub!(:read).and_return({})
|
17
|
+
end
|
18
|
+
it "should call parser with self option" do
|
19
|
+
Socky::Options::Parser.should_receive(:parse).with([:a,:b,:c])
|
20
|
+
Socky::Options.prepare([:a,:b,:c])
|
21
|
+
end
|
22
|
+
it "should call read_config with patch" do
|
23
|
+
Socky::Options::Config.should_receive(:read).with("/var/run/socky.yml", :kill => nil)
|
24
|
+
Socky::Options.prepare([])
|
25
|
+
end
|
26
|
+
it "should set Socky options to default hash when parse_options and read_config don't do anything" do
|
27
|
+
Socky::Options.prepare([])
|
28
|
+
Socky.options.should eql(default_options)
|
29
|
+
end
|
30
|
+
it "should value parse_options over default values" do
|
31
|
+
Socky::Options::Parser.stub!(:parse).and_return(:log_path => "parsed")
|
32
|
+
Socky::Options.prepare([])
|
33
|
+
Socky.options.should eql(default_options.merge(:log_path=>"parsed"))
|
34
|
+
end
|
35
|
+
it "should value read_config over default values" do
|
36
|
+
Socky::Options::Config.stub!(:read).and_return(:log_path => "from config")
|
37
|
+
Socky::Options.prepare([])
|
38
|
+
Socky.options.should eql(default_options.merge(:log_path=>"from config"))
|
39
|
+
end
|
40
|
+
it "should value parse_options over read_config" do
|
41
|
+
Socky::Options::Config.stub!(:read).and_return(:log_path => "from config")
|
42
|
+
Socky::Options::Parser.stub!(:parse).and_return(:log_path => "parsed")
|
43
|
+
Socky::Options.prepare([])
|
44
|
+
Socky.options.should eql(default_options.merge(:log_path=>"parsed"))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def default_options
|
50
|
+
{
|
51
|
+
:port => 8080,
|
52
|
+
:log_path => nil,
|
53
|
+
:debug => false,
|
54
|
+
:deep_debug => false,
|
55
|
+
:secure => false,
|
56
|
+
:config_path => "/var/run/socky.yml"
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Socky::Runner do
|
4
|
+
|
5
|
+
context "#class" do
|
6
|
+
context "#run" do
|
7
|
+
before(:each) do
|
8
|
+
@server = mock(:server, :start => nil)
|
9
|
+
described_class.stub!(:new).and_return(@server)
|
10
|
+
end
|
11
|
+
it "should create new instance of self" do
|
12
|
+
described_class.should_receive(:new).with("some args")
|
13
|
+
described_class.run("some args")
|
14
|
+
end
|
15
|
+
it "should call #start on new instance of self if daemonize option is false" do
|
16
|
+
Socky.stub(:options).and_return({:daemonize => false})
|
17
|
+
@server.should_receive(:start)
|
18
|
+
described_class.run
|
19
|
+
end
|
20
|
+
it "should call #daemonize on new instance of self if daemonize option is true" do
|
21
|
+
Socky.stub(:options).and_return({:daemonize => true})
|
22
|
+
@server.should_receive(:daemonize)
|
23
|
+
described_class.run
|
24
|
+
end
|
25
|
+
it "should call #kill_pid on new instance of self if kill option is true" do
|
26
|
+
Socky.stub(:options).and_return({:kill => true})
|
27
|
+
@server.should_receive(:kill_pid)
|
28
|
+
described_class.run
|
29
|
+
end
|
30
|
+
end
|
31
|
+
context "#new" do
|
32
|
+
it "should prepare options from args" do
|
33
|
+
begin
|
34
|
+
described_class.new(["-c", File.dirname(__FILE__) + "/../files/default.yml"])
|
35
|
+
Socky.options.class.should eql(Hash)
|
36
|
+
Socky.options.should_not be_empty
|
37
|
+
ensure
|
38
|
+
Socky.options = nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "#instance" do
|
45
|
+
before(:each) do
|
46
|
+
Socky::Options.stub!(:prepare)
|
47
|
+
@runner = described_class.new
|
48
|
+
end
|
49
|
+
|
50
|
+
context "#start" do
|
51
|
+
it "should create valid websocket server" do
|
52
|
+
begin
|
53
|
+
EM.run do
|
54
|
+
MSG = "Hello World!"
|
55
|
+
EventMachine.add_timer(0.1) do
|
56
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
|
57
|
+
http.errback {
|
58
|
+
EM.stop
|
59
|
+
fail
|
60
|
+
}
|
61
|
+
http.callback {
|
62
|
+
http.response_header.status.should == 101
|
63
|
+
EM.stop
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
Socky.stub!(:options).and_return({:port => 12345})
|
68
|
+
Socky.logger = mock(:logger, :info => nil, :debug => nil)
|
69
|
+
@runner.start
|
70
|
+
end
|
71
|
+
ensure
|
72
|
+
Socky.logger = nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "#stop should call EM.stop" do
|
78
|
+
begin
|
79
|
+
Socky.logger = mock(:logger, :info => nil, :debug => nil)
|
80
|
+
EM.should_receive(:stop)
|
81
|
+
@runner.stop
|
82
|
+
ensure
|
83
|
+
Socky.logger = nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
data/spec/socky_spec.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Socky do
|
4
|
+
|
5
|
+
context "class" do
|
6
|
+
|
7
|
+
it "should have non-blank version" do
|
8
|
+
Socky::VERSION.should_not be_nil
|
9
|
+
end
|
10
|
+
it "should have options in hash form" do
|
11
|
+
Socky.options.should_not be_nil
|
12
|
+
Socky.options.class.should eql(Hash)
|
13
|
+
end
|
14
|
+
it "should allow to set options" do
|
15
|
+
Socky.options.should eql(Hash.new)
|
16
|
+
begin
|
17
|
+
Socky.options = {:key => :value}
|
18
|
+
Socky.options.should eql({:key => :value})
|
19
|
+
ensure
|
20
|
+
Socky.options = Hash.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
it "should have logger" do
|
24
|
+
Socky.logger.should_not be_nil
|
25
|
+
Socky.logger.class.should eql(Logger)
|
26
|
+
end
|
27
|
+
it "should have logger with STDOUT at default" do
|
28
|
+
Socky.logger.instance_variable_get('@logdev').dev.class.should eql(IO)
|
29
|
+
end
|
30
|
+
it "should be able to set logger" do
|
31
|
+
begin
|
32
|
+
logger = Logger.new(STDOUT)
|
33
|
+
Socky.logger.should_not equal(logger)
|
34
|
+
Socky.logger = logger
|
35
|
+
Socky.logger.should equal(logger)
|
36
|
+
ensure
|
37
|
+
Socky.logger = nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
it "should be able to change verbosity of logger by setting debug option" do
|
41
|
+
begin
|
42
|
+
Socky.logger.level.should eql(Logger::INFO)
|
43
|
+
Socky.logger = nil
|
44
|
+
Socky.stub!(:options).and_return({:debug => true})
|
45
|
+
Socky.logger.level.should eql(Logger::DEBUG)
|
46
|
+
Socky.logger = nil
|
47
|
+
Socky.stub!(:options).and_return({:debug => false})
|
48
|
+
Socky.logger.level.should eql(Logger::INFO)
|
49
|
+
ensure
|
50
|
+
Socky.logger = nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
it "should not have default log path" do
|
54
|
+
Socky.log_path.should be_nil
|
55
|
+
end
|
56
|
+
it "should be able to change log path by settion log_path option" do
|
57
|
+
Socky.stub!(:options).and_return({:log_path => "abstract"})
|
58
|
+
Socky.log_path.should eql("abstract")
|
59
|
+
end
|
60
|
+
it "should be able to change logger write place" do
|
61
|
+
begin
|
62
|
+
Socky.options = {:log_path => File.join(File.dirname(__FILE__), 'files', 'socky.log')}
|
63
|
+
Socky.logger.should_not be_nil
|
64
|
+
Socky.logger.instance_variable_get('@logdev').dev.class.should eql(File)
|
65
|
+
ensure
|
66
|
+
Socky.logger = nil
|
67
|
+
Socky.options = {}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
it "should have default pid path" do
|
71
|
+
Socky.pid_path.should_not be_nil
|
72
|
+
Socky.pid_path.should eql("/var/run/socky.pid")
|
73
|
+
end
|
74
|
+
it "should be able to change pid path by settion pid_path option" do
|
75
|
+
Socky.stub!(:options).and_return({:pid_path => "abstract"})
|
76
|
+
Socky.pid_path.should eql("abstract")
|
77
|
+
end
|
78
|
+
it "should have default config path" do
|
79
|
+
Socky.config_path.should_not be_nil
|
80
|
+
Socky.config_path.should eql("/var/run/socky.yml")
|
81
|
+
end
|
82
|
+
it "should be able to change config path by settion config_path option" do
|
83
|
+
Socky.stub!(:options).and_return({:config_path => "abstract"})
|
84
|
+
Socky.config_path.should eql("abstract")
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# #--
|
2
|
+
# Includes portion originally Copyright (C)2008 Michael Fellinger
|
3
|
+
# license See file LICENSE for details
|
4
|
+
# #--
|
5
|
+
|
6
|
+
require 'rack'
|
7
|
+
|
8
|
+
module Stallion
|
9
|
+
class Mount
|
10
|
+
def initialize(name, *methods, &block)
|
11
|
+
@name, @methods, @block = name, methods, block
|
12
|
+
end
|
13
|
+
|
14
|
+
def ride
|
15
|
+
@block.call
|
16
|
+
end
|
17
|
+
|
18
|
+
def match?(request)
|
19
|
+
method = request['REQUEST_METHOD']
|
20
|
+
right_method = @methods.empty? or @methods.include?(method)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Stable
|
25
|
+
attr_reader :request, :response
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@boxes = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def in(path, *methods, &block)
|
32
|
+
mount = Mount.new(path, *methods, &block)
|
33
|
+
@boxes[[path, methods]] = mount
|
34
|
+
mount
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(request, response)
|
38
|
+
@request, @response = request, response
|
39
|
+
@boxes.each do |(path, methods), mount|
|
40
|
+
if mount.match?(request)
|
41
|
+
mount.ride
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
STABLES = {}
|
48
|
+
|
49
|
+
def self.saddle(name = nil)
|
50
|
+
STABLES[name] = stable = Stable.new
|
51
|
+
yield stable
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.run(options = {})
|
55
|
+
options = {:Host => "127.0.0.1", :Port => 8080}.merge(options)
|
56
|
+
Rack::Handler::Mongrel.run(Rack::Lint.new(self), options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.call(env)
|
60
|
+
request = Rack::Request.new(env)
|
61
|
+
response = Rack::Response.new
|
62
|
+
|
63
|
+
STABLES.each do |name, stable|
|
64
|
+
stable.call(request, response)
|
65
|
+
end
|
66
|
+
|
67
|
+
response.finish
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
Stallion.saddle :spec do |stable|
|
72
|
+
stable.in '/' do
|
73
|
+
|
74
|
+
if stable.request.path_info == '/fail'
|
75
|
+
stable.response.status = 404
|
76
|
+
|
77
|
+
elsif stable.request.path_info == '/timeout'
|
78
|
+
sleep(10)
|
79
|
+
stable.response.write 'timeout'
|
80
|
+
|
81
|
+
elsif
|
82
|
+
stable.response.write 'Hello, World!'
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
Thread.new do
|
89
|
+
begin
|
90
|
+
Stallion.run :Host => '127.0.0.1', :Port => 8765
|
91
|
+
rescue Exception => e
|
92
|
+
print e
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
sleep(1)
|
metadata
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: socky-server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Bernard Potocki
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-28 00:00:00 +02:00
|
19
|
+
default_executable: socky
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: em-websocket
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 19
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 1
|
33
|
+
- 4
|
34
|
+
version: 0.1.4
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: em-http-request
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: json
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :runtime
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: rspec
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ~>
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 2
|
76
|
+
- 0
|
77
|
+
version: "2.0"
|
78
|
+
type: :development
|
79
|
+
version_requirements: *id004
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: rack
|
82
|
+
prerelease: false
|
83
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 3
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
type: :development
|
93
|
+
version_requirements: *id005
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: mongrel
|
96
|
+
prerelease: false
|
97
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
hash: 3
|
103
|
+
segments:
|
104
|
+
- 0
|
105
|
+
version: "0"
|
106
|
+
type: :development
|
107
|
+
version_requirements: *id006
|
108
|
+
description: Socky is a WebSocket server and client for Ruby
|
109
|
+
email: bernard.potocki@imanel.org
|
110
|
+
executables:
|
111
|
+
- socky
|
112
|
+
extensions: []
|
113
|
+
|
114
|
+
extra_rdoc_files:
|
115
|
+
- README.md
|
116
|
+
files:
|
117
|
+
- CHANGELOG.textile
|
118
|
+
- README.md
|
119
|
+
- Rakefile
|
120
|
+
- VERSION
|
121
|
+
- bin/socky
|
122
|
+
- lib/em-websocket_hacks.rb
|
123
|
+
- lib/socky.rb
|
124
|
+
- lib/socky/connection.rb
|
125
|
+
- lib/socky/connection/authentication.rb
|
126
|
+
- lib/socky/connection/finders.rb
|
127
|
+
- lib/socky/message.rb
|
128
|
+
- lib/socky/misc.rb
|
129
|
+
- lib/socky/net_request.rb
|
130
|
+
- lib/socky/options.rb
|
131
|
+
- lib/socky/options/config.rb
|
132
|
+
- lib/socky/options/parser.rb
|
133
|
+
- lib/socky/runner.rb
|
134
|
+
- spec/em-websocket_spec.rb
|
135
|
+
- spec/files/default.yml
|
136
|
+
- spec/files/invalid.yml
|
137
|
+
- spec/socky/connection/authentication_spec.rb
|
138
|
+
- spec/socky/connection/finders_spec.rb
|
139
|
+
- spec/socky/connection_spec.rb
|
140
|
+
- spec/socky/message_spec.rb
|
141
|
+
- spec/socky/misc_spec.rb
|
142
|
+
- spec/socky/net_request_spec.rb
|
143
|
+
- spec/socky/options/config_spec.rb
|
144
|
+
- spec/socky/options/parser_spec.rb
|
145
|
+
- spec/socky/options_spec.rb
|
146
|
+
- spec/socky/runner_spec.rb
|
147
|
+
- spec/socky_spec.rb
|
148
|
+
- spec/spec_helper.rb
|
149
|
+
- spec/support/stallion.rb
|
150
|
+
has_rdoc: true
|
151
|
+
homepage: http://imanel.org/projects/socky
|
152
|
+
licenses: []
|
153
|
+
|
154
|
+
post_install_message:
|
155
|
+
rdoc_options:
|
156
|
+
- --charset=UTF-8
|
157
|
+
require_paths:
|
158
|
+
- lib
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
none: false
|
161
|
+
requirements:
|
162
|
+
- - ">="
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
hash: 3
|
165
|
+
segments:
|
166
|
+
- 0
|
167
|
+
version: "0"
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
hash: 3
|
174
|
+
segments:
|
175
|
+
- 0
|
176
|
+
version: "0"
|
177
|
+
requirements: []
|
178
|
+
|
179
|
+
rubyforge_project:
|
180
|
+
rubygems_version: 1.3.7
|
181
|
+
signing_key:
|
182
|
+
specification_version: 3
|
183
|
+
summary: Socky is a WebSocket server and client for Ruby
|
184
|
+
test_files:
|
185
|
+
- spec/em-websocket_spec.rb
|
186
|
+
- spec/socky/connection/authentication_spec.rb
|
187
|
+
- spec/socky/connection/finders_spec.rb
|
188
|
+
- spec/socky/connection_spec.rb
|
189
|
+
- spec/socky/message_spec.rb
|
190
|
+
- spec/socky/misc_spec.rb
|
191
|
+
- spec/socky/net_request_spec.rb
|
192
|
+
- spec/socky/options/config_spec.rb
|
193
|
+
- spec/socky/options/parser_spec.rb
|
194
|
+
- spec/socky/options_spec.rb
|
195
|
+
- spec/socky/runner_spec.rb
|
196
|
+
- spec/socky_spec.rb
|
197
|
+
- spec/spec_helper.rb
|
198
|
+
- spec/support/stallion.rb
|