From 0d7bf2514d1212613102023ee4f21824e9161593 Mon Sep 17 00:00:00 2001 From: Brandon Dyck Date: Sat, 11 May 2019 20:37:40 -0600 Subject: [PATCH] Benchmark against html/template --- README.md | 2 + benchmark_test.go | 104 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 benchmark_test.go diff --git a/README.md b/README.md index e926f79..fd05af9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ hatmill - HTML generation DSL for Go [![GoDoc](https://godoc.org/gitlab.codemonkeysoftware.net/b/hatmill?status.svg)](https://godoc.org/gitlab.codemonkeysoftware.net/b/hatmill) ![Badge count](https://img.shields.io/badge/badges-5-yellow.svg) +`hatmill` provides a simple set of types and helper functions for writing HTML in plain Go code, without having to deal with any template languages. It is not spectacularly performant, but is comparable to the `html/template` package (at least in simple cases; run `go test -bench=. -benchmem` for proof). `hatmill` “templates” are arguably easier to read and write than many template languages. + Installation --------------- There are three necessary packages: diff --git a/benchmark_test.go b/benchmark_test.go new file mode 100644 index 0000000..4106623 --- /dev/null +++ b/benchmark_test.go @@ -0,0 +1,104 @@ +package hatmill_test + +import ( + "bytes" + h "gitlab.codemonkeysoftware.net/b/hatmill" + he "gitlab.codemonkeysoftware.net/b/hatmill/element" + "html/template" + "io" + "testing" +) + +type benchPerson struct { + Name string + FavoriteAnimals []string +} + +type benchModel struct { + Title string + Persons []benchPerson +} + +var benchData = benchModel{ + Title: "People I know", + Persons: []benchPerson{ + {Name: "Malter Witty", FavoriteAnimals: []string{"porpoise"}}, + {Name: "Perry Moppins", FavoriteAnimals: []string{"penguin"}}, + {Name: "Ralbus Fumblemore", FavoriteAnimals: []string{"owl", "cat", "toad"}}, + {Name: "Wuce Brayne", FavoriteAnimals: []string{"bat", "robin"}}, + }, +} + +type testingT interface { + Helper() + Error(args ...interface{}) +} + +func checkError(t testingT, err error) { + t.Helper() + if err != nil { + t.Error(err) + } +} + +func stdHtmlTemplate(t testingT) func(testingT, io.Writer, benchModel) { + // hatmill does not pretty-print its output, so neither must we here. + const tpl = `{{.Title}}` + + `{{range .Persons}}

{{.Name}}

` + + `` + + `
{{end}}` + compiled, err := template.New("page").Parse(tpl) + checkError(t, err) + return func(t testingT, w io.Writer, model benchModel) { + checkError(t, compiled.Execute(w, model)) + } +} + +func hatmillTemplate(t testingT, w io.Writer, model benchModel) { + var personHtml h.Terms + for _, p := range model.Persons { + var animalHtml h.Terms + for _, a := range p.FavoriteAnimals { + animalHtml = append(animalHtml, he.Li()(h.Text(a))) + } + personHtml = append(personHtml, he.Div()( + he.H1()(h.Text(p.Name)), + he.Ul()(animalHtml), + )) + } + html := he.Html()( + he.Head()( + he.Title()(h.Text(model.Title)), + ), + he.Body()(personHtml), + ) + _, err := h.WriteDocument(w, html) + checkError(t, err) +} + +func TestBenchmarkEquivalence(t *testing.T) { + var stdHtmlOutput bytes.Buffer + stdHtmlTemplate(t)(t, &stdHtmlOutput, benchData) + var hatmillOutput bytes.Buffer + hatmillTemplate(t, &hatmillOutput, benchData) + if stdHtmlOutput.String() != hatmillOutput.String() { + t.Fatalf("Output does not match\nhtml/template: %s\nhatmill: %s", + &stdHtmlOutput, &hatmillOutput) + } +} + +func doBenchmark(b *testing.B, tpl func(testingT, io.Writer, benchModel)) { + var buf bytes.Buffer + for i := 0; i < b.N; i++ { + buf.Reset() + tpl(b, &buf, benchData) + } +} + +func BenchmarkStdHtmlTemplate(b *testing.B) { + doBenchmark(b, stdHtmlTemplate(b)) +} + +func BenchmarkHatmill(b *testing.B) { + doBenchmark(b, hatmillTemplate) +}