<commit_before>// SPDX-License-Identifier: MIT

package mux

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"runtime"
	"strings"
	"testing"
)

func init() {
	for _, api := range apis {
		path := strings.Replace(api.pattern, "}", "", -1)
		api.test = strings.Replace(path, "{", "", -1)
	}

	var srv *Router

	calcMemStats(func() {
		srv = NewRouter("", Lock)
		for _, api := range apis {
			srv.HandleFunc(api.pattern, benchHandler, api.method)
		}
	})
}

func benchHandler(w http.ResponseWriter, r *http.Request) {
	if _, err := w.Write([]byte(r.URL.Path)); err != nil {
		panic(err)
	}
}

func calcMemStats(load func()) {
	stats := &runtime.MemStats{}

	runtime.GC()
	runtime.ReadMemStats(stats)
	before := stats.HeapAlloc

	load()

	runtime.ReadMemStats(stats)
	after := stats.HeapAlloc
	fmt.Printf("%d Bytes\n", after-before)
}

// 动态添加和删除路由
func BenchmarkAdd_Serve(b *testing.B) {
	b.ReportAllocs()
	b.ResetTimer()

	router := NewRouter("", Lock)
	for i := 0; i < b.N; i++ {
		api := apis[i%len(apis)]

		w := httptest.NewRecorder()
		r := httptest.NewRequest(api.method, api.test, nil)

		router.HandleFunc(api.pattern, benchHandler, api.method)
		router.ServeHTTP(w, r)
		router.Remove(api.pattern, api.method)

		if w.Body.String() != r.URL.Path {
			b.Errorf("%s:%s", w.Body.String(), r.URL.Path)
		}
	}
}

// 对一组添加完成的路由进行路由操作
func BenchmarkServe(b *testing.B) {
	router := NewRouter("")
	for _, api := range apis {
		router.HandleFunc(api.pattern, benchHandler, api.method)
	}

	b.ReportAllocs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		api := apis[i%len(apis)]

		w := httptest.NewRecorder()
		r := httptest.NewRequest(api.method, api.test, nil)
		router.ServeHTTP(w, r)

		if w.Body.String() != r.URL.Path {
			b.Errorf("%s:%s", w.Body.String(), r.URL.Path)
		}
	}
}

type api struct {
	method  string
	pattern string // {xx} 风格的路由项
	test    string // 测试地址
}

// 数据来源 github.com 的接口定义
var apis = []*api{
	{method: http.MethodGet, pattern: "/events"},
	{method: http.MethodGet, pattern: "/repos/{owner}/{repo}/events"},
	{method: http.MethodGet, pattern: "/networks/{owner}/{repo}/events"},
	{method: http.MethodGet, pattern: "/orgs/{org}/events"},
	{method: http.MethodGet, pattern: "/users/{username}/received_events"},
	{method: http.MethodGet, pattern: "/users/{username}/received_events/public"},
	{method: http.MethodGet, pattern: "/users/{username}/events"},
	{method: http.MethodGet, pattern: "/users/{username}/events/public"},<commit_msg>test: 添加对 URL 的性能测试<commit_after>// SPDX-License-Identifier: MIT

package mux

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"runtime"
	"strings"
	"testing"

	"github.com/issue9/assert"
)

func init() {
	for _, api := range apis {
		ps := make(map[string]string, 5)
		p := api.pattern
		for {
			start := strings.IndexByte(p, '{')
			end := strings.IndexByte(p, '}')
			if start == -1 {
				break
			}
			if end <= start {
				panic("无效的路由项" + api.pattern)
			}
			k := p[start+1 : end]
			ps[k] = k

			if end+1 == len(p) {
				break
			}
			p = p[end+1:]
		}

		path := strings.Replace(api.pattern, "}", "", -1)
		api.test = strings.Replace(path, "{", "", -1)
		api.ps = ps
	}

	var srv *Router

	calcMemStats(func() {
		srv = NewRouter("", Lock)
		for _, api := range apis {
			srv.HandleFunc(api.pattern, benchHandler, api.method)
		}
	})
}

func benchHandler(w http.ResponseWriter, r *http.Request) {
	if _, err := w.Write([]byte(r.URL.Path)); err != nil {
		panic(err)
	}
}

func calcMemStats(load func()) {
	stats := &runtime.MemStats{}

	runtime.GC()
	runtime.ReadMemStats(stats)
	before := stats.HeapAlloc

	load()

	runtime.ReadMemStats(stats)
	after := stats.HeapAlloc
	fmt.Printf("%d Bytes\n", after-before)
}

func BenchmarkURL(b *testing.B) {
	a := assert.New(b)

	router := NewRouter("", Lock)
	for _, api := range apis {
		router.HandleFunc(api.pattern, benchHandler, api.method)
	}

	b.ReportAllocs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		api := apis[i%len(apis)]

		url, err := URL(api.pattern, api.ps)
		a.NotError(err).Equal(url, api.test)
	}
}

func BenchmarkRouter_URL(b *testing.B) {
	a := assert.New(b)
	domain := "https://github.com"

	router := NewRouter("", Lock, URLDomain(domain))
	for _, api := range apis {
		router.HandleFunc(api.pattern, benchHandler, api.method)
	}

	b.ReportAllocs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		api := apis[i%len(apis)]

		url, err := router.URL(true, api.pattern, api.ps)
		a.NotError(err).Equal(url, domain+api.test)
	}
}

// 动态添加和删除路由
func BenchmarkRouter_AddAndServeHTTP(b *testing.B) {
	router := NewRouter("", Lock)

	b.ReportAllocs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		api := apis[i%len(apis)]

		w := httptest.NewRecorder()
		r := httptest.NewRequest(api.method, api.test, nil)

		router.HandleFunc(api.pattern, benchHandler, api.method)
		router.ServeHTTP(w, r)
		router.Remove(api.pattern, api.method)

		if w.Body.String() != r.URL.Path {
			b.Errorf("%s:%s", w.Body.String(), r.URL.Path)
		}
	}
}

// 对一组添加完成的路由进行路由操作
func BenchmarkRouter_ServeHTTP(b *testing.B) {
	router := NewRouter("")
	for _, api := range apis {
		router.HandleFunc(api.pattern, benchHandler, api.method)
	}

	b.ReportAllocs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		api := apis[i%len(apis)]

		w := httptest.NewRecorder()
		r := httptest.NewRequest(api.method, api.test, nil)
		router.ServeHTTP(w, r)

		if w.Body.String() != r.URL.Path {
			b.Errorf("%s:%s", w.Body.String(), r.URL.Path)
		}
	}
}

type api struct {
	method  string
	pattern string // {xx} 风格的路由项
	test    string // 测试地址
	ps      map[string]string
}

// 数据来源 github.com 的接口定义
var apis = []*api{
	{method: http.MethodGet, pattern: "/events"},
	{method: http.MethodGet, pattern: "/repos/{owner}/{repo}/events"},
	{method: http.MethodGet, pattern: "/networks/{owner}/{repo}/events"},
	{method: http.MethodGet, pattern: "/orgs/{org}/events"},
	{method: http.MethodGet, pattern: "/users/{username}/received_events"},
	{method: http.MethodGet, pattern: "/users/{username}/received_events/public"},
	{method: http.MethodGet, pattern: "/users/{username}/events"},
	{method: http.MethodGet, pattern: "/users/{username}/events/public"},
{'fraction_non_alphanumeric': 0.19095640858815874, 'fraction_numerical': 0.002602472348731295, 'mean_word_length': 3.719109746738296, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 3, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2792584', 'n_tokens_mistral': 2594, 'n_tokens_neox': 2470, 'n_words': 479}