Add coverage requirement for url Build functions
This commit is contained in:
parent
a6d931334a
commit
e5beb209c0
|
@ -2,3 +2,4 @@ src/config/config.go
|
||||||
.vscode
|
.vscode
|
||||||
vendor/
|
vendor/
|
||||||
dbclones/
|
dbclones/
|
||||||
|
coverage.out
|
||||||
|
|
|
@ -35,6 +35,14 @@ func TestProjectIndex(t *testing.T) {
|
||||||
AssertRegexMatch(t, BuildProjectIndex(), RegexProjectIndex, nil)
|
AssertRegexMatch(t, BuildProjectIndex(), RegexProjectIndex, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShowcase(t *testing.T) {
|
||||||
|
AssertRegexMatch(t, BuildShowcase(), RegexShowcase, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStreams(t *testing.T) {
|
||||||
|
AssertRegexMatch(t, BuildStreams(), RegexStreams, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSiteMap(t *testing.T) {
|
func TestSiteMap(t *testing.T) {
|
||||||
AssertRegexMatch(t, BuildSiteMap(), RegexSiteMap, nil)
|
AssertRegexMatch(t, BuildSiteMap(), RegexSiteMap, nil)
|
||||||
}
|
}
|
||||||
|
@ -79,6 +87,13 @@ func TestFeed(t *testing.T) {
|
||||||
assert.Panics(t, func() { BuildFeedWithPage(0) })
|
assert.Panics(t, func() { BuildFeedWithPage(0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPodcast(t *testing.T) {
|
||||||
|
AssertRegexMatch(t, BuildPodcast(""), RegexPodcast, nil)
|
||||||
|
AssertSubdomain(t, BuildPodcast(""), "")
|
||||||
|
AssertSubdomain(t, BuildPodcast("hmn"), "")
|
||||||
|
AssertSubdomain(t, BuildPodcast("hero"), "hero")
|
||||||
|
}
|
||||||
|
|
||||||
func TestForumCategory(t *testing.T) {
|
func TestForumCategory(t *testing.T) {
|
||||||
AssertRegexMatch(t, BuildForumCategory("", nil, 1), RegexForumCategory, nil)
|
AssertRegexMatch(t, BuildForumCategory("", nil, 1), RegexForumCategory, nil)
|
||||||
AssertRegexMatch(t, BuildForumCategory("", []string{"wip"}, 2), RegexForumCategory, map[string]string{"cats": "wip", "page": "2"})
|
AssertRegexMatch(t, BuildForumCategory("", []string{"wip"}, 2), RegexForumCategory, map[string]string{"cats": "wip", "page": "2"})
|
||||||
|
@ -329,6 +344,8 @@ func TestMarkRead(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func AssertSubdomain(t *testing.T, fullUrl string, expectedSubdomain string) {
|
func AssertSubdomain(t *testing.T, fullUrl string, expectedSubdomain string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
parsed, err := url.Parse(fullUrl)
|
parsed, err := url.Parse(fullUrl)
|
||||||
ok := assert.Nilf(t, err, "Full url could not be parsed: %s", fullUrl)
|
ok := assert.Nilf(t, err, "Full url could not be parsed: %s", fullUrl)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -344,6 +361,8 @@ func AssertSubdomain(t *testing.T, fullUrl string, expectedSubdomain string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func AssertRegexMatch(t *testing.T, fullUrl string, regex *regexp.Regexp, paramsToVerify map[string]string) {
|
func AssertRegexMatch(t *testing.T, fullUrl string, regex *regexp.Regexp, paramsToVerify map[string]string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
parsed, err := url.Parse(fullUrl)
|
parsed, err := url.Parse(fullUrl)
|
||||||
ok := assert.Nilf(t, err, "Full url could not be parsed: %s", fullUrl)
|
ok := assert.Nilf(t, err, "Full url could not be parsed: %s", fullUrl)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -378,6 +397,8 @@ func AssertRegexMatch(t *testing.T, fullUrl string, regex *regexp.Regexp, params
|
||||||
}
|
}
|
||||||
|
|
||||||
func AssertRegexNoMatch(t *testing.T, fullUrl string, regex *regexp.Regexp) {
|
func AssertRegexNoMatch(t *testing.T, fullUrl string, regex *regexp.Regexp) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
parsed, err := url.Parse(fullUrl)
|
parsed, err := url.Parse(fullUrl)
|
||||||
ok := assert.Nilf(t, err, "Full url could not be parsed: %s", fullUrl)
|
ok := assert.Nilf(t, err, "Full url could not be parsed: %s", fullUrl)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/ansicolor"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
We have these tests in a separate package so that we can run the hmnurl package tests without
|
||||||
|
recursively invoking ourselves.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Test that all hmnurl functions starting with Build are covered by tests.
|
||||||
|
func TestRouteCoverage(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
covFilePath := filepath.Join(tmp, "coverage.out")
|
||||||
|
|
||||||
|
outputAndAssert(t, exec.Command("go", "test", "./..", "-coverprofile="+covFilePath), "failed to run hmnurl tests")
|
||||||
|
coverageOutput := outputAndAssert(t, exec.Command("go", "tool", "cover", "-func="+covFilePath), "failed to run coverage tool")
|
||||||
|
|
||||||
|
coverLineRe := regexp.MustCompile("(?P<name>\\w+)\\t+(?P<percent>[\\d.]+)%$")
|
||||||
|
var uncoveredBuildFuncs []string
|
||||||
|
for i, line := range bytes.Split(coverageOutput, []byte("\n")) {
|
||||||
|
line := string(line)
|
||||||
|
if line == "" || strings.HasPrefix(line, "total") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := coverLineRe.FindStringSubmatch(line)
|
||||||
|
if matches == nil {
|
||||||
|
panic(fmt.Sprintf("line %d of coverage data could not be parsed (\"%s\")", i+1, line))
|
||||||
|
}
|
||||||
|
|
||||||
|
funcName := matches[coverLineRe.SubexpIndex("name")]
|
||||||
|
coverPercentStr := matches[coverLineRe.SubexpIndex("percent")]
|
||||||
|
|
||||||
|
if strings.HasPrefix(funcName, "Build") {
|
||||||
|
coverPercent, err := strconv.ParseFloat(coverPercentStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if coverPercent == 0 {
|
||||||
|
uncoveredBuildFuncs = append(uncoveredBuildFuncs, funcName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(uncoveredBuildFuncs) > 0 {
|
||||||
|
t.Logf("The following url Build functions were not covered by tests:\n")
|
||||||
|
for _, funcName := range uncoveredBuildFuncs {
|
||||||
|
t.Logf("%s\n", funcName)
|
||||||
|
}
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputAndAssert(t *testing.T, cmd *exec.Cmd, args ...interface{}) []byte {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
cmd.Stdout = io.MultiWriter(os.Stdout, &stdout)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
fmt.Println(ansicolor.Gray + ansicolor.Italic + cmd.String() + ansicolor.Reset)
|
||||||
|
|
||||||
|
fmt.Print(ansicolor.Gray)
|
||||||
|
err := cmd.Run()
|
||||||
|
fmt.Print(ansicolor.Reset)
|
||||||
|
assert.Nil(t, err, args...)
|
||||||
|
|
||||||
|
return stdout.Bytes()
|
||||||
|
}
|
|
@ -9,6 +9,11 @@ import (
|
||||||
"git.handmade.network/hmn/hmn/src/oops"
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Any function in this package whose name starts with Build is required to be covered by a test.
|
||||||
|
This helps ensure that we don't generate URLs that can't be routed.
|
||||||
|
*/
|
||||||
|
|
||||||
// TODO(asaf): Make this whole file only crash in Dev
|
// TODO(asaf): Make this whole file only crash in Dev
|
||||||
|
|
||||||
var RegexHomepage = regexp.MustCompile("^/$")
|
var RegexHomepage = regexp.MustCompile("^/$")
|
||||||
|
@ -51,6 +56,8 @@ func BuildAtomFeed() string {
|
||||||
return Url("/atom", nil)
|
return Url("/atom", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QUESTION(ben): Can we change these routes?
|
||||||
|
|
||||||
var RegexLoginAction = regexp.MustCompile("^/login$")
|
var RegexLoginAction = regexp.MustCompile("^/login$")
|
||||||
|
|
||||||
func BuildLoginAction(redirectTo string) string {
|
func BuildLoginAction(redirectTo string) string {
|
||||||
|
@ -163,7 +170,7 @@ func BuildPodcast(projectSlug string) string {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO(asaf): This also matches urls generated by BuildForumThread (/t/ is identified as a cat, and the threadid as a page)
|
// TODO(asaf): This also matches urls generated by BuildForumThread (/t/ is identified as a cat, and the threadid as a page)
|
||||||
// This shouldn't be a problem since we will match Thread before Category in the router, but should be enforce it here?
|
// This shouldn't be a problem since we will match Thread before Category in the router, but should we enforce it here?
|
||||||
var RegexForumCategory = regexp.MustCompile(`^/forums(/(?P<cats>[^\d/]+(/[^\d]+)*))?(/(?P<page>\d+))?$`)
|
var RegexForumCategory = regexp.MustCompile(`^/forums(/(?P<cats>[^\d/]+(/[^\d]+)*))?(/(?P<page>\d+))?$`)
|
||||||
|
|
||||||
func BuildForumCategory(projectSlug string, subforums []string, page int) string {
|
func BuildForumCategory(projectSlug string, subforums []string, page int) string {
|
||||||
|
|
Loading…
Reference in New Issue