Merge branch '6087-collection-timing' closes #6087
[arvados.git] / apps / workbench / test / test_helper.rb
1 ENV["RAILS_ENV"] = "test" if (ENV["RAILS_ENV"] != "diagnostics" and ENV["RAILS_ENV"] != "performance")
2
3 unless ENV["NO_COVERAGE_TEST"]
4   begin
5     require 'simplecov'
6     require 'simplecov-rcov'
7     class SimpleCov::Formatter::MergedFormatter
8       def format(result)
9         SimpleCov::Formatter::HTMLFormatter.new.format(result)
10         SimpleCov::Formatter::RcovFormatter.new.format(result)
11       end
12     end
13     SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
14     SimpleCov.start do
15       add_filter '/test/'
16       add_filter 'initializers/secret_token'
17     end
18   rescue Exception => e
19     $stderr.puts "SimpleCov unavailable (#{e}). Proceeding without."
20   end
21 end
22
23 require File.expand_path('../../config/environment', __FILE__)
24 require 'rails/test_help'
25 require 'mocha/mini_test'
26
27 class ActiveSupport::TestCase
28   # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in
29   # alphabetical order.
30   #
31   # Note: You'll currently still have to declare fixtures explicitly
32   # in integration tests -- they do not yet inherit this setting
33   fixtures :all
34   def use_token token_name
35     was = Thread.current[:arvados_api_token]
36     auth = api_fixture('api_client_authorizations')[token_name.to_s]
37     Thread.current[:arvados_api_token] = auth['api_token']
38     if block_given?
39       begin
40         yield
41       ensure
42         Thread.current[:arvados_api_token] = was
43       end
44     end
45   end
46
47   teardown do
48     Thread.current[:arvados_api_token] = nil
49     Thread.current[:user] = nil
50     Thread.current[:reader_tokens] = nil
51     # Diagnostics suite doesn't run a server, so there's no cache to clear.
52     Rails.cache.clear unless (Rails.env == "diagnostics")
53     # Restore configuration settings changed during tests
54     self.class.reset_application_config
55   end
56
57   def self.reset_application_config
58     $application_config.each do |k,v|
59       if k.match /^[^.]*$/
60         Rails.configuration.send (k + '='), v
61       end
62     end
63   end
64 end
65
66 module ApiFixtureLoader
67   def self.included(base)
68     base.extend(ClassMethods)
69   end
70
71   module ClassMethods
72     @@api_fixtures = {}
73     def api_fixture(name, *keys)
74       # Returns the data structure from the named API server test fixture.
75       @@api_fixtures[name] ||= \
76       begin
77         path = File.join(ApiServerForTests::ARV_API_SERVER_DIR,
78                          'test', 'fixtures', "#{name}.yml")
79         file = IO.read(path)
80         trim_index = file.index('# Test Helper trims the rest of the file')
81         file = file[0, trim_index] if trim_index
82         YAML.load(file)
83       end
84       keys.inject(@@api_fixtures[name]) { |hash, key| hash[key] }
85     end
86   end
87   def api_fixture(name, *keys)
88     self.class.api_fixture(name, *keys)
89   end
90
91   def find_fixture(object_class, name)
92     object_class.find(api_fixture(object_class.to_s.pluralize.underscore,
93                                   name, "uuid"))
94   end
95 end
96
97 module ApiMockHelpers
98   def fake_api_response body, status_code, headers
99     resp = mock
100     resp.responds_like_instance_of HTTP::Message
101     resp.stubs(:headers).returns headers
102     resp.stubs(:content).returns body
103     resp.stubs(:status_code).returns status_code
104     resp
105   end
106
107   def stub_api_calls_with_body body, status_code=200, headers={}
108     stub_api_calls
109     resp = fake_api_response body, status_code, headers
110     stub_api_client.stubs(:post).returns resp
111   end
112
113   def stub_api_calls
114     @stubbed_client = ArvadosApiClient.new
115     @stubbed_client.instance_eval do
116       @api_client = HTTPClient.new
117     end
118     ArvadosApiClient.stubs(:new_or_current).returns(@stubbed_client)
119   end
120
121   def stub_api_calls_with_invalid_json
122     stub_api_calls_with_body ']"omg,bogus"['
123   end
124
125   # Return the HTTPClient mock used by the ArvadosApiClient mock. You
126   # must have called stub_api_calls first.
127   def stub_api_client
128     @stubbed_client.instance_eval do
129       @api_client
130     end
131   end
132 end
133
134 class ActiveSupport::TestCase
135   include ApiMockHelpers
136 end
137
138 class ActiveSupport::TestCase
139   include ApiFixtureLoader
140   def session_for api_client_auth_name
141     {
142       arvados_api_token: api_fixture('api_client_authorizations')[api_client_auth_name.to_s]['api_token']
143     }
144   end
145   def json_response
146     Oj.load(@response.body)
147   end
148 end
149
150 class ApiServerForTests
151   PYTHON_TESTS_DIR = File.expand_path('../../../../sdk/python/tests', __FILE__)
152   ARV_API_SERVER_DIR = File.expand_path('../../../../services/api', __FILE__)
153   SERVER_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
154   WEBSOCKET_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
155   @main_process_pid = $$
156   @@server_is_running = false
157
158   def check_output *args
159     output = nil
160     Bundler.with_clean_env do
161       output = IO.popen *args do |io|
162         io.read
163       end
164       if not $?.success?
165         raise RuntimeError, "Command failed (#{$?}): #{args.inspect}"
166       end
167     end
168     output
169   end
170
171   def run_test_server
172     env_script = nil
173     Dir.chdir PYTHON_TESTS_DIR do
174       # These are no-ops if we're running within run-tests.sh (except
175       # that we do get a useful env_script back from "start", even
176       # though it doesn't need to start up a new server).
177       env_script = check_output %w(python ./run_test_server.py start --auth admin)
178       check_output %w(python ./run_test_server.py start_arv-git-httpd)
179       check_output %w(python ./run_test_server.py start_nginx)
180     end
181     test_env = {}
182     env_script.each_line do |line|
183       line = line.chomp
184       if 0 == line.index('export ')
185         toks = line.sub('export ', '').split '=', 2
186         $stderr.puts "run_test_server.py: #{toks[0]}=#{toks[1]}"
187         test_env[toks[0]] = toks[1]
188       end
189     end
190     test_env
191   end
192
193   def stop_test_server
194     Dir.chdir PYTHON_TESTS_DIR do
195       # These are no-ops if we're running within run-tests.sh
196       check_output %w(python ./run_test_server.py stop_nginx)
197       check_output %w(python ./run_test_server.py stop_arv-git-httpd)
198       check_output %w(python ./run_test_server.py stop)
199     end
200     @@server_is_running = false
201   end
202
203   def run args=[]
204     return if @@server_is_running
205
206     # Stop server left over from interrupted previous run
207     stop_test_server
208
209     ::MiniTest.after_run do
210       stop_test_server
211     end
212
213     test_env = run_test_server
214     $application_config['arvados_login_base'] = "https://#{test_env['ARVADOS_API_HOST']}/login"
215     $application_config['arvados_v1_base'] = "https://#{test_env['ARVADOS_API_HOST']}/arvados/v1"
216     $application_config['arvados_insecure_host'] = true
217     ActiveSupport::TestCase.reset_application_config
218
219     @@server_is_running = true
220   end
221
222   def run_rake_task task_name, arg_string
223     Dir.chdir ARV_API_SERVER_DIR do
224       check_output ['bundle', 'exec', 'rake', "#{task_name}[#{arg_string}]"]
225     end
226   end
227 end
228
229 class ActionController::TestCase
230   setup do
231     @counter = 0
232   end
233
234   def check_counter action
235     @counter += 1
236     if @counter == 2
237       assert_equal 1, 2, "Multiple actions in controller test"
238     end
239   end
240
241   [:get, :post, :put, :patch, :delete].each do |method|
242     define_method method do |action, *args|
243       check_counter action
244       super action, *args
245     end
246   end
247 end
248
249 # Test classes can call reset_api_fixtures(when_to_reset,flag) to
250 # override the default. Example:
251 #
252 # class MySuite < ActionDispatch::IntegrationTest
253 #   reset_api_fixtures :after_each_test, false
254 #   reset_api_fixtures :after_suite, true
255 #   ...
256 # end
257 #
258 # The default behavior is reset_api_fixtures(:after_each_test,true).
259 #
260 class ActiveSupport::TestCase
261
262   def self.inherited subclass
263     subclass.class_eval do
264       class << self
265         attr_accessor :want_reset_api_fixtures
266       end
267       @want_reset_api_fixtures = {
268         after_each_test: true,
269         after_suite: false,
270         before_suite: false,
271       }
272     end
273     super
274   end
275   # Existing subclasses of ActiveSupport::TestCase (ones that already
276   # existed before we set up the self.inherited hook above) will not
277   # get their own instance variable. They're not real test cases
278   # anyway, so we give them a "don't reset anywhere" stub.
279   def self.want_reset_api_fixtures
280     {}
281   end
282
283   def self.reset_api_fixtures where, t=true
284     if not want_reset_api_fixtures.has_key? where
285       raise ArgumentError, "There is no #{where.inspect} hook"
286     end
287     self.want_reset_api_fixtures[where] = t
288   end
289
290   def self.run *args
291     reset_api_fixtures_now if want_reset_api_fixtures[:before_suite]
292     result = super
293     reset_api_fixtures_now if want_reset_api_fixtures[:after_suite]
294     result
295   end
296
297   def after_teardown
298     if self.class.want_reset_api_fixtures[:after_each_test] and
299         @want_reset_api_fixtures != false
300       self.class.reset_api_fixtures_now
301     end
302     super
303   end
304
305   def reset_api_fixtures_after_test t=true
306     @want_reset_api_fixtures = t
307   end
308
309   protected
310   def self.reset_api_fixtures_now
311     # Never try to reset fixtures when we're just using test
312     # infrastructure to run performance/diagnostics suites.
313     return unless Rails.env == 'test'
314
315     auth = api_fixture('api_client_authorizations')['admin_trustedclient']
316     Thread.current[:arvados_api_token] = auth['api_token']
317     ArvadosApiClient.new.api(nil, '../../database/reset', {})
318     Thread.current[:arvados_api_token] = nil
319   end
320 end
321
322 # If it quacks like a duck, it must be a HTTP request object.
323 class RequestDuck
324   def self.host
325     "localhost"
326   end
327
328   def self.port
329     8080
330   end
331
332   def self.protocol
333     "http"
334   end
335 end
336
337 # Example:
338 #
339 # apps/workbench$ RAILS_ENV=test bundle exec irb -Ilib:test
340 # > load 'test/test_helper.rb'
341 # > singletest 'integration/collection_upload_test.rb', 'Upload two empty files'
342 #
343 def singletest test_class_file, test_name
344   load File.join('test', test_class_file)
345   Minitest.run ['-v', '-n', "test_#{test_name.gsub ' ', '_'}"]
346   Object.send(:remove_const,
347               test_class_file.gsub(/.*\/|\.rb$/, '').camelize.to_sym)
348   ::Minitest::Runnable.runnables.reject! { true }
349 end
350
351 if ENV["RAILS_ENV"].eql? 'test'
352   ApiServerForTests.new.run
353   ApiServerForTests.new.run ["--websockets"]
354 end
355
356 # Reset fixtures now (i.e., before any tests run).
357 ActiveSupport::TestCase.reset_api_fixtures_now