Add 'apps/arv-web/' from commit 'f9732ad8460d013c2f28363655d0d1b91894dca5'
[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         _system('bundle', 'exec', 'rake', 'db:test:load')
164         _system('bundle', 'exec', 'rake', 'db:fixtures:load')
165         _system('bundle', 'exec', 'passenger', 'start', '-d', '-p3000',
166                 '--pid-file', @pidfile,
167                 '--ssl',
168                 '--ssl-certificate', 'self-signed.pem',
169                 '--ssl-certificate-key', 'self-signed.key')
170       end
171       timeout = Time.now.tv_sec + 10
172       good_pid = false
173       while (not good_pid) and (Time.now.tv_sec < timeout)
174         sleep 0.2
175         server_pid = find_server_pid
176         good_pid = (server_pid and
177                     (server_pid > 0) and
178                     (Process.kill(0, server_pid) rescue false))
179       end
180       if not good_pid
181         raise RuntimeError, "could not find API server Rails pid"
182       end
183     end
184   end
185
186   def run_rake_task(task_name, arg_string)
187     Dir.chdir(ARV_API_SERVER_DIR) do
188       _system('bundle', 'exec', 'rake', "#{task_name}[#{arg_string}]")
189     end
190   end
191 end
192
193 class ActionController::TestCase
194   setup do
195     @counter = 0
196   end
197
198   def check_counter action
199     @counter += 1
200     if @counter == 2
201       assert_equal 1, 2, "Multiple actions in controller test"
202     end
203   end
204
205   [:get, :post, :put, :patch, :delete].each do |method|
206     define_method method do |action, *args|
207       check_counter action
208       super action, *args
209     end
210   end
211 end
212
213 # Test classes can call reset_api_fixtures(when_to_reset,flag) to
214 # override the default. Example:
215 #
216 # class MySuite < ActionDispatch::IntegrationTest
217 #   reset_api_fixtures :after_each_test, false
218 #   reset_api_fixtures :after_suite, true
219 #   ...
220 # end
221 #
222 # The default behavior is reset_api_fixtures(:after_each_test,true).
223 #
224 class ActiveSupport::TestCase
225
226   def self.inherited subclass
227     subclass.class_eval do
228       class << self
229         attr_accessor :want_reset_api_fixtures
230       end
231       @want_reset_api_fixtures = {
232         after_each_test: true,
233         after_suite: false,
234         before_suite: false,
235       }
236     end
237     super
238   end
239   # Existing subclasses of ActiveSupport::TestCase (ones that already
240   # existed before we set up the self.inherited hook above) will not
241   # get their own instance variable. They're not real test cases
242   # anyway, so we give them a "don't reset anywhere" stub.
243   def self.want_reset_api_fixtures
244     {}
245   end
246
247   def self.reset_api_fixtures where, t=true
248     if not want_reset_api_fixtures.has_key? where
249       raise ArgumentError, "There is no #{where.inspect} hook"
250     end
251     self.want_reset_api_fixtures[where] = t
252   end
253
254   def self.run *args
255     reset_api_fixtures_now if want_reset_api_fixtures[:before_suite]
256     result = super
257     reset_api_fixtures_now if want_reset_api_fixtures[:after_suite]
258     result
259   end
260
261   def after_teardown
262     if self.class.want_reset_api_fixtures[:after_each_test]
263       self.class.reset_api_fixtures_now
264     end
265     super
266   end
267
268   protected
269   def self.reset_api_fixtures_now
270     # Never try to reset fixtures when we're just using test
271     # infrastructure to run performance/diagnostics suites.
272     return unless Rails.env == 'test'
273
274     auth = api_fixture('api_client_authorizations')['admin_trustedclient']
275     Thread.current[:arvados_api_token] = auth['api_token']
276     ArvadosApiClient.new.api(nil, '../../database/reset', {})
277     Thread.current[:arvados_api_token] = nil
278   end
279 end
280
281 # If it quacks like a duck, it must be a HTTP request object.
282 class RequestDuck
283   def self.host
284     "localhost"
285   end
286
287   def self.port
288     8080
289   end
290
291   def self.protocol
292     "http"
293   end
294 end
295
296 # Example:
297 #
298 # apps/workbench$ RAILS_ENV=test bundle exec irb -Ilib:test
299 # > load 'test/test_helper.rb'
300 # > singletest 'integration/collection_upload_test.rb', 'Upload two empty files'
301 #
302 def singletest test_class_file, test_name
303   load File.join('test', test_class_file)
304   Minitest.run ['-v', '-n', "test_#{test_name.gsub ' ', '_'}"]
305   Object.send(:remove_const,
306               test_class_file.gsub(/.*\/|\.rb$/, '').camelize.to_sym)
307   ::Minitest::Runnable.runnables.reject! { true }
308 end
309
310 if ENV["RAILS_ENV"].eql? 'test'
311   ApiServerForTests.new.run
312   ApiServerForTests.new.run ["--websockets"]
313 end