text:
facts("About creating RingArray") do
    context("bad curly braces") do
        @fact_throws MethodError test = RingArray{Any}()
    end
    context("without curly braces") do
        @fact_throws MethodError RingArray()
    end

    context("passing a size") do
        s = rand(1:10)
        b_s = (10,)
        d_l = 100
        test = RingArray{Int, 1}(max_blocks=s)

        @fact isdefined(test.blocks, 1:s...) --> false
        @fact test.max_blocks --> s
        @fact test.next_write --> 1
        @fact test.num_users --> zeros(Int, s)
        @fact test.block_size --> b_s
        @fact test.range --> 1:0
        @fact size(test) --> tuple(d_l,)
        @fact test.data_length --> d_l
    end
    context("passing a block size") do
        s = 10
        b_s = (10,)
        d_l = 100
        test = RingArray{Int, 1}(block_size=b_s)

        @fact isdefined(test.blocks, 1:s...) --> false
        @fact test.max_blocks --> s
        @fact test.next_write --> 1
        @fact test.num_users --> zeros(Int, s)
        @fact test.block_size --> b_s
        @fact test.range --> 1:0
        @fact size(test) --> tuple(d_l,)
        @fact test.data_length --> d_l
    end
    context("passing nothing") do
        s = 10
        b_s = (10,)
        d_l = 100
        test = RingArray{Int, 1}()

        @fact test.max_blocks --> s
        @fact test.next_write --> 1
        @fact test.num_users --> zeros(Int, s)
        @fact test.block_size --> b_s
        @fact test.range --> 1:0
        @fact size(test) --> tuple(d_l,)
        @fact test.data_length --> d_l
    end
    context("passing 0 for size") do
        s = 0
        b_s = (10,)
        d_l = 100
        test = RingArray{Int, 1}(max_blocks=s)

        @fact test.max_blocks --> s
        @fact test.next_write --> 1
        @fact test.num_users --> zeros(Int, s)
        @fact test.block_size --> b_s
        @fact test.range --> 1:0
        @fact size(test) --> tuple(d_l,)
        @fact test.data_length --> d_l
    end
    context("passing a negative for size") do
        s = -1
        @fact_throws test = RingArray{Int, 1}(max_blocks=s)
    end
    context("having a multi dimensional array") do
        s = 10
        b_s = (10,10)
        d_l = 100
        test = RingArray{Int, 2}(max_blocks=s, block_size=b_s, data_length=d_l)

        @fact test.max_blocks --> s
        @fact test.next_write --> 1
        @fact test.num_users --> zeros(Int, s)
        @fact test.block_size --> b_s
        @fact test.range --> 1:0
        @fact size(test) --> tuple(b_s[1]*s, b_s[2:end]...)
        @fact test.data_length --> d_l
    end
    context("having a multi dimensional array and passing data size") do
        s = 10
        b_s = (10,10)
        d_l = 100
        test = RingArray{Int, 2}(max_blocks=s, block_size=b_s, data_length=d_l)

        @fact test.max_blocks --> s
        @fact test.next_write --> 1
        @fact test.num_users --> zeros(Int, s)
        @fact test.block_size --> b_s
        @fact test.range --> 1:0
        @fact size(test) --> tuple(b_s[1]*s, b_s[2:end]...)
        @fact test.data_length --> d_l
    end
    context("creating a RingArray of dimension of the RingArray different form the block size") do
        s = 10
        b_s = (10,10,10)
        dim = 2
        d_l = 100
        @fact_throws DimensionMismatch RingArray{Int, dim}(max_blocks=s, block_size=b_s)

        test_error = 1
        try
            RingArray{Int, dim}(max_blocks=s, block_size=b_s)
        catch e
            test_error = e
        end

        @fact test_error.msg --> "block size $(b_s) does not match dimension of RingArray $(dim)"
    end
end

############################################################################################
# INDEXING
############################################################################################

facts("Getting values from RingArray") do
    context("getting the first value") do
        s = rand(1:10)
        b_s = (rand(1:10),)
        d_l = s * b_s[1]
        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:s
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[1]) --> Int
        @fact test[1] --> test.blocks[1][1]
        @fact test[1] --> test[1]
        @fact test[] --> nothing
        @fact test[test.range] --> expected[test.range]
    end
    context("getting the first value without loading") do
        s = rand(1:10)
        b_s = (rand(1:10),)
        d_l = s * b_s[1]
        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        @fact_throws RingArrayBoundsError test[1]

        test_error = 1
        try
            test[1]
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)

        range = tuple(test.range, test.block_size[2:end]...)
        @fact occured.data --> "RingArrayBoundsError: Cannot index $((1,)), outside of range $range".data
    end
    context("getting a value in the first block") do
        s = rand(1:10)
        b_s = (rand(2:10),)
        d_l = s * b_s[1]
        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)
        index = rand(2:b_s[1])

        expected = []
        for i in 1:s
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[index]) --> Int
        @fact test[index] --> test.blocks[1][index]
        @fact test[index] --> test[index]
        @fact test[test.range] --> expected[test.range]
    end
    context("getting a value in the second block after getting a value in the first block") do
        s = rand(2:10)
        b_s = (rand(2:10),)
        d_l = s * b_s[1]
        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)
        index = b_s[1] + 1

        expected = []
        for i in 1:s
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        test[1] # Need to get a value in the first block first
        @fact typeof(test[index]) --> Int
        @fact test[index] --> test.blocks[2][1]
        @fact test[index] --> test[index]
        @fact test[test.range] --> expected[test.range]
    end
    context("getting a value in the second block first") do
        s = rand(2:10)
        b_s = (rand(2:10),)
        d_l = s * b_s[1]
        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)
        index = b_s[1] + 1

        expected = []
        for i in 1:s
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[index]) --> Int
        @fact test[index] --> test.blocks[2][1]
        @fact test[index] --> test[index]
        @fact test[test.range] --> expected[test.range]
    end
    context("getting a value in the second block first while only loading the first block") do
        s = rand(2:10)
        b_s = (rand(2:10),)
        d_l = s * b_s[1]
        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)
        index = b_s[1] + 1

        expected = []
        for i in 1:1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact_throws RingArrayBoundsError test[index]
        @fact test[test.range] --> expected[test.range]

        test_error = 1
        try
            test[index]
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)

        range = tuple(test.range, test.block_size[2:end]...)
        @fact occured.data --> "RingArrayBoundsError: Cannot index $((index,)), outside of range $range".data
    end
    context("getting a value in any block first") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        d_l = s * b_s[1]
        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)
        block_picked = rand(3:s)
        index_in_block = rand(1:b_s[1])
        index = index_in_block + (block_picked - 1) * b_s[1]

        expected = []
        for i in 1:s
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[index]) --> Int
        @fact test[index] --> test.blocks[block_picked][index_in_block]
        @fact test[index] --> test[index]
        @fact test[test.range] --> expected[test.range]
    end
    context("getting value from 2 d array") do
        s = rand(3:10)
        b_s = (rand(1:10),rand(1:10))
        d_l = s * b_s[1]
        block_picked = rand(3:s)
        index_in_block = (rand(1:b_s[1]), rand(1:b_s[2]))
        index = (index_in_block[1] + (block_picked - 1) * b_s[1], index_in_block[2])

        test = RingArray{Int, 2}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:s
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[index...]) --> Int
        @fact test[index...] --> test.blocks[block_picked][index_in_block...]
        @fact test[index...] --> test[index...]
        @fact test[test.range, 1:b_s[2]] --> expected[test.range, 1:b_s[2]]
    end
    context("getting value from 2 d array like a 1 d array") do
        s = rand(3:10)
        b_s = (rand(2:10),rand(2:10))
        d_l = s * b_s[1]
        block_picked = rand(3:s)
        index_in_block = (rand(1:b_s[1]))
        index = (index_in_block[1] + (block_picked - 1) * b_s[1])
        index_overflow = d_l + 1

        test = RingArray{Int, 2}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:s
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact test[index...] --> expected[index...]
        @fact test[index_overflow] --> expected[index_overflow]
        @fact test[index_overflow] --> test[1,2]
        @fact test[index_overflow] --> expected[1,2]
    end
    context("getting value from 2 d array like a 1 d array after overflow") do
        s = rand(3:10)
        b_s = (rand(2:10),rand(2:10))
        block_picked = rand(3:s)
        index_in_block = (rand(1:b_s[1]))
        overflow = s * b_s[1]
        num_overflows = rand(1:10)
        index = index_in_block + (block_picked - 1) * b_s[1] + overflow * num_overflows
        d_l = index + (b_s[1] - index % b_s[1])
        index_overflow = index + d_l

        test = RingArray{Int, 2}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact test[index...] --> expected[index...]
        @fact test[index_overflow] --> expected[index_overflow]
        @fact test[index_overflow] --> test[index,2]
        @fact test[index_overflow] --> expected[index,2]
        @fact_throws RingArrayBoundsError test[d_l + 1]
    end
    context("getting value from N d array like a 1 d array after overflow") do
        s = rand(3:10)
        num_dimensions = rand(3:6)
        b_s = []
        for i in 1:num_dimensions
            push!(b_s, rand(2:10))
        end
        b_s = tuple(b_s...)
        block_picked = rand(3:s)
        index_in_block = (rand(1:b_s[1]))
        overflow = s * b_s[1]
        num_overflows = rand(1:10)
        index = index_in_block + (block_picked - 1) * b_s[1] + overflow * num_overflows
        d_l = index + (b_s[1] - index % b_s[1])
        index_overflow = index + d_l

        test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact test[index...] --> expected[index...]
        @fact test[index_overflow] --> expected[index_overflow]
        @fact test[index_overflow] --> test[index,2]
        @fact test[index_overflow] --> expected[index,2]
        @fact_throws RingArrayBoundsError test[d_l + 1]
    end
    context("getting the last value from N d array like a 1 d array after overflow") do
        s = rand(3:10)
        num_dimensions = rand(3:6)
        b_s = []
        for i in 1:num_dimensions
            push!(b_s, rand(2:10))
        end
        b_s = tuple(b_s...)
        block_picked = rand(3:s)
        index_in_block = (rand(1:b_s[1]))
        overflow = s * b_s[1]
        num_overflows = rand(1:10)
        index = index_in_block + (block_picked - 1) * b_s[1] + overflow * num_overflows
        d_l = index + (b_s[1] - index % b_s[1])
        index_overflow = index + d_l

        test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        last_value_index = test.range.stop
        for size in test.block_size[2:end]
            last_value_index *= size
        end

        @fact test[last_value_index...] --> expected[last_value_index...]
    end
    context("getting value from N d array") do
        s = rand(3:10)
        num_dimensions = rand(3:6)
        b_s = []
        for i in 1:num_dimensions
            push!(b_s, rand(1:10))
        end
        b_s = tuple(b_s...)
        d_l = s * b_s[1]
        block_picked = rand(3:s)
        index_in_block = []
        for i in 1:num_dimensions
            push!(index_in_block, rand(1:b_s[i]))
        end
        index = (index_in_block[1] + (block_picked - 1) * b_s[1], index_in_block[2:end]...)

        test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:s
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        ranges = []
        for i in 2:num_dimensions
            push!(ranges, 1:b_s[i])
        end

        @fact typeof(test[index...]) --> Int
        @fact test[index...] --> test.blocks[block_picked][index_in_block...]
        @fact test[index...] --> test[index...]
        @fact test[test.range, ranges...] --> expected[test.range, ranges...]
    end
end

############################################################################################
# INDEXING AFTER OVERFLOW
############################################################################################

facts("Getting values over the length (overflow) of the RingArray") do
    context("getting the first value after overflowing") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        block_picked = 1
        index_in_block = 1
        overflow = s * b_s[1]
        index = overflow + 1
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[index]) --> Int
        @fact test[index] --> test.blocks[block_picked][index_in_block]
        @fact test[index] --> test[index]
        @fact test[test.range] --> expected[test.range]
    end
    context("getting the first value after overflowing with only before overflow") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        block_picked = 1
        index_in_block = 1
        overflow = s * b_s[1]
        index = overflow + 1
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1]
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact_throws RingArrayBoundsError test[index]
        @fact test[test.range] --> expected[test.range]

        test_error = 1
        try
            test[index]
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)

        range = tuple(test.range, test.block_size[2:end]...)
        @fact occured.data --> "RingArrayBoundsError: Cannot index $((index,)), outside of range $range".data
    end
    context("getting any value after overflowing") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        block_picked = rand(3:s)
        index_in_block = rand(1:b_s[1])
        overflow = s * b_s[1]
        index = index_in_block + (block_picked - 1) * b_s[1] + overflow
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[index]) --> Int
        @fact test[index] --> test.blocks[block_picked][index_in_block]
        @fact test[index] --> test[index]
        @fact test[test.range] --> expected[test.range]
    end
    context("getting any value after any number of overflows") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        block_picked = rand(3:s)
        index_in_block = rand(1:b_s[1])
        overflow = s * b_s[1]
        num_overflows = rand(1:10)
        index = index_in_block + (block_picked - 1) * b_s[1] + overflow * num_overflows
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[index]) --> Int
        @fact test[index] --> test.blocks[block_picked][index_in_block]
        @fact test[index] --> test[index]
        @fact test[test.range] --> expected[test.range]
    end
    context("getting value from 2 d array after overflowing") do
        s = rand(3:10)
        b_s = (rand(1:10),rand(1:10))
        block_picked = rand(3:s)
        index_in_block = (rand(1:b_s[1]), rand(1:b_s[2]))
        overflow = s * b_s[1]
        index = (index_in_block[1] + (block_picked - 1) * b_s[1] + overflow, index_in_block[2])
        d_l = index[1] + (b_s[1] - index[1] % b_s[1])

        test = RingArray{Int, 2}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index[1] ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[index...]) --> Int
        @fact test[index...] --> test.blocks[block_picked][index_in_block...]
        @fact test[index...] --> test[index...]
        @fact test[test.range, 1:b_s[2]] --> expected[test.range, 1:b_s[2]]
    end
    context("getting value from N d array after any number of overflows") do
        s = rand(3:10)
        num_dimensions = rand(3:6)
        b_s = []
        for i in 1:num_dimensions
            push!(b_s, rand(1:10))
        end
        b_s = tuple(b_s...)
        block_picked = rand(3:s)
        index_in_block = []
        for i in 1:num_dimensions
            push!(index_in_block, rand(1:b_s[i]))
        end
        overflow = s * b_s[1]
        num_overflows = rand(1:10)
        index = (index_in_block[1] + (block_picked - 1) * b_s[1] + overflow * num_overflows, index_in_block[2:end]...)
        d_l = index[1] + (b_s[1] - index[1] % b_s[1])

        test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index[1] ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        ranges = []
        for i in 2:num_dimensions
            push!(ranges, 1:b_s[i])
        end

        @fact typeof(test[index...]) --> Int
        @fact test[index...] --> test.blocks[block_picked][index_in_block...]
        @fact test[index...] --> test[index...]
        @fact test[test.range, ranges...] --> expected[test.range, ranges...]
    end
end

############################################################################################
# DATA VIEWS
############################################################################################

facts("Getting data views") do
    context("looking at a small portion of the first block") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_s = (b_l,)
        block_picked = 1
        start = rand(1:b_l)
        last = rand(start:b_l)
        range = start:last
        d_l = range.stop + (b_l - range.stop % b_l)

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:range.stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[range]) --> VirtualArrays.VirtualArray{Int,1}
        @fact test[range] --> test.blocks[block_picked][range]
        @fact test[range] --> test[range]
        @fact test[test.range] --> expected[test.range]
    end
    context("trying to change a value in a view") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_s = (b_l,)
        block_picked = 1
        start = rand(1:b_l)
        last = rand(start:b_l)
        range = start:last
        d_l = range.stop + (b_l - range.stop % b_l)

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:range.stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact_throws ErrorException test[range][1] = 3
    end
    context("looking at a small portion of the first block without loading") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_s = (b_l,)
        block_picked = 1
        start = rand(1:b_l)
        last = rand(start:b_l)
        ring_range = start:last
        d_l = ring_range.stop + (b_l - ring_range.stop % b_l)

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        @fact_throws RingArrayBoundsError test[ring_range]

        test_error = 1
        try
            test[ring_range]
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)

        range = tuple(test.range, test.block_size[2:end]...)
        @fact occured.data --> "RingArrayBoundsError: Cannot index $((ring_range,)), outside of range $range".data
    end
    context("looking at a the whole portion of the first block") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_s = (b_l,)
        block_picked = 1
        start = 1
        last = b_l
        range = start:last
        d_l = range.stop + (b_l - range.stop % b_l)

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:range.stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[range]) --> VirtualArrays.VirtualArray{Int,1}
        @fact test[range] --> test.blocks[block_picked][range]
        @fact test[range] --> test[range]
        @fact test[test.range] --> expected[test.range]
    end
    context("looking at a small portion of any block") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_s = (b_l,)
        block_picked = rand(2:s)
        start = rand(1:b_l)
        last = rand(start:b_l)
        range = start:last
        ring_range = range + (block_picked - 1) * b_l
        d_l = ring_range.stop + (b_l - ring_range.stop % b_l)

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:ring_range.stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[ring_range]) --> VirtualArrays.VirtualArray{Int,1}
        @fact test[ring_range] --> test.blocks[block_picked][range]
        @fact test[ring_range] --> test[ring_range]
        @fact test[test.range] --> expected[test.range]
    end
    context("looking at a small portion of two blocks") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_s = (b_l,)
        block_picked = rand(2:s-1)
        start = rand(1:b_l)
        last = rand(start:b_l) + b_l
        range = start:last
        ring_range = range + (block_picked - 1) * b_l
        d_l = ring_range.stop + (b_l - ring_range.stop % b_l)

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:ring_range.stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[ring_range]) --> VirtualArrays.VirtualArray{Int,1}
        @fact test[ring_range] --> [test.blocks[block_picked][range.start:end]...,
                                    test.blocks[block_picked + 1][1:range.stop - b_l]...]
        @fact test[ring_range] --> test[ring_range]
        @fact test[test.range] --> expected[test.range]
    end
    context("looking at a portion of two blocks at overflow") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_s = (b_l,)
        block_picked = rand(2:s-1)
        start = rand(1:b_l)
        last = rand(start:b_l) + b_l
        range = start:last
        overflow = s * b_s[1]
        num_overflows = rand(1:10)
        ring_range = range + (block_picked - 1) * b_l
        d_l = ring_range.stop + (b_l - ring_range.stop % b_l)

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:ring_range.stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[ring_range]) --> VirtualArrays.VirtualArray{Int,1}
        @fact test[ring_range] --> [test.blocks[block_picked][range.start:end]...,
                                    test.blocks[block_picked + 1][1:range.stop - b_l]...]
        @fact test[ring_range] --> test[ring_range]
        @fact test[test.range] --> expected[test.range]
    end
    context("looking at a portion of two blocks at overflow of a 2d ring array") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_w = rand(1:10)
        b_s = (b_l,b_w)
        block_picked = rand(2:s-1)
        start = rand(1:b_l)
        last = rand(start:b_l) + b_l
        range = (start:last,1:b_w)
        overflow = s * b_s[1]
        num_overflows = rand(1:10)
        ring_range = (range[1] + (block_picked - 1) * b_l, 1:b_w)
        d_l = ring_range[1].stop + (b_l - ring_range[1].stop % b_l)

        test = RingArray{Int, 2}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:ring_range[1].stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[ring_range...]) --> VirtualArrays.VirtualArray{Int,2}
        @fact test[ring_range...] --> [test.blocks[block_picked][range[1].start:end, range[2]];
                                    test.blocks[block_picked + 1][1:range[1].stop - b_l, range[2]]]
        @fact test[ring_range...] --> test[ring_range...]
        @fact test[test.range, 1:b_w] --> expected[test.range, 1:b_w]
    end
    context("looking at a portion from one block from N d array after any number of overflows") do
        s = rand(3:10)
        num_dimensions = rand(3:6)
        b_s = []
        for i in 1:num_dimensions
            push!(b_s, rand(1:10))
        end
        b_s = tuple(b_s...)
        block_picked = rand(3:s)
        index_in_block = []
        for i in 1:num_dimensions
            push!(index_in_block, rand(1:b_s[i]))
        end
        overflow = s * b_s[1]
        num_overflows = rand(1:10)
        ranges = []
        for i in 1:num_dimensions
            start = rand(1:b_s[i])
            last = rand(start:b_s[i])
            push!(ranges, start:last)
        end
        ring_range = (ranges[1] + (block_picked - 1) * b_s[1] + overflow * num_overflows, ranges[2:end]...)
        d_l = ring_range[1].stop + (b_s[1] - ring_range[1].stop % b_s[1])

        test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:ring_range[1].stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact typeof(test[ring_range...]) --> VirtualArrays.VirtualArray{Int, num_dimensions}
        @fact test[ring_range...] --> test.blocks[block_picked][ranges...]
        @fact test[ring_range...] --> test[ring_range...]

        ranges = []
        for i in 2:num_dimensions
            push!(ranges, 1:b_s[i])
        end

        @fact test[test.range, ranges...] --> expected[test.range, ranges...]
    end
end

############################################################################################
# CHECKBOUNDS
############################################################################################

facts("Using checkbounds") do
    context("checking bounds before overflow without overflowing") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        block_picked = rand(3:s)
        index_in_block = rand(1:b_s[1])
        index = index_in_block + (block_picked - 1) * b_s[1]
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact checkbounds(test, index) --> true
        @fact checkbounds(test) --> true
        @fact test[test.range] --> expected[test.range]
    end
    context("checking bounds after overflow without overflowing") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        block_picked = rand(3:s)
        index_in_block = rand(1:b_s[1])
        overflow = s * b_s[1]
        index = index_in_block + (block_picked - 1) * b_s[1] + overflow
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact checkbounds(test, index) --> true
        @fact test[test.range] --> expected[test.range]
    end
    context("checking bounds after overflow with overflowing") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        block_picked = rand(3:s)
        index_in_block = rand(1:b_s[1])
        overflow = s * b_s[1]
        index = index_in_block + (block_picked - 1) * b_s[1] + overflow
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact checkbounds(test, index) --> true
        @fact test[test.range] --> expected[test.range]
    end
    context("checking bounds before overflow with overflowing") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        block_picked = rand(3:s)
        index_in_block = rand(1:b_s[1])
        overflow = s * b_s[1]

        index = index_in_block + (block_picked - 1) * b_s[1] + overflow
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact_throws RingArrayBoundsError checkbounds(test, 1)
        @fact test[test.range] --> expected[test.range]

        test_error = 1
        try
            checkbounds(test, 1)
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)

        range = tuple(test.range, test.block_size[2:end]...)
        @fact occured.data --> "RingArrayBoundsError: Cannot index $((1,)), outside of range $range".data
    end
    context("checking unit range bounds before overflow without overflowing") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_s = (b_l,)
        start = rand(1:b_l)
        last = rand(start:b_l) + b_l
        range = start:last
        d_l = range.stop + (b_s[1] - range.stop % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:range.stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact checkbounds(test, range) --> true
        @fact test[test.range] --> expected[test.range]
    end
    context("checking unit range bounds after overflow without overflowing") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_s = (b_l,)
        block_picked = rand(2:s-1)
        start = rand(1:b_l)
        last = rand(start:b_l) + b_l
        range = start:last
        overflow = s * b_s[1]
        ring_range = range + (block_picked - 1) * b_l + overflow
        d_l = ring_range.stop + (b_s[1] - ring_range.stop % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:ring_range.stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact checkbounds(test, ring_range) --> true
        @fact test[test.range] --> expected[test.range]
    end
    context("checking unit range bounds after overflow with overflowing") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_s = (b_l,)
        block_picked = rand(2:s-1)
        start = rand(1:b_l)
        last = rand(start:b_l) + b_l
        range = start:last
        overflow = s * b_s[1]
        num_overflows = rand(1:10)
        ring_range = range + (block_picked - 1) * b_l + overflow
        d_l = ring_range.stop + (b_s[1] - ring_range.stop % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:ring_range.stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact checkbounds(test, ring_range) --> true
        @fact test[test.range] --> expected[test.range]
    end
    context("checking unit range bounds before overflow with overflowing") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_s = (b_l,)
        block_picked = rand(2:s-1)
        start = rand(1:b_l)
        last = rand(start:b_l) + b_l
        range = start:last
        overflow = s * b_s[1]
        num_overflows = rand(1:10)
        ring_range = range + (block_picked - 1) * b_l + overflow
        d_l = ring_range.stop + (b_s[1] - ring_range.stop % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:ring_range.stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact_throws RingArrayBoundsError checkbounds(test, 1:overflow)
        @fact test[test.range] --> expected[test.range]

        test_error = 1
        try
            checkbounds(test, 1:overflow)
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)

        range = tuple(test.range, test.block_size[2:end]...)
        @fact occured.data --> "RingArrayBoundsError: Cannot index $((1:overflow,)), outside of range $range".data
    end
    context("checking unit range bounds that exceed the length of the ring") do
        s = rand(3:10)
        b_l = rand(1:10)
        b_s = (b_l,)
        overflow = s * b_s[1]
        d_l = b_l * (s + 1)

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:s
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end

        @fact_throws RingArrayBoundsError checkbounds(test, 1:overflow + 1)

        push!(expected, rand(Int, test.block_size))
        load_block(test, expected[end])
        expected = cat(1, expected...)

        @fact_throws RingArrayBoundsError checkbounds(test, 1:overflow + 1)
        @fact test[test.range] --> expected[test.range]

        test_error = 1
        try
            checkbounds(test, 1:overflow + 1)
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)

        range = tuple(test.range, test.block_size[2:end]...)
        @fact occured.data --> "RingArrayBoundsError: Cannot index $((1:overflow + 1,)), outside of range $range".data
    end
    context("checking indexing of N d RingArray") do
        s = rand(3:10)
        num_dimensions = rand(3:6)
        b_s = []
        for i in 1:num_dimensions
            push!(b_s, rand(1:10))
        end
        b_s = tuple(b_s...)
        block_picked = rand(3:s)
        index_in_block = []
        for i in 1:num_dimensions
            push!(index_in_block, rand(1:b_s[i]))
        end
        index = (index_in_block[1] + (block_picked - 1) * b_s[1], index_in_block[2:end]...)
        d_l = b_s[1] * s

        test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:s
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        ranges = []
        for i in 2:num_dimensions
            push!(ranges, 1:b_s[i])
        end

        @fact checkbounds(test, index...) --> true
        @fact test[test.range, ranges...] --> expected[test.range, ranges...]
    end
    context("checking out of bounds indexing of N d RingArray") do
        s = rand(3:10)
        num_dimensions = rand(3:6)
        b_s = []
        for i in 1:num_dimensions
            push!(b_s, rand(1:10))
        end
        b_s = tuple(b_s...)
        block_picked = rand(3:s)
        index_in_block = []
        for i in 1:num_dimensions
            push!(index_in_block, rand(1:b_s[i]))
        end
        overflow = s * b_s[1]
        index = (index_in_block[1] + (block_picked - 1) * b_s[1] + overflow, index_in_block[2:end]...)
        d_l = index[1] + (b_s[1] - index[1] % b_s[1])

        test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:s
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        ranges = []
        for i in 2:num_dimensions
            push!(ranges, 1:b_s[i])
        end

        @fact_throws RingArrayBoundsError checkbounds(test, index...)
        @fact test[test.range, ranges...] --> expected[test.range, ranges...]

        test_error = 1
        try
            checkbounds(test, index...)
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)

        range = tuple(test.range, test.block_size[2:end]...)
        @fact occured.data --> "RingArrayBoundsError: Cannot index $((index...,)), outside of range $range".data
    end
    context("checking out of bounds indexing not on the first dimension of N d RingArray") do
        s = rand(3:10)
        num_dimensions = rand(3:6)
        b_s = []
        for i in 1:num_dimensions
            push!(b_s, rand(1:10))
        end
        b_s = tuple(b_s...)
        block_picked = rand(3:s)
        index_in_block = []
        for i in 1:num_dimensions
            push!(index_in_block, rand(1:b_s[i]))
        end
        overflow = s * b_s[1]
        bad_index_index = rand(2:num_dimensions)
        index_in_block[bad_index_index] += b_s[bad_index_index]
        index = (index_in_block[1] + (block_picked - 1) * b_s[1], index_in_block[2:end]...)
        d_l = b_s[1] * s

        test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:s
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        ranges = []
        for i in 2:num_dimensions
            push!(ranges, 1:b_s[i])
        end

        @fact_throws RingArrayBoundsError checkbounds(test, index...)
        @fact test[test.range, ranges...] --> expected[test.range, ranges...]

        test_error = 1
        try
            checkbounds(test, index...)
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)

        range = tuple(test.range, test.block_size[2:end]...)
        @fact occured.data --> "RingArrayBoundsError: Cannot index $((index...,)), outside of range $range".data
    end
    context("checking range indexing of N d RingArray") do
        s = rand(3:10)
        num_dimensions = rand(3:6)
        b_s = []
        for i in 1:num_dimensions
            push!(b_s, rand(1:10))
        end
        b_s = tuple(b_s...)
        block_picked = rand(3:s)
        overflow = s * b_s[1]
        num_overflows = rand(1:10)
        ranges = []
        for i in 1:num_dimensions
            start = rand(1:b_s[i])
            last = rand(start:b_s[i])
            push!(ranges, start:last)
        end
        ring_range = (ranges[1] + (block_picked - 1) * b_s[1] + overflow * num_overflows, ranges[2:end]...)
        d_l = ring_range[1].stop + (b_s[1] - ring_range[1].stop % b_s[1])

        test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:ring_range[1].stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        ranges = []
        for i in 2:num_dimensions
            push!(ranges, 1:b_s[i])
        end

        @fact checkbounds(test, ring_range...) --> true
        @fact test[test.range, ranges...] --> expected[test.range, ranges...]
    end
    context("checking out of bounds range indexing of N d RingArray") do
        s = rand(3:10)
        num_dimensions = rand(3:6)
        b_s = []
        for i in 1:num_dimensions
            push!(b_s, rand(1:10))
        end
        b_s = tuple(b_s...)
        block_picked = rand(3:s)
        overflow = s * b_s[1]
        num_overflows = rand(1:10)
        ranges = []
        for i in 1:num_dimensions
            start = rand(1:b_s[i])
            last = rand(start:b_s[i])
            push!(ranges, start:last)
        end
        ring_range = collect((ranges[1] + (block_picked - 1) * b_s[1] + overflow * num_overflows, ranges[2:end]...))
        d_l = ring_range[1].stop + (b_s[1] - ring_range[1].stop % b_s[1])

        bad_index = rand(2:num_dimensions)
        ring_range[bad_index] += b_s[bad_index] * rand(-1:2:1)
        ring_range = tuple(ring_range...)

        test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:ring_range[1].stop ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        ranges = []
        for i in 2:num_dimensions
            push!(ranges, 1:b_s[i])
        end

        @fact_throws RingArrayBoundsError checkbounds(test, ring_range...)
        @fact test[test.range, ranges...] --> expected[test.range, ranges...]

        test_error = 1
        try
            checkbounds(test, ring_range...)
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)

        range = tuple(test.range, test.block_size[2:end]...)
        @fact occured.data --> "RingArrayBoundsError: Cannot index $((ring_range...,)), outside of range $range".data
    end
end

############################################################################################
# DISPLAY
############################################################################################

facts("Using display") do
    context("trying display on a typical RingArray") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        block_picked = rand(3:s)
        index_in_block = rand(1:b_s[1])
        index = index_in_block + (block_picked - 1) * b_s[1]
        d_l = b_s[1] * s

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        @fact display(test) --> nothing
    end
end

############################################################################################
# VIEWS
############################################################################################

facts("Using views") do
    context("having the RingArray overflow when no views in use") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        block_picked = rand(3:s)
        index_in_block = rand(1:b_s[1])
        overflow = s * b_s[1]
        index = index_in_block + (block_picked - 1) * b_s[1] + overflow
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        @fact test[index] --> test.blocks[block_picked][index_in_block]
        @fact test[test.range] --> expected[test.range]
    end
    context("having the RingArray overflow when first block is in use") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        block_picked = rand(3:s)
        index_in_block = rand(1:b_s[1])
        overflow = s * b_s[1]
        index = index_in_block + (block_picked - 1) * b_s[1] + overflow
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        push!(expected, rand(Int, test.block_size))
        load_block(test, expected[end])

        view = test[1:1]

        for i in 2:overflow ÷ b_s[1]
            push!(expected, rand(Int, test.block_size))
            @fact load_block(test, expected[end]) --> nothing
        end
        expected = cat(1, expected...)

        @fact_throws OverwriteError load_block(test, rand(Int, test.block_size))

        test_error = 1
        try
            load_block(test, rand(Int, test.block_size))
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)

        @fact occured.data --> "OverwriteError: Cannot overwrite block $(1) since it has $(1) views".data
        @fact test.num_users[1] --> 1
        @fact view --> test[1:1]
        @fact test[test.range] --> expected[test.range]
        @fact_throws OverwriteError load_block(test, rand(Int, test.block_size))
        @pending test.num_users[1] --> 1 # FactCheck saves results, so there are some views stored in FactCheck

    end
    context("having the RingArray overflow to the first block when second block is in use") do
        s = rand(3:10)
        b_l = rand(2:10)
        b_s = (b_l,)
        overflow = s * b_l
        index = overflow + 1
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        for i in 1:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            load_block(test, expected[end])
        end
        expected = cat(1, expected...)

        view = test[b_l + 1:b_l + 1]

        @fact test[index] --> test.blocks[1][1]
        @fact test[test.range] --> expected[test.range]
    end
    context("having a view that goes out of scope and run gc") do
        s = rand(3:10)
        b_l = rand(2:10)
        b_s = (b_l,)
        overflow = s * b_l
        index = overflow + 1
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        push!(expected, rand(Int, test.block_size))
        load_block(test, expected[end])

        let
            local view = test[1:1]
        end

        gc()

        for i in 2:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            @fact load_block(test, expected[end]) --> nothing
        end
        expected = cat(1, expected...)

        @fact test[index] --> test.blocks[1][1]
        @fact test[test.range] --> expected[test.range]
    end
    context("having a view that goes out of scope don't run gc") do
        s = rand(3:10)
        b_l = rand(2:10)
        b_s = (b_l,)
        overflow = s * b_l
        index = overflow + 1
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        push!(expected, rand(Int, test.block_size))
        load_block(test, expected[end])

        let
            local view = test[1:1]
        end

        for i in 2:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            @fact load_block(test, expected[end]) --> nothing
        end
        expected = cat(1, expected...)

        @fact test[index] --> test.blocks[1][1]
        @fact test[test.range] --> expected[test.range]
    end
    context("having many views that goes out of scope don't run gc") do
        s = rand(3:10)
        b_l = rand(2:10)
        b_s = (b_l,)
        overflow = s * b_l
        index = overflow + 1
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        push!(expected, rand(Int, test.block_size))
        load_block(test, expected[end])

        let
            for i in 1:rand(100:200)
                local view = test[1:1]
            end
        end

        for i in 2:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            @fact load_block(test, expected[end]) --> nothing
        end
        expected = cat(1, expected...)

        @fact test[index] --> test.blocks[1][1]
        @fact test[test.range] --> expected[test.range]
    end
    context("having a view that stays and many views that goes out of scope don't run gc") do
        s = rand(3:10)
        b_l = rand(2:10)
        b_s = (b_l,)
        overflow = s * b_l
        index = overflow + 1
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        push!(expected, rand(Int, test.block_size))
        load_block(test, expected[end])

        view = test[1:1]
        let
            for i in 1:rand(100:200)
                local view = test[1:1]
            end
        end

        for i in 2:index ÷ b_s[1]
            push!(expected, rand(Int, test.block_size))
            @fact load_block(test, expected[end]) --> nothing
        end
        expected = cat(1, expected...)

        @fact_throws OverwriteError load_block(test, rand(Int, test.block_size))

        test_error = 1
        try
            load_block(test, rand(Int, test.block_size))
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)

        @fact occured.data --> "OverwriteError: Cannot overwrite block $(1) since it has $(1) views".data
        @fact test.num_users[1] --> 1
        @fact view --> test[1:1]
        @fact test[test.range] --> expected[test.range]
        @fact_throws OverwriteError load_block(test, rand(Int, test.block_size))
        @pending test.num_users[1] --> 1 # FactCheck saves results, so there are some views stored in FactCheck

    end
    context("having the RingArray overflow to the first block when second block is in use") do
        s = rand(3:10)
        b_l = rand(2:10)
        b_s = (b_l,)
        overflow = s * b_l
        index = overflow + 1
        d_l = index + (b_s[1] - index % b_s[1])

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        expected = []
        push!(expected, rand(Int, test.block_size))
        load_block(test, expected[end])
        push!(expected, rand(Int, test.block_size))
        load_block(test, expected[end])

        view = test[b_l + 1:b_l + 1]

        for i in 3:index ÷ b_s[1] + 1
            push!(expected, rand(Int, test.block_size))
            @fact load_block(test, expected[end]) --> nothing
        end
        expected = cat(1, expected...)

        @fact test[index] --> test.blocks[1][1]
        @fact test[test.range] --> expected[test.range]
    end
end

############################################################################################
# LOADING BLOCKS
############################################################################################

facts("Loading blocks in RingArray") do
    context("loading the first block") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        d_l = b_s[1] * s

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        @fact load_block(test, rand(Int, test.block_size)) --> nothing
    end
    context("loading lots of blocks") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        d_l = b_s[1] * 100

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        for i in 1:100
            @fact load_block(test, rand(Int, test.block_size)) --> nothing
        end
    end
    context("loading a block of different size") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        d_l = b_s[1] * s

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        wrong_size = (test.block_size[1] + 1,)

        @fact_throws DimensionMismatch load_block(test, rand(Int, wrong_size))

        test_error = 1
        try
            load_block(test, rand(Int, wrong_size))
        catch e
            test_error = e
        end

        @fact test_error.msg --> "block size $(wrong_size) does not match what RingArray expects $(b_s)"
    end
    context("loading a block of different dimensions") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        d_l = b_s[1] * s

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        wrong_size = (test.block_size[1], test.block_size[1])

        @fact_throws MethodError load_block(test, rand(Int, wrong_size))
    end
    context("loading too many blocks") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        num_blocks = rand(s:100)
        d_l = b_s[1] * num_blocks

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        for i in 1:num_blocks
            @fact load_block(test, rand(Int, test.block_size)) --> nothing
        end

        @fact_throws RingArrayFullError load_block(test, rand(Int, test.block_size))

        test_error = 1
        try
            load_block(test, rand(Int, test.block_size))
        catch e
            test_error = e
        end

        occured = IOBuffer()
        showerror(occured, test_error)
        expected = "RingArrayFullError: Cannot load another block, max data length is $(d_l)"

        @fact occured.data --> expected.data
    end
end

############################################################################################
# ERROR
############################################################################################

facts("Testing custom errors") do
    context("testing output of OverwriteError") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        d_l = b_s[1] * s

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        err = OverwriteError(test)

        occured = IOBuffer()
        showerror(occured, err)
        expected = "OverwriteError: Cannot overwrite block $(1) since it has $(0) views"

        @fact occured.data --> expected.data
    end
    context("testing output of RingArrayFullError") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        d_l = b_s[1] * s

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        err = RingArrayFullError(test)

        occured = IOBuffer()
        showerror(occured, err)
        expected = "RingArrayFullError: Cannot load another block, max data length is $(test.data_length)"

        @fact occured.data --> expected.data
    end
    context("testing output of RingArrayBoundsError") do
        s = rand(3:10)
        b_s = (rand(2:10),)
        i = rand(1:1000)
        d_l = b_s[1] * s

        test = RingArray{Int, 1}(max_blocks=s, block_size=b_s, data_length=d_l)

        err = RingArrayBoundsError(test, i)

        occured = IOBuffer()
        showerror(occured, err)
        range = tuple(test.range, test.block_size[2:end]...)
        expected = "RingArrayBoundsError: Cannot index $(i), outside of range $(range)"

        @fact occured.data --> expected.data
    end
end

# Trying out testset
@testset "RingArray Tests" begin
    @testset "View" begin
        @testset "getting view from 2 d array like a 1 d array" begin
            s = rand(3:10)
            b_s = (rand(2:10),rand(2:10))
            d_l = s * b_s[1]
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            index = (index_in_block[1] + (block_picked - 1) * b_s[1])
            index_overflow = d_l + 1

            test = RingArray{Int, 2}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:s
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            range = index:index

            @test test[range] == expected[range]
            @test test[test.range] == expected[test.range]
        end
        @testset "getting view from 2 d array like a 1 d array after overflow" begin
            s = rand(3:10)
            b_s = (rand(2:10),rand(2:10))
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            overflow = s * b_s[1]
            num_overflows = rand(1:10)
            index = index_in_block + (block_picked - 1) * b_s[1] + overflow * num_overflows
            d_l = index + (b_s[1] - index % b_s[1])
            index_overflow = index + d_l

            test = RingArray{Int, 2}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:index ÷ b_s[1] + 1
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            range = index:index

            @test test[range] == expected[range]
            @test test[test.range] == expected[test.range]
        end
        @testset "getting view from N d array like a 1 d array after overflow" begin
            s = rand(3:10)
            num_dimensions = rand(3:6)
            b_s = []
            for i in 1:num_dimensions
                push!(b_s, rand(1:10))
            end
            b_s = tuple(b_s...)
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            overflow = s * b_s[1]
            num_overflows = rand(1:10)
            index = index_in_block + (block_picked - 1) * b_s[1] + overflow * num_overflows
            d_l = index + (b_s[1] - index % b_s[1])
            index_overflow = index + d_l

            test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:index ÷ b_s[1] + 1
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            dim = rand(2:num_dimensions)
            last_value_index = test.range.stop
            for size in test.block_size[2:dim]
                last_value_index *= size
            end
            range = last_value_index - overflow + 1 : last_value_index

            @test test[range] == expected[range]
            @test test[test.range] == expected[test.range]
        end
        @testset "getting the last view from N d array like a 1 d array after overflow" begin
            s = rand(3:10)
            num_dimensions = rand(3:6)
            b_s = []
            for i in 1:num_dimensions
                push!(b_s, rand(1:10))
            end
            b_s = tuple(b_s...)
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            overflow = s * b_s[1]
            num_overflows = rand(1:10)
            index = index_in_block + (block_picked - 1) * b_s[1] + overflow * num_overflows
            d_l = index + (b_s[1] - index % b_s[1])
            index_overflow = index + d_l

            test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:index ÷ b_s[1] + 1
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            last_value_index = test.range.stop
            for size in test.block_size[2:end]
                last_value_index *= size
            end
            range = last_value_index - overflow + 1 : last_value_index

            @test test[range] == expected[range]
        end
        @testset "testing a bad range" begin
            s = rand(3:10)
            num_dimensions = rand(3:6)
            b_s = []
            for i in 1:num_dimensions
                push!(b_s, rand(1:10))
            end
            b_s = tuple(b_s...)
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            overflow = s * b_s[1]
            num_overflows = rand(1:10)
            index = index_in_block + (block_picked - 1) * b_s[1] + overflow * num_overflows
            d_l = index + (b_s[1] - index % b_s[1])
            index_overflow = index + d_l

            test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:index ÷ b_s[1] + 1
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            last_value_index = test.range.stop
            for size in test.block_size[2:end]
                last_value_index *= size
            end
            range = last_value_index - (overflow * 2) + 1 : last_value_index

            @test typeof(expected[range]) == Array{Int,1}
            @test_throws RingArrayBoundsError test[range]

            test_error = 1
            try
                test[range]
            catch e
                test_error = e
            end

            occured = IOBuffer()
            showerror(occured, test_error)

            test_range = tuple(test.range, test.block_size[2:end]...)
            @test occured.data == "RingArrayBoundsError: Cannot index $((range,)), outside of range $test_range".data
        end
        @testset "testing a bad range that ends are both in valid ranges" begin
            s = rand(3:10)
            num_dimensions = rand(3:6)
            b_s = []
            for i in 1:num_dimensions
                push!(b_s, rand(1:10))
            end
            b_s = tuple(b_s...)
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            overflow = s * b_s[1]
            num_overflows = rand(1:10)
            index = index_in_block + (block_picked - 1) * b_s[1] + overflow * num_overflows
            d_l = index + (b_s[1] - index % b_s[1])
            index_overflow = index + d_l

            test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:index ÷ b_s[1] + 1
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            last_value_index = test.range.stop
            for size in test.block_size[2:end]
                last_value_index *= size
            end
            start_value_index = (RingArrays.divide(last_value_index, test.data_length) - 1) * test.data_length - b_s[1]
            range = start_value_index:last_value_index

            @test typeof(expected[range]) == Array{Int,1}
            @test_throws RingArrayBoundsError test[range]

            test_error = 1
            try
                test[range]
            catch e
                test_error = e
            end

            occured = IOBuffer()
            showerror(occured, test_error)

            test_range = tuple(test.range, test.block_size[2:end]...)
            @test occured.data == "RingArrayBoundsError: Cannot index $((range,)), outside of range $test_range".data
        end
        @testset "testing a bad range that is within the length of the RingArray but not valid" begin
            s = rand(3:10)
            num_dimensions = rand(3:6)
            b_s = []
            for i in 1:num_dimensions
                push!(b_s, rand(1:10))
            end
            b_s = tuple(b_s...)
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            overflow = s * b_s[1]
            num_overflows = rand(1:10)
            index = index_in_block + (block_picked - 1) * b_s[1] + overflow * num_overflows
            d_l = index + (b_s[1] - index % b_s[1])
            index_overflow = index + d_l

            test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:index ÷ b_s[1] + 1
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            range = test.range - 1

            @test typeof(expected[range]) == Array{Int,1}
            @test_throws RingArrayBoundsError test[range]

            test_error = 1
            try
                test[range]
            catch e
                test_error = e
            end

            occured = IOBuffer()
            showerror(occured, test_error)

            test_range = tuple(test.range, test.block_size[2:end]...)
            @test occured.data == "RingArrayBoundsError: Cannot index $((range,)), outside of range $test_range".data
        end
        @testset "testing a bad range that is valid but outside the length of the RingArray" begin
            s = rand(3:10)
            num_dimensions = rand(3:6)
            b_s = []
            for i in 1:num_dimensions
                push!(b_s, rand(1:10))
            end
            b_s = tuple(b_s...)
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            overflow = s * b_s[1]
            num_overflows = rand(1:10)
            index = index_in_block + (block_picked - 1) * b_s[1] + overflow * num_overflows
            d_l = index + (b_s[1] - index % b_s[1])
            index_overflow = index + d_l

            test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:index ÷ b_s[1] + 1
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            last_value_index = test.range.stop
            for size in test.block_size[2:end]
                last_value_index *= size
            end
            range = last_value_index - overflow + 1 : last_value_index
            range += test.data_length * rand(1:20)

            @test_throws BoundsError expected[range]
            @test_throws RingArrayBoundsError test[range]

            test_error = 1
            try
                test[range]
            catch e
                test_error = e
            end

            occured = IOBuffer()
            showerror(occured, test_error)

            test_range = tuple(test.range, test.block_size[2:end]...)
            @test occured.data == "RingArrayBoundsError: Cannot index $((range,)), outside of range $test_range".data
        end
        @testset "getting view from N d array like a M d array (M < N) after overflow" begin
            s = rand(3:10)
            num_dimensions = rand(3:6)
            b_s = []
            for i in 1:num_dimensions
                push!(b_s, rand(1:10))
            end
            b_s = tuple(b_s...)
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            overflow = s * b_s[1]
            num_overflows = rand(1:10)
            num_indexes = rand(2:num_dimensions - 1) # M < N

            ranges = []
            for i in 1:num_indexes
                start = rand(1:b_s[i])
                last = rand(start:b_s[i])
                push!(ranges, start:last)
            end
            ring_range = (ranges[1] + (block_picked - 1) * b_s[1] + overflow * num_overflows, ranges[2:end]...)
            d_l = ring_range[1].stop + (b_s[1] - ring_range[1].stop % b_s[1])

            test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:ring_range[1].stop ÷ b_s[1] + 1
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            dim = rand(2:num_dimensions)
            last_value_index = test.range.stop
            for size in test.block_size[2:dim]
                last_value_index *= size
            end
            range = last_value_index - overflow + 1 : last_value_index

            @test test[ring_range...] == expected[ring_range...]
            @test test[test.range] == expected[test.range]
        end
        @testset "getting view from N d array like a M d array (M < N) after overflow where the Mth index is larger" begin
            s = rand(3:10)
            num_dimensions = rand(3:6)
            b_s = []
            for i in 1:num_dimensions
                push!(b_s, rand(1:10))
            end
            b_s = tuple(b_s...)
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            overflow = s * b_s[1]
            num_overflows = rand(1:10)
            num_indexes = rand(2:num_dimensions - 1) # M < N

            ranges = []
            for i in 1:num_indexes
                start = rand(1:b_s[i])
                last = rand(start:b_s[i])
                push!(ranges, start:last)
            end

            increase_m_range = 1
            for size in b_s[num_indexes:num_dimensions]
                increase_m_range *= size
            end

            ring_range = (ranges[1] + (block_picked - 1) * b_s[1] + overflow * num_overflows,
                ranges[2:end-1]..., ranges[end].start : increase_m_range)
            d_l = ring_range[1].stop + (b_s[1] - ring_range[1].stop % b_s[1])

            test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:ring_range[1].stop ÷ b_s[1] + 1
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            dim = rand(2:num_dimensions)
            last_value_index = test.range.stop
            for size in test.block_size[2:dim]
                last_value_index *= size
            end
            range = last_value_index - overflow + 1 : last_value_index

            @test test[ring_range...] == expected[ring_range...]
            @test test[test.range] == expected[test.range]
        end
        @testset "testing a bad range in the first range when indexing into an N d array with an M d index" begin
            s = rand(3:10)
            num_dimensions = rand(3:6)
            b_s = []
            for i in 1:num_dimensions
                push!(b_s, rand(1:10))
            end
            b_s = tuple(b_s...)
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            overflow = s * b_s[1]
            num_overflows = rand(1:10)
            num_indexes = rand(2:num_dimensions - 1) # M < N

            ranges = []
            for i in 1:num_indexes
                start = rand(1:b_s[i])
                last = rand(start:b_s[i])
                push!(ranges, start:last)
            end

            increase_m_range = 1
            for size in b_s[num_indexes:num_dimensions]
                increase_m_range *= size
            end

            ring_range = (ranges[1] + (block_picked - 1) * b_s[1] + overflow * num_overflows,
                ranges[2:end-1]..., ranges[end].start : increase_m_range)
            d_l = ring_range[1].stop + (b_s[1] - ring_range[1].stop % b_s[1])

            # modify the first range to be out of bounds
            ring_range = (ring_range[1] + d_l * rand(1:5), ring_range[2:end]...)

            test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:d_l ÷ b_s[1]
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            @test_throws BoundsError expected[ring_range...]
            @test_throws RingArrayBoundsError test[ring_range...]

            test_error = 1
            try
                test[ring_range...]
            catch e
                test_error = e
            end

            occured = IOBuffer()
            showerror(occured, test_error)

            test_range = tuple(test.range, test.block_size[2:end]...)
            @test occured.data == "RingArrayBoundsError: Cannot index $(ring_range), outside of range $test_range".data
        end
        @testset "testing a bad range in the middle range when indexing into an N d array with an M d index" begin
            s = rand(3:10)
            num_dimensions = rand(4:6)
            b_s = []
            for i in 1:num_dimensions
                push!(b_s, rand(1:10))
            end
            b_s = tuple(b_s...)
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            overflow = s * b_s[1]
            num_overflows = rand(1:10)
            num_indexes = rand(3:num_dimensions - 1) # M < N
            bad_range = rand(2:num_indexes - 1) # M < N

            ranges = []
            for i in 1:num_indexes
                start = rand(1:b_s[i])
                last = rand(start:b_s[i])
                push!(ranges, start:last)
            end

            increase_m_range = 1
            for size in b_s[num_indexes:num_dimensions]
                increase_m_range *= size
            end

            ring_range = (ranges[1] + (block_picked - 1) * b_s[1] + overflow * num_overflows,
                ranges[2:bad_range - 1]...,
                ranges[bad_range] + b_s[bad_range] + 1,
                ranges[bad_range + 1:end - 1]..., ranges[end].start : increase_m_range)
            d_l = ring_range[1].stop + (b_s[1] - ring_range[1].stop % b_s[1])

            test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:ring_range[1].stop ÷ b_s[1] + 1
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            @test_throws BoundsError expected[ring_range...]
            @test_throws RingArrayBoundsError test[ring_range...]

            test_error = 1
            try
                test[ring_range...]
            catch e
                test_error = e
            end

            occured = IOBuffer()
            showerror(occured, test_error)

            test_range = tuple(test.range, test.block_size[2:end]...)
            @test occured.data == "RingArrayBoundsError: Cannot index $(ring_range), outside of range $test_range".data
        end
        @testset "testing a bad range in the last range when indexing into an N d array with an M d index" begin
            s = rand(3:10)
            num_dimensions = rand(4:6)
            b_s = []
            for i in 1:num_dimensions
                push!(b_s, rand(1:10))
            end
            b_s = tuple(b_s...)
            block_picked = rand(3:s)
            index_in_block = (rand(1:b_s[1]))
            overflow = s * b_s[1]
            num_overflows = rand(1:10)
            num_indexes = rand(3:num_dimensions - 1) # M < N
            bad_range = rand(2:num_indexes - 1) # M < N

            ranges = []
            for i in 1:num_indexes
                start = rand(1:b_s[i])
                last = rand(start:b_s[i])
                push!(ranges, start:last)
            end

            increase_m_range = 1
            for size in b_s[num_indexes:num_dimensions]
                increase_m_range *= size
            end
            increase_m_range += 1

            ring_range = (ranges[1] + (block_picked - 1) * b_s[1] + overflow * num_overflows,
                ranges[2:end - 1]..., ranges[end].start : increase_m_range)
            d_l = ring_range[1].stop + (b_s[1] - ring_range[1].stop % b_s[1])

            test = RingArray{Int, num_dimensions}(max_blocks=s, block_size=b_s, data_length=d_l)

            expected = []
            for i in 1:ring_range[1].stop ÷ b_s[1] + 1
                push!(expected, rand(Int, test.block_size))
                load_block(test, expected[end])
            end
            expected = cat(1, expected...)

            @test_throws BoundsError expected[ring_range...]
            @test_throws RingArrayBoundsError test[ring_range...]

            test_error = 1
            try
                test[ring_range...]
            catch e
                test_error = e
            end

            occured = IOBuffer()
            showerror(occured, test_error)

            test_range = tuple(test.range, test.block_size[2:end]...)
            @test occured.data == "RingArrayBoundsError: Cannot index $(ring_range), outside of range $test_range".data
        end
    end
end


text:
include("corey.jl")
include("grid.jl")
include("theis.jl")
include("thiem.jl")
include("transport.jl")

ext: jl


text:
# Julia "translation" of https://github.com/RRZE-HPC/likwid/blob/master/examples/C-internalMarkerAPI.c
using LIKWID
using LIKWID: PerfMon, MarkerFile
using Base.Threads: nthreads, @threads
using Printf

const NUM_FLOPS = 100_000_000
const NUM_COPIES = 100_000
const NUM_THREADS = 3
const MAX_NUM_EVENTS = 20
const ARRAY_SIZE = 2048

@assert nthreads() == NUM_THREADS

_zeroifnothing(x::Nothing) = 0.0
_zeroifnothing(x) = x

"Simple function designed to perform memory operations"
function do_copy(arr, copy_arr, num_copies)
    for _ in 1:num_copies
        for i in eachindex(arr)
            copy_arr[i] = arr[i]
        end
    end
    return nothing
end

"Simple function designed to do floating point computations"
function do_flops(a, b, c, num_flops)
    for _ in 1:num_flops # @simd
        c = a * b + c
        # c = muladd(a, b, c)
    end
    return c
end

# "Optimized function designed to do floating point computations"
# function do_flops(a, b, c, num_flops)
#     @simd for _ in 1:num_flops
#         c = muladd(a, b, c)
#     end
#     return c
# end

function main()

    # ====== Begin setting environment variables ======
    # The envrionment variables used to configure are typically set
    # at runtime with a command like:
    #
    # LIKWID_EVENTS="L2|L3" LIKWID_THREADS="0,2,3" LIKWID_FILEPATH="/tmp/likwid_marker.out" LIKWID_ACCESSMODE="1" LIKWID_FORCE="1" julia -t3 Julia-internalMarkerAPI.jl
    #
    # They are set here to ensure completeness of this example.

    # shouldn't be run under likwid-perfctr
    if haskey(ENV, "LIKWID_PIN")
        error(
            "It appears you are running this example with " *
            "likwid-perfctr. This example is intended to be run alone. " *
            "Results will be incorrect or missing.",
        )
        exit()
    end

    # The first envrionment variable is "LIKWID_EVENTS", which indicates the
    # groups to be measured. In this case, the first 3 are group names
    # specified in `/<LIKWID_PREFIX>/share/likwid/perfgroups/<ARCHITECTURE>/`
    # (default prefix is `/usr/local`). The final group is a custom group
    # specified with EVENT_NAME:COUNTER_NAME
    #
    # Be aware that the groups chosen below are defined for most but not all
    # architectures supported by likwid. For a more compatible set of groups,
    # see the commented group set. The second, more compatible set will work
    # for all architectures supported by likwid except nvidiagpus and Xeon Phi
    # (KNC).
    LIKWID.LIKWID_EVENTS("FLOPS_DP|L2|INSTR_RETIRED_ANY:FIXC0")
    # LIKWID.LIKWID_EVENTS("BRANCH|INSTR_RETIRED_ANY:FIXC0")

    # LIKWID_THREADS must be set to the list of hardware threads that we will use
    # for the Marker API
    LIKWID.LIKWID_THREADS("0,2,3")

    # the location the marker file will be stored
    LIKWID.LIKWID_FILEPATH(joinpath(@__DIR__, "likwid_marker.out"))

    # 1 is the code for access daemon
    LIKWID.LIKWID_MODE(1)

    # If the NMI watchdog is enabled or the application does not call
    # perfmon_finalize(), e.g. because of some error, LIKWID will fail with
    # a message "Counter in use". By settings LIKWID_FORCE you can overwrite
    # the registers.
    LIKWID.LIKWID_FORCE(true)

    # If the user desires more information about what's going on under the
    # hood, set this to get debug information. Values from 0-3 are valid, with
    # 0 being the default (none) and each level from 1-3 being an increased
    # level of verbosity
    LIKWID.LIKWID_DEBUG(0)
    # LIKWID.LIKWID_DEBUG(3)

    # ====== End setting environment variables ======

    # Calls perfmon_init() and perfmon_addEventSet. Uses environment variables
    # set above to configure likwid.
    # Note: LIKWID_MARKER_THREADINIT was required with past versions of likwid but
    # now is now commonly not needed.
    # It is only required if the pinning library fails and there is a risk of
    # threads getting migrated. I am currently unaware of any runtime system
    # that doesn't work.
    Marker.init_nothreads()
    PerfMon.init()

    # Virtual threads must be pinned to a physical thread. This is
    # demonstrated below. Alternatively, threads may be pinned on julia startup
    # using likwid-pin or similar.
    cpus = [0, 2, 3]
    @threads :static for threadid in 1:NUM_THREADS
        LIKWID.pinthread(cpus[threadid])

        # Registering regions is optional but strongly recommended, as it reduces
        # overhead of LIKWID_MARKER_START and prevents wrong counts in short
        # regions.
        #
        # There must be a barrier between registering a region and starting that
        # region. Typically these are done in separate parallel blocks, relying on
        # the implicit barrier at the end of the parallel block. Usually there is
        # a parallel block for initialization and a parallel block for execution.
        Marker.registerregion("Total")
        Marker.registerregion("calc_flops")

        # To demonstrate that registering regions is optional, we do not register
        # the "copy" region
        #
        # Marker.registerregion("copy")
    end

    # variables needed for flops/copy computations
    a = 1.8
    b = 3.2
    c = 1.0
    arr = zeros(ARRAY_SIZE)
    copy_arr = zeros(ARRAY_SIZE)

    # First, we will demonstrate measuring a single region, getting results
    # with `Marker.getregion`, and resetting the region so that these results
    # do not affect later measurements
    @threads :static for threadid in 1:NUM_THREADS
        # This region will measure flop-heavy computation. Notice that only the
        # first group specified, FLOPS_DP, will be measured. Measuring multiple
        # groups is demonstrated in the next example block.
        Marker.startregion("calc_flops")
        a = do_flops(c, a, b, NUM_FLOPS)
        Marker.stopregion("calc_flops")

        nevents, events, time, count = Marker.getregion("calc_flops")

        # group ID will let us get group and event names
        gid = PerfMon.get_id_of_active_group()

        # get group name
        group_name = PerfMon.get_name_of_group(gid)

        # print basic info
        @printf(
            "calc_flops thread %d finished measuring group %s.\nGot %d events, runtime %f s, and call count %d\n",
            threadid,
            group_name,
            nevents,
            time,
            count
        )

        # only allow one thread to print to prevent repeated output.
        if threadid == 1
            # uncomment the for loop if you'd like to inspect all threads.
            tid = 0
            for tid in 1:NUM_THREADS
                println("detailed event results:")
                for eid in 0:(nevents - 1)
                    # get event name
                    event_name = PerfMon.get_name_of_event(gid, eid)
                    # print results
                    @printf(
                        "%40s: %30f\n",
                        event_name,
                        _zeroifnothing(PerfMon.get_result(gid, eid, tid))
                    )
                end
            end
            println()
        end

        # Regions may be reset during execution. This should be called on every
        # thread that should be reset. Since we have already inspected results of
        # the calc_flops region, we will reset it here:
        Marker.resetregion("calc_flops")
    end

    # Next, we'll demonstrate nested regions and measuring multiple groups
    # using `Marker.nextgroup`. We will not inspect them with
    # `Marker.getregion`, but will instead use the marker file to inspect
    # regions after this parallel block is finished.

    # The code that is to be measured will be run multiple times to measure
    # each group specified above. Using `PerfMon.get_number_of_groups` to get the
    # number of iterations makes it easy to run the computations once for each
    # group.
    for i in 1:PerfMon.get_number_of_groups()
        @threads :static for threadid in 1:NUM_THREADS
            # Starting and stopping regions should be done in a parallel block. If
            # regions are started/stopped in a serial region, only the master
            # thread will be measured.
            Marker.startregion("Total")
            Marker.startregion("calc_flops")
            c = do_flops(a, b, c, NUM_FLOPS)
            Marker.stopregion("calc_flops")

            Marker.startregion("copy")
            do_copy(arr, copy_arr, NUM_COPIES)
            Marker.stopregion("copy")
            Marker.stopregion("Total")
        end

        # The barrier after stopping all regions but before switching groups
        # is absolutely required: without it, some threads may
        # not have stopped the "copy" and "Total" regions before switching
        # groups, which causes erroneous results

        # `Marker.nextgroup` should only be run by a single thread and all
        # threads have stopped regions before switching groups.
        #
        # Regions must be switched outside of all regions (e.g. after
        # `Marker.stopregion` is called for each region)
        Marker.nextgroup()
    end
    println()

    # Stops performance monitoring and writes to the file specified in the
    # LIKWID_FILEPATH environment variable. We will read this using likwid to
    # view results.
    #
    Marker.close()

    # Read file output by likwid so that we can process results
    MarkerFile.read(ENV["LIKWID_FILEPATH"])

    # Get information like region name, number of events, and number of
    # metrics. Notice that number of regions printed here is actually
    # (num_region#num_groups), because perfmon considers a region to be the
    # region/group combo. In other words, each time a region is measured with
    # a different group or event set, perfmon considers it a new region.
    #
    # Therefore, if we have two regions and 3 groups measured for each,
    # `MarkerFile.numregions()` will return 6.
    #
    nregions = MarkerFile.numregions()
    println("Marker API measured ", nregions, " regions")
    for rid in 0:(nregions - 1)
        gid = MarkerFile.regiongroup(rid)
        @printf(
            "Region %s with %d events and %d metrics\n",
            MarkerFile.regiontag(rid),
            MarkerFile.regionevents(rid),
            MarkerFile.regionmetrics(rid)
        )
    end
    println()

    # Print per-thread results.
    println("detailed results follow. Notice that the region \"calc_flops\"")
    println("will not appear, as it was reset after each time it was measured.")
    println()

    @printf(
        "%6s : %15s : %10s : %6s : %40s : %30s \n",
        "thread",
        "region",
        "group",
        "type",
        "result name",
        "result value"
    )

    # Uncomment the for loop if you'd like to inspect all threads
    tid = 0
    for tid in 0:(NUM_THREADS - 1)
        for rid in 0:(nregions - 1)
            # Returns the user-supplied region name
            region_name = MarkerFile.regiontag(rid)

            # gid is the group ID independent of region, where as i is the ID
            # of the region/group combo
            gid = MarkerFile.regiongroup(rid)
            # Get the name of the group measured, like "FLOPS_DP" or "L2"
            group_name = PerfMon.get_name_of_group(gid)

            # Get info for each event
            for eid in 0:(PerfMon.get_number_of_events(gid) - 1)
                # Get the event name, like "INSTR_RETIRED_ANY"
                event_name = PerfMon.get_name_of_event(gid, eid)
                # Get the associated value
                event_value = MarkerFile.regionresult(rid, eid, tid)

                @printf(
                    "%6d : %15s : %10s : %6s : %40s : %30f \n",
                    tid,
                    region_name,
                    group_name,
                    "event",
                    event_name,
                    event_value
                )
            end

            # Get info for each metric
            for mid in 0:(PerfMon.get_number_of_metrics(gid) - 1)
                # Get the metric name, like "L2 bandwidth [MBytes/s]"
                metric_name = PerfMon.get_name_of_metric(gid, mid)
                # Get the associated value
                metric_value = MarkerFile.regionmetric(rid, mid, tid)

                @printf(
                    "%6d : %15s : %10s : %6s : %40s : %30f \n",
                    tid,
                    region_name,
                    group_name,
                    "metric",
                    metric_name,
                    metric_value
                )
            end
        end
    end

    return nothing
end

main()


text:
using Compat

# Functions - most have to be implemented with the concrete type
occurrences(asm::AbstractAssemblage)::AbstractMatrix = error("function not defined for $(typeof(asm))")
view(asm::AbstractAssemblage) = error("function not defined for $(typeof(asm))")
places(asm::AbstractAssemblage)::AbstractPlaces = error("function not defined for $(typeof(asm))")
things(asm::AbstractAssemblage)::AbstractThings = error("function not defined for $(typeof(asm))")

nplaces(plc::AbstractPlaces)::Integer = error("function not defined for $(typeof(plc))")
nplaces(plc::AbstractAssemblage) = nplaces(places(plc))
placenames(plc::AbstractPlaces)::AbstractVector{<:String} = error("function not defined for $(typeof(plc))")
placenames(plc::AbstractAssemblage) = placenames(places(plc))

nthings(thg::AbstractThings)::Integer = error("function not defined for $(typeof(thg))")
nthings(asm::AbstractAssemblage) = nthings(things(asm))
thingnames(thg::AbstractThings)::AbstractVector{<:String} = error("function not defined for $(typeof(thg))")
thingnames(asm::AbstractAssemblage) = thingnames(things(asm))

nzrows(a::AbstractMatrix) = findall(vec(Compat.sum(a, dims = 2) .> 0))
nzcols(a::AbstractMatrix) = findall(vec(Compat.sum(a, dims = 1) .> 0))
nnz(a::AbstractArray) = Compat.sum(a .> 0)

occurring(asm::AbstractAssemblage) = occurring(occurrences(asm))
occurring(a::AbstractMatrix) = nzrows(a)
occurring(asm::AbstractAssemblage, idx) = occurring(occurrences(asm), idx)

occupied(asm::AbstractAssemblage) = occupied(occurrences(asm))
occupied(asm::AbstractAssemblage, idx) = occupied(occurrences(asm), idx)
occupied(a::AbstractMatrix) = nzcols(a)

if VERSION < v"0.7.0-"
    occupied(a::AbstractMatrix, idx) = collect(zip(findn(a[idx, :])))
    occurring(a::AbstractMatrix, idx) = collect(zip(findn(a[:, idx])))
else
    occupied(a::AbstractMatrix, idx) = findall(!iszero, a[idx, :])
    occurring(a::AbstractMatrix, idx) = findall(!iszero, a[:, idx])
end

noccurring(x) = length(occurring(x))
noccupied(x) = length(occupied(x))
noccurring(x, idx) = length(occurring(x, idx))
noccupied(x, idx) = length(occupied(x, idx))

thingoccurrences(asm::AbstractAssemblage, idx) = thingoccurrences(occurrences(asm), idx)
thingoccurrences(mat::AbstractMatrix, idx) = view(mat, idx, :)
placeoccurrences(asm::AbstractAssemblage, idx) = placeoccurrences(occurrences(asm), idx)
placeoccurrences(mat::AbstractMatrix, idx) = view(mat, :, idx) # make certain that the view implementation also takes thing or place names

richness(asm::AbstractAssemblage) = richness(occurrences(asm))
richness(a::AbstractMatrix{Bool}) = collect(vec(Compat.sum(a, dims = 1)))
richness(a::AbstractMatrix) = collect(vec(mapslices(nnz, a, dims = 1)))

occupancy(asm::AbstractAssemblage) = occupancy(occurrences(asm))
occupancy(a::AbstractMatrix{Bool}) = collect(vec(Compat.sum(a, dims=2)))
occupancy(a::AbstractMatrix) = collect(vec(mapslices(nnz, a, dims=2)))

nrecords(asm::AbstractAssemblage) = nrecords(occurrences(asm))
nrecords(a::AbstractMatrix) = nnz(a)

cooccurring(asm, inds...) = cooccurring(asm, [inds...])
function cooccurring(asm, inds::AbstractVector)
    sub = view(asm, species = inds)
    richness(sub) .== nthings(sub)
end

function createsummaryline(vec::AbstractVector{<:AbstractString})
    linefunc(vec) = mapreduce(x -> x * ", ", *, vec[1:(end-1)]) * vec[end]
    length(vec) == 1 && return vec[1]
    length(vec) < 6 && return linefunc(vec)
    linefunc(vec[1:3]) * "..." * linefunc(vec[(end-1):end])
end

function show(io::IO, asm::T) where T <: AbstractAssemblage
    tn = createsummaryline(thingnames(asm))
    pn = createsummaryline(placenames(asm))
    println(io,
    """$T with $(nthings(asm)) things in $(nplaces(asm)) places

    Thing names:
    $(tn)

    Place names:
    $(pn)
    """)
end

nplaces(asm::AbstractAssemblage, args...) = nplaces(places(asm), args...)
placenames(asm::AbstractAssemblage, args...) = placenames(places(asm), args...)
nthings(asm::AbstractAssemblage, args...) = nthings(things(asm), args...)
thingnames(asm::AbstractAssemblage, args...) = thingnames(things(asm), args...)

# TODO:
# accessing cache

# Methods for AbstractPlaces
getcoords(plc::AbstractPlaces{Nothing}) = plc # Pure places generate their own fake location data
getcoords(plc::AbstractPlaces{<: AbstractLocationData}) =
    error("function not defined for $(typeof(plc))")
coordinates(plc::AbstractPlaces) = error("function not defined for $(typeof(plc))")

# Methods for AbstractGrid
xmin(grd::AbstractGrid) = error("function not defined for $(typeof(grd))")
ymin(grd::AbstractGrid) = error("function not defined for $(typeof(grd))")
xcellsize(grd::AbstractGrid) = error("function not defined for $(typeof(grd))")
ycellsize(grd::AbstractGrid) = error("function not defined for $(typeof(grd))")
xcells(grd::AbstractGrid) = error("function not defined for $(typeof(grd))")
ycells(grd::AbstractGrid) = error("function not defined for $(typeof(grd))")
cellsize(grd) = xcellsize(grd), ycellsize(grd)
cells(grd) = xcells(grd), ycells(grd)
xrange(grd) = xmin(grd):xcellsize(grd):xmax(grd) #includes intermediary points
yrange(grd) = ymin(grd):ycellsize(grd):ymax(grd)
xmax(grd) = xmin(grd) + xcellsize(grd) * (xcells(grd) - 1)
ymax(grd) = ymin(grd) + ycellsize(grd) * (ycells(grd) - 1)

indices(grd::AbstractGrid) = error("function not defined for $(typeof(grd))")
indices(grd::AbstractGrid, idx) = error("function not defined for $(typeof(grd))")
coordinates(grd::AbstractGrid) = error("function not defined for $(typeof(grd))")