delphix_rb 0.0.0 → 0.1.0

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: 13023a01757e83dc91794bc757a79e93ccf5307f
4
- data.tar.gz: d54f385a9686a0cdad0b5e139dc7b80d384e5de8
3
+ metadata.gz: 7350c64cf8d446bda6529be6aa7385bd399fce50
4
+ data.tar.gz: 2a8d077c2ee44e55bfca06da8714b795afd36ec7
5
5
  SHA512:
6
- metadata.gz: f99321f8f6b734f43100a9af3a4f8bc4a9e1c15b80cf47cf05c6489bdb2bc174a2249d1d40b84987acb03ea927c2ce7543ef2588dfa0c7f4dbffb1d1ecc7a0e6
7
- data.tar.gz: 6cf8be136611eacb0c120d7fcb21f92982abbcdff830095a24652bb9221cc37b7ec44161343eed50598ec547b2785434d7b1e4956e19871c81d01ceea9039cd2
6
+ metadata.gz: ce887be1591ee007c0be1b187e34648a2519f9ad0cd1d94526d7b46807dda4c49c524f4b8ff883e0a2cfc948271a3fda36414173fd8ef01f259ba4822b1ff378
7
+ data.tar.gz: d5527ab922eb63d2c5b62b62d98a29ad7a49ca925a3fdf67af0020540681753c0ccd290966a0c903f802f026110ec5d4b15ba6572db2aee56b772048c29575c6
data/Gemfile CHANGED
@@ -1,9 +1,7 @@
1
1
  source "http://rubygems.org"
2
- # Add dependencies required to use your gem here.
3
- # Example:
4
- # gem "activesupport", ">= 2.3.5"
5
2
 
6
- # Add dependencies to develop your gem here.
3
+ #gem 'excon', '~> 0.45.4'
4
+
7
5
  # Include everything needed to run rake, tests, features, etc.
8
6
  group :development do
9
7
  gem "shoulda", ">= 0"
data/Rakefile CHANGED
@@ -21,7 +21,14 @@ Jeweler::Tasks.new do |gem|
21
21
  gem.description = %Q{Delphix Engine REST API}
22
22
  gem.email = "michael.kuehl@delphix.com"
23
23
  gem.authors = ["Michael Kuehl"]
24
+
25
+ # exclude tests and examples
26
+ gem.files.exclude 'examples/*'
27
+ gem.files.exclude 'test/*'
28
+
24
29
  # dependencies defined in Gemfile
30
+ gem.add_dependency 'excon', '~> 0.45.4'
31
+
25
32
  end
26
33
  Jeweler::RubygemsDotOrgTasks.new
27
34
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.1.0
data/delphix_rb.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: delphix_rb 0.0.0 ruby lib
5
+ # stub: delphix_rb 0.1.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "delphix_rb"
9
- s.version = "0.0.0"
9
+ s.version = "0.1.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Michael Kuehl"]
14
- s.date = "2016-01-12"
14
+ s.date = "2016-01-22"
15
15
  s.description = "Delphix Engine REST API"
16
16
  s.email = "michael.kuehl@delphix.com"
17
17
  s.extra_rdoc_files = [
@@ -28,9 +28,15 @@ Gem::Specification.new do |s|
28
28
  "Rakefile",
29
29
  "VERSION",
30
30
  "delphix_rb.gemspec",
31
- "lib/delphix_rb.rb",
32
- "test/helper.rb",
33
- "test/test_delphix_rb.rb"
31
+ "lib/delphix.rb",
32
+ "lib/delphix/base.rb",
33
+ "lib/delphix/base_array.rb",
34
+ "lib/delphix/connection.rb",
35
+ "lib/delphix/environment.rb",
36
+ "lib/delphix/error.rb",
37
+ "lib/delphix/group.rb",
38
+ "lib/delphix/repository.rb",
39
+ "lib/delphix/util.rb"
34
40
  ]
35
41
  s.homepage = "http://github.com/mickuehl/delphix_rb"
36
42
  s.licenses = ["MIT"]
@@ -46,12 +52,14 @@ Gem::Specification.new do |s|
46
52
  s.add_development_dependency(%q<bundler>, ["~> 1.0"])
47
53
  s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
48
54
  s.add_development_dependency(%q<simplecov>, [">= 0"])
55
+ s.add_runtime_dependency(%q<excon>, ["~> 0.45.4"])
49
56
  else
50
57
  s.add_dependency(%q<shoulda>, [">= 0"])
51
58
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
52
59
  s.add_dependency(%q<bundler>, ["~> 1.0"])
53
60
  s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
54
61
  s.add_dependency(%q<simplecov>, [">= 0"])
62
+ s.add_dependency(%q<excon>, ["~> 0.45.4"])
55
63
  end
56
64
  else
57
65
  s.add_dependency(%q<shoulda>, [">= 0"])
@@ -59,6 +67,7 @@ Gem::Specification.new do |s|
59
67
  s.add_dependency(%q<bundler>, ["~> 1.0"])
60
68
  s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
61
69
  s.add_dependency(%q<simplecov>, [">= 0"])
70
+ s.add_dependency(%q<excon>, ["~> 0.45.4"])
62
71
  end
63
72
  end
64
73
 
@@ -0,0 +1,53 @@
1
+
2
+ module Delphix::Base
3
+ include Delphix::Error
4
+
5
+ attr_accessor :connection, :info, :details
6
+
7
+ # The private new method accepts a connection and a hash
8
+ def initialize(connection, info={})
9
+ unless connection.is_a?(Delphix::Connection)
10
+ raise ArgumentError, "Expected a Delphix::Connection, got: #{connection}."
11
+ end
12
+
13
+ @connection, @info = connection, info
14
+ @details = nil
15
+ end
16
+
17
+ def type
18
+ @info['type'] || 'unknown'
19
+ end
20
+
21
+ def name
22
+ @info['name'] || ''
23
+ end
24
+
25
+ def reference
26
+ @info['reference'] || ''
27
+ end
28
+
29
+ def details
30
+ @details ||= refresh_details
31
+ @info = @details
32
+ @details
33
+ end
34
+
35
+ def refresh_details
36
+ # Placeholder. Subclasses need to implement this
37
+ end
38
+
39
+ def to_s
40
+ "#{self.class.name}[#{type}, #{name}, #{reference}]"
41
+ end
42
+
43
+ # a generic get method, used when there is not specialized method to invoke an API call
44
+ def get(endpoint, payload)
45
+ @connection.get( endpoint, {}, :body => payload)
46
+ end
47
+
48
+ # a generic get method, used when there is not specialized method to invoke an API call
49
+ def post(endpoint, payload)
50
+ @connection.post( endpoint, {}, :body => payload)
51
+ end
52
+
53
+ end
@@ -0,0 +1,28 @@
1
+
2
+ class Delphix::BaseArray < Array
3
+
4
+ def lookup_by_ref(ref)
5
+ return nil if self.size == 0
6
+ self.each do |o|
7
+ return o if o.reference == ref
8
+ end
9
+ return nil
10
+ end
11
+
12
+ def lookup_by_name(name)
13
+ return nil if self.size == 0
14
+ self.each do |o|
15
+ return o if o.name == name
16
+ end
17
+ return nil
18
+ end
19
+
20
+ def lookup_by_type(type)
21
+ return nil if self.size == 0
22
+ self.each do |o|
23
+ return o if o.type == type
24
+ end
25
+ return nil
26
+ end
27
+
28
+ end
@@ -0,0 +1,105 @@
1
+ # This class represents a Connection to a Delphix Engine. The Connection is
2
+ # immutable in that once the url and options is set they cannot be changed.
3
+ class Delphix::Connection
4
+ include Delphix::Error
5
+
6
+ attr_reader :url, :options
7
+
8
+ def initialize(url, opts)
9
+ case
10
+ when !url.is_a?(String)
11
+ raise ArgumentError, "Expected a String, got: '#{url}'"
12
+ when !opts.is_a?(Hash)
13
+ raise ArgumentError, "Expected a Hash, got: '#{opts}'"
14
+ else
15
+ @url, @options = url, opts
16
+
17
+ # new HTTP client and session
18
+ @session = Excon.new(url, options)
19
+ @session_cookie = nil
20
+ end
21
+ end
22
+
23
+ # The actual client that sends HTTP methods to the Delphix Engine.
24
+ def session
25
+ @session
26
+ end
27
+
28
+ private :session
29
+
30
+ # Send a request to the server
31
+ def request(*args, &block)
32
+
33
+ request = compile_request_params(*args, &block)
34
+
35
+ log_request(request) if Delphix.debug
36
+
37
+ # execute the request and grao the session cookie if not already set
38
+ response = session.request(request)
39
+ @session_cookie ||= cookie?(response)
40
+
41
+ log_response(response) if Delphix.debug
42
+
43
+ return Delphix::Util.parse_json( response.body)
44
+
45
+ rescue Excon::Errors::BadRequest => ex
46
+ raise ClientError, ex.response.body
47
+ rescue Excon::Errors::Unauthorized => ex
48
+ raise UnauthorizedError, ex.response.body
49
+ rescue Excon::Errors::NotFound => ex
50
+ raise NotFoundError, ex.response.body
51
+ rescue Excon::Errors::Conflict => ex
52
+ raise ConflictError, ex.response.body
53
+ rescue Excon::Errors::InternalServerError => ex
54
+ raise ServerError, ex.response.body
55
+ rescue Excon::Errors::Timeout => ex
56
+ raise TimeoutError, ex.message
57
+ end
58
+
59
+ # Delegate all HTTP methods to the #request.
60
+ [:get, :put, :post, :delete].each do |method|
61
+ define_method(method) { |*args, &block| request(method, *args, &block) }
62
+ end
63
+
64
+ def log_request(request)
65
+ puts "#{[request[:method], request[:path], request[:body]]}" if Delphix.debug
66
+ end
67
+
68
+ def log_response(response)
69
+ puts "#{[response.headers, response.body]}" if Delphix.debug
70
+ end
71
+
72
+ def to_s
73
+ "Delphix::Connection { :url => #{url}, :options => #{options} }"
74
+ end
75
+
76
+ private
77
+
78
+ # Given an HTTP method, path, optional query, extra options, and block compiles a request.
79
+ def compile_request_params(http_method, path, query = nil, opts = nil, &block)
80
+ query ||= {}
81
+ opts ||= {}
82
+ headers = opts.delete(:headers) || {}
83
+ headers = { 'Cookie' => @session_cookie}.merge(headers) if @session_cookie
84
+ content_type = 'application/json'
85
+ user_agent = "Delphix/Delphix-API"
86
+
87
+ {
88
+ :method => http_method,
89
+ :path => "#{path}",
90
+ :query => query,
91
+ :headers => { 'Content-Type' => content_type,
92
+ 'User-Agent' => user_agent,
93
+ }.merge(headers),
94
+ :expects => (200..204).to_a << 304 << 403 << 500,
95
+ :idempotent => http_method == :get,
96
+ :request_block => block
97
+ }.merge(opts).reject { |_, v| v.nil? }
98
+
99
+ end
100
+
101
+ def cookie?(response)
102
+ response.headers['Set-Cookie']
103
+ end
104
+
105
+ end
@@ -0,0 +1,66 @@
1
+
2
+ require 'delphix/base_array'
3
+ require 'delphix/repository'
4
+
5
+ class Delphix::Environment
6
+ include Delphix::Base
7
+
8
+ def initialize(reference)
9
+ @connection = Delphix.connection
10
+ @info = { 'reference' => reference }
11
+ @details = nil
12
+ end
13
+
14
+ def repositories
15
+ repos = Delphix::BaseArray.new
16
+ result = get('/resources/json/delphix/repository', nil)['result']
17
+ result.each do |o|
18
+ repos << Delphix::Repository.new(connection, o) if o['environment'] == reference
19
+ end
20
+ repos
21
+ end
22
+
23
+ def refresh_details
24
+ @details = get("/resources/json/delphix/environment/#{reference}", nil)['result']
25
+ end
26
+
27
+ def self.create(name, address, port, toolkit_path, username, password)
28
+ body = {
29
+ :type => 'HostEnvironmentCreateParameters',
30
+ :primaryUser => {
31
+ :type => 'EnvironmentUser',
32
+ :name => username,
33
+ :credential => {
34
+ :type => 'PasswordCredential',
35
+ :password => password
36
+ }
37
+ },
38
+ :hostEnvironment => {
39
+ :type => 'UnixHostEnvironment',
40
+ :name => name
41
+ },
42
+ :hostParameters => {
43
+ :type => 'UnixHostCreateParameters',
44
+ :host => {
45
+ :type => 'UnixHost',
46
+ :address => address,
47
+ :sshPort => port,
48
+ :toolkitPath => toolkit_path
49
+ }
50
+ }
51
+ }
52
+ response = Delphix.post('/resources/json/delphix/environment', body.to_json)
53
+
54
+ ref = response['result']
55
+ job = response['job']
56
+
57
+ # create a new skeleton group object
58
+ env = Delphix::Environment.new ref
59
+
60
+ # refresh the object from the DE
61
+ env.details
62
+
63
+ env
64
+ end
65
+
66
+ end
@@ -0,0 +1,39 @@
1
+ # This module holds the Errors for the gem.
2
+
3
+ module Delphix::Error
4
+
5
+ # The default error. It's never actually raised, but can be used to catch all
6
+ # gem-specific errors that are thrown as they all subclass from this.
7
+ class DelphixError < StandardError
8
+ end
9
+
10
+ # Raised when invalid arguments are passed to a method.
11
+ class ArgumentError < DelphixError; end
12
+
13
+ # Raised when a request returns a 400.
14
+ class ClientError < DelphixError; end
15
+
16
+ # Raised when a request returns a 401.
17
+ class UnauthorizedError < DelphixError; end
18
+
19
+ # Raised when a request returns a 404.
20
+ class NotFoundError < DelphixError; end
21
+
22
+ # Raised when a request returns a 500.
23
+ class ServerError < DelphixError; end
24
+
25
+ # Raised when there is an unexpected response code / body.
26
+ class UnexpectedResponseError < DelphixError; end
27
+
28
+ # Raised when there is an incompatible version of Delphix.
29
+ class VersionError < DelphixError; end
30
+
31
+ # Raised when a request times out.
32
+ class TimeoutError < DelphixError; end
33
+
34
+ # Raised when login fails.
35
+ class AuthenticationError < DelphixError; end
36
+
37
+ # Raised when an IO action fails.
38
+ class IOError < DelphixError; end
39
+ end
@@ -0,0 +1,31 @@
1
+
2
+ class Delphix::Group
3
+ include Delphix::Base
4
+
5
+ def initialize(reference)
6
+ @connection = Delphix.connection
7
+ @info = { 'reference' => reference }
8
+ @details = nil
9
+ end
10
+
11
+ def refresh_details
12
+ @details = get("/resources/json/delphix/group/#{reference}", nil)['result']
13
+ end
14
+
15
+ def self.create(name)
16
+ body = {
17
+ :type => 'Group',
18
+ :name => name
19
+ }
20
+ ref = Delphix.post('/resources/json/delphix/group', body.to_json)['result']
21
+
22
+ # create a new skeleton group object
23
+ group = Delphix::Group.new ref
24
+
25
+ # refresh the object from the DE
26
+ group.details
27
+
28
+ group
29
+ end
30
+
31
+ end
@@ -0,0 +1,9 @@
1
+
2
+ class Delphix::Repository
3
+ include Delphix::Base
4
+
5
+ def refresh_details
6
+ @details = get("/resources/json/delphix/repository/#{reference}", nil)['result']
7
+ end
8
+
9
+ end
@@ -0,0 +1,20 @@
1
+ # This module holds shared logic that doesn't really belong anywhere else in the gem.
2
+
3
+ module Delphix::Util
4
+ include Delphix::Error
5
+
6
+ module_function
7
+
8
+ def parse_json(body)
9
+ JSON.parse(body) unless body.nil? || body.empty? || (body == 'null')
10
+ rescue JSON::ParserError => ex
11
+ raise UnexpectedResponseError, ex.message
12
+ end
13
+
14
+ def pretty_print_json(body)
15
+ JSON.pretty_generate( JSON.parse( body))
16
+ end
17
+
18
+ module_function :parse_json, :pretty_print_json
19
+
20
+ end
data/lib/delphix.rb ADDED
@@ -0,0 +1,146 @@
1
+
2
+ require 'json'
3
+ require 'excon'
4
+
5
+ module Delphix
6
+
7
+ require 'delphix/error'
8
+ require 'delphix/util'
9
+ require 'delphix/connection'
10
+ require 'delphix/base'
11
+ require 'delphix/base_array'
12
+ require 'delphix/environment'
13
+ require 'delphix/group'
14
+ require 'delphix/repository'
15
+
16
+ def authenticate!(username,password)
17
+ case
18
+ when !username.is_a?(String)
19
+ raise ArgumentError, "Expected a String, got: '#{username}'"
20
+ when !password.is_a?(String)
21
+ raise ArgumentError, "Expected a String, got: '#{password}'"
22
+ else
23
+ @username, @password = username, password
24
+ end
25
+
26
+ reset_connection!
27
+
28
+ # create a session
29
+ session = {
30
+ :type => 'APISession',
31
+ :version => {
32
+ :type => 'APIVersion', # Delphix Engine 4.3.1.x and above
33
+ :major => 1,
34
+ :minor => 6,
35
+ :micro => 0
36
+ }
37
+ }
38
+ post('/resources/json/delphix/session', session.to_json)
39
+
40
+ # authenticate the session
41
+ auth = {
42
+ :type => 'LoginRequest',
43
+ :username => username,
44
+ :password => password
45
+ }
46
+ post('/resources/json/delphix/login', auth.to_json)
47
+
48
+ end
49
+
50
+ def environments
51
+ envs = BaseArray.new
52
+ result = get('/resources/json/delphix/environment', nil)['result']
53
+ result.each do |o|
54
+ envs << Environment.new(connection, o)
55
+ end
56
+ envs
57
+ end
58
+
59
+ def groups
60
+ groups = BaseArray.new
61
+ result = get('/resources/json/delphix/group', nil)['result']
62
+ result.each do |o|
63
+ groups << Group.new(connection, o)
64
+ end
65
+ groups
66
+ end
67
+
68
+ def repositories
69
+ repos = BaseArray.new
70
+ result = get('/resources/json/delphix/repository', nil)['result']
71
+ result.each do |o|
72
+ repos << Repository.new(connection, o)
73
+ end
74
+ repos
75
+ end
76
+
77
+ def targets
78
+ end
79
+
80
+ def sources
81
+ end
82
+
83
+ # a generic get method, used when there is not specialized method to invoke an API call
84
+ def get(endpoint, payload)
85
+ connection.get( endpoint, {}, :body => payload)
86
+ end
87
+
88
+ # a generic get method, used when there is not specialized method to invoke an API call
89
+ def post(endpoint, payload)
90
+ connection.post( endpoint, {}, :body => payload)
91
+ end
92
+
93
+ def url
94
+ @url ||= env_url
95
+ @url
96
+ end
97
+
98
+ def url=(new_url)
99
+ @url = new_url
100
+ reset_connection!
101
+ end
102
+
103
+ def options
104
+ @options ||= env_options
105
+ end
106
+
107
+ def options=(new_options)
108
+ @options = env_options.merge(new_options || {})
109
+ reset_connection!
110
+ end
111
+
112
+ def connection
113
+ @connection ||= Connection.new(url, options)
114
+ end
115
+
116
+ def reset_connection!
117
+ @connection = nil
118
+ end
119
+
120
+ def debug
121
+ @debug || false
122
+ end
123
+
124
+ def debug=(new_value)
125
+ @debug = new_value
126
+ end
127
+
128
+ private
129
+
130
+ def env_url
131
+ ENV['DELPHIX_URL'] || default_url
132
+ end
133
+
134
+ def default_url
135
+ 'http://localhost'
136
+ end
137
+
138
+ def env_options
139
+ {}
140
+ end
141
+
142
+ module_function :get, :post, :environments, :groups, :repositories, :targets, :sources,
143
+ :authenticate!, :url, :url=, :options, :options=, :connection, :reset_connection!, :debug, :debug=,
144
+ :env_url, :default_url, :env_options
145
+
146
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delphix_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Kuehl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-12 00:00:00.000000000 Z
11
+ date: 2016-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: shoulda
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: excon
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.45.4
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.45.4
83
97
  description: Delphix Engine REST API
84
98
  email: michael.kuehl@delphix.com
85
99
  executables: []
@@ -97,9 +111,15 @@ files:
97
111
  - Rakefile
98
112
  - VERSION
99
113
  - delphix_rb.gemspec
100
- - lib/delphix_rb.rb
101
- - test/helper.rb
102
- - test/test_delphix_rb.rb
114
+ - lib/delphix.rb
115
+ - lib/delphix/base.rb
116
+ - lib/delphix/base_array.rb
117
+ - lib/delphix/connection.rb
118
+ - lib/delphix/environment.rb
119
+ - lib/delphix/error.rb
120
+ - lib/delphix/group.rb
121
+ - lib/delphix/repository.rb
122
+ - lib/delphix/util.rb
103
123
  homepage: http://github.com/mickuehl/delphix_rb
104
124
  licenses:
105
125
  - MIT
data/lib/delphix_rb.rb DELETED
File without changes
data/test/helper.rb DELETED
@@ -1,34 +0,0 @@
1
- require 'simplecov'
2
-
3
- module SimpleCov::Configuration
4
- def clean_filters
5
- @filters = []
6
- end
7
- end
8
-
9
- SimpleCov.configure do
10
- clean_filters
11
- load_adapter 'test_frameworks'
12
- end
13
-
14
- ENV["COVERAGE"] && SimpleCov.start do
15
- add_filter "/.rvm/"
16
- end
17
- require 'rubygems'
18
- require 'bundler'
19
- begin
20
- Bundler.setup(:default, :development)
21
- rescue Bundler::BundlerError => e
22
- $stderr.puts e.message
23
- $stderr.puts "Run `bundle install` to install missing gems"
24
- exit e.status_code
25
- end
26
- require 'test/unit'
27
- require 'shoulda'
28
-
29
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
30
- $LOAD_PATH.unshift(File.dirname(__FILE__))
31
- require 'delphix_rb'
32
-
33
- class Test::Unit::TestCase
34
- end
@@ -1,7 +0,0 @@
1
- require 'helper'
2
-
3
- class TestDelphixRb < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
6
- end
7
- end