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