1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
5 ENV["RAILS_ENV"] = "test" if (ENV["RAILS_ENV"] != "diagnostics" and ENV["RAILS_ENV"] != "performance")
7 unless ENV["NO_COVERAGE_TEST"]
10 require 'simplecov-rcov'
11 class SimpleCov::Formatter::MergedFormatter
13 SimpleCov::Formatter::HTMLFormatter.new.format(result)
14 SimpleCov::Formatter::RcovFormatter.new.format(result)
17 SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
20 add_filter 'initializers/secret_token'
23 $stderr.puts "SimpleCov unavailable (#{e}). Proceeding without."
27 require File.expand_path('../../config/environment', __FILE__)
28 require 'rails/test_help'
29 require 'mocha/minitest'
31 class ActiveSupport::TestCase
32 # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in
35 # Note: You'll currently still have to declare fixtures explicitly
36 # in integration tests -- they do not yet inherit this setting
38 def use_token(token_name)
39 user_was = Thread.current[:user]
40 token_was = Thread.current[:arvados_api_token]
41 auth = api_fixture('api_client_authorizations')[token_name.to_s]
42 Thread.current[:arvados_api_token] = "v2/#{auth['uuid']}/#{auth['api_token']}"
47 Thread.current[:user] = user_was
48 Thread.current[:arvados_api_token] = token_was
54 Thread.current[:arvados_api_token] = nil
55 Thread.current[:user] = nil
56 Thread.current[:reader_tokens] = nil
57 # Diagnostics suite doesn't run a server, so there's no cache to clear.
58 Rails.cache.clear unless (Rails.env == "diagnostics")
59 # Restore configuration settings changed during tests
60 self.class.reset_application_config
63 def self.reset_application_config
64 # Restore configuration settings changed during tests
65 ConfigLoader.copy_into_config $arvados_config, Rails.configuration
66 ConfigLoader.copy_into_config $remaining_config, Rails.configuration
67 Rails.configuration.Services.Controller.ExternalURL = URI("https://#{ENV['ARVADOS_API_HOST']}")
68 Rails.configuration.TLS.Insecure = true
72 module ApiFixtureLoader
73 def self.included(base)
74 base.extend(ClassMethods)
79 def api_fixture(name, *keys)
80 # Returns the data structure from the named API server test fixture.
81 @@api_fixtures[name] ||= \
83 path = File.join(ApiServerForTests::ARV_API_SERVER_DIR,
84 'test', 'fixtures', "#{name}.yml")
86 trim_index = file.index('# Test Helper trims the rest of the file')
87 file = file[0, trim_index] if trim_index
88 YAML.load(file).each do |name, ob|
89 ob.reject! { |k, v| k.start_with?('secret_') }
92 keys.inject(@@api_fixtures[name]) { |hash, key| hash[key] }.deep_dup
96 def api_fixture(name, *keys)
97 self.class.api_fixture(name, *keys)
101 auth = api_fixture('api_client_authorizations')[name]
102 "v2/#{auth['uuid']}/#{auth['api_token']}"
105 def find_fixture(object_class, name)
106 object_class.find(api_fixture(object_class.to_s.pluralize.underscore,
111 module ApiMockHelpers
112 def fake_api_response body, status_code, headers
114 resp.responds_like_instance_of HTTP::Message
115 resp.stubs(:headers).returns headers
116 resp.stubs(:content).returns body
117 resp.stubs(:status_code).returns status_code
121 def stub_api_calls_with_body body, status_code=200, headers={}
123 resp = fake_api_response body, status_code, headers
124 stub_api_client.stubs(:post).returns resp
128 @stubbed_client = ArvadosApiClient.new
129 @stubbed_client.instance_eval do
130 @api_client = HTTPClient.new
132 ArvadosApiClient.stubs(:new_or_current).returns(@stubbed_client)
135 def stub_api_calls_with_invalid_json
136 stub_api_calls_with_body ']"omg,bogus"['
139 # Return the HTTPClient mock used by the ArvadosApiClient mock. You
140 # must have called stub_api_calls first.
142 @stubbed_client.instance_eval do
148 class ActiveSupport::TestCase
149 include ApiMockHelpers
152 class ActiveSupport::TestCase
153 include ApiFixtureLoader
154 def session_for api_client_auth_name
155 auth = api_fixture('api_client_authorizations')[api_client_auth_name.to_s]
157 arvados_api_token: "v2/#{auth['uuid']}/#{auth['api_token']}"
161 Oj.load(@response.body)
165 class ApiServerForTests
166 PYTHON_TESTS_DIR = File.expand_path('../../../../sdk/python/tests', __FILE__)
167 ARV_API_SERVER_DIR = File.expand_path('../../../../services/api', __FILE__)
168 SERVER_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
169 WEBSOCKET_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
170 @main_process_pid = $$
171 @@server_is_running = false
173 def check_output *args
175 Bundler.with_clean_env do
176 output = IO.popen *args do |io|
180 raise RuntimeError, "Command failed (#{$?}): #{args.inspect}"
187 Dir.chdir PYTHON_TESTS_DIR do
188 check_output %w(python ./run_test_server.py start_keep)
193 Dir.chdir PYTHON_TESTS_DIR do
194 check_output %w(python ./run_test_server.py stop_keep)
196 @@server_is_running = false
200 return if @@server_is_running
202 # Stop server left over from interrupted previous run
205 ::MiniTest.after_run do
210 ActiveSupport::TestCase.reset_application_config
212 @@server_is_running = true
215 def run_rake_task task_name, arg_string
216 Dir.chdir ARV_API_SERVER_DIR do
217 check_output ['bundle', 'exec', 'rake', "#{task_name}[#{arg_string}]"]
222 class ActionController::TestCase
227 def check_counter action
229 if @test_counter == 2
230 assert_equal 1, 2, "Multiple actions in controller test"
234 [:get, :post, :put, :patch, :delete].each do |method|
235 define_method method do |action, *args|
242 # Test classes can call reset_api_fixtures(when_to_reset,flag) to
243 # override the default. Example:
245 # class MySuite < ActionDispatch::IntegrationTest
246 # reset_api_fixtures :after_each_test, false
247 # reset_api_fixtures :after_suite, true
251 # The default behavior is reset_api_fixtures(:after_each_test,true).
253 class ActiveSupport::TestCase
255 def self.inherited subclass
256 subclass.class_eval do
258 attr_accessor :want_reset_api_fixtures
260 @want_reset_api_fixtures = {
261 after_each_test: true,
268 # Existing subclasses of ActiveSupport::TestCase (ones that already
269 # existed before we set up the self.inherited hook above) will not
270 # get their own instance variable. They're not real test cases
271 # anyway, so we give them a "don't reset anywhere" stub.
272 def self.want_reset_api_fixtures
276 def self.reset_api_fixtures where, t=true
277 if not want_reset_api_fixtures.has_key? where
278 raise ArgumentError, "There is no #{where.inspect} hook"
280 self.want_reset_api_fixtures[where] = t
284 reset_api_fixtures_now if want_reset_api_fixtures[:before_suite]
286 reset_api_fixtures_now if want_reset_api_fixtures[:after_suite]
291 if self.class.want_reset_api_fixtures[:after_each_test] and
292 (!defined?(@want_reset_api_fixtures) or @want_reset_api_fixtures != false)
293 self.class.reset_api_fixtures_now
298 def reset_api_fixtures_after_test t=true
299 @want_reset_api_fixtures = t
303 def self.reset_api_fixtures_now
304 # Never try to reset fixtures when we're just using test
305 # infrastructure to run performance/diagnostics suites.
306 return unless Rails.env == 'test'
308 auth = api_fixture('api_client_authorizations')['admin_trustedclient']
309 Thread.current[:arvados_api_token] = "v2/#{auth['uuid']}/#{auth['api_token']}"
310 ArvadosApiClient.new.api(nil, '../../database/reset', {})
311 Thread.current[:arvados_api_token] = nil
315 # If it quacks like a duck, it must be a HTTP request object.
332 # apps/workbench$ RAILS_ENV=test bundle exec irb -Ilib:test
333 # > load 'test/test_helper.rb'
334 # > singletest 'integration/collection_upload_test.rb', 'Upload two empty files'
336 def singletest test_class_file, test_name
337 load File.join('test', test_class_file)
338 Minitest.run ['-v', '-n', "test_#{test_name.gsub ' ', '_'}"]
339 Object.send(:remove_const,
340 test_class_file.gsub(/.*\/|\.rb$/, '').camelize.to_sym)
341 ::Minitest::Runnable.runnables.reject! { true }
344 if ENV["RAILS_ENV"].eql? 'test'
345 ApiServerForTests.new.run
346 ApiServerForTests.new.run ["--websockets"]
349 # Reset fixtures now (i.e., before any tests run).
350 ActiveSupport::TestCase.reset_api_fixtures_now
354 def capture_exceptions *args
359 rescue *PASSTHROUGH_EXCEPTIONS
361 rescue Exception => e
363 raise if n > 2 || e.is_a?(Skip)
364 STDERR.puts "Test failed, retrying (##{n})"
367 rescue *PASSTHROUGH_EXCEPTIONS
369 rescue Assertion => e
371 rescue Exception => e
372 self.failures << UnexpectedError.new(e)