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