Increase test coverage to 96.6% (#61)

* gitignore *.html for test coverage reports

* fix typos

* Increase hashcompute.go test coverage to 100 percent

* Add new Go versions. Upgrade GitHub Actions.

* Add tests for missing ext hashes

* Add test for hashing non-hexadecimal string

* Add tests for loading empty bytes buffer

* Run go fmt

* Fix comments

* Fix comments

* Fix spelling

* fix actions versions

* Upgrade to actions/setup-go@v4

* Add minimum Go version for go.mod
This commit is contained in:
Wu Tingfeng 2023-05-03 22:52:14 +08:00 committed by GitHub
parent 464cef2de9
commit d68e89bd8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 94 additions and 26 deletions

View File

@ -4,12 +4,12 @@ jobs:
test: test:
strategy: strategy:
matrix: matrix:
go-version: [1.6.x, 1.7.x, 1.8.x, 1.9.x, 1.10.x, 1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x] go-version: [1.6.x, 1.7.x, 1.8.x, 1.9.x, 1.10.x, 1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x]
platform: [ubuntu-latest] platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- name: Install Go - name: Install Go
uses: actions/setup-go@v1 uses: actions/setup-go@v4
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Set GOPATH - name: Set GOPATH

View File

@ -4,12 +4,12 @@ jobs:
test: test:
strategy: strategy:
matrix: matrix:
go-version: [1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x] go-version: [1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x]
platform: [ubuntu-latest, macos-latest, windows-latest] platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- name: Install Go - name: Install Go
uses: actions/setup-go@v1 uses: actions/setup-go@v4
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Checkout code - name: Checkout code

1
.gitignore vendored
View File

@ -9,6 +9,7 @@
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
*.html
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/ .glide/

2
go.mod
View File

@ -1,3 +1,5 @@
module github.com/corona10/goimagehash module github.com/corona10/goimagehash
go 1.11
require github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 require github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646

View File

@ -14,7 +14,7 @@ import (
"github.com/nfnt/resize" "github.com/nfnt/resize"
) )
// AverageHash fuction returns a hash computation of average hash. // AverageHash function returns a hash computation of average hash.
// Implementation follows // Implementation follows
// http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html // http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html
func AverageHash(img image.Image) (*ImageHash, error) { func AverageHash(img image.Image) (*ImageHash, error) {

View File

@ -124,6 +124,34 @@ func TestNilHashCompute(t *testing.T) {
} }
} }
func TestExtendHashCompute(t *testing.T) {
file, err := os.Open("_examples/sample1.jpg")
if err != nil {
t.Errorf("%s", err)
}
defer file.Close()
img, err := jpeg.Decode(file)
if err != nil {
t.Errorf("%s", err)
}
hash, err := ExtPerceptionHash(img, 0, 8)
if err == nil {
t.Errorf("Error should be got.")
}
if hash != nil {
t.Errorf("Nil hash should be got. but got %v", hash)
}
hash, err = ExtPerceptionHash(img, 16, 2)
if err != nil {
t.Errorf("%s", err)
}
if hash == nil {
t.Errorf("Hash should be got.")
}
}
func TestNilExtendHashCompute(t *testing.T) { func TestNilExtendHashCompute(t *testing.T) {
hash, err := ExtAverageHash(nil, 8, 8) hash, err := ExtAverageHash(nil, 8, 8)
if err == nil { if err == nil {
@ -348,7 +376,7 @@ func BenchmarkAverageHash(b *testing.B) {
} }
} }
func BenchmarkDiffrenceHash(b *testing.B) { func BenchmarkDifferenceHash(b *testing.B) {
file1, err := os.Open("_examples/sample3.jpg") file1, err := os.Open("_examples/sample3.jpg")
if err != nil { if err != nil {
b.Errorf("%s", err) b.Errorf("%s", err)

View File

@ -66,6 +66,9 @@ func TestSerialization(t *testing.T) {
methods := []func(img image.Image) (*ImageHash, error){ methods := []func(img image.Image) (*ImageHash, error){
AverageHash, PerceptionHash, DifferenceHash, AverageHash, PerceptionHash, DifferenceHash,
} }
extMethods := []func(img image.Image, width int, height int) (*ExtImageHash, error){
ExtAverageHash, ExtPerceptionHash, ExtDifferenceHash,
}
examples := []string{ examples := []string{
"_examples/sample1.jpg", "_examples/sample2.jpg", "_examples/sample3.jpg", "_examples/sample4.jpg", "_examples/sample1.jpg", "_examples/sample2.jpg", "_examples/sample3.jpg", "_examples/sample4.jpg",
} }
@ -103,15 +106,17 @@ func TestSerialization(t *testing.T) {
} }
// test for ExtIExtImageHash // test for ExtIExtImageHash
for _, extMethod := range extMethods {
extMethodStr := runtime.FuncForPC(reflect.ValueOf(extMethod).Pointer()).Name()
sizeList := []int{8, 16} sizeList := []int{8, 16}
for _, size := range sizeList { for _, size := range sizeList {
hash, err := ExtPerceptionHash(img, size, size) hash, err := extMethod(img, size, size)
checkErr(err) checkErr(err)
hex := hash.ToString() hex := hash.ToString()
// len(kind) == 1, len(":") == 1 // len(kind) == 1, len(":") == 1
if len(hex) != size*size/4+2 { if len(hex) != size*size/4+2 {
t.Errorf("Got invalid hex string '%v'; %v of '%v'", hex, "ExtPerceptionHash", ex) t.Errorf("Got invalid hex string '%v'; %v of '%v'", hex, extMethodStr, ex)
} }
reHash, err := ExtImageHashFromString(hex) reHash, err := ExtImageHashFromString(hex)
@ -127,6 +132,26 @@ func TestSerialization(t *testing.T) {
} }
} }
// test for hashing empty string
imageHash, err := ImageHashFromString("")
if imageHash != nil {
t.Errorf("Expected reHash to be nil, got %v", imageHash)
}
if err == nil {
t.Errorf("Should got error for empty string")
}
extImageHash, err := ExtImageHashFromString("")
if extImageHash != nil {
t.Errorf("Expected reHash to be nil, got %v", extImageHash)
}
if err == nil {
t.Errorf("Should got error for empty string")
}
// test for hashing invalid (non-hexadecimal) string
extImageHash, err = ExtImageHashFromString("k:g")
}
func TestDifferentBitSizeHash(t *testing.T) { func TestDifferentBitSizeHash(t *testing.T) {
checkErr := func(err error) { checkErr := func(err error) {
if err != nil { if err != nil {
@ -231,4 +256,16 @@ func TestDumpAndLoad(t *testing.T) {
} }
} }
} }
// test for loading empty bytes buffer
var b bytes.Buffer
bar := bufio.NewReader(&b)
_, err := LoadImageHash(bar)
if err == nil {
t.Errorf("Should got error for empty bytes buffer")
}
_, err = LoadExtImageHash(bar)
if err == nil {
t.Errorf("Should got error for empty bytes buffer")
}
} }

View File

@ -37,7 +37,7 @@ func forwardTransform(input, temp []float64, Len int) {
input[Len-2], input[Len-1] = temp[halfLen-1], temp[Len-1] input[Len-2], input[Len-1] = temp[halfLen-1], temp[Len-1]
} }
// DCT2D function returns a result of DCT2D by using the seperable property. // DCT2D function returns a result of DCT2D by using the separable property.
func DCT2D(input [][]float64, w int, h int) [][]float64 { func DCT2D(input [][]float64, w int, h int) [][]float64 {
output := make([][]float64, h) output := make([][]float64, h)
for i := range output { for i := range output {
@ -74,7 +74,7 @@ func DCT2D(input [][]float64, w int, h int) [][]float64 {
return output return output
} }
// DCT2DFast64 function returns a result of DCT2D by using the seperable property. // DCT2DFast64 function returns a result of DCT2D by using the separable property.
// Fast uses static DCT tables for improved performance. Returns flattened pixels. // Fast uses static DCT tables for improved performance. Returns flattened pixels.
func DCT2DFast64(input *[]float64) (flattens [64]float64) { func DCT2DFast64(input *[]float64) (flattens [64]float64) {
if len(*input) != 64*64 { if len(*input) != 64*64 {
@ -98,7 +98,7 @@ func DCT2DFast64(input *[]float64) (flattens [64]float64) {
return return
} }
// DCT2DFast256 function returns a result of DCT2D by using the seperable property. // DCT2DFast256 function returns a result of DCT2D by using the separable property.
// DCT type II, unscaled. Algorithm by Byeong Gi Lee, 1984. // DCT type II, unscaled. Algorithm by Byeong Gi Lee, 1984.
// Fast uses static DCT tables for improved performance. Returns flattened pixels. // Fast uses static DCT tables for improved performance. Returns flattened pixels.
func DCT2DFast256(input *[]float64) (flattens [256]float64) { func DCT2DFast256(input *[]float64) (flattens [256]float64) {

View File

@ -58,7 +58,7 @@ func rgb2GrayDefault(colorImg image.Image, pixels []float64, s int) {
} }
} }
// rgb2GrayYCbCR uses *image.YCbCr which is signifiantly faster than the image.Image interface. // rgb2GrayYCbCR uses *image.YCbCr which is significantly faster than the image.Image interface.
func rgb2GrayYCbCR(colorImg *image.YCbCr, pixels []float64, s int) { func rgb2GrayYCbCR(colorImg *image.YCbCr, pixels []float64, s int) {
for i := 0; i < s; i++ { for i := 0; i < s; i++ {
for j := 0; j < s; j++ { for j := 0; j < s; j++ {
@ -67,7 +67,7 @@ func rgb2GrayYCbCR(colorImg *image.YCbCr, pixels []float64, s int) {
} }
} }
// rgb2GrayYCbCR uses *image.RGBA which is signifiantly faster than the image.Image interface. // rgb2GrayRGBA uses *image.RGBA which is significantly faster than the image.Image interface.
func rgb2GrayRGBA(colorImg *image.RGBA, pixels []float64, s int) { func rgb2GrayRGBA(colorImg *image.RGBA, pixels []float64, s int) {
for i := 0; i < s; i++ { for i := 0; i < s; i++ {
for j := 0; j < s; j++ { for j := 0; j < s; j++ {