Merge branch 'master' into 4904-arv-web
[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   teardown 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     $application_config.each do |k,v|
47       if k.match /^[^.]*$/
48         Rails.configuration.send (k + '='), v
49       end
50     end
51   end
52 end
53
54 module ApiFixtureLoader
55   def self.included(base)
56     base.extend(ClassMethods)
57   end
58
59   module ClassMethods
60     @@api_fixtures = {}
61     def api_fixture(name, *keys)
62       # Returns the data structure from the named API server test fixture.
63       @@api_fixtures[name] ||= \
64       begin
65         path = File.join(ApiServerForTests::ARV_API_SERVER_DIR,
66                          'test', 'fixtures', "#{name}.yml")
67         file = IO.read(path)
68         trim_index = file.index('# Test Helper trims the rest of the file')
69         file = file[0, trim_index] if trim_index
70         YAML.load(file)
71       end
72       keys.inject(@@api_fixtures[name]) { |hash, key| hash[key] }
73     end
74   end
75   def api_fixture(name, *keys)
76     self.class.api_fixture(name, *keys)
77   end
78
79   def find_fixture(object_class, name)
80     object_class.find(api_fixture(object_class.to_s.pluralize.underscore,
81                                   name, "uuid"))
82   end
83 end
84
85 class ActiveSupport::TestCase
86   include ApiFixtureLoader
87   def session_for api_client_auth_name
88     {
89       arvados_api_token: api_fixture('api_client_authorizations')[api_client_auth_name.to_s]['api_token']
90     }
91   end
92   def json_response
93     Oj.load(@response.body)
94   end
95 end
96
97 class ApiServerForTests
98   ARV_API_SERVER_DIR = File.expand_path('../../../../services/api', __FILE__)
99   SERVER_PID_PATH = File.expand_path('tmp/pids/wbtest-server.pid', ARV_API_SERVER_DIR)
100   WEBSOCKET_PID_PATH = File.expand_path('tmp/pids/wstest-server.pid', ARV_API_SERVER_DIR)
101   @main_process_pid = $$
102
103   def _system(*cmd)
104     $stderr.puts "_system #{cmd.inspect}"
105     Bundler.with_clean_env do
106       if not system({'RAILS_ENV' => 'test', "ARVADOS_WEBSOCKETS" => (if @websocket then "ws-only" end)}, *cmd)
107         raise RuntimeError, "#{cmd[0]} returned exit code #{$?.exitstatus}"
108       end
109     end
110   end
111
112   def make_ssl_cert
113     unless File.exists? './self-signed.key'
114       _system('openssl', 'req', '-new', '-x509', '-nodes',
115               '-out', './self-signed.pem',
116               '-keyout', './self-signed.key',
117               '-days', '3650',
118               '-subj', '/CN=localhost')
119     end
120   end
121
122   def kill_server
123     if (pid = find_server_pid)
124       $stderr.puts "Sending TERM to API server, pid #{pid}"
125       Process.kill 'TERM', pid
126     end
127   end
128
129   def find_server_pid
130     pid = nil
131     begin
132       pid = IO.read(@pidfile).to_i
133       $stderr.puts "API server is running, pid #{pid.inspect}"
134     rescue Errno::ENOENT
135     end
136     return pid
137   end
138
139   def run(args=[])
140     ::MiniTest.after_run do
141       self.kill_server
142     end
143
144     @websocket = args.include?("--websockets")
145
146     @pidfile = if @websocket
147                  WEBSOCKET_PID_PATH
148                else
149                  SERVER_PID_PATH
150                end
151
152     # Kill server left over from previous test run
153     self.kill_server
154
155     Capybara.javascript_driver = :poltergeist
156     Dir.chdir(ARV_API_SERVER_DIR) do |apidir|
157       ENV["NO_COVERAGE_TEST"] = "1"
158       if @websocket
159         _system('bundle', 'exec', 'passenger', 'start', '-d', '-p3333',
160                 '--pid-file', @pidfile)
161       else
162         make_ssl_cert
163         if ENV['ARVADOS_TEST_API_INSTALLED'].blank?
164           _system('bundle', 'exec', 'rake', 'db:test:load')
165           _system('bundle', 'exec', 'rake', 'db:fixtures:load')
166         end
167         _system('bundle', 'exec', 'passenger', 'start', '-d', '-p3000',
168                 '--pid-file', @pidfile,
169                 '--ssl',
170                 '--ssl-certificate', 'self-signed.pem',
171                 '--ssl-certificate-key', 'self-signed.key')
172       end
173       timeout = Time.now.tv_sec + 10
174       good_pid = false
175       while (not good_pid) and (Time.now.tv_sec < timeout)
176         sleep 0.2
177         server_pid = find_server_pid
178         good_pid = (server_pid and
179                     (server_pid > 0) and
180                     (Process.kill(0, server_pid) rescue false))
181       end
182       if not good_pid
183         raise RuntimeError, "could not find API server Rails pid"
184       end
185     end
186   end
187
188   def run_rake_task(task_name, arg_string)
189     Dir.chdir(ARV_API_SERVER_DIR) do
190       _system('bundle', 'exec', 'rake', "#{task_name}[#{arg_string}]")
191     end
192   end
193 end
194
195 class ActionController::TestCase
196   setup do
197     @counter = 0
198   end
199
200   def check_counter action
201     @counter += 1
202     if @counter == 2
203       assert_equal 1, 2, "Multiple actions in controller test"
204     end
205   end
206
207   [:get, :post, :put, :patch, :delete].each do |method|
208     define_method method do |action, *args|
209       check_counter action
210       super action, *args
211     end
212   end
213 end
214
215 # Test classes can call reset_api_fixtures(when_to_reset,flag) to
216 # override the default. Example:
217 #
218 # class MySuite < ActionDispatch::IntegrationTest
219 #   reset_api_fixtures :after_each_test, false
220 #   reset_api_fixtures :after_suite, true
221 #   ...
222 # end
223 #
224 # The default behavior is reset_api_fixtures(:after_each_test,true).
225 #
226 class ActiveSupport::TestCase
227
228   def self.inherited subclass
229     subclass.class_eval do
230       class << self
231         attr_accessor :want_reset_api_fixtures
232       end
233       @want_reset_api_fixtures = {
234         after_each_test: true,
235         after_suite: false,
236         before_suite: false,
237       }
238     end
239     super
240   end
241   # Existing subclasses of ActiveSupport::TestCase (ones that already
242   # existed before we set up the self.inherited hook above) will not
243   # get their own instance variable. They're not real test cases
244   # anyway, so we give them a "don't reset anywhere" stub.
245   def self.want_reset_api_fixtures
246     {}
247   end
248
249   def self.reset_api_fixtures where, t=true
250     if not want_reset_api_fixtures.has_key? where
251       raise ArgumentError, "There is no #{where.inspect} hook"
252     end
253     self.want_reset_api_fixtures[where] = t
254   end
255
256   def self.run *args
257     reset_api_fixtures_now if want_reset_api_fixtures[:before_suite]
258     result = super
259     reset_api_fixtures_now if want_reset_api_fixtures[:after_suite]
260     result
261   end
262
263   def after_teardown
264     if self.class.want_reset_api_fixtures[:after_each_test]
265       self.class.reset_api_fixtures_now
266     end
267     super
268   end
269
270   protected
271   def self.reset_api_fixtures_now
272     # Never try to reset fixtures when we're just using test
273     # infrastructure to run performance/diagnostics suites.
274     return unless Rails.env == 'test'
275
276     auth = api_fixture('api_client_authorizations')['admin_trustedclient']
277     Thread.current[:arvados_api_token] = auth['api_token']
278     ArvadosApiClient.new.api(nil, '../../database/reset', {})
279     Thread.current[:arvados_api_token] = nil
280   end
281 end
282
283 # If it quacks like a duck, it must be a HTTP request object.
284 class RequestDuck
285   def self.host
286     "localhost"
287   end
288
289   def self.port
290     8080
291   end
292
293   def self.protocol
294     "http"
295   end
296 end
297
298 # Example:
299 #
300 # apps/workbench$ RAILS_ENV=test bundle exec irb -Ilib:test
301 # > load 'test/test_helper.rb'
302 # > singletest 'integration/collection_upload_test.rb', 'Upload two empty files'
303 #
304 def singletest test_class_file, test_name
305   load File.join('test', test_class_file)
306   Minitest.run ['-v', '-n', "test_#{test_name.gsub ' ', '_'}"]
307   Object.send(:remove_const,
308               test_class_file.gsub(/.*\/|\.rb$/, '').camelize.to_sym)
309   ::Minitest::Runnable.runnables.reject! { true }
310 end
311
312 if ENV["RAILS_ENV"].eql? 'test'
313   ApiServerForTests.new.run
314   ApiServerForTests.new.run ["--websockets"]
315 end
316
317 # Reset fixtures now (i.e., before any tests run).
318 ActiveSupport::TestCase.reset_api_fixtures_now