resources: Fix image test error on s390x, ppc64* and arm64

In TestImageOperationsGolden, tolerate slight floating-point rounding
differences due to the use or non-use of "fused multiply and add" (FMA)
instruction on different architectures.

Special thanks to @disintegration for the solution in goldenEqual();
see https://github.com/disintegration/gift/issues/20

Fixes #6387
This commit is contained in:
Anthony Fok 2019-10-07 12:35:00 -06:00 committed by Bjørn Erik Pedersen
parent 020a6fbd7f
commit 39ed33fceb

View file

@ -15,6 +15,7 @@ package resources
import ( import (
"fmt" "fmt"
"image"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"math/rand" "math/rand"
@ -22,6 +23,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime"
"strconv" "strconv"
"sync" "sync"
"testing" "testing"
@ -478,6 +480,47 @@ func BenchmarkImageExif(b *testing.B) {
} }
// usesFMA indicates whether "fused multiply and add" (FMA) instruction is
// used. The command "grep FMADD go/test/codegen/floats.go" can help keep
// the FMA-using architecture list updated.
var usesFMA = runtime.GOARCH == "s390x" ||
runtime.GOARCH == "ppc64" ||
runtime.GOARCH == "ppc64le" ||
runtime.GOARCH == "arm64"
// goldenEqual compares two NRGBA images. It is used in golden tests only.
// A small tolerance is allowed on architectures using "fused multiply and add"
// (FMA) instruction to accommodate for floating-point rounding differences
// with control golden images that were generated on amd64 architecture.
// See https://golang.org/ref/spec#Floating_point_operators
// and https://github.com/gohugoio/hugo/issues/6387 for more information.
//
// Borrowed from https://github.com/disintegration/gift/blob/a999ff8d5226e5ab14b64a94fca07c4ac3f357cf/gift_test.go#L598-L625
// Copyright (c) 2014-2019 Grigory Dryapak
// Licensed under the MIT License.
func goldenEqual(img1, img2 *image.NRGBA) bool {
maxDiff := 0
if usesFMA {
maxDiff = 1
}
if !img1.Rect.Eq(img2.Rect) {
return false
}
if len(img1.Pix) != len(img2.Pix) {
return false
}
for i := 0; i < len(img1.Pix); i++ {
diff := int(img1.Pix[i]) - int(img2.Pix[i])
if diff < 0 {
diff = -diff
}
if diff > maxDiff {
return false
}
}
return true
}
func TestImageOperationsGolden(t *testing.T) { func TestImageOperationsGolden(t *testing.T) {
c := qt.New(t) c := qt.New(t)
c.Parallel() c.Parallel()
@ -580,21 +623,51 @@ func TestImageOperationsGolden(t *testing.T) {
} }
fi2 := dirinfos2[i] fi2 := dirinfos2[i]
c.Assert(fi1.Name(), qt.Equals, fi2.Name()) c.Assert(fi1.Name(), qt.Equals, fi2.Name())
c.Assert(fi1, eq, fi2)
f1, err := os.Open(filepath.Join(dir1, fi1.Name())) f1, err := os.Open(filepath.Join(dir1, fi1.Name()))
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
f2, err := os.Open(filepath.Join(dir2, fi2.Name())) f2, err := os.Open(filepath.Join(dir2, fi2.Name()))
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
hash1, err := helpers.MD5FromReader(f1) img1, _, err := image.Decode(f1)
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
hash2, err := helpers.MD5FromReader(f2) img2, _, err := image.Decode(f2)
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
nrgba1 := image.NewNRGBA(img1.Bounds())
gift.New().Draw(nrgba1, img1)
nrgba2 := image.NewNRGBA(img2.Bounds())
gift.New().Draw(nrgba2, img2)
if !goldenEqual(nrgba1, nrgba2) {
switch fi1.Name() {
case "gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_4c320010919da2d8b63ed24818b4d8e1.png",
"gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9d4c2220235b3c2d9fa6506be571560f.png",
"gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c74bb417b961e09cf1aac2130b7b9b85.png":
c.Log("expectedly differs from golden due to dithering:", fi1.Name())
default:
t.Errorf("resulting image differs from golden: %s", fi1.Name())
}
}
if !usesFMA {
c.Assert(fi1, eq, fi2)
_, err = f1.Seek(0, 0)
c.Assert(err, qt.IsNil)
_, err = f2.Seek(0, 0)
c.Assert(err, qt.IsNil)
hash1, err := helpers.MD5FromReader(f1)
c.Assert(err, qt.IsNil)
hash2, err := helpers.MD5FromReader(f2)
c.Assert(err, qt.IsNil)
c.Assert(hash1, qt.Equals, hash2)
}
f1.Close() f1.Close()
f2.Close() f2.Close()
c.Assert(hash1, qt.Equals, hash2)
} }
} }