3021: Merge branch 'master' into 3021-leave-api-running
[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     auth = api_fixture('api_client_authorizations')[token_name.to_s]
36     Thread.current[:arvados_api_token] = auth['api_token']
37   end
38
39   setup do
40     Thread.current[:arvados_api_token] = nil
41     Thread.current[:user] = nil
42     Thread.current[:reader_tokens] = nil
43     # Diagnostics suite doesn't run a server, so there's no cache to clear.
44     Rails.cache.clear unless (Rails.env == "diagnostics")
45     # Restore configuration settings changed during tests
46     self.class.reset_application_config
47   end
48
49   def self.reset_application_config
50     $application_config.each do |k,v|
51       if k.match /^[^.]*$/
52         Rails.configuration.send (k + '='), v
53       end
54     end
55   end
56 end
57
58 module ApiFixtureLoader
59   def self.included(base)
60     base.extend(ClassMethods)
61   end
62
63   module ClassMethods
64     @@api_fixtures = {}
65     def api_fixture(name, *keys)
66       # Returns the data structure from the named API server test fixture.
67       @@api_fixtures[name] ||= \
68       begin
69         path = File.join(ApiServerForTests::ARV_API_SERVER_DIR,
70                          'test', 'fixtures', "#{name}.yml")
71         file = IO.read(path)
72         trim_index = file.index('# Test Helper trims the rest of the file')
73         file = file[0, trim_index] if trim_index
74         YAML.load(file)
75       end
76       keys.inject(@@api_fixtures[name]) { |hash, key| hash[key] }
77     end
78   end
79   def api_fixture(name, *keys)
80     self.class.api_fixture(name, *keys)
81   end
82
83   def find_fixture(object_class, name)
84     object_class.find(api_fixture(object_class.to_s.pluralize.underscore,
85                                   name, "uuid"))
86   end
87 end
88
89 class ActiveSupport::TestCase
90   include ApiFixtureLoader
91   def session_for api_client_auth_name
92     {
93       arvados_api_token: api_fixture('api_client_authorizations')[api_client_auth_name.to_s]['api_token']
94     }
95   end
96   def json_response
97     Oj.load(@response.body)
98   end
99 end
100
101 class ApiServerForTests
102   PYTHON_TESTS_DIR = File.expand_path('../../../../sdk/python/tests', __FILE__)
103   ARV_API_SERVER_DIR = File.expand_path('../../../../services/api', __FILE__)
104   SERVER_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
105   WEBSOCKET_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
106   @main_process_pid = $$
107   @@server_is_running = false
108
109   def check_call *args
110     output = nil
111     Bundler.with_clean_env do
112       output = IO.popen *args do |io|
113         io.read
114       end
115       if not $?.success?
116         raise RuntimeError, "Command failed (#{$?}): #{args.inspect}"
117       end
118     end
119     output
120   end
121
122   def run_test_server
123     env_script = nil
124     Dir.chdir PYTHON_TESTS_DIR do
125       env_script = check_call %w(python ./run_test_server.py start --auth admin)
126     end
127     test_env = {}
128     env_script.each_line do |line|
129       line = line.chomp
130       if 0 == line.index('export ')
131         toks = line.sub('export ', '').split '=', 2
132         $stderr.puts "run_test_server.py: #{toks[0]}=#{toks[1]}"
133         test_env[toks[0]] = toks[1]
134       end
135     end
136     test_env
137   end
138
139   def stop_test_server
140     Dir.chdir PYTHON_TESTS_DIR do
141       # This is a no-op if we're running within run-tests.sh
142       check_call %w(python ./run_test_server.py stop)
143     end
144     @@server_is_running = false
145   end
146
147   def run args=[]
148     return if @@server_is_running
149
150     # Stop server left over from interrupted previous run
151     stop_test_server
152
153     ::MiniTest.after_run do
154       stop_test_server
155     end
156
157     test_env = run_test_server
158     $application_config['arvados_login_base'] = "https://#{test_env['ARVADOS_API_HOST']}/login"
159     $application_config['arvados_v1_base'] = "https://#{test_env['ARVADOS_API_HOST']}/arvados/v1"
160     $application_config['arvados_insecure_host'] = true
161     ActiveSupport::TestCase.reset_application_config
162
163     @@server_is_running = true
164   end
165
166   def run_rake_task task_name, arg_string
167     Dir.chdir ARV_API_SERVER_DIR do
168       check_call ['bundle', 'exec', 'rake', "#{task_name}[#{arg_string}]"]
169     end
170   end
171 end
172
173 class ActionController::TestCase
174   setup do
175     @counter = 0
176   end
177
178   def check_counter action
179     @counter += 1
180     if @counter == 2
181       assert_equal 1, 2, "Multiple actions in controller test"
182     end
183   end
184
185   [:get, :post, :put, :patch, :delete].each do |method|
186     define_method method do |action, *args|
187       check_counter action
188       super action, *args
189     end
190   end
191 end
192
193 # Test classes can call reset_api_fixtures(when_to_reset,flag) to
194 # override the default. Example:
195 #
196 # class MySuite < ActionDispatch::IntegrationTest
197 #   reset_api_fixtures :after_each_test, false
198 #   reset_api_fixtures :after_suite, true
199 #   ...
200 # end
201 #
202 # The default behavior is reset_api_fixtures(:after_each_test,true).
203 #
204 class ActiveSupport::TestCase
205
206   def self.inherited subclass
207     subclass.class_eval do
208       class << self
209         attr_accessor :want_reset_api_fixtures
210       end
211       @want_reset_api_fixtures = {
212         after_each_test: true,
213         after_suite: false,
214         before_suite: false,
215       }
216     end
217     super
218   end
219   # Existing subclasses of ActiveSupport::TestCase (ones that already
220   # existed before we set up the self.inherited hook above) will not
221   # get their own instance variable. They're not real test cases
222   # anyway, so we give them a "don't reset anywhere" stub.
223   def self.want_reset_api_fixtures
224     {}
225   end
226
227   def self.reset_api_fixtures where, t=true
228     if not want_reset_api_fixtures.has_key? where
229       raise ArgumentError, "There is no #{where.inspect} hook"
230     end
231     self.want_reset_api_fixtures[where] = t
232   end
233
234   def self.run *args
235     reset_api_fixtures_now if want_reset_api_fixtures[:before_suite]
236     result = super
237     reset_api_fixtures_now if want_reset_api_fixtures[:after_suite]
238     result
239   end
240
241   def after_teardown
242     if self.class.want_reset_api_fixtures[:after_each_test]
243       self.class.reset_api_fixtures_now
244     end
245     super
246   end
247
248   protected
249   def self.reset_api_fixtures_now
250     # Never try to reset fixtures when we're just using test
251     # infrastructure to run performance/diagnostics suites.
252     return unless Rails.env == 'test'
253
254     auth = api_fixture('api_client_authorizations')['admin_trustedclient']
255     Thread.current[:arvados_api_token] = auth['api_token']
256     ArvadosApiClient.new.api(nil, '../../database/reset', {})
257     Thread.current[:arvados_api_token] = nil
258   end
259 end
260
261 # If it quacks like a duck, it must be a HTTP request object.
262 class RequestDuck
263   def self.host
264     "localhost"
265   end
266
267   def self.port
268     8080
269   end
270
271   def self.protocol
272     "http"
273   end
274 end
275
276 # Example:
277 #
278 # apps/workbench$ RAILS_ENV=test bundle exec irb -Ilib:test
279 # > load 'test/test_helper.rb'
280 # > singletest 'integration/collection_upload_test.rb', 'Upload two empty files'
281 #
282 def singletest test_class_file, test_name
283   load File.join('test', test_class_file)
284   Minitest.run ['-v', '-n', "test_#{test_name.gsub ' ', '_'}"]
285   Object.send(:remove_const,
286               test_class_file.gsub(/.*\/|\.rb$/, '').camelize.to_sym)
287   ::Minitest::Runnable.runnables.reject! { true }
288 end
289
290 if ENV["RAILS_ENV"].eql? 'test'
291   ApiServerForTests.new.run
292   ApiServerForTests.new.run ["--websockets"]
293 end
294
295 # Reset fixtures now (i.e., before any tests run).
296 ActiveSupport::TestCase.reset_api_fixtures_now