httpotemkin 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 +7 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/CONTRIBUTING.md +1 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +19 -0
- data/README.md +25 -0
- data/TODO +5 -0
- data/bin/httpotemkin +69 -0
- data/containers/api.rubygems/Dockerfile +12 -0
- data/containers/api.rubygems/latest_specs.4.8.gz +0 -0
- data/containers/api.rubygems/rubygems-update-2.5.0.gemspec.rz +0 -0
- data/containers/api.rubygems/secure_server.rb +21 -0
- data/containers/build_containers +35 -0
- data/containers/client/Dockerfile +30 -0
- data/containers/client/certificates/.gitignore +1 -0
- data/containers/client/oscrc +7 -0
- data/containers/client/run_test.rb +5 -0
- data/containers/client/server.crt +19 -0
- data/containers/obs/Dockerfile +10 -0
- data/containers/obs/secure_server.rb +13 -0
- data/containers/rubygems/Dockerfile +10 -0
- data/containers/rubygems/secure_server.rb +39 -0
- data/containers/server/Dockerfile +17 -0
- data/containers/server/secure_server.rb +13 -0
- data/containers/server/server.rb +17 -0
- data/containers/server/server_base.rb +30 -0
- data/containers/show_certificate.sh +1 -0
- data/expected_output +3 -0
- data/httpotemkin.gemspec +23 -0
- data/lib/client.rb +38 -0
- data/lib/containers.rb +113 -0
- data/lib/httpotemkin.rb +5 -0
- data/lib/test.rb +23 -0
- data/logs/.gitignore +1 -0
- data/spec/integration/bin/client/docker +5 -0
- data/spec/integration/bin/down/docker +8 -0
- data/spec/integration/bin/run.failure/docker +5 -0
- data/spec/integration/bin/run.success/docker +7 -0
- data/spec/integration/bin/status/docker +4 -0
- data/spec/integration/bin/up/docker +8 -0
- data/spec/integration/cli_spec.rb +157 -0
- data/spec/integration/spec_helper.rb +21 -0
- data/spec/system/client_spec.rb +70 -0
- data/spec/system/data/red_herring-000.tar.gz +0 -0
- data/spec/system/data/test.tar.gz +0 -0
- data/spec/system/hello_world_spec.rb +20 -0
- data/spec/system/spec_helper.rb +3 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 680e6b1ecaeb762c4d4ec9ad4e704d07e57d11f8
|
4
|
+
data.tar.gz: bd9941065a464eff2cfe82259f76bcba7bd647cc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 654de56651be4fdd59502f828c9404cd266cf47046f9a6c0a7712f6a58d1a0ba79b484787240b193254597e6602a1845ffca5134d12d237e39b490840ea4cab6
|
7
|
+
data.tar.gz: 88bbd23cf90c764c0505d39af88096b88cd987584067c6a83cb8775d1587913f97fdcef79b1579cfca9022d2203bd80b65a23e7223f1a46ab161565321ffcbe4
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Contributions are welcome. Let them flow through GitHub.
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2014 Cornelius Schumacher <schumacher@kde.org>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Httpotemkin provides tools to mock HTTP servers for system-level tests. It uses
|
2
|
+
docker containers to provide mocks of HTTP APIs and Ruby classes which can
|
3
|
+
be used in tests to execute applications in the environment of the mocked APIs.
|
4
|
+
This allows to test applications which talk to HTTP APIs without having to
|
5
|
+
change them in any way for the tests, so that very high-level system tests can
|
6
|
+
be done in a controlled environment suitable for test driven development.
|
7
|
+
|
8
|
+
The tests in `spec/system` provide examples of how tests using httpotemkin can
|
9
|
+
be written.
|
10
|
+
|
11
|
+
## Debugging
|
12
|
+
|
13
|
+
When debugging tests (which actually means reverse engineering the protocol of
|
14
|
+
the service and implementing the required bits) it is convenient to get in a
|
15
|
+
mode of interactive debugging, where there are containers for the server and
|
16
|
+
the client under control of a shell. This allows to manually trigger calls from
|
17
|
+
the client and see the resulting requests in the log of the server. By adapting
|
18
|
+
the server calls one at a time, it's easily possible to provide the minimal
|
19
|
+
mocking to make the tests run.
|
20
|
+
|
21
|
+
One way to enter the state of debugging a specific test is to add debug code to
|
22
|
+
the RSpec test, e.g. add a `binding.pry` call just before the client test call
|
23
|
+
is executed and passing `r.pry` to the `rspec` command. Then you end up in a
|
24
|
+
state where all containers are started, test data is injected, and you can debug
|
25
|
+
the calls which are supposed to take place.
|
data/TODO
ADDED
data/bin/httpotemkin
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require_relative "../lib/httpotemkin"
|
4
|
+
|
5
|
+
containers = Httpotemkin::Containers.new
|
6
|
+
containers.add_server("rubygems")
|
7
|
+
containers.add_server("api.rubygems")
|
8
|
+
containers.add_server("obs")
|
9
|
+
|
10
|
+
cmd = ARGV[0]
|
11
|
+
|
12
|
+
if cmd == "status"
|
13
|
+
containers.print_status
|
14
|
+
elsif cmd == "up"
|
15
|
+
puts "Starting server containers"
|
16
|
+
containers.up
|
17
|
+
elsif cmd == "down"
|
18
|
+
puts "Stopping server containers"
|
19
|
+
containers.down
|
20
|
+
elsif cmd == "client"
|
21
|
+
puts containers.run_client
|
22
|
+
elsif cmd == "run"
|
23
|
+
puts "Running tests"
|
24
|
+
|
25
|
+
expected_output = File.read("expected_output")
|
26
|
+
|
27
|
+
containers.up
|
28
|
+
sleep 3 unless ENV["RUN_BY_RSPEC"]
|
29
|
+
|
30
|
+
actual_output = containers.run_client
|
31
|
+
|
32
|
+
puts "--- Start Test ---"
|
33
|
+
puts actual_output
|
34
|
+
puts "---- End Test ----"
|
35
|
+
|
36
|
+
containers.down(save_logs: true)
|
37
|
+
|
38
|
+
puts
|
39
|
+
if actual_output == expected_output
|
40
|
+
puts "Success."
|
41
|
+
else
|
42
|
+
puts "Failed."
|
43
|
+
puts
|
44
|
+
puts "Expected output:"
|
45
|
+
puts expected_output
|
46
|
+
puts
|
47
|
+
puts "Actual output:"
|
48
|
+
puts actual_output
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
else
|
52
|
+
if !cmd
|
53
|
+
STDERR.puts "You need to provide a command"
|
54
|
+
elsif cmd != "help"
|
55
|
+
puts "Unknown command: #{cmd}"
|
56
|
+
end
|
57
|
+
|
58
|
+
puts "Usage: httpotemkin <command>"
|
59
|
+
puts
|
60
|
+
puts "Testing with HTTP mocks based on containers"
|
61
|
+
puts
|
62
|
+
puts "Commands:"
|
63
|
+
puts " status - Show status of containers"
|
64
|
+
puts " up - Start containers"
|
65
|
+
puts " down - Stop containters"
|
66
|
+
puts " client - Run client"
|
67
|
+
puts " run - Run tests"
|
68
|
+
puts " help - Show command line help"
|
69
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
FROM server
|
2
|
+
MAINTAINER Cornelius Schumacher <schumacher@kde.org>
|
3
|
+
|
4
|
+
COPY secure_server.rb /httpotemkin/api.rubygems/
|
5
|
+
COPY server.crt /httpotemkin/api.rubygems/
|
6
|
+
COPY server.key /httpotemkin/api.rubygems/
|
7
|
+
COPY latest_specs.4.8.gz /httpotemkin/api.rubygems/
|
8
|
+
COPY rubygems-update-2.5.0.gemspec.rz /httpotemkin/api.rubygems/
|
9
|
+
|
10
|
+
CMD /httpotemkin/api.rubygems/secure_server.rb
|
11
|
+
|
12
|
+
EXPOSE 443
|
Binary file
|
Binary file
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require "sinatra"
|
4
|
+
|
5
|
+
require_relative "../server/server_base.rb"
|
6
|
+
|
7
|
+
CERT_DIR = File.dirname(__FILE__)
|
8
|
+
|
9
|
+
configure_sinatra
|
10
|
+
|
11
|
+
get '/' do
|
12
|
+
"I'm the secure api.rubygems server\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
get '/latest_specs.4.8.gz' do
|
16
|
+
File.read(File.expand_path("../latest_specs.4.8.gz", __FILE__))
|
17
|
+
end
|
18
|
+
|
19
|
+
get '/quick/Marshal.4.8/rubygems-update-2.5.0.gemspec.rz' do
|
20
|
+
File.read(File.expand_path("../rubygems-update-2.5.0.gemspec.rz", __FILE__))
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
def run(cmd)
|
6
|
+
puts cmd
|
7
|
+
if !system(cmd)
|
8
|
+
STDERR.puts "Command failed. Exiting."
|
9
|
+
exit 1
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def build_image(name)
|
14
|
+
run("docker build -t #{name} #{name}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_api_image(image_name, server_name)
|
18
|
+
run("openssl req -x509 -newkey rsa:2048 -keyout #{image_name}/server.key -out #{image_name}/server.crt -days 1000 -nodes -subj '/CN=#{server_name}'")
|
19
|
+
|
20
|
+
FileUtils.copy("#{image_name}/server.crt", "client/certificates/#{image_name}.crt")
|
21
|
+
|
22
|
+
run("docker build -t #{image_name} #{image_name}")
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
build_image("server")
|
27
|
+
|
28
|
+
build_api_image("rubygems", "rubygems.org")
|
29
|
+
build_api_image("api.rubygems", "api.rubygems.org")
|
30
|
+
build_api_image("obs", "api.opensuse.org")
|
31
|
+
|
32
|
+
build_image("client")
|
33
|
+
|
34
|
+
|
35
|
+
puts "Success."
|
@@ -0,0 +1,30 @@
|
|
1
|
+
FROM opensuse:42.1
|
2
|
+
MAINTAINER Cornelius Schumacher <schumacher@kde.org>
|
3
|
+
|
4
|
+
run zypper --non-interactive install ca-certificates-mozilla
|
5
|
+
|
6
|
+
RUN zypper --non-interactive install curl
|
7
|
+
RUN zypper --non-interactive install ruby
|
8
|
+
RUN zypper --non-interactive install --no-recommends osc
|
9
|
+
|
10
|
+
# Dependencies for yes_ship_it
|
11
|
+
RUN zypper --non-interactive install ruby2.1-rubygem-inifile ruby2.1-rubygem-rest-client
|
12
|
+
RUN zypper --non-interactive install git
|
13
|
+
|
14
|
+
# Configuration for OBS client
|
15
|
+
COPY oscrc /root/.oscrc
|
16
|
+
|
17
|
+
COPY certificates/rubygems.crt /etc/pki/trust/anchors/rubygems.pem
|
18
|
+
COPY certificates/api.rubygems.crt /etc/pki/trust/anchors/api.rubygems.pem
|
19
|
+
COPY certificates/obs.crt /etc/pki/trust/anchors/obs.pem
|
20
|
+
|
21
|
+
RUN update-ca-certificates
|
22
|
+
|
23
|
+
COPY run_test.rb /run_test.rb
|
24
|
+
|
25
|
+
# Configuration for gem
|
26
|
+
RUN mkdir /root/.gem
|
27
|
+
RUN echo ":rubygems_api_key: 123" >/root/.gem/credentials
|
28
|
+
RUN chmod 0600 /root/.gem/credentials
|
29
|
+
|
30
|
+
CMD /run_test.rb
|
@@ -0,0 +1 @@
|
|
1
|
+
*.crt
|
@@ -0,0 +1,19 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDCTCCAfGgAwIBAgIJAOgD6vQm36guMA0GCSqGSIb3DQEBBQUAMBsxGTAXBgNV
|
3
|
+
BAMMEGFwaS5ydWJ5Z2Vtcy5vcmcwHhcNMTUwOTMwMTM0MTQ0WhcNMTgwNjI2MTM0
|
4
|
+
MTQ0WjAbMRkwFwYDVQQDDBBhcGkucnVieWdlbXMub3JnMIIBIjANBgkqhkiG9w0B
|
5
|
+
AQEFAAOCAQ8AMIIBCgKCAQEAyLIX5wODf0a4xLLFgJk7XjZ4alx8wOvqIpeKMc6J
|
6
|
+
yKfRp9rJWtHa578KYnLFfKZMSomHiIz1OdA+MWkOL6pt8kjJ8ciYahJ4q0gRx8+k
|
7
|
+
8bwXWBheh2gwm4jPubX+yBvXNn1Jc1+szQyJfVR7Yt1CNDTUnYuoL9K1qCxhaSiK
|
8
|
+
bVTu0IylVfeu83frV+hrr9IAveWZs6pD0Rm1j/V0cEbnaqWvppnHoO1HCh94rJRz
|
9
|
+
42iMVBIUeImwpmP2Eh6Q/1EvERoSP4GK3h8fGgO21iRcaj4/idUmYYd73ih7KHWu
|
10
|
+
FmIdK37nnyEu45dhuAnKb/OUEGW9Z07EFgy8bWDlrEMd4wIDAQABo1AwTjAdBgNV
|
11
|
+
HQ4EFgQUgEX0+CP53wGNF3VaOfk2QOGj/A0wHwYDVR0jBBgwFoAUgEX0+CP53wGN
|
12
|
+
F3VaOfk2QOGj/A0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAkMvw
|
13
|
+
NjeKUX/5S5dhR2fl7rsmVYhXGhFUmT8R8LXMIYsbeLkukBElEH+RIsn+GHkhww0x
|
14
|
+
imQsYKfy+ACmLUQ22rCwCu2UOyQGc+14T/RCHHwDSEMkz1Rnggo5SfjZ7NXLPnrE
|
15
|
+
c7iPlryxt8sMQXgjqZB7BzAFQVQmeRT8UKWhWYJeGvppmiOeY6E2xPiw6udlIeXA
|
16
|
+
Qr1qMIPZYrQIZY7+BJvB/oW9LDOgQH+IZ8clCHRajzE62KnwGEN+BzdycaulRHEK
|
17
|
+
xggqcxq4AGQ/VfY6JEwqERC+Px0ZW3Oi2M/KilfbeXk1QTd47fWFFI1Zqa5LLVJt
|
18
|
+
A+Ujm0Psp39L70exAg==
|
19
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require "sinatra"
|
4
|
+
|
5
|
+
require_relative "../server/server_base.rb"
|
6
|
+
|
7
|
+
CERT_DIR = File.dirname(__FILE__)
|
8
|
+
|
9
|
+
configure_sinatra
|
10
|
+
|
11
|
+
get '/' do
|
12
|
+
"I'm the secure rubygems server\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
post '/api/v1/gems' do
|
16
|
+
"Successfully registered gem: red_herring (0.0.2)"
|
17
|
+
end
|
18
|
+
|
19
|
+
get '/api/v1/versions/red_herring.json' do
|
20
|
+
json = <<EOT
|
21
|
+
[{
|
22
|
+
"authors": "Cornelius Schumacher",
|
23
|
+
"built_at": "2015-06-20T00:00:00.000Z",
|
24
|
+
"created_at": "2015-06-20T06:57:40.675Z",
|
25
|
+
"description": "This is a red herring.",
|
26
|
+
"downloads_count": 371,
|
27
|
+
"metadata": {},
|
28
|
+
"number": "0.0.1",
|
29
|
+
"summary": "Red herring",
|
30
|
+
"platform": "ruby",
|
31
|
+
"ruby_version": "\u003e= 0",
|
32
|
+
"prerelease": false,
|
33
|
+
"licenses": ["MIT"],
|
34
|
+
"requirements": [],
|
35
|
+
"sha": "8afa714a16b34800a3f4e7aefcab871cf98aca33d02758e9c9d658cbf4bd740b"
|
36
|
+
}]
|
37
|
+
EOT
|
38
|
+
json
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
FROM opensuse
|
2
|
+
MAINTAINER Cornelius Schumacher <schumacher@kde.org>
|
3
|
+
|
4
|
+
RUN zypper --non-interactive install ruby
|
5
|
+
RUN zypper --non-interactive install ruby-devel gcc-c++ make libopenssl-devel
|
6
|
+
RUN zypper --non-interactive install ca-certificates-mozilla
|
7
|
+
RUN zypper --non-interactive install curl
|
8
|
+
RUN gem install --no-document sinatra thin
|
9
|
+
|
10
|
+
COPY server_base.rb /httpotemkin/server/
|
11
|
+
COPY secure_server.rb /httpotemkin/server/
|
12
|
+
COPY server.rb /httpotemkin/server/
|
13
|
+
|
14
|
+
CMD /httpotemkin/server/server.rb -p 80
|
15
|
+
|
16
|
+
EXPOSE 80
|
17
|
+
EXPOSE 443
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require "thin"
|
4
|
+
|
5
|
+
class SecureServer < ::Thin::Backends::TcpServer
|
6
|
+
def initialize(host, port, options)
|
7
|
+
super(host, port)
|
8
|
+
@ssl = true
|
9
|
+
@ssl_options = options
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def configure_sinatra
|
14
|
+
configure do
|
15
|
+
set :environment, :production
|
16
|
+
set :bind, '0.0.0.0'
|
17
|
+
set :port, 443
|
18
|
+
set :server, "thin"
|
19
|
+
class << settings
|
20
|
+
def server_settings
|
21
|
+
{
|
22
|
+
:backend => SecureServer,
|
23
|
+
:private_key_file => CERT_DIR + "/server.key",
|
24
|
+
:cert_chain_file => CERT_DIR + "/server.crt",
|
25
|
+
:verify_peer => false
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
openssl x509 -in server.crt -text -noout
|
data/expected_output
ADDED
data/httpotemkin.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'httpotemkin'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.license = 'MIT'
|
5
|
+
s.platform = Gem::Platform::RUBY
|
6
|
+
s.authors = ['Cornelius Schumacher']
|
7
|
+
s.email = ['schumacher@kde.org']
|
8
|
+
s.homepage = 'https://github.com/cornelius/httpotemkin'
|
9
|
+
s.summary = 'Mock HTTP services for system tests'
|
10
|
+
s.description = 'Httpotemkin provides tools to mock HTTP servers for system-level tests.'
|
11
|
+
s.required_rubygems_version = '>= 1.3.6'
|
12
|
+
s.rubyforge_project = 'httpotemkin'
|
13
|
+
|
14
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
15
|
+
s.add_development_dependency 'given_filesystem', '>= 0.1.2'
|
16
|
+
s.add_development_dependency 'cli_tester', '>= 0.0.2'
|
17
|
+
|
18
|
+
s.add_runtime_dependency 'terminal-table', '~> 1.5'
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.require_path = 'lib'
|
22
|
+
s.executables = ["httpotemkin"]
|
23
|
+
end
|
data/lib/client.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Httpotemkin
|
2
|
+
class Client
|
3
|
+
def initialize(containers)
|
4
|
+
@containers = containers
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute(cmd, working_directory: nil)
|
8
|
+
@out = @containers.exec_client(cmd, working_directory: working_directory)
|
9
|
+
end
|
10
|
+
|
11
|
+
def exit_code
|
12
|
+
0
|
13
|
+
end
|
14
|
+
|
15
|
+
def out
|
16
|
+
@out
|
17
|
+
end
|
18
|
+
|
19
|
+
def err
|
20
|
+
""
|
21
|
+
end
|
22
|
+
|
23
|
+
def inject_tarball(filename)
|
24
|
+
Cheetah.run(["cat", filename], ["docker", "cp", "-", "client:/"])
|
25
|
+
end
|
26
|
+
|
27
|
+
def install_gem_from_spec(specfile)
|
28
|
+
Dir.chdir(File.dirname(specfile)) do
|
29
|
+
out = Cheetah.run(["gem", "build", File.basename(specfile)],
|
30
|
+
stdout: :capture)
|
31
|
+
gemfile = out[/File: (.*)\n/, 1]
|
32
|
+
@containers.run_docker(["cp", gemfile, "client:/tmp"])
|
33
|
+
@containers.run_docker(["exec", "client", "gem", "install", "--local",
|
34
|
+
File.join("/tmp", gemfile)])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/containers.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
module Httpotemkin
|
2
|
+
class Containers
|
3
|
+
attr_accessor :out
|
4
|
+
attr_accessor :log_dir
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@servers = []
|
8
|
+
@host_names = {
|
9
|
+
"rubygems" => "rubygems.org",
|
10
|
+
"api.rubygems" => "api.rubygems.org",
|
11
|
+
"obs" => "api.opensuse.org",
|
12
|
+
"server" => "server"
|
13
|
+
}
|
14
|
+
@out = $stdout
|
15
|
+
@err = $stderr
|
16
|
+
@log_dir = ENV["HTTPOTEMKIN_LOGS_DIR"] || "logs"
|
17
|
+
end
|
18
|
+
|
19
|
+
def run_docker(args, options = {})
|
20
|
+
cmd = ["docker"] + args
|
21
|
+
@out.puts cmd.join(" ")
|
22
|
+
out = Cheetah.run(cmd, options.merge({stdout: :capture}))
|
23
|
+
@out.puts out if out && !out.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def host_name(name)
|
27
|
+
@host_names[name]
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_server(name)
|
31
|
+
@servers.push(name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def print_status
|
35
|
+
running = `docker ps --format="{{.Names}}"`.split("\n")
|
36
|
+
table = Terminal::Table.new(headings: ["Container", "Status"]) do |t|
|
37
|
+
status.each do |container, status|
|
38
|
+
t.add_row([container, status ? "up" : "down"])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
@out.puts table
|
43
|
+
end
|
44
|
+
|
45
|
+
def status
|
46
|
+
@status ||= {}
|
47
|
+
if @status.empty?
|
48
|
+
running = `docker ps --format="{{.Names}}"`.split("\n")
|
49
|
+
@servers.each do |server|
|
50
|
+
@status[server] = running.include?(server)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@status
|
54
|
+
end
|
55
|
+
|
56
|
+
def up
|
57
|
+
@servers.each do |server|
|
58
|
+
if status[server]
|
59
|
+
@out.puts "'#{server}' already up"
|
60
|
+
else
|
61
|
+
run_docker(["run", "--name=#{server}", "-d", server])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def down(save_logs: false)
|
67
|
+
@servers.each do |server|
|
68
|
+
if (save_logs)
|
69
|
+
log_file = File.open(File.join(@log_dir,"#{server}.log"), "w")
|
70
|
+
run_docker(["logs", server], stderr: log_file)
|
71
|
+
end
|
72
|
+
if status[server]
|
73
|
+
run_docker(["rm", "-f", server])
|
74
|
+
else
|
75
|
+
@out.puts "'#{server}' already down"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def start_client
|
81
|
+
if links.empty?
|
82
|
+
run_docker(["run", "--name=client", "-d", "client", "sleep", "infinity"])
|
83
|
+
else
|
84
|
+
run_docker(["run"] + links + ["--name=client", "-d", "client", "sleep", "infinity"])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def stop_client
|
89
|
+
run_docker(["rm", "-f", "client"])
|
90
|
+
end
|
91
|
+
|
92
|
+
def exec_client(client_cmd, working_directory: nil)
|
93
|
+
cmd = ["docker", "exec", "client"]
|
94
|
+
if working_directory
|
95
|
+
cmd += ["sh", "-c", "cd #{working_directory}; #{client_cmd.join(' ')}"]
|
96
|
+
else
|
97
|
+
cmd += client_cmd
|
98
|
+
end
|
99
|
+
@out.puts cmd.join(" ")
|
100
|
+
Cheetah.run(cmd, stdout: :capture)
|
101
|
+
end
|
102
|
+
|
103
|
+
def links
|
104
|
+
@servers.map { |name| "--link=#{name}:#{host_name(name)}" }
|
105
|
+
end
|
106
|
+
|
107
|
+
def run_client
|
108
|
+
cmd = "docker run #{links.join(" ")} client"
|
109
|
+
@out.puts cmd
|
110
|
+
`#{cmd}`
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/httpotemkin.rb
ADDED
data/lib/test.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Httpotemkin
|
2
|
+
class Test
|
3
|
+
def initialize(out: $stdout)
|
4
|
+
@containers = Containers.new
|
5
|
+
@containers.out = out
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_server(name)
|
9
|
+
@containers.add_server(name)
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
@containers.up
|
14
|
+
@containers.start_client
|
15
|
+
begin
|
16
|
+
yield Client.new(@containers)
|
17
|
+
ensure
|
18
|
+
@containers.stop_client
|
19
|
+
@containers.down
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/logs/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.log
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
include CliTester
|
4
|
+
include GivenFilesystemSpecHelpers
|
5
|
+
|
6
|
+
describe "command line interface" do
|
7
|
+
describe "help" do
|
8
|
+
it "shows help" do
|
9
|
+
expected_output = <<-EOT
|
10
|
+
Usage: httpotemkin <command>
|
11
|
+
|
12
|
+
Testing with HTTP mocks based on containers
|
13
|
+
|
14
|
+
Commands:
|
15
|
+
status - Show status of containers
|
16
|
+
up - Start containers
|
17
|
+
down - Stop containters
|
18
|
+
client - Run client
|
19
|
+
run - Run tests
|
20
|
+
help - Show command line help
|
21
|
+
EOT
|
22
|
+
expect(run_command(args: ["help"])).to exit_with_success(expected_output)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "status" do
|
27
|
+
it "shows status" do
|
28
|
+
with_stubbed_binary("bin/status/docker") do
|
29
|
+
expected_output = <<-EOT
|
30
|
+
+--------------+--------+
|
31
|
+
| Container | Status |
|
32
|
+
+--------------+--------+
|
33
|
+
| rubygems | up |
|
34
|
+
| api.rubygems | down |
|
35
|
+
| obs | up |
|
36
|
+
+--------------+--------+
|
37
|
+
EOT
|
38
|
+
expect(run_command(args: ["status"])).to exit_with_success(expected_output)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "up" do
|
44
|
+
it "starts containers" do
|
45
|
+
with_stubbed_binary("bin/up/docker") do
|
46
|
+
expected_output = <<-EOT
|
47
|
+
Starting server containers
|
48
|
+
'rubygems' already up
|
49
|
+
'api.rubygems' already up
|
50
|
+
docker run --name=obs -d obs
|
51
|
+
123
|
52
|
+
EOT
|
53
|
+
expect(run_command(args: ["up"])).to exit_with_success(expected_output)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "down" do
|
59
|
+
it "stops containers" do
|
60
|
+
with_stubbed_binary("bin/down/docker") do
|
61
|
+
expected_output = <<-EOT
|
62
|
+
Stopping server containers
|
63
|
+
docker rm -f rubygems
|
64
|
+
rubygems
|
65
|
+
docker rm -f api.rubygems
|
66
|
+
api.rubygems
|
67
|
+
'obs' already down
|
68
|
+
EOT
|
69
|
+
expect(run_command(args: ["down"])).to exit_with_success(expected_output)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "client" do
|
75
|
+
it "runs client" do
|
76
|
+
with_stubbed_binary("bin/client/docker") do
|
77
|
+
expected_output = <<-EOT
|
78
|
+
docker run --link=rubygems:rubygems.org --link=api.rubygems:api.rubygems.org --link=obs:api.opensuse.org client
|
79
|
+
Hopss
|
80
|
+
Hopss
|
81
|
+
Hopss
|
82
|
+
EOT
|
83
|
+
expect(run_command(args: ["client"])).to exit_with_success(expected_output)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "run" do
|
89
|
+
use_given_filesystem
|
90
|
+
|
91
|
+
before(:each) do
|
92
|
+
@logs_dir = given_directory
|
93
|
+
ENV["HTTPOTEMKIN_LOGS_DIR"] = @logs_dir
|
94
|
+
end
|
95
|
+
|
96
|
+
it "runs test and succeeds" do
|
97
|
+
with_stubbed_binary("bin/run.success/docker") do
|
98
|
+
expected_output = <<-EOT
|
99
|
+
Running tests
|
100
|
+
docker run --name=rubygems -d rubygems
|
101
|
+
docker run --name=api.rubygems -d api.rubygems
|
102
|
+
docker run --name=obs -d obs
|
103
|
+
docker run --link=rubygems:rubygems.org --link=api.rubygems:api.rubygems.org --link=obs:api.opensuse.org client
|
104
|
+
--- Start Test ---
|
105
|
+
Hopss
|
106
|
+
Hopss
|
107
|
+
Hopss
|
108
|
+
---- End Test ----
|
109
|
+
docker logs rubygems
|
110
|
+
'rubygems' already down
|
111
|
+
docker logs api.rubygems
|
112
|
+
'api.rubygems' already down
|
113
|
+
docker logs obs
|
114
|
+
'obs' already down
|
115
|
+
|
116
|
+
Success.
|
117
|
+
EOT
|
118
|
+
expect(run_command(args: ["run"])).to exit_with_success(expected_output)
|
119
|
+
expect(File.exist?(File.join(@logs_dir, "rubygems.log"))).to be(true)
|
120
|
+
expect(File.exist?(File.join(@logs_dir, "api.rubygems.log"))).to be(true)
|
121
|
+
expect(File.exist?(File.join(@logs_dir, "obs.log"))).to be(true)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
it "runs test and fails" do
|
126
|
+
with_stubbed_binary("bin/run.failure/docker") do
|
127
|
+
expected_output = <<-EOT
|
128
|
+
Running tests
|
129
|
+
docker run --name=rubygems -d rubygems
|
130
|
+
docker run --name=api.rubygems -d api.rubygems
|
131
|
+
docker run --name=obs -d obs
|
132
|
+
docker run --link=rubygems:rubygems.org --link=api.rubygems:api.rubygems.org --link=obs:api.opensuse.org client
|
133
|
+
--- Start Test ---
|
134
|
+
error
|
135
|
+
---- End Test ----
|
136
|
+
docker logs rubygems
|
137
|
+
'rubygems' already down
|
138
|
+
docker logs api.rubygems
|
139
|
+
'api.rubygems' already down
|
140
|
+
docker logs obs
|
141
|
+
'obs' already down
|
142
|
+
|
143
|
+
Failed.
|
144
|
+
|
145
|
+
Expected output:
|
146
|
+
Hopss
|
147
|
+
Hopss
|
148
|
+
Hopss
|
149
|
+
|
150
|
+
Actual output:
|
151
|
+
error
|
152
|
+
EOT
|
153
|
+
expect(run_command(args: ["run"])).to exit_with_error(1, "", expected_output)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "cli_tester"
|
2
|
+
require "given_filesystem/spec_helpers"
|
3
|
+
|
4
|
+
ENV["RUN_BY_RSPEC"] = "1"
|
5
|
+
|
6
|
+
def with_stubbed_binary(bin_path)
|
7
|
+
saved_path = ENV["PATH"]
|
8
|
+
|
9
|
+
full_path = File.expand_path("../", __FILE__)
|
10
|
+
full_path = File.join(full_path, bin_path)
|
11
|
+
if !File.exist?(full_path)
|
12
|
+
STDERR.puts "Error: stubbing binary #{full_path} does not exist"
|
13
|
+
else
|
14
|
+
path = File.dirname(full_path)
|
15
|
+
ENV["PATH"] = path + ":" + ENV["PATH"]
|
16
|
+
end
|
17
|
+
|
18
|
+
yield
|
19
|
+
|
20
|
+
ENV["PATH"] = saved_path
|
21
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
include GivenFilesystemSpecHelpers
|
4
|
+
|
5
|
+
describe "client" do
|
6
|
+
use_given_filesystem(keep_files: true)
|
7
|
+
|
8
|
+
it "injects tarballs" do
|
9
|
+
out = double
|
10
|
+
allow(out).to receive(:puts)
|
11
|
+
|
12
|
+
test = Httpotemkin::Test.new(out: out)
|
13
|
+
|
14
|
+
test.run do |client|
|
15
|
+
client.inject_tarball("spec/system/data/test.tar.gz")
|
16
|
+
|
17
|
+
client.execute(["ls", "test"])
|
18
|
+
|
19
|
+
expect(client.exit_code).to eq(0)
|
20
|
+
expect(client.out).to eq("mydir\nmyfile\n")
|
21
|
+
expect(client.err.empty?).to be(true)
|
22
|
+
|
23
|
+
client.execute(["ls", "test/mydir"])
|
24
|
+
|
25
|
+
expect(client.exit_code).to eq(0)
|
26
|
+
expect(client.out).to eq("myotherfile1\nmyotherfile2\n")
|
27
|
+
expect(client.err.empty?).to be(true)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "installs gem from spec" do
|
32
|
+
out = double
|
33
|
+
allow(out).to receive(:puts)
|
34
|
+
|
35
|
+
test = Httpotemkin::Test.new(out: out)
|
36
|
+
|
37
|
+
test.run do |client|
|
38
|
+
gem_tarball = File.expand_path("../data/red_herring-000.tar.gz", __FILE__)
|
39
|
+
|
40
|
+
gem_dir = given_directory
|
41
|
+
Dir.chdir(gem_dir) do
|
42
|
+
Cheetah.run(["tar", "xzf", gem_tarball])
|
43
|
+
end
|
44
|
+
|
45
|
+
client.install_gem_from_spec(File.join(gem_dir, "red_herring",
|
46
|
+
"red_herring.gemspec"))
|
47
|
+
|
48
|
+
client.execute(["gem", "list", "red_herring"])
|
49
|
+
|
50
|
+
expect(client.exit_code).to eq(0)
|
51
|
+
expect(client.out).to match("red_herring (0.0.1)\n")
|
52
|
+
expect(client.err.empty?).to be(true)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "executes command in custom working directory" do
|
57
|
+
out = double
|
58
|
+
allow(out).to receive(:puts)
|
59
|
+
|
60
|
+
test = Httpotemkin::Test.new(out: out)
|
61
|
+
|
62
|
+
test.run do |client|
|
63
|
+
client.execute(["ls"], working_directory: "/srv")
|
64
|
+
|
65
|
+
expect(client.exit_code).to eq(0)
|
66
|
+
expect(client.out).to eq("ftp\nwww\n")
|
67
|
+
expect(client.err.empty?).to be(true)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
describe "hello world" do
|
4
|
+
it "answers" do
|
5
|
+
out = double
|
6
|
+
allow(out).to receive(:puts)
|
7
|
+
|
8
|
+
test = Httpotemkin::Test.new(out: out)
|
9
|
+
test.add_server("server")
|
10
|
+
|
11
|
+
test.run do |client|
|
12
|
+
sleep 2
|
13
|
+
client.execute(["curl", "server/hello"])
|
14
|
+
|
15
|
+
expect(client.exit_code).to eq(0)
|
16
|
+
expect(client.out).to eq("world\n")
|
17
|
+
expect(client.err.empty?).to be(true)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: httpotemkin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cornelius Schumacher
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: given_filesystem
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.2
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: cli_tester
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.0.2
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.0.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: terminal-table
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.5'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.5'
|
69
|
+
description: Httpotemkin provides tools to mock HTTP servers for system-level tests.
|
70
|
+
email:
|
71
|
+
- schumacher@kde.org
|
72
|
+
executables:
|
73
|
+
- httpotemkin
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- ".rspec"
|
79
|
+
- CONTRIBUTING.md
|
80
|
+
- Gemfile
|
81
|
+
- MIT-LICENSE
|
82
|
+
- README.md
|
83
|
+
- TODO
|
84
|
+
- bin/httpotemkin
|
85
|
+
- containers/api.rubygems/Dockerfile
|
86
|
+
- containers/api.rubygems/latest_specs.4.8.gz
|
87
|
+
- containers/api.rubygems/rubygems-update-2.5.0.gemspec.rz
|
88
|
+
- containers/api.rubygems/secure_server.rb
|
89
|
+
- containers/build_containers
|
90
|
+
- containers/client/Dockerfile
|
91
|
+
- containers/client/certificates/.gitignore
|
92
|
+
- containers/client/oscrc
|
93
|
+
- containers/client/run_test.rb
|
94
|
+
- containers/client/server.crt
|
95
|
+
- containers/obs/Dockerfile
|
96
|
+
- containers/obs/secure_server.rb
|
97
|
+
- containers/rubygems/Dockerfile
|
98
|
+
- containers/rubygems/secure_server.rb
|
99
|
+
- containers/server/Dockerfile
|
100
|
+
- containers/server/secure_server.rb
|
101
|
+
- containers/server/server.rb
|
102
|
+
- containers/server/server_base.rb
|
103
|
+
- containers/show_certificate.sh
|
104
|
+
- expected_output
|
105
|
+
- httpotemkin.gemspec
|
106
|
+
- lib/client.rb
|
107
|
+
- lib/containers.rb
|
108
|
+
- lib/httpotemkin.rb
|
109
|
+
- lib/test.rb
|
110
|
+
- logs/.gitignore
|
111
|
+
- spec/integration/bin/client/docker
|
112
|
+
- spec/integration/bin/down/docker
|
113
|
+
- spec/integration/bin/run.failure/docker
|
114
|
+
- spec/integration/bin/run.success/docker
|
115
|
+
- spec/integration/bin/status/docker
|
116
|
+
- spec/integration/bin/up/docker
|
117
|
+
- spec/integration/cli_spec.rb
|
118
|
+
- spec/integration/spec_helper.rb
|
119
|
+
- spec/system/client_spec.rb
|
120
|
+
- spec/system/data/red_herring-000.tar.gz
|
121
|
+
- spec/system/data/test.tar.gz
|
122
|
+
- spec/system/hello_world_spec.rb
|
123
|
+
- spec/system/spec_helper.rb
|
124
|
+
homepage: https://github.com/cornelius/httpotemkin
|
125
|
+
licenses:
|
126
|
+
- MIT
|
127
|
+
metadata: {}
|
128
|
+
post_install_message:
|
129
|
+
rdoc_options: []
|
130
|
+
require_paths:
|
131
|
+
- lib
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 1.3.6
|
142
|
+
requirements: []
|
143
|
+
rubyforge_project: httpotemkin
|
144
|
+
rubygems_version: 2.2.2
|
145
|
+
signing_key:
|
146
|
+
specification_version: 4
|
147
|
+
summary: Mock HTTP services for system tests
|
148
|
+
test_files: []
|
149
|
+
has_rdoc:
|