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