storj/cmd/segment-reaper/bitmask.go
Ivan Fraixedes 50e8314279 cmd/segment-reaper: Fix bitArray.IsSequence method
The bitArray.IsSequence method had a bug that caused that sometimes the
test "Bitmask/IsSequence/sequence starts at other index than 0" to fail
due to it uses random values.

The test mostly found a corner case, I've added new tests to catch the
bug and then apply the fix.

Change-Id: If01dc07006d261ba40bbd99d36ef776e09ed3efc
2020-05-12 13:02:52 +00:00

109 lines
2.7 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"math/bits"
"github.com/zeebo/errs"
)
// errorBitmaskInvalidIdx is the error class to return invalid indexes for the
// the bitArray type.
var errorBitmaskInvalidIdx = errs.Class("invalid index")
// bitArray allows easy access to bit values by indices.
type bitArray []byte
// Set tracks index in mask. It returns an error if index is negative.
// Set will resize the array if you access an index larger than its Length.
func (bytes *bitArray) Set(index int) error {
bitIndex, byteIndex := index%8, index/8
switch {
case index < 0:
return errorBitmaskInvalidIdx.New("negative value (%d)", index)
case byteIndex >= len(*bytes):
sizeToGrow := byteIndex - len(*bytes) + 1
*bytes = append(*bytes, make([]byte, sizeToGrow)...)
}
mask := byte(1) << bitIndex
(*bytes)[byteIndex] |= mask
return nil
}
// Unset removes bit from index in mask. It returns an error if index is negative.
func (bytes *bitArray) Unset(index int) error {
bitIndex, byteIndex := index%8, index/8
switch {
case index < 0:
return errorBitmaskInvalidIdx.New("negative value (%d)", index)
case byteIndex >= len(*bytes):
return nil
}
mask := byte(1) << bitIndex
(*bytes)[byteIndex] &^= mask
return nil
}
// Has returns true if the index is tracked in mask otherwise false.
// It returns an error if index is negative.
func (bytes *bitArray) Has(index int) (bool, error) {
bitIndex, byteIndex := index%8, index/8
switch {
case index < 0:
return false, errorBitmaskInvalidIdx.New("negative value (%d)", index)
case byteIndex >= len(*bytes):
return false, nil
}
mask := byte(1) << bitIndex
result := (*bytes)[byteIndex] & mask
return result != 0, nil
}
// Count returns the number of bits which are set.
func (bytes *bitArray) Count() int {
count := 0
for x := 0; x < len(*bytes); x++ {
count += bits.OnesCount8((*bytes)[x])
}
return count
}
// IsSequence returns true if mask has only tracked a correlative sequence of
// indexes starting from index 0.
func (bytes *bitArray) IsSequence() bool {
// find the last byte of the sequence that contains some one
var i int
for i = len(*bytes) - 1; i >= 0; i-- {
zeros := bits.LeadingZeros8((*bytes)[i])
if zeros == 8 {
continue
}
ones := bits.OnesCount8((*bytes)[i])
if zeros+ones != 8 {
// zeros and ones in this byte aren't in sequence
return false
}
break
}
// The rest of the bytes of the sequence must only contains ones
i--
for ; i >= 0; i-- {
if (*bytes)[i] != 255 {
return false
}
}
return true
}
// Length returns the current size of the array in bits.
func (bytes *bitArray) Length() int {
return len(*bytes) * 8
}