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

using Test, Mmap, Random

@test isempty(Test.detect_closure_boxes(Mmap))

file = tempname()
write(file, "Hello World\n")
t = b"Hello World"
@test mmap(file, Array{UInt8,3}, (11,1,1)) == reshape(t,(11,1,1))
GC.gc(); GC.gc()
@test mmap(file, Array{UInt8,3}, (1,11,1)) == reshape(t,(1,11,1))
GC.gc(); GC.gc()
@test mmap(file, Array{UInt8,3}, (1,1,11)) == reshape(t,(1,1,11))
GC.gc(); GC.gc()
@test size(mmap(file, Array{UInt8,3}, (11,0,1))) == (11,0,1)
@test mmap(file, Vector{UInt8}, (11,)) == t
GC.gc(); GC.gc()
@test mmap(file, Array{UInt8,2}, (1,11)) == t'
GC.gc(); GC.gc()
@test size(mmap(file, Array{UInt8,2}, (0,12))) == (0,12)
@test size(mmap(file, Matrix{Float32}, (10,0))) == (10,0)
m = mmap(file, Array{UInt8,3}, (1,2,1))
@test m == reshape(b"He",(1,2,1))
finalize(m); m=nothing; GC.gc()

# constructors
@test length(@inferred mmap(file)) == 12
@test length(@inferred mmap(file, Vector{Int8})) == 12
@test length(@inferred mmap(file, Matrix{Int8}, (12,1))) == 12
@test length(@inferred mmap(file, Matrix{Int8}, (12,1), 0)) == 12
@test length(@inferred mmap(file, Matrix{Int8}, (12,1), 0; grow=false)) == 12
@test length(@inferred mmap(file, Matrix{Int8}, (12,1), 0; shared=false)) == 12
@test length(@inferred mmap(file, Vector{Int8}, 12)) == 12
@test length(@inferred mmap(file, Vector{Int8}, 12, 0)) == 12
@test length(@inferred mmap(file, Vector{Int8}, 12, 0; grow=false)) == 12
@test length(@inferred mmap(file, Vector{Int8}, 12, 0; shared=false)) == 12
s = open(file)
@test length(@inferred mmap(s)) == 12
@test length(@inferred mmap(s, Vector{Int8})) == 12
@test length(@inferred mmap(s, Matrix{Int8}, (12,1))) == 12
@test length(@inferred mmap(s, Matrix{Int8}, (12,1), 0)) == 12
@test length(@inferred mmap(s, Matrix{Int8}, (12,1), 0; grow=false)) == 12
@test length(@inferred mmap(s, Matrix{Int8}, (12,1), 0; shared=false)) == 12
@test length(@inferred mmap(s, Vector{Int8}, 12)) == 12
@test length(@inferred mmap(s, Vector{Int8}, 12, 0)) == 12
@test length(@inferred mmap(s, Vector{Int8}, 12, 0; grow=false)) == 12
@test length(@inferred mmap(s, Vector{Int8}, 12, 0; shared=false)) == 12
close(s)
@test_throws ArgumentError mmap(file, Vector{Ref}) # must be bit-type
GC.gc(); GC.gc()

file = tempname() # new name to reduce chance of issues due slow windows fs
s = open(f->f,file,"w")
@test mmap(file) == Vector{UInt8}() # requested len=0 on empty file
@test mmap(file,Vector{UInt8},0) == Vector{UInt8}()
s = open(file, "r+")
m = mmap(s,Vector{UInt8},12)
m[:] = b"Hello World\n"
Mmap.sync!(m)
close(s); finalize(m); m=nothing; GC.gc()
@test open(x->read(x, String),file) == "Hello World\n"

s = open(file, "r")
close(s)
@test_throws Base.IOError mmap(s) # closed IOStream
@test_throws ArgumentError mmap(s,Vector{UInt8},12,0) # closed IOStream
@test_throws SystemError mmap("")

# negative length
@test_throws ArgumentError mmap(file, Vector{UInt8}, -1)
# negative offset
@test_throws ArgumentError mmap(file, Vector{UInt8}, 1, -1)

for i = 0x01:0x0c
    @test length(mmap(file, Vector{UInt8}, i)) == Int(i)
end
GC.gc(); GC.gc()

sz = filesize(file)
s = open(file, "r+")
m = mmap(s, Vector{UInt8}, sz+1)
@test length(m) == sz+1 # test growing
@test m[end] == 0x00
close(s); finalize(m); m=nothing; GC.gc()
sz = filesize(file)
s = open(file, "r+")
m = mmap(s, Vector{UInt8}, 1, sz)
@test length(m) == 1
@test m[1] == 0x00
close(s); finalize(m); m=nothing; GC.gc()
sz = filesize(file)
# test where offset is actually > than size of file; file is grown with zeroed bytes
s = open(file, "r+")
m = mmap(s, Vector{UInt8}, 1, sz+1)
@test length(m) == 1
@test m[1] == 0x00
close(s); finalize(m); m=nothing; GC.gc()

# See https://github.com/JuliaLang/julia/issues/32155
# On PPC we receive `SEGV_MAPERR` instead of `SEGV_ACCERR` and
# can thus not turn the segmentation fault into an exception.
if !(Sys.ARCH === :powerpc64le || Sys.ARCH === :ppc64le)
    s = open(file, "r")
    m = mmap(s)
    @test_throws ReadOnlyMemoryError m[5] = UInt8('x') # tries to setindex! on read-only array
    finalize(m); m=nothing;
end
GC.gc()
write(file, "Hello World\n")

s = open(file, "r")
m = mmap(s)
close(s)
finalize(m); m=nothing; GC.gc()
m = mmap(file)
s = open(file, "r+")
c = mmap(s)
d = mmap(s)
c[1] = UInt8('J')
Mmap.sync!(c)
close(s)
@test m[1] == UInt8('J')
@test d[1] == UInt8('J')
finalize(m); finalize(c); finalize(d)
m=nothing; c=nothing; d=nothing; GC.gc()

write(file, "Hello World\n")

s = open(file, "r")
@test isreadonly(s) == true
c = mmap(s, Vector{UInt8}, (11,))
@test c == b"Hello World"
finalize(c); c=nothing; GC.gc()
c = mmap(s, Vector{UInt8}, (UInt16(11),))
@test c == b"Hello World"
finalize(c); c=nothing; GC.gc()
@test_throws ArgumentError mmap(s, Vector{UInt8}, (Int16(-11),))
@test_throws ArgumentError mmap(s, Vector{UInt8}, (typemax(UInt),))
@test_throws ArgumentError mmap(s, Matrix{UInt8}, (typemax(Int) - Mmap.PAGESIZE - 1, 2)) # overflow
close(s)
s = open(file, "r+")
@test isreadonly(s) == false
c = mmap(s, Vector{UInt8}, (11,))
c[5] = UInt8('x')
Mmap.sync!(c)
close(s)
s = open(file, "r")
str = readline(s)
close(s)
@test startswith(str, "Hellx World")
finalize(c); c=nothing; GC.gc()

c = mmap(file)
@test c == b"Hellx World\n"
finalize(c); c=nothing; GC.gc()
c = mmap(file, Vector{UInt8}, 3)
@test c == b"Hel"
finalize(c); c=nothing; GC.gc()
s = open(file, "r")
c = mmap(s, Vector{UInt8}, 6)
@test c == b"Hellx "
close(s)
finalize(c); c=nothing; GC.gc()
c = mmap(file, Vector{UInt8}, 5, 6)
@test c == b"World"
finalize(c); c=nothing; GC.gc()

s = open(file, "w")
write(s, "Hello World\n")
close(s)

# test mmap
m = mmap(file)
tdata = b"Hello World\n"
for i = 1:12
    @test m[i] == tdata[i]
end
@test_throws BoundsError m[13]
finalize(m); m=nothing; GC.gc()

m = mmap(file,Vector{UInt8},6)
@test m[1] == b"H"[1]
@test m[2] == b"e"[1]
@test m[3] == b"l"[1]
@test m[4] == b"l"[1]
@test m[5] == b"o"[1]
@test m[6] == b" "[1]
@test_throws BoundsError m[7]
finalize(m); m=nothing; GC.gc()

m = mmap(file,Vector{UInt8},2,6)
@test m[1] == b"W"[1]
@test m[2] == b"o"[1]
@test_throws BoundsError m[3]
finalize(m); m = nothing; GC.gc()

file = tempname() # new name to reduce chance of issues due slow windows fs
s = open(file, "w")
write(s, [0xffffffffffffffff,
          0xffffffffffffffff,
          0xffffffffffffffff,
          0x000000001fffffff])
close(s)
s = open(file, "r")
@test isreadonly(s)
b = @inferred mmap(s, BitArray, (17,13))
@test Test._check_bitarray_consistency(b)
@test b == trues(17,13)
@test_throws ArgumentError mmap(s, BitArray, (7,3))
close(s)
s = open(file, "r+")
b = mmap(s, BitArray, (17,19))
@test Test._check_bitarray_consistency(b)
rand!(b)
Mmap.sync!(b)
b0 = copy(b)
@test Test._check_bitarray_consistency(b0)
close(s)
s = open(file, "r")
@test isreadonly(s)
b = mmap(s, BitArray, (17,19))
@test Test._check_bitarray_consistency(b)
@test b == b0
close(s)
finalize(b); finalize(b0)
b = nothing; b0 = nothing
GC.gc()

open(file,"w") do f
    write(f,UInt64(1))
    write(f,UInt8(1))
end
@test filesize(file) == 9
s = open(file, "r+")
m = mmap(s, BitArray, (72,))
@test Test._check_bitarray_consistency(m)
@test length(m) == 72
close(s); finalize(m); m = nothing; GC.gc()

m = mmap(file, BitArray, (72,))
@test Test._check_bitarray_consistency(m)
@test length(m) == 72
finalize(m); m = nothing; GC.gc()

s = open(file, "r+")
m = mmap(s, BitArray, 72) # len integer instead of dims
@test Test._check_bitarray_consistency(m)
@test length(m) == 72
close(s); finalize(m); m = nothing; GC.gc()

m = mmap(file, BitArray, 72) # len integer instead of dims
@test Test._check_bitarray_consistency(m)
@test length(m) == 72
finalize(m); m = nothing; GC.gc()
rm(file)

# mmap with an offset
A = rand(1:20, 500, 300)
fname = tempname()
s = open(fname, "w+")
write(s, size(A,1))
write(s, size(A,2))
write(s, A)
close(s)
s = open(fname)
m = read(s, Int)
n = read(s, Int)
A2 = mmap(s, Matrix{Int}, (m,n))
@test A == A2
seek(s, 0)
A3 = mmap(s, Matrix{Int}, (m,n), convert(Int64, 2*sizeof(Int)))
@test A == A3
seek(s, 0)
A4 = mmap(s, Matrix{Int}, (m,150), convert(Int64, (2+150*m)*sizeof(Int)))
@test A[:, 151:end] == A4
close(s)
finalize(A2); finalize(A3); finalize(A4)
A2 = A3 = A4 = nothing
GC.gc()
rm(fname)

# Anonymous mmaps

m = mmap(Vector{UInt8}, 12)
@test length(m) == 12
@test all(m .== 0x00)
@test m[1] === 0x00
@test m[end] === 0x00
m[1] = 0x0a
Mmap.sync!(m)
@test m[1] === 0x0a
m = mmap(Vector{UInt8}, 12; shared=false)
m = mmap(Vector{Int}, 12)
@test length(m) == 12
@test all(m .== 0)
@test m[1] === 0
@test m[end] === 0
m = mmap(Vector{Float64}, 12)
@test length(m) == 12
@test all(m .== 0.0)
m = mmap(Matrix{Int8}, (12,12))
@test size(m) == (12,12)
@test all(m == zeros(Int8, (12,12)))
@test sizeof(m) == prod((12,12))
n = similar(m)
@test size(n) == (12,12)
n = similar(m, (2,2))
@test size(n) == (2,2)
n = similar(m, 12)
@test length(n) == 12
@test size(n) == (12,)
finalize(m); m = nothing; GC.gc()

m = mmap(BitVector, 12)
@test length(m) == 12
@test !any(m)
finalize(m); m = nothing; GC.gc()

# 0-length anonymous mmaps
@test length(mmap(Vector{UInt8}, 0)) == 0
@test size(mmap(Matrix{Int8}, (0, 5))) == (0, 5)
@test length(mmap(BitVector, 0)) == 0
@test size(mmap(BitMatrix, (0, 5))) == (0, 5)

# Regression test: anonymous mmap size computation overflowing must raise ArgumentError, not
# a confusing InexactError from a wrapped/corrupted size reaching a later, unrelated conversion.
@test_throws ArgumentError mmap(Array{Int,1}, (typemax(Int) ÷ 4,))

# Regression test: offset addition overflowing must also raise ArgumentError, not InexactError,
# even when the byte-count computation alone does not overflow.
let file = tempname()
    write(file, UInt8(0))
    s = open(file, "r+")
    @test_throws ArgumentError mmap(s, Vector{UInt8}, (100,), typemax(Int) - 10)
    close(s)
    rm(file)
end

@static if Sys.islinux()

    function has_open_fd(name)
        for fd in readdir("/proc/self/fd")
            try
                basename(name) == basename(Base.Filesystem.readlink("/proc/self/fd/$fd")) && return true
            catch
                # fd may close between listing and reading the link
            end
        end
        return false
    end

    function has_open_fd(pid::Integer, name)
        for fd in readdir("/proc/$pid/fd")
            try
                basename(name) == basename(Base.Filesystem.readlink("/proc/$pid/fd/$fd")) && return true
            catch
                # fd may close between listing and reading the link
            end
        end
        return false
    end

elseif Sys.iswindows()

function named_mapping_open(segname)
    try
        io = open(Mmap.SharedMemory, segname, 1; readonly=true, create=false)
        close(io)
        return true
    catch
        return false
    end
end

end

@testset "SharedMemory" begin
    @testset "Properties" begin
        io = open(Mmap.SharedMemory, "", 12; readonly = false, create = true)
        @test io.name == ""
        @static if Sys.isunix()
            # On Unix systems, anonymous mappings are handled by `mmap` alone
            @test io.handle == Base.INVALID_OS_HANDLE
        else # Sys.iswindows()
            @test io.handle != Base.INVALID_OS_HANDLE
        end
        @test !io.readonly
        @test io.create
        @test io.size == 12
        @test io.ismapped == false
        @test isopen(io)
        @test !isfile(io)
        @test filesize(io) == io.size
        @test position(io) == 0
        @test isreadable(io)
        @test iswritable(io)
        @test Mmap.isanonymous(io)
        close(io)
        @test !isopen(io)

        name = "/jlsharedsegment"
        io = open(Mmap.SharedMemory, name, 12; readonly = false, create = true)
        @test io.name == name
        @test io.handle != Base.INVALID_OS_HANDLE
        @test isopen(io)
        @test !Mmap.isanonymous(io)
        close(io)
        @test !isopen(io)
    end

    @testset "SharedMemory is single-use" begin
        io = open(Mmap.SharedMemory, "", 12; readonly = false, create = true)
        m = mmap(io, Vector{UInt8}, 12)
        @test_throws ArgumentError mmap(io, Vector{UInt8}, 12)
        close(io); finalize(m); m = nothing; GC.gc()

        io = open(Mmap.SharedMemory, "/jlsharedsegment", 12; readonly = false, create = true)
        m = mmap(io, Vector{UInt8}, 12)
        @test_throws ArgumentError mmap(io, Vector{UInt8}, 12)
        close(io); finalize(m); m = nothing; GC.gc()
    end

    @testset "Anonymous SharedMemory mmaps are independent" begin
        io1 = open(Mmap.SharedMemory, "", 12; readonly = false, create = true)
        io2 = open(Mmap.SharedMemory, "", 12; readonly = false, create = true)
        m1 = mmap(io1, Vector{UInt8}, 12)
        m2 = mmap(io2, Vector{UInt8}, 12)
        @test all(m1 .== 0)
        @test all(m2 .== 0)
        m1 .= 1
        @test all(m1 .== 1)
        @test all(m2 .== 0)
        close(io1); close(io2); finalize(m1); finalize(m2); m1 = m2 = nothing; GC.gc()
    end

    @testset "SharedMemory modes" begin
        @test_throws ArgumentError open(Mmap.SharedMemory, "", 12; readonly = false, create = false)
        @test_throws ArgumentError open(Mmap.SharedMemory, "", 12; readonly = true, create = true)
        @test_throws Base.IOError open(Mmap.SharedMemory, "/jlsharedsegment", 12; readonly = false, create = false)

        io1 = open(Mmap.SharedMemory, "/jlsharedsegment", 12; readonly = false, create = true)
        @test_throws Base.IOError open(Mmap.SharedMemory, "/jlsharedsegment", 12; readonly = false, create = true)
        io2 = open(Mmap.SharedMemory, "/jlsharedsegment", 12; readonly = true, create = false)
        m = mmap(io2, Vector{UInt8}, 12)
        @test_throws ReadOnlyMemoryError m .= 1
        close(io1); close(io2); finalize(m); m = nothing; GC.gc()
    end

    @static if Sys.islinux()
        @testset "Opening an existing region with an oversized declared size is rejected at mmap()" begin
            name = "/jlsharedsegment"
            io1 = open(Mmap.SharedMemory, name, 12; readonly = false, create = true)

            io2 = open(Mmap.SharedMemory, name, 13; readonly = false, create = false)
            @test_throws Base.IOError mmap(io2, Vector{UInt8}, 13)
            close(io2)

            io3 = open(Mmap.SharedMemory, name, 12; readonly = false, create = false) # exact size still works
            m3 = mmap(io3, Vector{UInt8}, 12)
            finalize(m3); m3 = nothing; GC.gc()

            io4 = open(Mmap.SharedMemory, name, 5; readonly = false, create = false)  # smaller declared size is safe
            m4 = mmap(io4, Vector{UInt8}, 5)
            finalize(m4); m4 = nothing; GC.gc()

            close(io1); close(io3); close(io4)
        end
    elseif Sys.isapple() || Sys.iswindows()
        @testset "Opening an existing region with a declared size beyond the real page allocation still fails safely at mmap()" begin
            name = "/jlsharedsegment"
            io1 = open(Mmap.SharedMemory, name, 12; readonly = false, create = true)

            io2 = open(Mmap.SharedMemory, name, 1_000_000; readonly = false, create = false)
            @test_throws Base.IOError mmap(io2, Vector{UInt8}, 1_000_000)
            close(io2)

            io3 = open(Mmap.SharedMemory, name, 12; readonly = false, create = false) # exact size still works
            io4 = open(Mmap.SharedMemory, name, 5; readonly = false, create = false)  # smaller declared size is safe

            # Documented limitation: a declared size that still fits within the same committed
            # page as the real region is indistinguishable from it, so mmap() succeeds for it too.
            io5 = open(Mmap.SharedMemory, name, 100; readonly = false, create = false)
            m = mmap(io5, Vector{UInt8}, 100)
            finalize(m); m = nothing; GC.gc()

            close(io1); close(io3); close(io4); close(io5)
        end
    end

    @testset "Named SharedMemory mmaps are shared" begin
        io1 = open(Mmap.SharedMemory, "/jlsharedsegment", 12; readonly = false, create = true)
        io2 = open(Mmap.SharedMemory, "/jlsharedsegment", 12; readonly = false, create = false)
        m1 = mmap(io1, Vector{UInt8}, 12)
        m2 = mmap(io2, Vector{UInt8}, 12)
        @test all(m1 .== 0)
        @test all(m2 .== 0)
        m1 .= 1
        @test all(m1 .== 1)
        @test all(m2 .== 1)
        close(io1); close(io2); finalize(m1); finalize(m2); m1 = m2 = nothing; GC.gc()
    end

    @testset "Resource cleanup" begin
        name = "/jlsharedsegment"

        @static if Sys.islinux()

            io = open(Mmap.SharedMemory, name, 12; readonly = false, create = true)
            @test has_open_fd(name)
            close(io)
            @test !has_open_fd(name)

            io = open(Mmap.SharedMemory, name, 12; readonly = false, create = true)
            @test has_open_fd(name)
            finalize(io)
            @test !has_open_fd(name)
            io = nothing; GC.gc()

        elseif Sys.iswindows()

            io = open(Mmap.SharedMemory, name, 12; readonly = false, create = true)
            @test named_mapping_open(name)
            close(io)
            @test !named_mapping_open(name)

            io = open(Mmap.SharedMemory, name, 12; readonly = false, create = true)
            @test named_mapping_open(name)
            finalize(io)
            @test !named_mapping_open(name)
            io = nothing; GC.gc()

        else # other Unix, tests TODO

        end
    end

    @testset "SharedMemory handles/fds are not inherited by child processes" begin
        name = "/jlsharedsegment"

        @static if Sys.islinux()

            # A spawned child must not end up with an open fd to the segment: the fd is marked
            # close-on-exec via fcntl(F_SETFD, FD_CLOEXEC) right after shm_open(), so it should
            # vanish across the child's exec().
            io = open(Mmap.SharedMemory, name, 12; readonly = false, create = true)
            child = run(`sleep 5`; wait = false)
            try
                sleep(0.3) # let the child settle after exec()
                @test !has_open_fd(Base.Libc.getpid(child), name)
            finally
                close(io)
                kill(child)
                wait(child)
            end

        elseif Sys.iswindows()

            # If the handle were inherited (bInheritHandle=true), the still-running child
            # would keep the kernel object alive after we close our own handle, and
            # recreating the segment here would fail with ERROR_ALREADY_EXISTS.
            io = open(Mmap.SharedMemory, name, 12; readonly = false, create = true)
            child = run(`$(Base.julia_cmd()) -e "sleep(5)"`; wait = false)
            try
                sleep(0.5)
                close(io)
                io2 = open(Mmap.SharedMemory, name, 12; readonly = false, create = true)
                close(io2)
            finally
                kill(child)
                wait(child)
            end

        else # other Unix, tests TODO

        end
    end

    @testset "String mode" begin
        name = "/jlsharedsegment"
        io1 = open(Mmap.SharedMemory, name, 12, "w+")
        @test isreadable(io1)
        @test iswritable(io1)
        io2 = open(Mmap.SharedMemory, name, 12, "r+")
        @test isreadable(io2)
        @test iswritable(io2)
        io3 = open(Mmap.SharedMemory, name, 12, "r")
        @test isreadable(io3)
        @test !iswritable(io3)
        @test_throws Base.IOError open(Mmap.SharedMemory, name, 12, "w+")
        @test_throws ArgumentError open(Mmap.SharedMemory, name, 12, "w")
        @test_throws ArgumentError open(Mmap.SharedMemory, name, 12, "a")
        @test_throws ArgumentError open(Mmap.SharedMemory, name, 12, "a+")
        close(io1); close(io2); close(io3);
    end

    # TODO: Test for validation in `mmap`, `shared` kwarg usage, sync method, size == 0 error
end

@testset "munmap!" begin
    # File-backed Array: mapping is visible before munmap!, gone after
    file = tempname()
    write(file, fill(0x01, 100))
    m = mmap(file)
    @test m[1] == 0x01
    @static if Sys.islinux()
        @test any(line -> contains(line, file), readlines("/proc/self/maps"))
    end
    Mmap.munmap!(m)
    @static if Sys.islinux()
        @test !any(line -> contains(line, file), readlines("/proc/self/maps"))
    end
    rm(file)

    # File-backed BitArray
    file = tempname()
    write(file, rand(UInt8, 16))
    b = mmap(file, BitArray, 128)
    @static if Sys.islinux()
        @test any(line -> contains(line, file), readlines("/proc/self/maps"))
    end
    Mmap.munmap!(b)
    @static if Sys.islinux()
        @test !any(line -> contains(line, file), readlines("/proc/self/maps"))
    end
    rm(file)

    # Anonymous Array and BitArray: should release without error
    m = mmap(Vector{UInt8}, 12)
    Mmap.munmap!(m)
    b = mmap(BitVector, 12)
    Mmap.munmap!(b)
end

if Sys.isunix()
    file = tempname()
    write(file, rand(Float64, 20))
    A = mmap(file, Vector{Float64}, 20)
    @test Mmap.madvise!(A, Mmap.MADV_WILLNEED) === nothing # checking for no error
    finalize(A); A = nothing; GC.gc()

    write(file, BitArray(rand(Bool, 20)))
    b = mmap(file, BitArray, 20)
    @test Mmap.madvise!(b, Mmap.MADV_WILLNEED) === nothing
    finalize(b); b = nothing; GC.gc()
    rm(file)
end

# test #14885
file = tempname()
touch(file)
open(file, "r+") do s
    A = mmap(s, Vector{UInt8}, (10,), 0)
    Mmap.sync!(A)
    finalize(A); A = nothing; GC.gc()
    A = mmap(s, Vector{UInt8}, (10,), 1)
    Mmap.sync!(A)
    finalize(A); A = nothing;
end
GC.gc()
rm(file)

# test #30537
file = tempname()
rdat = [fill(0x00, 8); fill(typemax(UInt8), 8); 0x00]
open(f->write(f, rdat), file, "w+")
for T in (Int32, UInt32, UInt64, Int64)
    local b = Mmap.mmap(file, BitVector, (64), T(8))
    @test b isa BitVector
    @test all(b)
    @test length(b) == 64
end
GC.gc()
rm(file)

# test for #58982 - mmap with primitive types
file = tempname()
primitive type PrimType9Bytes 9*8 end
arr = Vector{PrimType9Bytes}(undef, 2)
write(file, arr)
m = mmap(file, Vector{PrimType9Bytes})
@test length(m) == 2
@test m[1] == arr[1]
@test m[2] == arr[2]
finalize(m); m = nothing; GC.gc()
rm(file)


@testset "Docstrings" begin
    @test isempty(Docs.undocumented_names(Mmap))
end
