| // Copyright ©2019 The Gonum Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package layout_test |
| |
| import ( |
| "bytes" |
| "encoding/base64" |
| "image" |
| "image/png" |
| "os" |
| "path/filepath" |
| "strings" |
| "testing" |
| |
| "gonum.org/v1/gonum/graph" |
| "gonum.org/v1/gonum/graph/internal/ordered" |
| "gonum.org/v1/gonum/graph/iterator" |
| "gonum.org/v1/plot/cmpimg" |
| ) |
| |
| // orderedGraph wraps a graph.Graph ensuring consistent ordering of nodes |
| // in graph queries. Removal of this causes to tests to fail due to changes |
| // in node iteration order, but the produced graph layouts are still good. |
| type orderedGraph struct { |
| graph.Graph |
| } |
| |
| func (g orderedGraph) Nodes() graph.Nodes { |
| n := graph.NodesOf(g.Graph.Nodes()) |
| ordered.ByID(n) |
| return iterator.NewOrderedNodes(n) |
| } |
| |
| func (g orderedGraph) From(id int64) graph.Nodes { |
| n := graph.NodesOf(g.Graph.From(id)) |
| ordered.ByID(n) |
| return iterator.NewOrderedNodes(n) |
| } |
| |
| func goldenPath(path string) string { |
| ext := filepath.Ext(path) |
| noext := strings.TrimSuffix(path, ext) |
| return noext + "_golden" + ext |
| } |
| |
| func checkRenderedLayout(t *testing.T, path string) (ok bool) { |
| if *cmpimg.GenerateTestData { |
| // Recreate Golden image and exit. |
| golden := goldenPath(path) |
| _ = os.Remove(golden) |
| if err := os.Rename(path, golden); err != nil { |
| t.Fatal(err) |
| } |
| return true |
| } |
| |
| // Read the images we've just generated and check them against the |
| // Golden Images. |
| got, err := os.ReadFile(path) |
| if err != nil { |
| t.Errorf("failed to read %s: %v", path, err) |
| return true |
| } |
| golden := goldenPath(path) |
| want, err := os.ReadFile(golden) |
| if err != nil { |
| t.Errorf("failed to read golden file %s: %v", golden, err) |
| return true |
| } |
| typ := filepath.Ext(path)[1:] // remove the dot in ".png" |
| ok, err = cmpimg.Equal(typ, got, want) |
| if err != nil { |
| t.Errorf("failed to compare image for %s: %v", path, err) |
| return true |
| } |
| if !ok { |
| t.Errorf("image mismatch for %s\n", path) |
| v1, _, err := image.Decode(bytes.NewReader(got)) |
| if err != nil { |
| t.Errorf("failed to decode %s: %v", path, err) |
| return false |
| } |
| v2, _, err := image.Decode(bytes.NewReader(want)) |
| if err != nil { |
| t.Errorf("failed to decode %s: %v", golden, err) |
| return false |
| } |
| |
| dst := image.NewRGBA64(v1.Bounds().Union(v2.Bounds())) |
| rect := cmpimg.Diff(dst, v1, v2) |
| t.Logf("image bounds union:%+v diff bounds intersection:%+v", dst.Bounds(), rect) |
| |
| var buf bytes.Buffer |
| err = png.Encode(&buf, dst) |
| if err != nil { |
| t.Errorf("failed to encode difference png: %v", err) |
| return false |
| } |
| t.Log("IMAGE:" + base64.StdEncoding.EncodeToString(buf.Bytes())) |
| } |
| return ok |
| } |