# This file is a part of Julia. License is MIT: https://julialang.org/license

# This includes a few helper variables and functions that provide information about the
# test environment (command line flags, current module, etc).
# This file can be included multiple times in the same module if necessary,
# which can happen with unisolated test runs.

if !@isdefined(testenv_defined)
    const testenv_defined = true
    if haskey(ENV, "JULIA_TEST_EXEFLAGS")
        const test_exeflags = `$(Base.shell_split(ENV["JULIA_TEST_EXEFLAGS"]))`
    else
        const test_exeflags = Base.julia_cmd()
        filter!(test_exeflags.exec) do c
            return !(startswith(c, "--depwarn") || startswith(c, "--check-bounds"))
        end
        push!(test_exeflags.exec, "--check-bounds=yes")
        push!(test_exeflags.exec, "--startup-file=no")
        push!(test_exeflags.exec, "--depwarn=error")
    end
    if haskey(ENV, "JULIA_TEST_EXTRA_EXEFLAGS")
        append!(test_exeflags.exec, Base.shell_split(ENV["JULIA_TEST_EXTRA_EXEFLAGS"]))
    end

    if haskey(ENV, "JULIA_TEST_EXENAME")
        popfirst!(test_exeflags.exec)
        const test_exename = `$(Base.shell_split(ENV["JULIA_TEST_EXENAME"]))`
    else
        const test_exename = popfirst!(test_exeflags.exec)
    end

    if haskey(ENV, "JULIA_RR")
        const rr_exename = `$(Base.shell_split(ENV["JULIA_RR"]))`
    else
        const rr_exename = ``
    end

    const test_relocated_depot = haskey(ENV, "RELOCATEDEPOT")

    # OS pids of the workers spawned by `addprocs_with_testenv`, recorded at
    # spawn time (while the worker is guaranteed healthy) so that the teardown
    # hook in runtests.jl can signal workers that have hung in the meantime.
    const worker_ospids = Dict{Int, Int}()

    # Best-effort enumeration of the direct children of `pid`.
    function child_pids(pid::Integer)
        children = Int[]
        try
            # On Linux, read /proc. Elsewhere, ask pgrep.
            if Sys.islinux()
                for tid in readdir("/proc/$pid/task")
                    append!(children, parse.(Int, split(read("/proc/$pid/task/$tid/children", String))))
                end
            else
                # `pgrep -P` exits non-zero when there are no matches
                append!(children, parse.(Int, split(read(ignorestatus(`pgrep -P $pid`), String))))
            end
        catch
            # the process may have exited already; return what we have
        end
        return children
    end

    # Best-effort enumeration of the live descendants (children, grandchildren, ...)
    # of `pid`, ordered parents-before-children.
    function descendant_pids(pid::Integer)
        pids = Int[]
        queue = Int[pid]
        while !isempty(queue)
            children = child_pids(popfirst!(queue))
            append!(pids, children)
            append!(queue, children)
        end
        return pids
    end

    function addprocs_with_testenv(X; rr_allowed=true, kwargs...)
        exename = rr_allowed ? `$rr_exename $test_exename` : test_exename
        if X isa Integer
            heap_size=round(Int,(Sys.total_memory()/(1024^2)/(X+1)))
            push!(test_exeflags.exec, "--heap-size-hint=$(heap_size)M")
        end
        procs = addprocs(X; exename=exename, exeflags=test_exeflags, kwargs...)
        for w in procs
            worker_ospids[w] = remotecall_fetch(getpid, w)
        end
        return procs
    end

    function rmprocs_with_testenv(pids...; kwargs...)
        result = rmprocs(pids...; kwargs...)
        for p in pids, q in (p isa Integer ? (p,) : p)
            delete!(worker_ospids, q)
        end
        return result
    end

    const curmod = @__MODULE__
    const curmod_name = fullname(curmod)
    const curmod_str = curmod === Main ? "Main" : join(curmod_name, ".")
    const curmod_prefix = curmod === Main ? "" : "$(["$m." for m in curmod_name]...)"

    # platforms that support cfunction with closures
    # (requires LLVM back-end support for trampoline intrinsics)
    const cfunction_closure = Sys.ARCH === :x86_64 || Sys.ARCH === :i686

    macro async_logerr(expr)
        :(@async try
            $(esc(expr))
        catch err
            @error("An async task failed", exception=(err, catch_backtrace()))
        end)
    end
end
