Merge branch '3198-inode-cache' refs #3198
[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 stub_api_calls_with_body body, status_code=200
99     resp = mock
100     stubbed_client = ArvadosApiClient.new
101     stubbed_client.instance_eval do
102       resp.responds_like_instance_of HTTP::Message
103       resp.stubs(:content).returns body
104       resp.stubs(:status_code).returns status_code
105       @api_client = HTTPClient.new
106       @api_client.stubs(:post).returns resp
107     end
108     ArvadosApiClient.stubs(:new_or_current).returns(stubbed_client)
109   end
110
111   def stub_api_calls_with_invalid_json
112     stub_api_calls_with_body ']"omg,bogus"['
113   end
114 end
115
116 class ActiveSupport::TestCase
117   include ApiMockHelpers
118 end
119
120 class ActiveSupport::TestCase
121   include ApiFixtureLoader
122   def session_for api_client_auth_name
123     {
124       arvados_api_token: api_fixture('api_client_authorizations')[api_client_auth_name.to_s]['api_token']
125     }
126   end
127   def json_response
128     Oj.load(@response.body)
129   end
130 end
131
132 class ApiServerForTests
133   PYTHON_TESTS_DIR = File.expand_path('../../../../sdk/python/tests', __FILE__)
134   ARV_API_SERVER_DIR = File.expand_path('../../../../services/api', __FILE__)
135   SERVER_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
136   WEBSOCKET_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
137   @main_process_pid = $$
138   @@server_is_running = false
139
140   def check_output *args
141     output = nil
142     Bundler.with_clean_env do
143       output = IO.popen *args do |io|
144         io.read
145       end
146       if not $?.success?
147         raise RuntimeError, "Command failed (#{$?}): #{args.inspect}"
148       end
149     end
150     output
151   end
152
153   def run_test_server
154     env_script = nil
155     Dir.chdir PYTHON_TESTS_DIR do
156       # These are no-ops if we're running within run-tests.sh (except
157       # that we do get a useful env_script back from "start", even
158       # though it doesn't need to start up a new server).
159       env_script = check_output %w(python ./run_test_server.py start --auth admin)
160       check_output %w(python ./run_test_server.py start_arv-git-httpd)
161       check_output %w(python ./run_test_server.py start_nginx)
162     end
163     test_env = {}
164     env_script.each_line do |line|
165       line = line.chomp
166       if 0 == line.index('export ')
167         toks = line.sub('export ', '').split '=', 2
168         $stderr.puts "run_test_server.py: #{toks[0]}=#{toks[1]}"
169         test_env[toks[0]] = toks[1]
170       end
171     end
172     test_env
173   end
174
175   def stop_test_server
176     Dir.chdir PYTHON_TESTS_DIR do
177       # These are no-ops if we're running within run-tests.sh
178       check_output %w(python ./run_test_server.py stop_nginx)
179       check_output %w(python ./run_test_server.py stop_arv-git-httpd)
180       check_output %w(python ./run_test_server.py stop)
181     end
182     @@server_is_running = false
183   end
184
185   def run args=[]
186     return if @@server_is_running
187
188     # Stop server left over from interrupted previous run
189     stop_test_server
190
191     ::MiniTest.after_run do
192       stop_test_server
193     end
194
195     test_env = run_test_server
196     $application_config['arvados_login_base'] = "https://#{test_env['ARVADOS_API_HOST']}/login"
197     $application_config['arvados_v1_base'] = "https://#{test_env['ARVADOS_API_HOST']}/arvados/v1"
198     $application_config['arvados_insecure_host'] = true
199     ActiveSupport::TestCase.reset_application_config
200
201     @@server_is_running = true
202   end
203
204   def run_rake_task task_name, arg_string
205     Dir.chdir ARV_API_SERVER_DIR do
206       check_output ['bundle', 'exec', 'rake', "#{task_name}[#{arg_string}]"]
207     end
208   end
209 end
210
211 class ActionController::TestCase
212   setup do
213     @counter = 0
214   end
215
216   def check_counter action
217     @counter += 1
218     if @counter == 2
219       assert_equal 1, 2, "Multiple actions in controller test"
220     end
221   end
222
223   [:get, :post, :put, :patch, :delete].each do |method|
224     define_method method do |action, *args|
225       check_counter action
226       super action, *args
227     end
228   end
229 end
230
231 # Test classes can call reset_api_fixtures(when_to_reset,flag) to
232 # override the default. Example:
233 #
234 # class MySuite < ActionDispatch::IntegrationTest
235 #   reset_api_fixtures :after_each_test, false
236 #   reset_api_fixtures :after_suite, true
237 #   ...
238 # end
239 #
240 # The default behavior is reset_api_fixtures(:after_each_test,true).
241 #
242 class ActiveSupport::TestCase
243
244   def self.inherited subclass
245     subclass.class_eval do
246       class << self
247         attr_accessor :want_reset_api_fixtures
248       end
249       @want_reset_api_fixtures = {
250         after_each_test: true,
251         after_suite: false,
252         before_suite: false,
253       }
254     end
255     super
256   end
257   # Existing subclasses of ActiveSupport::TestCase (ones that already
258   # existed before we set up the self.inherited hook above) will not
259   # get their own instance variable. They're not real test cases
260   # anyway, so we give them a "don't reset anywhere" stub.
261   def self.want_reset_api_fixtures
262     {}
263   end
264
265   def self.reset_api_fixtures where, t=true
266     if not want_reset_api_fixtures.has_key? where
267       raise ArgumentError, "There is no #{where.inspect} hook"
268     end
269     self.want_reset_api_fixtures[where] = t
270   end
271
272   def self.run *args
273     reset_api_fixtures_now if want_reset_api_fixtures[:before_suite]
274     result = super
275     reset_api_fixtures_now if want_reset_api_fixtures[:after_suite]
276     result
277   end
278
279   def after_teardown
280     if self.class.want_reset_api_fixtures[:after_each_test] and
281         @want_reset_api_fixtures != false
282       self.class.reset_api_fixtures_now
283     end
284     super
285   end
286
287   def reset_api_fixtures_after_test t=true
288     @want_reset_api_fixtures = t
289   end
290
291   protected
292   def self.reset_api_fixtures_now
293     # Never try to reset fixtures when we're just using test
294     # infrastructure to run performance/diagnostics suites.
295     return unless Rails.env == 'test'
296
297     auth = api_fixture('api_client_authorizations')['admin_trustedclient']
298     Thread.current[:arvados_api_token] = auth['api_token']
299     ArvadosApiClient.new.api(nil, '../../database/reset', {})
300     Thread.current[:arvados_api_token] = nil
301   end
302 end
303
304 # If it quacks like a duck, it must be a HTTP request object.
305 class RequestDuck
306   def self.host
307     "localhost"
308   end
309
310   def self.port
311     8080
312   end
313
314   def self.protocol
315     "http"
316   end
317 end
318
319 # Example:
320 #
321 # apps/workbench$ RAILS_ENV=test bundle exec irb -Ilib:test
322 # > load 'test/test_helper.rb'
323 # > singletest 'integration/collection_upload_test.rb', 'Upload two empty files'
324 #
325 def singletest test_class_file, test_name
326   load File.join('test', test_class_file)
327   Minitest.run ['-v', '-n', "test_#{test_name.gsub ' ', '_'}"]
328   Object.send(:remove_const,
329               test_class_file.gsub(/.*\/|\.rb$/, '').camelize.to_sym)
330   ::Minitest::Runnable.runnables.reject! { true }
331 end
332
333 if ENV["RAILS_ENV"].eql? 'test'
334   ApiServerForTests.new.run
335   ApiServerForTests.new.run ["--websockets"]
336 end
337
338 # Reset fixtures now (i.e., before any tests run).
339 ActiveSupport::TestCase.reset_api_fixtures_now