helpers.rb 4.12 KB
Newer Older
1
require 'digest/md5'
2 3
require 'sys/proctable'
require 'colorize'
4
require 'timeout'
5
require 'net/http'
6

7 8 9 10
def find_executable(exec)
    path = %x(which #{exec}).strip
    $?.exitstatus == 0 ? path : nil
end
11 12

def select_executable(*cmds)
13
    cmds.find_all{ |cmd| !find_executable(cmd).nil? }[0] || fail("No executables found from #{cmds.join(', ')}")
14 15 16
end

def django_admin(system, env, command, *args)
17
    return "./manage.py #{system} --settings #{env} #{command} --traceback #{args.join(' ')}"
18 19 20 21 22 23
end

def report_dir_path(dir)
    return File.join(REPORT_DIR, dir.to_s)
end

24
def compute_fingerprint(files, dirs)
25
    digest = Digest::MD5.new()
26 27

    # Digest the contents of all the files.
28 29 30
    Dir[*files].select{|file| File.file?(file)}.each do |file|
        digest.file(file)
    end
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

    # Digest the names of the files in all the dirs.
    dirs.each do |dir|
        file_names = Dir.entries(dir).sort.join(" ")
        digest.update(file_names)
    end

    digest.hexdigest
end

# Hash the contents of all the files, and the names of files in the dirs.
# Run the block if they've changed.
def when_changed(unchanged_message, files, dirs=[])
    Rake::Task[PREREQS_MD5_DIR].invoke
    cache_file = File.join(PREREQS_MD5_DIR, files[0].gsub(/\W+/, '-').sub(/-+$/, '')) + '.md5'
    if !File.exists?(cache_file) or compute_fingerprint(files, dirs) != File.read(cache_file)
47
        yield
48
        File.write(cache_file, compute_fingerprint(files, dirs))
49 50
    elsif !unchanged_message.empty?
        puts unchanged_message
51 52 53 54 55
    end
end

# Runs Process.spawn, and kills the process at the end of the rake process
# Expects the same arguments as Process.spawn
56 57 58
def background_process(command, logfile=nil)
    spawn_opts = {:pgroup => true}
    if !logfile.nil?
59
        puts "Running '#{command.join(' ')}', redirecting output to #{logfile}"
60 61 62 63
        spawn_opts[[:err, :out]] = [logfile, 'a']
    end
    pid = Process.spawn({}, *command, spawn_opts)
    command = [*command]
64 65 66 67 68

    at_exit do
        pgid = Process.getpgid(pid)
        begin
            Timeout.timeout(5) do
69
                Process.kill(:SIGINT, -pgid)
70 71 72
                Process.wait(-pgid)
            end
        rescue Timeout::Error
73 74 75 76 77 78 79 80 81
            begin
                Timeout.timeout(5) do
                    Process.kill(:SIGTERM, -pgid)
                    Process.wait(-pgid)
                end
            rescue Timeout::Error
                Process.kill(:SIGKILL, -pgid)
                Process.wait(-pgid)
            end
82 83 84 85
        end
    end
end

86 87
# Runs a command as a background process, as long as no other processes
# tagged with the same tag are running
88 89
def singleton_process(command, logfile=nil)
    command = [*command]
90
    if Sys::ProcTable.ps.select {|proc| proc.cmdline.include?(command.join(' '))}.empty?
91
        background_process(command, logfile)
92 93 94 95 96
    else
        puts "Process '#{command.join(' ')} already running, skipping".blue
    end
end

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
# Wait for a server to respond with status 200 at "/"
def wait_for_server(server, port)
    attempts = 0
    begin
        http = Net::HTTP.start(server, port, {open_timeout: 10, read_timeout: 10})
        response = http.head("/")
        response.code == "200"
        true
    rescue
        sleep(1)
        attempts += 1
        if attempts < 20
            retry
        else
            false
        end
    end
end

116 117 118 119 120
def environments(system)
    Dir["#{system}/envs/**/*.py"].select{|file| ! (/__init__.py$/ =~ file)}.map do |env_file|
        env_file.gsub("#{system}/envs/", '').gsub(/\.py/, '').gsub('/', '.')
    end
end
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147

$failed_tests = 0

# Run sh on args. If TESTS_FAIL_FAST is set, then stop on the first shell failure.
# Otherwise, a final task will be added that will fail if any tests have failed
def test_sh(*args)
    sh(*args) do |ok, res|
        if ok
            return
        end

        if ENV['TESTS_FAIL_FAST']
            fail("Test failed!")
        else
            $failed_tests += 1
        end
    end
end

# Add a task after all other tasks that fails if any tests have failed
if !ENV['TESTS_FAIL_FAST']
    task :fail_tests do
        fail("#{$failed_tests} tests failed!") if $failed_tests > 0
    end

    Rake.application.top_level_tasks << :fail_tests
end