yui_rest_client 0.5.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.
- checksums.yaml +7 -0
- data/.github/workflows/publish.yml +33 -0
- data/.github/workflows/test.yml +26 -0
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +41 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +216 -0
- data/Rakefile +20 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/yui_rest_client.rb +42 -0
- data/lib/yui_rest_client/actions.rb +12 -0
- data/lib/yui_rest_client/app.rb +230 -0
- data/lib/yui_rest_client/error.rb +13 -0
- data/lib/yui_rest_client/filter_extractor.rb +28 -0
- data/lib/yui_rest_client/http/http_client.rb +36 -0
- data/lib/yui_rest_client/http/response.rb +21 -0
- data/lib/yui_rest_client/http/version_controller.rb +26 -0
- data/lib/yui_rest_client/http/widget_controller.rb +54 -0
- data/lib/yui_rest_client/local_process.rb +73 -0
- data/lib/yui_rest_client/logger.rb +32 -0
- data/lib/yui_rest_client/timer.rb +20 -0
- data/lib/yui_rest_client/version.rb +6 -0
- data/lib/yui_rest_client/wait.rb +21 -0
- data/lib/yui_rest_client/waitable.rb +39 -0
- data/lib/yui_rest_client/widgets.rb +30 -0
- data/lib/yui_rest_client/widgets/bargraph.rb +62 -0
- data/lib/yui_rest_client/widgets/base.rb +114 -0
- data/lib/yui_rest_client/widgets/button.rb +33 -0
- data/lib/yui_rest_client/widgets/checkbox.rb +53 -0
- data/lib/yui_rest_client/widgets/combobox.rb +95 -0
- data/lib/yui_rest_client/widgets/datefield.rb +47 -0
- data/lib/yui_rest_client/widgets/label.rb +41 -0
- data/lib/yui_rest_client/widgets/menubutton.rb +48 -0
- data/lib/yui_rest_client/widgets/multilinebox.rb +84 -0
- data/lib/yui_rest_client/widgets/numberbox.rb +76 -0
- data/lib/yui_rest_client/widgets/progressbar.rb +45 -0
- data/lib/yui_rest_client/widgets/radiobutton.rb +35 -0
- data/lib/yui_rest_client/widgets/richtext.rb +36 -0
- data/lib/yui_rest_client/widgets/selectionbox.rb +87 -0
- data/lib/yui_rest_client/widgets/tab.rb +81 -0
- data/lib/yui_rest_client/widgets/table.rb +154 -0
- data/lib/yui_rest_client/widgets/textbox.rb +94 -0
- data/lib/yui_rest_client/widgets/timefield.rb +45 -0
- data/lib/yui_rest_client/widgets/tree.rb +149 -0
- data/yui_rest_client.gemspec +38 -0
- metadata +222 -0
@@ -0,0 +1,230 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YuiRestClient
|
4
|
+
class App
|
5
|
+
# Used to initialize main entry point of YuiRestClient and set host and port
|
6
|
+
# for the application under control.
|
7
|
+
# @param host [String] host address (e.g. 'localhost', '192.168.0.1')
|
8
|
+
# @param port [String] port opened for communication (e.g. '9999')
|
9
|
+
def initialize(host:, port:)
|
10
|
+
@host = host
|
11
|
+
@port = port
|
12
|
+
@widget_controller = Http::WidgetController.new(host: host, port: port)
|
13
|
+
@version_controller = Http::VersionController.new(host: host, port: port)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Initializes new instance of Bargraph with the filter provided.
|
17
|
+
# Does not make request to libyui-rest-api.
|
18
|
+
# @param filter [Hash] filter to find a widget
|
19
|
+
# @return [Widgets::Bargraph] new instance of Table
|
20
|
+
# @example
|
21
|
+
# app.bargraph(id: 'id', label: 'label', class: 'YBarGraph')
|
22
|
+
def bargraph(filter)
|
23
|
+
Widgets::Bargraph.new(@widget_controller, FilterExtractor.new(filter))
|
24
|
+
end
|
25
|
+
|
26
|
+
# Initializes new instance of Button with the filter provided.
|
27
|
+
# Does not make request to libyui-rest-api.
|
28
|
+
# @param filter [Hash] filter to find a widget
|
29
|
+
# @return [Widgets::Button] new instance of Button
|
30
|
+
# @example
|
31
|
+
# app.button(id: 'id', label: 'label', class: 'YPushButton')
|
32
|
+
def button(filter)
|
33
|
+
Widgets::Button.new(@widget_controller, FilterExtractor.new(filter))
|
34
|
+
end
|
35
|
+
|
36
|
+
# Initializes new instance of Checkbox with the filter provided.
|
37
|
+
# Does not make request to libyui-rest-api.
|
38
|
+
# @param filter [Hash] filter to find a widget
|
39
|
+
# @return [Widgets::Checkbox] new instance of Checkbox
|
40
|
+
# @example
|
41
|
+
# app.checkbox(id: 'id', label: 'label', class: 'YCheckBox')
|
42
|
+
def checkbox(filter)
|
43
|
+
Widgets::Checkbox.new(@widget_controller, FilterExtractor.new(filter))
|
44
|
+
end
|
45
|
+
|
46
|
+
# Initializes new instance of Combobox with the filter provided.
|
47
|
+
# Does not make request to libyui-rest-api.
|
48
|
+
# @param filter [Hash] filter to find a widget
|
49
|
+
# @return [Widgets::Combobox] new instance of Combobox
|
50
|
+
# @example
|
51
|
+
# app.combobox(id: 'id', label: 'label', class: 'YComboBox')
|
52
|
+
def combobox(filter)
|
53
|
+
Widgets::Combobox.new(@widget_controller, FilterExtractor.new(filter))
|
54
|
+
end
|
55
|
+
|
56
|
+
# Initializes new instance of Datefield with the filter provided.
|
57
|
+
# Does not make request to libyui-rest-api.
|
58
|
+
# @param filter [Hash] filter to find a widget
|
59
|
+
# @return [Widgets::Datefield] new instance of Datefield
|
60
|
+
# @example
|
61
|
+
# app.datefield(id: 'id', label: 'label', class: 'YDateField')
|
62
|
+
def datefield(filter)
|
63
|
+
Widgets::Datefield.new(@widget_controller, FilterExtractor.new(filter))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Initializes new instance of Label with the filter provided.
|
67
|
+
# Does not make request to libyui-rest-api.
|
68
|
+
# @param filter [Hash] filter to find a widget
|
69
|
+
# @return [Widgets::Label] new instance of Label
|
70
|
+
# @example
|
71
|
+
# app.label(id: 'id', label: 'label', class: 'YLabel')
|
72
|
+
def label(filter)
|
73
|
+
Widgets::Label.new(@widget_controller, FilterExtractor.new(filter))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Initializes new instance of Menubutton with the filter provided.
|
77
|
+
# Does not make request to libyui-rest-api.
|
78
|
+
# @param filter [Hash] filter to find a widget
|
79
|
+
# @return [Widgets::Menubutton] new instance of Menubutton
|
80
|
+
# @example
|
81
|
+
# app.menubutton(id: 'id', label: 'label', class: 'YMenuButton')
|
82
|
+
def menubutton(filter)
|
83
|
+
Widgets::Menubutton.new(@widget_controller, FilterExtractor.new(filter))
|
84
|
+
end
|
85
|
+
|
86
|
+
# Initializes new instance of Multilinebox with the filter provided.
|
87
|
+
# Does not make request to libyui-rest-api.
|
88
|
+
# @param filter [Hash] filter to find a widget
|
89
|
+
# @return [Widgets::Multilinebox] new instance of Multilinebox
|
90
|
+
# @example
|
91
|
+
# app.multilinebox(id: 'id', label: 'label', class: 'YMultiLineEdit')
|
92
|
+
def multilinebox(filter)
|
93
|
+
Widgets::Multilinebox.new(@widget_controller, FilterExtractor.new(filter))
|
94
|
+
end
|
95
|
+
|
96
|
+
# Initializes new instance of Numberbox with the filter provided.
|
97
|
+
# Does not make request to libyui-rest-api.
|
98
|
+
# @param filter [Hash] filter to find a widget
|
99
|
+
# @return [Widgets::Numberbox] new instance of Numberbox
|
100
|
+
# @example
|
101
|
+
# app.numberbox(id: 'id', label: 'label', class: 'YIntField')
|
102
|
+
def numberbox(filter)
|
103
|
+
Widgets::Numberbox.new(@widget_controller, FilterExtractor.new(filter))
|
104
|
+
end
|
105
|
+
|
106
|
+
# Initializes new instance of Progressbar with the filter provided.
|
107
|
+
# Does not make request to libyui-rest-api.
|
108
|
+
# @param filter [Hash] filter to find a widget
|
109
|
+
# @return [Widgets::Progressbar] new instance of Progressbar
|
110
|
+
# @example
|
111
|
+
# app.progressbar(id: 'id', label: 'label', class: 'YProgressBar')
|
112
|
+
def progressbar(filter)
|
113
|
+
Widgets::Progressbar.new(@widget_controller, FilterExtractor.new(filter))
|
114
|
+
end
|
115
|
+
|
116
|
+
# Initializes new instance of Radiobutton with the filter provided.
|
117
|
+
# Does not make request to libyui-rest-api.
|
118
|
+
# @param filter [Hash] filter to find a widget
|
119
|
+
# @return [Widgets::Radiobutton] new instance of Radiobutton
|
120
|
+
# @example
|
121
|
+
# app.radiobutton(id: 'id', label: 'label', class: 'YRadioButton')
|
122
|
+
def radiobutton(filter)
|
123
|
+
Widgets::Radiobutton.new(@widget_controller, FilterExtractor.new(filter))
|
124
|
+
end
|
125
|
+
|
126
|
+
# Initializes new instance of Richtext with the filter provided.
|
127
|
+
# Does not make request to libyui-rest-api.
|
128
|
+
# @param filter [Hash] filter to find a widget
|
129
|
+
# @return [Widgets::Richtext] new instance of Richtext
|
130
|
+
# @example
|
131
|
+
# app.richtext(id: 'id', label: 'label', class: 'YRichText')
|
132
|
+
def richtext(filter)
|
133
|
+
Widgets::Richtext.new(@widget_controller, FilterExtractor.new(filter))
|
134
|
+
end
|
135
|
+
|
136
|
+
# Initializes new instance of Selectionbox with the filter provided.
|
137
|
+
# Does not make request to libyui-rest-api.
|
138
|
+
# @param filter [Hash] filter to find a widget
|
139
|
+
# @return [Widgets::Selectionbox] new instance of Selectionbox
|
140
|
+
# @example
|
141
|
+
# app.selectionbox(id: 'id', label: 'label', class: 'YSelectionBox')
|
142
|
+
def selectionbox(filter)
|
143
|
+
Widgets::Selectionbox.new(@widget_controller, FilterExtractor.new(filter))
|
144
|
+
end
|
145
|
+
|
146
|
+
# Initializes new instance of Tab with the filter provided.
|
147
|
+
# Does not make request to libyui-rest-api.
|
148
|
+
# @param filter [Hash] filter to find a widget
|
149
|
+
# @return [Widgets::Tab] new instance of Tab
|
150
|
+
# @example
|
151
|
+
# app.tab(id: 'id', label: 'label', class: 'YDumbTab')
|
152
|
+
def tab(filter)
|
153
|
+
Widgets::Tab.new(@widget_controller, FilterExtractor.new(filter))
|
154
|
+
end
|
155
|
+
|
156
|
+
# Initializes new instance of Table with the filter provided.
|
157
|
+
# Does not make request to libyui-rest-api.
|
158
|
+
# @param filter [Hash] filter to find a widget
|
159
|
+
# @return [Widgets::Table] new instance of Table
|
160
|
+
# @example
|
161
|
+
# app.table(id: 'id', label: 'label', class: 'YTable')
|
162
|
+
def table(filter)
|
163
|
+
Widgets::Table.new(@widget_controller, FilterExtractor.new(filter))
|
164
|
+
end
|
165
|
+
|
166
|
+
# Initializes new instance of time field with the filter provided.
|
167
|
+
# Does not make request to libyui-rest-api.
|
168
|
+
# @param filter [Hash] filter to find a widget
|
169
|
+
# @return [Widgets::Timefield] new instance of Table
|
170
|
+
# @example
|
171
|
+
# app.timefield(id: 'id', label: 'label', class: 'YTimeField')
|
172
|
+
def timefield(filter)
|
173
|
+
Widgets::Timefield.new(@widget_controller, FilterExtractor.new(filter))
|
174
|
+
end
|
175
|
+
|
176
|
+
# Initializes new instance of Textbox with the filter provided.
|
177
|
+
# Does not make request to libyui-rest-api.
|
178
|
+
# @param filter [Hash] filter to find a widget
|
179
|
+
# @return [Widgets::Textbox] new instance of Textbox
|
180
|
+
# @example
|
181
|
+
# app.textbox(id: 'id', label: 'label', class: 'YInputField')
|
182
|
+
def textbox(filter)
|
183
|
+
Widgets::Textbox.new(@widget_controller, FilterExtractor.new(filter))
|
184
|
+
end
|
185
|
+
|
186
|
+
# Initializes new instance of Tree with the filter provided.
|
187
|
+
# Does not make request to libyui-rest-api.
|
188
|
+
# @param filter [Hash] filter to find a widget
|
189
|
+
# @return [Widgets::Tree] new instance of Tree
|
190
|
+
# @example
|
191
|
+
# app.tree(id: 'id', label: 'label', class: 'YTree')
|
192
|
+
def tree(filter)
|
193
|
+
Widgets::Tree.new(@widget_controller, FilterExtractor.new(filter))
|
194
|
+
end
|
195
|
+
|
196
|
+
# Initializes new instance of Wizard with the filter provided.
|
197
|
+
# Does not make request to libyui-rest-api.
|
198
|
+
# @param filter [Hash] filter to find a widget
|
199
|
+
# @return [Widgets::Wizard] new instance of Wizard
|
200
|
+
# @example
|
201
|
+
# app.wizard(id: 'id', label: 'label', class: 'YWizard')
|
202
|
+
def wizard(filter)
|
203
|
+
Widgets::Wizard.new(@widget_controller, FilterExtractor.new(filter))
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns client side libyui REST API version
|
207
|
+
# @return libyui client REST API version
|
208
|
+
def client_api_version
|
209
|
+
API_VERSION
|
210
|
+
end
|
211
|
+
|
212
|
+
# Returns server side libyui REST API version
|
213
|
+
# @return libyui server REST API version
|
214
|
+
def server_api_version
|
215
|
+
@version_controller.api_version
|
216
|
+
end
|
217
|
+
|
218
|
+
# Validates if server side REST API is compatible with client inside
|
219
|
+
# @return true if version is compatible, false if not or any error while
|
220
|
+
# receiving version from the server
|
221
|
+
def check_api_version
|
222
|
+
YuiRestClient.logger.info("Client API version: #{API_VERSION}")
|
223
|
+
server_api_v = server_api_version
|
224
|
+
raise Error::YuiRestClientError if server_api_v.nil?
|
225
|
+
|
226
|
+
YuiRestClient.logger.info("Server API version: #{server_api_v}")
|
227
|
+
server_api_v <= client_api_version
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YuiRestClient
|
4
|
+
module Error
|
5
|
+
class YuiRestClientError < StandardError; end
|
6
|
+
|
7
|
+
class TimeoutError < YuiRestClientError; end
|
8
|
+
|
9
|
+
class WidgetNotFoundError < YuiRestClientError; end
|
10
|
+
|
11
|
+
class ItemNotFoundInWidgetError < YuiRestClientError; end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YuiRestClient
|
4
|
+
class FilterExtractor
|
5
|
+
attr_reader :full, :plain, :regex
|
6
|
+
|
7
|
+
def initialize(filter)
|
8
|
+
@full = build_filters(filter)
|
9
|
+
@plain = @full.reject { |_, v| v.is_a? Regexp }
|
10
|
+
@regex = @full.select { |_, v| v.is_a? Regexp }
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
full.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def build_filters(hash)
|
20
|
+
filter = {}
|
21
|
+
filter[:id] = hash[:id]
|
22
|
+
# Replace '&' in label filter as search is not possible when it contains the character
|
23
|
+
filter[:label] = hash[:label].is_a?(String) ? hash[:label].tr('&', '') : hash[:label]
|
24
|
+
filter[:type] = hash[:class] if hash.key?(:class)
|
25
|
+
filter.compact
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YuiRestClient
|
4
|
+
module Http
|
5
|
+
module HttpClient
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def http_get(uri)
|
9
|
+
YuiRestClient.logger.debug("Request: [GET] #{uri}")
|
10
|
+
res = Net::HTTP.get_response(uri)
|
11
|
+
YuiRestClient.logger.debug("Response: [#{res.code}]\n#{res.body}")
|
12
|
+
res
|
13
|
+
end
|
14
|
+
|
15
|
+
def http_post(uri)
|
16
|
+
YuiRestClient.logger.debug("Request: [POST] #{uri}")
|
17
|
+
# a trick how to add query parameters to a POST request,
|
18
|
+
# the usual Net::HTTP.post(uri, data) does not allow using a query
|
19
|
+
req = Net::HTTP::Post.new("#{uri.path}?#{uri.query}")
|
20
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
21
|
+
res = http.request(req)
|
22
|
+
YuiRestClient.logger.debug("Response: [#{res.code}]\n#{res.body}")
|
23
|
+
res
|
24
|
+
end
|
25
|
+
|
26
|
+
def compose_uri(host, port, path, params = {})
|
27
|
+
URI::HTTP.build(
|
28
|
+
host: host,
|
29
|
+
port: port,
|
30
|
+
path: path,
|
31
|
+
query: URI.encode_www_form(params)
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YuiRestClient
|
4
|
+
module Http
|
5
|
+
class Response
|
6
|
+
def initialize(res)
|
7
|
+
@res = res
|
8
|
+
end
|
9
|
+
|
10
|
+
def body(regex_filter: {})
|
11
|
+
result = JSON.parse(@res.body, symbolize_names: true)
|
12
|
+
result.select do |widget|
|
13
|
+
regex_filter.all? { |key, value| value.match(widget[key.to_sym]) }
|
14
|
+
end
|
15
|
+
rescue JSON::ParserError => e
|
16
|
+
YuiRestClient.logger.error("Error while parsing JSON from response:\n"\
|
17
|
+
"#{e.message}\n#{e.backtrace.inspect}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YuiRestClient
|
4
|
+
module Http
|
5
|
+
class VersionController
|
6
|
+
def initialize(host:, port:)
|
7
|
+
@host = host
|
8
|
+
@port = port
|
9
|
+
@timeout = YuiRestClient.timeout
|
10
|
+
@interval = YuiRestClient.interval
|
11
|
+
end
|
12
|
+
|
13
|
+
# Gets server api version, so one could compare compatibility and detect
|
14
|
+
# if newer version was deployed
|
15
|
+
# @return server side REST API version
|
16
|
+
def api_version
|
17
|
+
Wait.until(timeout: @timeout, interval: @interval) do
|
18
|
+
res = HttpClient.http_get(HttpClient.compose_uri(@host, @port, '/version'))
|
19
|
+
raise Error::YuiRestClientError unless res.is_a?(Net::HTTPOK)
|
20
|
+
|
21
|
+
JSON.parse(res.body)['api_version']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YuiRestClient
|
4
|
+
module Http
|
5
|
+
class WidgetController
|
6
|
+
def initialize(host:, port:)
|
7
|
+
@host = host
|
8
|
+
@port = port
|
9
|
+
@timeout = YuiRestClient.timeout
|
10
|
+
@interval = YuiRestClient.interval
|
11
|
+
end
|
12
|
+
|
13
|
+
# Find a widget using the filter.
|
14
|
+
# @param filter [Hash] identifiers to find a widget
|
15
|
+
# @return [Response]
|
16
|
+
def find(filter)
|
17
|
+
res = nil
|
18
|
+
Wait.until(timeout: @timeout, interval: @interval) do
|
19
|
+
uri = HttpClient.compose_uri(@host, @port, "/#{API_VERSION}/widgets", filter)
|
20
|
+
res = HttpClient.http_get(uri)
|
21
|
+
Response.new(res) if res.is_a?(Net::HTTPOK)
|
22
|
+
end
|
23
|
+
rescue Error::TimeoutError
|
24
|
+
rescue_errors(res)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Perform an action on the widget.
|
28
|
+
# @param filter [Hash] identifiers to find a widget
|
29
|
+
# @param action [Hash] what to do with the widget
|
30
|
+
# @return [Response]
|
31
|
+
def send_action(filter, action)
|
32
|
+
res = nil
|
33
|
+
Wait.until(timeout: @timeout, interval: @interval) do
|
34
|
+
uri = HttpClient.compose_uri(@host, @port, "/#{API_VERSION}/widgets",
|
35
|
+
filter.merge(action))
|
36
|
+
res = HttpClient.http_post(uri)
|
37
|
+
Response.new(res) if res.is_a?(Net::HTTPOK)
|
38
|
+
end
|
39
|
+
rescue Error::TimeoutError
|
40
|
+
rescue_errors(res)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def rescue_errors(response)
|
46
|
+
raise Error::WidgetNotFoundError if response.is_a?(Net::HTTPNotFound)
|
47
|
+
|
48
|
+
raise Error::ItemNotFoundInWidgetError if response.is_a?(Net::HTTPUnprocessableEntity)
|
49
|
+
|
50
|
+
raise Error::YuiRestClientError
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Client to interact with YAST UI rest api framework for integration testing
|
4
|
+
module YuiRestClient
|
5
|
+
class LocalProcess
|
6
|
+
# default timeout for process
|
7
|
+
DEFAULT_TIMEOUT_PROCESS = 2
|
8
|
+
|
9
|
+
# start the application in background
|
10
|
+
# @param application [String] the command to start
|
11
|
+
def start_app(application)
|
12
|
+
@app_host = 'localhost'
|
13
|
+
@app_port = port
|
14
|
+
|
15
|
+
# another app already running?
|
16
|
+
raise "The port #{@app_host}:#{@app_port} is already open!" if port_open?(@app_host, @app_port)
|
17
|
+
|
18
|
+
YuiRestClient.logger.debug("Starting #{application}...")
|
19
|
+
# create a new process group so easily we will be able
|
20
|
+
# to kill all its sub-processes
|
21
|
+
@app_pid = spawn(application, pgroup: true)
|
22
|
+
wait_for_port(@app_host, @app_port)
|
23
|
+
YuiRestClient.logger.debug("App started: '#{application}'")
|
24
|
+
end
|
25
|
+
|
26
|
+
# kill the process if it is still running after finishing a scenario
|
27
|
+
def kill_app
|
28
|
+
return unless @app_pid
|
29
|
+
|
30
|
+
Process.waitpid(@app_pid, Process::WNOHANG)
|
31
|
+
YuiRestClient.logger.debug("Sending KILL signal for PID #{@app_pid}")
|
32
|
+
Process.kill('-KILL', @app_pid)
|
33
|
+
rescue Errno::ECHILD, Errno::ESRCH
|
34
|
+
# the process has already exited
|
35
|
+
@app_pid = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# set the application introspection port for communication
|
41
|
+
def port
|
42
|
+
ENV['YUI_HTTP_PORT'] ||= '9999'
|
43
|
+
end
|
44
|
+
|
45
|
+
# is the target port open?
|
46
|
+
# @param host [String] the host to connect to
|
47
|
+
# @param port [Integer] the port number
|
48
|
+
# @return [Boolean] true if the port is open, false otherwise
|
49
|
+
def port_open?(host, port)
|
50
|
+
TCPSocket.new(host, port).close
|
51
|
+
true
|
52
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
# wait until the specified port is open or until the timeout is reached
|
57
|
+
# @param host [String] the host to connect to
|
58
|
+
# @param port [Integer] the port number
|
59
|
+
# @raise YuiRestClient::Error::TimeoutError if the port is not opened in time
|
60
|
+
def wait_for_port(host, port)
|
61
|
+
Wait.until(timeout: YuiRestClient.timeout, interval: YuiRestClient.interval) do
|
62
|
+
YuiRestClient.logger.debug("Waiting for #{host}:#{port}...")
|
63
|
+
port_open?(host, port)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# optionally allow a short delay between the steps to watch the UI changes
|
68
|
+
def add_step_delay
|
69
|
+
delay = ENV['STEP_DELAY'].to_f
|
70
|
+
sleep(delay) if delay.positive?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|