diff --git a/lib/tasks/qunit.rake b/lib/tasks/qunit.rake index ab902a1e749..4ffcd5fdeb5 100644 --- a/lib/tasks/qunit.rake +++ b/lib/tasks/qunit.rake @@ -42,7 +42,10 @@ task "qunit:test", [:timeout, :qunit_path] do |_, args| "UNICORN_PID_PATH" => "#{Rails.root}/tmp/pids/unicorn_test_#{port}.pid", # So this can run alongside development "UNICORN_PORT" => port.to_s, "UNICORN_SIDEKIQS" => "0", - "DISCOURSE_SKIP_CSS_WATCHER" => "1" + "DISCOURSE_SKIP_CSS_WATCHER" => "1", + "UNICORN_LISTENER" => "127.0.0.1:#{port}", + "LOGSTASH_UNICORN_URI" => nil, + "UNICORN_WORKERS" => "3" }, "#{Rails.root}/bin/unicorn -c config/unicorn.conf.rb", pgroup: true diff --git a/lib/tasks/themes.rake b/lib/tasks/themes.rake index f11a628bec5..a69b5e07fb8 100644 --- a/lib/tasks/themes.rake +++ b/lib/tasks/themes.rake @@ -120,7 +120,22 @@ task "themes:qunit", :type, :value do |t, args| end desc "Install a theme/component on a temporary DB and run QUnit tests" -task "themes:install_and_test" => :environment do |t, args| +task "themes:isolated_test" => :environment do |t, args| + # This task can be called in a production environment that likely has a bunch + # of DISCOURSE_* env vars that we don't want to be picked up by the Unicorn + # server that will be spawned for the tests. So we need to unset them all + # before we proceed. + # Make this behavior opt-in to make it very obvious. + if ENV["UNSET_DISCOURSE_ENV_VARS"] == "1" + ENV.keys.each do |key| + next if !key.start_with?('DISCOURSE_') + ENV[key] = nil + end + end + + redis = TemporaryRedis.new + redis.start + $redis = redis.instance # rubocop:disable Style/GlobalVars db = TemporaryDb.new db.start db.migrate @@ -139,6 +154,7 @@ task "themes:install_and_test" => :environment do |t, args| ENV["PGHOST"] = "localhost" ENV["QUNIT_RAILS_ENV"] = "development" ENV["DISCOURSE_DEV_DB"] = "discourse" + ENV["DISCOURSE_REDIS_PORT"] = redis.port.to_s count = 0 themes.each do |(name, id)| @@ -155,4 +171,5 @@ task "themes:install_and_test" => :environment do |t, args| ensure db&.stop db&.remove + redis&.remove end diff --git a/lib/temporary_redis.rb b/lib/temporary_redis.rb new file mode 100644 index 00000000000..fb2a7994017 --- /dev/null +++ b/lib/temporary_redis.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +class TemporaryRedis + REDIS_TEMP_DIR = "/tmp/discourse_temp_redis" + REDIS_LOG_PATH = "#{REDIS_TEMP_DIR}/redis.log" + REDIS_PID_PATH = "#{REDIS_TEMP_DIR}/redis.pid" + + attr_reader :instance + + def initialize + set_redis_server_bin + end + + def port + @port ||= find_free_port(11000..11900) + end + + def start + return if @started + FileUtils.rm_rf(REDIS_TEMP_DIR) + Dir.mkdir(REDIS_TEMP_DIR) + FileUtils.touch(REDIS_LOG_PATH) + + puts "Starting redis on port: #{port}" + @thread = Thread.new do + system( + @redis_server_bin, + "--port", port.to_s, + "--pidfile", REDIS_PID_PATH, + "--logfile", REDIS_LOG_PATH, + "--databases", "1", + "--save", '""', + "--appendonly", "no", + "--daemonize", "no", + "--maxclients", "100", + "--dir", REDIS_TEMP_DIR + ) + end + + puts "Waiting for redis server to start..." + success = false + instance = nil + config = { + port: port, + host: "127.0.0.1", + db: 0 + } + start = Time.now + while !success + begin + instance = DiscourseRedis.new(config, namespace: true) + success = instance.ping == "PONG" + rescue Redis::CannotConnectError + ensure + if !success && (Time.now - start) >= 5 + STDERR.puts "ERROR: Could not connect to redis in 5 seconds." + self.remove + exit(1) + elsif !success + sleep 0.1 + end + end + end + puts "Redis is ready" + @instance = instance + @started = true + end + + def remove + if @instance + @instance.shutdown + @thread.join + puts "Redis has been shutdown." + end + FileUtils.rm_rf(REDIS_TEMP_DIR) + @started = false + puts "Redis files have been cleaned up." + end + + private + + def set_redis_server_bin + path = `which redis-server 2> /dev/null`.strip + if path.size < 1 + STDERR.puts 'ERROR: redis-server is not installed on this machine. Please install it' + exit(1) + end + @redis_server_bin = path + rescue => ex + STDERR.puts 'ERROR: Failed to find redis-server binary:' + STDERR.puts ex.inspect + exit(1) + end + + def find_free_port(range) + range.each do |port| + return port if port_available?(port) + end + end + + def port_available?(port) + TCPServer.open(port).close + true + rescue Errno::EADDRINUSE + false + end +end