I have the following code which generates some string output:
package formatter
import (
"bytes"
"log"
"text/template"
"github.com/foo/bar/internal/mapper"
)
// map of template functions that enable us to identify the final item within a
// collection being iterated over.
var fns = template.FuncMap{
"plus1": func(x int) int {
return x + 1
},
}
// Dot renders our results in dot format for use with graphviz
func Dot(results []mapper.Page) string {
dotTmpl := `digraph sitemap { {{range .}}
"{{.URL}}"
-> { {{$n := len .Anchors}}{{range $i, $v := .Anchors}}
"{{.}}"{{if eq (plus1 $i) $n}}{{else}},{{end}}{{end}}
} {{end}}
}`
tmpl, err := template.New("digraph").Funcs(fns).Parse(dotTmpl)
if err != nil {
log.Fatal(err)
}
var output bytes.Buffer
if err := tmpl.Execute(&output, results); err != nil {
log.Fatal(err)
}
return output.String()
}
It generates output like:
digraph sitemap {
"http://www.example.com/"
-> {
"http://www.example.com/foo",
"http://www.example.com/bar",
"http://www.example.com/baz"
}
}
Below is a test for this functionality...
package formatter
import (
"testing"
"github.com/foo/bar/internal/mapper"
)
func TestDot(t *testing.T) {
input := []mapper.Page{
mapper.Page{
URL: "http://www.example.com/",
Anchors: []string{
"http://www.example.com/foo",
"http://www.example.com/bar",
"http://www.example.com/baz",
},
Links: []string{
"http://www.example.com/foo.css",
"http://www.example.com/bar.css",
"http://www.example.com/baz.css",
},
Scripts: []string{
"http://www.example.com/foo.js",
"http://www.example.com/bar.js",
"http://www.example.com/baz.js",
},
},
}
output := `digraph sitemap {
"http://www.example.com/"
-> {
"http://www.example.com/foo",
"http://www.example.com/bar",
"http://www.example.com/baz"
}
}`
actual := Dot(input)
if actual != output {
t.Errorf("expected: %s
got: %s", output, actual)
}
}
Which fails with the following error (which is related to the outputted format spacing)...
--- FAIL: TestDot (0.00s)
format_test.go:43: expected: digraph sitemap {
"http://www.example.com/"
-> {
"http://www.example.com/foo",
"http://www.example.com/bar",
"http://www.example.com/baz"
}
}
got: digraph sitemap {
"http://www.example.com/"
-> {
"http://www.example.com/foo",
"http://www.example.com/bar",
"http://www.example.com/baz"
}
}
I've tried tweaking my test output
variable so the spacing would align with what's actually outputted from the real code. That didn't work.
I also tried using strings.Replace()
on both my output variable and the actual outputted content and bizarrely the output from my function (even though it was passed through strings.Replace
would still be multi-lined (and so the test would fail)?
Anyone have any ideas how I can make the output consistent for the sake of code verification?
Thanks.
I tried the approach suggested by @icza and it still fails the test, although the output in the test looks more like it's expected to be:
=== RUN TestDot
--- FAIL: TestDot (0.00s)
format_test.go:65: expected: digraph sitemap {
"http://www.example.com/"
-> {
"http://www.example.com/foo",
"http://www.example.com/bar",
"http://www.example.com/baz"
}
}
got: digraph sitemap {
"http://www.example.com/"
-> {
"http://www.example.com/foo",
"http://www.example.com/bar",
"http://www.example.com/baz"
}
}
The simplest solution is to use the same indentation in the test when specifying the expected output (the same what you use in the template).
You have:
output := `digraph sitemap {
"http://www.example.com/"
-> {
"http://www.example.com/foo",
"http://www.example.com/bar",
"http://www.example.com/baz"
}
}`
Change it to:
output := `digraph sitemap {
"http://www.example.com/"
-> {
"http://www.example.com/foo",
"http://www.example.com/bar",
"http://www.example.com/baz"
}
}`
Note that for example the final line is not indented. When you use raw string literal, every character including indentation characters is part of the literal as-is.
After all, this is completely a non-coding issue, but rather an issue of editors' auto-formatting and defining a raw string literal. An easy way to get it right is first to write an empty raw string literal, add an empty line to it and clear the auto-indentation inserted by the editor:
output := `
`
When you have this, copy-paste the correct input before the closing backtick, e.g.:
output := `
digraph sitemap {
"http://www.example.com/"
-> {
"http://www.example.com/foo",
"http://www.example.com/bar",
"http://www.example.com/baz"
}
}`
And as a last step, remove line break from the first line of the raw string literal, and you have the correct raw string literal:
output := `digraph sitemap {
"http://www.example.com/"
-> {
"http://www.example.com/foo",
"http://www.example.com/bar",
"http://www.example.com/baz"
}
}`
Once you have this, running gofmt
or auto-formatting of editors will not mess with it anymore.
UPDATE:
I checked your updated test result, and in the result you get, there is a space after the first line: digraph sitemap {
, and also there's a space after the 3rd line: -> {
, but you don't add those to your expected output. Either add those to your expected output too, or remove those spaces from the template! When comparing strings, they are compared byte-wise, every character (including white-spaces) matter.
To remove those extra spaces from the template:
dotTmpl := `digraph sitemap { {{- range .}}
"{{.URL}}"
-> { {{- $n := len .Anchors}}{{range $i, $v := .Anchors}}
"{{.}}"{{if eq (plus1 $i) $n}}{{else}},{{end}}{{end}}
} {{end}}
}`
Note the use of {{-
. This is to trim spaces around template actions, this was added in Go 1.6.
the problem is that there is an extra space. in your formatted text right after {
that seems to be your problem. You can fix it by changing your format string to this
`digraph sitemap {{{range .}}
"{{.URL}}"
-> {{{$n := len .Anchors}}{{range $i, $v := .Anchors}}
"{{.}}"{{if eq (plus1 $i) $n}}{{else}},{{end}}{{end}}
}{{end}}
}`
If you want to ignore format, you can use strings.Fields
.
output := strings.Fields(`digraph sitemap {
"http://www.example.com/"
-> {
"http://www.example.com/foo",
"http://www.example.com/bar",
"http://www.example.com/baz"
}
}`)
actual := strings.Fields(Dot(input))
if !equal(output,actual) {
// ...
}
where equal is a simple function that compares two slices.